mirror of
https://github.com/lubosz/overte.git
synced 2025-04-24 09:23:17 +02:00
Merge branch 'master' of https://github.com/worklist/hifi
This commit is contained in:
commit
88a0aa4221
34 changed files with 952 additions and 381 deletions
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
14
interface/resources/images/close.svg
Normal file
14
interface/resources/images/close.svg
Normal 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 |
5
interface/resources/resources.qrc
Normal file
5
interface/resources/resources.qrc
Normal file
|
@ -0,0 +1,5 @@
|
|||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>images/close.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
|
@ -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) {
|
||||
|
|
|
@ -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__) */
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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__) */
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -1177,8 +1177,6 @@ void VoxelSystem::init() {
|
|||
}
|
||||
|
||||
void VoxelSystem::changeTree(VoxelTree* newTree) {
|
||||
disconnect(_tree, 0, this, 0);
|
||||
|
||||
_tree = newTree;
|
||||
|
||||
_tree->setDirtyBit();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -187,9 +187,12 @@ private:
|
|||
bool _initialized;
|
||||
QScopedPointer<Texture> _billboardTexture;
|
||||
bool _shouldRenderBillboard;
|
||||
bool _modelsDirty;
|
||||
|
||||
void renderBody();
|
||||
void renderBillboard();
|
||||
|
||||
float getBillboardSize() const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -21,7 +21,7 @@ public:
|
|||
|
||||
FaceModel(Head* owningHead);
|
||||
|
||||
void simulate(float deltaTime, bool delayLoad = false);
|
||||
void simulate(float deltaTime);
|
||||
bool render(float alpha);
|
||||
|
||||
protected:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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__) */
|
||||
|
|
Loading…
Reference in a new issue