This commit is contained in:
Philip Rosedale 2014-03-13 21:37:40 -07:00
commit 88a0aa4221
34 changed files with 952 additions and 381 deletions

View file

@ -26,6 +26,10 @@ var PIXELS_PER_EXTRUDE_VOXEL = 16;
var WHEEL_PIXELS_PER_SCALE_CHANGE = 100;
var MAX_VOXEL_SCALE = 1.0;
var MIN_VOXEL_SCALE = 1.0 / Math.pow(2.0, 8.0);
var WHITE_COLOR = { red: 255, green: 255, blue: 255 };
var MAX_PASTE_VOXEL_SCALE = 256;
var MIN_PASTE_VOXEL_SCALE = .256;
var zFightingSizeAdjust = 0.002; // used to adjust preview voxels to prevent z fighting
var previewLineWidth = 1.5;
@ -199,6 +203,8 @@ var voxelToolAt = 0;
var recolorToolAt = 1;
var eyedropperToolAt = 2;
var pasteModeColor = { red: 132, green: 61, blue: 255 };
var voxelTool = Overlays.addOverlay("image", {
x: 0, y: 0, width: toolWidth, height: toolHeight,
subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight },
@ -262,7 +268,7 @@ var thumb = Overlays.addOverlay("image", {
visible: false
});
var pointerVoxelScale = 0; // this is the voxel scale used for click to add or delete
var pointerVoxelScale = Math.floor(MAX_VOXEL_SCALE + MIN_VOXEL_SCALE) / 2; // this is the voxel scale used for click to add or delete
var pointerVoxelScaleSet = false; // if voxel scale has not yet been set, we use the intersection size
var pointerVoxelScaleSteps = 8; // the number of slider position steps
@ -271,6 +277,106 @@ var pointerVoxelScaleMin = Math.pow(2, (1-pointerVoxelScaleOriginStep));
var pointerVoxelScaleMax = Math.pow(2, (pointerVoxelScaleSteps-pointerVoxelScaleOriginStep));
var thumbDeltaPerStep = thumbExtents / (pointerVoxelScaleSteps - 1);
///////////////////////////////////// IMPORT MODULE ///////////////////////////////
// Move the following code to a separate file when include will be available.
var importTree;
var importPreview;
var importBoundaries;
var isImporting;
var importPosition;
var importScale;
function initImport() {
importPreview = Overlays.addOverlay("localvoxels", {
name: "import",
position: { x: 0, y: 0, z: 0},
scale: 1,
visible: false
});
importBoundaries = Overlays.addOverlay("cube", {
position: { x: 0, y: 0, z: 0 },
scale: 1,
color: { red: 128, blue: 128, green: 128 },
solid: false,
visible: false
})
isImporting = false;
importPosition = { x: 0, y: 0, z: 0 };
importScale = 0;
}
function importVoxels() {
if (Clipboard.importVoxels()) {
isImporting = true;
if (importScale <= 0) {
importScale = 1;
}
} else {
isImporting = false;
}
return isImporting;
}
function moveImport(position) {
if (0 < position.x && 0 < position.y && 0 < position.z) {
importPosition = position;
Overlays.editOverlay(importPreview, {
position: { x: importPosition.x, y: importPosition.y, z: importPosition.z }
});
Overlays.editOverlay(importBoundaries, {
position: { x: importPosition.x, y: importPosition.y, z: importPosition.z }
});
}
}
function rescaleImport(scale) {
if (0 < scale) {
importScale = scale;
Overlays.editOverlay(importPreview, {
scale: importScale
});
Overlays.editOverlay(importBoundaries, {
scale: importScale
});
}
}
function showImport(doShow) {
Overlays.editOverlay(importPreview, {
visible: doShow
});
Overlays.editOverlay(importBoundaries, {
visible: doShow
});
}
function placeImport() {
if (isImporting) {
Clipboard.pasteVoxel(importPosition.x, importPosition.y, importPosition.z, importScale);
isImporting = false;
}
}
function cancelImport() {
if (isImporting) {
isImporting = false;
showImport(false);
}
}
function cleanupImport() {
Overlays.deleteOverlay(importPreview);
Overlays.deleteOverlay(importBoundaries);
isImporting = false;
importPostion = { x: 0, y: 0, z: 0 };
importScale = 0;
}
/////////////////////////////////// END IMPORT MODULE /////////////////////////////
initImport();
if (editToolsOn) {
moveTools();
}
@ -295,6 +401,13 @@ function calcScaleFromThumb(newThumbX) {
thumbAt = newThumbX - minThumbX;
thumbStep = Math.floor((thumbAt/ thumbExtents) * (pointerVoxelScaleSteps-1)) + 1;
pointerVoxelScale = Math.pow(2, (thumbStep-pointerVoxelScaleOriginStep));
// if importing, rescale import ...
if (isImporting) {
var importScale = (pointerVoxelScale / MAX_VOXEL_SCALE) * MAX_PASTE_VOXEL_SCALE;
rescaleImport(importScale);
}
// now reset the display accordingly...
calcThumbFromScale(pointerVoxelScale);
@ -308,7 +421,23 @@ function setAudioPosition() {
audioOptions.position = Vec3.sum(camera, forwardVector);
}
function getNewVoxelPosition() {
function getNewPasteVoxel(pickRay) {
var voxelSize = MIN_PASTE_VOXEL_SCALE + (MAX_PASTE_VOXEL_SCALE - MIN_PASTE_VOXEL_SCALE) * pointerVoxelScale - 1;
var origin = { x: pickRay.direction.x, y: pickRay.direction.y, z: pickRay.direction.z };
origin.x += pickRay.origin.x;
origin.y += pickRay.origin.y;
origin.z += pickRay.origin.z;
origin.x -= voxelSize / 2;
origin.y -= voxelSize / 2;
origin.z += voxelSize / 2;
return {origin: origin, voxelSize: voxelSize};
}
function getNewVoxelPosition() {
var camera = Camera.getPosition();
var forwardVector = Quat.getFront(MyAvatar.orientation);
var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, NEW_VOXEL_DISTANCE_FROM_CAMERA));
@ -337,6 +466,7 @@ var trackAsOrbitOrPan = false;
var voxelToolSelected = true;
var recolorToolSelected = false;
var eyedropperToolSelected = false;
var pasteMode = false;
function playRandomAddSound(audioOptions) {
if (Math.random() < 0.33) {
@ -523,6 +653,38 @@ function showPreviewVoxel() {
function showPreviewLines() {
var pickRay = Camera.computePickRay(trackLastMouseX, trackLastMouseY);
if (pasteMode) { // free voxel pasting
Overlays.editOverlay(voxelPreview, { visible: false });
Overlays.editOverlay(linePreviewLeft, { visible: false });
var pasteVoxel = getNewPasteVoxel(pickRay);
// X axis
Overlays.editOverlay(linePreviewBottom, {
position: pasteVoxel.origin,
end: {x: pasteVoxel.origin.x + pasteVoxel.voxelSize, y: pasteVoxel.origin.y, z: pasteVoxel.origin.z },
visible: true
});
// Y axis
Overlays.editOverlay(linePreviewRight, {
position: pasteVoxel.origin,
end: {x: pasteVoxel.origin.x, y: pasteVoxel.origin.y + pasteVoxel.voxelSize, z: pasteVoxel.origin.z },
visible: true
});
// Z axis
Overlays.editOverlay(linePreviewTop, {
position: pasteVoxel.origin,
end: {x: pasteVoxel.origin.x, y: pasteVoxel.origin.y, z: pasteVoxel.origin.z - pasteVoxel.voxelSize },
visible: true
});
return;
}
var intersection = Voxels.findRayIntersection(pickRay);
if (intersection.intersects) {
@ -617,6 +779,8 @@ function trackKeyReleaseEvent(event) {
if (editToolsOn) {
if (event.text == "ESC") {
pointerVoxelScaleSet = false;
pasteMode = false;
moveTools();
}
if (event.text == "-") {
thumbX -= thumbDeltaPerStep;
@ -821,6 +985,23 @@ function mousePressEvent(event) {
var pickRay = Camera.computePickRay(event.x, event.y);
var intersection = Voxels.findRayIntersection(pickRay);
audioOptions.position = Vec3.sum(pickRay.origin, pickRay.direction);
if (isImporting) {
print("placing import...");
placeImport();
showImport(false);
moveTools();
return;
}
if (pasteMode) {
var pasteVoxel = getNewPasteVoxel(pickRay);
Clipboard.pasteVoxel(pasteVoxel.origin.x, pasteVoxel.origin.y, pasteVoxel.origin.z, pasteVoxel.voxelSize);
pasteMode = false;
moveTools();
return;
}
if (intersection.intersects) {
// if the user hasn't updated the
if (!pointerVoxelScaleSet) {
@ -967,25 +1148,42 @@ function cleanupMenus() {
function menuItemEvent(menuItem) {
// handle clipboard items
if (selectToolSelected) {
if (editToolsOn) {
var pickRay = Camera.computePickRay(trackLastMouseX, trackLastMouseY);
var intersection = Voxels.findRayIntersection(pickRay);
selectedVoxel = calculateVoxelFromIntersection(intersection,"select");
if (menuItem == "Copy") {
print("copying...");
Clipboard.copyVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s);
pasteMode = true;
moveTools();
}
if (menuItem == "Cut") {
print("cutting...");
Clipboard.cutVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s);
pasteMode = true;
moveTools();
}
if (menuItem == "Paste") {
print("pasting...");
Clipboard.pasteVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s);
if (isImporting) {
print("placing import...");
placeImport();
showImport(false);
} else {
print("pasting...");
Clipboard.pasteVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s);
}
pasteMode = false;
moveTools();
}
if (menuItem == "Delete") {
print("deleting...");
Clipboard.deleteVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s);
if (isImporting) {
cancelImport();
} else {
Clipboard.deleteVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s);
}
}
if (menuItem == "Export Voxels") {
@ -993,8 +1191,11 @@ function menuItemEvent(menuItem) {
Clipboard.exportVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s);
}
if (menuItem == "Import Voxels") {
print("import");
Clipboard.importVoxels();
print("importing...");
if (importVoxels()) {
showImport(true);
}
moveTools();
}
if (menuItem == "Nudge") {
print("nudge");
@ -1149,19 +1350,23 @@ function moveTools() {
recolorToolOffset = 1,
eyedropperToolOffset = 1;
if (trackAsRecolor || recolorToolSelected) {
var voxelToolColor = WHITE_COLOR;
if (recolorToolSelected) {
recolorToolOffset = 2;
} else if (trackAsEyedropper || eyedropperToolSelected) {
} else if (eyedropperToolSelected) {
eyedropperToolOffset = 2;
} else if (trackAsOrbitOrPan) {
// nothing gets selected in this case...
} else {
if (pasteMode) {
voxelToolColor = pasteModeColor;
}
voxelToolOffset = 2;
}
Overlays.editOverlay(voxelTool, {
subImage: { x: 0, y: toolHeight * voxelToolOffset, width: toolWidth, height: toolHeight },
x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * voxelToolAt), width: toolWidth, height: toolHeight,
color: voxelToolColor,
visible: editToolsOn
});
@ -1323,14 +1528,27 @@ function checkControllers() {
}
function update(deltaTime) {
var newWindowDimensions = Controller.getViewportDimensions();
if (newWindowDimensions.x != windowDimensions.x || newWindowDimensions.y != windowDimensions.y) {
windowDimensions = newWindowDimensions;
moveTools();
}
if (editToolsOn) {
var newWindowDimensions = Controller.getViewportDimensions();
if (newWindowDimensions.x != windowDimensions.x || newWindowDimensions.y != windowDimensions.y) {
windowDimensions = newWindowDimensions;
moveTools();
}
checkControllers();
// Move Import Preview
if (isImporting) {
var position = MyAvatar.position;
var forwardVector = Quat.getFront(MyAvatar.orientation);
var targetPosition = Vec3.sum(position, Vec3.multiply(forwardVector, importScale));
var newPosition = {
x: Math.floor(targetPosition.x / importScale) * importScale,
y: Math.floor(targetPosition.y / importScale) * importScale,
z: Math.floor(targetPosition.z / importScale) * importScale
}
moveImport(newPosition);
}
}
}
@ -1356,6 +1574,11 @@ function wheelEvent(event) {
calcThumbFromScale(pointerVoxelScale);
trackMouseEvent(event);
wheelPixelsMoved = 0;
if (isImporting) {
var importScale = (pointerVoxelScale / MAX_VOXEL_SCALE) * MAX_PASTE_VOXEL_SCALE;
rescaleImport(importScale);
}
}
}
@ -1388,6 +1611,7 @@ function scriptEnding() {
Overlays.deleteOverlay(thumb);
Controller.releaseKeyEvents({ text: "+" });
Controller.releaseKeyEvents({ text: "-" });
cleanupImport();
cleanupMenus();
}
Script.scriptEnding.connect(scriptEnding);

View file

@ -73,8 +73,13 @@ file (GLOB_RECURSE QT_UI_FILES ui/*.ui)
# have qt5 wrap them and generate the appropriate header files
qt5_wrap_ui(QT_UI_HEADERS "${QT_UI_FILES}")
# grab the resource files in resources
file (GLOB_RECURSE QT_RESOURCE_FILES resources/*.qrc)
# have qt5 wrap them and generate the appropriate source files
qt5_add_resources(QT_RESOURCES "${QT_RESOURCE_FILES}")
# add them to the interface source files
set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}")
set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}")
set(QM ${TARGET_NAME}_en.qm)
set(TS ${TARGET_NAME}_en.ts)
@ -225,11 +230,7 @@ if (APPLE)
RUNTIME DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/install" COMPONENT Runtime
)
else (APPLE)
# remove and then copy the resources files beside the executable
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E remove_directory
$<TARGET_FILE_DIR:${TARGET_NAME}>/resources
)
# copy the resources files beside the executable
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_directory
"${PROJECT_SOURCE_DIR}/resources"

View file

@ -4,22 +4,22 @@
<context>
<name>Application</name>
<message>
<location filename="src/Application.cpp" line="1351"/>
<location filename="src/Application.cpp" line="1362"/>
<source>Export Voxels</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Application.cpp" line="1352"/>
<location filename="src/Application.cpp" line="1363"/>
<source>Sparse Voxel Octree Files (*.svo)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Application.cpp" line="3531"/>
<location filename="src/Application.cpp" line="3573"/>
<source>Open Script</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Application.cpp" line="3532"/>
<location filename="src/Application.cpp" line="3574"/>
<source>JavaScript Files (*.js)</source>
<translation type="unfinished"></translation>
</message>
@ -27,25 +27,25 @@
<context>
<name>ChatWindow</name>
<message>
<location filename="ui/chatWindow.ui" line="14"/>
<location filename="../build/interface/ui_chatWindow.h" line="114"/>
<location filename="ui/chatWindow.ui" line="20"/>
<location filename="../build/interface/ui_chatWindow.h" line="143"/>
<source>Chat</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/chatWindow.ui" line="41"/>
<location filename="../build/interface/ui_chatWindow.h" line="115"/>
<location filename="ui/chatWindow.ui" line="50"/>
<location filename="../build/interface/ui_chatWindow.h" line="144"/>
<source>Connecting to XMPP...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/chatWindow.ui" line="60"/>
<location filename="../build/interface/ui_chatWindow.h" line="116"/>
<location filename="ui/chatWindow.ui" line="71"/>
<location filename="../build/interface/ui_chatWindow.h" line="145"/>
<source> online now:</source>
<translation type="unfinished"></translation>
</message>
<message numerus="yes">
<location filename="src/ui/ChatWindow.cpp" line="110"/>
<location filename="src/ui/ChatWindow.cpp" line="124"/>
<source>day</source>
<translation>
<numerusform>%n day</numerusform>
@ -53,7 +53,7 @@
</translation>
</message>
<message numerus="yes">
<location filename="src/ui/ChatWindow.cpp" line="110"/>
<location filename="src/ui/ChatWindow.cpp" line="124"/>
<source>hour</source>
<translation>
<numerusform>%n hour</numerusform>
@ -61,7 +61,7 @@
</translation>
</message>
<message numerus="yes">
<location filename="src/ui/ChatWindow.cpp" line="110"/>
<location filename="src/ui/ChatWindow.cpp" line="124"/>
<source>minute</source>
<translation>
<numerusform>%n minute</numerusform>
@ -76,7 +76,7 @@
</translation>
</message>
<message>
<location filename="src/ui/ChatWindow.cpp" line="163"/>
<location filename="src/ui/ChatWindow.cpp" line="179"/>
<source>%1 online now:</source>
<translation type="unfinished"></translation>
</message>
@ -113,18 +113,18 @@
<context>
<name>Menu</name>
<message>
<location filename="src/Menu.cpp" line="422"/>
<location filename="src/Menu.cpp" line="429"/>
<source>Open .ini config file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Menu.cpp" line="424"/>
<location filename="src/Menu.cpp" line="436"/>
<location filename="src/Menu.cpp" line="431"/>
<location filename="src/Menu.cpp" line="443"/>
<source>Text files (*.ini)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Menu.cpp" line="434"/>
<location filename="src/Menu.cpp" line="441"/>
<source>Save .ini config file</source>
<translation type="unfinished"></translation>
</message>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 15.9 15.9" enable-background="new 0 0 15.9 15.9" xml:space="preserve">
<g>
<g>
<path fill="#666666" d="M15.5,13.7l-1.8,1.8c-0.2,0.2-0.6,0.4-0.9,0.4s-0.7-0.1-0.9-0.4L8,11.6L4,15.5c-0.2,0.2-0.6,0.4-0.9,0.4
s-0.7-0.1-0.9-0.4l-1.8-1.8C0.1,13.5,0,13.1,0,12.8s0.1-0.7,0.4-0.9L4.3,8L0.4,4C0.1,3.8,0,3.4,0,3.1s0.1-0.7,0.4-0.9l1.8-1.8
C2.4,0.1,2.8,0,3.1,0S3.8,0.1,4,0.4L8,4.3l3.9-3.9C12.1,0.1,12.5,0,12.8,0s0.7,0.1,0.9,0.4l1.8,1.8c0.2,0.2,0.4,0.6,0.4,0.9
S15.8,3.8,15.5,4L11.6,8l3.9,3.9c0.2,0.2,0.4,0.6,0.4,0.9S15.8,13.5,15.5,13.7z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 944 B

View file

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>images/close.svg</file>
</qresource>
</RCC>

View file

@ -27,6 +27,7 @@
#include <QColorDialog>
#include <QDesktopWidget>
#include <QCheckBox>
#include <QHBoxLayout>
#include <QImage>
#include <QKeyEvent>
#include <QMainWindow>
@ -62,6 +63,7 @@
#include <UUID.h>
#include <OctreeSceneStats.h>
#include <LocalVoxelsList.h>
#include <FstReader.h>
#include "Application.h"
#include "ClipboardScriptingInterface.h"
@ -121,8 +123,6 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt
QString& Application::resourcesPath() {
#ifdef Q_OS_MAC
static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/../Resources/";
#elif defined Q_OS_LINUX
static QString staticResourcePath = "resources/";
#else
static QString staticResourcePath = QCoreApplication::applicationDirPath() + "/resources/";
#endif
@ -140,12 +140,13 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_fps(120.0f),
_justStarted(true),
_voxelImporter(NULL),
_importSucceded(false),
_sharedVoxelSystem(TREE_SCALE, DEFAULT_MAX_VOXELS_PER_SYSTEM, &_clipboard),
_wantToKillLocalVoxels(false),
_viewFrustum(),
_lastQueriedViewFrustum(),
_lastQueriedTime(usecTimestampNow()),
_audioScope(256, 200, true),
_myAvatar(),
_mirrorViewRect(QRect(MIRROR_VIEW_LEFT_PADDING, MIRROR_VIEW_TOP_PADDING, MIRROR_VIEW_WIDTH, MIRROR_VIEW_HEIGHT)),
_mouseX(0),
_mouseY(0),
@ -291,7 +292,14 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
ResourceCache::setNetworkAccessManager(_networkAccessManager);
ResourceCache::setRequestLimit(3);
_window->setCentralWidget(_glWidget);
QWidget* centralWidget = new QWidget();
QHBoxLayout* mainLayout = new QHBoxLayout();
mainLayout->setSpacing(0);
mainLayout->setContentsMargins(0, 0, 0, 0);
centralWidget->setLayout(mainLayout);
_glWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
centralWidget->layout()->addWidget(_glWidget);
_window->setCentralWidget(centralWidget);
restoreSizeAndPosition();
@ -1368,6 +1376,8 @@ void Application::exportVoxels(const VoxelDetail& sourceVoxel) {
}
void Application::importVoxels() {
_importSucceded = false;
if (!_voxelImporter) {
_voxelImporter = new VoxelImporter(_window);
_voxelImporter->loadSettings(_settings);
@ -1375,6 +1385,7 @@ void Application::importVoxels() {
if (!_voxelImporter->exec()) {
qDebug() << "[DEBUG] Import succeeded." << endl;
_importSucceded = true;
} else {
qDebug() << "[DEBUG] Import failed." << endl;
if (_sharedVoxelSystem.getTree() == _voxelImporter->getVoxelTree()) {
@ -1385,6 +1396,8 @@ void Application::importVoxels() {
// restore the main window's active state
_window->activateWindow();
emit importDone();
}
void Application::cutVoxels(const VoxelDetail& sourceVoxel) {
@ -1476,10 +1489,9 @@ void Application::init() {
// Cleanup of the original shared tree
_sharedVoxelSystem.init();
VoxelTree* tmpTree = _sharedVoxelSystem.getTree();
_sharedVoxelSystem.changeTree(&_clipboard);
delete tmpTree;
_voxelImporter = new VoxelImporter(_window);
_environment.init();
_glowEffect.init();
@ -1522,6 +1534,10 @@ void Application::init() {
}
qDebug("Loaded settings");
// initialize Visage and Faceshift after loading the menu settings
_faceshift.init();
_visage.init();
// fire off an immediate domain-server check in now that settings are loaded
NodeList::getInstance()->sendDomainServerCheckIn();
@ -3457,7 +3473,10 @@ void Application::reloadAllScripts() {
}
void Application::uploadFST() {
_fstReader.zip();
FstReader reader;
if (reader.zip()) {
reader.send();
}
}
void Application::removeScriptName(const QString& fileNameString) {

View file

@ -28,7 +28,6 @@
#include <ParticleEditPacketSender.h>
#include <ScriptEngine.h>
#include <OctreeQuery.h>
#include <FstReader.h>
#include "Audio.h"
#include "BandwidthMeter.h"
@ -156,6 +155,7 @@ public:
VoxelTree* getVoxelTree() { return _voxels.getTree(); }
ParticleTreeRenderer* getParticles() { return &_particles; }
MetavoxelSystem* getMetavoxels() { return &_metavoxels; }
bool getImportSucceded() { return _importSucceded; }
VoxelSystem* getSharedVoxelSystem() { return &_sharedVoxelSystem; }
VoxelTree* getClipboard() { return &_clipboard; }
Environment* getEnvironment() { return &_environment; }
@ -224,6 +224,9 @@ signals:
/// Fired when we're rendering in-world interface elements; allows external parties to hook in.
void renderingInWorldInterface();
/// Fired when the import window is closed
void importDone();
public slots:
void domainChanged(const QString& domainHostname);
void updateWindowTitle();
@ -350,13 +353,13 @@ private:
glm::vec3 _gravity;
// Frame Rate Measurement
int _frameCount;
float _fps;
timeval _applicationStartupTime;
timeval _timerStart, _timerEnd;
timeval _lastTimeUpdated;
bool _justStarted;
Stars _stars;
BuckyBalls _buckyBalls;
@ -364,6 +367,7 @@ private:
VoxelSystem _voxels;
VoxelTree _clipboard; // if I copy/paste
VoxelImporter* _voxelImporter;
bool _importSucceded;
VoxelSystem _sharedVoxelSystem;
ViewFrustum _sharedVoxelSystemViewFrustum;
@ -474,8 +478,6 @@ private:
TouchEvent _lastTouchEvent;
Overlays _overlays;
FstReader _fstReader;
};
#endif /* defined(__interface__Application__) */

View file

@ -9,6 +9,7 @@
#include "ClipboardScriptingInterface.h"
ClipboardScriptingInterface::ClipboardScriptingInterface() {
connect(this, SIGNAL(readyToImport()), Application::getInstance(), SLOT(importVoxels()));
}
void ClipboardScriptingInterface::cutVoxel(const VoxelDetail& sourceVoxel) {
@ -70,12 +71,17 @@ void ClipboardScriptingInterface::exportVoxel(float x, float y, float z, float s
z / (float)TREE_SCALE,
s / (float)TREE_SCALE };
QMetaObject::invokeMethod(Application::getInstance(), "exportVoxels",
Q_ARG(const VoxelDetail&, sourceVoxel));
Application::getInstance()->exportVoxels(sourceVoxel);
}
void ClipboardScriptingInterface::importVoxels() {
QMetaObject::invokeMethod(Application::getInstance(), "importVoxels");
bool ClipboardScriptingInterface::importVoxels() {
qDebug() << "[DEBUG] Importing ... ";
QEventLoop loop;
connect(Application::getInstance(), SIGNAL(importDone()), &loop, SLOT(quit()));
emit readyToImport();
loop.exec();
return Application::getInstance()->getImportSucceded();
}
void ClipboardScriptingInterface::nudgeVoxel(const VoxelDetail& sourceVoxel, const glm::vec3& nudgeVec) {

View file

@ -18,6 +18,9 @@ class ClipboardScriptingInterface : public QObject {
public:
ClipboardScriptingInterface();
signals:
void readyToImport();
public slots:
void cutVoxel(const VoxelDetail& sourceVoxel);
void cutVoxel(float x, float y, float z, float s);
@ -34,7 +37,7 @@ public slots:
void exportVoxel(const VoxelDetail& sourceVoxel);
void exportVoxel(float x, float y, float z, float s);
void importVoxels();
bool importVoxels();
void nudgeVoxel(const VoxelDetail& sourceVoxel, const glm::vec3& nudgeVec);
void nudgeVoxel(float x, float y, float z, float s, const glm::vec3& nudgeVec);

View file

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

View file

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

View file

@ -129,7 +129,10 @@ Menu::Menu() :
this,
SLOT(goTo()));
addDisabledActionAndSeparator(fileMenu, "Upload/Browse");
addActionToQMenuAndActionHash(fileMenu, MenuOption::UploaderAvatarHead, 0, Application::getInstance(), SLOT(uploadFST()));
addActionToQMenuAndActionHash(fileMenu, MenuOption::UploaderAvatarSkeleton, 0, Application::getInstance(), SLOT(uploadFST()));
addDisabledActionAndSeparator(fileMenu, "Settings");
addActionToQMenuAndActionHash(fileMenu, MenuOption::SettingsImport, 0, this, SLOT(importSettings()));
addActionToQMenuAndActionHash(fileMenu, MenuOption::SettingsExport, 0, this, SLOT(exportSettings()));
@ -161,7 +164,6 @@ Menu::Menu() :
QMenu* toolsMenu = addMenu("Tools");
addActionToQMenuAndActionHash(toolsMenu, MenuOption::MetavoxelEditor, 0, this, SLOT(showMetavoxelEditor()));
addActionToQMenuAndActionHash(toolsMenu, MenuOption::FstUploader, 0, Application::getInstance(), SLOT(uploadFST()));
_chatAction = addActionToQMenuAndActionHash(toolsMenu,
MenuOption::Chat,
@ -270,11 +272,16 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::LookAtVectors, 0, false);
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu,
MenuOption::FaceshiftTCP,
MenuOption::Faceshift,
0,
false,
true,
appInstance->getFaceshift(),
SLOT(setTCPEnabled(bool)));
#ifdef HAVE_VISAGE
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Visage, 0, true,
appInstance->getVisage(), SLOT(updateEnabled()));
#endif
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false);
QMenu* handOptionsMenu = developerMenu->addMenu("Hand Options");
@ -1039,25 +1046,20 @@ void Menu::showChat() {
if (!_chatWindow) {
_chatWindow = new ChatWindow();
QMainWindow* mainWindow = Application::getInstance()->getWindow();
// the height of the title bar is given by frameGeometry().height() - geometry().height()
// however, frameGeometry() is initialised after showing (Qt queries the OS windowing system)
// on the other hand, moving a window after showing it flickers; so just use some reasonable value
int titleBarHeight = 16;
_chatWindow->setGeometry(mainWindow->width() - _chatWindow->width(),
mainWindow->geometry().y() + titleBarHeight,
_chatWindow->width(),
mainWindow->height() - titleBarHeight);
_chatWindow->show();
QBoxLayout* boxLayout = static_cast<QBoxLayout*>(mainWindow->centralWidget()->layout());
boxLayout->addWidget(_chatWindow, 0, Qt::AlignRight);
} else {
if (!_chatWindow->isVisible()) {
_chatWindow->show();
}
}
_chatWindow->raise();
}
void Menu::toggleChat() {
#ifdef HAVE_QXMPP
_chatAction->setEnabled(XmppClient::getInstance().getXMPPClient().isConnected());
if (!_chatAction->isEnabled() && _chatWindow) {
_chatWindow->close();
_chatWindow->hide();
}
#endif
}

View file

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

View file

@ -1177,8 +1177,6 @@ void VoxelSystem::init() {
}
void VoxelSystem::changeTree(VoxelTree* newTree) {
disconnect(_tree, 0, this, 0);
_tree = newTree;
_tree->setDirtyBit();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -28,7 +28,7 @@ const int NUM_MESSAGES_TO_TIME_STAMP = 20;
const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?))://\\S+)");
ChatWindow::ChatWindow() :
QDialog(Application::getInstance()->getGLWidget(), Qt::Tool),
QWidget(),
ui(new Ui::ChatWindow),
numMessagesAfterLastTimeStamp(0)
{
@ -39,7 +39,6 @@ ChatWindow::ChatWindow() :
ui->messagePlainTextEdit->installEventFilter(this);
setAttribute(Qt::WA_DeleteOnClose);
#ifdef HAVE_QXMPP
const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient();
if (xmppClient.isConnected()) {
@ -50,6 +49,7 @@ ChatWindow::ChatWindow() :
startTimerForTimeStamps();
} else {
ui->numOnlineLabel->hide();
ui->closeButton->hide();
ui->usersWidget->hide();
ui->messagesScrollArea->hide();
ui->messagePlainTextEdit->hide();
@ -71,6 +71,20 @@ ChatWindow::~ChatWindow() {
delete ui;
}
void ChatWindow::keyPressEvent(QKeyEvent* event) {
QWidget::keyPressEvent(event);
if (event->key() == Qt::Key_Escape) {
hide();
}
}
void ChatWindow::showEvent(QShowEvent* event) {
QWidget::showEvent(event);
if (!event->spontaneous()) {
ui->messagePlainTextEdit->setFocus();
}
}
bool ChatWindow::eventFilter(QObject* sender, QEvent* event) {
Q_UNUSED(sender);
@ -136,9 +150,11 @@ void ChatWindow::startTimerForTimeStamps() {
void ChatWindow::connected() {
ui->connectingToXMPPLabel->hide();
ui->numOnlineLabel->show();
ui->closeButton->show();
ui->usersWidget->show();
ui->messagesScrollArea->show();
ui->messagePlainTextEdit->show();
ui->messagePlainTextEdit->setFocus();
#ifdef HAVE_QXMPP
const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom();
connect(publicChatRoom, SIGNAL(participantsChanged()), this, SLOT(participantsChanged()));

View file

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

View file

@ -40,10 +40,12 @@ void LocalVoxelsOverlay::update(float deltatime) {
_voxelSystem->init();
}
if (_voxelCount != _tree->getOctreeElementsCount()) {
_tree->lockForRead();
if (_visible && _voxelCount != _tree->getOctreeElementsCount()) {
_voxelCount = _tree->getOctreeElementsCount();
_voxelSystem->forceRedrawEntireTree();
}
_tree->unlock();
}
void LocalVoxelsOverlay::render() {
@ -59,8 +61,11 @@ void LocalVoxelsOverlay::render() {
void LocalVoxelsOverlay::setProperties(const QScriptValue &properties) {
Volume3DOverlay::setProperties(properties);
if (properties.property("scale").isValid()) {
setSize(properties.property("scale").toVariant().toFloat());
}
QScriptValue treeName = properties.property("name");
// if "end" property was not there, check to see if they included aliases: endPoint, or p2
if (treeName.isValid()) {
if ((_treeName = treeName.toString()) == DOMAIN_TREE_NAME) {
qDebug() << "addOverlay(): Can't create overlay from domain tree";

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ChatWindow</class>
<widget class="QDialog" name="ChatWindow">
<widget class="QWidget" name="ChatWindow">
<property name="geometry">
<rect>
<x>0</x>
@ -10,9 +10,18 @@
<height>608</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Chat</string>
</property>
<property name="styleSheet">
<string notr="true">font-family: Helvetica, Arial, sans-serif;</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
@ -46,20 +55,53 @@
</widget>
</item>
<item>
<widget class="QLabel" name="numOnlineLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">font-weight: bold; color: palette(shadow); margin-bottom: 4px;</string>
</property>
<property name="text">
<string> online now:</string>
</property>
</widget>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="numOnlineLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">font-weight: bold; color: palette(shadow); margin-bottom: 4px;</string>
</property>
<property name="text">
<string> online now:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="closeButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/images/close.svg</normaloff>:/images/close.svg</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="usersWidget" native="true"/>
@ -67,7 +109,7 @@
<item>
<widget class="QScrollArea" name="messagesScrollArea">
<property name="styleSheet">
<string notr="true">margin-top: 12px; font-family: Helvetica, Arial, sans-serif;</string>
<string notr="true">margin-top: 12px;</string>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
@ -81,7 +123,7 @@
<x>0</x>
<y>0</y>
<width>358</width>
<height>454</height>
<height>464</height>
</rect>
</property>
<property name="styleSheet">
@ -121,6 +163,12 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>60</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">border-color: palette(dark); border-style: solid; border-left-width: 1px; border-right-width: 1px; border-bottom-width: 1px;</string>
</property>
@ -133,6 +181,9 @@
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="tabChangesFocus">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
@ -141,6 +192,25 @@
<tabstop>messagePlainTextEdit</tabstop>
<tabstop>messagesScrollArea</tabstop>
</tabstops>
<resources/>
<connections/>
<resources>
<include location="../resources/resources.qrc"/>
</resources>
<connections>
<connection>
<sender>closeButton</sender>
<signal>clicked()</signal>
<receiver>ChatWindow</receiver>
<slot>hide()</slot>
<hints>
<hint type="sourcelabel">
<x>390</x>
<y>42</y>
</hint>
<hint type="destinationlabel">
<x>550</x>
<y>42</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

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

View file

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

View file

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

View file

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