diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index d56db7aace..ed1f293c06 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index e865ab0035..dd566bc40b 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -37,7 +37,6 @@ #include #include #include -#include #include #include diff --git a/cmake/externals/glm/CMakeLists.txt b/cmake/externals/glm/CMakeLists.txt index 6e5b1ef870..7825e2c117 100644 --- a/cmake/externals/glm/CMakeLists.txt +++ b/cmake/externals/glm/CMakeLists.txt @@ -3,7 +3,7 @@ set(EXTERNAL_NAME glm) include(ExternalProject) ExternalProject_Add( ${EXTERNAL_NAME} - URL http://pkgs.fedoraproject.org/repo/pkgs/glm/glm-0.9.5.4.zip/fab76fc982b256b46208e5c750ed456a/glm-0.9.5.4.zip + URL http://hifi-public.s3.amazonaws.com/dependencies/glm-0.9.5.4.zip URL_MD5 fab76fc982b256b46208e5c750ed456a BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index d02ad73b47..7af9ffd85c 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -267,10 +267,6 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { connect(nodeList.data(), &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded); connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &DomainServer::nodeKilled); - QTimer* silentNodeTimer = new QTimer(this); - connect(silentNodeTimer, SIGNAL(timeout()), nodeList.data(), SLOT(removeSilentNodes())); - silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS); - connect(&nodeList->getNodeSocket(), SIGNAL(readyRead()), SLOT(readAvailableDatagrams())); // add whatever static assignments that have been parsed to the queue diff --git a/examples/defaultScripts.js b/examples/defaultScripts.js index d4efad9ee5..6b57bf18dd 100644 --- a/examples/defaultScripts.js +++ b/examples/defaultScripts.js @@ -9,7 +9,6 @@ // Script.load("progress.js"); -Script.load("lookWithTouch.js"); Script.load("editEntities.js"); Script.load("selectAudioDevice.js"); Script.load("controllers/hydra/hydraMove.js"); @@ -17,5 +16,5 @@ Script.load("headMove.js"); Script.load("inspect.js"); Script.load("lobby.js"); Script.load("notifications.js"); -Script.load("lookWithMouse.js"); +Script.load("look.js"); Script.load("users.js"); diff --git a/examples/editEntities.js b/examples/editEntities.js index faef875d9b..c236336266 100644 --- a/examples/editEntities.js +++ b/examples/editEntities.js @@ -95,6 +95,21 @@ var isActive = false; var placingEntityID = null; +IMPORTING_SVO_OVERLAY_WIDTH = 130; +IMPORTING_SVO_OVERLAY_HEIGHT = 30; +IMPORTING_SVO_OVERLAY_MARGIN = 6; +var importingSVOOverlay = Overlays.addOverlay("text", { + font: { size: 14 }, + text: "Importing SVO...", + x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH - IMPORTING_SVO_OVERLAY_MARGIN, + y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT - IMPORTING_SVO_OVERLAY_MARGIN, + width: IMPORTING_SVO_OVERLAY_WIDTH, + height: IMPORTING_SVO_OVERLAY_HEIGHT, + backgroundColor: { red: 80, green: 80, blue: 80 }, + backgroundAlpha: 0.7, + visible: false, +}); + var toolBar = (function () { var that = {}, toolBar, @@ -753,6 +768,8 @@ Script.scriptEnding.connect(function() { tooltip.cleanup(); selectionDisplay.cleanup(); Entities.setLightsArePickable(originalLightsArePickable); + + Overlays.deleteOverlay(importingSVOOverlay); }); // Do some stuff regularly, like check for placement of various overlays @@ -816,24 +833,7 @@ function handeMenuEvent(menuItem) { } if (importURL) { - var success = Clipboard.importEntities(importURL); - - if (success) { - var distance = cameraManager.enabled ? cameraManager.zoomDistance : DEFAULT_ENTITY_DRAG_DROP_DISTANCE; - var direction = Quat.getFront(Camera.orientation); - var offset = Vec3.multiply(distance, direction); - var position = Vec3.sum(Camera.position, offset); - - position.x = Math.max(0, position.x); - position.y = Math.max(0, position.y); - position.z = Math.max(0, position.z); - - var pastedEntityIDs = Clipboard.pasteEntities(position); - - selectionManager.setSelections(pastedEntityIDs); - } else { - Window.alert("There was an error importing the entity file."); - } + importSVO(importURL); } } else if (menuItem == "Entity List...") { entityListTool.toggleVisible(); @@ -841,6 +841,34 @@ function handeMenuEvent(menuItem) { tooltip.show(false); } +function importSVO(importURL) { + Overlays.editOverlay(importingSVOOverlay, { visible: true }); + + var success = Clipboard.importEntities(importURL); + + if (success) { + var distance = cameraManager.enabled ? cameraManager.zoomDistance : DEFAULT_ENTITY_DRAG_DROP_DISTANCE; + var direction = Quat.getFront(Camera.orientation); + var offset = Vec3.multiply(distance, direction); + var position = Vec3.sum(Camera.position, offset); + + position.x = Math.max(0, position.x); + position.y = Math.max(0, position.y); + position.z = Math.max(0, position.z); + + var pastedEntityIDs = Clipboard.pasteEntities(position); + + if (isActive) { + selectionManager.setSelections(pastedEntityIDs); + } + } else { + Window.alert("There was an error importing the entity file."); + } + + Overlays.editOverlay(importingSVOOverlay, { visible: false }); +} +Window.svoImportRequested.connect(importSVO); + Menu.menuItemEvent.connect(handeMenuEvent); Controller.keyPressEvent.connect(function(event) { diff --git a/examples/lookWithMouse.js b/examples/lookWithMouse.js deleted file mode 100644 index 470a56bd63..0000000000 --- a/examples/lookWithMouse.js +++ /dev/null @@ -1,88 +0,0 @@ -// -// lookWithMouse.js -// examples -// -// Created by Brad Hefta-Gaub on 1/28/14. -// Copyright 2014 High Fidelity, Inc. -// -// This is an example script that demonstrates use of the Controller class -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -var alwaysLook = false; // if you want the mouse look to happen only when you click, change this to false -var isMouseDown = false; -var lastX = 0; -var lastY = 0; -var yawFromMouse = 0; -var pitchFromMouse = 0; -var wantDebugging = false; - -function mousePressEvent(event) { - if (wantDebugging) { - print("mousePressEvent event.x,y=" + event.x + ", " + event.y); - } - isMouseDown = true; - lastX = event.x; - lastY = event.y; -} - -function mouseReleaseEvent(event) { - if (wantDebugging) { - print("mouseReleaseEvent event.x,y=" + event.x + ", " + event.y); - } - isMouseDown = false; -} - -function mouseMoveEvent(event) { - if (wantDebugging) { - print("mouseMoveEvent event.x,y=" + event.x + ", " + event.y); - } - - if (alwaysLook || isMouseDown) { - var MOUSE_YAW_SCALE = -0.25; - var MOUSE_PITCH_SCALE = -12.5; - var FIXED_MOUSE_TIMESTEP = 0.016; - yawFromMouse += ((event.x - lastX) * MOUSE_YAW_SCALE * FIXED_MOUSE_TIMESTEP); - pitchFromMouse += ((event.y - lastY) * MOUSE_PITCH_SCALE * FIXED_MOUSE_TIMESTEP); - lastX = event.x; - lastY = event.y; - } -} - -function update(deltaTime) { - if (wantDebugging) { - print("update()..."); - } - // rotate body yaw for yaw received from mouse - var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Radians( { x: 0, y: yawFromMouse, z: 0 } )); - if (wantDebugging) { - print("changing orientation" - + " [old]MyAvatar.orientation="+MyAvatar.orientation.x + "," + MyAvatar.orientation.y + "," - + MyAvatar.orientation.z + "," + MyAvatar.orientation.w - + " newOrientation="+newOrientation.x + "," + newOrientation.y + "," + newOrientation.z + "," + newOrientation.w); - } - MyAvatar.orientation = newOrientation; - yawFromMouse = 0; - - // apply pitch from mouse - var newPitch = MyAvatar.headPitch + pitchFromMouse; - if (wantDebugging) { - print("changing pitch [old]MyAvatar.headPitch="+MyAvatar.headPitch+ " newPitch="+newPitch); - } - MyAvatar.headPitch = newPitch; - pitchFromMouse = 0; -} - -// Map the mouse events to our functions -Controller.mousePressEvent.connect(mousePressEvent); -Controller.mouseMoveEvent.connect(mouseMoveEvent); -Controller.mouseReleaseEvent.connect(mouseReleaseEvent); - -MyAvatar.bodyYaw = 0; -MyAvatar.bodyPitch = 0; -MyAvatar.bodyRoll = 0; - -// would be nice to change to update -Script.update.connect(update); diff --git a/examples/lookWithTouch.js b/examples/lookWithTouch.js deleted file mode 100644 index e9e7b0735a..0000000000 --- a/examples/lookWithTouch.js +++ /dev/null @@ -1,96 +0,0 @@ -// -// lookWithTouch.js -// examples -// -// Created by Brad Hefta-Gaub on 1/28/14. -// Copyright 2014 High Fidelity, Inc. -// -// This is an example script that demonstrates use of the Controller class -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -var startedTouching = false; -var lastX = 0; -var lastY = 0; -var yawFromMouse = 0; -var pitchFromMouse = 0; -var wantDebugging = false; - -function touchBeginEvent(event) { - if (wantDebugging) { - print("touchBeginEvent event.x,y=" + event.x + ", " + event.y); - } - lastX = event.x; - lastY = event.y; - startedTouching = true; -} - -function touchEndEvent(event) { - if (wantDebugging) { - print("touchEndEvent event.x,y=" + event.x + ", " + event.y); - } - startedTouching = false; -} - -function touchUpdateEvent(event) { - if (wantDebugging) { - print("touchUpdateEvent event.x,y=" + event.x + ", " + event.y); - } - - if (!startedTouching) { - // handle Qt 5.4.x bug where we get touch update without a touch begin event - startedTouching = true; - lastX = event.x; - lastY = event.y; - } - - var MOUSE_YAW_SCALE = -0.25; - var MOUSE_PITCH_SCALE = -12.5; - var FIXED_MOUSE_TIMESTEP = 0.016; - yawFromMouse += ((event.x - lastX) * MOUSE_YAW_SCALE * FIXED_MOUSE_TIMESTEP); - pitchFromMouse += ((event.y - lastY) * MOUSE_PITCH_SCALE * FIXED_MOUSE_TIMESTEP); - lastX = event.x; - lastY = event.y; -} - -function update(deltaTime) { - if (startedTouching) { - // rotate body yaw for yaw received from mouse - var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromPitchYawRollRadians(0, yawFromMouse, 0)); - if (wantDebugging) { - print("changing orientation" - + " [old]MyAvatar.orientation="+MyAvatar.orientation.x + "," + MyAvatar.orientation.y + "," - + MyAvatar.orientation.z + "," + MyAvatar.orientation.w - + " newOrientation="+newOrientation.x + "," + newOrientation.y + "," + newOrientation.z + "," + newOrientation.w); - } - MyAvatar.orientation = newOrientation; - yawFromMouse = 0; - - // apply pitch from mouse - var newPitch = MyAvatar.headPitch + pitchFromMouse; - if (wantDebugging) { - print("changing pitch [old]MyAvatar.headPitch="+MyAvatar.headPitch+ " newPitch="+newPitch); - } - MyAvatar.headPitch = newPitch; - pitchFromMouse = 0; - } -} - -// Map the mouse events to our functions -Controller.touchBeginEvent.connect(touchBeginEvent); -Controller.touchUpdateEvent.connect(touchUpdateEvent); -Controller.touchEndEvent.connect(touchEndEvent); - -// disable the standard application for mouse events -Controller.captureTouchEvents(); - -function scriptEnding() { - // re-enabled the standard application for mouse events - Controller.releaseTouchEvents(); -} - -// would be nice to change to update -Script.update.connect(update); -Script.scriptEnding.connect(scriptEnding); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9979c54f11..437ef3374c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -91,7 +91,7 @@ #include "InterfaceVersion.h" #include "LODManager.h" #include "Menu.h" -#include "ModelUploader.h" +#include "ModelPackager.h" #include "Util.h" #include "avatar/AvatarManager.h" @@ -146,7 +146,6 @@ const qint64 MAXIMUM_CACHE_SIZE = 10 * BYTES_PER_GIGABYTES; // 10GB static QTimer* locationUpdateTimer = NULL; static QTimer* balanceUpdateTimer = NULL; -static QTimer* silentNodeTimer = NULL; static QTimer* identityPacketTimer = NULL; static QTimer* billboardPacketTimer = NULL; static QTimer* checkFPStimer = NULL; @@ -244,6 +243,7 @@ bool setupEssentials(int& argc, char** argv) { auto bandwidthRecorder = DependencyManager::set(); auto resouceCacheSharedItems = DependencyManager::set(); auto entityScriptingInterface = DependencyManager::set(); + auto windowScriptingInterface = DependencyManager::set(); #if defined(Q_OS_MAC) || defined(Q_OS_WIN) auto speechRecognizer = DependencyManager::set(); #endif @@ -257,7 +257,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _dependencyManagerIsSetup(setupEssentials(argc, argv)), _window(new MainWindow(desktop())), _toolWindow(NULL), - _nodeThread(new QThread(this)), _datagramProcessor(), _undoStack(), _undoStackScriptingInterface(&_undoStack), @@ -328,18 +327,25 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _runningScriptsWidget = new RunningScriptsWidget(_window); // start the nodeThread so its event loop is running - _nodeThread->setObjectName("Datagram Processor Thread"); - _nodeThread->start(); + QThread* nodeThread = new QThread(this); + nodeThread->setObjectName("Datagram Processor Thread"); + nodeThread->start(); // make sure the node thread is given highest priority - _nodeThread->setPriority(QThread::TimeCriticalPriority); + nodeThread->setPriority(QThread::TimeCriticalPriority); + + _datagramProcessor = new DatagramProcessor(nodeList.data()); + + // have the NodeList use deleteLater from DM customDeleter + nodeList->setCustomDeleter([](Dependency* dependency) { + static_cast(dependency)->deleteLater(); + }); // put the NodeList and datagram processing on the node thread - nodeList->moveToThread(_nodeThread); - _datagramProcessor.moveToThread(_nodeThread); + nodeList->moveToThread(nodeThread); // connect the DataProcessor processDatagrams slot to the QUDPSocket readyRead() signal - connect(&nodeList->getNodeSocket(), SIGNAL(readyRead()), &_datagramProcessor, SLOT(processDatagrams())); + connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, _datagramProcessor, &DatagramProcessor::processDatagrams); // put the audio processing on a separate thread QThread* audioThread = new QThread(); @@ -426,12 +432,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // connect to the packet sent signal of the _entityEditSender connect(&_entityEditSender, &EntityEditPacketSender::packetSent, this, &Application::packetSent); - // move the silentNodeTimer to the _nodeThread - silentNodeTimer = new QTimer(); - connect(silentNodeTimer, SIGNAL(timeout()), nodeList.data(), SLOT(removeSilentNodes())); - silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS); - silentNodeTimer->moveToThread(_nodeThread); - // send the identity packet for our avatar each second to our avatar mixer identityPacketTimer = new QTimer(); connect(identityPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendIdentityPacket); @@ -546,7 +546,7 @@ void Application::aboutToQuit() { } void Application::cleanupBeforeQuit() { - _datagramProcessor.shutdown(); // tell the datagram processor we're shutting down, so it can short circuit + _datagramProcessor->shutdown(); // tell the datagram processor we're shutting down, so it can short circuit _entities.shutdown(); // tell the entities system we're shutting down, so it will stop running scripts ScriptEngine::stopAllScripts(this); // stop all currently running global scripts @@ -554,7 +554,6 @@ void Application::cleanupBeforeQuit() { // depending on what thread they run in locationUpdateTimer->stop(); balanceUpdateTimer->stop(); - QMetaObject::invokeMethod(silentNodeTimer, "stop", Qt::BlockingQueuedConnection); identityPacketTimer->stop(); billboardPacketTimer->stop(); checkFPStimer->stop(); @@ -564,7 +563,6 @@ void Application::cleanupBeforeQuit() { // and then delete those that got created by "new" delete locationUpdateTimer; delete balanceUpdateTimer; - delete silentNodeTimer; delete identityPacketTimer; delete billboardPacketTimer; delete checkFPStimer; @@ -596,10 +594,6 @@ Application::~Application() { tree->lockForWrite(); _entities.getTree()->setSimulation(NULL); tree->unlock(); - - // ask the datagram processing thread to quit and wait until it is done - _nodeThread->quit(); - _nodeThread->wait(); _octreeProcessor.terminate(); _entityEditSender.terminate(); @@ -612,12 +606,20 @@ Application::~Application() { // stop the glWidget frame timer so it doesn't call paintGL _glWidget->stopFrameTimer(); - + + DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); //DependencyManager::destroy(); DependencyManager::destroy(); + + QThread* nodeThread = DependencyManager::get()->thread(); + DependencyManager::destroy(); + + // ask the node thread to quit and wait until it is done + nodeThread->quit(); + nodeThread->wait(); qInstallMessageHandler(NULL); // NOTE: Do this as late as possible so we continue to get our log messages } @@ -881,9 +883,15 @@ bool Application::event(QEvent* event) { if (event->type() == QEvent::FileOpen) { QFileOpenEvent* fileEvent = static_cast(event); + + QUrl url = fileEvent->url(); - if (!fileEvent->url().isEmpty()) { - DependencyManager::get()->handleLookupString(fileEvent->url().toString()); + if (!url.isEmpty()) { + if (url.scheme() == HIFI_URL_SCHEME) { + DependencyManager::get()->handleLookupString(fileEvent->url().toString()); + } else if (url.url().toLower().endsWith(SVO_EXTENSION)) { + emit svoImportRequested(url.url()); + } } return false; @@ -1450,6 +1458,10 @@ void Application::dropEvent(QDropEvent *event) { if (url.url().toLower().endsWith(SNAPSHOT_EXTENSION)) { snapshotPath = url.toLocalFile(); break; + } else if (url.url().toLower().endsWith(SVO_EXTENSION)) { + emit svoImportRequested(url.url()); + event->acceptProposedAction(); + return; } } @@ -1486,7 +1498,7 @@ void Application::checkFPS() { _fps = (float)_frameCount / diffTime; _frameCount = 0; - _datagramProcessor.resetCounters(); + _datagramProcessor->resetCounters(); _timerStart.start(); // ask the node list to check in with the domain server @@ -1806,6 +1818,9 @@ void Application::initDisplay() { } void Application::init() { + // Make sure Login state is up to date + DependencyManager::get()->toggleLoginDialog(); + _environment.init(); DependencyManager::get()->init(this); @@ -2724,7 +2739,6 @@ const GLfloat WORLD_DIFFUSE_COLOR[] = { 0.6f, 0.525f, 0.525f }; const GLfloat WORLD_SPECULAR_COLOR[] = { 0.94f, 0.94f, 0.737f, 1.0f }; const glm::vec3 GLOBAL_LIGHT_COLOR = { 0.6f, 0.525f, 0.525f }; -const float GLOBAL_LIGHT_INTENSITY = 1.0f; void Application::setupWorldLight() { @@ -3527,7 +3541,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri qScriptRegisterMetaType(scriptEngine, RayToOverlayIntersectionResultToScriptValue, RayToOverlayIntersectionResultFromScriptValue); - QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", WindowScriptingInterface::getInstance()); + QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", DependencyManager::get().data()); scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, LocationScriptingInterface::locationSetter, windowValue); // register `location` on the global object. @@ -3715,20 +3729,8 @@ void Application::toggleRunningScriptsWidget() { } } -void Application::uploadHead() { - ModelUploader::uploadHead(); -} - -void Application::uploadSkeleton() { - ModelUploader::uploadSkeleton(); -} - -void Application::uploadAttachment() { - ModelUploader::uploadAttachment(); -} - -void Application::uploadEntity() { - ModelUploader::uploadEntity(); +void Application::packageModel() { + ModelPackager::package(); } void Application::openUrl(const QUrl& url) { diff --git a/interface/src/Application.h b/interface/src/Application.h index d8d9132de9..248aaa0f6a 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -94,6 +94,7 @@ static const float NODE_KILLED_GREEN = 0.0f; static const float NODE_KILLED_BLUE = 0.0f; static const QString SNAPSHOT_EXTENSION = ".jpg"; +static const QString SVO_EXTENSION = ".svo"; static const float BILLBOARD_FIELD_OF_VIEW = 30.0f; // degrees static const float BILLBOARD_DISTANCE = 5.56f; // meters @@ -315,6 +316,8 @@ signals: void scriptLocationChanged(const QString& newPath); + void svoImportRequested(const QString& url); + public slots: void domainChanged(const QString& domainHostname); void updateWindowTitle(); @@ -340,11 +343,8 @@ public slots: void loadDefaultScripts(); void toggleRunningScriptsWidget(); void saveScripts(); - - void uploadHead(); - void uploadSkeleton(); - void uploadAttachment(); - void uploadEntity(); + + void packageModel(); void openUrl(const QUrl& url); @@ -445,10 +445,8 @@ private: MainWindow* _window; ToolWindow* _toolWindow; - - - QThread* _nodeThread; - DatagramProcessor _datagramProcessor; + + DatagramProcessor* _datagramProcessor; QUndoStack _undoStack; UndoStackScriptingInterface _undoStackScriptingInterface; diff --git a/interface/src/GLCanvas.cpp b/interface/src/GLCanvas.cpp index b72c00c779..4ece8f0857 100644 --- a/interface/src/GLCanvas.cpp +++ b/interface/src/GLCanvas.cpp @@ -170,7 +170,8 @@ void GLCanvas::wheelEvent(QWheelEvent* event) { void GLCanvas::dragEnterEvent(QDragEnterEvent* event) { const QMimeData *mimeData = event->mimeData(); foreach (QUrl url, mimeData->urls()) { - if (url.url().toLower().endsWith(SNAPSHOT_EXTENSION)) { + auto lower = url.url().toLower(); + if (lower.endsWith(SNAPSHOT_EXTENSION) || lower.endsWith(SVO_EXTENSION)) { event->acceptProposedAction(); break; } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index ad03062a89..cdd0d9bf90 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -105,16 +105,6 @@ Menu::Menu() { addActionToQMenuAndActionHash(fileMenu, MenuOption::CopyPath, 0, addressManager.data(), SLOT(copyPath())); - addDisabledActionAndSeparator(fileMenu, "Upload Avatar Model"); - addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadHead, 0, - qApp, SLOT(uploadHead())); - addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadSkeleton, 0, - qApp, SLOT(uploadSkeleton())); - addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadAttachment, 0, - qApp, SLOT(uploadAttachment())); - addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadEntity, 0, - qApp, SLOT(uploadEntity())); - addActionToQMenuAndActionHash(fileMenu, MenuOption::Quit, Qt::CTRL | Qt::Key_Q, @@ -180,6 +170,9 @@ Menu::Menu() { Qt::Key_Apostrophe, qApp, SLOT(resetSensors())); + + addActionToQMenuAndActionHash(toolsMenu, MenuOption::PackageModel, 0, + qApp, SLOT(packageModel())); QMenu* avatarMenu = addMenu("Avatar"); QObject* avatar = DependencyManager::get()->getMyAvatar(); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index ce8102443c..fc1347fa27 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -251,10 +251,7 @@ namespace MenuOption { const QString ToolWindow = "Tool Window"; const QString TransmitterDrive = "Transmitter Drive"; const QString TurnWithHead = "Turn using Head"; - const QString UploadAttachment = "Upload Attachment Model"; - const QString UploadEntity = "Upload Entity Model"; - const QString UploadHead = "Upload Head Model"; - const QString UploadSkeleton = "Upload Skeleton Model"; + const QString PackageModel = "Package Model"; const QString UserInterface = "User Interface"; const QString Visage = "Visage"; const QString Wireframe = "Wireframe"; diff --git a/interface/src/ModelPackager.cpp b/interface/src/ModelPackager.cpp new file mode 100644 index 0000000000..49d4ae566f --- /dev/null +++ b/interface/src/ModelPackager.cpp @@ -0,0 +1,397 @@ +// +// ModelPackager.cpp +// +// +// Created by Clement on 3/9/15. +// Copyright 2015 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 + +#include "ModelSelector.h" +#include "ModelPropertiesDialog.h" + +#include "ModelPackager.h" + +static const int MAX_TEXTURE_SIZE = 1024; + +void copyDirectoryContent(QDir& from, QDir& to) { + for (auto entry : from.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | + QDir::NoSymLinks | QDir::Readable)) { + if (entry.isDir()) { + to.mkdir(entry.fileName()); + from.cd(entry.fileName()); + to.cd(entry.fileName()); + copyDirectoryContent(from, to); + from.cdUp(); + to.cdUp(); + } else { // Files + QFile file(entry.absoluteFilePath()); + QString newPath = to.absolutePath() + "/" + entry.fileName(); + if (to.exists(entry.fileName())) { + QFile overridenFile(newPath); + overridenFile.remove(); + } + file.copy(newPath); + } + } +} + +bool ModelPackager::package() { + ModelPackager packager; + if (!packager.selectModel()) { + return false; + } + if (!packager.loadModel()) { + return false; + } + if (!packager.editProperties()) { + return false; + } + if (!packager.zipModel()) { + return false; + } + return true; +} + +bool ModelPackager::selectModel() { + ModelSelector selector; + if(selector.exec() == QDialog::Accepted) { + _modelFile = selector.getFileInfo(); + _modelType = selector.getModelType(); + return true; + } + return false; +} + +bool ModelPackager::loadModel() { + // First we check the FST file (if any) + if (_modelFile.completeSuffix().contains("fst")) { + QFile fst(_modelFile.filePath()); + if (!fst.open(QFile::ReadOnly | QFile::Text)) { + QMessageBox::warning(NULL, + QString("ModelPackager::loadModel()"), + QString("Could not open FST file %1").arg(_modelFile.filePath()), + QMessageBox::Ok); + qWarning() << QString("ModelPackager::loadModel(): Could not open FST file %1").arg(_modelFile.filePath()); + return false; + } + qDebug() << "Reading FST file : " << _modelFile.filePath(); + _mapping = readMapping(fst.readAll()); + fst.close(); + + _fbxInfo = QFileInfo(_modelFile.path() + "/" + _mapping.value(FILENAME_FIELD).toString()); + } else { + _fbxInfo = QFileInfo(_modelFile.filePath()); + } + + // open the fbx file + QFile fbx(_fbxInfo.filePath()); + if (!_fbxInfo.exists() || !_fbxInfo.isFile() || !fbx.open(QIODevice::ReadOnly)) { + QMessageBox::warning(NULL, + QString("ModelPackager::loadModel()"), + QString("Could not open FBX file %1").arg(_fbxInfo.filePath()), + QMessageBox::Ok); + qWarning() << QString("ModelPackager::loadModel(): Could not open FBX file %1").arg(_fbxInfo.filePath()); + return false; + } + qDebug() << "Reading FBX file : " << _fbxInfo.filePath(); + QByteArray fbxContents = fbx.readAll(); + _geometry = readFBX(fbxContents, QVariantHash()); + + // make sure we have some basic mappings + populateBasicMapping(_mapping, _fbxInfo.filePath(), _geometry); + return true; +} + +bool ModelPackager::editProperties() { + // open the dialog to configure the rest + ModelPropertiesDialog properties(_modelType, _mapping, _modelFile.path(), _geometry); + if (properties.exec() == QDialog::Rejected) { + return false; + } + _mapping = properties.getMapping(); + + // Make sure that a mapping for the root joint has been specified + QVariantHash joints = _mapping.value(JOINT_FIELD).toHash(); + if (!joints.contains("jointRoot")) { + qWarning() << QString("%1 root joint not configured for skeleton.").arg(_modelFile.fileName()); + + QString message = "Your did not configure a root joint for your skeleton model.\n\nThe upload will be canceled."; + QMessageBox msgBox; + msgBox.setWindowTitle("Model Upload"); + msgBox.setText(message); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + + return false; + } + + return true; +} + +bool ModelPackager::zipModel() { + QTemporaryDir dir; + dir.setAutoRemove(true); + QDir tempDir(dir.path()); + + QByteArray nameField = _mapping.value(NAME_FIELD).toByteArray(); + tempDir.mkpath(nameField + "/textures"); + QDir fbxDir(tempDir.path() + "/" + nameField); + QDir texDir(fbxDir.path() + "/textures"); + + // Copy textures + listTextures(); + if (!_textures.empty()) { + QByteArray texdirField = _mapping.value(TEXDIR_FIELD).toByteArray(); + _texDir = _modelFile.path() + "/" + texdirField; + copyTextures(_texDir, texDir); + } + + // Copy LODs + QVariantHash lodField = _mapping.value(LOD_FIELD).toHash(); + if (!lodField.empty()) { + for (auto it = lodField.constBegin(); it != lodField.constEnd(); ++it) { + QString oldPath = _modelFile.path() + "/" + it.key(); + QFile lod(oldPath); + QString newPath = fbxDir.path() + "/" + QFileInfo(lod).fileName(); + if (lod.exists()) { + lod.copy(newPath); + } + } + } + + // Copy FBX + QFile fbx(_fbxInfo.filePath()); + QByteArray filenameField = _mapping.value(FILENAME_FIELD).toByteArray(); + QString newPath = fbxDir.path() + "/" + QFileInfo(filenameField).fileName(); + fbx.copy(newPath); + + // Correct FST + _mapping[FILENAME_FIELD] = tempDir.relativeFilePath(newPath); + _mapping[TEXDIR_FIELD] = tempDir.relativeFilePath(texDir.path()); + + // Copy FST + QFile fst(tempDir.path() + "/" + nameField + ".fst"); + if (fst.open(QIODevice::WriteOnly)) { + fst.write(writeMapping(_mapping)); + fst.close(); + } else { + qDebug() << "Couldn't write FST file" << fst.fileName(); + return false; + } + + + QString saveDirPath = QFileDialog::getExistingDirectory(nullptr, "Save Model", + "", QFileDialog::ShowDirsOnly); + if (saveDirPath.isEmpty()) { + qDebug() << "Invalid directory" << saveDirPath; + return false; + } + + QDir saveDir(saveDirPath); + copyDirectoryContent(tempDir, saveDir); + return true; +} + +void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename, const FBXGeometry& geometry) { + if (!mapping.contains(NAME_FIELD)) { + mapping.insert(NAME_FIELD, QFileInfo(filename).baseName()); + } + + if (!mapping.contains(FILENAME_FIELD)) { + QDir root(_modelFile.path()); + mapping.insert(FILENAME_FIELD, root.relativeFilePath(filename)); + } + if (!mapping.contains(TEXDIR_FIELD)) { + mapping.insert(TEXDIR_FIELD, "."); + } + + // mixamo/autodesk defaults + if (!mapping.contains(SCALE_FIELD)) { + mapping.insert(SCALE_FIELD, 1.0); + } + QVariantHash joints = mapping.value(JOINT_FIELD).toHash(); + if (!joints.contains("jointEyeLeft")) { + joints.insert("jointEyeLeft", geometry.jointIndices.contains("jointEyeLeft") ? "jointEyeLeft" : + (geometry.jointIndices.contains("EyeLeft") ? "EyeLeft" : "LeftEye")); + } + if (!joints.contains("jointEyeRight")) { + joints.insert("jointEyeRight", geometry.jointIndices.contains("jointEyeRight") ? "jointEyeRight" : + geometry.jointIndices.contains("EyeRight") ? "EyeRight" : "RightEye"); + } + if (!joints.contains("jointNeck")) { + joints.insert("jointNeck", geometry.jointIndices.contains("jointNeck") ? "jointNeck" : "Neck"); + } + if (!joints.contains("jointRoot")) { + joints.insert("jointRoot", "Hips"); + } + if (!joints.contains("jointLean")) { + joints.insert("jointLean", "Spine"); + } + if (!joints.contains("jointHead")) { + const char* topName = (geometry.applicationName == "mixamo.com") ? "HeadTop_End" : "HeadEnd"; + joints.insert("jointHead", geometry.jointIndices.contains(topName) ? topName : "Head"); + } + if (!joints.contains("jointLeftHand")) { + joints.insert("jointLeftHand", "LeftHand"); + } + if (!joints.contains("jointRightHand")) { + joints.insert("jointRightHand", "RightHand"); + } + mapping.insert(JOINT_FIELD, joints); + if (!mapping.contains(FREE_JOINT_FIELD)) { + mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm"); + mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm"); + mapping.insertMulti(FREE_JOINT_FIELD, "RightArm"); + mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm"); + } + + // mixamo blendshapes - in the event that a mixamo file was edited by some other tool, it's likely the applicationName will + // be rewritten, so we detect the existence of several different blendshapes which indicate we're likely a mixamo file + bool likelyMixamoFile = geometry.applicationName == "mixamo.com" || + (geometry.blendshapeChannelNames.contains("BrowsDown_Right") && + geometry.blendshapeChannelNames.contains("MouthOpen") && + geometry.blendshapeChannelNames.contains("Blink_Left") && + geometry.blendshapeChannelNames.contains("Blink_Right") && + geometry.blendshapeChannelNames.contains("Squint_Right")); + + if (!mapping.contains(BLENDSHAPE_FIELD) && likelyMixamoFile) { + QVariantHash blendshapes; + blendshapes.insertMulti("BrowsD_L", QVariantList() << "BrowsDown_Left" << 1.0); + blendshapes.insertMulti("BrowsD_R", QVariantList() << "BrowsDown_Right" << 1.0); + blendshapes.insertMulti("BrowsU_C", QVariantList() << "BrowsUp_Left" << 1.0); + blendshapes.insertMulti("BrowsU_C", QVariantList() << "BrowsUp_Right" << 1.0); + blendshapes.insertMulti("BrowsU_L", QVariantList() << "BrowsUp_Left" << 1.0); + blendshapes.insertMulti("BrowsU_R", QVariantList() << "BrowsUp_Right" << 1.0); + blendshapes.insertMulti("ChinLowerRaise", QVariantList() << "Jaw_Up" << 1.0); + blendshapes.insertMulti("ChinUpperRaise", QVariantList() << "UpperLipUp_Left" << 0.5); + blendshapes.insertMulti("ChinUpperRaise", QVariantList() << "UpperLipUp_Right" << 0.5); + blendshapes.insertMulti("EyeBlink_L", QVariantList() << "Blink_Left" << 1.0); + blendshapes.insertMulti("EyeBlink_R", QVariantList() << "Blink_Right" << 1.0); + blendshapes.insertMulti("EyeOpen_L", QVariantList() << "EyesWide_Left" << 1.0); + blendshapes.insertMulti("EyeOpen_R", QVariantList() << "EyesWide_Right" << 1.0); + blendshapes.insertMulti("EyeSquint_L", QVariantList() << "Squint_Left" << 1.0); + blendshapes.insertMulti("EyeSquint_R", QVariantList() << "Squint_Right" << 1.0); + blendshapes.insertMulti("JawFwd", QVariantList() << "JawForeward" << 1.0); + blendshapes.insertMulti("JawLeft", QVariantList() << "JawRotateY_Left" << 0.5); + blendshapes.insertMulti("JawOpen", QVariantList() << "MouthOpen" << 0.7); + blendshapes.insertMulti("JawRight", QVariantList() << "Jaw_Right" << 1.0); + blendshapes.insertMulti("LipsFunnel", QVariantList() << "JawForeward" << 0.39); + blendshapes.insertMulti("LipsFunnel", QVariantList() << "Jaw_Down" << 0.36); + blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthNarrow_Left" << 1.0); + blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthNarrow_Right" << 1.0); + blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthWhistle_NarrowAdjust_Left" << 0.5); + blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthWhistle_NarrowAdjust_Right" << 0.5); + blendshapes.insertMulti("LipsFunnel", QVariantList() << "TongueUp" << 1.0); + blendshapes.insertMulti("LipsLowerClose", QVariantList() << "LowerLipIn" << 1.0); + blendshapes.insertMulti("LipsLowerDown", QVariantList() << "LowerLipDown_Left" << 0.7); + blendshapes.insertMulti("LipsLowerDown", QVariantList() << "LowerLipDown_Right" << 0.7); + blendshapes.insertMulti("LipsLowerOpen", QVariantList() << "LowerLipOut" << 1.0); + blendshapes.insertMulti("LipsPucker", QVariantList() << "MouthNarrow_Left" << 1.0); + blendshapes.insertMulti("LipsPucker", QVariantList() << "MouthNarrow_Right" << 1.0); + blendshapes.insertMulti("LipsUpperClose", QVariantList() << "UpperLipIn" << 1.0); + blendshapes.insertMulti("LipsUpperOpen", QVariantList() << "UpperLipOut" << 1.0); + blendshapes.insertMulti("LipsUpperUp", QVariantList() << "UpperLipUp_Left" << 0.7); + blendshapes.insertMulti("LipsUpperUp", QVariantList() << "UpperLipUp_Right" << 0.7); + blendshapes.insertMulti("MouthDimple_L", QVariantList() << "Smile_Left" << 0.25); + blendshapes.insertMulti("MouthDimple_R", QVariantList() << "Smile_Right" << 0.25); + blendshapes.insertMulti("MouthFrown_L", QVariantList() << "Frown_Left" << 1.0); + blendshapes.insertMulti("MouthFrown_R", QVariantList() << "Frown_Right" << 1.0); + blendshapes.insertMulti("MouthLeft", QVariantList() << "Midmouth_Left" << 1.0); + blendshapes.insertMulti("MouthRight", QVariantList() << "Midmouth_Right" << 1.0); + blendshapes.insertMulti("MouthSmile_L", QVariantList() << "Smile_Left" << 1.0); + blendshapes.insertMulti("MouthSmile_R", QVariantList() << "Smile_Right" << 1.0); + blendshapes.insertMulti("Puff", QVariantList() << "CheekPuff_Left" << 1.0); + blendshapes.insertMulti("Puff", QVariantList() << "CheekPuff_Right" << 1.0); + blendshapes.insertMulti("Sneer", QVariantList() << "NoseScrunch_Left" << 0.75); + blendshapes.insertMulti("Sneer", QVariantList() << "NoseScrunch_Right" << 0.75); + blendshapes.insertMulti("Sneer", QVariantList() << "Squint_Left" << 0.5); + blendshapes.insertMulti("Sneer", QVariantList() << "Squint_Right" << 0.5); + mapping.insert(BLENDSHAPE_FIELD, blendshapes); + } +} + +void ModelPackager::listTextures() { + _textures.clear(); + foreach (FBXMesh mesh, _geometry.meshes) { + foreach (FBXMeshPart part, mesh.parts) { + if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty() && + !_textures.contains(part.diffuseTexture.filename)) { + _textures << part.diffuseTexture.filename; + } + if (!part.normalTexture.filename.isEmpty() && part.normalTexture.content.isEmpty() && + !_textures.contains(part.normalTexture.filename)) { + + _textures << part.normalTexture.filename; + } + if (!part.specularTexture.filename.isEmpty() && part.specularTexture.content.isEmpty() && + !_textures.contains(part.specularTexture.filename)) { + _textures << part.specularTexture.filename; + } + if (!part.emissiveTexture.filename.isEmpty() && part.emissiveTexture.content.isEmpty() && + !_textures.contains(part.emissiveTexture.filename)) { + _textures << part.emissiveTexture.filename; + } + } + } +} + +bool ModelPackager::copyTextures(const QString& oldDir, const QDir& newDir) { + QString errors; + for (auto texture : _textures) { + QString oldPath = oldDir + "/" + texture; + QString newPath = newDir.path() + "/" + texture; + + // Make sure path exists + if (texture.contains("/")) { + QString dirPath = newDir.relativeFilePath(QFileInfo(newPath).path()); + newDir.mkpath(dirPath); + } + + QFile texFile(oldPath); + if (texFile.exists() && texFile.open(QIODevice::ReadOnly)) { + // Check if texture needs to be recoded + QFileInfo fileInfo(oldPath); + QString extension = fileInfo.suffix().toLower(); + bool isJpeg = (extension == "jpg"); + bool mustRecode = !(isJpeg || extension == "png"); + QImage image = QImage::fromData(texFile.readAll()); + + // Recode texture if too big + if (image.width() > MAX_TEXTURE_SIZE || image.height() > MAX_TEXTURE_SIZE) { + image = image.scaled(MAX_TEXTURE_SIZE, MAX_TEXTURE_SIZE, Qt::KeepAspectRatio); + mustRecode = true; + } + + // Copy texture + if (mustRecode) { + QFile newTexFile(newPath); + newTexFile.open(QIODevice::WriteOnly); + image.save(&newTexFile, isJpeg ? "JPG" : "PNG"); + } else { + texFile.copy(newPath); + } + } else { + errors += QString("\n%1").arg(oldPath); + } + } + + if (!errors.isEmpty()) { + QMessageBox::warning(nullptr, "ModelPackager::copyTextures()", + "Missing textures:" + errors); + qDebug() << "ModelPackager::copyTextures():" << errors; + return false; + } + + return true; +} + + diff --git a/interface/src/ModelPackager.h b/interface/src/ModelPackager.h new file mode 100644 index 0000000000..c62388f196 --- /dev/null +++ b/interface/src/ModelPackager.h @@ -0,0 +1,51 @@ +// +// ModelPackager.h +// +// +// Created by Clement on 3/9/15. +// Copyright 2015 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_ModelPackager_h +#define hifi_ModelPackager_h + +#include +#include + +#include + +#include "ui/ModelsBrowser.h" + +class ModelPackager : public QObject { +public: + static bool package(); + +private: + bool selectModel(); + + bool loadModel(); + bool editProperties(); + bool zipModel(); + + void populateBasicMapping(QVariantHash& mapping, QString filename, const FBXGeometry& geometry); + + void listTextures(); + bool copyTextures(const QString& oldDir, const QDir& newDir); + + QFileInfo _modelFile; + QFileInfo _fbxInfo; + ModelType _modelType; + QString _texDir; + + QVariantHash _mapping; + FBXGeometry _geometry; + QStringList _textures; +}; + + + + +#endif // hifi_ModelPackager_h \ No newline at end of file diff --git a/interface/src/ModelPropertiesDialog.cpp b/interface/src/ModelPropertiesDialog.cpp new file mode 100644 index 0000000000..de98407a2a --- /dev/null +++ b/interface/src/ModelPropertiesDialog.cpp @@ -0,0 +1,244 @@ +// +// ModelPropertiesDialog.cpp +// +// +// Created by Clement on 3/10/15. +// Copyright 2015 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 +#include +#include +#include +#include +#include + +#include + +#include "ModelPropertiesDialog.h" + + +ModelPropertiesDialog::ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping, + const QString& basePath, const FBXGeometry& geometry) : +_modelType(modelType), +_originalMapping(originalMapping), +_basePath(basePath), +_geometry(geometry) +{ + setWindowTitle("Set Model Properties"); + + QFormLayout* form = new QFormLayout(); + setLayout(form); + + form->addRow("Name:", _name = new QLineEdit()); + + form->addRow("Texture Directory:", _textureDirectory = new QPushButton()); + connect(_textureDirectory, SIGNAL(clicked(bool)), SLOT(chooseTextureDirectory())); + + form->addRow("Scale:", _scale = new QDoubleSpinBox()); + _scale->setMaximum(FLT_MAX); + _scale->setSingleStep(0.01); + + if (_modelType != ENTITY_MODEL) { + if (_modelType == ATTACHMENT_MODEL) { + QHBoxLayout* translation = new QHBoxLayout(); + form->addRow("Translation:", translation); + translation->addWidget(_translationX = createTranslationBox()); + translation->addWidget(_translationY = createTranslationBox()); + translation->addWidget(_translationZ = createTranslationBox()); + form->addRow("Pivot About Center:", _pivotAboutCenter = new QCheckBox()); + form->addRow("Pivot Joint:", _pivotJoint = createJointBox()); + connect(_pivotAboutCenter, SIGNAL(toggled(bool)), SLOT(updatePivotJoint())); + _pivotAboutCenter->setChecked(true); + + } else { + 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()); + form->addRow("Left Hand Joint:", _leftHandJoint = createJointBox()); + form->addRow("Right Hand Joint:", _rightHandJoint = createJointBox()); + + form->addRow("Free Joints:", _freeJoints = new QVBoxLayout()); + QPushButton* newFreeJoint = new QPushButton("New Free Joint"); + _freeJoints->addWidget(newFreeJoint); + connect(newFreeJoint, SIGNAL(clicked(bool)), SLOT(createNewFreeJoint())); + } + } + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | + QDialogButtonBox::Cancel | QDialogButtonBox::Reset); + connect(buttons, SIGNAL(accepted()), SLOT(accept())); + connect(buttons, SIGNAL(rejected()), SLOT(reject())); + connect(buttons->button(QDialogButtonBox::Reset), SIGNAL(clicked(bool)), SLOT(reset())); + + form->addRow(buttons); + + // reset to initialize the fields + reset(); +} + +QVariantHash ModelPropertiesDialog::getMapping() const { + QVariantHash mapping = _originalMapping; + mapping.insert(NAME_FIELD, _name->text()); + mapping.insert(TEXDIR_FIELD, _textureDirectory->text()); + mapping.insert(SCALE_FIELD, QString::number(_scale->value())); + + // update the joint indices + QVariantHash jointIndices; + for (int i = 0; i < _geometry.joints.size(); i++) { + jointIndices.insert(_geometry.joints.at(i).name, QString::number(i)); + } + mapping.insert(JOINT_INDEX_FIELD, jointIndices); + + if (_modelType != ENTITY_MODEL) { + QVariantHash joints = mapping.value(JOINT_FIELD).toHash(); + if (_modelType == ATTACHMENT_MODEL) { + glm::vec3 pivot; + if (_pivotAboutCenter->isChecked()) { + pivot = (_geometry.meshExtents.minimum + _geometry.meshExtents.maximum) * 0.5f; + + } else if (_pivotJoint->currentIndex() != 0) { + pivot = extractTranslation(_geometry.joints.at(_pivotJoint->currentIndex() - 1).transform); + } + mapping.insert(TRANSLATION_X_FIELD, -pivot.x * _scale->value() + _translationX->value()); + mapping.insert(TRANSLATION_Y_FIELD, -pivot.y * _scale->value() + _translationY->value()); + mapping.insert(TRANSLATION_Z_FIELD, -pivot.z * _scale->value() + _translationZ->value()); + + } else { + 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()); + insertJointMapping(joints, "jointLeftHand", _leftHandJoint->currentText()); + insertJointMapping(joints, "jointRightHand", _rightHandJoint->currentText()); + + mapping.remove(FREE_JOINT_FIELD); + for (int i = 0; i < _freeJoints->count() - 1; i++) { + QComboBox* box = static_cast(_freeJoints->itemAt(i)->widget()->layout()->itemAt(0)->widget()); + mapping.insertMulti(FREE_JOINT_FIELD, box->currentText()); + } + } + mapping.insert(JOINT_FIELD, joints); + } + + return mapping; +} + +static void setJointText(QComboBox* box, const QString& text) { + box->setCurrentIndex(qMax(box->findText(text), 0)); +} + +void ModelPropertiesDialog::reset() { + _name->setText(_originalMapping.value(NAME_FIELD).toString()); + _textureDirectory->setText(_originalMapping.value(TEXDIR_FIELD).toString()); + _scale->setValue(_originalMapping.value(SCALE_FIELD).toDouble()); + + QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash(); + + if (_modelType != ENTITY_MODEL) { + if (_modelType == ATTACHMENT_MODEL) { + _translationX->setValue(_originalMapping.value(TRANSLATION_X_FIELD).toDouble()); + _translationY->setValue(_originalMapping.value(TRANSLATION_Y_FIELD).toDouble()); + _translationZ->setValue(_originalMapping.value(TRANSLATION_Z_FIELD).toDouble()); + _pivotAboutCenter->setChecked(true); + _pivotJoint->setCurrentIndex(0); + + } else { + 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()); + setJointText(_leftHandJoint, jointHash.value("jointLeftHand").toString()); + setJointText(_rightHandJoint, jointHash.value("jointRightHand").toString()); + + while (_freeJoints->count() > 1) { + delete _freeJoints->itemAt(0)->widget(); + } + foreach (const QVariant& joint, _originalMapping.values(FREE_JOINT_FIELD)) { + QString jointName = joint.toString(); + if (_geometry.jointIndices.contains(jointName)) { + createNewFreeJoint(jointName); + } + } + } + } +} + +void ModelPropertiesDialog::chooseTextureDirectory() { + QString directory = QFileDialog::getExistingDirectory(this, "Choose Texture Directory", + _basePath + "/" + _textureDirectory->text()); + if (directory.isEmpty()) { + return; + } + if (!directory.startsWith(_basePath)) { + QMessageBox::warning(NULL, "Invalid texture directory", "Texture directory must be child of base path."); + return; + } + _textureDirectory->setText(directory.length() == _basePath.length() ? "." : directory.mid(_basePath.length() + 1)); +} + +void ModelPropertiesDialog::updatePivotJoint() { + _pivotJoint->setEnabled(!_pivotAboutCenter->isChecked()); +} + +void ModelPropertiesDialog::createNewFreeJoint(const QString& joint) { + QWidget* freeJoint = new QWidget(); + QHBoxLayout* freeJointLayout = new QHBoxLayout(); + freeJointLayout->setContentsMargins(QMargins()); + freeJoint->setLayout(freeJointLayout); + QComboBox* jointBox = createJointBox(false); + jointBox->setCurrentText(joint); + freeJointLayout->addWidget(jointBox, 1); + QPushButton* deleteJoint = new QPushButton("Delete"); + freeJointLayout->addWidget(deleteJoint); + freeJoint->connect(deleteJoint, SIGNAL(clicked(bool)), SLOT(deleteLater())); + _freeJoints->insertWidget(_freeJoints->count() - 1, freeJoint); +} + +QComboBox* ModelPropertiesDialog::createJointBox(bool withNone) const { + QComboBox* box = new QComboBox(); + if (withNone) { + box->addItem("(none)"); + } + foreach (const FBXJoint& joint, _geometry.joints) { + if (joint.isSkeletonJoint || !_geometry.hasSkeletonJoints) { + box->addItem(joint.name); + } + } + return box; +} + +QDoubleSpinBox* ModelPropertiesDialog::createTranslationBox() const { + QDoubleSpinBox* box = new QDoubleSpinBox(); + const double MAX_TRANSLATION = 1000000.0; + box->setMinimum(-MAX_TRANSLATION); + box->setMaximum(MAX_TRANSLATION); + return box; +} + +void ModelPropertiesDialog::insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const { + if (_geometry.jointIndices.contains(name)) { + joints.insert(joint, name); + } else { + joints.remove(joint); + } +} diff --git a/interface/src/ModelPropertiesDialog.h b/interface/src/ModelPropertiesDialog.h new file mode 100644 index 0000000000..65c5be6c21 --- /dev/null +++ b/interface/src/ModelPropertiesDialog.h @@ -0,0 +1,83 @@ +// +// ModelPropertiesDialog.h +// +// +// Created by Clement on 3/10/15. +// Copyright 2015 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_ModelPropertiesDialog_h +#define hifi_ModelPropertiesDialog_h + +#include + +#include + +#include "ui/ModelsBrowser.h" + +class QDoubleSpinBox; +class QComboBox; +class QCheckBox; +class QVBoxLayout; + +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 JOINT_INDEX_FIELD = "jointIndex"; +static const QString SCALE_FIELD = "scale"; +static const QString TRANSLATION_X_FIELD = "tx"; +static const QString TRANSLATION_Y_FIELD = "ty"; +static const QString TRANSLATION_Z_FIELD = "tz"; +static const QString JOINT_FIELD = "joint"; +static const QString FREE_JOINT_FIELD = "freeJoint"; +static const QString BLENDSHAPE_FIELD = "bs"; + +/// A dialog that allows customization of various model properties. +class ModelPropertiesDialog : public QDialog { + Q_OBJECT + +public: + ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping, + const QString& basePath, const FBXGeometry& geometry); + + QVariantHash getMapping() const; + +private slots: + void reset(); + void chooseTextureDirectory(); + void updatePivotJoint(); + void createNewFreeJoint(const QString& joint = QString()); + +private: + QComboBox* createJointBox(bool withNone = true) const; + QDoubleSpinBox* createTranslationBox() const; + void insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const; + + ModelType _modelType; + QVariantHash _originalMapping; + QString _basePath; + FBXGeometry _geometry; + QLineEdit* _name = nullptr; + QPushButton* _textureDirectory = nullptr; + QDoubleSpinBox* _scale = nullptr; + QDoubleSpinBox* _translationX = nullptr; + QDoubleSpinBox* _translationY = nullptr; + QDoubleSpinBox* _translationZ = nullptr; + QCheckBox* _pivotAboutCenter = nullptr; + QComboBox* _pivotJoint = nullptr; + QComboBox* _leftEyeJoint = nullptr; + QComboBox* _rightEyeJoint = nullptr; + QComboBox* _neckJoint = nullptr; + QComboBox* _rootJoint = nullptr; + QComboBox* _leanJoint = nullptr; + QComboBox* _headJoint = nullptr; + QComboBox* _leftHandJoint = nullptr; + QComboBox* _rightHandJoint = nullptr; + QVBoxLayout* _freeJoints = nullptr; +}; + +#endif // hifi_ModelPropertiesDialog_h \ No newline at end of file diff --git a/interface/src/ModelSelector.cpp b/interface/src/ModelSelector.cpp new file mode 100644 index 0000000000..c55d77dc00 --- /dev/null +++ b/interface/src/ModelSelector.cpp @@ -0,0 +1,89 @@ +// +// ModelSelector.cpp +// +// +// Created by Clement on 3/10/15. +// Copyright 2015 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 +#include +#include + +#include "ModelSelector.h" + +static const QString AVATAR_HEAD_STRING = "Avatar Head"; +static const QString AVATAR_BODY_STRING = "Avatar Body"; +static const QString AVATAR_ATTACHEMENT_STRING = "Avatar Attachment"; +static const QString ENTITY_MODEL_STRING = "Entity Model"; + +ModelSelector::ModelSelector() { + QFormLayout* form = new QFormLayout(this); + + setWindowTitle("Select Model"); + setLayout(form); + + _browseButton = new QPushButton("Browse", this); + connect(_browseButton, &QPushButton::clicked, this, &ModelSelector::browse); + form->addRow("Model File:", _browseButton); + + _modelType = new QComboBox(this); + _modelType->addItem(AVATAR_HEAD_STRING); + _modelType->addItem(AVATAR_BODY_STRING); + _modelType->addItem(AVATAR_ATTACHEMENT_STRING); + _modelType->addItem(ENTITY_MODEL_STRING); + form->addRow("Model Type:", _modelType); + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + connect(buttons, &QDialogButtonBox::accepted, this, &ModelSelector::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); + form->addRow(buttons); +} + +QFileInfo ModelSelector::getFileInfo() const { + return _modelFile; +} + +ModelType ModelSelector::getModelType() const { + QString text = _modelType->currentText(); + + if (text == AVATAR_HEAD_STRING) { + return HEAD_MODEL; + } else if (text == AVATAR_BODY_STRING) { + return SKELETON_MODEL; + } else if (text == AVATAR_ATTACHEMENT_STRING) { + return ATTACHMENT_MODEL; + } else if (text == ENTITY_MODEL_STRING) { + return ENTITY_MODEL; + } else { + Q_UNREACHABLE(); + } +} + +void ModelSelector::accept() { + if (!_modelFile.isFile()) { + return; + } + QDialog::accept(); +} + +void ModelSelector::browse() { + static Setting::Handle lastModelBrowseLocation("LastModelBrowseLocation", + QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); + QString filename = QFileDialog::getOpenFileName(NULL, "Select your model file ...", + lastModelBrowseLocation.get(), + "Model files (*.fst *.fbx)"); + QFileInfo fileInfo(filename); + + if (fileInfo.isFile() && fileInfo.completeSuffix().contains(QRegExp("fst|fbx|FST|FBX"))) { + _modelFile = fileInfo; + _browseButton->setText(fileInfo.fileName()); + lastModelBrowseLocation.set(fileInfo.path()); + } +} \ No newline at end of file diff --git a/interface/src/ModelSelector.h b/interface/src/ModelSelector.h new file mode 100644 index 0000000000..aaa35e01c3 --- /dev/null +++ b/interface/src/ModelSelector.h @@ -0,0 +1,46 @@ +// +// ModelSelector.h +// +// +// Created by Clement on 3/10/15. +// Copyright 2015 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_ModelSelector_h +#define hifi_ModelSelector_h + +#include +#include + +#include + +#include "ui/ModelsBrowser.h" + +class QComboBox; +class QPushButton; + +class ModelSelector : public QDialog { + Q_OBJECT + +public: + ModelSelector(); + + QFileInfo getFileInfo() const; + ModelType getModelType() const; + + public slots: + virtual void accept(); + + private slots: + void browse(); + +private: + QFileInfo _modelFile; + QPushButton* _browseButton; + QComboBox* _modelType; +}; + +#endif // hifi_ModelSelector_h \ No newline at end of file diff --git a/interface/src/ModelUploader.cpp b/interface/src/ModelUploader.cpp deleted file mode 100644 index aca02cb904..0000000000 --- a/interface/src/ModelUploader.cpp +++ /dev/null @@ -1,883 +0,0 @@ -// -// ModelUploader.cpp -// interface/src -// -// Created by Clément Brisset on 3/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 -// - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "ModelUploader.h" - - -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 JOINT_INDEX_FIELD = "jointIndex"; -static const QString SCALE_FIELD = "scale"; -static const QString TRANSLATION_X_FIELD = "tx"; -static const QString TRANSLATION_Y_FIELD = "ty"; -static const QString TRANSLATION_Z_FIELD = "tz"; -static const QString JOINT_FIELD = "joint"; -static const QString FREE_JOINT_FIELD = "freeJoint"; -static const QString BLENDSHAPE_FIELD = "bs"; - -static const QString S3_URL = "http://public.highfidelity.io"; -static const QString MODEL_URL = "/api/v1/models"; - -static const unsigned long long MAX_SIZE = 50 * 1024 * BYTES_PER_MEGABYTES; // 50 GB (Virtually remove limit) -static const int MAX_TEXTURE_SIZE = 1024; -static const int TIMEOUT = 1000; -static const int MAX_CHECK = 30; - -static const int QCOMPRESS_HEADER_POSITION = 0; -static const int QCOMPRESS_HEADER_SIZE = 4; - -Setting::Handle ModelUploader::_lastModelUploadLocation("LastModelUploadLocation", - QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); - -void ModelUploader::uploadModel(ModelType modelType) { - ModelUploader* uploader = new ModelUploader(modelType); - QThread* thread = new QThread(); - thread->setObjectName("Model Uploader"); - thread->connect(uploader, SIGNAL(destroyed()), SLOT(quit())); - thread->connect(thread, SIGNAL(finished()), SLOT(deleteLater())); - uploader->connect(thread, SIGNAL(started()), SLOT(send())); - - thread->start(); -} - -void ModelUploader::uploadHead() { - uploadModel(HEAD_MODEL); -} - -void ModelUploader::uploadSkeleton() { - uploadModel(SKELETON_MODEL); -} - -void ModelUploader::uploadAttachment() { - uploadModel(ATTACHMENT_MODEL); -} - -void ModelUploader::uploadEntity() { - uploadModel(ENTITY_MODEL); -} - -ModelUploader::ModelUploader(ModelType modelType) : - _lodCount(-1), - _texturesCount(-1), - _totalSize(0), - _modelType(modelType), - _readyToSend(false), - _dataMultiPart(new QHttpMultiPart(QHttpMultiPart::FormDataType)), - _numberOfChecks(MAX_CHECK) -{ - connect(&_timer, SIGNAL(timeout()), SLOT(checkS3())); -} - -ModelUploader::~ModelUploader() { - delete _dataMultiPart; -} - -bool ModelUploader::zip() { - // File Dialog - QString lastLocation = _lastModelUploadLocation.get(); - - if (lastLocation.isEmpty()) { - lastLocation = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); - // Temporary fix to Qt bug: http://stackoverflow.com/questions/16194475 -#ifdef __APPLE__ - lastLocation.append("/model.fst"); -#endif - } - - - QString filename = QFileDialog::getOpenFileName(NULL, "Select your model file ...", - lastLocation, "Model files (*.fst *.fbx)"); - if (filename == "") { - // If the user canceled we return. - return false; - } - _lastModelUploadLocation.set(filename); - - // First we check the FST file (if any) - QFile* fst; - QVariantHash mapping; - QString basePath; - QString fbxFile; - if (filename.toLower().endsWith(".fst")) { - fst = new QFile(filename, this); - if (!fst->open(QFile::ReadOnly | QFile::Text)) { - QMessageBox::warning(NULL, - QString("ModelUploader::zip()"), - QString("Could not open FST file."), - QMessageBox::Ok); - qDebug() << "[Warning] " << QString("Could not open FST file."); - return false; - } - qDebug() << "Reading FST file : " << QFileInfo(*fst).filePath(); - mapping = readMapping(fst->readAll()); - basePath = QFileInfo(*fst).path(); - fbxFile = basePath + "/" + mapping.value(FILENAME_FIELD).toString(); - QFileInfo fbxInfo(fbxFile); - if (!fbxInfo.exists() || !fbxInfo.isFile()) { // Check existence - QMessageBox::warning(NULL, - QString("ModelUploader::zip()"), - QString("FBX file %1 could not be found.").arg(fbxInfo.fileName()), - QMessageBox::Ok); - qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(fbxInfo.fileName()); - return false; - } - } else { - fst = new QTemporaryFile(this); - fst->open(QFile::WriteOnly); - fbxFile = filename; - basePath = QFileInfo(filename).path(); - mapping.insert(FILENAME_FIELD, QFileInfo(filename).fileName()); - } - - // open the fbx file - QFile fbx(fbxFile); - if (!fbx.open(QIODevice::ReadOnly)) { - return false; - } - QByteArray fbxContents = fbx.readAll(); - FBXGeometry geometry = readFBX(fbxContents, QVariantHash()); - - #if 0 /// Temporarily remove this check until CtrlAltDavid can come up with a fix. - // Make sure that a skeleton model has a skeleton - if (_modelType == SKELETON_MODEL) { - if (geometry.rootJointIndex == -1) { - - QString message = "Your selected skeleton model has no skeleton.\n\nThe upload will be canceled."; - QMessageBox msgBox; - msgBox.setWindowTitle("Model Upload"); - msgBox.setText(message); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setIcon(QMessageBox::Warning); - msgBox.exec(); - - return false; - } - } - #endif - - // make sure we have some basic mappings - populateBasicMapping(mapping, filename, geometry); - - // open the dialog to configure the rest - ModelPropertiesDialog properties(_modelType, mapping, basePath, geometry); - if (properties.exec() == QDialog::Rejected) { - return false; - } - mapping = properties.getMapping(); - - QByteArray nameField = mapping.value(NAME_FIELD).toByteArray(); - QString urlBase; - if (!nameField.isEmpty()) { - QHttpPart textPart; - textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"model_name\""); - textPart.setBody(nameField); - _dataMultiPart->append(textPart); - urlBase = S3_URL + "/models/" + MODEL_TYPE_NAMES[_modelType] + "/" + nameField; - _url = urlBase + ".fst"; - - } else { - QMessageBox::warning(NULL, - QString("ModelUploader::zip()"), - QString("Model name is missing in the .fst file."), - QMessageBox::Ok); - qDebug() << "[Warning] " << QString("Model name is missing in the .fst file."); - return false; - } - - QByteArray texdirField = mapping.value(TEXDIR_FIELD).toByteArray(); - QString texDir; - _textureBase = urlBase + "/textures/"; - if (!texdirField.isEmpty()) { - texDir = basePath + "/" + texdirField; - QFileInfo texInfo(texDir); - if (!texInfo.exists() || !texInfo.isDir()) { - QMessageBox::warning(NULL, - QString("ModelUploader::zip()"), - QString("Texture directory could not be found."), - QMessageBox::Ok); - qDebug() << "[Warning] " << QString("Texture directory could not be found."); - return false; - } - } - - QVariantHash lodField = mapping.value(LOD_FIELD).toHash(); - for (QVariantHash::const_iterator it = lodField.constBegin(); it != lodField.constEnd(); it++) { - QFileInfo lod(basePath + "/" + it.key()); - if (!lod.exists() || !lod.isFile()) { // Check existence - QMessageBox::warning(NULL, - QString("ModelUploader::zip()"), - QString("LOD file %1 could not be found.").arg(lod.fileName()), - QMessageBox::Ok); - qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(lod.fileName()); - } - // Compress and copy - if (!addPart(lod.filePath(), QString("lod%1").arg(++_lodCount))) { - return false; - } - } - - // Write out, compress and copy the fst - if (!addPart(*fst, writeMapping(mapping), QString("fst"))) { - return false; - } - - // Compress and copy the fbx - if (!addPart(fbx, fbxContents, "fbx")) { - return false; - } - - if (!addTextures(texDir, geometry)) { - return false; - } - - QHttpPart textPart; - textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;" - " name=\"model_category\""); - textPart.setBody(MODEL_TYPE_NAMES[_modelType]); - _dataMultiPart->append(textPart); - - _readyToSend = true; - return true; -} - -void ModelUploader::populateBasicMapping(QVariantHash& mapping, QString filename, const FBXGeometry& geometry) { - if (!mapping.contains(NAME_FIELD)) { - mapping.insert(NAME_FIELD, QFileInfo(filename).baseName()); - } - if (!mapping.contains(TEXDIR_FIELD)) { - mapping.insert(TEXDIR_FIELD, "."); - } - - // mixamo/autodesk defaults - if (!mapping.contains(SCALE_FIELD)) { - mapping.insert(SCALE_FIELD, 1.0); - } - QVariantHash joints = mapping.value(JOINT_FIELD).toHash(); - if (!joints.contains("jointEyeLeft")) { - joints.insert("jointEyeLeft", geometry.jointIndices.contains("jointEyeLeft") ? "jointEyeLeft" : - (geometry.jointIndices.contains("EyeLeft") ? "EyeLeft" : "LeftEye")); - } - if (!joints.contains("jointEyeRight")) { - joints.insert("jointEyeRight", geometry.jointIndices.contains("jointEyeRight") ? "jointEyeRight" : - geometry.jointIndices.contains("EyeRight") ? "EyeRight" : "RightEye"); - } - if (!joints.contains("jointNeck")) { - joints.insert("jointNeck", geometry.jointIndices.contains("jointNeck") ? "jointNeck" : "Neck"); - } - if (!joints.contains("jointRoot")) { - joints.insert("jointRoot", "Hips"); - } - if (!joints.contains("jointLean")) { - joints.insert("jointLean", "Spine"); - } - if (!joints.contains("jointHead")) { - const char* topName = (geometry.applicationName == "mixamo.com") ? "HeadTop_End" : "HeadEnd"; - joints.insert("jointHead", geometry.jointIndices.contains(topName) ? topName : "Head"); - } - if (!joints.contains("jointLeftHand")) { - joints.insert("jointLeftHand", "LeftHand"); - } - if (!joints.contains("jointRightHand")) { - joints.insert("jointRightHand", "RightHand"); - } - mapping.insert(JOINT_FIELD, joints); - if (!mapping.contains(FREE_JOINT_FIELD)) { - mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm"); - mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm"); - mapping.insertMulti(FREE_JOINT_FIELD, "RightArm"); - mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm"); - } - - // mixamo blendshapes - in the event that a mixamo file was edited by some other tool, it's likely the applicationName will - // be rewritten, so we detect the existence of several different blendshapes which indicate we're likely a mixamo file - bool likelyMixamoFile = geometry.applicationName == "mixamo.com" || - (geometry.blendshapeChannelNames.contains("BrowsDown_Right") && - geometry.blendshapeChannelNames.contains("MouthOpen") && - geometry.blendshapeChannelNames.contains("Blink_Left") && - geometry.blendshapeChannelNames.contains("Blink_Right") && - geometry.blendshapeChannelNames.contains("Squint_Right")); - - if (!mapping.contains(BLENDSHAPE_FIELD) && likelyMixamoFile) { - QVariantHash blendshapes; - blendshapes.insertMulti("BrowsD_L", QVariantList() << "BrowsDown_Left" << 1.0); - blendshapes.insertMulti("BrowsD_R", QVariantList() << "BrowsDown_Right" << 1.0); - blendshapes.insertMulti("BrowsU_C", QVariantList() << "BrowsUp_Left" << 1.0); - blendshapes.insertMulti("BrowsU_C", QVariantList() << "BrowsUp_Right" << 1.0); - blendshapes.insertMulti("BrowsU_L", QVariantList() << "BrowsUp_Left" << 1.0); - blendshapes.insertMulti("BrowsU_R", QVariantList() << "BrowsUp_Right" << 1.0); - blendshapes.insertMulti("ChinLowerRaise", QVariantList() << "Jaw_Up" << 1.0); - blendshapes.insertMulti("ChinUpperRaise", QVariantList() << "UpperLipUp_Left" << 0.5); - blendshapes.insertMulti("ChinUpperRaise", QVariantList() << "UpperLipUp_Right" << 0.5); - blendshapes.insertMulti("EyeBlink_L", QVariantList() << "Blink_Left" << 1.0); - blendshapes.insertMulti("EyeBlink_R", QVariantList() << "Blink_Right" << 1.0); - blendshapes.insertMulti("EyeOpen_L", QVariantList() << "EyesWide_Left" << 1.0); - blendshapes.insertMulti("EyeOpen_R", QVariantList() << "EyesWide_Right" << 1.0); - blendshapes.insertMulti("EyeSquint_L", QVariantList() << "Squint_Left" << 1.0); - blendshapes.insertMulti("EyeSquint_R", QVariantList() << "Squint_Right" << 1.0); - blendshapes.insertMulti("JawFwd", QVariantList() << "JawForeward" << 1.0); - blendshapes.insertMulti("JawLeft", QVariantList() << "JawRotateY_Left" << 0.5); - blendshapes.insertMulti("JawOpen", QVariantList() << "MouthOpen" << 0.7); - blendshapes.insertMulti("JawRight", QVariantList() << "Jaw_Right" << 1.0); - blendshapes.insertMulti("LipsFunnel", QVariantList() << "JawForeward" << 0.39); - blendshapes.insertMulti("LipsFunnel", QVariantList() << "Jaw_Down" << 0.36); - blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthNarrow_Left" << 1.0); - blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthNarrow_Right" << 1.0); - blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthWhistle_NarrowAdjust_Left" << 0.5); - blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthWhistle_NarrowAdjust_Right" << 0.5); - blendshapes.insertMulti("LipsFunnel", QVariantList() << "TongueUp" << 1.0); - blendshapes.insertMulti("LipsLowerClose", QVariantList() << "LowerLipIn" << 1.0); - blendshapes.insertMulti("LipsLowerDown", QVariantList() << "LowerLipDown_Left" << 0.7); - blendshapes.insertMulti("LipsLowerDown", QVariantList() << "LowerLipDown_Right" << 0.7); - blendshapes.insertMulti("LipsLowerOpen", QVariantList() << "LowerLipOut" << 1.0); - blendshapes.insertMulti("LipsPucker", QVariantList() << "MouthNarrow_Left" << 1.0); - blendshapes.insertMulti("LipsPucker", QVariantList() << "MouthNarrow_Right" << 1.0); - blendshapes.insertMulti("LipsUpperClose", QVariantList() << "UpperLipIn" << 1.0); - blendshapes.insertMulti("LipsUpperOpen", QVariantList() << "UpperLipOut" << 1.0); - blendshapes.insertMulti("LipsUpperUp", QVariantList() << "UpperLipUp_Left" << 0.7); - blendshapes.insertMulti("LipsUpperUp", QVariantList() << "UpperLipUp_Right" << 0.7); - blendshapes.insertMulti("MouthDimple_L", QVariantList() << "Smile_Left" << 0.25); - blendshapes.insertMulti("MouthDimple_R", QVariantList() << "Smile_Right" << 0.25); - blendshapes.insertMulti("MouthFrown_L", QVariantList() << "Frown_Left" << 1.0); - blendshapes.insertMulti("MouthFrown_R", QVariantList() << "Frown_Right" << 1.0); - blendshapes.insertMulti("MouthLeft", QVariantList() << "Midmouth_Left" << 1.0); - blendshapes.insertMulti("MouthRight", QVariantList() << "Midmouth_Right" << 1.0); - blendshapes.insertMulti("MouthSmile_L", QVariantList() << "Smile_Left" << 1.0); - blendshapes.insertMulti("MouthSmile_R", QVariantList() << "Smile_Right" << 1.0); - blendshapes.insertMulti("Puff", QVariantList() << "CheekPuff_Left" << 1.0); - blendshapes.insertMulti("Puff", QVariantList() << "CheekPuff_Right" << 1.0); - blendshapes.insertMulti("Sneer", QVariantList() << "NoseScrunch_Left" << 0.75); - blendshapes.insertMulti("Sneer", QVariantList() << "NoseScrunch_Right" << 0.75); - blendshapes.insertMulti("Sneer", QVariantList() << "Squint_Left" << 0.5); - blendshapes.insertMulti("Sneer", QVariantList() << "Squint_Right" << 0.5); - mapping.insert(BLENDSHAPE_FIELD, blendshapes); - } -} - -void ModelUploader::send() { - if (!zip()) { - deleteLater(); - return; - } - - JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; - callbackParams.jsonCallbackMethod = "checkJSON"; - callbackParams.errorCallbackReceiver = this; - callbackParams.errorCallbackMethod = "uploadFailed"; - - AccountManager::getInstance().authenticatedRequest(MODEL_URL + "/" + QFileInfo(_url).baseName(), - QNetworkAccessManager::GetOperation, - callbackParams); - - qDebug() << "Sending model..."; - _progressDialog = new QDialog(); - _progressBar = new QProgressBar(_progressDialog); - _progressBar->setRange(0, 100); - _progressBar->setValue(0); - - _progressDialog->setWindowTitle("Uploading model..."); - _progressDialog->setLayout(new QGridLayout(_progressDialog)); - _progressDialog->layout()->addWidget(_progressBar); - - _progressDialog->exec(); - - delete _progressDialog; - _progressDialog = NULL; - _progressBar = NULL; -} - -void ModelUploader::checkJSON(QNetworkReply& requestReply) { - QJsonObject jsonResponse = QJsonDocument::fromJson(requestReply.readAll()).object(); - - if (jsonResponse.contains("status") && jsonResponse.value("status").toString() == "success") { - qDebug() << "status : success"; - JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; - callbackParams.jsonCallbackMethod = "uploadSuccess"; - callbackParams.errorCallbackReceiver = this; - callbackParams.errorCallbackMethod = "uploadFailed"; - callbackParams.updateReciever = this; - callbackParams.updateSlot = SLOT(uploadUpdate(qint64, qint64)); - - if (jsonResponse.contains("exists") && jsonResponse.value("exists").toBool()) { - qDebug() << "exists : true"; - if (jsonResponse.contains("can_update") && jsonResponse.value("can_update").toBool()) { - qDebug() << "can_update : true"; - - AccountManager::getInstance().authenticatedRequest(MODEL_URL + "/" + QFileInfo(_url).baseName(), - QNetworkAccessManager::PutOperation, - callbackParams, - QByteArray(), - _dataMultiPart); - _dataMultiPart = NULL; - } else { - qDebug() << "can_update : false"; - if (_progressDialog) { - _progressDialog->reject(); - } - QMessageBox::warning(NULL, - QString("ModelUploader::checkJSON()"), - QString("This model already exist and is own by someone else."), - QMessageBox::Ok); - deleteLater(); - } - } else { - qDebug() << "exists : false"; - AccountManager::getInstance().authenticatedRequest(MODEL_URL, - QNetworkAccessManager::PostOperation, - callbackParams, - QByteArray(), - _dataMultiPart); - _dataMultiPart = NULL; - } - } else { - qDebug() << "status : failed"; - if (_progressDialog) { - _progressDialog->reject(); - } - QMessageBox::warning(NULL, - QString("ModelUploader::checkJSON()"), - QString("Something went wrong with the data-server."), - QMessageBox::Ok); - deleteLater(); - } -} - -void ModelUploader::uploadUpdate(qint64 bytesSent, qint64 bytesTotal) { - if (_progressDialog) { - _progressBar->setRange(0, bytesTotal); - _progressBar->setValue(bytesSent); - } -} - -void ModelUploader::uploadSuccess(QNetworkReply& requestReply) { - if (_progressDialog) { - _progressDialog->accept(); - } - QMessageBox::information(NULL, - QString("ModelUploader::uploadSuccess()"), - QString("We are reading your model information."), - QMessageBox::Ok); - qDebug() << "Model sent with success"; - checkS3(); -} - -void ModelUploader::uploadFailed(QNetworkReply& errorReply) { - if (_progressDialog) { - _progressDialog->reject(); - } - qDebug() << "Model upload failed (" << errorReply.error() << "): " << errorReply.errorString(); - QMessageBox::warning(NULL, - QString("ModelUploader::uploadFailed()"), - QString("There was a problem with your upload, please try again later."), - QMessageBox::Ok); - deleteLater(); -} - -void ModelUploader::checkS3() { - qDebug() << "Checking S3 for " << _url; - QNetworkRequest request(_url); - request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - QNetworkReply* reply = NetworkAccessManager::getInstance().head(request); - connect(reply, SIGNAL(finished()), SLOT(processCheck())); -} - -void ModelUploader::processCheck() { - QNetworkReply* reply = static_cast(sender()); - _timer.stop(); - - switch (reply->error()) { - case QNetworkReply::NoError: { - QMessageBox::information(NULL, - QString("ModelUploader::processCheck()"), - QString("Your model is now available in the browser."), - QMessageBox::Ok); - DependencyManager::get()->refresh(_url); - auto textureCache = DependencyManager::get(); - foreach (const QByteArray& filename, _textureFilenames) { - textureCache->refresh(_textureBase + filename); - } - deleteLater(); - break; - } - case QNetworkReply::ContentNotFoundError: - if (--_numberOfChecks) { - _timer.start(TIMEOUT); - break; - } - default: - QMessageBox::warning(NULL, - QString("ModelUploader::processCheck()"), - QString("We could not verify that your model was sent sucessfully\n" - "but it may have. If you do not see it in the model browser, try to upload again."), - QMessageBox::Ok); - deleteLater(); - break; - } - - delete reply; -} - -bool ModelUploader::addTextures(const QString& texdir, const FBXGeometry& geometry) { - foreach (FBXMesh mesh, geometry.meshes) { - foreach (FBXMeshPart part, mesh.parts) { - if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty() && - !_textureFilenames.contains(part.diffuseTexture.filename)) { - if (!addPart(texdir + "/" + part.diffuseTexture.filename, - QString("texture%1").arg(++_texturesCount), true)) { - return false; - } - _textureFilenames.insert(part.diffuseTexture.filename); - } - if (!part.normalTexture.filename.isEmpty() && part.normalTexture.content.isEmpty() && - !_textureFilenames.contains(part.normalTexture.filename)) { - if (!addPart(texdir + "/" + part.normalTexture.filename, - QString("texture%1").arg(++_texturesCount), true)) { - return false; - } - _textureFilenames.insert(part.normalTexture.filename); - } - if (!part.specularTexture.filename.isEmpty() && part.specularTexture.content.isEmpty() && - !_textureFilenames.contains(part.specularTexture.filename)) { - if (!addPart(texdir + "/" + part.specularTexture.filename, - QString("texture%1").arg(++_texturesCount), true)) { - return false; - } - _textureFilenames.insert(part.specularTexture.filename); - } - if (!part.emissiveTexture.filename.isEmpty() && part.emissiveTexture.content.isEmpty() && - !_textureFilenames.contains(part.emissiveTexture.filename)) { - if (!addPart(texdir + "/" + part.emissiveTexture.filename, - QString("texture%1").arg(++_texturesCount), true)) { - return false; - } - _textureFilenames.insert(part.emissiveTexture.filename); - } - } - } - - return true; -} - -bool ModelUploader::addPart(const QString &path, const QString& name, bool isTexture) { - QFile file(path); - if (!file.open(QIODevice::ReadOnly)) { - QMessageBox::warning(NULL, - QString("ModelUploader::addPart()"), - QString("Could not open %1").arg(path), - QMessageBox::Ok); - qDebug() << "[Warning] " << QString("Could not open %1").arg(path); - return false; - } - return addPart(file, file.readAll(), name, isTexture); -} - -bool ModelUploader::addPart(const QFile& file, const QByteArray& contents, const QString& name, bool isTexture) { - QFileInfo fileInfo(file); - QByteArray recodedContents = contents; - if (isTexture) { - QString extension = fileInfo.suffix().toLower(); - bool isJpeg = (extension == "jpg"); - bool mustRecode = !(isJpeg || extension == "png"); - QImage image = QImage::fromData(contents); - if (image.width() > MAX_TEXTURE_SIZE || image.height() > MAX_TEXTURE_SIZE) { - image = image.scaled(MAX_TEXTURE_SIZE, MAX_TEXTURE_SIZE, Qt::KeepAspectRatio); - mustRecode = true; - } - if (mustRecode) { - QBuffer buffer; - buffer.open(QIODevice::WriteOnly); - image.save(&buffer, isJpeg ? "JPG" : "PNG"); - recodedContents = buffer.data(); - } - } - QByteArray buffer = qCompress(recodedContents); - - // Qt's qCompress() default compression level (-1) is the standard zLib compression. - // Here remove Qt's custom header that prevent the data server from uncompressing the files with zLib. - buffer.remove(QCOMPRESS_HEADER_POSITION, QCOMPRESS_HEADER_SIZE); - - QHttpPart part; - part.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data;" - " name=\"" + name.toUtf8() + "\";" - " filename=\"" + QFileInfo(file).fileName().toUtf8() + "\"")); - part.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); - part.setBody(buffer); - _dataMultiPart->append(part); - - - qDebug() << "File " << QFileInfo(file).fileName() << " added to model."; - _totalSize += recodedContents.size(); - if (_totalSize > MAX_SIZE) { - QMessageBox::warning(NULL, - QString("ModelUploader::zip()"), - QString("Model too big, over %1 MB.").arg(MAX_SIZE / BYTES_PER_MEGABYTES), - QMessageBox::Ok); - qDebug() << "[Warning] " << QString("Model too big, over %1 MB.").arg(MAX_SIZE / BYTES_PER_MEGABYTES); - return false; - } - qDebug() << "Current model size: " << _totalSize; - - return true; -} - -static QDoubleSpinBox* createTranslationBox() { - QDoubleSpinBox* box = new QDoubleSpinBox(); - const double MAX_TRANSLATION = 1000000.0; - box->setMinimum(-MAX_TRANSLATION); - box->setMaximum(MAX_TRANSLATION); - return box; -} - -ModelPropertiesDialog::ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping, - const QString& basePath, const FBXGeometry& geometry) : - _modelType(modelType), - _originalMapping(originalMapping), - _basePath(basePath), - _geometry(geometry) -{ - setWindowTitle("Set Model Properties"); - - QFormLayout* form = new QFormLayout(); - setLayout(form); - - form->addRow("Name:", _name = new QLineEdit()); - - form->addRow("Texture Directory:", _textureDirectory = new QPushButton()); - connect(_textureDirectory, SIGNAL(clicked(bool)), SLOT(chooseTextureDirectory())); - - form->addRow("Scale:", _scale = new QDoubleSpinBox()); - _scale->setMaximum(FLT_MAX); - _scale->setSingleStep(0.01); - - if (_modelType != ENTITY_MODEL) { - if (_modelType == ATTACHMENT_MODEL) { - QHBoxLayout* translation = new QHBoxLayout(); - form->addRow("Translation:", translation); - translation->addWidget(_translationX = createTranslationBox()); - translation->addWidget(_translationY = createTranslationBox()); - translation->addWidget(_translationZ = createTranslationBox()); - form->addRow("Pivot About Center:", _pivotAboutCenter = new QCheckBox()); - form->addRow("Pivot Joint:", _pivotJoint = createJointBox()); - connect(_pivotAboutCenter, SIGNAL(toggled(bool)), SLOT(updatePivotJoint())); - _pivotAboutCenter->setChecked(true); - - } else { - 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()); - form->addRow("Left Hand Joint:", _leftHandJoint = createJointBox()); - form->addRow("Right Hand Joint:", _rightHandJoint = createJointBox()); - - form->addRow("Free Joints:", _freeJoints = new QVBoxLayout()); - QPushButton* newFreeJoint = new QPushButton("New Free Joint"); - _freeJoints->addWidget(newFreeJoint); - connect(newFreeJoint, SIGNAL(clicked(bool)), SLOT(createNewFreeJoint())); - } - } - - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | - QDialogButtonBox::Cancel | QDialogButtonBox::Reset); - connect(buttons, SIGNAL(accepted()), SLOT(accept())); - connect(buttons, SIGNAL(rejected()), SLOT(reject())); - connect(buttons->button(QDialogButtonBox::Reset), SIGNAL(clicked(bool)), SLOT(reset())); - - form->addRow(buttons); - - // reset to initialize the fields - reset(); -} - -QVariantHash ModelPropertiesDialog::getMapping() const { - QVariantHash mapping = _originalMapping; - mapping.insert(NAME_FIELD, _name->text()); - mapping.insert(TEXDIR_FIELD, _textureDirectory->text()); - mapping.insert(SCALE_FIELD, QString::number(_scale->value())); - - // update the joint indices - QVariantHash jointIndices; - for (int i = 0; i < _geometry.joints.size(); i++) { - jointIndices.insert(_geometry.joints.at(i).name, QString::number(i)); - } - mapping.insert(JOINT_INDEX_FIELD, jointIndices); - - if (_modelType != ENTITY_MODEL) { - QVariantHash joints = mapping.value(JOINT_FIELD).toHash(); - if (_modelType == ATTACHMENT_MODEL) { - glm::vec3 pivot; - if (_pivotAboutCenter->isChecked()) { - pivot = (_geometry.meshExtents.minimum + _geometry.meshExtents.maximum) * 0.5f; - - } else if (_pivotJoint->currentIndex() != 0) { - pivot = extractTranslation(_geometry.joints.at(_pivotJoint->currentIndex() - 1).transform); - } - mapping.insert(TRANSLATION_X_FIELD, -pivot.x * _scale->value() + _translationX->value()); - mapping.insert(TRANSLATION_Y_FIELD, -pivot.y * _scale->value() + _translationY->value()); - mapping.insert(TRANSLATION_Z_FIELD, -pivot.z * _scale->value() + _translationZ->value()); - - } else { - 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()); - insertJointMapping(joints, "jointLeftHand", _leftHandJoint->currentText()); - insertJointMapping(joints, "jointRightHand", _rightHandJoint->currentText()); - - mapping.remove(FREE_JOINT_FIELD); - for (int i = 0; i < _freeJoints->count() - 1; i++) { - QComboBox* box = static_cast(_freeJoints->itemAt(i)->widget()->layout()->itemAt(0)->widget()); - mapping.insertMulti(FREE_JOINT_FIELD, box->currentText()); - } - } - mapping.insert(JOINT_FIELD, joints); - } - - return mapping; -} - -static void setJointText(QComboBox* box, const QString& text) { - box->setCurrentIndex(qMax(box->findText(text), 0)); -} - -void ModelPropertiesDialog::reset() { - _name->setText(_originalMapping.value(NAME_FIELD).toString()); - _textureDirectory->setText(_originalMapping.value(TEXDIR_FIELD).toString()); - _scale->setValue(_originalMapping.value(SCALE_FIELD).toDouble()); - - QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash(); - - if (_modelType != ENTITY_MODEL) { - if (_modelType == ATTACHMENT_MODEL) { - _translationX->setValue(_originalMapping.value(TRANSLATION_X_FIELD).toDouble()); - _translationY->setValue(_originalMapping.value(TRANSLATION_Y_FIELD).toDouble()); - _translationZ->setValue(_originalMapping.value(TRANSLATION_Z_FIELD).toDouble()); - _pivotAboutCenter->setChecked(true); - _pivotJoint->setCurrentIndex(0); - - } else { - 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()); - setJointText(_leftHandJoint, jointHash.value("jointLeftHand").toString()); - setJointText(_rightHandJoint, jointHash.value("jointRightHand").toString()); - - while (_freeJoints->count() > 1) { - delete _freeJoints->itemAt(0)->widget(); - } - foreach (const QVariant& joint, _originalMapping.values(FREE_JOINT_FIELD)) { - QString jointName = joint.toString(); - if (_geometry.jointIndices.contains(jointName)) { - createNewFreeJoint(jointName); - } - } - } - } -} - -void ModelPropertiesDialog::chooseTextureDirectory() { - QString directory = QFileDialog::getExistingDirectory(this, "Choose Texture Directory", - _basePath + "/" + _textureDirectory->text()); - if (directory.isEmpty()) { - return; - } - if (!directory.startsWith(_basePath)) { - QMessageBox::warning(NULL, "Invalid texture directory", "Texture directory must be child of base path."); - return; - } - _textureDirectory->setText(directory.length() == _basePath.length() ? "." : directory.mid(_basePath.length() + 1)); -} - -void ModelPropertiesDialog::updatePivotJoint() { - _pivotJoint->setEnabled(!_pivotAboutCenter->isChecked()); -} - -void ModelPropertiesDialog::createNewFreeJoint(const QString& joint) { - QWidget* freeJoint = new QWidget(); - QHBoxLayout* freeJointLayout = new QHBoxLayout(); - freeJointLayout->setContentsMargins(QMargins()); - freeJoint->setLayout(freeJointLayout); - QComboBox* jointBox = createJointBox(false); - jointBox->setCurrentText(joint); - freeJointLayout->addWidget(jointBox, 1); - QPushButton* deleteJoint = new QPushButton("Delete"); - freeJointLayout->addWidget(deleteJoint); - freeJoint->connect(deleteJoint, SIGNAL(clicked(bool)), SLOT(deleteLater())); - _freeJoints->insertWidget(_freeJoints->count() - 1, freeJoint); -} - -QComboBox* ModelPropertiesDialog::createJointBox(bool withNone) const { - QComboBox* box = new QComboBox(); - if (withNone) { - box->addItem("(none)"); - } - foreach (const FBXJoint& joint, _geometry.joints) { - if (joint.isSkeletonJoint || !_geometry.hasSkeletonJoints) { - box->addItem(joint.name); - } - } - return box; -} - -void ModelPropertiesDialog::insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const { - if (_geometry.jointIndices.contains(name)) { - joints.insert(joint, name); - } else { - joints.remove(joint); - } -} - diff --git a/interface/src/ModelUploader.h b/interface/src/ModelUploader.h deleted file mode 100644 index 59a424b346..0000000000 --- a/interface/src/ModelUploader.h +++ /dev/null @@ -1,126 +0,0 @@ -// -// ModelUploader.h -// interface/src -// -// Created by Clément Brisset on 3/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_ModelUploader_h -#define hifi_ModelUploader_h - -#include -#include - -#include -#include - -#include "ui/ModelsBrowser.h" - -class QCheckBox; -class QComboBox; -class QDoubleSpinBox; -class QFileInfo; -class QHttpMultiPart; -class QLineEdit; -class QProgressBar; -class QPushButton; -class QVBoxLayout; - -class ModelUploader : public QObject { - Q_OBJECT - -public: - static void uploadModel(ModelType modelType); - - static void uploadHead(); - static void uploadSkeleton(); - static void uploadAttachment(); - static void uploadEntity(); - -private slots: - void send(); - void checkJSON(QNetworkReply& requestReply); - void uploadUpdate(qint64 bytesSent, qint64 bytesTotal); - void uploadSuccess(QNetworkReply& requestReply); - void uploadFailed(QNetworkReply& errorReply); - void checkS3(); - void processCheck(); - -private: - ModelUploader(ModelType type); - ~ModelUploader(); - - void populateBasicMapping(QVariantHash& mapping, QString filename, const FBXGeometry& geometry); - bool zip(); - bool addTextures(const QString& texdir, const FBXGeometry& geometry); - bool addPart(const QString& path, const QString& name, bool isTexture = false); - bool addPart(const QFile& file, const QByteArray& contents, const QString& name, bool isTexture = false); - - QString _url; - QString _textureBase; - QSet _textureFilenames; - int _lodCount; - int _texturesCount; - unsigned long _totalSize; - ModelType _modelType; - bool _readyToSend; - - QHttpMultiPart* _dataMultiPart = nullptr; - - int _numberOfChecks; - QTimer _timer; - - QDialog* _progressDialog = nullptr; - QProgressBar* _progressBar = nullptr; - - static Setting::Handle _lastModelUploadLocation; -}; - -/// A dialog that allows customization of various model properties. -class ModelPropertiesDialog : public QDialog { - Q_OBJECT - -public: - ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping, - const QString& basePath, const FBXGeometry& geometry); - - QVariantHash getMapping() const; - -private slots: - void reset(); - void chooseTextureDirectory(); - void updatePivotJoint(); - void createNewFreeJoint(const QString& joint = QString()); - -private: - QComboBox* createJointBox(bool withNone = true) const; - void insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const; - - ModelType _modelType; - QVariantHash _originalMapping; - QString _basePath; - FBXGeometry _geometry; - QLineEdit* _name = nullptr; - QPushButton* _textureDirectory = nullptr; - QDoubleSpinBox* _scale = nullptr; - QDoubleSpinBox* _translationX = nullptr; - QDoubleSpinBox* _translationY = nullptr; - QDoubleSpinBox* _translationZ = nullptr; - QCheckBox* _pivotAboutCenter = nullptr; - QComboBox* _pivotJoint = nullptr; - QComboBox* _leftEyeJoint = nullptr; - QComboBox* _rightEyeJoint = nullptr; - QComboBox* _neckJoint = nullptr; - QComboBox* _rootJoint = nullptr; - QComboBox* _leanJoint = nullptr; - QComboBox* _headJoint = nullptr; - QComboBox* _leftHandJoint = nullptr; - QComboBox* _rightHandJoint = nullptr; - QVBoxLayout* _freeJoints = nullptr; -}; - -#endif // hifi_ModelUploader_h diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 9ecc0a3798..d9c9ff3ad1 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -49,8 +49,6 @@ using namespace std; const glm::vec3 DEFAULT_UP_DIRECTION(0.0f, 1.0f, 0.0f); const float YAW_SPEED = 500.0f; // degrees/sec const float PITCH_SPEED = 100.0f; // degrees/sec -const float COLLISION_RADIUS_SCALAR = 1.2f; // pertains to avatar-to-avatar collisions -const float COLLISION_RADIUS_SCALE = 0.125f; const float DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES = 30.0f; const float MAX_WALKING_SPEED = 2.5f; // human walking speed diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 093c4b1668..5dad5a0a67 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -791,8 +791,6 @@ void SkeletonModel::renderBoundingCollisionShapes(float alpha) { glPopMatrix(); } -const int BALL_SUBDIVISIONS = 10; - bool SkeletonModel::hasSkeleton() { return isActive() ? _geometry->getFBXGeometry().rootJointIndex != -1 : false; } diff --git a/interface/src/scripting/WebWindowClass.cpp b/interface/src/scripting/WebWindowClass.cpp index be87870f26..2e0f88c776 100644 --- a/interface/src/scripting/WebWindowClass.cpp +++ b/interface/src/scripting/WebWindowClass.cpp @@ -73,7 +73,7 @@ void WebWindowClass::setVisible(bool visible) { QScriptValue WebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { WebWindowClass* retVal; QString file = context->argument(0).toString(); - QMetaObject::invokeMethod(WindowScriptingInterface::getInstance(), "doCreateWebWindow", Qt::BlockingQueuedConnection, + QMetaObject::invokeMethod(DependencyManager::get().data(), "doCreateWebWindow", Qt::BlockingQueuedConnection, Q_RETURN_ARG(WebWindowClass*, retVal), Q_ARG(const QString&, file), Q_ARG(QString, context->argument(1).toString()), diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 8ec9fbbb82..52de31df3c 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -25,11 +25,6 @@ #include "WindowScriptingInterface.h" -WindowScriptingInterface* WindowScriptingInterface::getInstance() { - static WindowScriptingInterface sharedInstance; - return &sharedInstance; -} - WindowScriptingInterface::WindowScriptingInterface() : _editDialog(NULL), _nonBlockingFormActive(false), @@ -37,6 +32,7 @@ WindowScriptingInterface::WindowScriptingInterface() : { const DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); connect(&domainHandler, &DomainHandler::hostnameChanged, this, &WindowScriptingInterface::domainChanged); + connect(Application::getInstance(), &Application::svoImportRequested, this, &WindowScriptingInterface::svoImportRequested); } WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height) { diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 5c0aa4f0a8..34942366eb 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -21,7 +21,7 @@ #include "WebWindowClass.h" -class WindowScriptingInterface : public QObject { +class WindowScriptingInterface : public QObject, public Dependency { Q_OBJECT Q_PROPERTY(int innerWidth READ getInnerWidth) Q_PROPERTY(int innerHeight READ getInnerHeight) @@ -29,7 +29,7 @@ class WindowScriptingInterface : public QObject { Q_PROPERTY(int y READ getY) Q_PROPERTY(bool cursorVisible READ isCursorVisible WRITE setCursorVisible) public: - static WindowScriptingInterface* getInstance(); + WindowScriptingInterface(); int getInnerWidth(); int getInnerHeight(); int getX(); @@ -60,6 +60,7 @@ signals: void domainChanged(const QString& domainHostname); void inlineButtonClicked(const QString& name); void nonBlockingFormClosed(); + void svoImportRequested(const QString& url); private slots: QScriptValue showAlert(const QString& message); @@ -85,7 +86,6 @@ private slots: WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height); private: - WindowScriptingInterface(); QString jsRegExp2QtRegExp(QString string); QDialog* createForm(const QString& title, QScriptValue form); diff --git a/interface/src/ui/BandwidthDialog.h b/interface/src/ui/BandwidthDialog.h index a504a5964f..1fc8627191 100644 --- a/interface/src/ui/BandwidthDialog.h +++ b/interface/src/ui/BandwidthDialog.h @@ -16,6 +16,7 @@ #include #include #include +#include #include "Node.h" #include "BandwidthRecorder.h" diff --git a/interface/src/ui/ModelsBrowser.h b/interface/src/ui/ModelsBrowser.h index fa09a67826..0c8bb59c85 100644 --- a/interface/src/ui/ModelsBrowser.h +++ b/interface/src/ui/ModelsBrowser.h @@ -16,6 +16,8 @@ #include #include +class QNetworkReply; + enum ModelType { ENTITY_MODEL, HEAD_MODEL, diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp index 3265891b18..62a44c7e21 100644 --- a/libraries/entities/src/LightEntityItem.cpp +++ b/libraries/entities/src/LightEntityItem.cpp @@ -32,7 +32,6 @@ LightEntityItem::LightEntityItem(const EntityItemID& entityItemID, const EntityI _type = EntityTypes::Light; // default property values - const quint8 MAX_COLOR = 255; _color[RED_INDEX] = _color[GREEN_INDEX] = _color[BLUE_INDEX] = 0; _intensity = 1.0f; _exponent = 0.0f; diff --git a/libraries/model/src/model/Stage.cpp b/libraries/model/src/model/Stage.cpp index 970539a908..1c171eee76 100644 --- a/libraries/model/src/model/Stage.cpp +++ b/libraries/model/src/model/Stage.cpp @@ -134,55 +134,55 @@ void EarthSunModel::setSunLongitude(float lon) { _sunLongitude = validateLongitude(lon); invalidate(); } - -Atmosphere::Atmosphere() { - // only if created from nothing shall we create the Buffer to store the properties - Data data; - _dataBuffer = gpu::BufferView(new gpu::Buffer(sizeof(Data), (const gpu::Buffer::Byte*) &data)); - + +Atmosphere::Atmosphere() { + // only if created from nothing shall we create the Buffer to store the properties + Data data; + _dataBuffer = gpu::BufferView(new gpu::Buffer(sizeof(Data), (const gpu::Buffer::Byte*) &data)); + setScatteringWavelength(_scatteringWavelength); setRayleighScattering(_rayleighScattering); setInnerOuterRadiuses(getInnerRadius(), getOuterRadius()); } -void Atmosphere::setScatteringWavelength(Vec3 wavelength) { - _scatteringWavelength = wavelength; - Data& data = editData(); - data._invWaveLength = Vec4(1.0f / powf(wavelength.x, 4.0f), 1.0f / powf(wavelength.y, 4.0f), 1.0f / powf(wavelength.z, 4.0f), 0.0f); -} - -void Atmosphere::setRayleighScattering(float scattering) { - _rayleighScattering = scattering; - updateScattering(); -} - -void Atmosphere::setMieScattering(float scattering) { - _mieScattering = scattering; - updateScattering(); -} - -void Atmosphere::setSunBrightness(float brightness) { - _sunBrightness = brightness; - updateScattering(); -} +void Atmosphere::setScatteringWavelength(Vec3 wavelength) { + _scatteringWavelength = wavelength; + Data& data = editData(); + data._invWaveLength = Vec4(1.0f / powf(wavelength.x, 4.0f), 1.0f / powf(wavelength.y, 4.0f), 1.0f / powf(wavelength.z, 4.0f), 0.0f); +} -void Atmosphere::updateScattering() { - Data& data = editData(); - - data._scatterings.x = getRayleighScattering() * getSunBrightness(); - data._scatterings.y = getMieScattering() * getSunBrightness(); - - data._scatterings.z = getRayleighScattering() * 4.0f * glm::pi(); - data._scatterings.w = getMieScattering() * 4.0f * glm::pi(); -} +void Atmosphere::setRayleighScattering(float scattering) { + _rayleighScattering = scattering; + updateScattering(); +} -void Atmosphere::setInnerOuterRadiuses(float inner, float outer) { - Data& data = editData(); - data._radiuses.x = inner; - data._radiuses.y = outer; - data._scales.x = 1.0f / (outer - inner); - data._scales.z = data._scales.x / data._scales.y; -} +void Atmosphere::setMieScattering(float scattering) { + _mieScattering = scattering; + updateScattering(); +} + +void Atmosphere::setSunBrightness(float brightness) { + _sunBrightness = brightness; + updateScattering(); +} + +void Atmosphere::updateScattering() { + Data& data = editData(); + + data._scatterings.x = getRayleighScattering() * getSunBrightness(); + data._scatterings.y = getMieScattering() * getSunBrightness(); + + data._scatterings.z = getRayleighScattering() * 4.0f * glm::pi(); + data._scatterings.w = getMieScattering() * 4.0f * glm::pi(); +} + +void Atmosphere::setInnerOuterRadiuses(float inner, float outer) { + Data& data = editData(); + data._radiuses.x = inner; + data._radiuses.y = outer; + data._scales.x = 1.0f / (outer - inner); + data._scales.z = data._scales.x / data._scales.y; +} const int NUM_DAYS_PER_YEAR = 365; @@ -267,7 +267,7 @@ void SunSkyStage::updateGraphicsObject() const { static int firstTime = 0; if (firstTime == 0) { firstTime++; - bool result = gpu::Shader::makeProgram(*(_skyPipeline->getProgram())); + gpu::Shader::makeProgram(*(_skyPipeline->getProgram())); } diff --git a/libraries/networking/src/BandwidthRecorder.h b/libraries/networking/src/BandwidthRecorder.h index a7f51fbb45..c22665d2cc 100644 --- a/libraries/networking/src/BandwidthRecorder.h +++ b/libraries/networking/src/BandwidthRecorder.h @@ -16,7 +16,6 @@ #include #include -#include #include "DependencyManager.h" #include "Node.h" #include "SimpleMovingAverage.h" diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index fe50647c20..78ec64832b 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -32,7 +32,6 @@ DomainHandler::DomainHandler(QObject* parent) : _iceServerSockAddr(), _icePeer(), _isConnected(false), - _handshakeTimer(NULL), _settingsObject(), _failedSettingsRequests(0) { @@ -50,12 +49,6 @@ void DomainHandler::clearConnectionInfo() { } setIsConnected(false); - - if (_handshakeTimer) { - _handshakeTimer->stop(); - delete _handshakeTimer; - _handshakeTimer = NULL; - } } void DomainHandler::clearSettings() { diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 295e6eac01..0877f657e1 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -100,7 +100,6 @@ private: HifiSockAddr _iceServerSockAddr; NetworkPeer _icePeer; bool _isConnected; - QTimer* _handshakeTimer; QJsonObject _settingsObject; int _failedSettingsRequests; }; diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 279d958082..520dc650ed 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -80,6 +80,10 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short connect(localSocketUpdate, &QTimer::timeout, this, &LimitedNodeList::updateLocalSockAddr); localSocketUpdate->start(LOCAL_SOCKET_UPDATE_INTERVAL_MSECS); + QTimer* silentNodeTimer = new QTimer(this); + connect(silentNodeTimer, &QTimer::timeout, this, &LimitedNodeList::removeSilentNodes); + silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS); + // check the local socket right now updateLocalSockAddr(); @@ -500,6 +504,7 @@ void LimitedNodeList::resetPacketStats() { } void LimitedNodeList::removeSilentNodes() { + QSet killedNodes; eachNodeHashIterator([&](NodeHash::iterator& it){ diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index afbdf23fba..a071eced31 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -223,6 +223,8 @@ protected: HifiSockAddr _localSockAddr; HifiSockAddr _publicSockAddr; HifiSockAddr _stunSockAddr; + + QTimer* _silentNodeTimer; // XXX can BandwidthRecorder be used for this? int _numCollectedPackets; diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 1c6de4bb6c..ccfaa7a4cf 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -37,6 +37,7 @@ const quint64 DOMAIN_SERVER_CHECK_IN_MSECS = 1 * 1000; const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 5; +class Application; class Assignment; class NodeList : public LimitedNodeList { @@ -95,6 +96,8 @@ private: HifiSockAddr _assignmentServerSocket; bool _hasCompletedInitialSTUNFailure; unsigned int _stunRequestsSinceSuccess; + + friend class Application; }; #endif // hifi_NodeList_h diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index ea94a8e22c..79b4e7f437 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -67,10 +67,6 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy connect(domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit())); domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); - QTimer* silentNodeRemovalTimer = new QTimer(this); - connect(silentNodeRemovalTimer, SIGNAL(timeout()), nodeList.data(), SLOT(removeSilentNodes())); - silentNodeRemovalTimer->start(NODE_SILENCE_THRESHOLD_MSECS); - if (shouldSendStats) { // send a stats packet every 1 second QTimer* statsTimer = new QTimer(this); diff --git a/libraries/physics/src/MeshInfo.cpp b/libraries/physics/src/MeshInfo.cpp index 8df5ff914d..29ddc74a98 100644 --- a/libraries/physics/src/MeshInfo.cpp +++ b/libraries/physics/src/MeshInfo.cpp @@ -17,9 +17,11 @@ using namespace meshinfo; //origin is the default reference point for generating the tetrahedron from each triangle of the mesh. MeshInfo::MeshInfo(vector *vertices, vector *triangles) :\ -_vertices(vertices), -_triangles(triangles), -_centerOfMass(Vertex(0.0, 0.0, 0.0)){ + _vertices(vertices), + _centerOfMass(Vertex(0.0, 0.0, 0.0)), + _triangles(triangles) +{ + } MeshInfo::~MeshInfo(){ diff --git a/libraries/shared/src/DependencyManager.h b/libraries/shared/src/DependencyManager.h index 3868bf14da..01b755fdd0 100644 --- a/libraries/shared/src/DependencyManager.h +++ b/libraries/shared/src/DependencyManager.h @@ -17,18 +17,25 @@ #include #include +#include #include #define SINGLETON_DEPENDENCY \ friend class DependencyManager; class Dependency { +public: + typedef std::function DeleterFunction; + protected: virtual ~Dependency() {} virtual void customDeleter() { - delete this; + _customDeleter(this); } - + + void setCustomDeleter(DeleterFunction customDeleter) { _customDeleter = customDeleter; } + DeleterFunction _customDeleter = [](Dependency* pointer) { delete pointer; }; + friend class DependencyManager; };