mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 01:03:57 +02:00
merge upstream/master into andrew/isentropic
This commit is contained in:
commit
be252147e2
39 changed files with 1090 additions and 1361 deletions
|
@ -12,7 +12,6 @@
|
|||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QEventLoop>
|
||||
#include <QtCore/QStandardPaths>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtNetwork/QNetworkDiskCache>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonValue>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
|
|
2
cmake/externals/glm/CMakeLists.txt
vendored
2
cmake/externals/glm/CMakeLists.txt
vendored
|
@ -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=<INSTALL_DIR>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
|
@ -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);
|
|
@ -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<BandwidthRecorder>();
|
||||
auto resouceCacheSharedItems = DependencyManager::set<ResouceCacheSharedItems>();
|
||||
auto entityScriptingInterface = DependencyManager::set<EntityScriptingInterface>();
|
||||
auto windowScriptingInterface = DependencyManager::set<WindowScriptingInterface>();
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
auto speechRecognizer = DependencyManager::set<SpeechRecognizer>();
|
||||
#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<NodeList*>(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<AvatarManager>();
|
||||
DependencyManager::destroy<AnimationCache>();
|
||||
DependencyManager::destroy<TextureCache>();
|
||||
DependencyManager::destroy<GeometryCache>();
|
||||
//DependencyManager::destroy<ScriptCache>();
|
||||
DependencyManager::destroy<SoundCache>();
|
||||
|
||||
QThread* nodeThread = DependencyManager::get<NodeList>()->thread();
|
||||
DependencyManager::destroy<NodeList>();
|
||||
|
||||
// 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<QFileOpenEvent*>(event);
|
||||
|
||||
QUrl url = fileEvent->url();
|
||||
|
||||
if (!fileEvent->url().isEmpty()) {
|
||||
DependencyManager::get<AddressManager>()->handleLookupString(fileEvent->url().toString());
|
||||
if (!url.isEmpty()) {
|
||||
if (url.scheme() == HIFI_URL_SCHEME) {
|
||||
DependencyManager::get<AddressManager>()->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<DialogsManager>()->toggleLoginDialog();
|
||||
|
||||
_environment.init();
|
||||
|
||||
DependencyManager::get<DeferredLightingEffect>()->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<WindowScriptingInterface>().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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<AvatarManager>()->getMyAvatar();
|
||||
|
|
|
@ -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";
|
||||
|
|
397
interface/src/ModelPackager.cpp
Normal file
397
interface/src/ModelPackager.cpp
Normal file
|
@ -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 <QFile>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
|
51
interface/src/ModelPackager.h
Normal file
51
interface/src/ModelPackager.h
Normal file
|
@ -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 <QFileInfo>
|
||||
#include <QVariantHash>
|
||||
|
||||
#include <FBXReader.h>
|
||||
|
||||
#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
|
244
interface/src/ModelPropertiesDialog.cpp
Normal file
244
interface/src/ModelPropertiesDialog.cpp
Normal file
|
@ -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 <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QFileDialog>
|
||||
#include <QFormLayout>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
|
||||
#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<QComboBox*>(_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);
|
||||
}
|
||||
}
|
83
interface/src/ModelPropertiesDialog.h
Normal file
83
interface/src/ModelPropertiesDialog.h
Normal file
|
@ -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 <QDialog>
|
||||
|
||||
#include <FBXReader.h>
|
||||
|
||||
#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
|
89
interface/src/ModelSelector.cpp
Normal file
89
interface/src/ModelSelector.cpp
Normal file
|
@ -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 <QDialogButtonBox>
|
||||
#include <QComboBox>
|
||||
#include <QFileDialog>
|
||||
#include <QFormLayout>
|
||||
#include <QPushButton>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#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<QString> 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());
|
||||
}
|
||||
}
|
46
interface/src/ModelSelector.h
Normal file
46
interface/src/ModelSelector.h
Normal file
|
@ -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 <QDialog>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include <SettingHandle.h>
|
||||
|
||||
#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
|
|
@ -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 <QBuffer>
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDebug>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QFile>
|
||||
#include <QFileDialog>
|
||||
#include <QFormLayout>
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QHttpMultiPart>
|
||||
#include <QImage>
|
||||
#include <QJsonDocument>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QStandardPaths>
|
||||
#include <QTemporaryFile>
|
||||
#include <QTextStream>
|
||||
#include <QThread>
|
||||
#include <QVBoxLayout>
|
||||
#include <QVariant>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include <GeometryCache.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <ResourceCache.h>
|
||||
#include <TextureCache.h>
|
||||
|
||||
#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<QString> 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<QNetworkReply*>(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<GeometryCache>()->refresh(_url);
|
||||
auto textureCache = DependencyManager::get<TextureCache>();
|
||||
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<QComboBox*>(_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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <QDialog>
|
||||
#include <QTimer>
|
||||
|
||||
#include <FBXReader.h>
|
||||
#include <SettingHandle.h>
|
||||
|
||||
#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<QByteArray> _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<QString> _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
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<WindowScriptingInterface>().data(), "doCreateWebWindow", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(WebWindowClass*, retVal),
|
||||
Q_ARG(const QString&, file),
|
||||
Q_ARG(QString, context->argument(1).toString()),
|
||||
|
|
|
@ -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<NodeList>()->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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <QLabel>
|
||||
#include <QFormLayout>
|
||||
#include <QVector>
|
||||
#include <QTimer>
|
||||
|
||||
#include "Node.h"
|
||||
#include "BandwidthRecorder.h"
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
#include <QStandardItemModel>
|
||||
#include <QTreeView>
|
||||
|
||||
class QNetworkReply;
|
||||
|
||||
enum ModelType {
|
||||
ENTITY_MODEL,
|
||||
HEAD_MODEL,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<float>();
|
||||
data._scatterings.w = getMieScattering() * 4.0f * glm::pi<float>();
|
||||
}
|
||||
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<float>();
|
||||
data._scatterings.w = getMieScattering() * 4.0f * glm::pi<float>();
|
||||
}
|
||||
|
||||
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()));
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
#include <QObject>
|
||||
#include <QElapsedTimer>
|
||||
#include <QTimer>
|
||||
#include "DependencyManager.h"
|
||||
#include "Node.h"
|
||||
#include "SimpleMovingAverage.h"
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -100,7 +100,6 @@ private:
|
|||
HifiSockAddr _iceServerSockAddr;
|
||||
NetworkPeer _icePeer;
|
||||
bool _isConnected;
|
||||
QTimer* _handshakeTimer;
|
||||
QJsonObject _settingsObject;
|
||||
int _failedSettingsRequests;
|
||||
};
|
||||
|
|
|
@ -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<SharedNodePointer> killedNodes;
|
||||
|
||||
eachNodeHashIterator([&](NodeHash::iterator& it){
|
||||
|
|
|
@ -223,6 +223,8 @@ protected:
|
|||
HifiSockAddr _localSockAddr;
|
||||
HifiSockAddr _publicSockAddr;
|
||||
HifiSockAddr _stunSockAddr;
|
||||
|
||||
QTimer* _silentNodeTimer;
|
||||
|
||||
// XXX can BandwidthRecorder be used for this?
|
||||
int _numCollectedPackets;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<Vertex> *vertices, vector<int> *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(){
|
||||
|
|
|
@ -17,18 +17,25 @@
|
|||
#include <QSharedPointer>
|
||||
#include <QWeakPointer>
|
||||
|
||||
#include <functional>
|
||||
#include <typeinfo>
|
||||
|
||||
#define SINGLETON_DEPENDENCY \
|
||||
friend class DependencyManager;
|
||||
|
||||
class Dependency {
|
||||
public:
|
||||
typedef std::function<void(Dependency* pointer)> 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;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue