diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 13b1f2bdcf..46f4d233c2 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -25,7 +26,8 @@ Agent::Agent(const QByteArray& packet) : ThreadedAssignment(packet), _voxelEditSender(), - _particleEditSender() + _particleEditSender(), + _avatarAudioStream(NULL) { // be the parent of the script engine so it gets moved when we do _scriptEngine.setParent(this); @@ -34,6 +36,30 @@ Agent::Agent(const QByteArray& packet) : _scriptEngine.getParticlesScriptingInterface()->setPacketSender(&_particleEditSender); } +Agent::~Agent() { + delete _avatarAudioStream; +} + +const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * SAMPLE_RATE) / (1000 * 1000)) + 0.5); + +void Agent::setSendAvatarAudioStream(bool sendAvatarAudioStream) { + if (sendAvatarAudioStream) { + // the agentAudioStream number of samples is related to the ScriptEngine callback rate + _avatarAudioStream = new int16_t[SCRIPT_AUDIO_BUFFER_SAMPLES]; + + // fill the _audioStream with zeroes to start + memset(_avatarAudioStream, 0, SCRIPT_AUDIO_BUFFER_SAMPLES * sizeof(int16_t)); + + _scriptEngine.setNumAvatarAudioBufferSamples(SCRIPT_AUDIO_BUFFER_SAMPLES); + _scriptEngine.setAvatarAudioBuffer(_avatarAudioStream); + } else { + delete _avatarAudioStream; + _avatarAudioStream = NULL; + + _scriptEngine.setAvatarAudioBuffer(NULL); + } +} + void Agent::readPendingDatagrams() { QByteArray receivedPacket; HifiSockAddr senderSockAddr; diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 1b41636874..a051f42faf 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -28,12 +28,17 @@ class Agent : public ThreadedAssignment { Q_OBJECT Q_PROPERTY(bool isAvatar READ isAvatar WRITE setIsAvatar) + Q_PROPERTY(bool sendAvatarAudioStream READ isSendingAvatarAudioStream WRITE setSendAvatarAudioStream) public: Agent(const QByteArray& packet); + ~Agent(); void setIsAvatar(bool isAvatar) { QMetaObject::invokeMethod(&_scriptEngine, "setIsAvatar", Q_ARG(bool, isAvatar)); } bool isAvatar() const { return _scriptEngine.isAvatar(); } + void setSendAvatarAudioStream(bool sendAvatarAudioStream); + bool isSendingAvatarAudioStream() const { return (bool) _scriptEngine.sendsAvatarAudioStream(); } + public slots: void run(); void readPendingDatagrams(); @@ -45,6 +50,8 @@ private: ParticleTreeHeadlessViewer _particleViewer; VoxelTreeHeadlessViewer _voxelViewer; + + int16_t* _avatarAudioStream; }; #endif /* defined(__hifi__Agent__) */ diff --git a/examples/cameraExample.js b/examples/cameraExample.js index ddfff15935..6e3c51a348 100644 --- a/examples/cameraExample.js +++ b/examples/cameraExample.js @@ -74,7 +74,7 @@ function checkCamera(deltaTime) { if (yaw < -360) { yaw += 360; } - var orientation = Quat.fromPitchYawRoll(pitch, yaw, roll); + var orientation = Quat.fromPitchYawRollDegrees(pitch, yaw, roll); Camera.setOrientation(orientation); } } diff --git a/examples/crazylegs.js b/examples/crazylegs.js index 0f26088ae0..0daf2275f3 100644 --- a/examples/crazylegs.js +++ b/examples/crazylegs.js @@ -14,10 +14,10 @@ var cumulativeTime = 0.0; Script.update.connect(function(deltaTime) { cumulativeTime += deltaTime; - MyAvatar.setJointData("joint_R_hip", Quat.fromPitchYawRoll(0.0, 0.0, AMPLITUDE * Math.sin(cumulativeTime * FREQUENCY))); - MyAvatar.setJointData("joint_L_hip", Quat.fromPitchYawRoll(0.0, 0.0, -AMPLITUDE * Math.sin(cumulativeTime * FREQUENCY))); - MyAvatar.setJointData("joint_R_knee", Quat.fromPitchYawRoll(0.0, 0.0, + MyAvatar.setJointData("joint_R_hip", Quat.fromPitchYawRollDegrees(0.0, 0.0, AMPLITUDE * Math.sin(cumulativeTime * FREQUENCY))); + MyAvatar.setJointData("joint_L_hip", Quat.fromPitchYawRollDegrees(0.0, 0.0, -AMPLITUDE * Math.sin(cumulativeTime * FREQUENCY))); + MyAvatar.setJointData("joint_R_knee", Quat.fromPitchYawRollDegrees(0.0, 0.0, AMPLITUDE * (1.0 + Math.sin(cumulativeTime * FREQUENCY)))); - MyAvatar.setJointData("joint_L_knee", Quat.fromPitchYawRoll(0.0, 0.0, + MyAvatar.setJointData("joint_L_knee", Quat.fromPitchYawRollDegrees(0.0, 0.0, AMPLITUDE * (1.0 - Math.sin(cumulativeTime * FREQUENCY)))); }); diff --git a/examples/editVoxels.js b/examples/editVoxels.js index b083128485..31d483d798 100644 --- a/examples/editVoxels.js +++ b/examples/editVoxels.js @@ -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); diff --git a/examples/gun.js b/examples/gun.js index dee01fb84d..45b7487f25 100644 --- a/examples/gun.js +++ b/examples/gun.js @@ -78,7 +78,7 @@ function update(deltaTime) { // Check for mouseLook movement, update rotation // rotate body yaw for yaw received from mouse - var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3( { x: 0, y: yawFromMouse, z: 0 } )); + var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Radians( { x: 0, y: yawFromMouse, z: 0 } )); MyAvatar.orientation = newOrientation; yawFromMouse = 0; diff --git a/examples/hydraMove.js b/examples/hydraMove.js index 1f9634a8e6..92c594df9e 100644 --- a/examples/hydraMove.js +++ b/examples/hydraMove.js @@ -184,7 +184,7 @@ function flyWithHydra(deltaTime) { // change the body yaw based on our x controller var orientation = MyAvatar.orientation; - var deltaOrientation = Quat.fromPitchYawRoll(0, (-1 * viewJoystickPosition.x * JOYSTICK_YAW_MAG * deltaTime), 0); + var deltaOrientation = Quat.fromPitchYawRollDegrees(0, (-1 * viewJoystickPosition.x * JOYSTICK_YAW_MAG * deltaTime), 0); MyAvatar.orientation = Quat.multiply(orientation, deltaOrientation); // change the headPitch based on our x controller diff --git a/examples/lookWithMouse.js b/examples/lookWithMouse.js index 878813a94a..ef8f11ec16 100644 --- a/examples/lookWithMouse.js +++ b/examples/lookWithMouse.js @@ -54,7 +54,7 @@ function update(deltaTime) { print("update()..."); } // rotate body yaw for yaw received from mouse - var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3( { x: 0, y: yawFromMouse, z: 0 } )); + var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Radians( { x: 0, y: yawFromMouse, z: 0 } )); if (wantDebugging) { print("changing orientation" + " [old]MyAvatar.orientation="+MyAvatar.orientation.x + "," + MyAvatar.orientation.y + "," diff --git a/examples/lookWithTouch.js b/examples/lookWithTouch.js index ddc42c04c0..852573aea6 100644 --- a/examples/lookWithTouch.js +++ b/examples/lookWithTouch.js @@ -45,7 +45,7 @@ function touchUpdateEvent(event) { function update(deltaTime) { // rotate body yaw for yaw received from mouse - var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3( { x: 0, y: yawFromMouse, z: 0 } )); + var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromPitchYawRollRadians(0, yawFromMouse, 0)); if (wantDebugging) { print("changing orientation" + " [old]MyAvatar.orientation="+MyAvatar.orientation.x + "," + MyAvatar.orientation.y + "," diff --git a/examples/multitouchExample.js b/examples/multitouchExample.js index 1041651c7b..51bbcc2c20 100644 --- a/examples/multitouchExample.js +++ b/examples/multitouchExample.js @@ -92,7 +92,7 @@ Controller.touchEndEvent.connect(touchEndEvent); function update(deltaTime) { // rotate body yaw for yaw received from multitouch rotate - var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3( { x: 0, y: yawFromMultiTouch, z: 0 } )); + var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Radians( { x: 0, y: yawFromMultiTouch, z: 0 } )); if (wantDebugging) { print("changing orientation" + " [old]MyAvatar.orientation="+MyAvatar.orientation.x + "," + MyAvatar.orientation.y + "," diff --git a/examples/seeingVoxelsExample.js b/examples/seeingVoxelsExample.js index 62ebd599ee..257e869b5b 100644 --- a/examples/seeingVoxelsExample.js +++ b/examples/seeingVoxelsExample.js @@ -17,7 +17,7 @@ var yawMin = 20; var isLocal = false; // set up our VoxelViewer with a position and orientation -var orientation = Quat.fromPitchYawRoll(0, yaw, 0); +var orientation = Quat.fromPitchYawRollDegrees(0, yaw, 0); function init() { if (isLocal) { @@ -40,7 +40,7 @@ function keepLooking(deltaTime) { count++; if (count % 10 == 0) { yaw += yawDirection; - orientation = Quat.fromPitchYawRoll(0, yaw, 0); + orientation = Quat.fromPitchYawRollDegrees(0, yaw, 0); if (yaw > yawMax || yaw < yawMin) { yawDirection = yawDirection * -1; } diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index d049fc0154..f39e7d7e50 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -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 - $/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" diff --git a/interface/interface_en.ts b/interface/interface_en.ts index 175a1f4525..f07f50f287 100644 --- a/interface/interface_en.ts +++ b/interface/interface_en.ts @@ -4,22 +4,22 @@ Application - + Export Voxels - + Sparse Voxel Octree Files (*.svo) - + Open Script - + JavaScript Files (*.js) @@ -27,25 +27,25 @@ ChatWindow - - + + Chat - - + + Connecting to XMPP... - - + + online now: - + day %n day @@ -53,7 +53,7 @@ - + hour %n hour @@ -61,7 +61,7 @@ - + minute %n minute @@ -76,7 +76,7 @@ - + %1 online now: @@ -113,18 +113,18 @@ Menu - + Open .ini config file - - + + Text files (*.ini) - + Save .ini config file diff --git a/interface/resources/images/close.svg b/interface/resources/images/close.svg new file mode 100644 index 0000000000..8fe4bf4bdb --- /dev/null +++ b/interface/resources/images/close.svg @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/interface/resources/resources.qrc b/interface/resources/resources.qrc new file mode 100644 index 0000000000..372fa8b1d4 --- /dev/null +++ b/interface/resources/resources.qrc @@ -0,0 +1,5 @@ + + + images/close.svg + + diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 854e449c23..37b2810c68 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -62,6 +62,7 @@ #include #include #include +#include #include "Application.h" #include "ClipboardScriptingInterface.h" @@ -121,8 +122,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 +139,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), @@ -1368,6 +1368,8 @@ void Application::exportVoxels(const VoxelDetail& sourceVoxel) { } void Application::importVoxels() { + _importSucceded = false; + if (!_voxelImporter) { _voxelImporter = new VoxelImporter(_window); _voxelImporter->loadSettings(_settings); @@ -1375,6 +1377,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 +1388,8 @@ void Application::importVoxels() { // restore the main window's active state _window->activateWindow(); + + emit importDone(); } void Application::cutVoxels(const VoxelDetail& sourceVoxel) { @@ -1476,10 +1481,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 +1526,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 +3465,10 @@ void Application::reloadAllScripts() { } void Application::uploadFST() { - _fstReader.zip(); + FstReader reader; + if (reader.zip()) { + reader.send(); + } } void Application::removeScriptName(const QString& fileNameString) { diff --git a/interface/src/Application.h b/interface/src/Application.h index de6ac2ad06..a9fa04904f 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -28,7 +28,6 @@ #include #include #include -#include #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__) */ diff --git a/interface/src/ClipboardScriptingInterface.cpp b/interface/src/ClipboardScriptingInterface.cpp index d9a8f04d90..2ddef524aa 100644 --- a/interface/src/ClipboardScriptingInterface.cpp +++ b/interface/src/ClipboardScriptingInterface.cpp @@ -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) { diff --git a/interface/src/ClipboardScriptingInterface.h b/interface/src/ClipboardScriptingInterface.h index 99747f56f6..78e72d6de0 100644 --- a/interface/src/ClipboardScriptingInterface.h +++ b/interface/src/ClipboardScriptingInterface.h @@ -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); diff --git a/interface/src/GLCanvas.cpp b/interface/src/GLCanvas.cpp index 7bc79e56d8..b68c4fdacd 100644 --- a/interface/src/GLCanvas.cpp +++ b/interface/src/GLCanvas.cpp @@ -11,18 +11,26 @@ #include "GLCanvas.h" #include #include +#include -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()) { diff --git a/interface/src/GLCanvas.h b/interface/src/GLCanvas.h index 0f0cb5c7d0..e6dcc38977 100644 --- a/interface/src/GLCanvas.h +++ b/interface/src/GLCanvas.h @@ -10,13 +10,19 @@ #define __hifi__GLCanvas__ #include +#include /// 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__) */ diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index f73da0400a..c4b7fdcdd6 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -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"); @@ -1037,27 +1044,20 @@ void Menu::showMetavoxelEditor() { 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(); + Application::getInstance()->getWindow()->addDockWidget(Qt::RightDockWidgetArea, _chatWindow = new ChatWindow()); + + } else { + if (!_chatWindow->toggleViewAction()->isChecked()) { + _chatWindow->toggleViewAction()->trigger(); + } } - _chatWindow->raise(); } void Menu::toggleChat() { #ifdef HAVE_QXMPP _chatAction->setEnabled(XmppClient::getInstance().getXMPPClient().isConnected()); - if (!_chatAction->isEnabled() && _chatWindow) { - _chatWindow->close(); + if (!_chatAction->isEnabled() && _chatWindow && _chatWindow->toggleViewAction()->isChecked()) { + _chatWindow->toggleViewAction()->trigger(); } #endif } diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 00ef51178a..466568cc35 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -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"; diff --git a/interface/src/VoxelSystem.cpp b/interface/src/VoxelSystem.cpp index aa628bac7b..a8bdeb1114 100644 --- a/interface/src/VoxelSystem.cpp +++ b/interface/src/VoxelSystem.cpp @@ -1177,8 +1177,6 @@ void VoxelSystem::init() { } void VoxelSystem::changeTree(VoxelTree* newTree) { - disconnect(_tree, 0, this, 0); - _tree = newTree; _tree->setDirtyBit(); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index b7e3d675a5..bcf24a7bfb 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -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; } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index bba86828cb..2bd7fc89e8 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -187,9 +187,12 @@ private: bool _initialized; QScopedPointer _billboardTexture; bool _shouldRenderBillboard; + bool _modelsDirty; void renderBody(); void renderBillboard(); + + float getBillboardSize() const; }; #endif diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index eba8f7bf1a..db6c3fe98d 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -18,9 +18,9 @@ FaceModel::FaceModel(Head* owningHead) : { } -void FaceModel::simulate(float deltaTime, bool delayLoad) { +void FaceModel::simulate(float deltaTime) { + QVector newJointStates = updateGeometry(); if (!isActive()) { - Model::simulate(deltaTime, delayLoad); return; } Avatar* owningAvatar = static_cast(_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) { diff --git a/interface/src/avatar/FaceModel.h b/interface/src/avatar/FaceModel.h index 597075dc42..d0f0f6baef 100644 --- a/interface/src/avatar/FaceModel.h +++ b/interface/src/avatar/FaceModel.h @@ -21,7 +21,7 @@ public: FaceModel(Head* owningHead); - void simulate(float deltaTime, bool delayLoad = false); + void simulate(float deltaTime); bool render(float alpha); protected: diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 6c2bad865f..a9b85ffce2 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -36,9 +36,9 @@ Head::Head(Avatar* owningAvatar) : _leftEyeBlinkVelocity(0.0f), _rightEyeBlinkVelocity(0.0f), _timeWithoutTalking(0.0f), - _tweakedPitch(0.f), - _tweakedYaw(0.f), - _tweakedRoll(0.f), + _pitchTweak(0.f), + _yawTweak(0.f), + _rollTweak(0.f), _isCameraMoving(false), _faceModel(this) { @@ -202,15 +202,15 @@ glm::vec3 Head::getScalePivot() const { } float Head::getTweakedYaw() const { - return glm::clamp(_yaw + _tweakedYaw, MIN_HEAD_YAW, MAX_HEAD_YAW); + return glm::clamp(_yaw + _yawTweak, MIN_HEAD_YAW, MAX_HEAD_YAW); } float Head::getTweakedPitch() const { - return glm::clamp(_pitch + _tweakedPitch, MIN_HEAD_PITCH, MAX_HEAD_PITCH); + return glm::clamp(_pitch + _pitchTweak, MIN_HEAD_PITCH, MAX_HEAD_PITCH); } float Head::getTweakedRoll() const { - return glm::clamp(_roll + _tweakedRoll, MIN_HEAD_ROLL, MAX_HEAD_ROLL); + return glm::clamp(_roll + _rollTweak, MIN_HEAD_ROLL, MAX_HEAD_ROLL); } void Head::applyCollision(CollisionInfo& collision) { diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index 7e7a96a3a7..a9ea9b4cc6 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -70,10 +70,15 @@ public: /// Returns the point about which scaling occurs. glm::vec3 getScalePivot() const; - void tweakPitch(float pitch) { _tweakedPitch = pitch; } - void tweakYaw(float yaw) { _tweakedYaw = yaw; } - void tweakRoll(float roll) { _tweakedRoll = roll; } + void setPitchTweak(float pitch) { _pitchTweak = pitch; } + float getPitchTweak() const { return _pitchTweak; } + void setYawTweak(float yaw) { _yawTweak = yaw; } + float getYawTweak() const { return _yawTweak; } + + void setRollTweak(float roll) { _rollTweak = roll; } + float getRollTweak() const { return _rollTweak; } + virtual float getTweakedPitch() const; virtual float getTweakedYaw() const; virtual float getTweakedRoll() const; @@ -104,9 +109,9 @@ private: float _timeWithoutTalking; // tweaked angles affect the rendered head, but not the camera - float _tweakedPitch; - float _tweakedYaw; - float _tweakedRoll; + float _pitchTweak; + float _yawTweak; + float _rollTweak; bool _isCameraMoving; FaceModel _faceModel; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 0a2462e656..e4aaa07a4b 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -363,8 +363,9 @@ void MyAvatar::updateFromGyros(float deltaTime) { // restore rotation, lean to neutral positions const float RESTORE_PERIOD = 1.f; // seconds float restorePercentage = glm::clamp(deltaTime/RESTORE_PERIOD, 0.f, 1.f); - head->setYaw(glm::mix(head->getYaw(), 0.0f, restorePercentage)); - head->setRoll(glm::mix(head->getRoll(), 0.0f, restorePercentage)); + head->setPitchTweak(glm::mix(head->getPitchTweak(), 0.0f, restorePercentage)); + head->setYawTweak(glm::mix(head->getYawTweak(), 0.0f, restorePercentage)); + head->setRollTweak(glm::mix(head->getRollTweak(), 0.0f, restorePercentage)); head->setLeanSideways(glm::mix(head->getLeanSideways(), 0.0f, restorePercentage)); head->setLeanForward(glm::mix(head->getLeanForward(), 0.0f, restorePercentage)); return; @@ -375,9 +376,9 @@ void MyAvatar::updateFromGyros(float deltaTime) { const float AVATAR_HEAD_PITCH_MAGNIFY = 1.0f; const float AVATAR_HEAD_YAW_MAGNIFY = 1.0f; const float AVATAR_HEAD_ROLL_MAGNIFY = 1.0f; - head->tweakPitch(estimatedRotation.x * AVATAR_HEAD_PITCH_MAGNIFY); - head->tweakYaw(estimatedRotation.y * AVATAR_HEAD_YAW_MAGNIFY); - head->tweakRoll(estimatedRotation.z * AVATAR_HEAD_ROLL_MAGNIFY); + head->setPitchTweak(estimatedRotation.x * AVATAR_HEAD_PITCH_MAGNIFY); + head->setYawTweak(estimatedRotation.y * AVATAR_HEAD_YAW_MAGNIFY); + head->setRollTweak(estimatedRotation.z * AVATAR_HEAD_ROLL_MAGNIFY); // Update torso lean distance based on accelerometer data const float TORSO_LENGTH = 0.5f; diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 7edf48602e..1ffa3d838e 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -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 diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 533f22975f..0bcbcef2ea 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -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 diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp index 4fe89ff98b..88974ce493 100644 --- a/interface/src/devices/Faceshift.cpp +++ b/interface/src/devices/Faceshift.cpp @@ -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; diff --git a/interface/src/devices/Faceshift.h b/interface/src/devices/Faceshift.h index 908354ae9d..a0898c446d 100644 --- a/interface/src/devices/Faceshift.h +++ b/interface/src/devices/Faceshift.h @@ -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& coefficients) const; +signals: + + void connectionStateChanged(); + public slots: void setTCPEnabled(bool enabled); diff --git a/interface/src/devices/Visage.cpp b/interface/src/devices/Visage.cpp index 727c083265..b96ef1ee72 100644 --- a/interface/src/devices/Visage.cpp +++ b/interface/src/devices/Visage.cpp @@ -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 >& 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 +} diff --git a/interface/src/devices/Visage.h b/interface/src/devices/Visage.h index c238b3bb8c..7e50812ba7 100644 --- a/interface/src/devices/Visage.h +++ b/interface/src/devices/Visage.h @@ -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 > _actionUnitIndexMap; #endif + void setEnabled(bool enabled); + + bool _enabled; bool _active; glm::quat _headRotation; glm::vec3 _headTranslation; diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 21fdc80858..60fae5e596 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -156,142 +156,9 @@ void Model::updateShapePositions() { } } -void Model::simulate(float deltaTime, bool delayLoad) { - // update our LOD - QVector 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, 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::updateGeometry() { + QVector 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 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::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& 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, 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::updateGeometry(bool delayLoad) { - QVector 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 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::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(); diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index c7aadee8dc..165465d2cc 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -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 _meshStates; + QVector updateGeometry(); + void simulate(float deltaTime, bool fullUpdate, const QVector& newJointStates); + /// Updates the state of the joint at the specified index. virtual void updateJointState(int index); @@ -256,7 +259,6 @@ protected: private: - QVector updateGeometry(bool delayLoad); void applyNextGeometry(); void deleteGeometry(); void renderMeshes(float alpha, bool translucent); diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index 5fc5d56430..55f32c5c7c 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -28,18 +28,21 @@ const int NUM_MESSAGES_TO_TIME_STAMP = 20; const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?))://\\S+)"); ChatWindow::ChatWindow() : - QDialog(Application::getInstance()->getGLWidget(), Qt::Tool), ui(new Ui::ChatWindow), numMessagesAfterLastTimeStamp(0) { - ui->setupUi(this); + QWidget* widget = new QWidget(); + setWidget(widget); + + ui->setupUi(widget); FlowLayout* flowLayout = new FlowLayout(0, 4, 4); ui->usersWidget->setLayout(flowLayout); ui->messagePlainTextEdit->installEventFilter(this); - setAttribute(Qt::WA_DeleteOnClose); + ui->closeButton->hide(); + #ifdef HAVE_QXMPP const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient(); if (xmppClient.isConnected()) { @@ -50,6 +53,7 @@ ChatWindow::ChatWindow() : startTimerForTimeStamps(); } else { ui->numOnlineLabel->hide(); + ui->closeButton->hide(); ui->usersWidget->hide(); ui->messagesScrollArea->hide(); ui->messagePlainTextEdit->hide(); @@ -71,6 +75,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 +154,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())); diff --git a/interface/src/ui/ChatWindow.h b/interface/src/ui/ChatWindow.h index 9250da8933..da8d423b9d 100644 --- a/interface/src/ui/ChatWindow.h +++ b/interface/src/ui/ChatWindow.h @@ -9,8 +9,8 @@ #ifndef __interface__ChatWindow__ #define __interface__ChatWindow__ -#include #include +#include #include #include @@ -26,13 +26,16 @@ namespace Ui { class ChatWindow; } -class ChatWindow : public QDialog { +class ChatWindow : public QDockWidget { Q_OBJECT public: ChatWindow(); ~ChatWindow(); + virtual void keyPressEvent(QKeyEvent *event); + virtual void showEvent(QShowEvent* event); + protected: bool eventFilter(QObject* sender, QEvent* event); diff --git a/interface/src/ui/LocalVoxelsOverlay.cpp b/interface/src/ui/LocalVoxelsOverlay.cpp index 01a885dedc..7eaf9ed5c5 100644 --- a/interface/src/ui/LocalVoxelsOverlay.cpp +++ b/interface/src/ui/LocalVoxelsOverlay.cpp @@ -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"; diff --git a/interface/ui/chatWindow.ui b/interface/ui/chatWindow.ui index d254efb71a..1106fca3cd 100644 --- a/interface/ui/chatWindow.ui +++ b/interface/ui/chatWindow.ui @@ -1,7 +1,7 @@ ChatWindow - + 0 @@ -10,9 +10,18 @@ 608 + + + 400 + 0 + + Chat + + font-family: Helvetica, Arial, sans-serif; + 0 @@ -46,20 +55,53 @@ - - - - 0 - 0 - - - - font-weight: bold; color: palette(shadow); margin-bottom: 4px; - - - online now: - - + + + + + + 0 + 0 + + + + font-weight: bold; color: palette(shadow); margin-bottom: 4px; + + + online now: + + + + + + + + 0 + 0 + + + + + 16 + 16 + + + + Qt::NoFocus + + + + + + + :/images/close.svg:/images/close.svg + + + true + + + + @@ -67,7 +109,7 @@ - margin-top: 12px; font-family: Helvetica, Arial, sans-serif; + margin-top: 12px; Qt::ScrollBarAlwaysOff @@ -81,7 +123,7 @@ 0 0 358 - 454 + 464 @@ -121,6 +163,12 @@ 0 + + + 0 + 60 + + border-color: palette(dark); border-style: solid; border-left-width: 1px; border-right-width: 1px; border-bottom-width: 1px; @@ -133,6 +181,9 @@ QAbstractScrollArea::AdjustToContents + + true + @@ -141,6 +192,25 @@ messagePlainTextEdit messagesScrollArea - - + + + + + + closeButton + clicked() + ChatWindow + hide() + + + 390 + 42 + + + 550 + 42 + + + + diff --git a/libraries/script-engine/src/Quat.cpp b/libraries/script-engine/src/Quat.cpp index 14025f0c67..c939811db4 100644 --- a/libraries/script-engine/src/Quat.cpp +++ b/libraries/script-engine/src/Quat.cpp @@ -22,14 +22,22 @@ glm::quat Quat::multiply(const glm::quat& q1, const glm::quat& q2) { return q1 * q2; } -glm::quat Quat::fromVec3(const glm::vec3& eulerAngles) { +glm::quat Quat::fromVec3Degrees(const glm::vec3& eulerAngles) { return glm::quat(glm::radians(eulerAngles)); } -glm::quat Quat::fromPitchYawRoll(float pitch, float yaw, float roll) { +glm::quat Quat::fromVec3Radians(const glm::vec3& eulerAngles) { + return glm::quat(eulerAngles); +} + +glm::quat Quat::fromPitchYawRollDegrees(float pitch, float yaw, float roll) { return glm::quat(glm::radians(glm::vec3(pitch, yaw, roll))); } +glm::quat Quat::fromPitchYawRollRadians(float pitch, float yaw, float roll) { + return glm::quat(glm::vec3(pitch, yaw, roll)); +} + glm::quat Quat::inverse(const glm::quat& q) { return glm::inverse(q); } diff --git a/libraries/script-engine/src/Quat.h b/libraries/script-engine/src/Quat.h index 3e5f46682c..02c0a3e147 100644 --- a/libraries/script-engine/src/Quat.h +++ b/libraries/script-engine/src/Quat.h @@ -23,8 +23,10 @@ class Quat : public QObject { public slots: glm::quat multiply(const glm::quat& q1, const glm::quat& q2); - glm::quat fromVec3(const glm::vec3& vec3); - glm::quat fromPitchYawRoll(float pitch, float yaw, float roll); // degrees + glm::quat fromVec3Degrees(const glm::vec3& vec3); // degrees + glm::quat fromVec3Radians(const glm::vec3& vec3); // radians + glm::quat fromPitchYawRollDegrees(float pitch, float yaw, float roll); // degrees + glm::quat fromPitchYawRollRadians(float pitch, float yaw, float roll); // radians glm::quat inverse(const glm::quat& q); glm::vec3 getFront(const glm::quat& orientation); glm::vec3 getRight(const glm::quat& orientation); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 04916687fb..9a672317ae 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -28,8 +28,6 @@ #include "LocalVoxels.h" #include "ScriptEngine.h" -const unsigned int VISUAL_DATA_CALLBACK_USECS = (1.0 / 60.0) * 1000 * 1000; - int ScriptEngine::_scriptNumber = 1; VoxelsScriptingInterface ScriptEngine::_voxelsScriptingInterface; ParticlesScriptingInterface ScriptEngine::_particlesScriptingInterface; @@ -54,6 +52,7 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, co _avatarIdentityTimer(NULL), _avatarBillboardTimer(NULL), _timerFunctionMap(), + _avatarAudioBuffer(NULL), _controllerScriptingInterface(controllerScriptingInterface), _avatarData(NULL), _wantMenuItems(wantMenuItems), @@ -77,9 +76,6 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, co _scriptNumber++; } -ScriptEngine::~ScriptEngine() { -} - void ScriptEngine::setIsAvatar(bool isAvatar) { _isAvatar = isAvatar; @@ -169,8 +165,8 @@ void ScriptEngine::init() { _engine.globalObject().setProperty("TREE_SCALE", treeScaleValue); // let the VoxelPacketSender know how frequently we plan to call it - _voxelsScriptingInterface.getVoxelPacketSender()->setProcessCallIntervalHint(VISUAL_DATA_CALLBACK_USECS); - _particlesScriptingInterface.getParticlePacketSender()->setProcessCallIntervalHint(VISUAL_DATA_CALLBACK_USECS); + _voxelsScriptingInterface.getVoxelPacketSender()->setProcessCallIntervalHint(SCRIPT_DATA_CALLBACK_USECS); + _particlesScriptingInterface.getParticlePacketSender()->setProcessCallIntervalHint(SCRIPT_DATA_CALLBACK_USECS); } @@ -228,7 +224,7 @@ void ScriptEngine::run() { qint64 lastUpdate = usecTimestampNow(); while (!_isFinished) { - int usecToSleep = usecTimestamp(&startTime) + (thisFrame++ * VISUAL_DATA_CALLBACK_USECS) - usecTimestampNow(); + int usecToSleep = usecTimestamp(&startTime) + (thisFrame++ * SCRIPT_DATA_CALLBACK_USECS) - usecTimestampNow(); if (usecToSleep > 0) { usleep(usecToSleep); } @@ -268,6 +264,21 @@ void ScriptEngine::run() { avatarPacket.append(_avatarData->toByteArray()); nodeList->broadcastToNodes(avatarPacket, NodeSet() << NodeType::AvatarMixer); + + if (_avatarAudioBuffer && _numAvatarAudioBufferSamples > 0) { + // if have an avatar audio stream then send it out to our audio-mixer + QByteArray audioPacket = byteArrayWithPopulatedHeader(PacketTypeMicrophoneAudioNoEcho); + QDataStream packetStream(&audioPacket, QIODevice::Append); + + // use the orientation and position of this avatar for the source of this audio + packetStream.writeRawData(reinterpret_cast(&_avatarData->getPosition()), sizeof(glm::vec3)); + glm::quat headOrientation = _avatarData->getHeadOrientation(); + packetStream.writeRawData(reinterpret_cast(&headOrientation), sizeof(glm::quat)); + packetStream.writeRawData(reinterpret_cast(_avatarAudioBuffer), + _numAvatarAudioBufferSamples * sizeof(int16_t)); + + nodeList->broadcastToNodes(audioPacket, NodeSet() << NodeType::AudioMixer); + } } qint64 now = usecTimestampNow(); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 186524eb7f..606d0aabf4 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -20,14 +20,16 @@ #include -class ParticlesScriptingInterface; - #include "AbstractControllerScriptingInterface.h" #include "Quat.h" #include "Vec3.h" +class ParticlesScriptingInterface; + const QString NO_SCRIPT(""); +const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0 / 60.0f) * 1000 * 1000) + 0.5); + class ScriptEngine : public QObject { Q_OBJECT public: @@ -35,8 +37,6 @@ public: const QString& scriptMenuName = QString(""), AbstractControllerScriptingInterface* controllerScriptingInterface = NULL); - ~ScriptEngine(); - /// Access the VoxelsScriptingInterface in order to initialize it with a custom packet sender and jurisdiction listener static VoxelsScriptingInterface* getVoxelsScriptingInterface() { return &_voxelsScriptingInterface; } @@ -56,6 +56,11 @@ public: void setAvatarData(AvatarData* avatarData, const QString& objectName); + void setAvatarAudioBuffer(int16_t* avatarAudioBuffer) { _avatarAudioBuffer = avatarAudioBuffer; } + bool sendsAvatarAudioStream() const { return (bool) _avatarAudioBuffer; } + void setNumAvatarAudioBufferSamples(int numAvatarAudioBufferSamples) + { _numAvatarAudioBufferSamples = numAvatarAudioBufferSamples; } + void init(); void run(); /// runs continuously until Agent.stop() is called void evaluate(); /// initializes the engine, and evaluates the script, but then returns control to caller @@ -86,6 +91,8 @@ protected: QTimer* _avatarIdentityTimer; QTimer* _avatarBillboardTimer; QHash _timerFunctionMap; + int16_t* _avatarAudioBuffer; + int _numAvatarAudioBufferSamples; private: void sendAvatarIdentityPacket(); diff --git a/libraries/shared/src/AccountManager.cpp b/libraries/shared/src/AccountManager.cpp index 7915a60690..bb32896ca0 100644 --- a/libraries/shared/src/AccountManager.cpp +++ b/libraries/shared/src/AccountManager.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #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; diff --git a/libraries/shared/src/AccountManager.h b/libraries/shared/src/AccountManager.h index ee821fa43c..bfe84f392e 100644 --- a/libraries/shared/src/AccountManager.h +++ b/libraries/shared/src/AccountManager.h @@ -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; diff --git a/libraries/shared/src/FstReader.cpp b/libraries/shared/src/FstReader.cpp index fa2aebb2fe..d82ddf68a3 100644 --- a/libraries/shared/src/FstReader.cpp +++ b/libraries/shared/src/FstReader.cpp @@ -10,16 +10,36 @@ #include #include #include -#include #include #include +#include +#include + +#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 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; +} diff --git a/libraries/shared/src/FstReader.h b/libraries/shared/src/FstReader.h index 44cd3f95cd..aab42bd967 100644 --- a/libraries/shared/src/FstReader.h +++ b/libraries/shared/src/FstReader.h @@ -10,28 +10,31 @@ #ifndef __hifi__FstReader__ #define __hifi__FstReader__ -#include -#include #include -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__) */