diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index ed1f293c06..098460ecac 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -45,7 +45,7 @@ Agent::Agent(const QByteArray& packet) : DependencyManager::get()->setPacketSender(&_entityEditSender); - DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); } diff --git a/cmake/externals/tbb/CMakeLists.txt b/cmake/externals/tbb/CMakeLists.txt index ad46196b95..b9b2b65010 100644 --- a/cmake/externals/tbb/CMakeLists.txt +++ b/cmake/externals/tbb/CMakeLists.txt @@ -13,16 +13,28 @@ if (ANDROID) BUILD_COMMAND ${NDK_BUILD_COMMAND} --directory=jni target=android tbb tbbmalloc arch=arm BUILD_IN_SOURCE 1 CONFIGURE_COMMAND "" - INSTALL_COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/AndroidTBBLibCopy.cmake + INSTALL_COMMAND ${CMAKE_COMMAND} -DTBB_LIBS_SUFFIX=so -P ${CMAKE_CURRENT_SOURCE_DIR}/TBBLibCopy.cmake + LOG_DOWNLOAD 1 + LOG_CONFIGURE 1 + LOG_BUILD 1 + ) +elseif (APPLE) + find_program(MAKE_COMMAND NAMES make DOC "Path to the make command") + + ExternalProject_Add( + ${EXTERNAL_NAME} + URL http://hifi-public.s3.amazonaws.com/dependencies/tbb43_20150316oss_src.tgz + URL_MD5 bf090eaa86cf89ea014b7b462786a440 + BUILD_COMMAND ${MAKE_COMMAND} tbb_os=macos + BUILD_IN_SOURCE 1 + CONFIGURE_COMMAND "" + INSTALL_COMMAND ${CMAKE_COMMAND} -DTBB_LIBS_SUFFIX=dylib -P ${CMAKE_CURRENT_SOURCE_DIR}/TBBLibCopy.cmake LOG_DOWNLOAD 1 LOG_CONFIGURE 1 LOG_BUILD 1 ) else () - if (APPLE) - set(DOWNLOAD_URL http://hifi-public.s3.amazonaws.com/dependencies/tbb43_20150316oss_osx.tgz) - set(DOWNLOAD_MD5 25a36ebff070ff801760ec658079f6aa) - elseif (WIN32) + if (WIN32) set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb43_20150316oss_win.zip) set(DOWNLOAD_MD5 d250d40bb93b255f75bcbb19e976a440) else () @@ -46,7 +58,7 @@ ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) if (APPLE) - set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/libc++") + set(_TBB_LIB_DIR "${SOURCE_DIR}/lib") set(_LIB_PREFIX "lib") set(_LIB_EXT "dylib") @@ -95,14 +107,8 @@ elseif (UNIX) endif () if (DEFINED _TBB_LIB_DIR) - if (NOT APPLE) - set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbb_debug.${_LIB_EXT} CACHE FILEPATH "TBB debug library location") - set(${EXTERNAL_NAME_UPPER}_MALLOC_LIBRARY_DEBUG ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbbmalloc_debug.${_LIB_EXT} CACHE FILEPATH "TBB malloc debug library location") - else () - set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG "" CACHE FILEPATH "TBB debug library location") - set(${EXTERNAL_NAME_UPPER}_MALLOC_LIBRARY_DEBUG "" CACHE FILEPATH "TBB malloc debug library location") - endif () - + set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbb_debug.${_LIB_EXT} CACHE FILEPATH "TBB debug library location") + set(${EXTERNAL_NAME_UPPER}_MALLOC_LIBRARY_DEBUG ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbbmalloc_debug.${_LIB_EXT} CACHE FILEPATH "TBB malloc debug library location") set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbb.${_LIB_EXT} CACHE FILEPATH "TBB release library location") set(${EXTERNAL_NAME_UPPER}_MALLOC_LIBRARY_RELEASE ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbbmalloc.${_LIB_EXT} CACHE FILEPATH "TBB malloc release library location") endif () diff --git a/cmake/externals/tbb/OSXTBBInstallNameChange.cmake b/cmake/externals/tbb/OSXTBBInstallNameChange.cmake index c263ed7d2e..cc4df46812 100644 --- a/cmake/externals/tbb/OSXTBBInstallNameChange.cmake +++ b/cmake/externals/tbb/OSXTBBInstallNameChange.cmake @@ -10,9 +10,11 @@ # # first find the so files in the source dir -set(_TBB_LIBRARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/libc++) +set(_TBB_LIBRARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib) file(GLOB_RECURSE _TBB_LIBRARIES "${_TBB_LIBRARY_DIR}/*.dylib") +message(${_TBB_LIBRARIES}) + # raise an error if we found none if (NOT _TBB_LIBRARIES) message(FATAL_ERROR "Did not find any TBB libraries") @@ -28,20 +30,6 @@ find_program(LIPO_COMMAND NAMES lipo DOC "Path to the lipo command") foreach(_TBB_LIBRARY ${_TBB_LIBRARIES}) get_filename_component(_TBB_LIBRARY_FILENAME ${_TBB_LIBRARY} NAME) - set(_LIPO_ARGS -remove i386 ${_TBB_LIBRARY_FILENAME} -output ${_TBB_LIBRARY_FILENAME}) - message(STATUS "${LIPO_COMMAND} ${_LIPO_ARGS}") - - # first we use lipo to remove i386 from each dylib - execute_process( - COMMAND ${LIPO_COMMAND} ${_LIPO_ARGS} - WORKING_DIRECTORY ${_TBB_LIBRARY_DIR} - ERROR_VARIABLE _LIPO_ERROR - ) - - if (_LIPO_ERROR) - message(FATAL_ERROR "There was an error removing i386 for ${_TBB_LIBRARY_FILENAME} - ${_LIPO_ERROR}") - endif () - set(_INSTALL_NAME_ARGS ${INSTALL_NAME_TOOL_COMMAND} -id ${_TBB_LIBRARY} ${_TBB_LIBRARY_FILENAME}) message(STATUS "${INSTALL_NAME_COMMAND} ${_INSTALL_NAME_ARGS}") diff --git a/cmake/externals/tbb/AndroidTBBLibCopy.cmake b/cmake/externals/tbb/TBBLibCopy.cmake similarity index 94% rename from cmake/externals/tbb/AndroidTBBLibCopy.cmake rename to cmake/externals/tbb/TBBLibCopy.cmake index 1c7697ab54..8f5423750b 100644 --- a/cmake/externals/tbb/AndroidTBBLibCopy.cmake +++ b/cmake/externals/tbb/TBBLibCopy.cmake @@ -1,5 +1,5 @@ # -# AndroidTBBLibCopy.cmake +# TBBLibCopy.cmake # cmake/externals/tbb # # Copyright 2015 High Fidelity, Inc. @@ -10,7 +10,7 @@ # # first find the so files in the source dir -file(GLOB_RECURSE _TBB_LIBRARIES "${CMAKE_CURRENT_SOURCE_DIR}/build/*.so") +file(GLOB_RECURSE _TBB_LIBRARIES "${CMAKE_CURRENT_SOURCE_DIR}/build/*.${TBB_LIBS_SUFFIX}") # raise an error if we found none if (NOT _TBB_LIBRARIES) diff --git a/examples/blocks.js b/examples/blocks.js index 30c2126096..7bc52824db 100644 --- a/examples/blocks.js +++ b/examples/blocks.js @@ -43,8 +43,8 @@ var floor = Entities.addEntity( var edge1 = Entities.addEntity( { type: "Box", position: Vec3.sum(center, { x: FLOOR_SIZE / 2.0, y: FLOOR_THICKNESS / 2.0, z: 0 }), - dimensions: { x: EDGE_THICKESS, y: EDGE_THICKESS, z: FLOOR_SIZE }, - color: { red: 128, green: 128, blue: 128 }, + dimensions: { x: EDGE_THICKESS, y: EDGE_THICKESS, z: FLOOR_SIZE + EDGE_THICKESS }, + color: { red: 100, green: 100, blue: 100 }, gravity: { x: 0, y: 0, z: 0 }, ignoreCollisions: false, visible: true, @@ -54,8 +54,8 @@ var edge1 = Entities.addEntity( var edge2 = Entities.addEntity( { type: "Box", position: Vec3.sum(center, { x: -FLOOR_SIZE / 2.0, y: FLOOR_THICKNESS / 2.0, z: 0 }), - dimensions: { x: EDGE_THICKESS, y: EDGE_THICKESS, z: FLOOR_SIZE }, - color: { red: 128, green: 128, blue: 128 }, + dimensions: { x: EDGE_THICKESS, y: EDGE_THICKESS, z: FLOOR_SIZE + EDGE_THICKESS }, + color: { red: 100, green: 100, blue: 100 }, gravity: { x: 0, y: 0, z: 0 }, ignoreCollisions: false, visible: true, @@ -65,8 +65,8 @@ var edge2 = Entities.addEntity( var edge3 = Entities.addEntity( { type: "Box", position: Vec3.sum(center, { x: 0, y: FLOOR_THICKNESS / 2.0, z: -FLOOR_SIZE / 2.0 }), - dimensions: { x: FLOOR_SIZE, y: EDGE_THICKESS, z: EDGE_THICKESS }, - color: { red: 128, green: 128, blue: 128 }, + dimensions: { x: FLOOR_SIZE + EDGE_THICKESS, y: EDGE_THICKESS, z: EDGE_THICKESS }, + color: { red: 100, green: 100, blue: 100 }, gravity: { x: 0, y: 0, z: 0 }, ignoreCollisions: false, visible: true, @@ -76,8 +76,8 @@ var edge3 = Entities.addEntity( var edge4 = Entities.addEntity( { type: "Box", position: Vec3.sum(center, { x: 0, y: FLOOR_THICKNESS / 2.0, z: FLOOR_SIZE / 2.0 }), - dimensions: { x: FLOOR_SIZE, y: EDGE_THICKESS, z: EDGE_THICKESS }, - color: { red: 128, green: 128, blue: 128 }, + dimensions: { x: FLOOR_SIZE + EDGE_THICKESS, y: EDGE_THICKESS, z: EDGE_THICKESS }, + color: { red: 100, green: 100, blue: 100 }, gravity: { x: 0, y: 0, z: 0 }, ignoreCollisions: false, visible: true, @@ -97,6 +97,7 @@ for (var i = 0; i < NUM_BLOCKS; i++) { dimensions: { x: type.x * SCALE, y: type.y * SCALE, z: type.z * SCALE }, color: { red: type.red, green: type.green, blue: type.blue }, gravity: { x: 0, y: GRAVITY, z: 0 }, + velocity: { x: 0, y: 0.05, z: 0 }, ignoreCollisions: false, damping: DAMPING, lifetime: LIFETIME, @@ -104,6 +105,11 @@ for (var i = 0; i < NUM_BLOCKS; i++) { } function scriptEnding() { + Entities.editEntity(edge1, { locked: false }); + Entities.editEntity(edge2, { locked: false }); + Entities.editEntity(edge3, { locked: false }); + Entities.editEntity(edge4, { locked: false }); + Entities.editEntity(floor, { locked: false }); Entities.deleteEntity(edge1); Entities.deleteEntity(edge2); Entities.deleteEntity(edge3); diff --git a/examples/controllers/hydra/gun.js b/examples/controllers/hydra/gun.js index da10b48634..f2ed50733d 100644 --- a/examples/controllers/hydra/gun.js +++ b/examples/controllers/hydra/gun.js @@ -91,44 +91,49 @@ var score = 0; var bulletID = false; var targetID = false; -// Create a reticle image in center of screen +// Create overlay buttons and reticle + +var BUTTON_SIZE = 32; +var PADDING = 3; +var NUM_BUTTONS = 3; + var screenSize = Controller.getViewportDimensions(); +var startX = screenSize.x / 2 - (NUM_BUTTONS * (BUTTON_SIZE + PADDING)) / 2; var reticle = Overlays.addOverlay("image", { - x: screenSize.x / 2 - 16, - y: screenSize.y / 2 - 16, - width: 32, - height: 32, + x: screenSize.x / 2 - (BUTTON_SIZE / 2), + y: screenSize.y / 2 - (BUTTON_SIZE / 2), + width: BUTTON_SIZE, + height: BUTTON_SIZE, imageURL: HIFI_PUBLIC_BUCKET + "images/gun/crosshairs.svg", - color: { red: 255, green: 255, blue: 255}, alpha: 1 }); var offButton = Overlays.addOverlay("image", { - x: screenSize.x - 48, - y: 96, - width: 32, - height: 32, + x: startX, + y: screenSize.y - (BUTTON_SIZE + PADDING), + width: BUTTON_SIZE, + height: BUTTON_SIZE, imageURL: HIFI_PUBLIC_BUCKET + "images/gun/close.svg", - color: { red: 255, green: 255, blue: 255}, alpha: 1 }); +startX += BUTTON_SIZE + PADDING; var platformButton = Overlays.addOverlay("image", { - x: screenSize.x - 48, - y: 130, - width: 32, - height: 32, + x: startX, + y: screenSize.y - (BUTTON_SIZE + PADDING), + width: BUTTON_SIZE, + height: BUTTON_SIZE, imageURL: HIFI_PUBLIC_BUCKET + "images/gun/platform-targets.svg", - color: { red: 255, green: 255, blue: 255}, alpha: 1 }); + +startX += BUTTON_SIZE + PADDING; var gridButton = Overlays.addOverlay("image", { - x: screenSize.x - 48, - y: 164, - width: 32, - height: 32, + x: startX, + y: screenSize.y - (BUTTON_SIZE + PADDING), + width: BUTTON_SIZE, + height: BUTTON_SIZE, imageURL: HIFI_PUBLIC_BUCKET + "images/gun/floating-targets.svg", - color: { red: 255, green: 255, blue: 255}, alpha: 1 }); @@ -284,7 +289,7 @@ function makePlatform(gravity, scale, size) { z: pos.z - (separation * size / 2.0) + z * separation }, dimensions: dimensions, color: { red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255 }, - velocity: { x: 0, y: 0, z: 0 }, + velocity: { x: 0, y: 0.05, z: 0 }, gravity: { x: 0, y: gravity, z: 0 }, lifetime: TARGET_LIFE, damping: 0.1, @@ -299,7 +304,7 @@ function makePlatform(gravity, scale, size) { type: "Box", position: { x: pos.x, y: pos.y - separation / 2.0, z: pos.z }, dimensions: { x: 2.0 * separation * size, y: separation / 2.0, z: 2.0 * separation * size }, - color: { red: 128, green: 128, blue: 128 }, + color: { red: 100, green: 100, blue: 100 }, lifetime: TARGET_LIFE }); diff --git a/examples/dice.js b/examples/dice.js index b0021ecebb..d27de904d7 100644 --- a/examples/dice.js +++ b/examples/dice.js @@ -24,20 +24,24 @@ HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var rollSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/dice/diceRoll.wav"); var screenSize = Controller.getViewportDimensions(); + +var BUTTON_SIZE = 32; +var PADDING = 3; + var offButton = Overlays.addOverlay("image", { - x: screenSize.x - 48, - y: 96, - width: 32, - height: 32, + x: screenSize.x / 2 - BUTTON_SIZE, + y: screenSize.y- (BUTTON_SIZE + PADDING), + width: BUTTON_SIZE, + height: BUTTON_SIZE, imageURL: HIFI_PUBLIC_BUCKET + "images/close.png", color: { red: 255, green: 255, blue: 255}, alpha: 1 }); var diceButton = Overlays.addOverlay("image", { - x: screenSize.x - 48, - y: 130, - width: 32, - height: 32, + x: screenSize.x / 2 + PADDING, + y: screenSize.y - (BUTTON_SIZE + PADDING), + width: BUTTON_SIZE, + height: BUTTON_SIZE, imageURL: HIFI_PUBLIC_BUCKET + "images/die.png", color: { red: 255, green: 255, blue: 255}, alpha: 1 @@ -45,6 +49,10 @@ var diceButton = Overlays.addOverlay("image", { var GRAVITY = -3.5; var LIFETIME = 300; +// NOTE: angularVelocity is in radians/sec +var MAX_ANGULAR_SPEED = Math.PI; + + function shootDice(position, velocity) { for (var i = 0; i < NUMBER_OF_DICE; i++) { dice.push(Entities.addEntity( @@ -53,9 +61,7 @@ function shootDice(position, velocity) { position: position, velocity: velocity, rotation: Quat.fromPitchYawRollDegrees(Math.random() * 360, Math.random() * 360, Math.random() * 360), - // NOTE: angularVelocity is in radians/sec - var maxAngularSpeed = Math.PI; - angularVelocity: { x: Math.random() * maxAngularSpeed, y: Math.random() * maxAngularSpeed, z: Math.random() * maxAngularSpeed }, + angularVelocity: { x: Math.random() * MAX_ANGULAR_SPEED, y: Math.random() * MAX_ANGULAR_SPEED, z: Math.random() * MAX_ANGULAR_SPEED }, lifetime: LIFETIME, gravity: { x: 0, y: GRAVITY, z: 0 }, shapeType: "box", @@ -91,7 +97,7 @@ function mousePressEvent(event) { var clickedText = false; var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); if (clickedOverlay == offButton) { - deleteDice(); + Script.stop(); } else if (clickedOverlay == diceButton) { var HOW_HARD = 2.0; var position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); diff --git a/examples/example/entities/makeHouses.js b/examples/example/entities/makeHouses.js index 37bc1d5a8e..d49f737880 100644 --- a/examples/example/entities/makeHouses.js +++ b/examples/example/entities/makeHouses.js @@ -70,6 +70,7 @@ var rotOdd = Quat.fromPitchYawRollDegrees(0, 90.0 + MyAvatar.bodyYaw, 0.0); var housePos = Vec3.sum(MyAvatar.position, Quat.getFront(Camera.getOrientation())); + var housePositions = [] for (var j = 0; j < measures.rows; j++) { var posX1 = 0 - (xRange / 2); @@ -87,11 +88,8 @@ y: 0, z: dd }; - - print("House nr.:" + (houses.length + 1)); - houses.push( - addHouseAt(Vec3.sum(housePos, posShift), (j % 2 == 0) ? rotEven : rotOdd) - ); + + housePositions.push(Vec3.sum(housePos, posShift)); posX1 += measures.parcelWidth; } } @@ -144,14 +142,21 @@ }; } - function cleanup() { - while (houses.length > 0) { - if (!houses[0].isKnownID) { - houses[0] = Entities.identifyEntity(houses[0]); - } - Entities.deleteEntity(houses.shift()); + var addHouses = function() { + if (housePositions.length > 0) { + position = housePositions.pop(); + print("House nr.:" + (houses.length + 1)); + houses.push( + addHouseAt(position, (housePositions.length % 2 == 0) ? rotEven : rotOdd) + ); + + // max 20 per second + Script.setTimeout(addHouses, 50); + } else { + Script.stop(); } - } - - Script.scriptEnding.connect(cleanup); + }; + + addHouses(); + })(); diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index 596bf5c9d5..1c5fd58084 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -210,6 +210,9 @@ disableChildren(document.getElementById("properties-list"), 'input'); } else { + var activeElement = document.activeElement; + var selected = activeElement.selectionStart == 0 && activeElement.selectionEnd == activeElement.value.length; + var properties = data.selections[0].properties; elID.innerHTML = properties.id; @@ -335,6 +338,11 @@ elLightExponent.value = properties.exponent; elLightCutoff.value = properties.cutoff; } + + if (selected) { + activeElement.focus(); + activeElement.select(); + } } } }); diff --git a/examples/users.js b/examples/users.js index ebd59886aa..2bc69d1638 100644 --- a/examples/users.js +++ b/examples/users.js @@ -11,8 +11,11 @@ var usersWindow = (function () { - var WINDOW_WIDTH_2D = 160, + var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/", + + WINDOW_WIDTH_2D = 160, WINDOW_MARGIN_2D = 12, + WINDOW_BASE_MARGIN_2D = 6, // A little less is needed in order look correct WINDOW_FONT_2D = { size: 12 }, WINDOW_FOREGROUND_COLOR_2D = { red: 240, green: 240, blue: 240 }, WINDOW_FOREGROUND_ALPHA_2D = 0.9, @@ -22,6 +25,14 @@ var usersWindow = (function () { WINDOW_BACKGROUND_ALPHA_2D = 0.7, windowPane2D, windowHeading2D, + MINIMIZE_BUTTON_SVG = HIFI_PUBLIC_BUCKET + "images/tools/min-max-toggle.svg", + MINIMIZE_BUTTON_SVG_WIDTH = 17.1, + MINIMIZE_BUTTON_SVG_HEIGHT = 32.5, + MINIMIZE_BUTTON_WIDTH_2D = 14, + MINIMIZE_BUTTON_HEIGHT_2D = MINIMIZE_BUTTON_WIDTH_2D, + MINIMIZE_BUTTON_COLOR_2D = { red: 255, green: 255, blue: 255 }, + MINIMIZE_BUTTON_ALPHA_2D = 0.9, + minimizeButton2D, SCROLLBAR_BACKGROUND_WIDTH_2D = 12, SCROLLBAR_BACKGROUND_COLOR_2D = { red: 80, green: 80, blue: 80 }, SCROLLBAR_BACKGROUND_ALPHA_2D = 0.8, @@ -65,6 +76,7 @@ var usersWindow = (function () { MENU_ITEM_AFTER = "Chat...", isVisible = true, + isMinimized = false, viewportHeight, isMirrorDisplay = false, @@ -77,7 +89,6 @@ var usersWindow = (function () { scrollbarBarClickedAt, // 0.0 .. 1.0 scrollbarValue = 0.0, // 0.0 .. 1.0 - HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/", RADIO_BUTTON_SVG = HIFI_PUBLIC_BUCKET + "images/radio-button.svg", RADIO_BUTTON_SVG_DIAMETER = 14, RADIO_BUTTON_DISPLAY_SCALE = 0.7, // 1.0 = windowTextHeight @@ -89,9 +100,15 @@ var usersWindow = (function () { nonUsersHeight, maxWindowHeight; + if (isMinimized) { + windowHeight = windowTextHeight + WINDOW_MARGIN_2D + WINDOW_BASE_MARGIN_2D; + return; + } + // Reserve 5 lines for window heading plus visibility heading and controls // Subtract windowLineSpacing for both end of user list and end of controls - nonUsersHeight = 5 * windowLineHeight - 2 * windowLineSpacing + VISIBILITY_SPACER_2D + 2 * WINDOW_MARGIN_2D; + nonUsersHeight = 5 * windowLineHeight - 2 * windowLineSpacing + VISIBILITY_SPACER_2D + WINDOW_MARGIN_2D + + WINDOW_BASE_MARGIN_2D; // Limit window to height of viewport minus VU meter and mirror if displayed windowHeight = linesOfUsers.length * windowLineHeight + nonUsersHeight; @@ -102,7 +119,7 @@ var usersWindow = (function () { windowHeight = Math.max(Math.min(windowHeight, maxWindowHeight), nonUsersHeight); // Corresponding number of users to actually display - numUsersToDisplay = Math.max(Math.round((windowHeight - nonUsersHeight) / windowLineHeight), 0); + numUsersToDisplay = Math.max(Math.floor((windowHeight - nonUsersHeight) / windowLineHeight), 0); isUsingScrollbars = 0 < numUsersToDisplay && numUsersToDisplay < linesOfUsers.length; if (isUsingScrollbars) { firstUserToDisplay = Math.floor(scrollbarValue * (linesOfUsers.length - numUsersToDisplay)); @@ -123,6 +140,10 @@ var usersWindow = (function () { y: viewportHeight - windowHeight + WINDOW_MARGIN_2D }); + Overlays.editOverlay(minimizeButton2D, { + y: viewportHeight - windowHeight + WINDOW_MARGIN_2D / 2 + }); + scrollbarBackgroundPosition.y = viewportHeight - windowHeight + WINDOW_MARGIN_2D + windowTextHeight; Overlays.editOverlay(scrollbarBackground2D, { y: scrollbarBackgroundPosition.y @@ -133,10 +154,10 @@ var usersWindow = (function () { y: scrollbarBarPosition.y }); Overlays.editOverlay(visibilityHeading2D, { - y: viewportHeight - 4 * windowLineHeight + windowLineSpacing - WINDOW_MARGIN_2D + y: viewportHeight - 4 * windowLineHeight + windowLineSpacing - WINDOW_BASE_MARGIN_2D }); for (i = 0; i < visibilityControls2D.length; i += 1) { - y = viewportHeight - (3 - i) * windowLineHeight + windowLineSpacing - WINDOW_MARGIN_2D; + y = viewportHeight - (3 - i) * windowLineHeight + windowLineSpacing - WINDOW_BASE_MARGIN_2D; Overlays.editOverlay(visibilityControls2D[i].radioOverlay, { y: y }); Overlays.editOverlay(visibilityControls2D[i].textOverlay, { y: y }); } @@ -166,29 +187,43 @@ var usersWindow = (function () { reducedTextWidth, i; - maxTextWidth = WINDOW_WIDTH_2D - (isUsingScrollbars ? SCROLLBAR_BACKGROUND_WIDTH_2D : 0) - 2 * WINDOW_MARGIN_2D; - ellipsisWidth = Overlays.textSize(windowPane2D, "...").width; - reducedTextWidth = maxTextWidth - ellipsisWidth; + if (!isMinimized) { + maxTextWidth = WINDOW_WIDTH_2D - (isUsingScrollbars ? SCROLLBAR_BACKGROUND_WIDTH_2D : 0) - 2 * WINDOW_MARGIN_2D; + ellipsisWidth = Overlays.textSize(windowPane2D, "...").width; + reducedTextWidth = maxTextWidth - ellipsisWidth; - for (i = 0; i < numUsersToDisplay; i += 1) { - user = usersOnline[linesOfUsers[firstUserToDisplay + i]]; - userText = user.text; - textWidth = user.textWidth; + for (i = 0; i < numUsersToDisplay; i += 1) { + user = usersOnline[linesOfUsers[firstUserToDisplay + i]]; + userText = user.text; + textWidth = user.textWidth; - if (textWidth > maxTextWidth) { - // Trim and append "..." to fit window width - maxTextWidth = maxTextWidth - Overlays.textSize(windowPane2D, "...").width; - while (textWidth > reducedTextWidth) { - userText = userText.slice(0, -1); - textWidth = Overlays.textSize(windowPane2D, userText).width; + if (textWidth > maxTextWidth) { + // Trim and append "..." to fit window width + maxTextWidth = maxTextWidth - Overlays.textSize(windowPane2D, "...").width; + while (textWidth > reducedTextWidth) { + userText = userText.slice(0, -1); + textWidth = Overlays.textSize(windowPane2D, userText).width; + } + userText += "..."; } - userText += "..."; + + displayText += "\n" + userText; } - displayText += "\n" + userText; - } + displayText = displayText.slice(1); // Remove leading "\n". - displayText = displayText.slice(1); // Remove leading "\n". + scrollbarBackgroundHeight = numUsersToDisplay * windowLineHeight - windowLineSpacing / 2; + Overlays.editOverlay(scrollbarBackground2D, { + height: scrollbarBackgroundHeight, + visible: isUsingScrollbars + }); + scrollbarBarHeight = Math.max(numUsersToDisplay / linesOfUsers.length * scrollbarBackgroundHeight, + SCROLLBAR_BAR_MIN_HEIGHT); + Overlays.editOverlay(scrollbarBar2D, { + height: scrollbarBarHeight, + visible: isUsingScrollbars + }); + } Overlays.editOverlay(windowPane2D, { height: windowHeight, @@ -198,20 +233,6 @@ var usersWindow = (function () { Overlays.editOverlay(windowHeading2D, { text: linesOfUsers.length > 0 ? "Users online" : "No users online" }); - - scrollbarBackgroundHeight = numUsersToDisplay * windowLineHeight - windowLineSpacing / 2; - Overlays.editOverlay(scrollbarBackground2D, { - height: scrollbarBackgroundHeight, - visible: isUsingScrollbars - }); - scrollbarBarHeight = Math.max(numUsersToDisplay / linesOfUsers.length * scrollbarBackgroundHeight, - SCROLLBAR_BAR_MIN_HEIGHT); - Overlays.editOverlay(scrollbarBar2D, { - height: scrollbarBarHeight, - visible: isUsingScrollbars - }); - - updateOverlayPositions(); } function pollUsers() { @@ -264,6 +285,7 @@ var usersWindow = (function () { calculateWindowHeight(); updateUsersDisplay(); + updateOverlayPositions(); } else { print("Error: Request for users status returned " + usersRequest.status + " " + usersRequest.statusText); @@ -289,9 +311,22 @@ var usersWindow = (function () { } } - function setVisible(visible) { + function updateOverlayVisibility() { var i; + Overlays.editOverlay(windowPane2D, { visible: isVisible }); + Overlays.editOverlay(windowHeading2D, { visible: isVisible }); + Overlays.editOverlay(minimizeButton2D, { visible: isVisible }); + Overlays.editOverlay(scrollbarBackground2D, { visible: isVisible && isUsingScrollbars && !isMinimized }); + Overlays.editOverlay(scrollbarBar2D, { visible: isVisible && isUsingScrollbars && !isMinimized }); + Overlays.editOverlay(visibilityHeading2D, { visible: isVisible && !isMinimized }); + for (i = 0; i < visibilityControls2D.length; i += 1) { + Overlays.editOverlay(visibilityControls2D[i].radioOverlay, { visible: isVisible && !isMinimized }); + Overlays.editOverlay(visibilityControls2D[i].textOverlay, { visible: isVisible && !isMinimized }); + } + } + + function setVisible(visible) { isVisible = visible; if (isVisible) { @@ -303,15 +338,15 @@ var usersWindow = (function () { usersTimer = null; } - Overlays.editOverlay(windowPane2D, { visible: isVisible }); - Overlays.editOverlay(windowHeading2D, { visible: isVisible }); - Overlays.editOverlay(scrollbarBackground2D, { visible: isVisible && isUsingScrollbars }); - Overlays.editOverlay(scrollbarBar2D, { visible: isVisible && isUsingScrollbars }); - Overlays.editOverlay(visibilityHeading2D, { visible: isVisible }); - for (i = 0; i < visibilityControls2D.length; i += 1) { - Overlays.editOverlay(visibilityControls2D[i].radioOverlay, { visible: isVisible }); - Overlays.editOverlay(visibilityControls2D[i].textOverlay, { visible: isVisible }); - } + updateOverlayVisibility(); + } + + function setMinimized(minimized) { + isMinimized = minimized; + Overlays.editOverlay(minimizeButton2D, { + subImage: { y: isMinimized ? MINIMIZE_BUTTON_SVG_HEIGHT / 2 : 0 } + }); + updateOverlayVisibility(); } function onMenuItemEvent(event) { @@ -360,6 +395,8 @@ var usersWindow = (function () { //print("Go to " + usersOnline[linesOfUsers[userClicked]].username); location.goToUser(usersOnline[linesOfUsers[userClicked]].username); } + + return; } visibilityChanged = false; @@ -376,6 +413,15 @@ var usersWindow = (function () { visibilityControls2D[i].selected = clickedOverlay === visibilityControls2D[i].textOverlay; } updateVisibilityControls(); + return; + } + + if (clickedOverlay === minimizeButton2D) { + setMinimized(!isMinimized); + calculateWindowHeight(); + updateOverlayPositions(); + updateUsersDisplay(); + return; } if (clickedOverlay === scrollbarBar2D) { @@ -384,6 +430,7 @@ var usersWindow = (function () { backgroundAlpha: SCROLLBAR_BAR_SELECTED_ALPHA_2D }); isMovingScrollbar = true; + return; } if (clickedOverlay === scrollbarBackground2D) { @@ -398,6 +445,7 @@ var usersWindow = (function () { firstUserToDisplay = Math.floor(scrollbarValue * (linesOfUsers.length - numUsersToDisplay)); updateOverlayPositions(); updateUsersDisplay(); + return; } } @@ -492,7 +540,19 @@ var usersWindow = (function () { backgroundAlpha: 0.0, text: "No users online", font: WINDOW_FONT_2D, - visible: isVisible + visible: isVisible && !isMinimized + }); + + minimizeButton2D = Overlays.addOverlay("image", { + x: WINDOW_WIDTH_2D - WINDOW_MARGIN_2D / 2 - MINIMIZE_BUTTON_WIDTH_2D, + y: viewportHeight, + width: MINIMIZE_BUTTON_WIDTH_2D, + height: MINIMIZE_BUTTON_HEIGHT_2D, + imageURL: MINIMIZE_BUTTON_SVG, + subImage: { x: 0, y: 0, width: MINIMIZE_BUTTON_SVG_WIDTH, height: MINIMIZE_BUTTON_SVG_HEIGHT / 2 }, + color: MINIMIZE_BUTTON_COLOR_2D, + alpha: MINIMIZE_BUTTON_ALPHA_2D, + visible: isVisible && !isMinimized }); scrollbarBackgroundPosition = { @@ -507,7 +567,7 @@ var usersWindow = (function () { backgroundColor: SCROLLBAR_BACKGROUND_COLOR_2D, backgroundAlpha: SCROLLBAR_BACKGROUND_ALPHA_2D, text: "", - visible: isVisible && isUsingScrollbars + visible: isVisible && isUsingScrollbars && !isMinimized }); scrollbarBarPosition = { @@ -522,7 +582,7 @@ var usersWindow = (function () { backgroundColor: SCROLLBAR_BAR_COLOR_2D, backgroundAlpha: SCROLLBAR_BAR_ALPHA_2D, text: "", - visible: isVisible && isUsingScrollbars + visible: isVisible && isUsingScrollbars && !isMinimized }); visibilityHeading2D = Overlays.addOverlay("text", { @@ -537,7 +597,7 @@ var usersWindow = (function () { backgroundAlpha: 0.0, text: "I am visible to:", font: WINDOW_FONT_2D, - visible: isVisible + visible: isVisible && !isMinimized }); myVisibility = GlobalServices.findableBy; @@ -561,7 +621,8 @@ var usersWindow = (function () { height: RADIO_BUTTON_SVG_DIAMETER }, color: WINDOW_HEADING_COLOR_2D, - alpha: WINDOW_FOREGROUND_ALPHA_2D + alpha: WINDOW_FOREGROUND_ALPHA_2D, + visible: isVisible && !isMinimized }), textOverlay: Overlays.addOverlay("text", { x: WINDOW_MARGIN_2D, @@ -575,7 +636,7 @@ var usersWindow = (function () { backgroundAlpha: 0.0, text: optionText, font: WINDOW_FONT_2D, - visible: isVisible + visible: isVisible && !isMinimized }), selected: myVisibility === VISIBILITY_VALUES[0] }]; @@ -634,6 +695,7 @@ var usersWindow = (function () { Script.clearTimeout(usersTimer); Overlays.deleteOverlay(windowPane2D); Overlays.deleteOverlay(windowHeading2D); + Overlays.deleteOverlay(minimizeButton2D); Overlays.deleteOverlay(scrollbarBackground2D); Overlays.deleteOverlay(scrollbarBar2D); Overlays.deleteOverlay(visibilityHeading2D); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 549f05a905..fba126309e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -76,7 +76,7 @@ #include #include #include -//#include +#include #include #include #include @@ -136,6 +136,13 @@ #include "ui/StandAloneJSConsole.h" #include "ui/Stats.h" +// ON WIndows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU +#if defined(Q_OS_WIN) +extern "C" { + _declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; +} +#endif + using namespace std; // Starfield information @@ -221,7 +228,7 @@ bool setupEssentials(int& argc, char** argv) { auto addressManager = DependencyManager::set(); auto nodeList = DependencyManager::set(NodeType::Agent, listenPort); auto geometryCache = DependencyManager::set(); - //auto scriptCache = DependencyManager::set(); + auto scriptCache = DependencyManager::set(); auto soundCache = DependencyManager::set(); auto glowEffect = DependencyManager::set(); auto faceshift = DependencyManager::set(); @@ -241,7 +248,7 @@ bool setupEssentials(int& argc, char** argv) { auto jsConsole = DependencyManager::set(); auto dialogsManager = DependencyManager::set(); auto bandwidthRecorder = DependencyManager::set(); - auto resouceCacheSharedItems = DependencyManager::set(); + auto resourceCacheSharedItems = DependencyManager::set(); auto entityScriptingInterface = DependencyManager::set(); auto windowScriptingInterface = DependencyManager::set(); #if defined(Q_OS_MAC) || defined(Q_OS_WIN) @@ -620,7 +627,7 @@ Application::~Application() { DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); - //DependencyManager::destroy(); + DependencyManager::destroy(); DependencyManager::destroy(); QThread* nodeThread = DependencyManager::get()->thread(); @@ -646,6 +653,11 @@ void Application::initializeGL() { } #endif + qDebug() << "GL Version: " << QString((const char*) glGetString(GL_VERSION)); + qDebug() << "GL Shader Language Version: " << QString((const char*) glGetString(GL_SHADING_LANGUAGE_VERSION)); + qDebug() << "GL Vendor: " << QString((const char*) glGetString(GL_VENDOR)); + qDebug() << "GL Renderer: " << QString((const char*) glGetString(GL_RENDERER)); + #ifdef WIN32 GLenum err = glewInit(); if (GLEW_OK != err) { @@ -1524,7 +1536,7 @@ void Application::idle() { } // Normally we check PipelineWarnings, but since idle will often take more than 10ms we only show these idle timing - // details if we're in ExtraDebugging mode. However, the ::update() and it's subcomponents will show their timing + // details if we're in ExtraDebugging mode. However, the ::update() and its subcomponents will show their timing // details normally. bool showWarnings = getLogger()->extraDebugging(); PerformanceWarning warn(showWarnings, "idle()"); @@ -2540,7 +2552,7 @@ bool Application::isHMDMode() const { QRect Application::getDesirableApplicationGeometry() { QRect applicationGeometry = getWindow()->geometry(); - // If our parent window is on the HMD, then don't use it's geometry, instead use + // If our parent window is on the HMD, then don't use its geometry, instead use // the "main screen" geometry. HMDToolsDialog* hmdTools = DependencyManager::get()->getHMDToolsDialog(); if (hmdTools && hmdTools->hasHMDScreen()) { @@ -3376,7 +3388,7 @@ void Application::nodeKilled(SharedNodePointer node) { void Application::trackIncomingOctreePacket(const QByteArray& packet, const SharedNodePointer& sendingNode, bool wasStatsPacket) { - // Attempt to identify the sender from it's address. + // Attempt to identify the sender from its address. if (sendingNode) { QUuid nodeUUID = sendingNode->getUUID(); @@ -3445,7 +3457,7 @@ int Application::parseOctreeStats(const QByteArray& packet, const SharedNodePoin } // store jurisdiction details for later use // This is bit of fiddling is because JurisdictionMap assumes it is the owner of the values used to construct it - // but OctreeSceneStats thinks it's just returning a reference to it's contents. So we need to make a copy of the + // but OctreeSceneStats thinks it's just returning a reference to its contents. So we need to make a copy of the // details from the OctreeSceneStats to construct the JurisdictionMap JurisdictionMap jurisdictionMap; jurisdictionMap.copyContents(temp.getJurisdictionRoot(), temp.getJurisdictionEndNodes()); @@ -3665,17 +3677,56 @@ bool Application::askToSetAvatarUrl(const QString& url) { msgBox.exec(); return false; } - - QString message = "Would you like to use this model for part of avatar:\n" + url; + + // Download the FST file, to attempt to determine its model type + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest networkRequest = QNetworkRequest(url); + networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + QNetworkReply* reply = networkAccessManager.get(networkRequest); + qDebug() << "Downloading avatar file at " << url; + QEventLoop loop; + QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + loop.exec(); + QByteArray fstContents = reply->readAll(); + delete reply; + QVariantHash fstMapping = FSTReader::readMapping(fstContents); + + FSTReader::ModelType modelType = FSTReader::predictModelType(fstMapping); + QMessageBox msgBox; - msgBox.setIcon(QMessageBox::Question); msgBox.setWindowTitle("Set Avatar"); - msgBox.setText(message); + QPushButton* headButton = NULL; + QPushButton* bodyButton = NULL; + QPushButton* bodyAndHeadButton = NULL; + + QString message; + QString typeInfo; + switch (modelType) { + case FSTReader::HEAD_MODEL: + message = QString("Would you like to use '") + fstMapping["name"].toString() + QString("' for your avatar head?"); + headButton = msgBox.addButton(tr("Yes"), QMessageBox::ActionRole); + break; - QPushButton* headButton = msgBox.addButton(tr("Head"), QMessageBox::ActionRole); - QPushButton* bodyButton = msgBox.addButton(tr("Body"), QMessageBox::ActionRole); - QPushButton* bodyAndHeadButton = msgBox.addButton(tr("Body + Head"), QMessageBox::ActionRole); + case FSTReader::BODY_ONLY_MODEL: + message = QString("Would you like to use '") + fstMapping["name"].toString() + QString("' for your avatar body?"); + bodyButton = msgBox.addButton(tr("Yes"), QMessageBox::ActionRole); + break; + + case FSTReader::HEAD_AND_BODY_MODEL: + message = QString("Would you like to use '") + fstMapping["name"].toString() + QString("' for your avatar?"); + bodyAndHeadButton = msgBox.addButton(tr("Yes"), QMessageBox::ActionRole); + break; + + default: + message = QString("Would you like to use '") + fstMapping["name"].toString() + QString("' for some part of your avatar head?"); + headButton = msgBox.addButton(tr("Use for Head"), QMessageBox::ActionRole); + bodyButton = msgBox.addButton(tr("Use for Body"), QMessageBox::ActionRole); + bodyAndHeadButton = msgBox.addButton(tr("Use for Body and Head"), QMessageBox::ActionRole); + break; + } + + msgBox.setText(message); msgBox.addButton(QMessageBox::Cancel); msgBox.exec(); @@ -3688,6 +3739,11 @@ bool Application::askToSetAvatarUrl(const QString& url) { } else if (msgBox.clickedButton() == bodyButton) { qDebug() << "Chose to use for body: " << url; _myAvatar->setSkeletonModelURL(url); + // if the head is empty, reset it to the default head. + if (_myAvatar->getFaceModelURLString().isEmpty()) { + _myAvatar->setFaceModelURL(DEFAULT_HEAD_MODEL_URL); + UserActivityLogger::getInstance().changedModel("head", DEFAULT_HEAD_MODEL_URL.toString()); + } UserActivityLogger::getInstance().changedModel("skeleton", url); _myAvatar->sendIdentityPacket(); } else if (msgBox.clickedButton() == bodyAndHeadButton) { diff --git a/interface/src/Menu.h b/interface/src/Menu.h index a4e644f20f..63dab63711 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -246,7 +246,7 @@ namespace MenuOption { const QString ToolWindow = "Tool Window"; const QString TransmitterDrive = "Transmitter Drive"; const QString TurnWithHead = "Turn using Head"; - const QString PackageModel = "Package Model"; + const QString PackageModel = "Package Model..."; const QString Visage = "Visage"; const QString Wireframe = "Wireframe"; } diff --git a/interface/src/ModelPackager.cpp b/interface/src/ModelPackager.cpp index f552d67a98..787c21a2ef 100644 --- a/interface/src/ModelPackager.cpp +++ b/interface/src/ModelPackager.cpp @@ -85,7 +85,7 @@ bool ModelPackager::loadModel() { return false; } qDebug() << "Reading FST file : " << _modelFile.filePath(); - _mapping = readMapping(fst.readAll()); + _mapping = FSTReader::readMapping(fst.readAll()); fst.close(); _fbxInfo = QFileInfo(_modelFile.path() + "/" + _mapping.value(FILENAME_FIELD).toString()); @@ -119,21 +119,23 @@ bool ModelPackager::editProperties() { return false; } _mapping = properties.getMapping(); - - // Make sure that a mapping for the root joint has been specified - QVariantHash joints = _mapping.value(JOINT_FIELD).toHash(); - if (!joints.contains("jointRoot")) { - qWarning() << QString("%1 root joint not configured for skeleton.").arg(_modelFile.fileName()); + + if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) { + // Make sure that a mapping for the root joint has been specified + QVariantHash joints = _mapping.value(JOINT_FIELD).toHash(); + if (!joints.contains("jointRoot")) { + qWarning() << QString("%1 root joint not configured for skeleton.").arg(_modelFile.fileName()); - QString message = "Your did not configure a root joint for your skeleton model.\n\nThe upload will be canceled."; - QMessageBox msgBox; - msgBox.setWindowTitle("Model Upload"); - msgBox.setText(message); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setIcon(QMessageBox::Warning); - msgBox.exec(); + QString message = "Your did not configure a root joint for your skeleton model.\n\nPackaging will be canceled."; + QMessageBox msgBox; + msgBox.setWindowTitle("Model Packager"); + msgBox.setText(message); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); - return false; + return false; + } } return true; @@ -183,7 +185,7 @@ bool ModelPackager::zipModel() { // Copy FST QFile fst(tempDir.path() + "/" + nameField + ".fst"); if (fst.open(QIODevice::WriteOnly)) { - fst.write(writeMapping(_mapping)); + fst.write(FSTReader::writeMapping(_mapping)); fst.close(); } else { qDebug() << "Couldn't write FST file" << fst.fileName(); @@ -204,6 +206,18 @@ bool ModelPackager::zipModel() { } void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename, const FBXGeometry& geometry) { + + bool isBodyType = _modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL; + + // mixamo files - in the event that a mixamo file was edited by some other tool, it's likely the applicationName will + // be rewritten, so we detect the existence of several different blendshapes which indicate we're likely a mixamo file + bool likelyMixamoFile = geometry.applicationName == "mixamo.com" || + (geometry.blendshapeChannelNames.contains("BrowsDown_Right") && + geometry.blendshapeChannelNames.contains("MouthOpen") && + geometry.blendshapeChannelNames.contains("Blink_Left") && + geometry.blendshapeChannelNames.contains("Blink_Right") && + geometry.blendshapeChannelNames.contains("Squint_Right")); + if (!mapping.contains(NAME_FIELD)) { mapping.insert(NAME_FIELD, QFileInfo(filename).baseName()); } @@ -232,39 +246,40 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename if (!joints.contains("jointNeck")) { joints.insert("jointNeck", geometry.jointIndices.contains("jointNeck") ? "jointNeck" : "Neck"); } - if (!joints.contains("jointRoot")) { - joints.insert("jointRoot", "Hips"); - } - if (!joints.contains("jointLean")) { - joints.insert("jointLean", "Spine"); + + if (isBodyType) { + if (!joints.contains("jointRoot")) { + joints.insert("jointRoot", "Hips"); + } + if (!joints.contains("jointLean")) { + joints.insert("jointLean", "Spine"); + } + if (!joints.contains("jointLeftHand")) { + joints.insert("jointLeftHand", "LeftHand"); + } + if (!joints.contains("jointRightHand")) { + joints.insert("jointRightHand", "RightHand"); + } } + if (!joints.contains("jointHead")) { - const char* topName = (geometry.applicationName == "mixamo.com") ? "HeadTop_End" : "HeadEnd"; + const char* topName = likelyMixamoFile ? "HeadTop_End" : "HeadEnd"; joints.insert("jointHead", geometry.jointIndices.contains(topName) ? topName : "Head"); } - if (!joints.contains("jointLeftHand")) { - joints.insert("jointLeftHand", "LeftHand"); - } - if (!joints.contains("jointRightHand")) { - joints.insert("jointRightHand", "RightHand"); - } + mapping.insert(JOINT_FIELD, joints); - if (!mapping.contains(FREE_JOINT_FIELD)) { - mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm"); - mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm"); - mapping.insertMulti(FREE_JOINT_FIELD, "RightArm"); - mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm"); + + if (isBodyType) { + if (!mapping.contains(FREE_JOINT_FIELD)) { + mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm"); + mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm"); + mapping.insertMulti(FREE_JOINT_FIELD, "RightArm"); + mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm"); + } } - // mixamo blendshapes - in the event that a mixamo file was edited by some other tool, it's likely the applicationName will - // be rewritten, so we detect the existence of several different blendshapes which indicate we're likely a mixamo file - bool likelyMixamoFile = geometry.applicationName == "mixamo.com" || - (geometry.blendshapeChannelNames.contains("BrowsDown_Right") && - geometry.blendshapeChannelNames.contains("MouthOpen") && - geometry.blendshapeChannelNames.contains("Blink_Left") && - geometry.blendshapeChannelNames.contains("Blink_Right") && - geometry.blendshapeChannelNames.contains("Squint_Right")); - + // If there are no blendshape mappings, and we detect that this is likely a mixamo file, + // then we can add the default mixamo to "faceshift" mappings if (!mapping.contains(BLENDSHAPE_FIELD) && likelyMixamoFile) { QVariantHash blendshapes; blendshapes.insertMulti("BrowsD_L", QVariantList() << "BrowsDown_Left" << 1.0); diff --git a/interface/src/ModelPackager.h b/interface/src/ModelPackager.h index 2c90395e56..c681ae436f 100644 --- a/interface/src/ModelPackager.h +++ b/interface/src/ModelPackager.h @@ -35,7 +35,7 @@ private: QFileInfo _modelFile; QFileInfo _fbxInfo; - ModelType _modelType; + FSTReader::ModelType _modelType; QString _texDir; QVariantHash _mapping; diff --git a/interface/src/ModelPropertiesDialog.cpp b/interface/src/ModelPropertiesDialog.cpp index 81fe9ce7fd..28a50205c9 100644 --- a/interface/src/ModelPropertiesDialog.cpp +++ b/interface/src/ModelPropertiesDialog.cpp @@ -25,7 +25,7 @@ #include "ModelPropertiesDialog.h" -ModelPropertiesDialog::ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping, +ModelPropertiesDialog::ModelPropertiesDialog(FSTReader::ModelType modelType, const QVariantHash& originalMapping, const QString& basePath, const FBXGeometry& geometry) : _modelType(modelType), _originalMapping(originalMapping), @@ -46,8 +46,8 @@ _geometry(geometry) _scale->setMaximum(FLT_MAX); _scale->setSingleStep(0.01); - if (_modelType != ENTITY_MODEL) { - if (_modelType == ATTACHMENT_MODEL) { + if (_modelType != FSTReader::ENTITY_MODEL) { + if (_modelType == FSTReader::ATTACHMENT_MODEL) { QHBoxLayout* translation = new QHBoxLayout(); form->addRow("Translation:", translation); translation->addWidget(_translationX = createTranslationBox()); @@ -63,7 +63,7 @@ _geometry(geometry) form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox()); form->addRow("Neck Joint:", _neckJoint = createJointBox()); } - if (_modelType == SKELETON_MODEL) { + if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) { form->addRow("Root Joint:", _rootJoint = createJointBox()); form->addRow("Lean Joint:", _leanJoint = createJointBox()); form->addRow("Head Joint:", _headJoint = createJointBox()); @@ -89,8 +89,14 @@ _geometry(geometry) reset(); } + +QString ModelPropertiesDialog::getType() const { + return FSTReader::getNameFromType(_modelType); +} + QVariantHash ModelPropertiesDialog::getMapping() const { QVariantHash mapping = _originalMapping; + mapping.insert(TYPE_FIELD, getType()); mapping.insert(NAME_FIELD, _name->text()); mapping.insert(TEXDIR_FIELD, _textureDirectory->text()); mapping.insert(SCALE_FIELD, QString::number(_scale->value())); @@ -102,9 +108,9 @@ QVariantHash ModelPropertiesDialog::getMapping() const { } mapping.insert(JOINT_INDEX_FIELD, jointIndices); - if (_modelType != ENTITY_MODEL) { + if (_modelType != FSTReader::ENTITY_MODEL) { QVariantHash joints = mapping.value(JOINT_FIELD).toHash(); - if (_modelType == ATTACHMENT_MODEL) { + if (_modelType == FSTReader::ATTACHMENT_MODEL) { glm::vec3 pivot; if (_pivotAboutCenter->isChecked()) { pivot = (_geometry.meshExtents.minimum + _geometry.meshExtents.maximum) * 0.5f; @@ -121,7 +127,9 @@ QVariantHash ModelPropertiesDialog::getMapping() const { insertJointMapping(joints, "jointEyeRight", _rightEyeJoint->currentText()); insertJointMapping(joints, "jointNeck", _neckJoint->currentText()); } - if (_modelType == SKELETON_MODEL) { + + + if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) { insertJointMapping(joints, "jointRoot", _rootJoint->currentText()); insertJointMapping(joints, "jointLean", _leanJoint->currentText()); insertJointMapping(joints, "jointHead", _headJoint->currentText()); @@ -151,8 +159,8 @@ void ModelPropertiesDialog::reset() { QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash(); - if (_modelType != ENTITY_MODEL) { - if (_modelType == ATTACHMENT_MODEL) { + if (_modelType != FSTReader::ENTITY_MODEL) { + if (_modelType == FSTReader::ATTACHMENT_MODEL) { _translationX->setValue(_originalMapping.value(TRANSLATION_X_FIELD).toDouble()); _translationY->setValue(_originalMapping.value(TRANSLATION_Y_FIELD).toDouble()); _translationZ->setValue(_originalMapping.value(TRANSLATION_Z_FIELD).toDouble()); @@ -164,7 +172,8 @@ void ModelPropertiesDialog::reset() { setJointText(_rightEyeJoint, jointHash.value("jointEyeRight").toString()); setJointText(_neckJoint, jointHash.value("jointNeck").toString()); } - if (_modelType == SKELETON_MODEL) { + + if (_modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL) { setJointText(_rootJoint, jointHash.value("jointRoot").toString()); setJointText(_leanJoint, jointHash.value("jointLean").toString()); setJointText(_headJoint, jointHash.value("jointHead").toString()); diff --git a/interface/src/ModelPropertiesDialog.h b/interface/src/ModelPropertiesDialog.h index 5af4d173f1..11abc5ab54 100644 --- a/interface/src/ModelPropertiesDialog.h +++ b/interface/src/ModelPropertiesDialog.h @@ -15,6 +15,7 @@ #include #include +#include #include "ui/ModelsBrowser.h" @@ -28,7 +29,7 @@ class ModelPropertiesDialog : public QDialog { Q_OBJECT public: - ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping, + ModelPropertiesDialog(FSTReader::ModelType modelType, const QVariantHash& originalMapping, const QString& basePath, const FBXGeometry& geometry); QVariantHash getMapping() const; @@ -43,8 +44,9 @@ private: QComboBox* createJointBox(bool withNone = true) const; QDoubleSpinBox* createTranslationBox() const; void insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const; + QString getType() const; - ModelType _modelType; + FSTReader::ModelType _modelType; QVariantHash _originalMapping; QString _basePath; FBXGeometry _geometry; diff --git a/interface/src/ModelSelector.cpp b/interface/src/ModelSelector.cpp index c55d77dc00..8e130cec1a 100644 --- a/interface/src/ModelSelector.cpp +++ b/interface/src/ModelSelector.cpp @@ -18,8 +18,9 @@ #include "ModelSelector.h" -static const QString AVATAR_HEAD_STRING = "Avatar Head"; -static const QString AVATAR_BODY_STRING = "Avatar Body"; +static const QString AVATAR_HEAD_STRING = "Avatar Head Only"; +static const QString AVATAR_BODY_STRING = "Avatar Body Only"; +static const QString AVATAR_HEAD_AND_BODY_STRING = "Avatar Body with Head"; static const QString AVATAR_ATTACHEMENT_STRING = "Avatar Attachment"; static const QString ENTITY_MODEL_STRING = "Entity Model"; @@ -36,6 +37,7 @@ ModelSelector::ModelSelector() { _modelType = new QComboBox(this); _modelType->addItem(AVATAR_HEAD_STRING); _modelType->addItem(AVATAR_BODY_STRING); + _modelType->addItem(AVATAR_HEAD_AND_BODY_STRING); _modelType->addItem(AVATAR_ATTACHEMENT_STRING); _modelType->addItem(ENTITY_MODEL_STRING); form->addRow("Model Type:", _modelType); @@ -50,17 +52,19 @@ QFileInfo ModelSelector::getFileInfo() const { return _modelFile; } -ModelType ModelSelector::getModelType() const { +FSTReader::ModelType ModelSelector::getModelType() const { QString text = _modelType->currentText(); if (text == AVATAR_HEAD_STRING) { - return HEAD_MODEL; + return FSTReader::HEAD_MODEL; } else if (text == AVATAR_BODY_STRING) { - return SKELETON_MODEL; + return FSTReader::BODY_ONLY_MODEL; + } else if (text == AVATAR_HEAD_AND_BODY_STRING) { + return FSTReader::HEAD_AND_BODY_MODEL; } else if (text == AVATAR_ATTACHEMENT_STRING) { - return ATTACHMENT_MODEL; + return FSTReader::ATTACHMENT_MODEL; } else if (text == ENTITY_MODEL_STRING) { - return ENTITY_MODEL; + return FSTReader::ENTITY_MODEL; } else { Q_UNREACHABLE(); } diff --git a/interface/src/ModelSelector.h b/interface/src/ModelSelector.h index aaa35e01c3..0ac3df5963 100644 --- a/interface/src/ModelSelector.h +++ b/interface/src/ModelSelector.h @@ -29,7 +29,7 @@ public: ModelSelector(); QFileInfo getFileInfo() const; - ModelType getModelType() const; + FSTReader::ModelType getModelType() const; public slots: virtual void accept(); diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index a5b8128d1e..6ed3e48274 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -638,7 +638,7 @@ QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QS /// \param const QString& nameFilter filter to filter filenames /// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue` QScriptValue WindowScriptingInterface::showS3Browse(const QString& nameFilter) { - ModelsBrowser browser(ENTITY_MODEL); + ModelsBrowser browser(FSTReader::ENTITY_MODEL); if (nameFilter != "") { browser.setNameFilter(nameFilter); } diff --git a/interface/src/ui/AttachmentsDialog.cpp b/interface/src/ui/AttachmentsDialog.cpp index 58b46e5bbf..2a6ff2b2b1 100644 --- a/interface/src/ui/AttachmentsDialog.cpp +++ b/interface/src/ui/AttachmentsDialog.cpp @@ -164,7 +164,7 @@ AttachmentData AttachmentPanel::getAttachmentData() const { } void AttachmentPanel::chooseModelURL() { - ModelsBrowser modelBrowser(ATTACHMENT_MODEL, this); + ModelsBrowser modelBrowser(FSTReader::ATTACHMENT_MODEL, this); connect(&modelBrowser, SIGNAL(selected(QString)), SLOT(setModelURL(const QString&))); modelBrowser.browse(); } diff --git a/interface/src/ui/ModelsBrowser.cpp b/interface/src/ui/ModelsBrowser.cpp index 070c6a85cc..91de4e36ac 100644 --- a/interface/src/ui/ModelsBrowser.cpp +++ b/interface/src/ui/ModelsBrowser.cpp @@ -28,7 +28,7 @@ #include "ModelsBrowser.h" -const char* MODEL_TYPE_NAMES[] = { "entities", "heads", "skeletons", "attachments" }; +const char* MODEL_TYPE_NAMES[] = { "entities", "heads", "skeletons", "skeletons", "attachments" }; static const QString S3_URL = "http://s3.amazonaws.com/hifi-public"; static const QString PUBLIC_URL = "http://public.highfidelity.io"; @@ -71,7 +71,7 @@ static const QString propertiesIds[MODEL_METADATA_COUNT] = { "Tags" }; -ModelsBrowser::ModelsBrowser(ModelType modelsType, QWidget* parent) : +ModelsBrowser::ModelsBrowser(FSTReader::ModelType modelsType, QWidget* parent) : QWidget(parent, Qt::WindowStaysOnTopHint), _handler(new ModelHandler(modelsType)) { @@ -184,7 +184,7 @@ void ModelsBrowser::browse() { } -ModelHandler::ModelHandler(ModelType modelsType, QWidget* parent) : +ModelHandler::ModelHandler(FSTReader::ModelType modelsType, QWidget* parent) : QObject(parent), _initiateExit(false), _type(modelsType), diff --git a/interface/src/ui/ModelsBrowser.h b/interface/src/ui/ModelsBrowser.h index 0c8bb59c85..2cb3c67991 100644 --- a/interface/src/ui/ModelsBrowser.h +++ b/interface/src/ui/ModelsBrowser.h @@ -16,21 +16,16 @@ #include #include -class QNetworkReply; +#include -enum ModelType { - ENTITY_MODEL, - HEAD_MODEL, - SKELETON_MODEL, - ATTACHMENT_MODEL -}; +class QNetworkReply; extern const char* MODEL_TYPE_NAMES[]; class ModelHandler : public QObject { Q_OBJECT public: - ModelHandler(ModelType modelsType, QWidget* parent = NULL); + ModelHandler(FSTReader::ModelType modelsType, QWidget* parent = NULL); void lockModel() { _lock.lockForRead(); } QStandardItemModel* getModel() { return &_model; } @@ -51,7 +46,7 @@ private slots: private: bool _initiateExit; - ModelType _type; + FSTReader::ModelType _type; QReadWriteLock _lock; QStandardItemModel _model; QString _nameFilter; @@ -66,7 +61,7 @@ class ModelsBrowser : public QWidget { Q_OBJECT public: - ModelsBrowser(ModelType modelsType, QWidget* parent = NULL); + ModelsBrowser(FSTReader::ModelType modelsType, QWidget* parent = NULL); QString getSelectedFile() { return _selectedFile; } signals: diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index a07de371a2..4a9165ae44 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -67,13 +67,13 @@ void PreferencesDialog::setSkeletonUrl(QString modelUrl) { } void PreferencesDialog::openHeadModelBrowser() { - ModelsBrowser modelBrowser(HEAD_MODEL); + ModelsBrowser modelBrowser(FSTReader::HEAD_MODEL); connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setHeadUrl); modelBrowser.browse(); } void PreferencesDialog::openBodyModelBrowser() { - ModelsBrowser modelBrowser(SKELETON_MODEL); + ModelsBrowser modelBrowser(FSTReader::HEAD_AND_BODY_MODEL); connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setSkeletonUrl); modelBrowser.browse(); } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 3faa06fc53..95ca30a90f 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -101,7 +101,7 @@ void EntityTreeRenderer::init() { _lastAvatarPosition = _viewState->getAvatarPosition() + glm::vec3((float)TREE_SCALE); connect(entityTree, &EntityTree::deletingEntity, this, &EntityTreeRenderer::deletingEntity); - connect(entityTree, &EntityTree::addingEntity, this, &EntityTreeRenderer::checkAndCallPreload); + connect(entityTree, &EntityTree::addingEntity, this, &EntityTreeRenderer::addingEntity); connect(entityTree, &EntityTree::entityScriptChanging, this, &EntityTreeRenderer::entitySciptChanging); connect(entityTree, &EntityTree::changingEntityID, this, &EntityTreeRenderer::changingEntityID); } @@ -110,14 +110,30 @@ void EntityTreeRenderer::shutdown() { _shuttingDown = true; } +void EntityTreeRenderer::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) { + if (_waitingOnPreload.contains(url)) { + QList entityIDs = _waitingOnPreload.values(url); + _waitingOnPreload.remove(url); + foreach(EntityItemID entityID, entityIDs) { + checkAndCallPreload(entityID); + } + } +} -QScriptValue EntityTreeRenderer::loadEntityScript(const EntityItemID& entityItemID) { +void EntityTreeRenderer::errorInLoadingScript(const QUrl& url) { + if (_waitingOnPreload.contains(url)) { + _waitingOnPreload.remove(url); + } +} + +QScriptValue EntityTreeRenderer::loadEntityScript(const EntityItemID& entityItemID, bool isPreload) { EntityItem* entity = static_cast(_tree)->findEntityByEntityItemID(entityItemID); - return loadEntityScript(entity); + return loadEntityScript(entity, isPreload); } -QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorText, bool& isURL) { +QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorText, bool& isURL, bool& isPending, QUrl& urlOut) { + isPending = false; QUrl url(scriptMaybeURLorText); // If the url is not valid, this must be script text... @@ -126,6 +142,7 @@ QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorTe return scriptMaybeURLorText; } isURL = true; + urlOut = url; QString scriptContents; // assume empty @@ -148,20 +165,11 @@ QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorTe qDebug() << "ERROR Loading file:" << fileName; } } else { - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest networkRequest = QNetworkRequest(url); - networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - QNetworkReply* reply = networkAccessManager.get(networkRequest); - qDebug() << "Downloading script at" << url; - QEventLoop loop; - QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); - loop.exec(); - if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) { - scriptContents = reply->readAll(); - } else { - qDebug() << "ERROR Loading file:" << url.toString(); + auto scriptCache = DependencyManager::get(); + + if (!scriptCache->isInBadScriptList(url)) { + scriptContents = scriptCache->getScript(url, this, isPending); } - delete reply; } } @@ -169,7 +177,7 @@ QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorTe } -QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) { +QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity, bool isPreload) { if (_shuttingDown) { return QScriptValue(); // since we're shutting down, we don't load any more scripts } @@ -185,7 +193,7 @@ QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) { // can accomplish all we need to here with just the script "text" and the ID. EntityItemID entityID = entity->getEntityItemID(); QString entityScript = entity->getScript(); - + if (_entityScripts.contains(entityID)) { EntityScriptDetails details = _entityScripts[entityID]; @@ -203,7 +211,23 @@ QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) { } bool isURL = false; // loadScriptContents() will tell us if this is a URL or just text. - QString scriptContents = loadScriptContents(entityScript, isURL); + bool isPending = false; + QUrl url; + QString scriptContents = loadScriptContents(entityScript, isURL, isPending, url); + + if (isPending && isPreload && isURL) { + _waitingOnPreload.insert(url, entityID); + } + + auto scriptCache = DependencyManager::get(); + + if (isURL && scriptCache->isInBadScriptList(url)) { + return QScriptValue(); // no script contents... + } + + if (scriptContents.isEmpty()) { + return QScriptValue(); // no script contents... + } QScriptSyntaxCheckResult syntaxCheck = QScriptEngine::checkSyntax(scriptContents); if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { @@ -211,6 +235,9 @@ QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) { qDebug() << " " << syntaxCheck.errorMessage() << ":" << syntaxCheck.errorLineNumber() << syntaxCheck.errorColumnNumber(); qDebug() << " SCRIPT:" << entityScript; + + scriptCache->addScriptToBadScriptList(url); + return QScriptValue(); // invalid script } @@ -223,6 +250,9 @@ QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) { qDebug() << "EntityTreeRenderer::loadEntityScript() entity:" << entityID; qDebug() << " NOT CONSTRUCTOR"; qDebug() << " SCRIPT:" << entityScript; + + scriptCache->addScriptToBadScriptList(url); + return QScriptValue(); // invalid script } else { entityScriptConstructor = _entitiesScriptEngine->evaluate(scriptContents); @@ -910,6 +940,10 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { _entityScripts.remove(entityID); } +void EntityTreeRenderer::addingEntity(const EntityItemID& entityID) { + checkAndCallPreload(entityID); +} + void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID) { if (_tree && !_shuttingDown) { checkAndCallUnload(entityID); @@ -920,7 +954,7 @@ void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID) { void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID) { if (_tree && !_shuttingDown) { // load the entity script if needed... - QScriptValue entityScript = loadEntityScript(entityID); + QScriptValue entityScript = loadEntityScript(entityID, true); // is preload! if (entityScript.property("preload").isValid()) { QScriptValueList entityArgs = createEntityArgs(entityID); entityScript.property("preload").call(entityScript, entityArgs); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index c4aa5a42d0..e8d70c9df9 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -16,6 +16,7 @@ #include // for RayToEntityIntersectionResult #include #include +#include class Model; class ScriptEngine; @@ -31,7 +32,7 @@ public: }; // Generic client side Octree renderer class. -class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService { +class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService, public ScriptUser { Q_OBJECT public: EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState, @@ -84,6 +85,9 @@ public: /// hovering over, and entering entities void connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface); + virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents); + virtual void errorInLoadingScript(const QUrl& url); + signals: void mousePressOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); void mouseMoveOnEntity(const EntityItemID& entityItemID, const MouseEvent& event); @@ -101,6 +105,7 @@ signals: void leaveEntity(const EntityItemID& entityItemID); public slots: + void addingEntity(const EntityItemID& entityID); void deletingEntity(const EntityItemID& entityID); void changingEntityID(const EntityItemID& oldEntityID, const EntityItemID& newEntityID); void entitySciptChanging(const EntityItemID& entityID); @@ -138,10 +143,10 @@ private: ScriptEngine* _entitiesScriptEngine; ScriptEngine* _sandboxScriptEngine; - QScriptValue loadEntityScript(EntityItem* entity); - QScriptValue loadEntityScript(const EntityItemID& entityItemID); + QScriptValue loadEntityScript(EntityItem* entity, bool isPreload = false); + QScriptValue loadEntityScript(const EntityItemID& entityItemID, bool isPreload = false); QScriptValue getPreviouslyLoadedEntityScript(const EntityItemID& entityItemID); - QString loadScriptContents(const QString& scriptMaybeURLorText, bool& isURL); + QString loadScriptContents(const QString& scriptMaybeURLorText, bool& isURL, bool& isPending, QUrl& url); QScriptValueList createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID); QScriptValueList createMouseEventArgs(const EntityItemID& entityID, const MouseEvent& mouseEvent); @@ -157,6 +162,8 @@ private: bool _dontDoPrecisionPicking; bool _shuttingDown = false; + + QMultiMap _waitingOnPreload; }; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 826df45294..2353a20e7d 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -61,7 +61,7 @@ void RenderableModelEntityItem::remapTextures() { } if (!_model->isLoadedWithTextures()) { - return; // nothing to do if the model has not yet loaded it's default textures + return; // nothing to do if the model has not yet loaded its default textures } if (!_originalTexturesRead && _model->isLoadedWithTextures()) { @@ -220,7 +220,7 @@ Model* RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) { // if we have a URL, then we will want to end up returning a model... if (!getModelURL().isEmpty()) { - // if we have a previously allocated model, but it's URL doesn't match + // if we have a previously allocated model, but its URL doesn't match // then we need to let our renderer update our model for us. if (_model && QUrl(getModelURL()) != _model->getURL()) { result = _model = _myRenderer->updateModel(_model, getModelURL(), getCollisionModelURL()); @@ -293,7 +293,7 @@ bool RenderableModelEntityItem::isReadyToComputeShape() { } if (_model->getCollisionURL().isEmpty()) { - // no model url, so we're ready to compute a shape. + // no collision-model url, so we're ready to compute a shape (of type None). return true; } @@ -312,34 +312,30 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { info.setParams(getShapeType(), 0.5f * getDimensions()); } else { const QSharedPointer collisionNetworkGeometry = _model->getCollisionGeometry(); - const FBXGeometry& fbxGeometry = collisionNetworkGeometry->getFBXGeometry(); + const FBXGeometry& collisionGeometry = collisionNetworkGeometry->getFBXGeometry(); + + const QSharedPointer renderNetworkGeometry = _model->getGeometry(); + const FBXGeometry& renderGeometry = renderNetworkGeometry->getFBXGeometry(); - AABox aaBox; _points.clear(); unsigned int i = 0; - foreach (const FBXMesh& mesh, fbxGeometry.meshes) { + // the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect + // to find one actual "mesh" (with one or more meshParts in it), but we loop over the meshes, just in case. + foreach (const FBXMesh& mesh, collisionGeometry.meshes) { + // each meshPart is a convex hull foreach (const FBXMeshPart &meshPart, mesh.parts) { QVector pointsInPart; + + // run through all the triangles and (uniquely) add each point to the hull unsigned int triangleCount = meshPart.triangleIndices.size() / 3; - assert((unsigned int)meshPart.triangleIndices.size() == triangleCount*3); for (unsigned int j = 0; j < triangleCount; j++) { unsigned int p0Index = meshPart.triangleIndices[j*3]; unsigned int p1Index = meshPart.triangleIndices[j*3+1]; unsigned int p2Index = meshPart.triangleIndices[j*3+2]; - - assert(p0Index < (unsigned int)mesh.vertices.size()); - assert(p1Index < (unsigned int)mesh.vertices.size()); - assert(p2Index < (unsigned int)mesh.vertices.size()); - glm::vec3 p0 = mesh.vertices[p0Index]; glm::vec3 p1 = mesh.vertices[p1Index]; glm::vec3 p2 = mesh.vertices[p2Index]; - - aaBox += p0; - aaBox += p1; - aaBox += p2; - if (!pointsInPart.contains(p0)) { pointsInPart << p0; } @@ -351,17 +347,52 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { } } + // run through all the quads and (uniquely) add each point to the hull + unsigned int quadCount = meshPart.quadIndices.size() / 4; + assert((unsigned int)meshPart.quadIndices.size() == quadCount*4); + for (unsigned int j = 0; j < quadCount; j++) { + unsigned int p0Index = meshPart.quadIndices[j*4]; + unsigned int p1Index = meshPart.quadIndices[j*4+1]; + unsigned int p2Index = meshPart.quadIndices[j*4+2]; + unsigned int p3Index = meshPart.quadIndices[j*4+3]; + glm::vec3 p0 = mesh.vertices[p0Index]; + glm::vec3 p1 = mesh.vertices[p1Index]; + glm::vec3 p2 = mesh.vertices[p2Index]; + glm::vec3 p3 = mesh.vertices[p3Index]; + if (!pointsInPart.contains(p0)) { + pointsInPart << p0; + } + if (!pointsInPart.contains(p1)) { + pointsInPart << p1; + } + if (!pointsInPart.contains(p2)) { + pointsInPart << p2; + } + if (!pointsInPart.contains(p3)) { + pointsInPart << p3; + } + } + + if (pointsInPart.size() == 0) { + qDebug() << "Warning -- meshPart has no faces"; + continue; + } + + // add next convex hull QVector newMeshPoints; _points << newMeshPoints; + // add points to the new convex hull _points[i++] << pointsInPart; } } - // make sure we aren't about to divide by zero - glm::vec3 aaBoxDim = aaBox.getDimensions(); - aaBoxDim = glm::clamp(aaBoxDim, glm::vec3(FLT_EPSILON), aaBoxDim); + // We expect that the collision model will have the same units and will be displaced + // from its origin in the same way the visual model is. The visual model has + // been centered and probably scaled. We take the scaling and offset which were applied + // to the visual model and apply them to the collision model (without regard for the + // collision model's extents). - glm::vec3 scale = _dimensions / aaBoxDim; + glm::vec3 scale = _dimensions / renderGeometry.getUnscaledMeshExtents().size(); // multiply each point by scale before handing the point-set off to the physics engine for (int i = 0; i < _points.size(); i++) { diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 266aa2bdce..cb5d43693d 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -404,7 +404,8 @@ void EntityTree::handleAddEntityResponse(const QByteArray& packet) { EntityItem* foundEntity = NULL; EntityItemID creatorTokenVersion = searchEntityID.convertToCreatorTokenVersion(); EntityItemID knownIDVersion = searchEntityID.convertToKnownIDVersion(); - + + _changedEntityIDs[creatorTokenVersion] = knownIDVersion; // First look for and find the "viewed version" of this entity... it's possible we got // the known ID version sent to us between us creating our local version, and getting this @@ -592,6 +593,9 @@ EntityItem* EntityTree::findEntityByEntityItemID(const EntityItemID& entityID) / EntityTreeElement* containingElement = getContainingElement(entityID); if (containingElement) { foundEntity = containingElement->getEntityWithEntityItemID(entityID); + if (!foundEntity && _changedEntityIDs.contains(entityID)) { + foundEntity = containingElement->getEntityWithEntityItemID(_changedEntityIDs[entityID]); + } } return foundEntity; } @@ -958,6 +962,12 @@ EntityTreeElement* EntityTree::getContainingElement(const EntityItemID& entityIt creatorTokenOnly.isKnownID = false; element = _entityToElementMap.value(creatorTokenOnly); } + + // If we still didn't find the entity, but the ID was in our changed entityIDs, search for the new ID version + if (!element && _changedEntityIDs.contains(entityItemID)) { + element = getContainingElement(_changedEntityIDs[entityItemID]); + } + return element; } diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 29fecc88b4..c49cfb2600 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -195,6 +195,7 @@ private: EntityItemFBXService* _fbxService; QHash _entityToElementMap; + QHash _changedEntityIDs; EntitySimulation* _simulation; diff --git a/libraries/fbx/src/FSTReader.cpp b/libraries/fbx/src/FSTReader.cpp index f1ffe11996..6a5cc2bd53 100644 --- a/libraries/fbx/src/FSTReader.cpp +++ b/libraries/fbx/src/FSTReader.cpp @@ -13,7 +13,7 @@ #include "FSTReader.h" -QVariantHash parseMapping(QIODevice* device) { +QVariantHash FSTReader::parseMapping(QIODevice* device) { QVariantHash properties; QByteArray line; @@ -48,13 +48,13 @@ QVariantHash parseMapping(QIODevice* device) { return properties; } -QVariantHash readMapping(const QByteArray& data) { +QVariantHash FSTReader::readMapping(const QByteArray& data) { QBuffer buffer(const_cast(&data)); buffer.open(QIODevice::ReadOnly); - return parseMapping(&buffer); + return FSTReader::parseMapping(&buffer); } -void writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it) { +void FSTReader::writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it) { QByteArray key = it.key().toUtf8() + " = "; QVariantHash hashValue = it.value().toHash(); if (hashValue.isEmpty()) { @@ -76,8 +76,8 @@ void writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it) { } } -QByteArray writeMapping(const QVariantHash& mapping) { - static const QStringList PREFERED_ORDER = QStringList() << NAME_FIELD << SCALE_FIELD << FILENAME_FIELD +QByteArray FSTReader::writeMapping(const QVariantHash& mapping) { + static const QStringList PREFERED_ORDER = QStringList() << NAME_FIELD << TYPE_FIELD << SCALE_FIELD << FILENAME_FIELD << TEXDIR_FIELD << JOINT_FIELD << FREE_JOINT_FIELD << BLENDSHAPE_FIELD << JOINT_INDEX_FIELD; QBuffer buffer; @@ -96,4 +96,76 @@ QByteArray writeMapping(const QVariantHash& mapping) { } } return buffer.data(); -} \ No newline at end of file +} + +QHash FSTReader::_typesToNames; +QString FSTReader::getNameFromType(ModelType modelType) { + if (_typesToNames.size() == 0) { + _typesToNames[ENTITY_MODEL] = "entity"; + _typesToNames[HEAD_MODEL] = "head"; + _typesToNames[BODY_ONLY_MODEL] = "body"; + _typesToNames[HEAD_AND_BODY_MODEL] = "body+head"; + _typesToNames[ATTACHMENT_MODEL] = "attachment"; + } + return _typesToNames[modelType]; +} + +QHash FSTReader::_namesToTypes; +FSTReader::ModelType FSTReader::getTypeFromName(const QString& name) { + if (_namesToTypes.size() == 0) { + _namesToTypes["entity"] = ENTITY_MODEL; + _namesToTypes["head"] = HEAD_MODEL ; + _namesToTypes["body"] = BODY_ONLY_MODEL; + _namesToTypes["body+head"] = HEAD_AND_BODY_MODEL; + _namesToTypes["attachment"] = ATTACHMENT_MODEL; + } + return _namesToTypes[name]; +} + +FSTReader::ModelType FSTReader::predictModelType(const QVariantHash& mapping) { + + QVariantHash joints; + + if (mapping.contains("joint") && mapping["joint"].type() == QVariant::Hash) { + joints = mapping["joint"].toHash(); + } + + // if the mapping includes the type hint... then we trust the mapping + if (mapping.contains(TYPE_FIELD)) { + return FSTReader::getTypeFromName(mapping[TYPE_FIELD].toString()); + } + + // check for blendshapes + bool hasBlendshapes = mapping.contains(BLENDSHAPE_FIELD); + + // a Head needs to have these minimum fields... + //joint = jointEyeLeft = EyeL = 1 + //joint = jointEyeRight = EyeR = 1 + //joint = jointNeck = Head = 1 + bool hasHeadMinimum = joints.contains("jointNeck") && joints.contains("jointEyeLeft") && joints.contains("jointEyeRight"); + + // a Body needs to have these minimum fields... + //joint = jointRoot = Hips + //joint = jointLean = Spine + //joint = jointNeck = Neck + //joint = jointHead = HeadTop_End + + bool hasBodyMinimumJoints = joints.contains("jointRoot") && joints.contains("jointLean") && joints.contains("jointNeck") + && joints.contains("jointHead"); + + bool isLikelyHead = hasBlendshapes || hasHeadMinimum; + + if (isLikelyHead && hasBodyMinimumJoints) { + return HEAD_AND_BODY_MODEL; + } + + if (isLikelyHead) { + return HEAD_MODEL; + } + + if (hasBodyMinimumJoints) { + return BODY_ONLY_MODEL; + } + + return ENTITY_MODEL; +} diff --git a/libraries/fbx/src/FSTReader.h b/libraries/fbx/src/FSTReader.h index 59559dea74..5752a224c6 100644 --- a/libraries/fbx/src/FSTReader.h +++ b/libraries/fbx/src/FSTReader.h @@ -12,9 +12,11 @@ #ifndef hifi_FSTReader_h #define hifi_FSTReader_h +#include #include static const QString NAME_FIELD = "name"; +static const QString TYPE_FIELD = "type"; static const QString FILENAME_FIELD = "filename"; static const QString TEXDIR_FIELD = "texdir"; static const QString LOD_FIELD = "lod"; @@ -27,10 +29,35 @@ static const QString JOINT_FIELD = "joint"; static const QString FREE_JOINT_FIELD = "freeJoint"; static const QString BLENDSHAPE_FIELD = "bs"; -/// Reads an FST mapping from the supplied data. -QVariantHash readMapping(const QByteArray& data); +class FSTReader { +public: -/// Writes an FST mapping to a byte array. -QByteArray writeMapping(const QVariantHash& mapping); + enum ModelType { + ENTITY_MODEL, + HEAD_MODEL, + BODY_ONLY_MODEL, + HEAD_AND_BODY_MODEL, + ATTACHMENT_MODEL + }; + + /// Reads an FST mapping from the supplied data. + static QVariantHash readMapping(const QByteArray& data); + + /// Writes an FST mapping to a byte array. + static QByteArray writeMapping(const QVariantHash& mapping); + + /// Predicts the type of model by examining the mapping + static ModelType predictModelType(const QVariantHash& mapping); + + static QString getNameFromType(ModelType modelType); + static FSTReader::ModelType getTypeFromName(const QString& name); + +private: + static void writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it); + static QVariantHash parseMapping(QIODevice* device); + + static QHash _typesToNames; + static QHash _namesToTypes; +}; #endif // hifi_FSTReader_h \ No newline at end of file diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index db2733f27b..c87a942baa 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -27,7 +27,8 @@ public: enum SpecialToken { NO_TOKEN = -1, NO_PUSHBACKED_TOKEN = -1, - DATUM_TOKEN = 0x100 + DATUM_TOKEN = 0x100, + COMMENT_TOKEN = 0x101 }; int nextToken(); const QByteArray& getDatum() const { return _datum; } @@ -35,11 +36,13 @@ public: void skipLine() { _device->readLine(); } void pushBackToken(int token) { _pushedBackToken = token; } void ungetChar(char ch) { _device->ungetChar(ch); } + const QString getComment() const { return _comment; } private: QIODevice* _device; QByteArray _datum; int _pushedBackToken; + QString _comment; }; @@ -56,9 +59,11 @@ int OBJTokenizer::nextToken() { continue; // skip whitespace } switch (ch) { - case '#': - _device->readLine(); // skip the comment - break; + case '#': { + _comment = _device->readLine(); // skip the comment + qDebug() << "COMMENT:" << _comment; + return COMMENT_TOKEN; + } case '\"': _datum = ""; @@ -104,7 +109,8 @@ bool OBJTokenizer::isNextTokenFloat() { } bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, - FBXGeometry &geometry, QVector& faceNormals, QVector& faceNormalIndexes) { + FBXGeometry &geometry, QVector& faceNormals, QVector& faceNormalIndexes, + float& scaleGuess) { FBXMesh &mesh = geometry.meshes[0]; mesh.parts.append(FBXMeshPart()); FBXMeshPart &meshPart = mesh.parts.last(); @@ -128,7 +134,17 @@ bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, meshPart._material->setEmissive(glm::vec3(0.0, 0.0, 0.0)); while (true) { - if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { + int tokenType = tokenizer.nextToken(); + if (tokenType == OBJTokenizer::COMMENT_TOKEN) { + if (tokenizer.getComment().contains("This file uses centimeters as units")) { + scaleGuess = 1.0f / 100.0f; + } + if (tokenizer.getComment().contains("This file uses millimeters as units")) { + scaleGuess = 1.0f / 1000.0f; + } + continue; + } + if (tokenType != OBJTokenizer::DATUM_TOKEN) { result = false; break; } @@ -192,6 +208,7 @@ bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, while (true) { if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { if (indices.count() == 0) { + // nonsense, bail out. goto done; } break; @@ -266,22 +283,22 @@ bool parseOBJGroup(OBJTokenizer &tokenizer, const QVariantHash& mapping, } } else { // something we don't (yet) care about - qDebug() << "OBJ parser is skipping a line with" << token; + // qDebug() << "OBJ parser is skipping a line with" << token; tokenizer.skipLine(); } } done: + + if (meshPart.triangleIndices.size() == 0 && meshPart.quadIndices.size() == 0) { + // empty mesh? + mesh.parts.pop_back(); + } + return result; } -FBXGeometry extractOBJGeometry(const FBXNode& node, const QVariantHash& mapping) { - FBXGeometry geometry; - return geometry; -} - - FBXGeometry readOBJ(const QByteArray& model, const QVariantHash& mapping) { QBuffer buffer(const_cast(&model)); buffer.open(QIODevice::ReadOnly); @@ -294,24 +311,30 @@ FBXGeometry readOBJ(QIODevice* device, const QVariantHash& mapping) { OBJTokenizer tokenizer(device); QVector faceNormalIndexes; QVector faceNormals; + float scaleGuess = 1.0f; faceNormalIndexes.clear(); geometry.meshExtents.reset(); geometry.meshes.append(FBXMesh()); - - try { // call parseOBJGroup as long as it's returning true. Each successful call will // add a new meshPart to the geometry's single mesh. bool success = true; while (success) { - success = parseOBJGroup(tokenizer, mapping, geometry, faceNormals, faceNormalIndexes); + success = parseOBJGroup(tokenizer, mapping, geometry, faceNormals, faceNormalIndexes, scaleGuess); } FBXMesh &mesh = geometry.meshes[0]; + // if we got a hint about units, scale all the points + if (scaleGuess != 1.0f) { + for (int i = 0; i < mesh.vertices.size(); i++) { + mesh.vertices[i] *= scaleGuess; + } + } + mesh.meshExtents.reset(); foreach (const glm::vec3& vertex, mesh.vertices) { mesh.meshExtents.addPoint(vertex); diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 739e587f5f..9cf0d53244 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -70,6 +70,7 @@ QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& bool delayLoad, void* extra) { QSharedPointer resource = _resources.value(url); if (!resource.isNull()) { + removeUnusedResource(resource); return resource; } @@ -83,16 +84,14 @@ QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& return getResource(fallback, QUrl(), delayLoad); } - if (resource.isNull()) { - resource = createResource(url, fallback.isValid() ? - getResource(fallback, QUrl(), true) : QSharedPointer(), delayLoad, extra); - resource->setSelf(resource); - resource->setCache(this); - _resources.insert(url, resource); - - } else { - removeUnusedResource(resource); - } + resource = createResource(url, fallback.isValid() ? + getResource(fallback, QUrl(), true) : QSharedPointer(), delayLoad, extra); + resource->setSelf(resource); + resource->setCache(this); + _resources.insert(url, resource); + removeUnusedResource(resource); + resource->ensureLoading(); + return resource; } @@ -134,7 +133,7 @@ void ResourceCache::reserveUnusedResource(qint64 resourceSize) { } void ResourceCache::attemptRequest(Resource* resource) { - auto sharedItems = DependencyManager::get(); + auto sharedItems = DependencyManager::get(); if (_requestLimit <= 0) { // wait until a slot becomes available sharedItems->_pendingRequests.append(resource); @@ -147,7 +146,7 @@ void ResourceCache::attemptRequest(Resource* resource) { void ResourceCache::requestCompleted(Resource* resource) { - auto sharedItems = DependencyManager::get(); + auto sharedItems = DependencyManager::get(); sharedItems->_loadingRequests.removeOne(resource); _requestLimit++; diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index c7aceb2e1a..ebe2c6e684 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -48,14 +48,14 @@ static const qint64 MAX_UNUSED_MAX_SIZE = 10 * BYTES_PER_GIGABYTES; // ResourceCache derived classes. Since we can't count on the ordering of // static members destruction, we need to use this Dependency manager implemented // object instead -class ResouceCacheSharedItems : public Dependency { +class ResourceCacheSharedItems : public Dependency { SINGLETON_DEPENDENCY public: QList > _pendingRequests; QList _loadingRequests; private: - ResouceCacheSharedItems() { } - virtual ~ResouceCacheSharedItems() { } + ResourceCacheSharedItems() { } + virtual ~ResourceCacheSharedItems() { } }; @@ -71,10 +71,10 @@ public: qint64 getUnusedResourceCacheSize() const { return _unusedResourcesMaxSize; } static const QList& getLoadingRequests() - { return DependencyManager::get()->_loadingRequests; } + { return DependencyManager::get()->_loadingRequests; } static int getPendingRequestCount() - { return DependencyManager::get()->_pendingRequests.size(); } + { return DependencyManager::get()->_pendingRequests.size(); } ResourceCache(QObject* parent = NULL); virtual ~ResourceCache(); diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp old mode 100644 new mode 100755 index 626a1ea3a9..f8c8e31ece --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -212,6 +212,9 @@ btVector3 CharacterController::perpindicularComponent(const btVector3& direction } const btVector3 LOCAL_UP_AXIS(0.0f, 1.0f, 0.0f); +const float DEFAULT_GRAVITY = 5.0f; +const float TERMINAL_VELOCITY = 55.0f; +const float JUMP_SPEED = 5.0f; CharacterController::CharacterController(AvatarData* avatarData) { assert(avatarData); @@ -226,9 +229,9 @@ CharacterController::CharacterController(AvatarData* avatarData) { _velocityTimeInterval = 0.0f; _verticalVelocity = 0.0f; _verticalOffset = 0.0f; - _gravity = 5.0f; // slower than Earth's - _maxFallSpeed = 55.0f; // Terminal velocity of a sky diver in m/s. - _jumpSpeed = 5.0f; + _gravity = DEFAULT_GRAVITY; // slower than Earth's + _maxFallSpeed = TERMINAL_VELOCITY; // Terminal velocity of a sky diver in m/s. + _jumpSpeed = JUMP_SPEED; _isOnGround = false; _isJumping = false; _isHovering = true; @@ -274,7 +277,6 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl collisionWorld->getDispatcher()->dispatchAllCollisionPairs(_ghostObject->getOverlappingPairCache(), collisionWorld->getDispatchInfo(), collisionWorld->getDispatcher()); _currentPosition = _ghostObject->getWorldTransform().getOrigin(); - btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS); btVector3 currentPosition = _currentPosition; @@ -308,7 +310,7 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl btVector3 normal = pt.m_normalWorldOnB; normal *= directionSign; // always points from object to character - btScalar normalDotUp = normal.dot(up); + btScalar normalDotUp = normal.dot(_currentUp); if (normalDotUp < _maxSlopeCosine) { // this contact has a non-vertical normal... might need to ignored btVector3 collisionPoint; @@ -319,9 +321,9 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl } // we do math in frame where character base is origin - btVector3 characterBase = currentPosition - (_radius + _halfHeight) * up; + btVector3 characterBase = currentPosition - (_radius + _halfHeight) * _currentUp; collisionPoint -= characterBase; - btScalar collisionHeight = collisionPoint.dot(up); + btScalar collisionHeight = collisionPoint.dot(_currentUp); if (collisionHeight < _lastStepUp) { // This contact is below the lastStepUp, so we ignore it for penetration resolution, @@ -351,17 +353,17 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl return penetration; } + void CharacterController::scanDown(btCollisionWorld* world) { // we test with downward raycast and if we don't find floor close enough then turn on "hover" btKinematicClosestNotMeRayResultCallback callback(_ghostObject); callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; - btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS); btVector3 start = _currentPosition; const btScalar MAX_SCAN_HEIGHT = 20.0f + _halfHeight + _radius; // closest possible floor for disabling hover const btScalar MIN_HOVER_HEIGHT = 3.0f + _halfHeight + _radius; // distance to floor for enabling hover - btVector3 end = start - MAX_SCAN_HEIGHT * up; + btVector3 end = start - MAX_SCAN_HEIGHT * _currentUp; world->rayTest(start, end, callback); if (!callback.hasHit()) { @@ -377,15 +379,14 @@ void CharacterController::stepUp(btCollisionWorld* world) { // compute start and end btTransform start, end; start.setIdentity(); - btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS); - start.setOrigin(_currentPosition + up * (_convexShape->getMargin() + _addedMargin)); + start.setOrigin(_currentPosition + _currentUp * (_convexShape->getMargin() + _addedMargin)); - _targetPosition = _currentPosition + up * _stepUpHeight; + _targetPosition = _currentPosition + _currentUp * _stepUpHeight; end.setIdentity(); end.setOrigin(_targetPosition); // sweep up - btVector3 sweepDirNegative = - up; + btVector3 sweepDirNegative = - _currentUp; btKinematicClosestNotMeConvexResultCallback callback(_ghostObject, sweepDirNegative, btScalar(0.7071)); callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; @@ -397,7 +398,7 @@ void CharacterController::stepUp(btCollisionWorld* world) { _verticalOffset = 0.0f; // Only modify the position if the hit was a slope and not a wall or ceiling. - if (callback.m_hitNormalWorld.dot(up) > 0.0f) { + if (callback.m_hitNormalWorld.dot(_currentUp) > 0.0f) { _lastStepUp = _stepUpHeight * callback.m_closestHitFraction; _currentPosition.setInterpolate3(_currentPosition, _targetPosition, callback.m_closestHitFraction); } else { @@ -469,8 +470,8 @@ void CharacterController::stepForward(btCollisionWorld* collisionWorld, const bt // sweep forward btVector3 sweepDirNegative(_currentPosition - _targetPosition); btKinematicClosestNotMeConvexResultCallback callback(_ghostObject, sweepDirNegative, btScalar(0.0)); - callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; - callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; + callback.m_collisionFilterGroup = _ghostObject->getBroadphaseHandle()->m_collisionFilterGroup; + callback.m_collisionFilterMask = _ghostObject->getBroadphaseHandle()->m_collisionFilterMask; _ghostObject->convexSweepTest(_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); if (callback.hasHit()) { @@ -502,17 +503,16 @@ void CharacterController::stepDown(btCollisionWorld* collisionWorld, btScalar dt // reach of the character's feet. // first sweep for ledge - btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS); - btVector3 step = (_verticalVelocity * dt - _lastStepUp) * up; + btVector3 step = (_verticalVelocity * dt - _lastStepUp) * _currentUp; StepDownConvexResultCallback callback(_ghostObject, - up, + _currentUp, _currentPosition, step, _walkDirection, _maxSlopeCosine, _radius, _halfHeight); - callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; - callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; + callback.m_collisionFilterGroup = _ghostObject->getBroadphaseHandle()->m_collisionFilterGroup; + callback.m_collisionFilterMask = _ghostObject->getBroadphaseHandle()->m_collisionFilterMask; btTransform start, end; start.setIdentity(); @@ -532,16 +532,16 @@ void CharacterController::stepDown(btCollisionWorld* collisionWorld, btScalar dt _isOnGround = true; } else if (!_isJumping) { // sweep again for floor within downStep threshold - step = -_stepDownHeight * up; + step = -_stepDownHeight * _currentUp; StepDownConvexResultCallback callback2 (_ghostObject, - up, + _currentUp, _currentPosition, step, _walkDirection, _maxSlopeCosine, _radius, _halfHeight); - callback2.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; - callback2.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; + callback2.m_collisionFilterGroup = _ghostObject->getBroadphaseHandle()->m_collisionFilterGroup; + callback2.m_collisionFilterMask = _ghostObject->getBroadphaseHandle()->m_collisionFilterMask; _currentPosition = _targetPosition; _targetPosition = _currentPosition + step; @@ -617,10 +617,10 @@ void CharacterController::preStep(btCollisionWorld* collisionWorld) { } } + // the CharacterController algorithm can only change the position, + // so we don't bother to pull the rotation out of the transform const btTransform& transform = _ghostObject->getWorldTransform(); - _currentRotation = transform.getRotation(); _currentPosition = transform.getOrigin(); - _targetPosition = _currentPosition; } void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar dt) { @@ -868,6 +868,7 @@ void CharacterController::updateShapeIfNecessary() { void CharacterController::preSimulation(btScalar timeStep) { if (_enabled && _dynamicsWorld) { glm::quat rotation = _avatarData->getOrientation(); + _currentUp = quatRotate(glmToBullet(rotation), LOCAL_UP_AXIS); glm::vec3 position = _avatarData->getPosition() + rotation * _shapeLocalOffset; btVector3 walkVelocity = glmToBullet(_avatarData->getVelocity()); @@ -895,8 +896,7 @@ void CharacterController::postSimulation() { // cap the velocity of the step so that the character doesn't POP! so hard on steps glm::vec3 finalStep = position - _lastPosition; btVector3 finalVelocity = _walkDirection; - btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS); - finalVelocity += _verticalVelocity * up; + finalVelocity += _verticalVelocity * _currentUp; const btScalar MAX_RESOLUTION_SPEED = 5.0f; // m/sec btScalar maxStepLength = glm::max(MAX_RESOLUTION_SPEED, 2.0f * finalVelocity.length()) * _stepDt; btScalar stepLength = glm::length(finalStep); diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index eeaa5836dd..e4e73b6d3f 100644 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -42,9 +42,22 @@ class btPairCachingGhostObject; ATTRIBUTE_ALIGNED16(class) CharacterController : public btCharacterControllerInterface { protected: + ///this is the desired walk direction, set by the user + btVector3 _walkDirection; + btVector3 _normalizedDirection; + + //some internal variables + btVector3 _currentPosition; + btVector3 _currentUp; + btVector3 _targetPosition; + glm::vec3 _lastPosition; + btVector3 _floorNormal; // points from object to character + + glm::vec3 _shapeLocalOffset; + glm::vec3 _boxScale; // used to compute capsule shape AvatarData* _avatarData = NULL; - btPairCachingGhostObject* _ghostObject; + btPairCachingGhostObject* _ghostObject = NULL; btConvexShape* _convexShape;//is also in _ghostObject, but it needs to be convex, so we store it here to avoid upcast btScalar _radius; @@ -64,22 +77,12 @@ protected: btScalar _addedMargin;//@todo: remove this and fix the code - ///this is the desired walk direction, set by the user - btVector3 _walkDirection; - btVector3 _normalizedDirection; - - //some internal variables - btVector3 _currentPosition; - btQuaternion _currentRotation; - btVector3 _targetPosition; - glm::vec3 _lastPosition; btScalar _lastStepUp; ///keep track of the contact manifolds btManifoldArray _manifoldArray; bool _touchingContact; - btVector3 _floorNormal; // points from object to character bool _enabled; bool _isOnGround; @@ -90,9 +93,6 @@ protected: btScalar _stepDt; uint32_t _pendingFlags; - glm::vec3 _shapeLocalOffset; - glm::vec3 _boxScale; // used to compute capsule shape - btDynamicsWorld* _dynamicsWorld = NULL; btVector3 computeReflectionDirection(const btVector3& direction, const btVector3& normal); diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 139a954b22..467b51560f 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -36,8 +36,8 @@ PhysicsEngine::~PhysicsEngine() { delete _collisionDispatcher; delete _broadphaseFilter; delete _constraintSolver; - // delete _dynamicsWorld; - // delete _ghostPairCallback; + delete _dynamicsWorld; + delete _ghostPairCallback; } // begin EntitySimulation overrides diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index ffa84adbdf..f46bd689d4 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -535,10 +535,13 @@ void DeferredLightingEffect::loadLightProgram(const char* fragSource, bool limit } void DeferredLightingEffect::setAmbientLightMode(int preset) { - if ((preset >= -1) && (preset < model::SphericalHarmonics::NUM_PRESET)) { + if ((preset >= 0) && (preset < model::SphericalHarmonics::NUM_PRESET)) { _ambientLightMode = preset; auto light = _allocatedLights.front(); light->setAmbientSpherePreset(model::SphericalHarmonics::Preset(preset % model::SphericalHarmonics::NUM_PRESET)); + } else { + // force to preset 0 + setAmbientLightMode(0); } } diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index f8fc4633cc..98843cc87f 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -1775,10 +1775,10 @@ QSharedPointer GeometryCache::getGeometry(const QUrl& url, cons return getResource(url, fallback, delayLoad, NULL).staticCast(); } -QSharedPointer GeometryCache::createResource(const QUrl& url, - const QSharedPointer& fallback, bool delayLoad, const void* extra) { +QSharedPointer GeometryCache::createResource(const QUrl& url, const QSharedPointer& fallback, + bool delayLoad, const void* extra) { QSharedPointer geometry(new NetworkGeometry(url, fallback.staticCast(), delayLoad), - &Resource::allReferencesCleared); + &Resource::allReferencesCleared); geometry->setLODParent(geometry); return geometry.staticCast(); } @@ -2132,7 +2132,7 @@ void NetworkGeometry::downloadFinished(QNetworkReply* reply) { QUrl url = reply->url(); if (url.path().toLower().endsWith(".fst")) { // it's a mapping file; parse it and get the mesh filename - _mapping = readMapping(reply->readAll()); + _mapping = FSTReader::readMapping(reply->readAll()); reply->deleteLater(); QString filename = _mapping.value("filename").toString(); if (filename.isNull()) { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 81c9f9448c..e9cfdaf345 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -2372,7 +2372,7 @@ int Model::renderMeshesForModelsInScene(gpu::Batch& batch, RenderMode mode, bool int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args, - bool forceRenderSomeMeshes) { + bool forceRenderMeshes) { PROFILE_RANGE(__FUNCTION__); int meshPartsRendered = 0; @@ -2395,7 +2395,7 @@ int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, fl pickPrograms(batch, mode, translucent, alphaThreshold, hasLightmap, hasTangents, hasSpecular, isSkinned, args, locations, skinLocations); meshPartsRendered = renderMeshesFromList(list, batch, mode, translucent, alphaThreshold, - args, locations, skinLocations, forceRenderSomeMeshes); + args, locations, skinLocations, forceRenderMeshes); GLBATCH(glUseProgram)(0); return meshPartsRendered; @@ -2403,7 +2403,7 @@ int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, fl int Model::renderMeshesFromList(QVector& list, gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, RenderArgs* args, - Locations* locations, SkinLocations* skinLocations, bool forceRenderSomeMeshes) { + Locations* locations, SkinLocations* skinLocations, bool forceRenderMeshes) { PROFILE_RANGE(__FUNCTION__); auto textureCache = DependencyManager::get(); @@ -2439,21 +2439,14 @@ int Model::renderMeshesFromList(QVector& list, gpu::Batch& batch, RenderMod // if we got here, then check to see if this mesh is in view if (args) { bool shouldRender = true; - bool forceRender = false; args->_meshesConsidered++; if (args->_viewFrustum) { - // NOTE: This is a hack to address the fact that for avatar meshes, the _calculatedMeshBoxes can be wrong - // for some meshes. Those meshes where the mesh's modelTransform is the identity matrix, and will have - // incorrectly calculated mesh boxes. In this case, we will ignore the box and assume it's visible. - if (forceRenderSomeMeshes && (geometry.meshes.at(i).modelTransform == glm::mat4())) { - forceRender = true; - } - - shouldRender = forceRender || args->_viewFrustum->boxInFrustum(_calculatedMeshBoxes.at(i)) != ViewFrustum::OUTSIDE; + shouldRender = forceRenderMeshes || + args->_viewFrustum->boxInFrustum(_calculatedMeshBoxes.at(i)) != ViewFrustum::OUTSIDE; - if (shouldRender && !forceRender) { + if (shouldRender && !forceRenderMeshes) { float distance = args->_viewFrustum->distanceToCamera(_calculatedMeshBoxes.at(i).calcCenter()); shouldRender = !_viewState ? false : _viewState->shouldRenderMesh(_calculatedMeshBoxes.at(i).getLargestDimension(), distance); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 3b4cbdd450..299853ec9d 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -460,14 +460,14 @@ private: bool renderCore(float alpha, RenderMode mode, RenderArgs* args); int renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args = NULL, - bool forceRenderSomeMeshes = false); + bool forceRenderMeshes = false); void setupBatchTransform(gpu::Batch& batch); QVector* pickMeshList(bool translucent, float alphaThreshold, bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned); int renderMeshesFromList(QVector& list, gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, RenderArgs* args, Locations* locations, SkinLocations* skinLocations, - bool forceRenderSomeMeshes = false); + bool forceRenderMeshes = false); static void pickPrograms(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args, diff --git a/libraries/render-utils/src/TextRenderer.cpp b/libraries/render-utils/src/TextRenderer.cpp index 9238db91e3..75448f90cc 100644 --- a/libraries/render-utils/src/TextRenderer.cpp +++ b/libraries/render-utils/src/TextRenderer.cpp @@ -359,8 +359,12 @@ void Font::setupGL() { // FIXME there has to be a cleaner way of doing this QStringList Font::tokenizeForWrapping(const QString & str) const { QStringList result; - foreach(const QString & token1, str.split(" ", QString::SkipEmptyParts)) { + foreach(const QString & token1, str.split(" ")) { bool lineFeed = false; + if (token1.isEmpty()) { + result << token1; + continue; + } foreach(const QString & token2, token1.split("\n")) { if (lineFeed) { result << "\n"; diff --git a/libraries/script-engine/src/ScriptCache.cpp b/libraries/script-engine/src/ScriptCache.cpp new file mode 100644 index 0000000000..c0ffc7b4e7 --- /dev/null +++ b/libraries/script-engine/src/ScriptCache.cpp @@ -0,0 +1,76 @@ +// +// ScriptCache.cpp +// libraries/script-engine/src +// +// Created by Brad Hefta-Gaub on 2015-03-30 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include +#include + +#include +#include + +#include "ScriptCache.h" + +ScriptCache::ScriptCache(QObject* parent) { + // nothing to do here... +} + +QString ScriptCache::getScript(const QUrl& url, ScriptUser* scriptUser, bool& isPending) { + QString scriptContents; + if (_scriptCache.contains(url)) { + qDebug() << "Found script in cache:" << url.toString(); + scriptContents = _scriptCache[url]; + scriptUser->scriptContentsAvailable(url, scriptContents); + isPending = false; + } else { + isPending = true; + bool alreadyWaiting = _scriptUsers.contains(url); + _scriptUsers.insert(url, scriptUser); + + if (alreadyWaiting) { + qDebug() << "Already downloading script at:" << url.toString(); + } else { + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest networkRequest = QNetworkRequest(url); + networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + + qDebug() << "Downloading script at:" << url.toString(); + QNetworkReply* reply = networkAccessManager.get(networkRequest); + connect(reply, &QNetworkReply::finished, this, &ScriptCache::scriptDownloaded); + } + } + return scriptContents; +} + +void ScriptCache::scriptDownloaded() { + QNetworkReply* reply = qobject_cast(sender()); + QUrl url = reply->url(); + QList scriptUsers = _scriptUsers.values(url); + _scriptUsers.remove(url); + + if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) { + _scriptCache[url] = reply->readAll(); + qDebug() << "Done downloading script at:" << url.toString(); + + foreach(ScriptUser* user, scriptUsers) { + user->scriptContentsAvailable(url, _scriptCache[url]); + } + } else { + qDebug() << "ERROR Loading file:" << reply->url().toString(); + foreach(ScriptUser* user, scriptUsers) { + user->errorInLoadingScript(url); + } + } + reply->deleteLater(); +} + + diff --git a/libraries/script-engine/src/ScriptCache.h b/libraries/script-engine/src/ScriptCache.h new file mode 100644 index 0000000000..16bf04c53e --- /dev/null +++ b/libraries/script-engine/src/ScriptCache.h @@ -0,0 +1,44 @@ +// +// ScriptCache.h +// libraries/script-engine/src +// +// Created by Brad Hefta-Gaub on 2015-03-30 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ScriptCache_h +#define hifi_ScriptCache_h + +#include + +class ScriptUser { +public: + virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents) = 0; + virtual void errorInLoadingScript(const QUrl& url) = 0; +}; + +/// Interface for loading scripts +class ScriptCache : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public: + QString getScript(const QUrl& url, ScriptUser* scriptUser, bool& isPending); + void addScriptToBadScriptList(const QUrl& url) { _badScripts.insert(url); } + bool isInBadScriptList(const QUrl& url) { return _badScripts.contains(url); } + +private slots: + void scriptDownloaded(); + +private: + ScriptCache(QObject* parent = NULL); + + QHash _scriptCache; + QMultiMap _scriptUsers; + QSet _badScripts; +}; + +#endif // hifi_ScriptCache_h \ No newline at end of file diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 831db73a0a..c31f5be46a 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -34,6 +34,7 @@ #include "EventTypes.h" #include "MenuItemProperties.h" #include "ScriptAudioInjector.h" +#include "ScriptCache.h" #include "ScriptEngine.h" #include "TypedArrays.h" #include "XMLHttpRequestClass.h" @@ -275,31 +276,26 @@ void ScriptEngine::loadURL(const QUrl& scriptURL) { _scriptContents = in.readAll(); emit scriptLoaded(_fileNameString); } else { - qDebug() << "ERROR Loading file:" << _fileNameString; + qDebug() << "ERROR Loading file:" << _fileNameString << "line:" << __LINE__; emit errorLoadingScript(_fileNameString); } } else { - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest networkRequest = QNetworkRequest(url); - networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - QNetworkReply* reply = networkAccessManager.get(networkRequest); - connect(reply, &QNetworkReply::finished, this, &ScriptEngine::handleScriptDownload); + bool isPending; + auto scriptCache = DependencyManager::get(); + scriptCache->getScript(url, this, isPending); + } } } -void ScriptEngine::handleScriptDownload() { - QNetworkReply* reply = qobject_cast(sender()); - - if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) { - _scriptContents = reply->readAll(); - emit scriptLoaded(_fileNameString); - } else { - qDebug() << "ERROR Loading file:" << reply->url().toString(); - emit errorLoadingScript(_fileNameString); - } - - reply->deleteLater(); +void ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) { + _scriptContents = scriptContents; + emit scriptLoaded(_fileNameString); +} + +void ScriptEngine::errorInLoadingScript(const QUrl& url) { + qDebug() << "ERROR Loading file:" << url.toString() << "line:" << __LINE__; + emit errorLoadingScript(_fileNameString); // ?? } void ScriptEngine::init() { @@ -765,7 +761,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac for (QUrl url : urls) { QString contents = data[url]; if (contents.isNull()) { - qDebug() << "Error loading file: " << url; + qDebug() << "Error loading file: " << url << "line:" << __LINE__; } else { QScriptValue result = evaluate(contents, url.toString()); } diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 84d2527bd0..45e56850a7 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -28,6 +28,7 @@ #include "ArrayBufferClass.h" #include "AudioScriptingInterface.h" #include "Quat.h" +#include "ScriptCache.h" #include "ScriptUUID.h" #include "Vec3.h" @@ -35,7 +36,7 @@ const QString NO_SCRIPT(""); const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0 / 60.0f) * 1000 * 1000) + 0.5); -class ScriptEngine : public QScriptEngine { +class ScriptEngine : public QScriptEngine, public ScriptUser { Q_OBJECT public: ScriptEngine(const QString& scriptContents = NO_SCRIPT, @@ -94,6 +95,9 @@ public: void waitTillDoneRunning(); + virtual void scriptContentsAvailable(const QUrl& url, const QString& scriptContents); + virtual void errorInLoadingScript(const QUrl& url); + public slots: void loadURL(const QUrl& scriptURL); void stop(); @@ -160,8 +164,6 @@ private: ArrayBufferClass* _arrayBufferClass; QHash _outgoingScriptAudioSequenceNumbers; -private slots: - void handleScriptDownload(); private: static QSet _allKnownScriptEngines; diff --git a/libraries/shared/src/Extents.h b/libraries/shared/src/Extents.h index 95f242c30b..024d72013a 100644 --- a/libraries/shared/src/Extents.h +++ b/libraries/shared/src/Extents.h @@ -14,6 +14,7 @@ #define hifi_Extents_h #include +#include #include #include "StreamUtils.h" @@ -46,6 +47,9 @@ public: /// rotate the extents around orign by rotation void rotate(const glm::quat& rotation); + glm::vec3 size() const { return maximum - minimum; } + float largestDimension() const {glm::vec3 s = size(); return glm::max(s[0], s[1], s[2]); } + /// \return new Extents which is original rotated around orign by rotation Extents getRotated(const glm::quat& rotation) const { Extents temp = { minimum, maximum }; @@ -68,4 +72,4 @@ inline QDebug operator<<(QDebug debug, const Extents& extents) { } -#endif // hifi_Extents_h \ No newline at end of file +#endif // hifi_Extents_h diff --git a/libraries/shared/src/SettingInterface.cpp b/libraries/shared/src/SettingInterface.cpp index 0ef22e3fcb..217a8f00b2 100644 --- a/libraries/shared/src/SettingInterface.cpp +++ b/libraries/shared/src/SettingInterface.cpp @@ -1,6 +1,6 @@ // // SettingInterface.cpp -// +// libraries/shared/src // // Created by Clement on 2/2/15. // Copyright 2015 High Fidelity, Inc. @@ -9,7 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // - #include #include #include @@ -23,8 +22,16 @@ namespace Setting { // cleans up the settings private instance. Should only be run once at closing down. void cleanupPrivateInstance() { - delete privateInstance; - privateInstance = nullptr; + // grab the thread before we nuke the instance + QThread* settingsManagerThread = privateInstance->thread(); + + // tell the private instance to clean itself up on its thread + privateInstance->deleteLater(); + privateInstance = NULL; + + // quit the settings manager thread and wait on it to make sure it's gone + settingsManagerThread->quit(); + settingsManagerThread->wait(); } // Sets up the settings private instance. Should only be run once at startup diff --git a/libraries/shared/src/StreamUtils.cpp b/libraries/shared/src/StreamUtils.cpp index 96967648ed..f58115d288 100644 --- a/libraries/shared/src/StreamUtils.cpp +++ b/libraries/shared/src/StreamUtils.cpp @@ -143,4 +143,12 @@ QDebug& operator<<(QDebug& dbg, const glm::mat4& m) { return dbg << " ]}"; } +QDebug& operator<<(QDebug& dbg, const QVariantHash& v) { + dbg.nospace() << "["; + for (QVariantHash::const_iterator it = v.constBegin(); it != v.constEnd(); it++) { + dbg << it.key() << ":" << it.value(); + } + return dbg << " ]"; +} + #endif // QT_NO_DEBUG_STREAM diff --git a/libraries/shared/src/StreamUtils.h b/libraries/shared/src/StreamUtils.h index b9823a6743..fd22f7c068 100644 --- a/libraries/shared/src/StreamUtils.h +++ b/libraries/shared/src/StreamUtils.h @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -54,6 +55,7 @@ QDebug& operator<<(QDebug& s, const glm::vec3& v); QDebug& operator<<(QDebug& s, const glm::vec4& v); QDebug& operator<<(QDebug& s, const glm::quat& q); QDebug& operator<<(QDebug& s, const glm::mat4& m); +QDebug& operator<<(QDebug& dbg, const QVariantHash& v); #endif // QT_NO_DEBUG_STREAM #endif // hifi_StreamUtils_h diff --git a/tools/vhacd/src/VHACDUtil.cpp b/tools/vhacd/src/VHACDUtil.cpp index bc9ad09bde..4f2cb0e2b8 100644 --- a/tools/vhacd/src/VHACDUtil.cpp +++ b/tools/vhacd/src/VHACDUtil.cpp @@ -44,7 +44,13 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, vhacd::LoadFBXResults *re int count = 0; foreach(FBXMesh mesh, geometry.meshes) { //get vertices for each mesh - QVector vertices = mesh.vertices; + // QVector vertices = mesh.vertices; + + + QVector vertices; + foreach (glm::vec3 vertex, mesh.vertices) { + vertices.append(glm::vec3(mesh.modelTransform * glm::vec4(vertex, 1.0f))); + } //get the triangle indices for each mesh QVector triangles; @@ -57,8 +63,15 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, vhacd::LoadFBXResults *re if (triangles.count() <= 0){ continue; } + + AABox aaBox; + foreach (glm::vec3 p, vertices) { + aaBox += p; + } + results->perMeshVertices.append(vertices); results->perMeshTriangleIndices.append(triangles); + results->perMeshLargestDimension.append(aaBox.getLargestDimension()); count++; } @@ -66,23 +79,117 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, vhacd::LoadFBXResults *re return true; } -bool vhacd::VHACDUtil::computeVHACD(vhacd::LoadFBXResults *meshes, VHACD::IVHACD::Parameters params, vhacd::ComputeResults *results)const{ + +void vhacd::VHACDUtil::combineMeshes(vhacd::LoadFBXResults *meshes, vhacd::LoadFBXResults *results) const { + float largestDimension = 0; + int indexStart = 0; + + QVector emptyVertices; + QVector emptyTriangles; + results->perMeshVertices.append(emptyVertices); + results->perMeshTriangleIndices.append(emptyTriangles); + results->perMeshLargestDimension.append(largestDimension); + + for (int i = 0; i < meshes->meshCount; i++) { + QVector vertices = meshes->perMeshVertices.at(i); + QVector triangles = meshes->perMeshTriangleIndices.at(i); + const float largestDimension = meshes->perMeshLargestDimension.at(i); + + for (int j = 0; j < triangles.size(); j++) { + triangles[ j ] += indexStart; + } + indexStart += vertices.size(); + + results->perMeshVertices[0] << vertices; + results->perMeshTriangleIndices[0] << triangles; + if (results->perMeshLargestDimension[0] < largestDimension) { + results->perMeshLargestDimension[0] = largestDimension; + } + } + + results->meshCount = 1; +} + + +void vhacd::VHACDUtil::fattenMeshes(vhacd::LoadFBXResults *meshes, vhacd::LoadFBXResults *results) const { + + for (int i = 0; i < meshes->meshCount; i++) { + QVector vertices = meshes->perMeshVertices.at(i); + QVector triangles = meshes->perMeshTriangleIndices.at(i); + const float largestDimension = meshes->perMeshLargestDimension.at(i); + + results->perMeshVertices.append(vertices); + results->perMeshTriangleIndices.append(triangles); + results->perMeshLargestDimension.append(largestDimension); + + for (int j = 0; j < triangles.size(); j += 3) { + auto p0 = vertices[triangles[j]]; + auto p1 = vertices[triangles[j+1]]; + auto p2 = vertices[triangles[j+2]]; + + auto d0 = p1 - p0; + auto d1 = p2 - p0; + + auto cp = glm::cross(d0, d1); + cp = 5.0f * glm::normalize(cp); + + auto p3 = p0 + cp; + auto p4 = p1 + cp; + auto p5 = p2 + cp; + + auto n = results->perMeshVertices.size(); + results->perMeshVertices[i] << p3 << p4 << p5; + results->perMeshTriangleIndices[i] << n << n+1 << n+2; + } + + results->meshCount++; + } +} + + + +bool vhacd::VHACDUtil::computeVHACD(vhacd::LoadFBXResults *meshes, VHACD::IVHACD::Parameters params, + vhacd::ComputeResults *results, + int startMeshIndex, int endMeshIndex, float minimumMeshSize) const { + + // vhacd::LoadFBXResults *meshes = new vhacd::LoadFBXResults; + // combineMeshes(inMeshes, meshes); + + // vhacd::LoadFBXResults *meshes = new vhacd::LoadFBXResults; + // fattenMeshes(inMeshes, meshes); + + VHACD::IVHACD * interfaceVHACD = VHACD::CreateVHACD(); int meshCount = meshes->meshCount; int count = 0; std::cout << "Performing V-HACD computation on " << meshCount << " meshes ..... " << std::endl; - for (int i = 0; i < meshCount; i++){ + if (startMeshIndex < 0) { + startMeshIndex = 0; + } + if (endMeshIndex < 0) { + endMeshIndex = meshCount; + } + for (int i = startMeshIndex; i < endMeshIndex; i++){ + qDebug() << "--------------------"; std::vector vertices = meshes->perMeshVertices.at(i).toStdVector(); std::vector triangles = meshes->perMeshTriangleIndices.at(i).toStdVector(); int nPoints = (unsigned int)vertices.size(); int nTriangles = (unsigned int)triangles.size() / 3; - std::cout << "Mesh " << i + 1 << " : "; + const float largestDimension = meshes->perMeshLargestDimension.at(i); + + qDebug() << "Mesh " << i << " -- " << nPoints << " points, " << nTriangles << " triangles, " + << "size =" << largestDimension; + + if (largestDimension < minimumMeshSize /* || largestDimension > 1000 */) { + qDebug() << " Skipping..."; + continue; + } // compute approximate convex decomposition bool res = interfaceVHACD->Compute(&vertices[0].x, 3, nPoints, &triangles[0], 3, nTriangles, params); if (!res){ - std::cout << "V-HACD computation failed for Mesh : " << i + 1 << std::endl; + qDebug() << "V-HACD computation failed for Mesh : " << i; continue; } count++; //For counting number of successfull computations @@ -111,8 +218,6 @@ bool vhacd::VHACDUtil::computeVHACD(vhacd::LoadFBXResults *meshes, VHACD::IVHACD m_triangles_copy[ i ] = hull.m_triangles[ i ]; } hull.m_triangles = m_triangles_copy; - - convexHulls.append(hull); } results->convexHullList.append(convexHulls); diff --git a/tools/vhacd/src/VHACDUtil.h b/tools/vhacd/src/VHACDUtil.h index b0b9da9720..fd5d253334 100644 --- a/tools/vhacd/src/VHACDUtil.h +++ b/tools/vhacd/src/VHACDUtil.h @@ -34,12 +34,16 @@ namespace vhacd { int meshCount; QVector> perMeshVertices; QVector> perMeshTriangleIndices; + QVector perMeshLargestDimension; } LoadFBXResults; class VHACDUtil { public: bool loadFBX(const QString filename, vhacd::LoadFBXResults *results); - bool computeVHACD(vhacd::LoadFBXResults *meshes, VHACD::IVHACD::Parameters params, vhacd::ComputeResults *results)const; + void combineMeshes(vhacd::LoadFBXResults *meshes, vhacd::LoadFBXResults *results) const; + void fattenMeshes(vhacd::LoadFBXResults *meshes, vhacd::LoadFBXResults *results) const; + bool computeVHACD(vhacd::LoadFBXResults *meshes, VHACD::IVHACD::Parameters params, + vhacd::ComputeResults *results, int startMeshIndex, int endMeshIndex, float minimumMeshSize) const; ~VHACDUtil(); }; diff --git a/tools/vhacd/src/VHACDUtilApp.cpp b/tools/vhacd/src/VHACDUtilApp.cpp index b4c141acae..a1a04fd127 100644 --- a/tools/vhacd/src/VHACDUtilApp.cpp +++ b/tools/vhacd/src/VHACDUtilApp.cpp @@ -98,6 +98,15 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : const QCommandLineOption outputFilenameOption("o", "output file", "filename.obj"); parser.addOption(outputFilenameOption); + const QCommandLineOption startMeshIndexOption("s", "start-mesh index", "0"); + parser.addOption(startMeshIndexOption); + + const QCommandLineOption endMeshIndexOption("e", "end-mesh index", "0"); + parser.addOption(endMeshIndexOption); + + const QCommandLineOption minimumMeshSizeOption("m", "minimum mesh size to consider", "0"); + parser.addOption(minimumMeshSizeOption); + if (!parser.parse(QCoreApplication::arguments())) { qCritical() << parser.errorText() << endl; @@ -138,13 +147,31 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : Q_UNREACHABLE(); } + int startMeshIndex = -1; + // check for an assignment pool passed on the command line or in the config + if (parser.isSet(startMeshIndexOption)) { + startMeshIndex = parser.value(startMeshIndexOption).toInt(); + } + + int endMeshIndex = -1; + // check for an assignment pool passed on the command line or in the config + if (parser.isSet(endMeshIndexOption)) { + endMeshIndex = parser.value(endMeshIndexOption).toInt(); + } + + float minimumMeshSize = 0.0f; + // check for an assignment pool passed on the command line or in the config + if (parser.isSet(minimumMeshSizeOption)) { + minimumMeshSize = parser.value(minimumMeshSizeOption).toFloat(); + } + //set parameters for V-HACD params.m_callback = &pCallBack; //progress callback params.m_resolution = 100000; // 100000 params.m_depth = 20; // 20 params.m_concavity = 0.001; // 0.001 - params.m_delta = 0.01; // 0.05 + params.m_delta = 0.05; // 0.05 params.m_planeDownsampling = 4; // 4 params.m_convexhullDownsampling = 4; // 4 params.m_alpha = 0.05; // 0.05 // controls the bias toward clipping along symmetry planes @@ -153,7 +180,7 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : params.m_pca = 0; // 0 enable/disable normalizing the mesh before applying the convex decomposition params.m_mode = 0; // 0: voxel-based (recommended), 1: tetrahedron-based params.m_maxNumVerticesPerCH = 64; // 64 - params.m_minVolumePerCH = 0.00001; // 0.0001 + params.m_minVolumePerCH = 0.0001; // 0.0001 params.m_callback = 0; // 0 params.m_logger = 0; // 0 params.m_convexhullApproximation = true; // true @@ -172,7 +199,7 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : begin = std::chrono::high_resolution_clock::now(); - if (!vUtil.computeVHACD(&fbx, params, &results)){ + if (!vUtil.computeVHACD(&fbx, params, &results, startMeshIndex, endMeshIndex, minimumMeshSize)) { cout << "Compute Failed..."; } end = std::chrono::high_resolution_clock::now();