diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 64f4aa6821..d874100ca2 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -453,6 +453,13 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // or that somehow we haven't sent if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) { ++numAvatarsHeldBack; + + // BUGZ-781 verbose debugging: + auto usecLastTimeSent = destinationNodeData->getLastOtherAvatarEncodeTime(sourceAvatarNodeData->getNodeLocalID()); + if (usecLastTimeSent != 0 && startIgnoreCalculation - usecLastTimeSent > 10 * USECS_PER_SECOND) { + qCDebug(avatars) << "Not sent avatar" << *sourceAvatarNode << "to Node" << *destinationNode << "in > 10 s"; + } + sendAvatar = false; } else if (lastSeqFromSender == 0) { // We have have not yet received any data about this avatar. Ignore it for now diff --git a/cmake/macros/AutoScribeShader.cmake b/cmake/macros/AutoScribeShader.cmake index c5bb2b4054..f5a497962c 100755 --- a/cmake/macros/AutoScribeShader.cmake +++ b/cmake/macros/AutoScribeShader.cmake @@ -66,6 +66,9 @@ macro(AUTOSCRIBE_PLATFORM_SHADER) list(APPEND SHADER_GEN_LINE ${TEMP_PATH}) file(RELATIVE_PATH TEMP_PATH ${CMAKE_SOURCE_DIR} ${AUTOSCRIBE_OUTPUT_FILE}) list(APPEND SHADER_GEN_LINE ${TEMP_PATH}) + if (NOT("${DEFINES}" STREQUAL "")) + list(APPEND SHADER_GEN_LINE "defines:${DEFINES}") + endif() list(APPEND SHADER_GEN_LINE ${AUTOSCRIBE_SHADER_SEEN_LIBS}) string(CONCAT AUTOSCRIBE_SHADERGEN_COMMANDS "${AUTOSCRIBE_SHADERGEN_COMMANDS}" "${SHADER_GEN_LINE}\n") endmacro() @@ -108,6 +111,10 @@ macro(AUTOSCRIBE_SHADER) set(SHADER_TYPE geom) endif() + if (NOT("${DEFINES}" STREQUAL "")) + string(CONCAT SHADER_NAME "${SHADER_NAME}" "_${DEFINES}") + endif() + set(SCRIBE_ARGS -D GLPROFILE ${GLPROFILE} -T ${SHADER_TYPE} ${SCRIBE_INCLUDES} ) # SHADER_SCRIBED -> the output of scribe @@ -135,11 +142,78 @@ macro(AUTOSCRIBE_SHADER) endif() endif() - string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "${SHADER_NAME} = ${SHADER_COUNT},\n") + string(CONCAT SHADER_LIST "${SHADER_LIST}" "${SHADER_NAME} = ${SHADER_COUNT},\n") string(CONCAT SHADER_SHADERS_ARRAY "${SHADER_SHADERS_ARRAY}" "${SHADER_COUNT},\n") MATH(EXPR SHADER_COUNT "${SHADER_COUNT}+1") endmacro() +# This function takes in the list of defines, which would look like: +# (normalmap;translucent:f)/shadow;deformed:v +# and handles parentheses and slashes, producing the semicolon-separated final list of all combinations, which in that case will look like: +# normalmap;translucent:f;normalmap_translucent:f;shadow;normalmap_deformed:v;translucent:f_deformed:v;normalmap_translucent:f_deformed:v;shadow_deformed:v +function(GENERATE_DEFINES_LIST_HELPER INPUT_LIST RETURN_LIST) + # This while loop handles parentheses, looking for matching ( and ) and then calling GENERATE_DEFINES_LIST_HELPER recursively on the text in between + string(LENGTH "${INPUT_LIST}" STR_LENGTH) + set(OPEN_INDEX -1) + set(STR_INDEX 0) + set(NESTED_DEPTH 0) + while ("${STR_INDEX}" LESS "${STR_LENGTH}") + string(SUBSTRING "${INPUT_LIST}" ${STR_INDEX} 1 CURRENT_CHAR) + + if (("${CURRENT_CHAR}" STREQUAL "(") AND (OPEN_INDEX EQUAL -1)) + set(OPEN_INDEX ${STR_INDEX}) + MATH(EXPR STR_INDEX "${STR_INDEX}+1") + continue() + elseif (("${CURRENT_CHAR}" STREQUAL "(") AND NOT(OPEN_INDEX EQUAL -1)) + MATH(EXPR NESTED_DEPTH "${NESTED_DEPTH}+1") + MATH(EXPR STR_INDEX "${STR_INDEX}+1") + continue() + elseif (("${CURRENT_CHAR}" STREQUAL ")") AND NOT(OPEN_INDEX EQUAL -1) AND (NESTED_DEPTH GREATER 0)) + MATH(EXPR NESTED_DEPTH "${NESTED_DEPTH}-1") + MATH(EXPR STR_INDEX "${STR_INDEX}+1") + continue() + elseif (("${CURRENT_CHAR}" STREQUAL ")") AND NOT(OPEN_INDEX EQUAL -1) AND (NESTED_DEPTH EQUAL 0)) + MATH(EXPR OPEN_INDEX "${OPEN_INDEX}+1") + MATH(EXPR SUBSTR_LENGTH "${STR_INDEX}-${OPEN_INDEX}") + string(SUBSTRING "${INPUT_LIST}" ${OPEN_INDEX} ${SUBSTR_LENGTH} GROUP_STR) + GENERATE_DEFINES_LIST_HELPER("${GROUP_STR}" EXPANDED_GROUP_LIST) + string(REPLACE ";" "/" EXPANDED_GROUP_LIST "${EXPANDED_GROUP_LIST}") + string(REPLACE "(${GROUP_STR})" "${EXPANDED_GROUP_LIST}" INPUT_LIST "${INPUT_LIST}") + MATH(EXPR STR_INDEX "${OPEN_INDEX}-1") + set(OPEN_INDEX -1) + string(LENGTH "${INPUT_LIST}" STR_LENGTH) + continue() + endif() + + MATH(EXPR STR_INDEX "${STR_INDEX}+1") + endwhile() + + # Here we handle the base case, the recursive case, and slashes + list(LENGTH INPUT_LIST NUM_DEFINES) + if (NUM_DEFINES EQUAL 1) + string(REPLACE "/" ";" INPUT_LIST "${INPUT_LIST}") + set(${RETURN_LIST} ${INPUT_LIST} PARENT_SCOPE) + elseif (NUM_DEFINES GREATER 1) + list(GET INPUT_LIST 0 CURRENT_DEFINES) + string(REPLACE "/" ";" CURRENT_DEFINES "${CURRENT_DEFINES}") + list(REMOVE_AT INPUT_LIST 0) + GENERATE_DEFINES_LIST_HELPER("${INPUT_LIST}" REMAINING_DEFINES_LIST) + set(TO_RETURN_LIST "${CURRENT_DEFINES}") + foreach(REMAINING_DEFINES ${REMAINING_DEFINES_LIST}) + list(APPEND TO_RETURN_LIST "${REMAINING_DEFINES}") + foreach(CURRENT_DEFINE ${CURRENT_DEFINES}) + list(APPEND TO_RETURN_LIST "${CURRENT_DEFINE}_${REMAINING_DEFINES}") + endforeach() + endforeach() + set(${RETURN_LIST} ${TO_RETURN_LIST} PARENT_SCOPE) + endif() +endfunction() + +macro(GENERATE_DEFINES_LIST) + set(DEFINES_LIST "") + GENERATE_DEFINES_LIST_HELPER("${ARGV0}" DEFINES_LIST) +endmacro() + macro(AUTOSCRIBE_SHADER_LIB) if (NOT ("${TARGET_NAME}" STREQUAL "shaders")) message(FATAL_ERROR "AUTOSCRIBE_SHADER_LIB can only be used by the shaders library") @@ -164,24 +238,24 @@ macro(AUTOSCRIBE_SHADER_LIB) if (SHADER_VERTEX_FILES) source_group("${SHADER_LIB}/Vertex" FILES ${SHADER_VERTEX_FILES}) list(APPEND ALL_SCRIBE_SHADERS ${SHADER_VERTEX_FILES}) - string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "namespace vertex { enum {\n") - foreach(SHADER_FILE ${SHADER_VERTEX_FILES}) + set(SHADER_LIST "namespace vertex { enum {\n") + foreach(SHADER_FILE ${SHADER_VERTEX_FILES}) AUTOSCRIBE_SHADER(${ALL_SHADER_HEADERS}) endforeach() - string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "}; } // vertex \n") + set(VERTEX_ENUMS "${SHADER_LIST}") endif() file(GLOB_RECURSE SHADER_FRAGMENT_FILES ${SRC_FOLDER}/*.slf) if (SHADER_FRAGMENT_FILES) source_group("${SHADER_LIB}/Fragment" FILES ${SHADER_FRAGMENT_FILES}) list(APPEND ALL_SCRIBE_SHADERS ${SHADER_FRAGMENT_FILES}) - string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "namespace fragment { enum {\n") - foreach(SHADER_FILE ${SHADER_FRAGMENT_FILES}) + set(SHADER_LIST "namespace fragment { enum {\n") + foreach(SHADER_FILE ${SHADER_FRAGMENT_FILES}) AUTOSCRIBE_SHADER(${ALL_SHADER_HEADERS}) endforeach() - string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "}; } // fragment \n") + set(FRAGMENT_ENUMS "${SHADER_LIST}") endif() - + # FIXME add support for geometry, compute and tesselation shaders #file(GLOB_RECURSE SHADER_GEOMETRY_FILES ${SRC_FOLDER}/*.slg) #file(GLOB_RECURSE SHADER_COMPUTE_FILES ${SRC_FOLDER}/*.slc) @@ -191,13 +265,13 @@ macro(AUTOSCRIBE_SHADER_LIB) if (SHADER_PROGRAM_FILES) source_group("${SHADER_LIB}/Program" FILES ${SHADER_PROGRAM_FILES}) list(APPEND ALL_SCRIBE_SHADERS ${SHADER_PROGRAM_FILES}) - string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "namespace program { enum {\n") + set(PROGRAM_ENUMS "namespace program { enum {\n") foreach(PROGRAM_FILE ${SHADER_PROGRAM_FILES}) get_filename_component(PROGRAM_NAME ${PROGRAM_FILE} NAME_WE) - set(AUTOSCRIBE_PROGRAM_FRAGMENT ${PROGRAM_NAME}) file(READ ${PROGRAM_FILE} PROGRAM_CONFIG) set(AUTOSCRIBE_PROGRAM_VERTEX ${PROGRAM_NAME}) set(AUTOSCRIBE_PROGRAM_FRAGMENT ${PROGRAM_NAME}) + set(AUTOSCRIBE_PROGRAM_DEFINES "") if (NOT("${PROGRAM_CONFIG}" STREQUAL "")) string(REGEX MATCH ".*VERTEX +([_\\:A-Z0-9a-z]+)" MVERT ${PROGRAM_CONFIG}) @@ -208,6 +282,12 @@ macro(AUTOSCRIBE_SHADER_LIB) if (CMAKE_MATCH_1) set(AUTOSCRIBE_PROGRAM_FRAGMENT ${CMAKE_MATCH_1}) endif() + string(REGEX MATCH ".*DEFINES +([a-zA-Z\(\)/: ]+)" MDEF ${PROGRAM_CONFIG}) + if (CMAKE_MATCH_1) + set(AUTOSCRIBE_PROGRAM_DEFINES ${CMAKE_MATCH_1}) + string(TOLOWER AUTOSCRIBE_PROGRAM_DEFINES "${AUTOSCRIBE_PROGRAM_DEFINES}") + string(REGEX REPLACE " +" ";" AUTOSCRIBE_PROGRAM_DEFINES "${AUTOSCRIBE_PROGRAM_DEFINES}") + endif() endif() if (NOT (${AUTOSCRIBE_PROGRAM_VERTEX} MATCHES ".*::.*")) @@ -216,12 +296,75 @@ macro(AUTOSCRIBE_SHADER_LIB) if (NOT (${AUTOSCRIBE_PROGRAM_FRAGMENT} MATCHES ".*::.*")) set(AUTOSCRIBE_PROGRAM_FRAGMENT "fragment::${AUTOSCRIBE_PROGRAM_FRAGMENT}") endif() + string(REGEX REPLACE ".*::" "" VERTEX_NAME "${AUTOSCRIBE_PROGRAM_VERTEX}") + string(REGEX REPLACE ".*::" "" FRAGMENT_NAME "${AUTOSCRIBE_PROGRAM_FRAGMENT}") - set(PROGRAM_ENTRY "${PROGRAM_NAME} = (${AUTOSCRIBE_PROGRAM_VERTEX} << 16) | ${AUTOSCRIBE_PROGRAM_FRAGMENT},\n") - string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "${PROGRAM_ENTRY}") + GENERATE_DEFINES_LIST("${AUTOSCRIBE_PROGRAM_DEFINES}") + + string(CONCAT PROGRAM_ENUMS "${PROGRAM_ENUMS}" "${PROGRAM_NAME} = (${AUTOSCRIBE_PROGRAM_VERTEX} << 16) | ${AUTOSCRIBE_PROGRAM_FRAGMENT},\n") string(CONCAT SHADER_PROGRAMS_ARRAY "${SHADER_PROGRAMS_ARRAY} ${SHADER_NAMESPACE}::program::${PROGRAM_NAME},\n") + + foreach(DEFINES ${DEFINES_LIST}) + set(ORIG_DEFINES "${DEFINES}") + + # Below here we handle :v and :f. The program name includes both, but the vertex and fragment names + # remove the elements with :f and :v respectively, and only have to call AUTOSCRIBE_SHADER if they don't have those + # (because the shaders without them will have already been generated) + string(REPLACE ":v" "" VERTEX_DEFINES "${ORIG_DEFINES}") + string(FIND "${ORIG_DEFINES}" ":f" HAS_FRAGMENT) + if (HAS_FRAGMENT EQUAL -1) + set(DEFINES "${VERTEX_DEFINES}") + set(SHADER_LIST "") + set(SHADER_FILE "${SRC_FOLDER}/${VERTEX_NAME}.slv") + AUTOSCRIBE_SHADER(${ALL_SHADER_HEADERS}) + string(CONCAT VERTEX_ENUMS "${VERTEX_ENUMS}" "${SHADER_LIST}") + else() + string(REGEX REPLACE "_*[^_]*:f" "" VERTEX_DEFINES "${VERTEX_DEFINES}") + endif() + + if (NOT("${VERTEX_DEFINES}" STREQUAL "") AND NOT("${VERTEX_DEFINES}" MATCHES "^_.*")) + set(VERTEX_DEFINES "_${VERTEX_DEFINES}") + endif() + + string(REPLACE ":f" "" FRAGMENT_DEFINES "${ORIG_DEFINES}") + string(FIND "${ORIG_DEFINES}" ":v" HAS_VERTEX) + if (HAS_VERTEX EQUAL -1) + set(DEFINES "${FRAGMENT_DEFINES}") + set(SHADER_LIST "") + set(SHADER_FILE "${SRC_FOLDER}/${FRAGMENT_NAME}.slf") + AUTOSCRIBE_SHADER(${ALL_SHADER_HEADERS}) + string(CONCAT FRAGMENT_ENUMS "${FRAGMENT_ENUMS}" "${SHADER_LIST}") + else() + string(REGEX REPLACE "_*[^_]*:v" "" FRAGMENT_DEFINES "${FRAGMENT_DEFINES}") + endif() + + if (NOT("${FRAGMENT_DEFINES}" STREQUAL "") AND NOT("${FRAGMENT_DEFINES}" MATCHES "^_.*")) + set(FRAGMENT_DEFINES "_${FRAGMENT_DEFINES}") + endif() + + string(REGEX REPLACE ":(f|v)" "" PROGRAM_DEFINES "${ORIG_DEFINES}") + + if (NOT("${PROGRAM_DEFINES}" STREQUAL "")) + string(CONCAT PROGRAM_ENUMS "${PROGRAM_ENUMS}" "${PROGRAM_NAME}_${PROGRAM_DEFINES} = (${AUTOSCRIBE_PROGRAM_VERTEX}${VERTEX_DEFINES} << 16) | ${AUTOSCRIBE_PROGRAM_FRAGMENT}${FRAGMENT_DEFINES},\n") + string(CONCAT SHADER_PROGRAMS_ARRAY "${SHADER_PROGRAMS_ARRAY} ${SHADER_NAMESPACE}::program::${PROGRAM_NAME}_${PROGRAM_DEFINES},\n") + endif() + endforeach() endforeach() - string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "}; } // program \n") + endif() + + if (SHADER_VERTEX_FILES) + string(CONCAT VERTEX_ENUMS "${VERTEX_ENUMS}" "}; } // vertex \n") + string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "${VERTEX_ENUMS}") + endif() + + if (SHADER_FRAGMENT_FILES) + string(CONCAT FRAGMENT_ENUMS "${FRAGMENT_ENUMS}" "}; } // fragment \n") + string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "${FRAGMENT_ENUMS}") + endif() + + if (SHADER_PROGRAM_FILES) + string(CONCAT PROGRAM_ENUMS "${PROGRAM_ENUMS}" "}; } // program \n") + string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "${PROGRAM_ENUMS}") endif() # Finish the shader enums diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 6aaa348f6c..09a0446468 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -121,12 +121,13 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointersetNodeInterestSet(safeInterestSet); nodeData->setPlaceName(nodeConnection.placeName); + QMetaEnum metaEnum = QMetaEnum::fromType(); qDebug() << "Allowed connection from node" << uuidStringWithoutCurlyBraces(node->getUUID()) << "on" << message->getSenderSockAddr() << "with MAC" << nodeConnection.hardwareAddress << "and machine fingerprint" << nodeConnection.machineFingerprint << "user" << username - << "reason" << QString(nodeConnection.connectReason ? "SilentDomainDisconnect" : "Connect") + << "reason" << QString(metaEnum.valueToKey(nodeConnection.connectReason)) << "previous connection uptime" << nodeConnection.previousConnectionUpTime/USECS_PER_MSEC << "msec" << "sysinfo" << nodeConnection.SystemInfo; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index c3a4a94c7c..b7c723ab48 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2498,7 +2498,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return true; } else if (allNodesDeleteRegex.indexIn(url.path()) != -1) { qDebug() << "Received request to kill all nodes."; - nodeList->eraseAllNodes(); + nodeList->eraseAllNodes(url.path()); return true; } diff --git a/interface/resources/avatar/animations/fly.fbx b/interface/resources/avatar/animations/fly.fbx index 4a855032f9..5d8fcce23b 100644 Binary files a/interface/resources/avatar/animations/fly.fbx and b/interface/resources/avatar/animations/fly.fbx differ diff --git a/interface/resources/avatar/animations/idle.fbx b/interface/resources/avatar/animations/idle.fbx index 88c79185a1..a2cbb9b43b 100644 Binary files a/interface/resources/avatar/animations/idle.fbx and b/interface/resources/avatar/animations/idle.fbx differ diff --git a/interface/resources/avatar/animations/idle02.fbx b/interface/resources/avatar/animations/idle02.fbx new file mode 100644 index 0000000000..c18162dda1 Binary files /dev/null and b/interface/resources/avatar/animations/idle02.fbx differ diff --git a/interface/resources/avatar/animations/idle03.fbx b/interface/resources/avatar/animations/idle03.fbx new file mode 100644 index 0000000000..5349c49a31 Binary files /dev/null and b/interface/resources/avatar/animations/idle03.fbx differ diff --git a/interface/resources/avatar/animations/idle_lookaround01.fbx b/interface/resources/avatar/animations/idle04.fbx similarity index 59% rename from interface/resources/avatar/animations/idle_lookaround01.fbx rename to interface/resources/avatar/animations/idle04.fbx index fbea065713..c0676a7980 100644 Binary files a/interface/resources/avatar/animations/idle_lookaround01.fbx and b/interface/resources/avatar/animations/idle04.fbx differ diff --git a/interface/resources/avatar/animations/idleWS.fbx b/interface/resources/avatar/animations/idleWS.fbx deleted file mode 100644 index e730165012..0000000000 Binary files a/interface/resources/avatar/animations/idleWS.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/idleWS_all.fbx b/interface/resources/avatar/animations/idleWS_all.fbx index f9ac3dacfb..3605ecfcf4 100644 Binary files a/interface/resources/avatar/animations/idleWS_all.fbx and b/interface/resources/avatar/animations/idleWS_all.fbx differ diff --git a/interface/resources/avatar/animations/idle_LFF_all.fbx b/interface/resources/avatar/animations/idle_LFF_all.fbx index 6904773cd5..0344a270a7 100644 Binary files a/interface/resources/avatar/animations/idle_LFF_all.fbx and b/interface/resources/avatar/animations/idle_LFF_all.fbx differ diff --git a/interface/resources/avatar/animations/idle_RFF_all.fbx b/interface/resources/avatar/animations/idle_RFF_all.fbx index 77ea06dc70..8aec6a33c0 100644 Binary files a/interface/resources/avatar/animations/idle_RFF_all.fbx and b/interface/resources/avatar/animations/idle_RFF_all.fbx differ diff --git a/interface/resources/avatar/animations/idle_once_armstretch.fbx b/interface/resources/avatar/animations/idle_once_armstretch.fbx deleted file mode 100644 index 23eeed3b26..0000000000 Binary files a/interface/resources/avatar/animations/idle_once_armstretch.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/idle_once_bigstretch.fbx b/interface/resources/avatar/animations/idle_once_bigstretch.fbx deleted file mode 100644 index 5e4731279f..0000000000 Binary files a/interface/resources/avatar/animations/idle_once_bigstretch.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/idle_once_checkwatch.fbx b/interface/resources/avatar/animations/idle_once_checkwatch.fbx deleted file mode 100644 index 888d0bcbfc..0000000000 Binary files a/interface/resources/avatar/animations/idle_once_checkwatch.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/idle_once_fidget.fbx b/interface/resources/avatar/animations/idle_once_fidget.fbx new file mode 100644 index 0000000000..2270614901 Binary files /dev/null and b/interface/resources/avatar/animations/idle_once_fidget.fbx differ diff --git a/interface/resources/avatar/animations/idle_once_headtilt.fbx b/interface/resources/avatar/animations/idle_once_headtilt.fbx index 21d1bc43c8..eeae4604aa 100644 Binary files a/interface/resources/avatar/animations/idle_once_headtilt.fbx and b/interface/resources/avatar/animations/idle_once_headtilt.fbx differ diff --git a/interface/resources/avatar/animations/idle_once_lookaround.fbx b/interface/resources/avatar/animations/idle_once_lookaround.fbx index 15be33092c..b638191654 100644 Binary files a/interface/resources/avatar/animations/idle_once_lookaround.fbx and b/interface/resources/avatar/animations/idle_once_lookaround.fbx differ diff --git a/interface/resources/avatar/animations/idle_once_lookleftright.fbx b/interface/resources/avatar/animations/idle_once_lookleftright.fbx new file mode 100644 index 0000000000..72aee7931e Binary files /dev/null and b/interface/resources/avatar/animations/idle_once_lookleftright.fbx differ diff --git a/interface/resources/avatar/animations/idle_once_neckstretch.fbx b/interface/resources/avatar/animations/idle_once_neckstretch.fbx index 3968b96615..5ee4a99d65 100644 Binary files a/interface/resources/avatar/animations/idle_once_neckstretch.fbx and b/interface/resources/avatar/animations/idle_once_neckstretch.fbx differ diff --git a/interface/resources/avatar/animations/idle_once_shiftheelpivot.fbx b/interface/resources/avatar/animations/idle_once_shiftheelpivot.fbx new file mode 100644 index 0000000000..073b4343aa Binary files /dev/null and b/interface/resources/avatar/animations/idle_once_shiftheelpivot.fbx differ diff --git a/interface/resources/avatar/animations/idle_once_slownod.fbx b/interface/resources/avatar/animations/idle_once_slownod.fbx index ad4f4e17bf..7a27add7bf 100644 Binary files a/interface/resources/avatar/animations/idle_once_slownod.fbx and b/interface/resources/avatar/animations/idle_once_slownod.fbx differ diff --git a/interface/resources/avatar/animations/jog_bwd.fbx b/interface/resources/avatar/animations/jog_bwd.fbx index 479392d44c..b3eb8c919d 100644 Binary files a/interface/resources/avatar/animations/jog_bwd.fbx and b/interface/resources/avatar/animations/jog_bwd.fbx differ diff --git a/interface/resources/avatar/animations/jog_fwd.fbx b/interface/resources/avatar/animations/jog_fwd.fbx index f389fea364..9d38caacab 100644 Binary files a/interface/resources/avatar/animations/jog_fwd.fbx and b/interface/resources/avatar/animations/jog_fwd.fbx differ diff --git a/interface/resources/avatar/animations/jog_left.fbx b/interface/resources/avatar/animations/jog_left.fbx index 88600005b9..7732050201 100644 Binary files a/interface/resources/avatar/animations/jog_left.fbx and b/interface/resources/avatar/animations/jog_left.fbx differ diff --git a/interface/resources/avatar/animations/jog_right.fbx b/interface/resources/avatar/animations/jog_right.fbx new file mode 100644 index 0000000000..9b419b6eec Binary files /dev/null and b/interface/resources/avatar/animations/jog_right.fbx differ diff --git a/interface/resources/avatar/animations/jump_in_air.fbx b/interface/resources/avatar/animations/jump_in_air.fbx deleted file mode 100644 index 2e6ad32f4f..0000000000 Binary files a/interface/resources/avatar/animations/jump_in_air.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/jump_land.fbx b/interface/resources/avatar/animations/jump_land.fbx deleted file mode 100644 index f057c80085..0000000000 Binary files a/interface/resources/avatar/animations/jump_land.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/jump_running_launch_land.fbx b/interface/resources/avatar/animations/jump_running_launch_land.fbx deleted file mode 100644 index ad922d948e..0000000000 Binary files a/interface/resources/avatar/animations/jump_running_launch_land.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/jump_running_launch_land_all.fbx b/interface/resources/avatar/animations/jump_running_launch_land_all.fbx new file mode 100644 index 0000000000..71dae6bcc5 Binary files /dev/null and b/interface/resources/avatar/animations/jump_running_launch_land_all.fbx differ diff --git a/interface/resources/avatar/animations/jump_standing_apex.fbx b/interface/resources/avatar/animations/jump_standing_apex.fbx deleted file mode 100644 index 53b79758aa..0000000000 Binary files a/interface/resources/avatar/animations/jump_standing_apex.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/jump_standing_apex_all.fbx b/interface/resources/avatar/animations/jump_standing_apex_all.fbx new file mode 100644 index 0000000000..5d5873c39a Binary files /dev/null and b/interface/resources/avatar/animations/jump_standing_apex_all.fbx differ diff --git a/interface/resources/avatar/animations/jump_standing_land.fbx b/interface/resources/avatar/animations/jump_standing_land.fbx deleted file mode 100644 index aaeeb186fc..0000000000 Binary files a/interface/resources/avatar/animations/jump_standing_land.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/jump_standing_land_settle.fbx b/interface/resources/avatar/animations/jump_standing_land_settle.fbx deleted file mode 100644 index 1a11781e22..0000000000 Binary files a/interface/resources/avatar/animations/jump_standing_land_settle.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/jump_standing_land_settle_all.fbx b/interface/resources/avatar/animations/jump_standing_land_settle_all.fbx new file mode 100644 index 0000000000..ef8e83be34 Binary files /dev/null and b/interface/resources/avatar/animations/jump_standing_land_settle_all.fbx differ diff --git a/interface/resources/avatar/animations/jump_standing_launch.fbx b/interface/resources/avatar/animations/jump_standing_launch.fbx deleted file mode 100644 index 06479c8885..0000000000 Binary files a/interface/resources/avatar/animations/jump_standing_launch.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/jump_standing_launch_all.fbx b/interface/resources/avatar/animations/jump_standing_launch_all.fbx new file mode 100644 index 0000000000..e10e428579 Binary files /dev/null and b/interface/resources/avatar/animations/jump_standing_launch_all.fbx differ diff --git a/interface/resources/avatar/animations/jump_standing_takeoff.fbx b/interface/resources/avatar/animations/jump_standing_takeoff.fbx deleted file mode 100644 index 5fc6befdd0..0000000000 Binary files a/interface/resources/avatar/animations/jump_standing_takeoff.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/jump_takeoff.fbx b/interface/resources/avatar/animations/jump_takeoff.fbx deleted file mode 100644 index 3976725676..0000000000 Binary files a/interface/resources/avatar/animations/jump_takeoff.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/run_bwd.fbx b/interface/resources/avatar/animations/run_bwd.fbx index 62f49c9729..60ade1f2ac 100644 Binary files a/interface/resources/avatar/animations/run_bwd.fbx and b/interface/resources/avatar/animations/run_bwd.fbx differ diff --git a/interface/resources/avatar/animations/run_fast_fwd.fbx b/interface/resources/avatar/animations/run_fast_fwd.fbx new file mode 100644 index 0000000000..cb964d57d8 Binary files /dev/null and b/interface/resources/avatar/animations/run_fast_fwd.fbx differ diff --git a/interface/resources/avatar/animations/run_fast_left.fbx b/interface/resources/avatar/animations/run_fast_left.fbx new file mode 100644 index 0000000000..5078d0cb22 Binary files /dev/null and b/interface/resources/avatar/animations/run_fast_left.fbx differ diff --git a/interface/resources/avatar/animations/run_fast_right.fbx b/interface/resources/avatar/animations/run_fast_right.fbx new file mode 100644 index 0000000000..ddf081bef6 Binary files /dev/null and b/interface/resources/avatar/animations/run_fast_right.fbx differ diff --git a/interface/resources/avatar/animations/run_fwd.fbx b/interface/resources/avatar/animations/run_fwd.fbx deleted file mode 100644 index f28f9e0569..0000000000 Binary files a/interface/resources/avatar/animations/run_fwd.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/settle_to_idle.fbx b/interface/resources/avatar/animations/settle_to_idle.fbx deleted file mode 100644 index fba4305e9a..0000000000 Binary files a/interface/resources/avatar/animations/settle_to_idle.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/settle_to_idle_small.fbx b/interface/resources/avatar/animations/settle_to_idle_small.fbx new file mode 100644 index 0000000000..3d79d1930c Binary files /dev/null and b/interface/resources/avatar/animations/settle_to_idle_small.fbx differ diff --git a/interface/resources/avatar/animations/side_step_left.fbx b/interface/resources/avatar/animations/side_step_left.fbx index ef8cbe0a5c..b74fe9074a 100644 Binary files a/interface/resources/avatar/animations/side_step_left.fbx and b/interface/resources/avatar/animations/side_step_left.fbx differ diff --git a/interface/resources/avatar/animations/side_step_left_fast.fbx b/interface/resources/avatar/animations/side_step_left_fast.fbx index af71a46bb5..f4c997345f 100644 Binary files a/interface/resources/avatar/animations/side_step_left_fast.fbx and b/interface/resources/avatar/animations/side_step_left_fast.fbx differ diff --git a/interface/resources/avatar/animations/side_step_left_fast02.fbx b/interface/resources/avatar/animations/side_step_left_fast02.fbx deleted file mode 100644 index 9064b35d37..0000000000 Binary files a/interface/resources/avatar/animations/side_step_left_fast02.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/side_step_right.fbx b/interface/resources/avatar/animations/side_step_right.fbx deleted file mode 100644 index 6404657741..0000000000 Binary files a/interface/resources/avatar/animations/side_step_right.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/side_step_short_left.fbx b/interface/resources/avatar/animations/side_step_short_left.fbx index f236f30c31..8f55364b17 100644 Binary files a/interface/resources/avatar/animations/side_step_short_left.fbx and b/interface/resources/avatar/animations/side_step_short_left.fbx differ diff --git a/interface/resources/avatar/animations/side_step_short_right.fbx b/interface/resources/avatar/animations/side_step_short_right.fbx deleted file mode 100644 index df074c17e0..0000000000 Binary files a/interface/resources/avatar/animations/side_step_short_right.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/side_strafe_left.fbx b/interface/resources/avatar/animations/side_strafe_left.fbx deleted file mode 100644 index 61c3ba7b32..0000000000 Binary files a/interface/resources/avatar/animations/side_strafe_left.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/side_strafe_left02.fbx b/interface/resources/avatar/animations/side_strafe_left02.fbx deleted file mode 100644 index e9bc801997..0000000000 Binary files a/interface/resources/avatar/animations/side_strafe_left02.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/sitting.fbx b/interface/resources/avatar/animations/sitting.fbx deleted file mode 100644 index dfb51afb66..0000000000 Binary files a/interface/resources/avatar/animations/sitting.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/sitting_idle.fbx b/interface/resources/avatar/animations/sitting_idle.fbx deleted file mode 100644 index ee03d942cd..0000000000 Binary files a/interface/resources/avatar/animations/sitting_idle.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/talk.fbx b/interface/resources/avatar/animations/talk.fbx index c29bf1e9bf..6cc34049be 100644 Binary files a/interface/resources/avatar/animations/talk.fbx and b/interface/resources/avatar/animations/talk.fbx differ diff --git a/interface/resources/avatar/animations/talk02.fbx b/interface/resources/avatar/animations/talk02.fbx new file mode 100644 index 0000000000..a502ff261b Binary files /dev/null and b/interface/resources/avatar/animations/talk02.fbx differ diff --git a/interface/resources/avatar/animations/talk03.fbx b/interface/resources/avatar/animations/talk03.fbx new file mode 100644 index 0000000000..da95717540 Binary files /dev/null and b/interface/resources/avatar/animations/talk03.fbx differ diff --git a/interface/resources/avatar/animations/talk04.fbx b/interface/resources/avatar/animations/talk04.fbx index be2ba0a11f..9e5505b969 100644 Binary files a/interface/resources/avatar/animations/talk04.fbx and b/interface/resources/avatar/animations/talk04.fbx differ diff --git a/interface/resources/avatar/animations/talk_armsdown.fbx b/interface/resources/avatar/animations/talk_armsdown.fbx new file mode 100644 index 0000000000..e069811f7c Binary files /dev/null and b/interface/resources/avatar/animations/talk_armsdown.fbx differ diff --git a/interface/resources/avatar/animations/talk_lefthand.fbx b/interface/resources/avatar/animations/talk_lefthand.fbx new file mode 100644 index 0000000000..d7fd1d5e07 Binary files /dev/null and b/interface/resources/avatar/animations/talk_lefthand.fbx differ diff --git a/interface/resources/avatar/animations/talk_righthand.fbx b/interface/resources/avatar/animations/talk_righthand.fbx index df8849b4b4..332afcc1e7 100644 Binary files a/interface/resources/avatar/animations/talk_righthand.fbx and b/interface/resources/avatar/animations/talk_righthand.fbx differ diff --git a/interface/resources/avatar/animations/teleport.fbx b/interface/resources/avatar/animations/teleport.fbx deleted file mode 100644 index 99c950ced6..0000000000 Binary files a/interface/resources/avatar/animations/teleport.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/turn_left.fbx b/interface/resources/avatar/animations/turn_left.fbx index 3d32d832e6..b7cadf7a66 100644 Binary files a/interface/resources/avatar/animations/turn_left.fbx and b/interface/resources/avatar/animations/turn_left.fbx differ diff --git a/interface/resources/avatar/animations/turn_right.fbx b/interface/resources/avatar/animations/turn_right.fbx new file mode 100644 index 0000000000..4ee3d52daf Binary files /dev/null and b/interface/resources/avatar/animations/turn_right.fbx differ diff --git a/interface/resources/avatar/animations/walk_bwd.fbx b/interface/resources/avatar/animations/walk_bwd.fbx index 158ba52840..9daa769885 100644 Binary files a/interface/resources/avatar/animations/walk_bwd.fbx and b/interface/resources/avatar/animations/walk_bwd.fbx differ diff --git a/interface/resources/avatar/animations/walk_bwd_fast.fbx b/interface/resources/avatar/animations/walk_bwd_fast.fbx index f9a9ba0e26..af22d0d0b0 100644 Binary files a/interface/resources/avatar/animations/walk_bwd_fast.fbx and b/interface/resources/avatar/animations/walk_bwd_fast.fbx differ diff --git a/interface/resources/avatar/animations/walk_fwd.fbx b/interface/resources/avatar/animations/walk_fwd.fbx index 8ad461f62e..f86a8744c9 100644 Binary files a/interface/resources/avatar/animations/walk_fwd.fbx and b/interface/resources/avatar/animations/walk_fwd.fbx differ diff --git a/interface/resources/avatar/animations/walk_fwd_fast.fbx b/interface/resources/avatar/animations/walk_fwd_fast.fbx index db440fc9c4..8d2becf4b8 100644 Binary files a/interface/resources/avatar/animations/walk_fwd_fast.fbx and b/interface/resources/avatar/animations/walk_fwd_fast.fbx differ diff --git a/interface/resources/avatar/animations/walk_fwd_fast02.fbx b/interface/resources/avatar/animations/walk_fwd_fast02.fbx deleted file mode 100644 index 470b1d57f3..0000000000 Binary files a/interface/resources/avatar/animations/walk_fwd_fast02.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/walk_fwd_fast_armout.fbx b/interface/resources/avatar/animations/walk_fwd_fast_armout.fbx deleted file mode 100644 index 6aa07d0a45..0000000000 Binary files a/interface/resources/avatar/animations/walk_fwd_fast_armout.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/walk_left.fbx b/interface/resources/avatar/animations/walk_left.fbx index bfe10aa18f..f35d4f6a77 100644 Binary files a/interface/resources/avatar/animations/walk_left.fbx and b/interface/resources/avatar/animations/walk_left.fbx differ diff --git a/interface/resources/avatar/animations/walk_left_fast.fbx b/interface/resources/avatar/animations/walk_left_fast.fbx index ced6c08bac..8414ee6805 100644 Binary files a/interface/resources/avatar/animations/walk_left_fast.fbx and b/interface/resources/avatar/animations/walk_left_fast.fbx differ diff --git a/interface/resources/avatar/animations/walk_right.fbx b/interface/resources/avatar/animations/walk_right.fbx new file mode 100644 index 0000000000..5fe005f96c Binary files /dev/null and b/interface/resources/avatar/animations/walk_right.fbx differ diff --git a/interface/resources/avatar/animations/walk_right_fast.fbx b/interface/resources/avatar/animations/walk_right_fast.fbx new file mode 100644 index 0000000000..37b7bbbb62 Binary files /dev/null and b/interface/resources/avatar/animations/walk_right_fast.fbx differ diff --git a/interface/resources/avatar/animations/walk_short_bwd.fbx b/interface/resources/avatar/animations/walk_short_bwd.fbx deleted file mode 100644 index d7e4bedf53..0000000000 Binary files a/interface/resources/avatar/animations/walk_short_bwd.fbx and /dev/null differ diff --git a/interface/resources/avatar/animations/walk_short_fwd.fbx b/interface/resources/avatar/animations/walk_short_fwd.fbx index 51f0794ac1..b52371869b 100644 Binary files a/interface/resources/avatar/animations/walk_short_fwd.fbx and b/interface/resources/avatar/animations/walk_short_fwd.fbx differ diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index fd206ce475..b1277a26f5 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -733,7 +733,7 @@ }, { "id": "turnRight", - "interpTarget": 6, + "interpTarget": 8, "interpDuration": 8, "transitions": [ { "var": "isNotTurning", "state": "idle" }, @@ -753,7 +753,7 @@ }, { "id": "turnLeft", - "interpTarget": 6, + "interpTarget": 8, "interpDuration": 8, "transitions": [ { "var": "isNotTurning", "state": "idle" }, @@ -773,7 +773,7 @@ }, { "id": "strafeRightHmd", - "interpTarget": 5, + "interpTarget": 8, "interpDuration": 8, "interpType": "snapshotPrev", "transitions": [ @@ -794,7 +794,7 @@ }, { "id": "strafeLeftHmd", - "interpTarget": 5, + "interpTarget": 8, "interpDuration": 8, "interpType": "snapshotPrev", "transitions": [ @@ -905,40 +905,72 @@ "id": "idle", "type": "overlay", "data": { - "alpha": 1.0, + "alpha": 1.0, "alphaVar": "idleOverlayAlpha", "boneSet": "upperBody" }, "children": [ - { - "id": "idleTalk", + { + "id": "idleTalk", "type": "randomSwitchStateMachine", "data": { - "currentState": "idleTalk1", - "triggerRandomSwitch": "idleTalkSwitch", - "randomSwitchTimeMin": 5.0, - "randomSwitchTimeMax": 10.0, + "currentState": "talk", + "triggerRandomSwitch": "idleTalkSwitch", + "randomSwitchTimeMin": 7.0, + "randomSwitchTimeMax": 12.0, "states": [ { - "id": "idleTalk1", - "interpTarget": 6, - "interpDuration": 15, + "id": "talk", + "interpTarget": 20, + "interpDuration": 20, "priority": 0.33, "resume": true, "transitions": [] }, { - "id": "idleTalk2", - "interpTarget": 6, - "interpDuration": 15, + "id": "talk02", + "interpTarget": 20, + "interpDuration": 20, "priority": 0.33, "resume": true, "transitions": [] }, { - "id": "idleTalk3", - "interpTarget": 6, - "interpDuration": 15, + "id": "talk03", + "interpTarget": 20, + "interpDuration": 20, + "priority": 0.33, + "resume": true, + "transitions": [] + }, + { + "id": "talk04", + "interpTarget": 20, + "interpDuration": 20, + "priority": 0.33, + "resume": true, + "transitions": [] + }, + { + "id": "talk_armsdown", + "interpTarget": 20, + "interpDuration": 20, + "priority": 0.33, + "resume": true, + "transitions": [] + }, + { + "id": "talk_lefthand", + "interpTarget": 20, + "interpDuration": 20, + "priority": 0.33, + "resume": true, + "transitions": [] + }, + { + "id": "talk_righthand", + "interpTarget": 20, + "interpDuration": 20, "priority": 0.33, "resume": true, "transitions": [] @@ -947,57 +979,105 @@ }, "children": [ { - "id": "idleTalk1", + "id": "talk", "type": "clip", "data": { "url": "qrc:///avatar/animations/talk.fbx", "startFrame": 1.0, - "endFrame": 800.0, + "endFrame": 500.0, "timeScale": 1.0, "loopFlag": true }, "children": [] }, { - "id": "idleTalk2", + "id": "talk02", "type": "clip", "data": { - "url": "qrc:///avatar/animations/talk_righthand.fbx", + "url": "qrc:///avatar/animations/talk02.fbx", "startFrame": 1.0, - "endFrame": 501.0, + "endFrame": 325.0, "timeScale": 1.0, "loopFlag": true }, "children": [] }, { - "id": "idleTalk3", + "id": "talk03", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/talk03.fbx", + "startFrame": 1.0, + "endFrame": 300.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "talk04", "type": "clip", "data": { "url": "qrc:///avatar/animations/talk04.fbx", "startFrame": 1.0, - "endFrame": 499.0, + "endFrame": 500.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "talk_armsdown", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/talk_armsdown.fbx", + "startFrame": 1.0, + "endFrame": 215.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "talk_lefthand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/talk_lefthand.fbx", + "startFrame": 1.0, + "endFrame": 500.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "talk_righthand", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/talk_righthand.fbx", + "startFrame": 1.0, + "endFrame": 502.0, "timeScale": 1.0, "loopFlag": true }, "children": [] } ] - }, + }, { "id": "idleStand", "type": "randomSwitchStateMachine", "data": { "currentState": "masterIdle", - "triggerTimeMin": 10.0, - "triggerTimeMax": 60.0, - "transitionVar": "timeToFidget", + "triggerTimeMin": 10.0, + "triggerTimeMax": 50.0, + "transitionVar": "timeToFidget", "states": [ { "id": "masterIdle", "interpTarget": 21, "interpDuration": 20, - "priority": 1.0, + "priority": 1.0, "resume": false, "transitions": [ { "var": "timeToFidget", "randomSwitchState": "fidget" } @@ -1007,17 +1087,17 @@ "id": "fidget", "interpTarget": 21, "interpDuration": 20, - "priority": -1.0, + "priority": -1.0, "resume": false, "transitions": [ - { "var": "movement1OnDone", "randomSwitchState": "masterIdle" }, - { "var": "movement2OnDone", "randomSwitchState": "masterIdle" }, - { "var": "movement3OnDone", "randomSwitchState": "masterIdle" }, - { "var": "movement4OnDone", "randomSwitchState": "masterIdle" }, - { "var": "movement5OnDone", "randomSwitchState": "masterIdle" }, - { "var": "movement6OnDone", "randomSwitchState": "masterIdle" }, - { "var": "movement7OnDone", "randomSwitchState": "masterIdle" }, - { "var": "movement8OnDone", "randomSwitchState": "masterIdle" }, + { "var": "idle_once_slownodOnDone", "randomSwitchState": "masterIdle" }, + { "var": "idle_once_headtiltOnDone", "randomSwitchState": "masterIdle" }, + { "var": "idle_once_shiftheelpivotOnDone", "randomSwitchState": "masterIdle" }, + { "var": "idleWS_allOnDone", "randomSwitchState": "masterIdle" }, + { "var": "idle_once_lookaroundOnDone", "randomSwitchState": "masterIdle" }, + { "var": "idle_once_neckstretchOnDone", "randomSwitchState": "masterIdle" }, + { "var": "idle_once_lookleftrightOnDone", "randomSwitchState": "masterIdle" }, + { "var": "idle_once_fidgetOnDone", "randomSwitchState": "masterIdle" }, { "var": "alt1ToMasterIdleOnDone", "randomSwitchState": "masterIdle" }, { "var": "alt2ToMasterIdleOnDone", "randomSwitchState": "masterIdle" } ] @@ -1032,13 +1112,13 @@ "currentState": "masterIdle1", "triggerRandomSwitch": "masterIdleSwitch", "randomSwitchTimeMin": 10.0, - "randomSwitchTimeMax": 60.0, + "randomSwitchTimeMax": 30.0, "states": [ { "id": "masterIdle1", "interpTarget": 21, "interpDuration": 20, - "priority": 0.33, + "priority": 0.25, "resume": true, "transitions": [] }, @@ -1046,7 +1126,7 @@ "id": "masterIdle2", "interpTarget": 21, "interpDuration": 20, - "priority": 0.33, + "priority": 0.25, "resume": true, "transitions": [] }, @@ -1054,7 +1134,15 @@ "id": "masterIdle3", "interpTarget": 21, "interpDuration": 20, - "priority": 0.33, + "priority": 0.25, + "resume": true, + "transitions": [] + }, + { + "id": "masterIdle4", + "interpTarget": 21, + "interpDuration": 20, + "priority": 0.25, "resume": true, "transitions": [] } @@ -1077,10 +1165,10 @@ "id": "masterIdle2", "type": "clip", "data": { - "url": "qrc:///avatar/animations/idleWS_all.fbx", + "url": "qrc:///avatar/animations/idle02.fbx", "startFrame": 1.0, - "endFrame": 1620.0, - "timeScale": 1.0, + "endFrame": 400.0, + "timeScale": 0.75, "loopFlag": true }, "children": [] @@ -1089,9 +1177,21 @@ "id": "masterIdle3", "type": "clip", "data": { - "url": "qrc:///avatar/animations/idle_lookaround01.fbx", + "url": "qrc:///avatar/animations/idle03.fbx", "startFrame": 1.0, - "endFrame": 901.0, + "endFrame": 800.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "masterIdle4", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle04.fbx", + "startFrame": 1.0, + "endFrame": 902.0, "timeScale": 1.0, "loopFlag": true }, @@ -1109,7 +1209,7 @@ "id": "movement", "interpTarget": 17, "interpDuration": 15, - "priority": 0.8, + "priority": 0.6, "resume": false, "transitions": [] }, @@ -1117,7 +1217,7 @@ "id": "alternateIdle", "interpTarget": 17, "interpDuration": 15, - "priority": 0.2, + "priority": 0.4, "resume": false, "transitions": [] } @@ -1128,10 +1228,10 @@ "id": "movement", "type": "randomSwitchStateMachine", "data": { - "currentState": "movement1", + "currentState": "idle_once_slownod", "states": [ { - "id": "movement1", + "id": "idle_once_slownod", "interpTarget": 21, "interpDuration": 20, "priority": 0.2, @@ -1139,7 +1239,7 @@ "transitions": [] }, { - "id": "movement2", + "id": "idle_once_headtilt", "interpTarget": 21, "interpDuration": 20, "priority": 0.2, @@ -1147,7 +1247,7 @@ "transitions": [] }, { - "id": "movement3", + "id": "idle_once_shiftheelpivot", "interpTarget": 21, "interpDuration": 20, "priority": 0.2, @@ -1155,7 +1255,7 @@ "transitions": [] }, { - "id": "movement4", + "id": "idleWS_all", "interpTarget": 21, "interpDuration": 20, "priority": 0.2, @@ -1163,7 +1263,7 @@ "transitions": [] }, { - "id": "movement5", + "id": "idle_once_lookaround", "interpTarget": 21, "interpDuration": 20, "priority": 0.2, @@ -1171,7 +1271,7 @@ "transitions": [] }, { - "id": "movement6", + "id": "idle_once_neckstretch", "interpTarget": 21, "interpDuration": 20, "priority": 0.2, @@ -1179,7 +1279,7 @@ "transitions": [] }, { - "id": "movement7", + "id": "idle_once_lookleftright", "interpTarget": 21, "interpDuration": 20, "priority": 0.2, @@ -1187,7 +1287,7 @@ "transitions": [] }, { - "id": "movement8", + "id": "idle_once_fidget", "interpTarget": 21, "interpDuration": 20, "priority": 0.2, @@ -1198,11 +1298,11 @@ }, "children": [ { - "id": "movement1", + "id": "idle_once_slownod", "type": "clip", "data": { "url": "qrc:///avatar/animations/idle_once_slownod.fbx", - "startFrame": 1, + "startFrame": 1.0, "endFrame": 91.0, "timeScale": 1.0, "loopFlag": false @@ -1210,84 +1310,84 @@ "children": [] }, { - "id": "movement2", + "id": "idle_once_headtilt", "type": "clip", "data": { "url": "qrc:///avatar/animations/idle_once_headtilt.fbx", - "startFrame": 1, - "endFrame": 154, + "startFrame": 1.0, + "endFrame": 154.0, "timeScale": 1.0, "loopFlag": false }, "children": [] }, { - "id": "movement3", + "id": "idle_once_shiftheelpivot", "type": "clip", "data": { - "url": "qrc:///avatar/animations/idle_once_headtilt.fbx", - "startFrame": 1, - "endFrame": 154, + "url": "qrc:///avatar/animations/idle_once_shiftheelpivot.fbx", + "startFrame": 1.0, + "endFrame": 491.0, "timeScale": 1.0, "loopFlag": false }, "children": [] }, { - "id": "movement4", + "id": "idleWS_all", "type": "clip", "data": { "url": "qrc:///avatar/animations/idleWS_all.fbx", - "startFrame": 1, - "endFrame": 1620, - "timeScale": 1.0, + "startFrame": 1.0, + "endFrame": 1620.0, + "timeScale": 0.7, "loopFlag": false }, "children": [] }, { - "id": "movement5", + "id": "idle_once_lookaround", "type": "clip", "data": { "url": "qrc:///avatar/animations/idle_once_lookaround.fbx", - "startFrame": 1, - "endFrame": 324, + "startFrame": 1.0, + "endFrame": 324.0, "timeScale": 1.0, "loopFlag": false }, "children": [] }, { - "id": "movement6", + "id": "idle_once_neckstretch", "type": "clip", "data": { "url": "qrc:///avatar/animations/idle_once_neckstretch.fbx", - "startFrame": 1, - "endFrame": 169, + "startFrame": 1.0, + "endFrame": 169.0, "timeScale": 1.0, "loopFlag": false }, "children": [] }, { - "id": "movement7", + "id": "idle_once_lookleftright", "type": "clip", "data": { - "url": "qrc:///avatar/animations/idleWS_all.fbx", - "startFrame": 1, - "endFrame": 1620, - "timeScale": 1.0, + "url": "qrc:///avatar/animations/idle_once_lookleftright.fbx", + "startFrame": 1.0, + "endFrame": 375.0, + "timeScale": 0.7, "loopFlag": false }, "children": [] }, { - "id": "movement8", + "id": "idle_once_fidget", "type": "clip", "data": { - "url": "qrc:///avatar/animations/idle_once_lookaround.fbx", - "startFrame": 1, - "endFrame": 324, + "url": "qrc:///avatar/animations/idle_once_fidget.fbx", + "startFrame": 1.0, + "endFrame": 429.0, "timeScale": 1.0, "loopFlag": false }, @@ -1381,7 +1481,7 @@ "data": { "url": "qrc:///avatar/animations/idle_LFF_all.fbx", "startFrame": 1, - "endFrame": 55, + "endFrame": 80, "timeScale": 1.0, "loopFlag": false }, @@ -1392,8 +1492,8 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/idle_RFF_all.fbx", - "startFrame": 1, - "endFrame": 56, + "startFrame": 1.0, + "endFrame": 80.0, "timeScale": 1.0, "loopFlag": false }, @@ -1404,7 +1504,7 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/idle_LFF_all.fbx", - "startFrame": 55, + "startFrame": 80, "endFrame": 389, "timeScale": 1.0, "loopFlag": true @@ -1416,8 +1516,8 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/idle_RFF_all.fbx", - "startFrame": 56, - "endFrame": 390, + "startFrame": 80.0, + "endFrame": 390.0, "timeScale": 1.0, "loopFlag": true }, @@ -1440,8 +1540,8 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/idle_RFF_all.fbx", - "startFrame": 390, - "endFrame": 453, + "startFrame": 390.0, + "endFrame": 453.0, "timeScale": 1.0, "loopFlag": false }, @@ -1463,7 +1563,7 @@ "data": { "alpha": 0.0, "desiredSpeed": 1.4, - "characteristicSpeeds": [0.5, 1.8, 2.3, 3.0, 5.0], + "characteristicSpeeds": [0.5, 1.8, 2.5, 3.55, 5.675], "alphaVar": "moveForwardAlpha", "desiredSpeedVar": "moveForwardSpeed" }, @@ -1473,8 +1573,8 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/walk_short_fwd.fbx", - "startFrame": 0.0, - "endFrame": 39.0, + "startFrame": 1.0, + "endFrame": 40.0, "timeScale": 1.0, "loopFlag": true }, @@ -1485,7 +1585,7 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/walk_fwd.fbx", - "startFrame": 0.0, + "startFrame": 1.0, "endFrame": 30.0, "timeScale": 1.0, "loopFlag": true @@ -1497,8 +1597,8 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/walk_fwd_fast.fbx", - "startFrame": 0.0, - "endFrame": 25.0, + "startFrame": 1.0, + "endFrame": 26.0, "timeScale": 1.0, "loopFlag": true }, @@ -1510,7 +1610,7 @@ "data": { "url": "qrc:///avatar/animations/jog_fwd.fbx", "startFrame": 1.0, - "endFrame": 22.0, + "endFrame": 18.0, "timeScale": 1.0, "loopFlag": true }, @@ -1520,9 +1620,9 @@ "id": "walkFwdRun_c", "type": "clip", "data": { - "url": "qrc:///avatar/animations/run_fwd.fbx", + "url": "qrc:///avatar/animations/run_fast_fwd.fbx", "startFrame": 1.0, - "endFrame": 23.0, + "endFrame": 19.0, "timeScale": 1.0, "loopFlag": true }, @@ -1546,7 +1646,7 @@ "id": "idleSettle", "type": "clip", "data": { - "url": "qrc:///avatar/animations/settle_to_idle.fbx", + "url": "qrc:///avatar/animations/settle_to_idle_small.fbx", "startFrame": 1.0, "endFrame": 59.0, "timeScale": 1.0, @@ -1560,7 +1660,7 @@ "data": { "alpha": 0.0, "desiredSpeed": 1.4, - "characteristicSpeeds": [0.6, 1.6, 2.3, 3.1], + "characteristicSpeeds": [0.6, 1.6, 2.8, 4.5], "alphaVar": "moveBackwardAlpha", "desiredSpeedVar": "moveBackwardSpeed" }, @@ -1569,9 +1669,9 @@ "id": "walkBwdShort_c", "type": "clip", "data": { - "url": "qrc:///avatar/animations/walk_short_bwd.fbx", - "startFrame": 0.0, - "endFrame": 38.0, + "url": "qrc:///avatar/animations/walk_bwd.fbx", + "startFrame": 1.0, + "endFrame": 37.0, "timeScale": 1.0, "loopFlag": true }, @@ -1582,8 +1682,8 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/walk_bwd_fast.fbx", - "startFrame": 0.0, - "endFrame": 27.0, + "startFrame": 1.0, + "endFrame": 28.0, "timeScale": 1.0, "loopFlag": true }, @@ -1594,8 +1694,8 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/jog_bwd.fbx", - "startFrame": 0.0, - "endFrame": 24.0, + "startFrame": 1.0, + "endFrame": 20.0, "timeScale": 1.0, "loopFlag": true }, @@ -1606,8 +1706,8 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/run_bwd.fbx", - "startFrame": 0.0, - "endFrame": 16.0, + "startFrame": 1.0, + "endFrame": 14.0, "timeScale": 1.0, "loopFlag": true }, @@ -1620,8 +1720,8 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/turn_left.fbx", - "startFrame": 0.0, - "endFrame": 32.0, + "startFrame": 1.0, + "endFrame": 33.0, "timeScale": 1.0, "loopFlag": true }, @@ -1631,12 +1731,12 @@ "id": "turnRight", "type": "clip", "data": { - "url": "qrc:///avatar/animations/turn_left.fbx", - "startFrame": 0.0, - "endFrame": 32.0, + "url": "qrc:///avatar/animations/turn_right.fbx", + "startFrame": 1.0, + "endFrame": 31.0, "timeScale": 1.0, "loopFlag": true, - "mirrorFlag": true + "mirrorFlag": false }, "children": [] }, @@ -1646,7 +1746,7 @@ "data": { "alpha": 0.0, "desiredSpeed": 1.4, - "characteristicSpeeds": [0.1, 0.5, 1.0, 2.6, 3.0], + "characteristicSpeeds": [0.1, 0.5, 1.0, 2.55, 3.35, 5.25], "alphaVar": "moveLateralAlpha", "desiredSpeedVar": "moveLateralSpeed" }, @@ -1656,8 +1756,8 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, + "startFrame": 1.0, + "endFrame": 30.0, "timeScale": 1.0, "loopFlag": true }, @@ -1668,7 +1768,7 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, + "startFrame": 1.0, "endFrame": 20.0, "timeScale": 1.0, "loopFlag": true @@ -1680,7 +1780,7 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/walk_left.fbx", - "startFrame": 0.0, + "startFrame": 1.0, "endFrame": 35.0, "timeScale": 1.0, "loopFlag": true @@ -1692,7 +1792,7 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/walk_left_fast.fbx", - "startFrame": 0.0, + "startFrame": 1.0, "endFrame": 21.0, "timeScale": 1.0, "loopFlag": true @@ -1704,8 +1804,20 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/jog_left.fbx", - "startFrame": 0.0, - "endFrame": 24.0, + "startFrame": 1.0, + "endFrame": 20.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftRun_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/run_fast_left.fbx", + "startFrame": 1.0, + "endFrame": 19.0, "timeScale": 1.0, "loopFlag": true }, @@ -1719,7 +1831,7 @@ "data": { "alpha": 0.0, "desiredSpeed": 1.4, - "characteristicSpeeds": [0.1, 0.5, 1.0, 2.6, 3.0], + "characteristicSpeeds": [0.1, 0.5, 1.0, 2.55, 3.4, 5.25], "alphaVar": "moveLateralAlpha", "desiredSpeedVar": "moveLateralSpeed" }, @@ -1728,8 +1840,8 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, + "startFrame": 1.0, + "endFrame": 30.0, "timeScale": 1.0, "loopFlag": true, "mirrorFlag": true @@ -1741,7 +1853,7 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, + "startFrame": 1.0, "endFrame": 20.0, "timeScale": 1.0, "loopFlag": true, @@ -1753,12 +1865,12 @@ "id": "strafeRightWalk_c", "type": "clip", "data": { - "url": "qrc:///avatar/animations/walk_left.fbx", - "startFrame": 0.0, + "url": "qrc:///avatar/animations/walk_right.fbx", + "startFrame": 1.0, "endFrame": 35.0, "timeScale": 1.0, "loopFlag": true, - "mirrorFlag": true + "mirrorFlag": false }, "children": [] }, @@ -1766,12 +1878,12 @@ "id": "strafeRightFast_c", "type": "clip", "data": { - "url": "qrc:///avatar/animations/walk_left_fast.fbx", - "startFrame": 0.0, + "url": "qrc:///avatar/animations/walk_right_fast.fbx", + "startFrame": 1.0, "endFrame": 21.0, "timeScale": 1.0, "loopFlag": true, - "mirrorFlag": true + "mirrorFlag": false }, "children": [] }, @@ -1779,12 +1891,25 @@ "id": "strafeRightJog_c", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jog_left.fbx", - "startFrame": 0.0, - "endFrame": 24.0, + "url": "qrc:///avatar/animations/jog_right.fbx", + "startFrame": 1.0, + "endFrame": 20.0, "timeScale": 1.0, "loopFlag": true, - "mirrorFlag": true + "mirrorFlag": false + }, + "children": [] + }, + { + "id": "strafeRightRun_c", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/run_fast_right.fbx", + "startFrame": 1.0, + "endFrame": 19.0, + "timeScale": 1.0, + "loopFlag": true, + "mirrorFlag": false }, "children": [] } @@ -1806,8 +1931,8 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, + "startFrame": 1.0, + "endFrame": 30.0, "timeScale": 1.0, "loopFlag": true }, @@ -1818,7 +1943,7 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, + "startFrame": 1.0, "endFrame": 20.0, "timeScale": 1.0, "loopFlag": true @@ -1830,7 +1955,7 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/side_step_left_fast.fbx", - "startFrame": 0.0, + "startFrame": 1.0, "endFrame": 16.0, "timeScale": 1.0, "loopFlag": true @@ -1855,8 +1980,8 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/side_step_short_left.fbx", - "startFrame": 0.0, - "endFrame": 29.0, + "startFrame": 1.0, + "endFrame": 30.0, "timeScale": 1.0, "loopFlag": true, "mirrorFlag": true @@ -1868,7 +1993,7 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/side_step_left.fbx", - "startFrame": 0.0, + "startFrame": 1.0, "endFrame": 20.0, "timeScale": 1.0, "loopFlag": true, @@ -1881,7 +2006,7 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/side_step_left_fast.fbx", - "startFrame": 0.0, + "startFrame": 1.0, "endFrame": 16.0, "timeScale": 1.0, "loopFlag": true, @@ -1897,7 +2022,7 @@ "data": { "url": "qrc:///avatar/animations/fly.fbx", "startFrame": 1.0, - "endFrame": 80.0, + "endFrame": 79.0, "timeScale": 1.0, "loopFlag": true }, @@ -1907,8 +2032,8 @@ "id": "takeoffStand", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_standing_launch.fbx", - "startFrame": 2.0, + "url": "qrc:///avatar/animations/jump_standing_launch_all.fbx", + "startFrame": 1.0, "endFrame": 16.0, "timeScale": 1.0, "loopFlag": false @@ -1919,7 +2044,7 @@ "id": "TAKEOFFRUN", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "url": "qrc:///avatar/animations/jump_running_launch_land_all.fbx", "startFrame": 4.0, "endFrame": 15.0, "timeScale": 1.0, @@ -1939,9 +2064,9 @@ "id": "inAirStandPreApex", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_standing_apex.fbx", - "startFrame": 0.0, - "endFrame": 0.0, + "url": "qrc:///avatar/animations/jump_standing_apex_all.fbx", + "startFrame": 1.0, + "endFrame": 1.0, "timeScale": 1.0, "loopFlag": false }, @@ -1951,9 +2076,9 @@ "id": "inAirStandApex", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_standing_apex.fbx", - "startFrame": 1.0, - "endFrame": 1.0, + "url": "qrc:///avatar/animations/jump_standing_apex_all.fbx", + "startFrame": 2.0, + "endFrame": 2.0, "timeScale": 1.0, "loopFlag": false }, @@ -1963,9 +2088,9 @@ "id": "inAirStandPostApex", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_standing_apex.fbx", - "startFrame": 2.0, - "endFrame": 2.0, + "url": "qrc:///avatar/animations/jump_standing_apex_all.fbx", + "startFrame": 3.0, + "endFrame": 3.0, "timeScale": 1.0, "loopFlag": false }, @@ -1985,7 +2110,7 @@ "id": "inAirRunPreApex", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "url": "qrc:///avatar/animations/jump_running_launch_land_all.fbx", "startFrame": 16.0, "endFrame": 16.0, "timeScale": 1.0, @@ -1997,7 +2122,7 @@ "id": "inAirRunApex", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "url": "qrc:///avatar/animations/jump_running_launch_land_all.fbx", "startFrame": 22.0, "endFrame": 22.0, "timeScale": 1.0, @@ -2009,7 +2134,7 @@ "id": "inAirRunPostApex", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "url": "qrc:///avatar/animations/jump_running_launch_land_all.fbx", "startFrame": 33.0, "endFrame": 33.0, "timeScale": 1.0, @@ -2023,7 +2148,7 @@ "id": "landStandImpact", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", + "url": "qrc:///avatar/animations/jump_standing_land_settle_all.fbx", "startFrame": 1.0, "endFrame": 6.0, "timeScale": 1.0, @@ -2035,7 +2160,7 @@ "id": "landStand", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_standing_land_settle.fbx", + "url": "qrc:///avatar/animations/jump_standing_land_settle_all.fbx", "startFrame": 6.0, "endFrame": 68.0, "timeScale": 1.0, @@ -2047,7 +2172,7 @@ "id": "LANDRUN", "type": "clip", "data": { - "url": "qrc:///avatar/animations/jump_running_launch_land.fbx", + "url": "qrc:///avatar/animations/jump_running_launch_land_all.fbx", "startFrame": 29.0, "endFrame": 40.0, "timeScale": 1.0, @@ -2078,8 +2203,8 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/idle.fbx", - "startFrame": 0.0, - "endFrame": 90.0, + "startFrame": 1.0, + "endFrame": 300.0, "timeScale": 1.0, "loopFlag": true }, @@ -2090,8 +2215,8 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/idle.fbx", - "startFrame": 0.0, - "endFrame": 90.0, + "startFrame": 1.0, + "endFrame": 300.0, "timeScale": 1.0, "loopFlag": true }, @@ -2099,4 +2224,4 @@ } ] } -} +} \ No newline at end of file diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index 7e8218b7df..2cf07e32bf 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -400,7 +400,8 @@ Item { size: 24; x: 120 anchors.verticalCenter: nameCardConnectionInfoImage.verticalCenter - anchors.left: has3DHTML ? nameCardConnectionInfoText.right + 10 : avatarImage.right + anchors.left: has3DHTML ? nameCardConnectionInfoText.right : avatarImage.right + anchors.leftMargin: has3DHTML ? 10 : 0 } MouseArea { anchors.fill:nameCardRemoveConnectionImage diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/about/About.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/about/About.qml index 3e3758e7a8..d562aae70d 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/about/About.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/about/About.qml @@ -25,6 +25,14 @@ Flickable { if (visible) { root.contentX = 0; root.contentY = 0; + + // When the user clicks the About tab, refresh the audio I/O model so that + // the delegate Component.onCompleted handlers fire, which will update + // the text that appears in the About screen. + audioOutputDevices.model = undefined; + audioOutputDevices.model = AudioScriptingInterface.devices.output; + audioInputDevices.model = undefined; + audioInputDevices.model = AudioScriptingInterface.devices.input; } } @@ -47,8 +55,8 @@ Flickable { source: "images/logo.png" Layout.alignment: Qt.AlignHCenter Layout.topMargin: 16 - Layout.preferredWidth: 200 - Layout.preferredHeight: 150 + Layout.preferredWidth: 160 + Layout.preferredHeight: 120 fillMode: Image.PreserveAspectFit mipmap: true } @@ -195,6 +203,71 @@ Flickable { wrapMode: Text.Wrap } + // This is a bit of a hack to get the name of the currently-selected audio input device + // in the current mode (Desktop or VR). The reason this is necessary is because it seems to me like + // the only way one can get a human-readable list of the audio I/O devices is by using a ListView + // and grabbing the names from the AudioScriptingInterface; you can't do it using a ListModel. + // See `AudioDevices.h`, specifically the comment above the declaration of `QVariant data()`. + ListView { + id: audioInputDevices + visible: false + property string selectedInputDeviceName + Layout.preferredWidth: parent.width + Layout.preferredHeight: contentItem.height + interactive: false + delegate: Item { + Component.onCompleted: { + if (HMD.active && selectedHMD) { + audioInputDevices.selectedInputDeviceName = model.devicename + } else if (!HMD.active && selectedDesktop) { + audioInputDevices.selectedInputDeviceName = model.devicename + } + } + } + } + + HifiStylesUit.GraphikRegular { + text: "Audio Input: " + audioInputDevices.selectedInputDeviceName + Layout.maximumWidth: parent.width + height: paintedHeight + size: 16 + color: simplifiedUI.colors.text.white + wrapMode: Text.Wrap + } + + + // This is a bit of a hack to get the name of the currently-selected audio output device + // in the current mode (Desktop or VR). The reason this is necessary is because it seems to me like + // the only way one can get a human-readable list of the audio I/O devices is by using a ListView + // and grabbing the names from the AudioScriptingInterface; you can't do it using a ListModel. + // See `AudioDevices.h`, specifically the comment above the declaration of `QVariant data()`. + ListView { + id: audioOutputDevices + visible: false + property string selectedOutputDeviceName + Layout.preferredWidth: parent.width + Layout.preferredHeight: contentItem.height + interactive: false + delegate: Item { + Component.onCompleted: { + if (HMD.active && selectedHMD) { + audioOutputDevices.selectedOutputDeviceName = model.devicename + } else if (!HMD.active && selectedDesktop) { + audioOutputDevices.selectedOutputDeviceName = model.devicename + } + } + } + } + + HifiStylesUit.GraphikRegular { + text: "Audio Output: " + audioOutputDevices.selectedOutputDeviceName + Layout.maximumWidth: parent.width + height: paintedHeight + size: 16 + color: simplifiedUI.colors.text.white + wrapMode: Text.Wrap + } + SimplifiedControls.Button { Layout.topMargin: 8 width: 200 @@ -248,6 +321,8 @@ Flickable { textToCopy += "GPU: " + gpuModel + "\n"; textToCopy += "VR Hand Controllers: " + (PlatformInfo.hasRiftControllers() ? "Rift" : (PlatformInfo.hasViveControllers() ? "Vive" : "None")) + "\n"; + textToCopy += "Audio Input: " + audioInputDevices.selectedInputDeviceName + "\n"; + textToCopy += "Audio Output: " + audioOutputDevices.selectedOutputDeviceName + "\n"; textToCopy += "\n**All Platform Info**\n"; textToCopy += JSON.stringify(JSON.parse(PlatformInfo.getPlatform()), null, 4); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f8dadcbc22..606bbf2bf0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -548,6 +548,13 @@ public: return true; } + if (message->message == WM_POWERBROADCAST) { + if (message->wParam == PBT_APMRESUMEAUTOMATIC) { + qCInfo(interfaceapp) << "Waking up from sleep or hybernation."; + QMetaObject::invokeMethod(DependencyManager::get().data(), "noteAwakening", Qt::QueuedConnection); + } + } + if (message->message == WM_COPYDATA) { COPYDATASTRUCT* pcds = (COPYDATASTRUCT*)(message->lParam); QUrl url = QUrl((const char*)(pcds->lpData)); @@ -1465,6 +1472,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _saveAvatarOverrideUrl = true; } + // setDefaultFormat has no effect after the platform window has been created, so call it here. + QSurfaceFormat::setDefaultFormat(getDefaultOpenGLSurfaceFormat()); + _glWidget = new GLCanvas(); getApplicationCompositor().setRenderingWidget(_glWidget); _window->setCentralWidget(_glWidget); @@ -4161,6 +4171,13 @@ bool Application::event(QEvent* event) { case QEvent::FocusOut: focusOutEvent(static_cast(event)); return true; + case QEvent::FocusIn: + { //testing to see if we can set focus when focus is not set to root window. + _glWidget->activateWindow(); + _glWidget->setFocus(); + return true; + } + case QEvent::TouchBegin: touchBeginEvent(static_cast(event)); event->accept(); @@ -5122,7 +5139,7 @@ void Application::idle() { // If the offscreen Ui has something active that is NOT the root, then assume it has keyboard focus. if (offscreenUi && offscreenUi->getWindow()) { auto activeFocusItem = offscreenUi->getWindow()->activeFocusItem(); - if (_keyboardDeviceHasFocus && activeFocusItem != offscreenUi->getRootItem()) { + if (_keyboardDeviceHasFocus && (activeFocusItem != NULL && activeFocusItem != offscreenUi->getRootItem())) { _keyboardMouseDevice->pluginFocusOutEvent(); _keyboardDeviceHasFocus = false; synthesizeKeyReleasEvents(); @@ -7877,7 +7894,7 @@ void Application::showAssetServerWidget(QString filePath) { if (!hmd->getShouldShowTablet() && !isHMDMode()) { getOffscreenUI()->show(url, "AssetServer", startUpload); } else { - static const QUrl url("hifi/dialogs/TabletAssetServer.qml"); + static const QUrl url("qrc:///qml/hifi/dialogs/TabletAssetServer.qml"); if (!tablet->isPathLoaded(url)) { tablet->pushOntoStack(url); } @@ -8769,6 +8786,8 @@ bool Application::isThrottleRendering() const { bool Application::hasFocus() const { bool result = (QApplication::activeWindow() != nullptr); + + #if defined(Q_OS_WIN) // On Windows, QWidget::activateWindow() - as called in setFocus() - makes the application's taskbar icon flash but doesn't // take user focus away from their current window. So also check whether the application is the user's current foreground diff --git a/interface/src/PerformanceManager.cpp b/interface/src/PerformanceManager.cpp index 0a028f95cc..ec12ab0404 100644 --- a/interface/src/PerformanceManager.cpp +++ b/interface/src/PerformanceManager.cpp @@ -10,6 +10,8 @@ // #include "PerformanceManager.h" +#include +#include #include #include "scripting/RenderScriptingInterface.h" @@ -65,6 +67,19 @@ void PerformanceManager::applyPerformancePreset(PerformanceManager::PerformanceP // Ugly case that prevent us to run deferred everywhere... bool isDeferredCapable = platform::Profiler::isRenderMethodDeferredCapable(); + auto masterDisplay = platform::getDisplay(platform::getMasterDisplay()); + + // eval recommanded PPI and Scale + float recommandedPpiScale = 1.0f; + const float RECOMMANDED_PPI[] = { 200.0f, 120.f, 160.f, 250.f}; + if (!masterDisplay.empty() && masterDisplay.count(platform::keys::display::ppi)) { + float ppi = masterDisplay[platform::keys::display::ppi]; + // only scale if the actual ppi is higher than the recommended ppi + if (ppi > RECOMMANDED_PPI[preset]) { + // make sure the scale is no less than a quarter + recommandedPpiScale = std::max(0.25f, RECOMMANDED_PPI[preset] / (float) ppi); + } + } switch (preset) { case PerformancePreset::HIGH: @@ -72,17 +87,21 @@ void PerformanceManager::applyPerformancePreset(PerformanceManager::PerformanceP RenderScriptingInterface::RenderMethod::DEFERRED : RenderScriptingInterface::RenderMethod::FORWARD ) ); + RenderScriptingInterface::getInstance()->setViewportResolutionScale(recommandedPpiScale); + RenderScriptingInterface::getInstance()->setShadowsEnabled(true); qApp->getRefreshRateManager().setRefreshRateProfile(RefreshRateManager::RefreshRateProfile::REALTIME); DependencyManager::get()->setWorldDetailQuality(0.5f); - + break; case PerformancePreset::MID: RenderScriptingInterface::getInstance()->setRenderMethod((isDeferredCapable ? RenderScriptingInterface::RenderMethod::DEFERRED : RenderScriptingInterface::RenderMethod::FORWARD)); + RenderScriptingInterface::getInstance()->setViewportResolutionScale(recommandedPpiScale); + RenderScriptingInterface::getInstance()->setShadowsEnabled(false); qApp->getRefreshRateManager().setRefreshRateProfile(RefreshRateManager::RefreshRateProfile::INTERACTIVE); DependencyManager::get()->setWorldDetailQuality(0.5f); @@ -93,6 +112,8 @@ void PerformanceManager::applyPerformancePreset(PerformanceManager::PerformanceP RenderScriptingInterface::getInstance()->setShadowsEnabled(false); qApp->getRefreshRateManager().setRefreshRateProfile(RefreshRateManager::RefreshRateProfile::ECO); + RenderScriptingInterface::getInstance()->setViewportResolutionScale(recommandedPpiScale); + DependencyManager::get()->setWorldDetailQuality(0.75f); break; diff --git a/interface/src/octree/SafeLanding.cpp b/interface/src/octree/SafeLanding.cpp index c358df746b..75e512232b 100644 --- a/interface/src/octree/SafeLanding.cpp +++ b/interface/src/octree/SafeLanding.cpp @@ -19,6 +19,11 @@ const int SafeLanding::SEQUENCE_MODULO = std::numeric_limits::max() + 1; +CalculateEntityLoadingPriority SafeLanding::entityLoadingOperatorElevateCollidables = [](const EntityItem& entityItem) { + const int COLLIDABLE_ENTITY_PRIORITY = 10.0f; + return entityItem.getCollisionless() * COLLIDABLE_ENTITY_PRIORITY; +}; + namespace { template bool lessThanWraparound(int a, int b) { constexpr int MAX_T_VALUE = std::numeric_limits::max(); @@ -52,7 +57,8 @@ void SafeLanding::startTracking(QSharedPointer entityTreeRen connect(std::const_pointer_cast(entityTree).get(), &EntityTree::deletingEntity, this, &SafeLanding::deleteTrackedEntity); - EntityTreeRenderer::setEntityLoadingPriorityFunction(&ElevatedPriority); + _prevEntityLoadingPriorityOperator = EntityTreeRenderer::getEntityLoadingPriorityOperator(); + EntityTreeRenderer::setEntityLoadingPriorityFunction(entityLoadingOperatorElevateCollidables); } } } @@ -162,7 +168,7 @@ void SafeLanding::stopTracking() { &EntityTree::deletingEntity, this, &SafeLanding::deleteTrackedEntity); _entityTreeRenderer.reset(); } - EntityTreeRenderer::setEntityLoadingPriorityFunction(StandardPriority); + EntityTreeRenderer::setEntityLoadingPriorityFunction(_prevEntityLoadingPriorityOperator); } bool SafeLanding::trackingIsComplete() const { @@ -205,10 +211,6 @@ bool SafeLanding::isEntityPhysicsReady(const EntityItemPointer& entity) { return true; } -float SafeLanding::ElevatedPriority(const EntityItem& entityItem) { - return entityItem.getCollisionless() ? 0.0f : 10.0f; -} - void SafeLanding::debugDumpSequenceIDs() const { int p = -1; qCDebug(interfaceapp) << "Sequence set size:" << _sequenceNumbers.size(); diff --git a/interface/src/octree/SafeLanding.h b/interface/src/octree/SafeLanding.h index 2f1db2366f..87dac16ba3 100644 --- a/interface/src/octree/SafeLanding.h +++ b/interface/src/octree/SafeLanding.h @@ -20,6 +20,8 @@ #include "EntityItem.h" #include "EntityDynamicInterface.h" +#include "EntityTreeRenderer.h" + class EntityTreeRenderer; class EntityItemID; @@ -64,8 +66,8 @@ private: std::set _sequenceNumbers; - static float ElevatedPriority(const EntityItem& entityItem); - static float StandardPriority(const EntityItem&) { return 0.0f; } + static CalculateEntityLoadingPriority entityLoadingOperatorElevateCollidables; + CalculateEntityLoadingPriority _prevEntityLoadingPriorityOperator { nullptr }; static const int SEQUENCE_MODULO; }; diff --git a/interface/src/raypick/LaserPointerScriptingInterface.h b/interface/src/raypick/LaserPointerScriptingInterface.h index 425ffd7de4..6c5ce0dbaf 100644 --- a/interface/src/raypick/LaserPointerScriptingInterface.h +++ b/interface/src/raypick/LaserPointerScriptingInterface.h @@ -21,121 +21,146 @@ class LaserPointerScriptingInterface : public QObject, public Dependency { SINGLETON_DEPENDENCY /**jsdoc - * Synonym for {@link Pointers} as used for laser pointers. Deprecated. + * The LaserPointers API is a subset of the {@link Pointers} API. It lets you create, manage, and visually + * represent objects for repeatedly calculating ray intersections with avatars, entities, and overlays. Ray pointers can also + * be configured to generate events on entities and overlays intersected. + * + *

Deprecated: This API is deprecated. Use {@link Pointers} instead. * * @namespace LaserPointers * * @hifi-interface * @hifi-client-entity * @hifi-avatar + * + * @borrows Pointers.enablePointer as enableLaserPointer + * @borrows Pointers.disablePointer as disableLaserPointer + * @borrows Pointers.removePointer as removeLaserPointer + * @borrows Pointers.setPrecisionPicking as setPrecisionPicking */ public: /**jsdoc + * Creates a new ray pointer. The pointer can have a wide range of behaviors depending on the properties specified. For + * example, it may be a static ray pointer, a mouse ray pointer, or joint ray pointer. + *

Warning: Pointers created using this method currently always intersect at least visible and + * collidable things but this may not always be the case.

* @function LaserPointers.createLaserPointer - * @param {Pointers.LaserPointerProperties} properties - * @returns {number} + * @param {Pointers.RayPointerProperties} properties - The properties of the pointer, including the properties of the + * underlying pick that the pointer uses to do its picking. + * @returns {number} The ID of the pointer if successfully created, otherwise 0. */ Q_INVOKABLE unsigned int createLaserPointer(const QVariant& properties) const; - /**jsdoc - * @function LaserPointers.enableLaserPointer - * @param {number} id - */ + // jsdoc @borrows from Pointers Q_INVOKABLE void enableLaserPointer(unsigned int uid) const { DependencyManager::get()->enablePointer(uid); } - /**jsdoc - * @function LaserPointers.disableLaserPointer - * @param {number} id - */ + // jsdoc @borrows from Pointers Q_INVOKABLE void disableLaserPointer(unsigned int uid) const { DependencyManager::get()->disablePointer(uid); } - /**jsdoc - * @function LaserPointers.removeLaserPointer - * @param {number} id - */ + // jsdoc @borrows from Pointers Q_INVOKABLE void removeLaserPointer(unsigned int uid) const { DependencyManager::get()->removePointer(uid); } /**jsdoc + * Edits a render state of a pointer, to change its visual appearance for the state when the pointer is intersecting + * something. + *

Note: You can only edit the properties of the existing parts of the pointer; you cannot change the + * type of any part.

+ *

Note: You cannot use this method to change the appearance of a default render state.

* @function LaserPointers.editRenderState - * @param {number} id - * @param {string} renderState - * @param {Pointers.RayPointerRenderState} properties + * @param {number} id - The ID of the pointer. + * @param {string} renderState - The name of the render state to edit. + * @param {Pointers.RayPointerRenderState} properties - The new properties for the render state. Only the overlay + * properties to change need be specified. */ Q_INVOKABLE void editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const; /**jsdoc + * Sets the render state of a pointer, to change its visual appearance and possibly disable or enable it. * @function LaserPointers.setRenderState - * @param {string} renderState - * @param {number} id + * @param {string} renderState -

The name of the render state to set the pointer to. This may be:

+ *
    + *
  • The name of one of the render states set in the pointer's properties.
  • + *
  • "", to hide the pointer and disable emitting of events.
  • + *
+ * @param {number} id - The ID of the pointer. */ Q_INVOKABLE void setRenderState(unsigned int uid, const QString& renderState) const { DependencyManager::get()->setRenderState(uid, renderState.toStdString()); } /**jsdoc + * Gets the most recent intersection of a pointer. A pointer continues to be updated ready to return a result, as long as + * it is enabled, regardless of the render state. * @function LaserPointers.getPrevRayPickResult - * @param {number} id - * @returns {RayPickResult} + * @param {number} id - The ID of the pointer. + * @returns {RayPickResult} The most recent intersection of the pointer. */ Q_INVOKABLE QVariantMap getPrevRayPickResult(unsigned int uid) const; - - /**jsdoc - * @function LaserPointers.setPrecisionPicking - * @param {number} id - * @param {boolean} precisionPicking - */ + // jsdoc @borrows from Pointers Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking) const { DependencyManager::get()->setPrecisionPicking(uid, precisionPicking); } /**jsdoc + * Sets the length of a pointer. * @function LaserPointers.setLaserLength - * @param {number} id - * @param {number} laserLength + * @param {number} id - The ID of the pointer. + * @param {number} laserLength - The desired length of the pointer. */ Q_INVOKABLE void setLaserLength(unsigned int uid, float laserLength) const { DependencyManager::get()->setLength(uid, laserLength); } /**jsdoc + * Sets a list of entity and avatar IDs that a pointer should ignore during intersection. * @function LaserPointers.setIgnoreItems - * @param {number} id - * @param {Uuid[]} ignoreItems + * @param {number} id - The ID of the pointer. + * @param {Uuid[]} ignoreItems - A list of IDs to ignore. */ Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities) const; /**jsdoc + * Sets a list of entity and avatar IDs that a pointer should include during intersection, instead of intersecting with + * everything. * @function LaserPointers.setIncludeItems - * @param {number} id - * @param {Uuid[]} includeItems + * @param {number} id - The ID of the pointer. + * @param {Uuid[]} includeItems - A list of IDs to include. */ Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities) const; /**jsdoc + * Locks a pointer onto a specific entity or avatar. * @function LaserPointers.setLockEndUUID - * @param {number} id - * @param {Uuid} itemID - * @param {boolean} isAvatar - * @param {Mat4} [offsetMat] + * @param {number} id - The ID of the pointer. + * @param {Uuid} targetID - The ID of the entity or avatar to lock the pointer on to. + * @param {boolean} isAvatar - true if the target is an avatar, false if it is an entity. + * @param {Mat4} [offset] - The offset of the target point from the center of the target item. If not specified, the + * pointer locks on to the center of the target item. */ Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isAvatar, const glm::mat4& offsetMat = glm::mat4()) const { DependencyManager::get()->setLockEndUUID(uid, objectID, isAvatar, offsetMat); } /**jsdoc + * Checks if a pointer is associated with the left hand: a pointer with joint property set to + * "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND". * @function LaserPointers.isLeftHand - * @param {number} id - * @returns {boolean} + * @param {number} id - The ID of the pointer. + * @returns {boolean} true if the pointer is associated with the left hand, false if it isn't. */ Q_INVOKABLE bool isLeftHand(unsigned int uid) { return DependencyManager::get()->isLeftHand(uid); } /**jsdoc + * Checks if a pointer is associated with the right hand: a pointer with joint property set to + * "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND". * @function LaserPointers.isRightHand - * @param {number} id - * @returns {boolean} + * @param {number} id - The ID of the pointer. + * @returns {boolean} true if the pointer is associated with the right hand, false if it isn't. */ Q_INVOKABLE bool isRightHand(unsigned int uid) { return DependencyManager::get()->isRightHand(uid); } /**jsdoc + * Checks if a pointer is associated with the system mouse: a pointer with joint property set to + * "Mouse". * @function LaserPointers.isMouse - * @param {number} id - * @returns {boolean} + * @param {number} id - The ID of the pointer. + * @returns {boolean} true if the pointer is associated with the system mouse, false if it isn't. */ Q_INVOKABLE bool isMouse(unsigned int uid) { return DependencyManager::get()->isMouse(uid); } }; diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 82d00d803f..12f68c1430 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -55,7 +55,7 @@ PickFilter getPickFilter(unsigned int filter) { } /**jsdoc - * A set of properties that can be passed to {@link Picks.createPick} when creating a new ray pick. + * The properties of a ray pick. * * @typedef {object} Picks.RayPickProperties * @property {boolean} [enabled=false] - true if this pick should start enabled, false if it should @@ -138,7 +138,7 @@ unsigned int PickScriptingInterface::createRayPick(const QVariant& properties) { } /**jsdoc - * A set of properties that can be passed to {@link Picks.createPick} when creating a new stylus pick. + * The properties of a stylus pick. * * @typedef {object} Picks.StylusPickProperties * @property {number} [hand=-1] 0 for the left hand, 1 for the right hand, invalid (-1) @@ -189,7 +189,7 @@ unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties // NOTE: Laser pointer still uses scaleWithAvatar. Until scaleWithAvatar is also deprecated for pointers, scaleWithAvatar should not be removed from the pick API. /**jsdoc - * A set of properties that can be passed to {@link Picks.createPick} when creating a new parabola pick. + * The properties of a parabola pick. * * @typedef {object} Picks.ParabolaPickProperties * @property {boolean} [enabled=false] - true if this pick should start enabled, false if it should @@ -297,7 +297,7 @@ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properti /**jsdoc - * A set of properties that can be passed to {@link Picks.createPick} when creating a new collision pick. + * The properties of a collision pick. * * @typedef {object} Picks.CollisionPickProperties * @property {boolean} [enabled=false] - true if this pick should start enabled, false if it should diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 1cbdaa92f7..e973ee3643 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -194,7 +194,8 @@ public: Q_INVOKABLE QVariantMap getPrevPickResult(unsigned int uid); /**jsdoc - * Sets whether or not to use precision picking, i.e., whether to pick against precise meshes or coarse meshes. + * Sets whether or not a pick should use precision picking, i.e., whether it should pick against precise meshes or coarse + * meshes. * This has the same effect as using the PICK_PRECISE or PICK_COARSE filter flags. * @function Picks.setPrecisionPicking * @param {number} id - The ID of the pick. @@ -203,7 +204,7 @@ public: Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking); /**jsdoc - * Sets a list of entity and avatar IDs to ignore during intersection. + * Sets a list of entity and avatar IDs that a pick should ignore during intersection. *

Note: Not used by stylus picks.

* @function Picks.setIgnoreItems * @param {number} id - The ID of the pick. @@ -212,8 +213,9 @@ public: Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems); /**jsdoc - * Sets a list of entity IDs and/or avatar IDs to include during intersection, instead of intersecting with everything. - *

Note: Stylus picks only intersect with objects in their include list.

+ * Sets a list of entity and avatar IDs that a pick should include during intersection, instead of intersecting with + * everything. + *

Note: Stylus picks only intersect with items in their include list.

* @function Picks.setIncludeItems * @param {number} id - The ID of the pick. * @param {Uuid[]} includeItems - The list of IDs to include. @@ -221,9 +223,9 @@ public: Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeItems); /**jsdoc - * Checks if a pick is associated with the left hand: a ray or parabola pick with joint set to - * "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a stylus pick with hand - * set to 0. + * Checks if a pick is associated with the left hand: a ray or parabola pick with joint property set to + * "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a stylus pick with + * hand property set to 0. * @function Picks.isLeftHand * @param {number} id - The ID of the pick. * @returns {boolean} true if the pick is associated with the left hand, false if it isn't. @@ -231,9 +233,9 @@ public: Q_INVOKABLE bool isLeftHand(unsigned int uid); /**jsdoc - * Checks if a pick is associated with the right hand: a ray or parabola pick with joint set to - * "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a stylus pick with hand - * set to 1. + * Checks if a pick is associated with the right hand: a ray or parabola pick with joint property set to + * "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a stylus pick with + * hand property set to 1. * @function Picks.isRightHand * @param {number} id - The ID of the pick. * @returns {boolean} true if the pick is associated with the right hand, false if it isn't. @@ -241,7 +243,8 @@ public: Q_INVOKABLE bool isRightHand(unsigned int uid); /**jsdoc - * Checks if a pick is associated with the system mouse: a ray or parabola pick with joint set to "Mouse". + * Checks if a pick is associated with the system mouse: a ray or parabola pick with joint property set to + * "Mouse". * @function Picks.isMouse * @param {number} id - The ID of the pick. * @returns {boolean} true if the pick is associated with the system mouse, false if it isn't. diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index 1c80caff88..f1dcf7bd5d 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -51,21 +51,22 @@ unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType& } /**jsdoc - * A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick. + * The properties of a stylus pointer. These include the properties from the underlying stylus pick that the pointer uses. * @typedef {object} Pointers.StylusPointerProperties - * @property {boolean} [hover=false] If this pointer should generate hover events. - * @property {boolean} [enabled=false] - * @property {Vec3} [tipOffset] The specified offset of the from the joint index. - * @property {Pointers.StylusPointerProperties.model} [model] Data to replace the default model url, positionOffset and rotationOffset. + * @property {Pointers.StylusPointerModel} [model] - Override some or all of the default stylus model properties. + * @property {boolean} [hover=false] - true if the pointer generates {@link Entities} hover events, + * false if it doesn't. + * @see {@link Picks.StylusPickProperties} for additional properties from the underlying stylus pick. + */ +/**jsdoc + * The properties of a stylus pointer model. + * @typedef {object} Pointers.StylusPointerModel + * @property {string} [url] - The url of a model to use for the stylus, to override the default stylus mode. + * @property {Vec3} [dimensions] - The dimensions of the stylus, to override the default stylus dimensions. + * @property {Vec3} [positionOffset] - The position offset of the model from the stylus tip, to override the default position + * offset. + * @property {Quat} [rotationOffset] - The rotation offset of the model from the hand, to override the default rotation offset. */ - /**jsdoc - * properties defining stylus pick model that can be included to {@link Pointers.StylusPointerProperties} - * @typedef {object} Pointers.StylusPointerProperties.model - * @property {string} [url] url to the model - * @property {Vec3} [dimensions] the dimensions of the model - * @property {Vec3} [positionOffset] the position offset of the model from the stylus tip. - * @property {Vec3} [rotationOffset] the rotation offset of the model from the parent joint index - */ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties) const { QVariantMap propertyMap = properties.toMap(); @@ -104,48 +105,76 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties) } /**jsdoc - * A set of properties used to define the visual aspect of a Ray Pointer in the case that the Pointer is not intersecting something. Same as a {@link Pointers.RayPointerRenderState}, - * but with an additional distance field. - * + * Properties that define the visual appearance of a ray pointer when the pointer is not intersecting something. These are the + * properties of {@link Pointers.RayPointerRenderState} but with an additional property. * @typedef {object} Pointers.DefaultRayPointerRenderState - * @augments Pointers.RayPointerRenderState - * @property {number} distance The distance at which to render the end of this Ray Pointer, if one is defined. + * @property {number} distance - The distance at which to render the end of the ray pointer. + * @see {@link Pointers.RayPointerRenderState} for the remainder of the properties. */ /**jsdoc - * A set of properties which define the visual aspect of a Ray Pointer in the case that the Pointer is intersecting something. - * + * Properties that define the visual appearance of a ray pointer when the pointer is intersecting something. * @typedef {object} Pointers.RayPointerRenderState - * @property {string} name When using {@link Pointers.createPointer}, the name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState} - * @property {Overlays.OverlayProperties|QUuid} [start] When using {@link Pointers.createPointer}, an optionally defined object to represent the beginning of the Ray Pointer, - * using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). - * When returned from {@link Pointers.getPointerProperties}, the ID of the created object if it exists, or a null ID otherwise. - * @property {Overlays.OverlayProperties|QUuid} [path] When using {@link Pointers.createPointer}, an optionally defined object to represent the path of the Ray Pointer, - * using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field), which must be "line3d". - * When returned from {@link Pointers.getPointerProperties}, the ID of the created object if it exists, or a null ID otherwise. - * @property {Overlays.OverlayProperties|QUuid} [end] When using {@link Pointers.createPointer}, an optionally defined object to represent the end of the Ray Pointer, - * using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). - * When returned from {@link Pointers.getPointerProperties}, the ID of the created object if it exists, or a null ID otherwise. + * @property {string} name - When creating using {@link Pointers.createPointer}, the name of the render state. + * @property {Overlays.OverlayProperties|Uuid} [start] + *

When creating or editing using {@link Pointers.createPointer} or {@link Pointers.editRenderState}, the properties of + * an overlay to render at the start of the ray pointer. The type property must be specified.

+ *

When getting using {@link Pointers.getPointerProperties}, the ID of the overlay rendered at the start of the ray; + * null if there is no overlay. + * + * @property {Overlays.OverlayProperties|Uuid} [path] + *

When creating or editing using {@link Pointers.createPointer} or {@link Pointers.editRenderState}, the properties of + * the overlay rendered for the path of the ray pointer. The type property must be specified and be + * "line3d".

+ *

When getting using {@link Pointers.getPointerProperties}, the ID of the overlay rendered for the path of the ray; + * null if there is no overlay. + * + * @property {Overlays.OverlayProperties|Uuid} [end] + *

When creating or editing using {@link Pointers.createPointer} or {@link Pointers.editRenderState}, the properties of + * an overlay to render at the end of the ray pointer. The type property must be specified.

+ *

When getting using {@link Pointers.getPointerProperties}, the ID of the overlay rendered at the end of the ray; + * null if there is no overlay. */ /**jsdoc - * A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick. - * @typedef {object} Pointers.LaserPointerProperties - * @property {boolean} [faceAvatar=false] If true, the end of the Pointer will always rotate to face the avatar. - * @property {boolean} [centerEndY=true] If false, the end of the Pointer will be moved up by half of its height. - * @property {boolean} [lockEnd=false] If true, the end of the Pointer will lock on to the center of the object at which the pointer is pointing. - * @property {boolean} [distanceScaleEnd=false] If true, the dimensions of the end of the Pointer will scale linearly with distance. - * @property {boolean} [scaleWithParent=false] If true, the width of the Pointer's path will scale linearly with the pick parent's scale. scaleWithAvatar is an alias but is deprecated. - * @property {boolean} [followNormal=false] If true, the end of the Pointer will rotate to follow the normal of the intersected surface. - * @property {number} [followNormalStrength=0.0] The strength of the interpolation between the real normal and the visual normal if followNormal is true. 0-1. If 0 or 1, - * the normal will follow exactly. - * @property {boolean} [enabled=false] - * @property {Pointers.RayPointerRenderState[]|Object.} [renderStates] A collection of different visual states to switch between. - * When using {@link Pointers.createPointer}, a list of RayPointerRenderStates. - * When returned from {@link Pointers.getPointerProperties}, a map between render state names and RayPointRenderStates. - * @property {Pointers.DefaultRayPointerRenderState[]|Object.} [defaultRenderStates] A collection of different visual states to use if there is no intersection. - * When using {@link Pointers.createPointer}, a list of DefaultRayPointerRenderStates. - * When returned from {@link Pointers.getPointerProperties}, a map between render state names and DefaultRayPointRenderStates. - * @property {boolean} [hover=false] If this Pointer should generate hover events. - * @property {Pointers.Trigger[]} [triggers] A list of different triggers mechanisms that control this Pointer's click event generation. + * The properties of a ray pointer. These include the properties from the underlying ray pick that the pointer uses. + * @typedef {object} Pointers.RayPointerProperties + * @property {boolean} [faceAvatar=false] - true if the overlay rendered at the end of the ray rotates about the + * world y-axis to always face the avatar; false if it maintains its world orientation. + * @property {boolean} [centerEndY=true] - true if the overlay rendered at the end of the ray is centered on + * the ray end; false if the overlay is positioned against the surface if followNormal is + * true, or above the ray end if followNormal is false. +* @property {boolean} [lockEnd=false] - true if the end of the ray is locked to the center of the object at + * which the ray is pointing; false if the end of the ray is at the intersected surface. + * @property {boolean} [distanceScaleEnd=false] - true if the dimensions of the overlay at the end of the ray + * scale linearly with distance; false if they aren't. + * @property {boolean} [scaleWithParent=false] - true if the width of the ray's path and the size of the + * start and end overlays scale linearly with the pointer parent's scale; false if they don't scale. + * @property {boolean} [scaleWithAvatar=false] - A synonym for scalewithParent. + *

Deprecated: This property is deprecated and will be removed.

+ * @property {boolean} [followNormal=false] - true if the overlay rendered at the end of the ray rotates to + * follow the normal of the surface if one is intersected; false if it doesn't. + * @property {number} [followNormalStrength=0.0] - How quickly the overlay rendered at the end of the ray rotates to follow + * the normal of an intersected surface. If 0 or 1, the overlay rotation follows instantaneously; + * for other values, the larger the value the more quickly the rotation follows. + * @property {Pointers.RayPointerRenderState[]|Object.} [renderStates] + *

A set of visual states that can be switched among using {@link Pointers.setRenderState}. These define the visual + * appearance of the pointer when it is intersecting something.

+ *

When setting using {@link Pointers.createPointer}, an array of + * {@link Pointers.RayPointerRenderState|RayPointerRenderState} values.

+ *

When getting using {@link Pointers.getPointerProperties}, an object mapping render state names to + * {@link Pointers.RayPointerRenderState|RayPointerRenderState} values.

+ * @property {Pointers.DefaultRayPointerRenderState[]|Object.} + * [defaultRenderStates] + *

A set of visual states that can be switched among using {@link Pointers.setRenderState}. These define the visual + * appearance of the pointer when it is not intersecting something.

+ *

When setting using {@link Pointers.createPointer}, an array of + * {@link Pointers.DefaultRayPointerRenderState|DefaultRayPointerRenderState} values.

+ *

When getting using {@link Pointers.getPointerProperties}, an object mapping render state names to + * {@link Pointers.DefaultRayPointerRenderState|DefaultRayPointerRenderState} values.

+ * @property {boolean} [hover=false] - true if the pointer generates {@link Entities} hover events, + * false if it doesn't. + * @property {Pointers.Trigger[]} [triggers=[]] - A list of ways that a {@link Controller} action or function should trigger + * events on the entity or overlay currently intersected. + * @see {@link Picks.RayPickProperties} for additional properties from the underlying ray pick. */ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& properties) const { QVariantMap propertyMap = properties.toMap(); @@ -260,58 +289,84 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope } /**jsdoc -* The rendering properties of the parabolic path -* -* @typedef {object} Pointers.ParabolaProperties -* @property {Color} color=255,255,255 The color of the parabola. -* @property {number} alpha=1.0 The alpha of the parabola. -* @property {number} width=0.01 The width of the parabola, in meters. -* @property {boolean} isVisibleInSecondaryCamera=false The width of the parabola, in meters. -* @property {boolean} drawInFront=false If true, the parabola is rendered in front of other items in the scene. -*/ + * The visual appearance of the parabolic path. + * @typedef {object} Pointers.ParabolaPointerPath + * @property {Color} [color=255,255,255] - The color of the parabola. + * @property {number} [alpha=1.0] - The opacity of the parabola, range 0.01.0. + * @property {number} [width=0.01] - The width of the parabola, in meters. + * @property {boolean} [isVisibleInSecondaryCamera=false] - true if the parabola is rendered in the secondary + * camera, false if it isn't. + * @property {boolean} [drawInFront=false] - true if the parabola is rendered in front of objects in the world, + * but behind the HUD, false if it is occluded by objects in front of it. + */ /**jsdoc -* A set of properties used to define the visual aspect of a Parabola Pointer in the case that the Pointer is not intersecting something. Same as a {@link Pointers.ParabolaPointerRenderState}, -* but with an additional distance field. -* -* @typedef {object} Pointers.DefaultParabolaPointerRenderState -* @augments Pointers.ParabolaPointerRenderState -* @property {number} distance The distance along the parabola at which to render the end of this Parabola Pointer, if one is defined. -*/ + * Properties that define the visual appearance of a parabola pointer when the pointer is not intersecting something. These are + * properties of {@link Pointers.ParabolaPointerRenderState} but with an additional property. + * @typedef {object} Pointers.DefaultParabolaPointerRenderState + * @property {number} distance - The distance along the parabola at which to render the end of the parabola pointer. + * @see {@link Pointers.ParabolaPointerRenderState} for the remainder of the properties. + */ /**jsdoc -* A set of properties used to define the visual aspect of a Parabola Pointer in the case that the Pointer is intersecting something. -* -* @typedef {object} Pointers.ParabolaPointerRenderState -* @property {string} name When using {@link Pointers.createPointer}, the name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState} -* @property {Overlays.OverlayProperties|QUuid} [start] When using {@link Pointers.createPointer}, an optionally defined object to represent the beginning of the Parabola Pointer, -* using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). -* When returned from {@link Pointers.getPointerProperties}, the ID of the created object if it exists, or a null ID otherwise. -* @property {Pointers.ParabolaProperties} [path] When using {@link Pointers.createPointer}, the optionally defined rendering properties of the parabolic path defined by the Parabola Pointer. -* Not defined in {@link Pointers.getPointerProperties}. -* @property {Overlays.OverlayProperties|QUuid} [end] When using {@link Pointers.createPointer}, an optionally defined object to represent the end of the Parabola Pointer, -* using the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). -* When returned from {@link Pointers.getPointerProperties}, the ID of the created object if it exists, or a null ID otherwise. -*/ + * Properties that define the visual appearance of a parabola pointer when the pointer is intersecting something. + * @typedef {object} Pointers.ParabolaPointerRenderState + * @property {string} name - When creating using {@link Pointers.createPointer}, the name of the render state. + * @property {Overlays.OverlayProperties|Uuid} [start] + *

When creating or editing using {@link Pointers.createPointer} or {@link Pointers.editRenderState}, the properties of + * an overlay to render at the start of the parabola pointer. The type property must be specified.

+ *

When getting using {@link Pointers.getPointerProperties}, the ID of the overlay rendered at the start of the + * parabola; null if there is no overlay. + * @property {Pointers.ParabolaPointerPath|Uuid} [path] + *

When creating or editing using {@link Pointers.createPointer} or {@link Pointers.editRenderState}, the properties of + * the rendered path of the parabola pointer.

+ *

This property is not provided when getting using {@link Pointers.getPointerProperties}. + * @property {Overlays.OverlayProperties|Uuid} [end] + *

When creating or editing using {@link Pointers.createPointer} or {@link Pointers.editRenderState}, the properties of + * an overlay to render at the end of the ray pointer. The type property must be specified.

+ *

When getting using {@link Pointers.getPointerProperties}, the ID of the overlay rendered at the end of the parabola; + * null if there is no overlay. + */ /**jsdoc -* A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick. -* @typedef {object} Pointers.ParabolaPointerProperties -* @property {boolean} [faceAvatar=false] If true, the end of the Pointer will always rotate to face the avatar. -* @property {boolean} [centerEndY=true] If false, the end of the Pointer will be moved up by half of its height. -* @property {boolean} [lockEnd=false] If true, the end of the Pointer will lock on to the center of the object at which the pointer is pointing. -* @property {boolean} [distanceScaleEnd=false] If true, the dimensions of the end of the Pointer will scale linearly with distance. -* @property {boolean} [scaleWithParent=true] If true, the width of the Pointer's path will scale linearly with the pick parent's scale. scaleWithAvatar is an alias but is deprecated. -* @property {boolean} [followNormal=false] If true, the end of the Pointer will rotate to follow the normal of the intersected surface. -* @property {number} [followNormalStrength=0.0] The strength of the interpolation between the real normal and the visual normal if followNormal is true. 0-1. If 0 or 1, -* the normal will follow exactly. -* @property {boolean} [enabled=false] -* @property {Pointers.ParabolaPointerRenderState[]|Object.} [renderStates] A collection of different visual states to switch between. -* When using {@link Pointers.createPointer}, a list of ParabolaPointerRenderStates. -* When returned from {@link Pointers.getPointerProperties}, a map between render state names and ParabolaPointerRenderStates. -* @property {Pointers.DefaultParabolaPointerRenderState[]|Object.} [defaultRenderStates] A collection of different visual states to use if there is no intersection. -* When using {@link Pointers.createPointer}, a list of DefaultParabolaPointerRenderStates. -* When returned from {@link Pointers.getPointerProperties}, a map between render state names and DefaultParabolaPointerRenderStates. -* @property {boolean} [hover=false] If this Pointer should generate hover events. -* @property {Pointers.Trigger[]} [triggers] A list of different triggers mechanisms that control this Pointer's click event generation. -*/ + * The properties of a parabola pointer. These include the properties from the underlying parabola pick that the pointer uses. + * @typedef {object} Pointers.ParabolaPointerProperties + * @property {boolean} [faceAvatar=false] - true if the overlay rendered at the end of the ray rotates about the + * world y-axis to always face the avatar; false if it maintains its world orientation. + * @property {boolean} [centerEndY=true] - true if the overlay rendered at the end of the ray is centered on + * the ray end; false if the overlay is positioned against the surface if followNormal is + * true, or above the ray end if followNormal is false. +* @property {boolean} [lockEnd=false] - true if the end of the ray is locked to the center of the object at + * which the ray is pointing; false if the end of the ray is at the intersected surface. + * @property {boolean} [distanceScaleEnd=false] - true if the dimensions of the overlay at the end of the ray + * scale linearly with distance; false if they aren't. + * @property {boolean} [scaleWithParent=false] - true if the width of the ray's path and the size of the + * start and end overlays scale linearly with the pointer parent's scale; false if they don't scale. + * @property {boolean} [scaleWithAvatar=false] - A synonym for scalewithParent. + *

Deprecated: This property is deprecated and will be removed.

+ * @property {boolean} [followNormal=false] - true if the overlay rendered at the end of the ray rotates to + * follow the normal of the surface if one is intersected; false if it doesn't. + * @property {number} [followNormalStrength=0.0] - How quickly the overlay rendered at the end of the ray rotates to follow + * the normal of an intersected surface. If 0 or 1, the overlay rotation follows instantaneously; + * for other values, the larger the value the more quickly the rotation follows. + * @property {Pointers.ParabolaPointerRenderState[]|Object.} [renderStates] + *

A set of visual states that can be switched among using {@link Pointers.setRenderState}. These define the visual + * appearance of the pointer when it is intersecting something.

+ *

When setting using {@link Pointers.createPointer}, an array of + * {@link Pointers.ParabolaPointerRenderState|ParabolaPointerRenderState} values.

+ *

When getting using {@link Pointers.getPointerProperties}, an object mapping render state names to + * {@link Pointers.ParabolaPointerRenderState|ParabolaPointerRenderState} values.

+ * @property {Pointers.DefaultParabolaPointerRenderState[]|Object.} + * [defaultRenderStates] + *

A set of visual states that can be switched among using {@link Pointers.setRenderState}. These define the visual + * appearance of the pointer when it is not intersecting something.

+ *

When setting using {@link Pointers.createPointer}, an array of + * {@link Pointers.DefaultParabolaPointerRenderState|DefaultParabolaPointerRenderState} values.

+ *

When getting using {@link Pointers.getPointerProperties}, an object mapping render state names to + * {@link Pointers.DefaultParabolaPointerRenderState|DefaultParabolaPointerRenderState} values.

+ * @property {boolean} [hover=false] - true if the pointer generates {@link Entities} hover events, + * false if it doesn't. + * @property {Pointers.Trigger[]} [triggers=[]] - A list of ways that a {@link Controller} action or function should trigger + * events on the entity or overlay currently intersected. + * @see {@link Picks.ParabolaPickProperties} for additional properties from the underlying parabola pick. + */ unsigned int PointerScriptingInterface::createParabolaPointer(const QVariant& properties) const { QVariantMap propertyMap = properties.toMap(); diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 268b178fb6..e6efaae09f 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -15,8 +15,9 @@ #include /**jsdoc - * The Pointers API lets you create and manage objects for repeatedly calculating intersections in different ways, as well as the visual representation of those objects. - * Pointers can also be configured to automatically generate {@link PointerEvent}s on {@link Entities}. + * The Pointers API lets you create, manage, and visually represent objects for repeatedly calculating + * intersections with avatars, entities, and overlays. Pointers can also be configured to generate events on entities and + * overlays intersected. * * @namespace Pointers * @@ -35,184 +36,416 @@ public: unsigned int createParabolaPointer(const QVariant& properties) const; /**jsdoc - * A trigger mechanism for Ray and Parabola Pointers. - * - * @typedef {object} Pointers.Trigger - * @property {Controller.Standard|Controller.Actions|function} action This can be a built-in Controller action, like Controller.Standard.LTClick, or a function that evaluates to >= 1.0 when you want to trigger button. - * @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered, - * it will try to set the entity focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None". - */ + * Specifies that a {@link Controller} action or function should trigger events on the entity or overlay currently + * intersected by a {@link Pointers.RayPointerProperties|Ray} or {@link Pointers.ParabolaPointerProperties|Parabola} + * pointer. + * @typedef {object} Pointers.Trigger + * @property {Controller.Standard|Controller.Actions|function} action - The controller output or function that triggers the + * events on the entity or overlay. If a function, it must return a number >= 1.0 to start the action + * and < 1.0 to terminate the action. + * @property {string} button - Which button to trigger. + *
    + *
  • "Primary", "Secondary", and "Tertiary" cause {@link Entities} and + * {@link Overlays} mouse pointer events. Other button names also cause mouse events but the button + * property in the event will be "None".
  • + *
  • "Focus" will try to give focus to the entity or overlay which the pointer is intersecting.
  • + *
+ */ /**jsdoc - * Adds a new Pointer - * Different {@link PickType}s use different properties, and within one PickType, the properties you choose can lead to a wide range of behaviors. For example, - * with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pointer, a Mouse Ray Pointer, or a Joint Ray Pointer. - * Pointers created with this method always intersect at least visible and collidable things + * Creates a new ray, parabola, or stylus pointer. The pointer can have a wide range of behaviors depending on the + * properties specified. For example, a ray pointer may be a static ray pointer, a mouse ray pointer, or joint ray + * pointer. + *

Warning: Pointers created using this method currently always intersect at least visible and + * collidable things but this may not always be the case.

* @function Pointers.createPointer - * @param {PickType} type A PickType that specifies the method of picking to use. Cannot be {@link PickType|PickType.Collision}. - * @param {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties|Pointers.ParabolaPointerProperties} properties A PointerProperties object, containing all the properties for initializing this Pointer and the {@link Picks.PickProperties} for the Pick that - * this Pointer will use to do its picking. - * @returns {number} The ID of the created Pointer. Used for managing the Pointer. 0 if invalid. + * @param {PickType} type - The type of pointer to create. Cannot be {@link PickType|PickType.Collision}. + * @param {Pointers.RayPointerProperties|Pointers.ParabolaPointerProperties|Pointers.StylusPointerProperties} properties - + * The properties of the pointer, per the pointer type, including the properties of the underlying pick + * that the pointer uses to do its picking. + * @returns {number} The ID of the pointer if successfully created, otherwise 0. * - * @example Create a left hand Ray Pointer that triggers events on left controller trigger click and changes color when it's intersecting something. - * - * var end = { + * @example Create a ray pointer on the left hand that changes color when it's intersecting and that triggers + * events.
+ * Note: Stop controllerScripts.js from running to disable similar behavior from it. + * var intersectEnd = { * type: "sphere", - * dimensions: {x:0.5, y:0.5, z:0.5}, + * dimensions: { x: 0.2, y: 0.2, z: 0.2 }, * solid: true, - * color: {red:0, green:255, blue:0}, + * color: { red: 0, green: 255, blue: 0 }, * ignorePickIntersection: true * }; - * var end2 = { + * var intersectedPath = { + * type: "line3d", + * color: { red: 0, green: 255, blue: 0 }, + * }; + * var searchEnd = { * type: "sphere", - * dimensions: {x:0.5, y:0.5, z:0.5}, + * dimensions: { x: 0.2, y: 0.2, z: 0.2 }, * solid: true, - * color: {red:255, green:0, blue:0}, + * color: { red: 255, green: 0, blue: 0 }, * ignorePickIntersection: true * }; - * - * var renderStates = [ {name: "test", end: end} ]; - * var defaultRenderStates = [ {name: "test", distance: 10.0, end: end2} ]; - * var pointer = Pointers.createPointer(PickType.Ray, { + * var searchPath = { + * type: "line3d", + * color: { red: 255, green: 0, blue: 0 }, + * }; + * + * var renderStates = [{ name: "example", path: intersectedPath, end: intersectEnd }]; + * var defaultRenderStates = [{ name: "example", distance: 20.0, path: searchPath, end: searchEnd }]; + * + * // Create the pointer. + * var rayPointer = Pointers.createPointer(PickType.Ray, { * joint: "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", * filter: Picks.PICK_LOCAL_ENTITIES | Picks.PICK_DOMAIN_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE, * renderStates: renderStates, * defaultRenderStates: defaultRenderStates, - * distanceScaleEnd: true, - * triggers: [ {action: Controller.Standard.LTClick, button: "Focus"}, {action: Controller.Standard.LTClick, button: "Primary"} ], - * hover: true, + * hover: true, // Generate hover events. + * triggers: [ + * { action: Controller.Standard.LTClick, button: "Primary" }, // Generate mouse events. + * { action: Controller.Standard.LTClick, button: "Focus" } // Focus on web entities. + * ], * enabled: true * }); - * Pointers.setRenderState(pointer, "test"); + * Pointers.setRenderState(rayPointer, "example"); + * + * // Hover events. + * Entities.hoverEnterEntity.connect(function (entityID, event) { + * print("hoverEnterEntity() : " + entityID); + * }); + * Entities.hoverLeaveEntity.connect(function (entityID, event) { + * print("hoverLeaveEntity() : " + entityID); + * }); + * + * // Mouse events. + * Entities.mousePressOnEntity.connect(function (entityID, event) { + * print("mousePressOnEntity() : " + entityID + " , " + event.button); + * }); + * Entities.mouseReleaseOnEntity.connect(function (entityID, event) { + * print("mouseReleaseOnEntity() : " + entityID + " , " + event.button); + * }); + * + * // Tidy up. + * Script.scriptEnding.connect(function () { + * Pointers.removePointer(rayPointer); + * }); */ // TODO: expand Pointers to be able to be fully configurable with PickFilters Q_INVOKABLE unsigned int createPointer(const PickQuery::PickType& type, const QVariant& properties); /**jsdoc - * Enables a Pointer. + * Enables and shows a pointer. Enabled pointers update their pick results and generate events. * @function Pointers.enablePointer - * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. + * @param {number} id - The ID of the pointer. */ Q_INVOKABLE void enablePointer(unsigned int uid) const { DependencyManager::get()->enablePointer(uid); } /**jsdoc - * Disables a Pointer. + * Disables and hides a pointer. Disabled pointers do not update their pick results or generate events. * @function Pointers.disablePointer - * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. + * @param {number} id - The ID of the pointer. */ Q_INVOKABLE void disablePointer(unsigned int uid) const { DependencyManager::get()->disablePointer(uid); } /**jsdoc - * Removes a Pointer. + * Removes (deletes) a pointer. * @function Pointers.removePointer - * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. + * @param {number} id - The ID of the pointer. */ Q_INVOKABLE void removePointer(unsigned int uid) const { DependencyManager::get()->removePointer(uid); } /**jsdoc - * Edit some visual aspect of a Pointer. Currently only supported for Ray Pointers. + * Edits a render state of a {@link Pointers.RayPointerProperties|ray} or + * {@link Pointers.ParabolaPointerProperties|parabola} pointer, to change its visual appearance for the state when the + * pointer is intersecting something. + *

Note: You can only edit the properties of the existing parts of the pointer; you cannot change the + * type of any part.

+ *

Note: You cannot use this method to change the appearance of a default render state.

+ *

Note: Not able to be used with stylus pointers.

* @function Pointers.editRenderState - * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. - * @param {string} renderState The name of the render state you want to edit. - * @param {Pointers.RayPointerRenderState} properties The new properties for renderStates item. + * @param {number} id - The ID of the pointer. + * @param {string} renderState - The name of the render state to edit. + * @param {Pointers.RayPointerRenderState|Pointers.ParabolaPointerRenderState} properties - The new properties for the + * render state. Only the overlay properties to change need be specified. + * @example Change the dimensions of a ray pointer's intersecting end overlay. + * var intersectEnd = { + * type: "sphere", + * dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + * solid: true, + * color: { red: 0, green: 255, blue: 0 }, + * ignorePickIntersection: true + * }; + * var intersectedPath = { + * type: "line3d", + * color: { red: 0, green: 255, blue: 0 }, + * }; + * var searchEnd = { + * type: "sphere", + * dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + * solid: true, + * color: { red: 255, green: 0, blue: 0 }, + * ignorePickIntersection: true + * }; + * var searchPath = { + * type: "line3d", + * color: { red: 255, green: 0, blue: 0 }, + * }; + * + * var renderStates = [ { name: "example", path: intersectedPath, end: intersectEnd } ]; + * var defaultRenderStates = [ { name: "example", distance: 20.0, path: searchPath, end: searchEnd } ]; + * + * // Create the pointer. + * var rayPointer = Pointers.createPointer(PickType.Ray, { + * joint: "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", + * filter: Picks.PICK_LOCAL_ENTITIES | Picks.PICK_DOMAIN_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE, + * renderStates: renderStates, + * defaultRenderStates: defaultRenderStates, + * enabled: true + * }); + * Pointers.setRenderState(rayPointer, "example"); + * + * // Edit the intersecting render state. + * Script.setTimeout(function () { + * print("Edit render state"); + * Pointers.editRenderState(rayPointer, "example", { + * end: { dimensions: { x: 0.5, y: 0.5, z: 0.5 } } + * }); + * }, 10000); + * + * Script.setTimeout(function () { + * print("Edit render state"); + * Pointers.editRenderState(rayPointer, "example", { + * end: { dimensions: { x: 0.2, y: 0.2, z: 0.2 } } + * }); + * }, 15000); + * + * // Tidy up. + * Script.scriptEnding.connect(function () { + * Pointers.removePointer(rayPointer); + * }); */ Q_INVOKABLE void editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const; /**jsdoc - * Set the render state of a Pointer. For Ray Pointers, this means switching between their {@link Pointers.RayPointerRenderState}s, or "" to turn off rendering and hover/trigger events. - * For Stylus Pointers, there are three built-in options: "events on" (render and send events, the default), "events off" (render but don't send events), and "disabled" (don't render, don't send events). + * Sets the render state of a pointer, to change its visual appearance and possibly disable or enable it. * @function Pointers.setRenderState - * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. - * @param {string} renderState The name of the render state to which you want to switch. + * @param {number} id - The ID of the pointer. + * @param {string} renderState -

The name of the render state to set the pointer to.

+ *

For {@link Pointers.RayPointerProperties|ray} and {@link Pointers.ParabolaPointerProperties|parabola} pointers, + * this may be:

+ *
    + *
  • The name of one of the render states set in the pointer's properties.
  • + *
  • "", to hide the pointer and disable emitting of events.
  • + *
+ *

For {@link Pointers.StylusPointerProperties|stylus} pointers, the values may be:

+ *
    + *
  • "events on", to render and emit events (the default).
  • + *
  • "events off", to render but don't emit events.
  • + *
  • "disabled", to not render and not emit events.
  • + *
+ * @example Switch a ray pointer between having a path and not having a path. + * var intersectEnd = { + * type: "sphere", + * dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + * solid: true, + * color: { red: 0, green: 255, blue: 0 }, + * ignorePickIntersection: true + * }; + * var intersectedPath = { + * type: "line3d", + * color: { red: 0, green: 255, blue: 0 }, + * }; + * var searchEnd = { + * type: "sphere", + * dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + * solid: true, + * color: { red: 255, green: 0, blue: 0 }, + * ignorePickIntersection: true + * }; + * var searchPath = { + * type: "line3d", + * color: { red: 255, green: 0, blue: 0 }, + * }; + * + * var renderStates = [ + * { name: "examplePath", path: intersectedPath, end: intersectEnd }, + * { name: "exampleNoPath", end: intersectEnd } + * ]; + * var defaultRenderStates = [ + * { name: "examplePath", distance: 20.0, path: searchPath, end: searchEnd }, + * { name: "exampleNoPath", distance: 20.0, end: searchEnd } + * ]; + * + * // Create the pointer. + * var rayPointer = Pointers.createPointer(PickType.Ray, { + * joint: "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", + * filter: Picks.PICK_LOCAL_ENTITIES | Picks.PICK_DOMAIN_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE, + * renderStates: renderStates, + * defaultRenderStates: defaultRenderStates, + * enabled: true + * }); + * Pointers.setRenderState(rayPointer, "examplePath"); + * + * // Change states. + * Script.setTimeout(function () { + * print("Without path"); + * Pointers.setRenderState(rayPointer, "exampleNoPath"); + * }, 10000); + * + * Script.setTimeout(function () { + * print("With path"); + * Pointers.setRenderState(rayPointer, "examplePath"); + * }, 15000); + * + * // Tidy up. + * Script.scriptEnding.connect(function () { + * Pointers.removePointer(rayPointer); + * }); */ Q_INVOKABLE void setRenderState(unsigned int uid, const QString& renderState) const { DependencyManager::get()->setRenderState(uid, renderState.toStdString()); } /**jsdoc - * Get the most recent pick result from this Pointer. This will be updated as long as the Pointer is enabled, regardless of the render state. + * Gets the most recent intersection of a pointer. A pointer continues to be updated ready to return a result, as long as + * it is enabled, regardless of the render state. * @function Pointers.getPrevPickResult - * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. - * @returns {RayPickResult|StylusPickResult} The most recent intersection result. This will be slightly different for different PickTypes. + * @param {number} id - The ID of the pointer. + * @returns {RayPickResult|ParabolaPickResult|StylusPickResult} The most recent intersection of the pointer. */ Q_INVOKABLE QVariantMap getPrevPickResult(unsigned int uid) const; /**jsdoc - * Sets whether or not to use precision picking. + * Sets whether or not a pointer should use precision picking, i.e., whether it should pick against precise meshes or + * coarse meshes. This has the same effect as using the PICK_PRECISE or PICK_COARSE filter flags. * @function Pointers.setPrecisionPicking - * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. - * @param {boolean} precisionPicking Whether or not to use precision picking + * @param {number} id - The ID of the pointer. + * @param {boolean} precisionPicking - true to use precision picking, false to use coarse picking. */ Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking) const { DependencyManager::get()->setPrecisionPicking(uid, precisionPicking); } /**jsdoc - * Sets the length of this Pointer. No effect on Stylus Pointers. + * Sets the length of a pointer. + *

Note: Not used by stylus pointers.

* @function Pointers.setLength - * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. - * @param {number} length The desired length of the Pointer. + * @param {number} id - The ID of the pointer. + * @param {number} length - The desired length of the pointer. */ Q_INVOKABLE void setLength(unsigned int uid, float length) const { DependencyManager::get()->setLength(uid, length); } /**jsdoc - * Sets a list of Entity IDs and/or Avatar IDs to ignore during intersection. Not used by Stylus Pointers. + * Sets a list of entity and avatar IDs that a pointer should ignore during intersection. + *

Note: Not used by stylus pointers.

* @function Pointers.setIgnoreItems - * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. - * @param {Uuid[]} ignoreItems A list of IDs to ignore. + * @param {number} id - The ID of the pointer. + * @param {Uuid[]} ignoreItems - A list of IDs to ignore. */ Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities) const; /**jsdoc - * Sets a list of Entity IDs and/or Avatar IDs to include during intersection, instead of intersecting with everything. Stylus - * Pointers only intersect with objects in their include list. + * Sets a list of entity and avatar IDs that a pointer should include during intersection, instead of intersecting with + * everything. + *

Note: Stylus pointers only intersect with items in their include list.

* @function Pointers.setIncludeItems - * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. - * @param {Uuid[]} includeItems A list of IDs to include. + * @param {number} id - The ID of the pointer. + * @param {Uuid[]} includeItems - A list of IDs to include. */ Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities) const; /**jsdoc - * Lock a Pointer onto a specific object (entity or avatar). Optionally, provide an offset in object-space, otherwise the Pointer will lock on to the center of the object. - * Not used by Stylus Pointers. + * Locks a pointer onto a specific entity or avatar. + *

Note: Not used by stylus pointers.

* @function Pointers.setLockEndUUID - * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. - * @param {Uuid} objectID The ID of the object to which to lock on. - * @param {boolean} isAvatar False for entities, true for avatars - * @param {Mat4} [offsetMat] The offset matrix to use if you do not want to lock on to the center of the object. + * @param {number} id - The ID of the pointer. + * @param {Uuid} targetID - The ID of the entity or avatar to lock the pointer on to. + * @param {boolean} isAvatar - true if the target is an avatar, false if it is an entity. + * @param {Mat4} [offset] - The offset of the target point from the center of the target item. If not specified, the + * pointer locks on to the center of the target item. */ Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isAvatar, const glm::mat4& offsetMat = glm::mat4()) const { DependencyManager::get()->setLockEndUUID(uid, objectID, isAvatar, offsetMat); } /**jsdoc - * Check if a Pointer is associated with the left hand. + * Checks if a pointer is associated with the left hand: a ray or parabola pointer with joint property set to + * "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a stylus pointer with + * hand property set to 0. * @function Pointers.isLeftHand - * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. - * @returns {boolean} True if the Pointer is a Joint Ray Pointer with joint == "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a Stylus Pointer with hand == 0 + * @param {number} id - The ID of the pointer. + * @returns {boolean} true if the pointer is associated with the left hand, false if it isn't. */ Q_INVOKABLE bool isLeftHand(unsigned int uid) { return DependencyManager::get()->isLeftHand(uid); } /**jsdoc - * Check if a Pointer is associated with the right hand. + * Checks if a pointer is associated with the right hand: a ray or parabola pointer with joint property set to + * "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a stylus pointer with + * hand property set to 1. * @function Pointers.isRightHand - * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. - * @returns {boolean} True if the Pointer is a Joint Ray Pointer with joint == "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a Stylus Pointer with hand == 1 + * @param {number} id - The ID of the pointer. + * @returns {boolean} true if the pointer is associated with the right hand, false if it isn't. */ Q_INVOKABLE bool isRightHand(unsigned int uid) { return DependencyManager::get()->isRightHand(uid); } /**jsdoc - * Check if a Pointer is associated with the system mouse. + * Checks if a pointer is associated with the system mouse: a ray or parabola pointer with joint property set + * to "Mouse". * @function Pointers.isMouse - * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. - * @returns {boolean} True if the Pointer is a Mouse Ray Pointer, false otherwise. + * @param {number} id - The ID of the pointer. + * @returns {boolean} true if the pointer is associated with the system mouse, false if it isn't. */ Q_INVOKABLE bool isMouse(unsigned int uid) { return DependencyManager::get()->isMouse(uid); } /**jsdoc - * Returns information about an existing Pointer + * Gets information about a pointer. * @function Pointers.getPointerProperties - * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. - * @returns {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties|Pointers.ParabolaPointerProperties} The information about the Pointer. - * Currently only includes renderStates and defaultRenderStates with associated entity IDs. + * @param {number} id - The ID of the pointer. + * @returns {Pointers.RayPointerProperties|Pointers.ParabolaPointerProperties|object} The renderStates and + * defaultRenderStates for ray and parabola pointers, {} for stylus pointers. + * @example Report the properties of a parabola pointer. + * var intersectEnd = { + * type: "sphere", + * dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + * solid: true, + * color: { red: 0, green: 255, blue: 0 }, + * ignorePickIntersection: true + * }; + * var intersectedPath = { + * color: { red: 0, green: 255, blue: 0 }, + * }; + * var searchEnd = { + * type: "sphere", + * dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + * solid: true, + * color: { red: 255, green: 0, blue: 0 }, + * ignorePickIntersection: true + * }; + * var searchPath = { + * color: { red: 255, green: 0, blue: 0 }, + * }; + * + * var renderStates = [{ name: "example", path: intersectedPath, end: intersectEnd }]; + * var defaultRenderStates = [{ name: "example", distance: 20.0, path: searchPath, end: searchEnd }]; + * + * // Create the pointer. + * var parabolaPointer = Pointers.createPointer(PickType.Parabola, { + * joint: "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", + * filter: Picks.PICK_LOCAL_ENTITIES | Picks.PICK_DOMAIN_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE, + * renderStates: renderStates, + * defaultRenderStates: defaultRenderStates, + * enabled: true + * }); + * Pointers.setRenderState(parabolaPointer, "example"); + * + * // Report the pointer properties. + * Script.setTimeout(function () { + * var properties = Pointers.getPointerProperties(parabolaPointer); + * print("Pointer properties:" + JSON.stringify(properties)); + * }, 500); + * + * // Tidy up. + * Script.scriptEnding.connect(function () { + * Pointers.removePointer(parabolaPointer); + * }); */ Q_INVOKABLE QVariantMap getPointerProperties(unsigned int uid) const; }; diff --git a/interface/src/raypick/RayPickScriptingInterface.h b/interface/src/raypick/RayPickScriptingInterface.h index 32a2ec4a5d..2a00847510 100644 --- a/interface/src/raypick/RayPickScriptingInterface.h +++ b/interface/src/raypick/RayPickScriptingInterface.h @@ -107,7 +107,8 @@ public: /**jsdoc - * Sets whether or not to use precision picking, i.e., whether to pick against precise meshes or coarse meshes. + * Sets whether or not a ray pick should use precision picking, i.e., whether it should pick against precise meshes or + * coarse meshes. * @function RayPick.setPrecisionPicking * @param {number} id - The ID of the ray pick. * @param {boolean} precisionPicking - true to use precision picking, false to use coarse picking. @@ -115,7 +116,7 @@ public: Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking); /**jsdoc - * Sets a list of entity and avatar IDs to ignore during intersection. + * Sets a list of entity and avatar IDs that a ray pick should ignore during intersection. * @function RayPick.setIgnoreItems * @param {number} id - The ID of the ray pick. * @param {Uuid[]} ignoreItems - The list of IDs to ignore. @@ -123,7 +124,8 @@ public: Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities); /**jsdoc - * Sets a list of entity IDs and/or avatar IDs to include during intersection, instead of intersecting with everything. + * Sets a list of entity and avatar IDs that a ray pick should include during intersection, instead of intersecting with + * everything. * @function RayPick.setIncludeItems * @param {number} id - The ID of the ray pick. * @param {Uuid[]} includeItems - The list of IDs to include. @@ -132,9 +134,9 @@ public: /**jsdoc - * Checks if a pick is associated with the left hand: a ray or parabola pick with joint set to - * "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a stylus pick with hand - * set to 0. + * Checks if a pick is associated with the left hand: a ray or parabola pick with joint property set to + * "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a stylus pick with + * hand property set to 0. * @function RayPick.isLeftHand * @param {number} id - The ID of the ray pick. * @returns {boolean} true if the pick is associated with the left hand, false if it isn't. @@ -142,9 +144,9 @@ public: Q_INVOKABLE bool isLeftHand(unsigned int uid); /**jsdoc - * Checks if a pick is associated with the right hand: a ray or parabola pick with joint set to - * "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a stylus pick with hand - * set to 1. + * Checks if a pick is associated with the right hand: a ray or parabola pick with joint property set to + * "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a stylus pick with + * hand property set to 1. * @function RayPick.isRightHand * @param {number} id - The ID of the ray pick. * @returns {boolean} true if the pick is associated with the right hand, false if it isn't. @@ -152,7 +154,8 @@ public: Q_INVOKABLE bool isRightHand(unsigned int uid); /**jsdoc - * Checks if a pick is associated with the system mouse: a ray or parabola pick with joint set to "Mouse". + * Checks if a pick is associated with the system mouse: a ray or parabola pick with joint property set to + * "Mouse". * @function RayPick.isMouse * @param {number} id - The ID of the ray pick. * @returns {boolean} true if the pick is associated with the system mouse, false if it isn't. diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.cpp b/interface/src/scripting/PlatformInfoScriptingInterface.cpp index 84c4d923d0..cbd94b3dd5 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.cpp +++ b/interface/src/scripting/PlatformInfoScriptingInterface.cpp @@ -167,7 +167,9 @@ bool PlatformInfoScriptingInterface::isStandalone() { int PlatformInfoScriptingInterface::getNumCPUs() { return platform::getNumCPUs(); } - +int PlatformInfoScriptingInterface::getMasterCPU() { + return platform::getMasterCPU(); +} QString PlatformInfoScriptingInterface::getCPU(int index) { auto desc = platform::getCPU(index); return QString(desc.dump().c_str()); @@ -176,7 +178,9 @@ QString PlatformInfoScriptingInterface::getCPU(int index) { int PlatformInfoScriptingInterface::getNumGPUs() { return platform::getNumGPUs(); } - +int PlatformInfoScriptingInterface::getMasterGPU() { + return platform::getMasterGPU(); +} QString PlatformInfoScriptingInterface::getGPU(int index) { auto desc = platform::getGPU(index); return QString(desc.dump().c_str()); @@ -185,7 +189,9 @@ QString PlatformInfoScriptingInterface::getGPU(int index) { int PlatformInfoScriptingInterface::getNumDisplays() { return platform::getNumDisplays(); } - +int PlatformInfoScriptingInterface::getMasterDisplay() { + return platform::getMasterDisplay(); +} QString PlatformInfoScriptingInterface::getDisplay(int index) { auto desc = platform::getDisplay(index); return QString(desc.dump().c_str()); diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.h b/interface/src/scripting/PlatformInfoScriptingInterface.h index 113509d6d9..9ac67ec0bd 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.h +++ b/interface/src/scripting/PlatformInfoScriptingInterface.h @@ -94,8 +94,8 @@ public slots: * @returns {string} The graphics card type. * @deprecated This function is deprecated and will be removed. * use getNumGPUs() to know the number of GPUs in the hardware, at least one is expected - * use getGPU(0)["vendor"] to get the brand of the vendor - * use getGPU(0)["model"] to get the model name of the gpu + * use getGPU(getMasterGPU())["vendor"] to get the brand of the vendor + * use getGPU(getMasterGPU())["model"] to get the model name of the gpu */ QString getGraphicsCardType(); @@ -135,6 +135,13 @@ public slots: */ int getNumCPUs(); + /**jsdoc + * Get the index of the master CPU. + * @function PlatformInfo.getMasterCPU + * @returns {number} The index of the master CPU detected on the hardware platform. + */ + int getMasterCPU(); + /**jsdoc * Get the description of the CPU at the index parameter * expected fields are: @@ -152,6 +159,13 @@ public slots: */ int getNumGPUs(); + /**jsdoc + * Get the index of the master GPU. + * @function PlatformInfo.getMasterGPU + * @returns {number} The index of the master GPU detected on the hardware platform. + */ + int getMasterGPU(); + /**jsdoc * Get the description of the GPU at the index parameter * expected fields are: @@ -169,6 +183,13 @@ public slots: */ int getNumDisplays(); + /**jsdoc + * Get the index of the master Display. + * @function PlatformInfo.getMasterDisplay + * @returns {number} The index of the master Display detected on the hardware platform. + */ + int getMasterDisplay(); + /**jsdoc * Get the description of the Display at the index parameter * expected fields are: diff --git a/interface/src/ui/AnimStats.cpp b/interface/src/ui/AnimStats.cpp index d4696a8c04..47116ea281 100644 --- a/interface/src/ui/AnimStats.cpp +++ b/interface/src/ui/AnimStats.cpp @@ -132,7 +132,7 @@ void AnimStats::updateStats(bool force) { if (type == AnimNodeType::Clip) { // figure out the grayScale color of this line. - const float LIT_TIME = 2.0f; + const float LIT_TIME = 20.0f; const float FADE_OUT_TIME = 1.0f; float grayScale = 0.0f; float secondsElapsed = (float)(now - _animAlphaValueChangedTimers[key]) / (float)USECS_PER_SECOND; @@ -176,7 +176,7 @@ void AnimStats::updateStats(bool force) { } // figure out the grayScale color of this line. - const float LIT_TIME = 2.0f; + const float LIT_TIME = 20.0f; const float FADE_OUT_TIME = 0.5f; float grayScale = 0.0f; float secondsElapsed = (float)(now - _animVarChangedTimers[key]) / (float)USECS_PER_SECOND; diff --git a/launchers/win32/LauncherApp.cpp b/launchers/win32/LauncherApp.cpp index 244d618fcb..c15ba75a9b 100644 --- a/launchers/win32/LauncherApp.cpp +++ b/launchers/win32/LauncherApp.cpp @@ -32,6 +32,11 @@ CLauncherApp theApp; // CLauncherApp initialization BOOL CLauncherApp::InitInstance() { + // Close interface if is running + int interfacePID = -1; + if (LauncherUtils::isProcessRunning(L"interface.exe", interfacePID)) { + LauncherUtils::shutdownProcess(interfacePID, 0); + } int iNumOfArgs; LPWSTR* pArgs = CommandLineToArgvW(GetCommandLine(), &iNumOfArgs); bool isUninstalling = false; diff --git a/launchers/win32/LauncherDlg.cpp b/launchers/win32/LauncherDlg.cpp index 6016424563..5c69add723 100644 --- a/launchers/win32/LauncherDlg.cpp +++ b/launchers/win32/LauncherDlg.cpp @@ -119,15 +119,22 @@ BOOL CLauncherDlg::OnInitDialog() { return TRUE; } +POINT CLauncherDlg::getMouseCoords(MSG* pMsg) { + POINT pos; + pos.x = (int)(short)LOWORD(pMsg->lParam); + pos.y = (int)(short)HIWORD(pMsg->lParam); + return pos; +} + BOOL CLauncherDlg::PreTranslateMessage(MSG* pMsg) { - if ((pMsg->message == WM_KEYDOWN)) - { + switch (pMsg->message) { + case WM_KEYDOWN: if (pMsg->wParam == 'A' && GetKeyState(VK_CONTROL) < 0) { CWnd* wnd = GetFocus(); CWnd* myWnd = this->GetDlgItem(IDC_ORGNAME); if (wnd && (wnd == this->GetDlgItem(IDC_ORGNAME) || - wnd == this->GetDlgItem(IDC_USERNAME) || - wnd == this->GetDlgItem(IDC_PASSWORD))) { + wnd == this->GetDlgItem(IDC_USERNAME) || + wnd == this->GetDlgItem(IDC_PASSWORD))) { ((CEdit*)wnd)->SetSel(0, -1); } return TRUE; @@ -135,6 +142,33 @@ BOOL CLauncherDlg::PreTranslateMessage(MSG* pMsg) { OnNextClicked(); return TRUE; } + break; + case WM_LBUTTONDOWN: + if (pMsg->hwnd == GetSafeHwnd()) { + _draggingWindow = true; + _dragOffset = getMouseCoords(pMsg); + SetCapture(); + } + break; + case WM_LBUTTONUP: + if (_draggingWindow) { + ReleaseCapture(); + _draggingWindow = false; + } + break; + case WM_MOUSEMOVE: + if (_draggingWindow) { + POINT pos = getMouseCoords(pMsg); + RECT windowRect; + GetWindowRect(&windowRect); + int width = windowRect.right - windowRect.left; + int height = windowRect.bottom - windowRect.top; + ClientToScreen(&pos); + MoveWindow(pos.x - _dragOffset.x, pos.y - _dragOffset.y, width, height, FALSE); + } + break; + default: + break; } return CDialog::PreTranslateMessage(pMsg); } @@ -174,19 +208,11 @@ void CLauncherDlg::startProcess() { theApp._manager.addToLog(_T("Starting Process Setup")); setDrawDialog(DrawStep::DrawProcessSetup); } - theApp._manager.addToLog(_T("Deleting directories before install")); - - CString installDir; - theApp._manager.getAndCreatePaths(LauncherManager::PathType::Interface_Directory, installDir); + theApp._manager.addToLog(_T("Deleting download directory")); CString downloadDir; theApp._manager.getAndCreatePaths(LauncherManager::PathType::Download_Directory, downloadDir); - - LauncherUtils::deleteDirectoriesOnThread(installDir, downloadDir, [&](int error) { - LauncherUtils::DeleteDirError deleteError = (LauncherUtils::DeleteDirError)error; - switch(error) { - case LauncherUtils::DeleteDirError::NoErrorDeleting: - theApp._manager.addToLog(_T("Install directory deleted.")); - theApp._manager.addToLog(_T("Downloads directory deleted.")); + LauncherUtils::deleteDirectoryOnThread(downloadDir, [&](bool error) { + if (!error) { if (!theApp._manager.isLoggedIn()) { theApp._manager.addToLog(_T("Downloading Content")); theApp._manager.downloadContent(); @@ -194,23 +220,12 @@ void CLauncherDlg::startProcess() { theApp._manager.addToLog(_T("Downloading App")); theApp._manager.downloadApplication(); } - break; - case LauncherUtils::DeleteDirError::ErrorDeletingBothDirs: - theApp._manager.addToLog(_T("Error deleting directories.")); - break; - case LauncherUtils::DeleteDirError::ErrorDeletingApplicationDir: - theApp._manager.addToLog(_T("Error deleting application directory.")); - break; - case LauncherUtils::DeleteDirError::ErrorDeletingDownloadsDir: - theApp._manager.addToLog(_T("Error deleting downloads directory.")); - break; - default: - break; - } - if (error != LauncherUtils::DeleteDirError::NoErrorDeleting) { + } else { + theApp._manager.addToLog(_T("Error deleting download directory.")); theApp._manager.setFailed(true); } }); + } BOOL CLauncherDlg::getHQInfo(const CString& orgname) { @@ -629,7 +644,7 @@ void CLauncherDlg::OnTimer(UINT_PTR nIDEvent) { ::SetForegroundWindow(_applicationWND); ::SetActiveWindow(_applicationWND); } - if (LauncherUtils::IsProcessRunning(L"interface.exe")) { + if (LauncherUtils::isProcessWindowOpened(L"interface.exe")) { exit(0); } } @@ -653,7 +668,7 @@ void CLauncherDlg::OnTimer(UINT_PTR nIDEvent) { } _splashStep++; } else if (theApp._manager.shouldShutDown()) { - if (LauncherUtils::IsProcessRunning(L"interface.exe")) { + if (LauncherUtils::isProcessWindowOpened(L"interface.exe")) { exit(0); } } diff --git a/launchers/win32/LauncherDlg.h b/launchers/win32/LauncherDlg.h index 9d34fe5503..4d830c2e21 100644 --- a/launchers/win32/LauncherDlg.h +++ b/launchers/win32/LauncherDlg.h @@ -60,12 +60,18 @@ protected: DrawStep _drawStep { DrawStep::DrawLogo }; BOOL getTextFormat(int ResID, TextFormat& formatOut); void showWindows(std::vector windows, bool show); + POINT getMouseCoords(MSG* pMsg); - bool _isConsoleRunning{ false }; - bool _isInstalling{ false }; - bool _isFirstDraw{ false }; - bool _showSplash{ true }; - int _splashStep{ 0 }; + + bool _isConsoleRunning { false }; + bool _isInstalling { false }; + bool _isFirstDraw { false }; + bool _showSplash { true }; + + bool _draggingWindow { false }; + POINT _dragOffset; + + int _splashStep { 0 }; float _logoRotation { 0.0f }; HICON m_hIcon; diff --git a/launchers/win32/LauncherManager.cpp b/launchers/win32/LauncherManager.cpp index 2916f614cb..146e871028 100644 --- a/launchers/win32/LauncherManager.cpp +++ b/launchers/win32/LauncherManager.cpp @@ -15,13 +15,10 @@ #include "LauncherManager.h" -LauncherManager::LauncherManager() -{ +LauncherManager::LauncherManager() { } - -LauncherManager::~LauncherManager() -{ +LauncherManager::~LauncherManager() { } void LauncherManager::init() { @@ -41,6 +38,9 @@ void LauncherManager::init() { addToLog(_T("New build found. Updating")); _shouldUpdate = TRUE; } + } else if (_loggedIn) { + addToLog(_T("Interface not found but logged in. Reinstalling")); + _shouldUpdate = TRUE; } } else { _hasFailed = true; @@ -113,8 +113,11 @@ BOOL LauncherManager::installLauncher() { // The installer is not running on the desired location and has to be installed // Kill of running before self-copy addToLog(_T("Installing Launcher.")); - if (LauncherUtils::IsProcessRunning(LAUNCHER_EXE_FILENAME)) { - ShellExecute(NULL, NULL, L"taskkill", L"/F /T /IM " + LAUNCHER_EXE_FILENAME, NULL, SW_HIDE); + int launcherPID = -1; + if (LauncherUtils::isProcessRunning(LAUNCHER_EXE_FILENAME, launcherPID)) { + if (!LauncherUtils::shutdownProcess(launcherPID, 0)) { + addToLog(_T("Error shutting down the Launcher")); + } } CopyFile(appPath, instalationPath, FALSE); } @@ -151,7 +154,7 @@ BOOL LauncherManager::createShortcuts() { CString installDir; getAndCreatePaths(PathType::Launcher_Directory, installDir); CString installPath = installDir + LAUNCHER_EXE_FILENAME; - if (!LauncherUtils::CreateLink(installPath, (LPCSTR)CStringA(desktopLnkPath), _T("CLick to Setup and Launch HQ."))) { + if (!LauncherUtils::createLink(installPath, (LPCSTR)CStringA(desktopLnkPath), _T("CLick to Setup and Launch HQ."))) { return FALSE; } CString startLinkPath; @@ -159,13 +162,13 @@ BOOL LauncherManager::createShortcuts() { CString appStartLinkPath = startLinkPath + _T("HQ Launcher.lnk"); CString uniStartLinkPath = startLinkPath + _T("Uninstall HQ.lnk"); CString uniLinkPath = installDir + _T("Uninstall HQ.lnk"); - if (!LauncherUtils::CreateLink(installPath, (LPCSTR)CStringA(appStartLinkPath), _T("CLick to Setup and Launch HQ."))) { + if (!LauncherUtils::createLink(installPath, (LPCSTR)CStringA(appStartLinkPath), _T("CLick to Setup and Launch HQ."))) { return FALSE; } - if (!LauncherUtils::CreateLink(installPath, (LPCSTR)CStringA(uniStartLinkPath), _T("CLick to Uninstall HQ."), _T("--uninstall"))) { + if (!LauncherUtils::createLink(installPath, (LPCSTR)CStringA(uniStartLinkPath), _T("CLick to Uninstall HQ."), _T("--uninstall"))) { return FALSE; } - if (!LauncherUtils::CreateLink(installPath, (LPCSTR)CStringA(uniLinkPath), _T("CLick to Uninstall HQ."), _T("--uninstall"))) { + if (!LauncherUtils::createLink(installPath, (LPCSTR)CStringA(uniLinkPath), _T("CLick to Uninstall HQ."), _T("--uninstall"))) { return FALSE; } return TRUE; @@ -189,9 +192,9 @@ BOOL LauncherManager::isApplicationInstalled(CString& version, CString& domain, CString applicationPath = applicationDir + "interface\\interface.exe"; BOOL isApplicationInstalled = PathFileExistsW(applicationPath); BOOL configFileExist = PathFileExistsW(applicationDir + _T("interface\\config.json")); - if (isApplicationInstalled && configFileExist) { + if (configFileExist) { LauncherUtils::ResponseError status = readConfigJSON(version, domain, content, loggedIn); - return status == LauncherUtils::ResponseError::NoError; + return isApplicationInstalled && status == LauncherUtils::ResponseError::NoError; } return FALSE; } @@ -308,7 +311,8 @@ LauncherUtils::ResponseError LauncherManager::readConfigJSON(CString& version, C } Json::Value config; configFile >> config; - if (config["version"].isString() && config["domain"].isString() && + if (config["version"].isString() && + config["domain"].isString() && config["content"].isString()) { loggedIn = config["loggedIn"].asBool(); version = config["version"].asCString(); @@ -440,10 +444,6 @@ void LauncherManager::onZipExtracted(ZipType type, int size) { downloadApplication(); } else if (type == ZipType::ZipApplication) { createShortcuts(); - CString versionPath; - getAndCreatePaths(LauncherManager::PathType::Launcher_Directory, versionPath); - addToLog(_T("Creating config.json")); - createConfigJSON(); addToLog(_T("Launching application.")); _shouldLaunch = TRUE; if (!_shouldUpdate) { @@ -457,6 +457,8 @@ void LauncherManager::onZipExtracted(ZipType type, int size) { BOOL LauncherManager::extractApplication() { CString installPath; getAndCreatePaths(LauncherManager::PathType::Interface_Directory, installPath); + addToLog(_T("Creating config.json")); + createConfigJSON(); BOOL success = LauncherUtils::unzipFileOnThread(ZipType::ZipApplication, LauncherUtils::cStringToStd(_applicationZipPath), LauncherUtils::cStringToStd(installPath), [&](int type, int size) { if (size > 0) { @@ -476,11 +478,31 @@ BOOL LauncherManager::extractApplication() { void LauncherManager::onFileDownloaded(DownloadType type) { if (type == DownloadType::DownloadContent) { - addToLog(_T("Installing content.")); - installContent(); + addToLog(_T("Deleting content directory before install")); + CString contentDir; + getAndCreatePaths(PathType::Content_Directory, contentDir); + LauncherUtils::deleteDirectoryOnThread(contentDir, [&](bool error) { + if (!error) { + addToLog(_T("Installing content.")); + installContent(); + } else { + addToLog(_T("Error deleting content directory.")); + setFailed(true); + } + }); } else if (type == DownloadType::DownloadApplication) { - addToLog(_T("Installing application.")); - extractApplication(); + addToLog(_T("Deleting application directory before install")); + CString applicationDir; + getAndCreatePaths(PathType::Interface_Directory, applicationDir); + LauncherUtils::deleteDirectoryOnThread(applicationDir, [&](bool error) { + if (!error) { + addToLog(_T("Installing application.")); + extractApplication(); + } else { + addToLog(_T("Error deleting install directory.")); + setFailed(true); + } + }); } } diff --git a/launchers/win32/LauncherUtils.cpp b/launchers/win32/LauncherUtils.cpp index 9365dd52a1..094ab09307 100644 --- a/launchers/win32/LauncherUtils.cpp +++ b/launchers/win32/LauncherUtils.cpp @@ -37,17 +37,53 @@ CString LauncherUtils::urlEncodeString(const CString& url) { return stringOut; } -BOOL LauncherUtils::IsProcessRunning(const wchar_t *processName) { +BOOL LauncherUtils::shutdownProcess(DWORD dwProcessId, UINT uExitCode) { + DWORD dwDesiredAccess = PROCESS_TERMINATE; + BOOL bInheritHandle = FALSE; + HANDLE hProcess = OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId); + if (hProcess == NULL) { + return FALSE; + } + BOOL result = TerminateProcess(hProcess, uExitCode); + CloseHandle(hProcess); + return result; +} + +BOOL CALLBACK LauncherUtils::isWindowOpenedCallback(HWND hWnd, LPARAM lparam) { + ProcessData* processData = reinterpret_cast(lparam); + if (processData) { + DWORD idptr; + GetWindowThreadProcessId(hWnd, &idptr); + if (idptr && (int)(idptr) == processData->processID) { + processData->isOpened = IsWindowVisible(hWnd); + return FALSE; + } + } + return TRUE; +} + +BOOL LauncherUtils::isProcessWindowOpened(const wchar_t *processName) { + ProcessData processData; + BOOL result = isProcessRunning(processName, processData.processID); + if (result) { + EnumWindows(LauncherUtils::isWindowOpenedCallback, reinterpret_cast(&processData)); + return processData.isOpened; + } + return result; +} + +BOOL LauncherUtils::isProcessRunning(const wchar_t *processName, int& processID) { bool exists = false; PROCESSENTRY32 entry; entry.dwSize = sizeof(PROCESSENTRY32); HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); - + if (Process32First(snapshot, &entry)) { while (Process32Next(snapshot, &entry)) { if (!_wcsicmp(entry.szExeFile, processName)) { exists = true; + processID = entry.th32ProcessID; break; } } @@ -56,7 +92,7 @@ BOOL LauncherUtils::IsProcessRunning(const wchar_t *processName) { return exists; } -HRESULT LauncherUtils::CreateLink(LPCWSTR lpszPathObj, LPCSTR lpszPathLink, LPCWSTR lpszDesc, LPCWSTR lpszArgs) { +HRESULT LauncherUtils::createLink(LPCWSTR lpszPathObj, LPCSTR lpszPathLink, LPCWSTR lpszDesc, LPCWSTR lpszArgs) { IShellLink* psl; // Get a pointer to the IShellLink interface. It is assumed that CoInitialize @@ -444,16 +480,10 @@ DWORD WINAPI LauncherUtils::downloadThread(LPVOID lpParameter) { return 0; } -DWORD WINAPI LauncherUtils::deleteDirectoriesThread(LPVOID lpParameter) { +DWORD WINAPI LauncherUtils::deleteDirectoryThread(LPVOID lpParameter) { DeleteThreadData& data = *((DeleteThreadData*)lpParameter); - DeleteDirError error = DeleteDirError::NoErrorDeleting; - if (!LauncherUtils::deleteFileOrDirectory(data._applicationDir)) { - error = DeleteDirError::ErrorDeletingApplicationDir; - } - if (!LauncherUtils::deleteFileOrDirectory(data._downloadsDir)) { - error = error == NoError ? DeleteDirError::ErrorDeletingDownloadsDir : DeleteDirError::ErrorDeletingBothDirs; - } - data.callback(error); + BOOL success = LauncherUtils::deleteFileOrDirectory(data._dirPath); + data.callback(!success); return 0; } @@ -487,15 +517,12 @@ BOOL LauncherUtils::downloadFileOnThread(int type, const CString& url, const CSt return FALSE; } -BOOL LauncherUtils::deleteDirectoriesOnThread(const CString& applicationDir, - const CString& downloadsDir, - std::function callback) { +BOOL LauncherUtils::deleteDirectoryOnThread(const CString& dirPath, std::function callback) { DWORD myThreadID; DeleteThreadData* deleteThreadData = new DeleteThreadData(); - deleteThreadData->_applicationDir = applicationDir; - deleteThreadData->_downloadsDir = downloadsDir; + deleteThreadData->_dirPath = dirPath; deleteThreadData->setCallback(callback); - HANDLE myHandle = CreateThread(0, 0, deleteDirectoriesThread, deleteThreadData, 0, &myThreadID); + HANDLE myHandle = CreateThread(0, 0, deleteDirectoryThread, deleteThreadData, 0, &myThreadID); if (myHandle) { CloseHandle(myHandle); return TRUE; diff --git a/launchers/win32/LauncherUtils.h b/launchers/win32/LauncherUtils.h index a6f124e18d..a373428ad6 100644 --- a/launchers/win32/LauncherUtils.h +++ b/launchers/win32/LauncherUtils.h @@ -30,13 +30,6 @@ public: NoError }; - enum DeleteDirError { - NoErrorDeleting = 0, - ErrorDeletingApplicationDir, - ErrorDeletingDownloadsDir, - ErrorDeletingBothDirs - }; - struct DownloadThreadData { int _type; CString _url; @@ -60,10 +53,14 @@ public: }; struct DeleteThreadData { - CString _applicationDir; - CString _downloadsDir; - std::function callback; - void setCallback(std::function fn) { callback = std::bind(fn, std::placeholders::_1); } + CString _dirPath; + std::function callback; + void setCallback(std::function fn) { callback = std::bind(fn, std::placeholders::_1); } + }; + + struct ProcessData { + int processID = -1; + BOOL isOpened = FALSE; }; static BOOL parseJSON(const CString& jsonTxt, Json::Value& jsonObject); @@ -73,19 +70,20 @@ public: static std::string cStringToStd(CString cstring); static BOOL getFont(const CString& fontName, int fontSize, bool isBold, CFont& fontOut); static BOOL launchApplication(LPCWSTR lpApplicationName, LPTSTR cmdArgs = _T("")); - static BOOL IsProcessRunning(const wchar_t *processName); + static BOOL CALLBACK isWindowOpenedCallback(HWND hWnd, LPARAM lparam); + static BOOL isProcessRunning(const wchar_t *processName, int& processID); + static BOOL isProcessWindowOpened(const wchar_t *processName); + static BOOL shutdownProcess(DWORD dwProcessId, UINT uExitCode); static BOOL insertRegistryKey(const std::string& regPath, const std::string& name, const std::string& value); static BOOL insertRegistryKey(const std::string& regPath, const std::string& name, DWORD value); static BOOL deleteFileOrDirectory(const CString& dirPath, bool noRecycleBin = true); - static HRESULT CreateLink(LPCWSTR lpszPathObj, LPCSTR lpszPathLink, LPCWSTR lpszDesc, LPCWSTR lpszArgs = _T("")); + static HRESULT createLink(LPCWSTR lpszPathObj, LPCSTR lpszPathLink, LPCWSTR lpszDesc, LPCWSTR lpszArgs = _T("")); static BOOL hMac256(const CString& message, const char* key, CString& hashOut); static uint64_t extractZip(const std::string& zipFile, const std::string& path, std::vector& files); static BOOL deleteRegistryKey(const CString& registryPath); static BOOL unzipFileOnThread(int type, const std::string& zipFile, const std::string& path, std::function callback); static BOOL downloadFileOnThread(int type, const CString& url, const CString& file, std::function callback); - static BOOL deleteDirectoriesOnThread(const CString& applicationDir, - const CString& downloadsDir, - std::function callback); + static BOOL deleteDirectoryOnThread(const CString& dirPath, std::function callback); static CString urlEncodeString(const CString& url); static HWND executeOnForeground(const CString& path, const CString& params); @@ -93,5 +91,5 @@ private: // Threads static DWORD WINAPI unzipThread(LPVOID lpParameter); static DWORD WINAPI downloadThread(LPVOID lpParameter); - static DWORD WINAPI deleteDirectoriesThread(LPVOID lpParameter); + static DWORD WINAPI deleteDirectoryThread(LPVOID lpParameter); }; \ No newline at end of file diff --git a/libraries/animation/src/AnimVariant.cpp b/libraries/animation/src/AnimVariant.cpp index cf7228b27b..930dbed2d9 100644 --- a/libraries/animation/src/AnimVariant.cpp +++ b/libraries/animation/src/AnimVariant.cpp @@ -159,15 +159,15 @@ std::map AnimVariantMap::toDebugMap() const { arg(QString::number(value.y, 'f', 3)). arg(QString::number(value.z, 'f', 3)). arg(QString::number(value.w, 'f', 3)); - break; */ + break; } case AnimVariant::Type::String: // To prevent filling up anim stats, don't show string values /* result[pair.first] = pair.second.getString(); - break; */ + break; default: // invalid AnimVariant::Type assert(false); diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index cb14d7ef41..0c7054424f 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -280,6 +280,10 @@ AvatarSharedPointer AvatarHashMap::parseAvatarData(QSharedPointer(); + qCDebug(networking_ice) << "Sending connect request ( REASON:" << QString(metaEnum.valueToKey(_connectReason)) << ") to domain-server at" << hostname; // is this our localhost domain-server? // if so we need to make sure we have an up-to-date local port in case it restarted diff --git a/libraries/platform/src/platform/Platform.h b/libraries/platform/src/platform/Platform.h index 9405c77ae0..8cda6332ee 100644 --- a/libraries/platform/src/platform/Platform.h +++ b/libraries/platform/src/platform/Platform.h @@ -21,12 +21,15 @@ bool enumeratePlatform(); int getNumCPUs(); json getCPU(int index); +int getMasterCPU(); int getNumGPUs(); json getGPU(int index); +int getMasterGPU(); int getNumDisplays(); json getDisplay(int index); +int getMasterDisplay(); json getMemory(); diff --git a/libraries/platform/src/platform/PlatformKeys.h b/libraries/platform/src/platform/PlatformKeys.h index e6c255ce75..5008a4f6ce 100644 --- a/libraries/platform/src/platform/PlatformKeys.h +++ b/libraries/platform/src/platform/PlatformKeys.h @@ -20,6 +20,7 @@ namespace platform { namespace keys{ extern const char* model; extern const char* clockSpeed; extern const char* numCores; + extern const char* isMaster; } namespace gpu { extern const char* vendor; @@ -30,6 +31,8 @@ namespace platform { namespace keys{ extern const char* model; extern const char* videoMemory; extern const char* driver; + extern const char* displays; + extern const char* isMaster; } namespace nic { extern const char* mac; @@ -38,10 +41,19 @@ namespace platform { namespace keys{ namespace display { extern const char* description; extern const char* name; - extern const char* coordsLeft; - extern const char* coordsRight; - extern const char* coordsTop; - extern const char* coordsBottom; + extern const char* boundsLeft; + extern const char* boundsRight; + extern const char* boundsTop; + extern const char* boundsBottom; + extern const char* gpu; + extern const char* ppi; + extern const char* ppiDesktop; + extern const char* physicalWidth; + extern const char* physicalHeight; + extern const char* modeRefreshrate; + extern const char* modeWidth; + extern const char* modeHeight; + extern const char* isMaster; } namespace memory { extern const char* memTotal; diff --git a/libraries/platform/src/platform/Profiler.cpp b/libraries/platform/src/platform/Profiler.cpp index 1c055a5ec9..3e4dff9fd1 100644 --- a/libraries/platform/src/platform/Profiler.cpp +++ b/libraries/platform/src/platform/Profiler.cpp @@ -32,9 +32,9 @@ Profiler::Tier Profiler::profilePlatform() { return platformTier; } - // Not filtered yet, let s try to make sense of the cpu and gpu info - auto cpuInfo = platform::getCPU(0); - auto gpuInfo = platform::getGPU(0); + // Not filtered yet, let s try to make sense of the master cpu and master gpu info + auto cpuInfo = platform::getCPU(platform::getMasterCPU()); + auto gpuInfo = platform::getGPU(platform::getMasterGPU()); if (filterOnProcessors(computerInfo, cpuInfo, gpuInfo, platformTier)) { return platformTier; } @@ -133,10 +133,12 @@ bool filterOnProcessors(const platform::json& computer, const platform::json& cp // YES on macos EXCEPT for macbookair with gpu intel iris or intel HD 6000 bool Profiler::isRenderMethodDeferredCapable() { #if defined(Q_OS_MAC) + // Deferred works correctly on every supported macos platform at the moment, let s enable it +/* auto computer = platform::getComputer(); const auto computerModel = (computer.count(keys::computer::model) ? computer[keys::computer::model].get() : ""); - auto gpuInfo = platform::getGPU(0); + auto gpuInfo = platform::getGPU(getMasterGPU()); const auto gpuModel = (gpuInfo.count(keys::gpu::model) ? gpuInfo[keys::gpu::model].get() : ""); @@ -154,7 +156,7 @@ bool Profiler::isRenderMethodDeferredCapable() { if ((gpuModel.find("Intel ") != std::string::npos)) { return false; } - +*/ return true; #elif defined(Q_OS_ANDROID) return false; diff --git a/libraries/platform/src/platform/backend/AndroidPlatform.cpp b/libraries/platform/src/platform/backend/AndroidPlatform.cpp index b0a4c5e67b..4487d305cf 100644 --- a/libraries/platform/src/platform/backend/AndroidPlatform.cpp +++ b/libraries/platform/src/platform/backend/AndroidPlatform.cpp @@ -10,6 +10,7 @@ #include "../PlatformKeys.h" #include #include +#include using namespace platform; @@ -23,7 +24,7 @@ void AndroidInstance::enumerateCpus() { _cpus.push_back(cpu); } -void AndroidInstance::enumerateGpus() { +void AndroidInstance::enumerateGpusAndDisplays() { GPUIdent* ident = GPUIdent::getInstance(); json gpu = {}; gpu[keys::gpu::model] = ident->getName().toUtf8().constData(); diff --git a/libraries/platform/src/platform/backend/AndroidPlatform.h b/libraries/platform/src/platform/backend/AndroidPlatform.h index 6592b3519d..488f564057 100644 --- a/libraries/platform/src/platform/backend/AndroidPlatform.h +++ b/libraries/platform/src/platform/backend/AndroidPlatform.h @@ -16,7 +16,7 @@ namespace platform { public: void enumerateCpus() override; - void enumerateGpus() override; + void enumerateGpusAndDisplays() override; void enumerateMemory() override; void enumerateComputer() override; }; diff --git a/libraries/platform/src/platform/backend/LinuxPlatform.cpp b/libraries/platform/src/platform/backend/LinuxPlatform.cpp index 61501669cb..eb2c8d4259 100644 --- a/libraries/platform/src/platform/backend/LinuxPlatform.cpp +++ b/libraries/platform/src/platform/backend/LinuxPlatform.cpp @@ -12,6 +12,9 @@ #include #include #include + +#include + #include #include @@ -27,7 +30,7 @@ void LinuxInstance::enumerateCpus() { _cpus.push_back(cpu); } -void LinuxInstance::enumerateGpus() { +void LinuxInstance::enumerateGpusAndDisplays() { GPUIdent* ident = GPUIdent::getInstance(); json gpu = {}; gpu[keys::gpu::model] = ident->getName().toUtf8().constData(); diff --git a/libraries/platform/src/platform/backend/LinuxPlatform.h b/libraries/platform/src/platform/backend/LinuxPlatform.h index 2f2529db7c..0d2e567e0b 100644 --- a/libraries/platform/src/platform/backend/LinuxPlatform.h +++ b/libraries/platform/src/platform/backend/LinuxPlatform.h @@ -16,7 +16,7 @@ namespace platform { public: void enumerateCpus() override; - void enumerateGpus() override; + void enumerateGpusAndDisplays() override; void enumerateMemory() override; void enumerateComputer() override; }; diff --git a/libraries/platform/src/platform/backend/MACOSPlatform.cpp b/libraries/platform/src/platform/backend/MACOSPlatform.cpp index cacbd06816..66c9cd2c5d 100644 --- a/libraries/platform/src/platform/backend/MACOSPlatform.cpp +++ b/libraries/platform/src/platform/backend/MACOSPlatform.cpp @@ -12,16 +12,22 @@ #include #include #include -#include + +#include #ifdef Q_OS_MAC #include #include #include +#include +#include + #include #include #include +#include +#include #endif using namespace platform; @@ -36,73 +42,249 @@ void MACOSInstance::enumerateCpus() { _cpus.push_back(cpu); } -void MACOSInstance::enumerateGpus() { + +void MACOSInstance::enumerateGpusAndDisplays() { #ifdef Q_OS_MAC + // ennumerate the displays first + std::vector displayMasks; + { + uint32_t numDisplays = 0; + CGError error = CGGetOnlineDisplayList(0, nullptr, &numDisplays); + if (numDisplays <= 0 || error != kCGErrorSuccess) { + return; + } - GPUIdent* ident = GPUIdent::getInstance(); - json gpu = {}; + std::vector onlineDisplayIDs(numDisplays, 0); + error = CGGetOnlineDisplayList(onlineDisplayIDs.size(), onlineDisplayIDs.data(), &numDisplays); + if (error != kCGErrorSuccess) { + return; + } - gpu[keys::gpu::model] = ident->getName().toUtf8().constData(); - gpu[keys::gpu::vendor] = findGPUVendorInDescription(gpu[keys::gpu::model].get()); - gpu[keys::gpu::videoMemory] = ident->getMemory(); - gpu[keys::gpu::driver] = ident->getDriver().toUtf8().constData(); - - _gpus.push_back(gpu); + for (auto displayID : onlineDisplayIDs) { + auto displaySize = CGDisplayScreenSize(displayID); + const auto MM_TO_IN = 0.0393701f; + auto displaySizeWidthInches = displaySize.width * MM_TO_IN; + auto displaySizeHeightInches = displaySize.height * MM_TO_IN; + + auto displayBounds = CGDisplayBounds(displayID); + auto displayMaster =CGDisplayIsMain(displayID); + + auto displayUnit =CGDisplayUnitNumber(displayID); + auto displayModel =CGDisplayModelNumber(displayID); + auto displayVendor = CGDisplayVendorNumber(displayID); + auto displaySerial = CGDisplaySerialNumber(displayID); + + auto displayMode = CGDisplayCopyDisplayMode(displayID); + auto displayModeWidth = CGDisplayModeGetPixelWidth(displayMode); + auto displayModeHeight = CGDisplayModeGetPixelHeight(displayMode); + auto displayRefreshrate = CGDisplayModeGetRefreshRate(displayMode); + + auto ppiH = displayModeWidth / displaySizeWidthInches; + auto ppiV = displayModeHeight / displaySizeHeightInches; + + auto ppiHScaled = displayBounds.size.width / displaySizeWidthInches; + auto ppiVScaled = displayBounds.size.height / displaySizeHeightInches; + + auto glmask = CGDisplayIDToOpenGLDisplayMask(displayID); + // Metal device ID is the recommended new way but requires objective c + // auto displayDevice = CGDirectDisplayCopyCurrentMetalDevice(displayID); + + CGDisplayModeRelease(displayMode); + + json display = {}; + + // Rect region of the desktop in desktop units + display[keys::display::boundsLeft] = displayBounds.origin.x; + display[keys::display::boundsRight] = displayBounds.origin.x + displayBounds.size.width; + display[keys::display::boundsTop] = displayBounds.origin.y; + display[keys::display::boundsBottom] = displayBounds.origin.y + displayBounds.size.height; + + // PPI & resolution + display[keys::display::physicalWidth] = displaySizeWidthInches; + display[keys::display::physicalHeight] = displaySizeHeightInches; + display[keys::display::modeWidth] = displayModeWidth; + display[keys::display::modeHeight] = displayModeHeight; + + //Average the ppiH and V for the simple ppi + display[keys::display::ppi] = std::round(0.5f * (ppiH + ppiV)); + display[keys::display::ppiDesktop] = std::round(0.5f * (ppiHScaled + ppiVScaled)); + + // refreshrate + display[keys::display::modeRefreshrate] = displayRefreshrate; + + // Master display ? + display[keys::display::isMaster] = (displayMaster ? true : false); + + // Macos specific + display["macos_unit"] = displayUnit; + display["macos_vendor"] = displayVendor; + display["macos_model"] = displayModel; + display["macos_serial"] = displaySerial; + display["macos_glmask"] = glmask; + displayMasks.push_back(glmask); + + _displays.push_back(display); + } + } -#endif + // Collect Renderer info as exposed by the CGL layers + GLuint cglDisplayMask = -1; // Iterate over all of them. + CGLRendererInfoObj rendererInfo; + GLint rendererInfoCount; + CGLError error = CGLQueryRendererInfo(cglDisplayMask, &rendererInfo, &rendererInfoCount); + if (rendererInfoCount <= 0 || 0 != error) { + return; + } + + // Iterate over all of the renderers and use the figure for the one with the most VRAM, + // on the assumption that this is the one that will actually be used. + for (GLint i = 0; i < rendererInfoCount; i++) { + struct CGLRendererDesc { + int rendererID{0}; + int deviceVRAM{0}; + int accelerated{0}; + int displayMask{0}; + } desc; + + CGLDescribeRenderer(rendererInfo, i, kCGLRPRendererID, &desc.rendererID); + CGLDescribeRenderer(rendererInfo, i, kCGLRPVideoMemoryMegabytes, &desc.deviceVRAM); + CGLDescribeRenderer(rendererInfo, i, kCGLRPDisplayMask, &desc.displayMask); + CGLDescribeRenderer(rendererInfo, i, kCGLRPAccelerated, &desc.accelerated); + + // If this renderer is not hw accelerated then just skip it in the enumeration + if (!desc.accelerated) { + continue; + } + + // From the rendererID identify the vendorID + // https://github.com/apitrace/apitrace/blob/master/retrace/glws_cocoa.mm + GLint vendorID = desc.rendererID & kCGLRendererIDMatchingMask & ~0xfff; + const GLint VENDOR_ID_SOFTWARE { 0x00020000 }; + const GLint VENDOR_ID_AMD { 0x00021000 }; + const GLint VENDOR_ID_NVIDIA { 0x00022000 }; + const GLint VENDOR_ID_INTEL { 0x00024000 }; + + const char* vendorName; + switch (vendorID) { + case VENDOR_ID_SOFTWARE: + // Software renderer then skip it (should already have been caught by hwaccelerated test abve + continue; + break; + case VENDOR_ID_AMD: + vendorName = keys::gpu::vendor_AMD; + break; + case VENDOR_ID_NVIDIA: + vendorName = keys::gpu::vendor_NVIDIA; + break; + case VENDOR_ID_INTEL: + vendorName = keys::gpu::vendor_Intel; + break; + default: + vendorName = keys::UNKNOWN; + break; + } + + //once we reach this point, the renderer is legit + // Start defining the gpu json + json gpu = {}; + gpu[keys::gpu::vendor] = vendorName; + gpu[keys::gpu::videoMemory] = desc.deviceVRAM; + gpu["macos_rendererID"] = desc.rendererID; + gpu["macos_displayMask"] = desc.displayMask; + + std::vector displayIndices; + for (int d = 0; d < (int) displayMasks.size(); d++) { + if (desc.displayMask & displayMasks[d]) { + displayIndices.push_back(d); + _displays[d][keys::display::gpu] = _gpus.size(); + } + } + gpu[keys::gpu::displays] = displayIndices; + + _gpus.push_back(gpu); + } + + CGLDestroyRendererInfo(rendererInfo); + + + { //get gpu information from the system profiler that we don't know how to retreive otherwise + struct ChipsetModelDesc { + std::string model; + std::string vendor; + int deviceID{0}; + int videoMemory{0}; + }; + std::vector chipsetDescs; + FILE* stream = popen("system_profiler SPDisplaysDataType | grep -e Chipset -e VRAM -e Vendor -e \"Device ID\"", "r"); + std::ostringstream hostStream; + while (!feof(stream) && !ferror(stream)) { + char buf[128]; + int bytesRead = fread(buf, 1, 128, stream); + hostStream.write(buf, bytesRead); + } + std::string gpuArrayDesc = hostStream.str(); + + // Find the Chipset model first + const std::regex chipsetModelToken("(Chipset Model: )(.*)"); + std::smatch found; + + while (std::regex_search(gpuArrayDesc, found, chipsetModelToken)) { + ChipsetModelDesc desc; -} - -void MACOSInstance::enumerateDisplays() { -#ifdef Q_OS_MAC - auto displayID = CGMainDisplayID(); - auto displaySize = CGDisplayScreenSize(displayID); - - const auto MM_TO_IN = 0.0393701; - auto displaySizeWidthInches = displaySize.width * MM_TO_IN; - auto displaySizeHeightInches = displaySize.height * MM_TO_IN; - auto displaySizeDiagonalInches = sqrt(displaySizeWidthInches * displaySizeWidthInches + displaySizeHeightInches * displaySizeHeightInches); + desc.model = found.str(2); + + // Find the end of the gpu block + gpuArrayDesc = found.suffix(); + std::string gpuDesc = gpuArrayDesc; + const std::regex endGpuToken("Chipset Model: "); + if (std::regex_search(gpuArrayDesc, found, endGpuToken)) { + gpuDesc = found.prefix(); + } + + // Find the vendor + desc.vendor = findGPUVendorInDescription(desc.model); + + // Find the memory amount in MB + const std::regex memoryToken("(VRAM .*: )(.*)"); + if (std::regex_search(gpuDesc, found, memoryToken)) { + auto memAmountUnit = found.str(2); + std::smatch amount; + const std::regex memAmountGBToken("(\\d*) GB"); + const std::regex memAmountMBToken("(\\d*) MB"); + const int GB_TO_MB { 1024 }; + if (std::regex_search(memAmountUnit, amount, memAmountGBToken)) { + desc.videoMemory = std::stoi(amount.str(1)) * GB_TO_MB; + } else if (std::regex_search(memAmountUnit, amount, memAmountMBToken)) { + desc.videoMemory = std::stoi(amount.str(1)); + } else { + desc.videoMemory = std::stoi(memAmountUnit); + } + } + + // Find the Device ID + const std::regex deviceIDToken("(Device ID: )(.*)"); + if (std::regex_search(gpuDesc, found, deviceIDToken)) { + desc.deviceID = std::stoi(found.str(2), 0, 16); + } + + chipsetDescs.push_back(desc); + } + + // GO through the detected gpus in order and complete missing information from ChipsetModelDescs + // assuming the order is the same and checking that the vendor and memory amount match as a simple check + auto numDescs = (int) std::min(chipsetDescs.size(),_gpus.size()); + for (int i = 0; i < numDescs; ++i) { + const auto& chipset = chipsetDescs[i]; + auto& gpu = _gpus[i]; + + if ( (chipset.vendor.find( gpu[keys::gpu::vendor]) != std::string::npos) + && (chipset.videoMemory == gpu[keys::gpu::videoMemory]) ) { + gpu[keys::gpu::model] = chipset.model; + gpu["macos_deviceID"] = chipset.deviceID; + } + } + } - auto displayBounds = CGDisplayBounds(displayID); - auto displayMaster =CGDisplayIsMain(displayID); - - auto displayUnit =CGDisplayUnitNumber(displayID); - auto displayModel =CGDisplayModelNumber(displayID); - auto displayVendor = CGDisplayVendorNumber(displayID); - auto displaySerial = CGDisplaySerialNumber(displayID); - - auto displayMode = CGDisplayCopyDisplayMode(displayID); - auto displayModeWidth = CGDisplayModeGetPixelWidth(displayMode); - auto displayModeHeight = CGDisplayModeGetPixelHeight(displayMode); - auto displayRefreshrate = CGDisplayModeGetRefreshRate(displayMode); - - CGDisplayModeRelease(displayMode); - - json display = {}; - - display["physicalWidth"] = displaySizeWidthInches; - display["physicalHeight"] = displaySizeHeightInches; - display["physicalDiagonal"] = displaySizeDiagonalInches; - - display["ppi"] = sqrt(displayModeHeight * displayModeHeight + displayModeWidth * displayModeWidth) / displaySizeDiagonalInches; - - display["coordLeft"] = displayBounds.origin.x; - display["coordRight"] = displayBounds.origin.x + displayBounds.size.width; - display["coordTop"] = displayBounds.origin.y; - display["coordBottom"] = displayBounds.origin.y + displayBounds.size.height; - - display["isMaster"] = displayMaster; - - display["unit"] = displayUnit; - display["vendor"] = displayVendor; - display["model"] = displayModel; - display["serial"] = displaySerial; - - display["refreshrate"] =displayRefreshrate; - display["modeWidth"] = displayModeWidth; - display["modeHeight"] = displayModeHeight; - - _displays.push_back(display); #endif } @@ -132,11 +314,11 @@ void MACOSInstance::enumerateComputer(){ _computer[keys::computer::model]=std::string(model); free(model); - -#endif auto sysInfo = QSysInfo(); _computer[keys::computer::OSVersion] = sysInfo.kernelVersion().toStdString(); +#endif + } diff --git a/libraries/platform/src/platform/backend/MACOSPlatform.h b/libraries/platform/src/platform/backend/MACOSPlatform.h index e893dda739..f249dad001 100644 --- a/libraries/platform/src/platform/backend/MACOSPlatform.h +++ b/libraries/platform/src/platform/backend/MACOSPlatform.h @@ -16,8 +16,7 @@ namespace platform { public: void enumerateCpus() override; - void enumerateGpus() override; - void enumerateDisplays() override; + void enumerateGpusAndDisplays() override; void enumerateMemory() override; void enumerateComputer() override; }; diff --git a/libraries/platform/src/platform/backend/Platform.cpp b/libraries/platform/src/platform/backend/Platform.cpp index 9284e0b3a0..17d9d8019e 100644 --- a/libraries/platform/src/platform/backend/Platform.cpp +++ b/libraries/platform/src/platform/backend/Platform.cpp @@ -21,7 +21,8 @@ namespace platform { namespace keys { const char* model = "model"; const char* clockSpeed = "clockSpeed"; const char* numCores = "numCores"; - } + const char* isMaster = "isMaster"; + } namespace gpu { const char* vendor = "vendor"; const char* vendor_NVIDIA = "NVIDIA"; @@ -31,6 +32,8 @@ namespace platform { namespace keys { const char* model = "model"; const char* videoMemory = "videoMemory"; const char* driver = "driver"; + const char* displays = "displays"; + const char* isMaster = "isMaster"; } namespace nic { const char* mac = "mac"; @@ -39,10 +42,19 @@ namespace platform { namespace keys { namespace display { const char* description = "description"; const char* name = "deviceName"; - const char* coordsLeft = "coordinatesleft"; - const char* coordsRight = "coordinatesright"; - const char* coordsTop = "coordinatestop"; - const char* coordsBottom = "coordinatesbottom"; + const char* boundsLeft = "boundsLeft"; + const char* boundsRight = "boundsRight"; + const char* boundsTop = "boundsTop"; + const char* boundsBottom = "boundsBottom"; + const char* gpu = "gpu"; + const char* ppi = "ppi"; + const char* ppiDesktop = "ppiDesktop"; + const char* physicalWidth = "physicalWidth"; + const char* physicalHeight = "physicalHeight"; + const char* modeRefreshrate = "modeRefreshrate"; + const char* modeWidth = "modeWidth"; + const char* modeHeight = "modeHeight"; + const char* isMaster = "isMaster"; } namespace memory { const char* memTotal = "memTotal"; @@ -117,6 +129,10 @@ json platform::getCPU(int index) { return _instance->getCPU(index); } +int platform::getMasterCPU() { + return _instance->getMasterCPU(); +} + int platform::getNumGPUs() { return _instance->getNumGPUs(); } @@ -125,6 +141,10 @@ json platform::getGPU(int index) { return _instance->getGPU(index); } +int platform::getMasterGPU() { + return _instance->getMasterGPU(); +} + int platform::getNumDisplays() { return _instance->getNumDisplays(); } @@ -133,6 +153,10 @@ json platform::getDisplay(int index) { return _instance->getDisplay(index); } +int platform::getMasterDisplay() { + return _instance->getMasterDisplay(); +} + json platform::getMemory() { return _instance->getMemory(); } diff --git a/libraries/platform/src/platform/backend/PlatformInstance.cpp b/libraries/platform/src/platform/backend/PlatformInstance.cpp index 33b19cd012..038521d398 100644 --- a/libraries/platform/src/platform/backend/PlatformInstance.cpp +++ b/libraries/platform/src/platform/backend/PlatformInstance.cpp @@ -16,18 +16,83 @@ using namespace platform; bool Instance::enumeratePlatform() { + //clear all knowledge + _computer.clear(); + _memory.clear(); + _cpus.clear(); + _gpus.clear(); + _displays.clear(); + _nics.clear(); + + // enumerate platform components enumerateComputer(); enumerateMemory(); enumerateCpus(); - enumerateGpus(); - enumerateDisplays(); + enumerateGpusAndDisplays(); enumerateNics(); + + // eval the master index for each platform scopes + updateMasterIndices(); // And profile the platform and put the tier in "computer" _computer[keys::computer::profileTier] = Profiler::TierNames[Profiler::profilePlatform()]; return true; } + +void Instance::updateMasterIndices() { + // We assume a single CPU at the moment: + { + if (!_cpus.empty()) { + _masterCPU = 0; + _cpus[0][keys::cpu::isMaster] = true; + } else { + _masterCPU = NOT_FOUND; + } + } + + // Go through the displays list + { + _masterDisplay = NOT_FOUND; + for (int i = 0; i < (int) _displays.size(); ++i) { + const auto& display = _displays[i]; + if (display.count(keys::display::isMaster)) { + if (display[keys::display::isMaster].get()) { + _masterDisplay = i; + break; + } + } + } + // NO master display found, return the first one or NOT_FOUND if no display + if (_masterDisplay == NOT_FOUND) { + if (!_displays.empty()) { + _masterDisplay = 0; + _displays[0][keys::display::isMaster] = true; + } + } + } + + // From the master display decide the master gpu + { + _masterGPU = NOT_FOUND; + if (_masterDisplay != NOT_FOUND) { + const auto& display = _displays[_masterDisplay]; + // FInd the GPU index of the master display + if (display.count(keys::display::gpu)) { + _masterGPU = display[keys::display::gpu]; + _gpus[_masterGPU][keys::gpu::isMaster] = true; + } + } + // NO master GPU found from master display, bummer, return the first one or NOT_FOUND if no display + if (_masterGPU == NOT_FOUND) { + if (!_gpus.empty()) { + _masterGPU = 0; + _gpus[0][keys::gpu::isMaster] = true; + } + } + } +} + void Instance::enumerateNics() { QNetworkInterface interface; foreach(interface, interface.allInterfaces()) { @@ -57,6 +122,7 @@ json Instance::getGPU(int index) { return _gpus.at(index); } + json Instance::getDisplay(int index) { assert(index <(int) _displays.size()); @@ -98,13 +164,13 @@ json Instance::listAllKeys() { keys::gpu::model, keys::gpu::videoMemory, keys::gpu::driver, + keys::gpu::displays, - keys::display::description, - keys::display::name, - keys::display::coordsLeft, - keys::display::coordsRight, - keys::display::coordsTop, - keys::display::coordsBottom, + keys::display::boundsLeft, + keys::display::boundsRight, + keys::display::boundsTop, + keys::display::boundsBottom, + keys::display::gpu, keys::memory::memTotal, diff --git a/libraries/platform/src/platform/backend/PlatformInstance.h b/libraries/platform/src/platform/backend/PlatformInstance.h index 6ceacaa3ff..069124853e 100644 --- a/libraries/platform/src/platform/backend/PlatformInstance.h +++ b/libraries/platform/src/platform/backend/PlatformInstance.h @@ -17,16 +17,21 @@ namespace platform { class Instance { public: + const int NOT_FOUND { -1 }; + bool virtual enumeratePlatform(); int getNumCPUs() { return (int)_cpus.size(); } json getCPU(int index); + int getMasterCPU() const { return _masterCPU; } int getNumGPUs() { return (int)_gpus.size(); } json getGPU(int index); + int getMasterGPU() const { return _masterGPU; } int getNumDisplays() { return (int)_displays.size(); } json getDisplay(int index); + int getMasterDisplay() const { return _masterDisplay; } json getMemory() { return _memory; } @@ -35,8 +40,7 @@ public: json getAll(); void virtual enumerateCpus()=0; - void virtual enumerateGpus()=0; - void virtual enumerateDisplays() {} + void virtual enumerateGpusAndDisplays()=0; void virtual enumerateNics(); void virtual enumerateMemory() = 0; void virtual enumerateComputer()=0; @@ -55,6 +59,14 @@ protected: std::vector _nics; json _memory; json _computer; + + int _masterCPU{ -1 }; + int _masterGPU{ -1 }; + int _masterDisplay{ -1 }; + + // Traverse the cpus, gpus and displays to update the "master" index in each domain + void updateMasterIndices(); + }; } // namespace platform diff --git a/libraries/platform/src/platform/backend/WINPlatform.cpp b/libraries/platform/src/platform/backend/WINPlatform.cpp index d07bb83510..e528618fe1 100644 --- a/libraries/platform/src/platform/backend/WINPlatform.cpp +++ b/libraries/platform/src/platform/backend/WINPlatform.cpp @@ -11,21 +11,31 @@ #include #include + #include -#include + +#include #ifdef Q_OS_WIN +#include +#include +#include #include #include #include #include +#include +#pragma comment(lib, "dxgi.lib") +#include +#pragma comment(lib, "Shcore.lib") + #endif using namespace platform; void WINInstance::enumerateCpus() { json cpu = {}; - + cpu[keys::cpu::vendor] = CPUIdent::Vendor(); cpu[keys::cpu::model] = CPUIdent::Brand(); cpu[keys::cpu::numCores] = std::thread::hardware_concurrency(); @@ -33,23 +43,153 @@ void WINInstance::enumerateCpus() { _cpus.push_back(cpu); } -void WINInstance::enumerateGpus() { +void WINInstance::enumerateGpusAndDisplays() { +#ifdef Q_OS_WIN + struct ConvertLargeIntegerToString { + std::string convert(const LARGE_INTEGER& version) { + std::ostringstream value; + value << uint32_t(((version.HighPart & 0xFFFF0000) >> 16) & 0x0000FFFF); + value << "."; + value << uint32_t((version.HighPart) & 0x0000FFFF); + value << "."; + value << uint32_t(((version.LowPart & 0xFFFF0000) >> 16) & 0x0000FFFF); + value << "."; + value << uint32_t((version.LowPart) & 0x0000FFFF); + + return value.str(); + } + } convertDriverVersionToString; - GPUIdent* ident = GPUIdent::getInstance(); - - json gpu = {}; - gpu[keys::gpu::model] = ident->getName().toUtf8().constData(); - gpu[keys::gpu::vendor] = findGPUVendorInDescription(gpu[keys::gpu::model].get()); - gpu[keys::gpu::videoMemory] = ident->getMemory(); - gpu[keys::gpu::driver] = ident->getDriver().toUtf8().constData(); + // Create the DXGI factory + // Let s get into DXGI land: + HRESULT hr = S_OK; - _gpus.push_back(gpu); - _displays = ident->getOutput(); + IDXGIFactory1* pFactory = nullptr; + hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)(&pFactory)); + if (hr != S_OK || pFactory == nullptr) { + return; + } + + std::vector validAdapterList; + using AdapterEntry = std::pair, std::vector>; + std::vector adapterToOutputs; + // Enumerate adapters and outputs + { + UINT adapterNum = 0; + IDXGIAdapter1* pAdapter = nullptr; + while (pFactory->EnumAdapters1(adapterNum, &pAdapter) != DXGI_ERROR_NOT_FOUND) { + // Found an adapter, get descriptor + DXGI_ADAPTER_DESC1 adapterDesc; + pAdapter->GetDesc1(&adapterDesc); + + // Only describe gpu if it is a hardware adapter + if (!(adapterDesc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)) { + + LARGE_INTEGER version; + hr = pAdapter->CheckInterfaceSupport(__uuidof(IDXGIDevice), &version); + + std::wstring wDescription(adapterDesc.Description); + std::string description(wDescription.begin(), wDescription.end()); + + json gpu = {}; + gpu[keys::gpu::model] = description; + gpu[keys::gpu::vendor] = findGPUVendorInDescription(gpu[keys::gpu::model].get()); + const SIZE_T BYTES_PER_MEGABYTE = 1024 * 1024; + gpu[keys::gpu::videoMemory] = (uint32_t)(adapterDesc.DedicatedVideoMemory / BYTES_PER_MEGABYTE); + gpu[keys::gpu::driver] = convertDriverVersionToString.convert(version); + + std::vector displayIndices; + + UINT outputNum = 0; + IDXGIOutput* pOutput; + bool hasOutputConnectedToDesktop = false; + while (pAdapter->EnumOutputs(outputNum, &pOutput) != DXGI_ERROR_NOT_FOUND) { + // FOund an output attached to the adapter, get descriptor + DXGI_OUTPUT_DESC outputDesc; + pOutput->GetDesc(&outputDesc); + pOutput->Release(); + outputNum++; + + // Grab the monitor info + MONITORINFO monitorInfo; + monitorInfo.cbSize = sizeof(MONITORINFO); + GetMonitorInfo(outputDesc.Monitor, &monitorInfo); + + // Grab the dpi info for the monitor + UINT dpiX{ 0 }; + UINT dpiY{ 0 }; + GetDpiForMonitor(outputDesc.Monitor, MDT_RAW_DPI, &dpiX, &dpiY); + UINT dpiXScaled{ 0 }; + UINT dpiYScaled{ 0 }; + GetDpiForMonitor(outputDesc.Monitor, MDT_EFFECTIVE_DPI, &dpiXScaled, &dpiYScaled); + + // Current display mode + DEVMODEW devMode; + devMode.dmSize = sizeof(DEVMODEW); + EnumDisplaySettingsW(outputDesc.DeviceName, ENUM_CURRENT_SETTINGS, &devMode); + + auto physicalWidth = devMode.dmPelsWidth / (float)dpiX; + auto physicalHeight = devMode.dmPelsHeight / (float)dpiY; + + json display = {}; + + // Display name + std::wstring wDeviceName(outputDesc.DeviceName); + std::string deviceName(wDeviceName.begin(), wDeviceName.end()); + display[keys::display::name] = deviceName; + display[keys::display::description] = ""; + + // Rect region of the desktop in desktop units + //display["desktopRect"] = (outputDesc.AttachedToDesktop ? true : false); + display[keys::display::boundsLeft] = outputDesc.DesktopCoordinates.left; + display[keys::display::boundsRight] = outputDesc.DesktopCoordinates.right; + display[keys::display::boundsBottom] = outputDesc.DesktopCoordinates.bottom; + display[keys::display::boundsTop] = outputDesc.DesktopCoordinates.top; + + // PPI & resolution + display[keys::display::physicalWidth] = physicalWidth; + display[keys::display::physicalHeight] = physicalHeight; + display[keys::display::modeWidth] = devMode.dmPelsWidth; + display[keys::display::modeHeight] = devMode.dmPelsHeight; + + //Average the ppiH and V for the simple ppi + display[keys::display::ppi] = std::round(0.5f * (dpiX + dpiY)); + display[keys::display::ppiDesktop] = std::round(0.5f * (dpiXScaled + dpiYScaled)); + + // refreshrate + display[keys::display::modeRefreshrate] = devMode.dmDisplayFrequency;; + + // Master display ? + display[keys::display::isMaster] = (bool) (monitorInfo.dwFlags & MONITORINFOF_PRIMARY); + + // Add the display index to the list of displays of the gpu + displayIndices.push_back((int) _displays.size()); + + // And set the gpu index to the display description + display[keys::display::gpu] = (int) _gpus.size(); + + // WIN specific + + + // One more display desc + _displays.push_back(display); + } + gpu[keys::gpu::displays] = displayIndices; + + _gpus.push_back(gpu); + } + + pAdapter->Release(); + adapterNum++; + } + } + pFactory->Release(); +#endif } void WINInstance::enumerateMemory() { json ram = {}; - + #ifdef Q_OS_WIN MEMORYSTATUSEX statex; statex.dwLength = sizeof(statex); @@ -60,16 +200,18 @@ void WINInstance::enumerateMemory() { _memory = ram; } -void WINInstance::enumerateComputer(){ +void WINInstance::enumerateComputer() { _computer[keys::computer::OS] = keys::computer::OS_WINDOWS; _computer[keys::computer::vendor] = ""; _computer[keys::computer::model] = ""; - - auto sysInfo = QSysInfo(); +#ifdef Q_OS_WIN + auto sysInfo = QSysInfo(); _computer[keys::computer::OSVersion] = sysInfo.kernelVersion().toStdString(); +#endif } + void WINInstance::enumerateNics() { // Start with the default from QT, getting the result into _nics: Instance::enumerateNics(); @@ -78,23 +220,23 @@ void WINInstance::enumerateNics() { // We can usually do better than the QNetworkInterface::humanReadableName() by // matching up Iphlpapi.lib IP_ADAPTER_INFO by mac id. ULONG buflen = sizeof(IP_ADAPTER_INFO); - IP_ADAPTER_INFO* pAdapterInfo = (IP_ADAPTER_INFO*) malloc(buflen); + IP_ADAPTER_INFO* pAdapterInfo = (IP_ADAPTER_INFO*)malloc(buflen); // Size the buffer: if (GetAdaptersInfo(pAdapterInfo, &buflen) == ERROR_BUFFER_OVERFLOW) { free(pAdapterInfo); - pAdapterInfo = (IP_ADAPTER_INFO *) malloc(buflen); + pAdapterInfo = (IP_ADAPTER_INFO*)malloc(buflen); } // Now get the data... if (GetAdaptersInfo(pAdapterInfo, &buflen) == NO_ERROR) { - for (json& nic : _nics) { // ... loop through the nics from above... + for (json& nic : _nics) { // ... loop through the nics from above... // ...convert the json to a string without the colons... QString qtmac = nic[keys::nic::mac].get().c_str(); QString qtraw = qtmac.remove(QChar(':'), Qt::CaseInsensitive).toLower(); // ... and find the matching one in pAdapter: for (IP_ADAPTER_INFO* pAdapter = pAdapterInfo; pAdapter; pAdapter = pAdapter->Next) { - QByteArray wmac = QByteArray((const char*) (pAdapter->Address), pAdapter->AddressLength); + QByteArray wmac = QByteArray((const char*)(pAdapter->Address), pAdapter->AddressLength); QString wraw = wmac.toHex(); if (qtraw == wraw) { nic[keys::nic::name] = pAdapter->Description; @@ -108,4 +250,4 @@ void WINInstance::enumerateNics() { free(pAdapterInfo); } #endif -} \ No newline at end of file +} diff --git a/libraries/platform/src/platform/backend/WINPlatform.h b/libraries/platform/src/platform/backend/WINPlatform.h index 4926d578f7..cc56ebfbbc 100644 --- a/libraries/platform/src/platform/backend/WINPlatform.h +++ b/libraries/platform/src/platform/backend/WINPlatform.h @@ -16,7 +16,7 @@ namespace platform { public: void enumerateCpus() override; - void enumerateGpus() override; + void enumerateGpusAndDisplays() override; void enumerateMemory() override; void enumerateComputer () override; void enumerateNics() override; diff --git a/scripts/developer/utilities/lib/prop/PropGroup.qml b/scripts/developer/utilities/lib/prop/PropGroup.qml index 848ff8b288..c6b2c694f6 100644 --- a/scripts/developer/utilities/lib/prop/PropGroup.qml +++ b/scripts/developer/utilities/lib/prop/PropGroup.qml @@ -86,19 +86,30 @@ PropFolderPanel { var itemLabel = proItem.property; var itemDepth = root.indentDepth + 1; if (Array.isArray(itemRootObject)) { - if (objectItem.length > 1) { - itemLabel = itemLabel + " " + objectItem.length + itemLabel = proItem.property + "[] / " + itemRootObject.length + if (itemRootObject.length == 0) { + var component = Qt.createComponent("PropItem.qml"); + component.createObject(propItemsContainer, { + "label": itemLabel + }) } else { - itemLabel = itemLabel + " " + objectItem.length - itemRootObject = itemRootObject[0]; + var component = Qt.createComponent("PropGroup.qml"); + component.createObject(propItemsContainer, { + "label": itemLabel, + "rootObject":itemRootObject, + "indentDepth": itemDepth, + "isUnfold": true, + }) } + } else { + var component = Qt.createComponent("PropGroup.qml"); + component.createObject(propItemsContainer, { + "label": itemLabel, + "rootObject":itemRootObject, + "indentDepth": itemDepth, + "isUnfold": true, + }) } - var component = Qt.createComponent("PropGroup.qml"); - component.createObject(propItemsContainer, { - "label": itemLabel, - "rootObject":itemRootObject, - "indentDepth": itemDepth, - }) } break; case 'printLabel': { var component = Qt.createComponent("PropItem.qml"); @@ -125,12 +136,6 @@ PropFolderPanel { function populateFromObjectProps(object) { var propsModel = [] - if (Array.isArray(object)) { - if (object.length <= 1) { - object = object[0]; - } - } - var props = Object.keys(object); for (var p in props) { var o = {}; diff --git a/scripts/developer/utilities/render/performanceSetup.qml b/scripts/developer/utilities/render/performanceSetup.qml index 4654736f72..be9b6a3271 100644 --- a/scripts/developer/utilities/render/performanceSetup.qml +++ b/scripts/developer/utilities/render/performanceSetup.qml @@ -51,6 +51,7 @@ Rectangle { } Prop.PropFolderPanel { label: "Platform" + isUnfold: true panelFrameData: Component { Platform { } diff --git a/scripts/simplifiedUI/system/assets/images/hourglass.svg b/scripts/simplifiedUI/system/assets/images/hourglass.svg new file mode 100644 index 0000000000..5c5055b755 --- /dev/null +++ b/scripts/simplifiedUI/system/assets/images/hourglass.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/assets/data/createAppTooltips.json b/scripts/simplifiedUI/system/create/assets/data/createAppTooltips.json similarity index 100% rename from scripts/simplifiedUI/system/assets/data/createAppTooltips.json rename to scripts/simplifiedUI/system/create/assets/data/createAppTooltips.json diff --git a/scripts/simplifiedUI/system/create/assets/images/hourglass.svg b/scripts/simplifiedUI/system/create/assets/images/hourglass.svg new file mode 100644 index 0000000000..5c5055b755 --- /dev/null +++ b/scripts/simplifiedUI/system/create/assets/images/hourglass.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/create/create-icons/90-particles-01.svg b/scripts/simplifiedUI/system/create/assets/images/icon-particles.svg similarity index 100% rename from scripts/simplifiedUI/system/create/create-icons/90-particles-01.svg rename to scripts/simplifiedUI/system/create/assets/images/icon-particles.svg diff --git a/scripts/simplifiedUI/system/create/assets/images/icon-point-light.svg b/scripts/simplifiedUI/system/create/assets/images/icon-point-light.svg new file mode 100644 index 0000000000..896c35b63b --- /dev/null +++ b/scripts/simplifiedUI/system/create/assets/images/icon-point-light.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/create/assets/images/icon-spot-light.svg b/scripts/simplifiedUI/system/create/assets/images/icon-spot-light.svg new file mode 100644 index 0000000000..ac2f87bb27 --- /dev/null +++ b/scripts/simplifiedUI/system/create/assets/images/icon-spot-light.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/create/assets/images/icon-zone.svg b/scripts/simplifiedUI/system/create/assets/images/icon-zone.svg new file mode 100644 index 0000000000..41aeac4951 --- /dev/null +++ b/scripts/simplifiedUI/system/create/assets/images/icon-zone.svg @@ -0,0 +1,73 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/scripts/simplifiedUI/system/create/edit.js b/scripts/simplifiedUI/system/create/edit.js new file mode 100644 index 0000000000..f50bc547e5 --- /dev/null +++ b/scripts/simplifiedUI/system/create/edit.js @@ -0,0 +1,2858 @@ +// edit.js +// +// Created by Brad Hefta-Gaub on 10/2/14. +// Persist toolbar by HRS 6/11/15. +// Copyright 2014 High Fidelity, Inc. +// +// This script allows you to edit entities with a new UI/UX for mouse and trackpad based editing +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, + Overlays, OverlayWebWindow, UserActivityLogger, Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, + progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, OverlaySystemWindow, + keyUpEventFromUIWindow:true */ + +(function() { // BEGIN LOCAL_SCOPE + +"use strict"; + +var EDIT_TOGGLE_BUTTON = "com.highfidelity.interface.system.editButton"; + +var CONTROLLER_MAPPING_NAME = "com.highfidelity.editMode"; + +Script.include([ + "../libraries/stringHelpers.js", + "../libraries/dataViewHelpers.js", + "../libraries/progressDialog.js", + "../libraries/ToolTip.js", + "../libraries/entityCameraTool.js", + "../libraries/utils.js", + "../libraries/entityIconOverlayManager.js", + "../libraries/gridTool.js", + "entityList/entityList.js", + "entitySelectionTool/entitySelectionTool.js" +]); + +var CreateWindow = Script.require('./modules/createWindow.js'); + +var TITLE_OFFSET = 60; +var CREATE_TOOLS_WIDTH = 490; +var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942; + +var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg"; + +var createToolsWindow = new CreateWindow( + Script.resolvePath("qml/EditTools.qml"), + 'Create Tools', + 'com.highfidelity.create.createToolsWindow', + function () { + var windowHeight = Window.innerHeight - TITLE_OFFSET; + if (windowHeight > MAX_DEFAULT_ENTITY_LIST_HEIGHT) { + windowHeight = MAX_DEFAULT_ENTITY_LIST_HEIGHT; + } + return { + size: { + x: CREATE_TOOLS_WIDTH, + y: windowHeight + }, + position: { + x: Window.x + Window.innerWidth - CREATE_TOOLS_WIDTH, + y: Window.y + TITLE_OFFSET + } + } + }, + false +); + +/** + * @description Returns true in case we should use the tablet version of the CreateApp + * @returns boolean + */ +var shouldUseEditTabletApp = function() { + return HMD.active || (!HMD.active && !Settings.getValue("desktopTabletBecomesToolbar", true)); +}; + + +var selectionDisplay = SelectionDisplay; +var selectionManager = SelectionManager; + +var PARTICLE_SYSTEM_URL = Script.resolvePath("assets/images/icon-particles.svg"); +var POINT_LIGHT_URL = Script.resolvePath("assets/images/icon-point-light.svg"); +var SPOT_LIGHT_URL = Script.resolvePath("assets/images/icon-spot-light.svg"); +var ZONE_URL = Script.resolvePath("assets/images/icon-zone.svg"); + +var entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleEffect', 'Zone'], function(entityID) { + var properties = Entities.getEntityProperties(entityID, ['type', 'isSpotlight']); + if (properties.type === 'Light') { + return { + url: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL, + }; + } else if (properties.type === 'Zone') { + return { + url: ZONE_URL, + }; + } else { + return { + url: PARTICLE_SYSTEM_URL, + }; + } +}); + +var cameraManager = new CameraManager(); + +var grid = new Grid(); +var gridTool = new GridTool({ + horizontalGrid: grid, + createToolsWindow: createToolsWindow, + shouldUseEditTabletApp: shouldUseEditTabletApp +}); +gridTool.setVisible(false); + +var EntityShapeVisualizer = Script.require('./modules/entityShapeVisualizer.js'); +var entityShapeVisualizer = new EntityShapeVisualizer(["Zone"]); + +var entityListTool = new EntityListTool(shouldUseEditTabletApp); + +selectionManager.addEventListener(function () { + selectionDisplay.updateHandles(); + entityIconOverlayManager.updatePositions(); + entityShapeVisualizer.setEntities(selectionManager.selections); +}); + +var DEGREES_TO_RADIANS = Math.PI / 180.0; +var RADIANS_TO_DEGREES = 180.0 / Math.PI; + +var MIN_ANGULAR_SIZE = 2; +var MAX_ANGULAR_SIZE = 45; +var allowLargeModels = true; +var allowSmallModels = true; + +var DEFAULT_DIMENSION = 0.20; + +var DEFAULT_DIMENSIONS = { + x: DEFAULT_DIMENSION, + y: DEFAULT_DIMENSION, + z: DEFAULT_DIMENSION +}; + +var DEFAULT_LIGHT_DIMENSIONS = Vec3.multiply(20, DEFAULT_DIMENSIONS); + +var MENU_AUTO_FOCUS_ON_SELECT = "Auto Focus on Select"; +var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus"; +var MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "Show Lights and Particle Systems in Create Mode"; +var MENU_SHOW_ZONES_IN_EDIT_MODE = "Show Zones in Create Mode"; + +var MENU_CREATE_ENTITIES_GRABBABLE = "Create Entities As Grabbable (except Zones, Particles, and Lights)"; +var MENU_ALLOW_SELECTION_LARGE = "Allow Selecting of Large Models"; +var MENU_ALLOW_SELECTION_SMALL = "Allow Selecting of Small Models"; +var MENU_ALLOW_SELECTION_LIGHTS = "Allow Selecting of Lights"; + +var SETTING_AUTO_FOCUS_ON_SELECT = "autoFocusOnSelect"; +var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus"; +var SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "showLightsAndParticlesInEditMode"; +var SETTING_SHOW_ZONES_IN_EDIT_MODE = "showZonesInEditMode"; + +var SETTING_EDIT_PREFIX = "Edit/"; + + +var CREATE_ENABLED_ICON = "icons/tablet-icons/edit-i.svg"; +var CREATE_DISABLED_ICON = "icons/tablet-icons/edit-disabled.svg"; + +// marketplace info, etc. not quite ready yet. +var SHOULD_SHOW_PROPERTY_MENU = false; +var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to edit on this domain."; +var INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG = "You do not have the necessary permissions to place items on this domain."; + +var isActive = false; +var createButton = null; + +var IMPORTING_SVO_OVERLAY_WIDTH = 144; +var IMPORTING_SVO_OVERLAY_HEIGHT = 30; +var IMPORTING_SVO_OVERLAY_MARGIN = 5; +var IMPORTING_SVO_OVERLAY_LEFT_MARGIN = 34; +var importingSVOImageOverlay = Overlays.addOverlay("image", { + imageURL: Script.resolvePath("assets/images/hourglass.svg"), + width: 20, + height: 20, + alpha: 1.0, + x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH, + y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT, + visible: false +}); +var importingSVOTextOverlay = Overlays.addOverlay("text", { + font: { + size: 14 + }, + text: "Importing SVO...", + leftMargin: IMPORTING_SVO_OVERLAY_LEFT_MARGIN, + x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH - IMPORTING_SVO_OVERLAY_MARGIN, + y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT - IMPORTING_SVO_OVERLAY_MARGIN, + width: IMPORTING_SVO_OVERLAY_WIDTH, + height: IMPORTING_SVO_OVERLAY_HEIGHT, + backgroundColor: { + red: 80, + green: 80, + blue: 80 + }, + backgroundAlpha: 0.7, + visible: false +}); + +var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; +var marketplaceWindow = new OverlayWebWindow({ + title: 'Marketplace', + source: "about:blank", + width: 900, + height: 700, + visible: false +}); + +function showMarketplace(marketplaceID) { + var url = MARKETPLACE_URL; + if (marketplaceID) { + url = url + "/items/" + marketplaceID; + } + marketplaceWindow.setURL(url); + marketplaceWindow.setVisible(true); + marketplaceWindow.raise(); + + UserActivityLogger.logAction("opened_marketplace"); +} + +function hideMarketplace() { + marketplaceWindow.setVisible(false); + marketplaceWindow.setURL("about:blank"); +} + +// function toggleMarketplace() { +// if (marketplaceWindow.visible) { +// hideMarketplace(); +// } else { +// showMarketplace(); +// } +// } + +function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { + // Adjust the position such that the bounding box (registration, dimensions and orientation) lies behind the original + // position in the given direction. + var CORNERS = [ + { x: 0, y: 0, z: 0 }, + { x: 0, y: 0, z: 1 }, + { x: 0, y: 1, z: 0 }, + { x: 0, y: 1, z: 1 }, + { x: 1, y: 0, z: 0 }, + { x: 1, y: 0, z: 1 }, + { x: 1, y: 1, z: 0 }, + { x: 1, y: 1, z: 1 }, + ]; + + // Go through all corners and find least (most negative) distance in front of position. + var distance = 0; + for (var i = 0, length = CORNERS.length; i < length; i++) { + var cornerVector = + Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions)); + var cornerDistance = Vec3.dot(cornerVector, direction); + distance = Math.min(cornerDistance, distance); + } + position = Vec3.sum(Vec3.multiply(distance, direction), position); + return position; +} + +var GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit"; + +// Handles any edit mode updates required when domains have switched +function checkEditPermissionsAndUpdate() { + if ((createButton === null) || (createButton === undefined)) { + //--EARLY EXIT--( nothing to safely update ) + return; + } + + var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()); + createButton.editProperties({ + icon: (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON), + captionColor: (hasRezPermissions ? "#ffffff" : "#888888"), + }); + + if (!hasRezPermissions && isActive) { + that.setActive(false); + tablet.gotoHomeScreen(); + } +} + +// Copies the properties in `b` into `a`. `a` will be modified. +function copyProperties(a, b) { + for (var key in b) { + a[key] = b[key]; + } + return a; +} + +const DEFAULT_DYNAMIC_PROPERTIES = { + dynamic: true, + damping: 0.39347, + angularDamping: 0.39347, + gravity: { x: 0, y: -9.8, z: 0 }, +}; + +const DEFAULT_NON_DYNAMIC_PROPERTIES = { + dynamic: false, + damping: 0, + angularDamping: 0, + gravity: { x: 0, y: 0, z: 0 }, +}; + +const DEFAULT_ENTITY_PROPERTIES = { + All: { + description: "", + rotation: { x: 0, y: 0, z: 0, w: 1 }, + collidesWith: "static,dynamic,kinematic,otherAvatar,myAvatar", + collisionSoundURL: "", + cloneable: false, + ignoreIK: true, + canCastShadow: true, + href: "", + script: "", + serverScripts:"", + velocity: { + x: 0, + y: 0, + z: 0 + }, + angularVelocity: { + x: 0, + y: 0, + z: 0 + }, + restitution: 0.5, + friction: 0.5, + density: 1000, + dynamic: false, + }, + Shape: { + shape: "Box", + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + color: { red: 0, green: 180, blue: 239 }, + }, + Text: { + text: "Text", + dimensions: { + x: 0.65, + y: 0.3, + z: 0.01 + }, + textColor: { red: 255, green: 255, blue: 255 }, + backgroundColor: { red: 0, green: 0, blue: 0 }, + lineHeight: 0.06, + faceCamera: false, + }, + Zone: { + dimensions: { + x: 10, + y: 10, + z: 10 + }, + flyingAllowed: true, + ghostingAllowed: true, + filter: "", + keyLightMode: "inherit", + keyLightColor: { red: 255, green: 255, blue: 255 }, + keyLight: { + intensity: 1.0, + direction: { + x: 0.0, + y: -0.707106769084930, // 45 degrees + z: 0.7071067690849304 + }, + castShadows: true + }, + ambientLightMode: "inherit", + ambientLight: { + ambientIntensity: 0.5, + ambientURL: "" + }, + hazeMode: "inherit", + haze: { + hazeRange: 1000, + hazeAltitudeEffect: false, + hazeBaseRef: 0, + hazeColor: { + red: 128, + green: 154, + blue: 179 + }, + hazeBackgroundBlend: 0, + hazeEnableGlare: false, + hazeGlareColor: { + red: 255, + green: 229, + blue: 179 + }, + }, + shapeType: "box", + bloomMode: "inherit", + avatarPriority: "inherit" + }, + Model: { + collisionShape: "none", + compoundShapeURL: "", + animation: { + url: "", + running: false, + allowTranslation: false, + loop: true, + hold: false, + currentFrame: 0, + firstFrame: 0, + lastFrame: 100000, + fps: 30.0, + } + }, + Image: { + dimensions: { + x: 0.5385, + y: 0.2819, + z: 0.0092 + }, + shapeType: "box", + collisionless: true, + keepAspectRatio: false, + imageURL: DEFAULT_IMAGE + }, + Web: { + dimensions: { + x: 1.6, + y: 0.9, + z: 0.01 + }, + sourceUrl: "https://highfidelity.com/", + dpi: 30, + }, + ParticleEffect: { + lifespan: 1.5, + maxParticles: 10, + textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png", + emitRate: 5.5, + emitSpeed: 0, + speedSpread: 0, + emitDimensions: { x: 0, y: 0, z: 0 }, + emitOrientation: { x: 0, y: 0, z: 0, w: 1 }, + emitterShouldTrail: true, + particleRadius: 0.25, + radiusStart: 0, + radiusSpread: 0, + particleColor: { + red: 255, + green: 255, + blue: 255 + }, + colorSpread: { + red: 0, + green: 0, + blue: 0 + }, + alpha: 0, + alphaStart: 1, + alphaSpread: 0, + emitAcceleration: { + x: 0, + y: 2.5, + z: 0 + }, + accelerationSpread: { + x: 0, + y: 0, + z: 0 + }, + particleSpin: 0, + spinSpread: 0, + rotateWithEntity: false, + polarStart: 0, + polarFinish: Math.PI, + azimuthStart: -Math.PI, + azimuthFinish: Math.PI + }, + Light: { + color: { red: 255, green: 255, blue: 255 }, + intensity: 5.0, + dimensions: DEFAULT_LIGHT_DIMENSIONS, + falloffRadius: 1.0, + isSpotlight: false, + exponent: 1.0, + cutoff: 75.0, + }, +}; + +var toolBar = (function () { + var EDIT_SETTING = "io.highfidelity.isEditing"; // for communication with other scripts + var that = {}, + toolBar, + activeButton = null, + systemToolbar = null, + dialogWindow = null, + tablet = null; + + function createNewEntity(requestedProperties) { + var dimensions = requestedProperties.dimensions ? requestedProperties.dimensions : DEFAULT_DIMENSIONS; + var position = getPositionToCreateEntity(); + var entityID = null; + + var properties = {}; + + copyProperties(properties, DEFAULT_ENTITY_PROPERTIES.All); + + var type = requestedProperties.type; + if (type === "Box" || type === "Sphere") { + copyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Shape); + } else { + copyProperties(properties, DEFAULT_ENTITY_PROPERTIES[type]); + } + + // We apply the requested properties first so that they take priority over any default properties. + copyProperties(properties, requestedProperties); + + if (properties.dynamic) { + copyProperties(properties, DEFAULT_DYNAMIC_PROPERTIES); + } else { + copyProperties(properties, DEFAULT_NON_DYNAMIC_PROPERTIES); + } + + + if (position !== null && position !== undefined) { + var direction; + if (Camera.mode === "entity" || Camera.mode === "independent") { + direction = Camera.orientation; + } else { + direction = MyAvatar.orientation; + } + direction = Vec3.multiplyQbyV(direction, Vec3.UNIT_Z); + + var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Image", "Web", "Material"]; + if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { + + // Adjust position of entity per bounding box prior to creating it. + var registration = properties.registration; + if (registration === undefined) { + var DEFAULT_REGISTRATION = { x: 0.5, y: 0.5, z: 0.5 }; + registration = DEFAULT_REGISTRATION; + } + + var orientation = properties.orientation; + if (orientation === undefined) { + properties.orientation = MyAvatar.orientation; + var DEFAULT_ORIENTATION = properties.orientation; + orientation = DEFAULT_ORIENTATION; + } else { + // If the orientation is already defined, we perform the corresponding rotation assuming that + // our start referential is the avatar referential. + properties.orientation = Quat.multiply(MyAvatar.orientation, properties.orientation); + var DEFAULT_ORIENTATION = properties.orientation; + orientation = DEFAULT_ORIENTATION; + } + + position = adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation); + } + + position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions); + properties.position = position; + + if (!properties.grab) { + properties.grab = {}; + if (Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE) && + !(properties.type === "Zone" || properties.type === "Light" || properties.type === "ParticleEffect")) { + properties.grab.grabbable = true; + } else { + properties.grab.grabbable = false; + } + } + + entityID = Entities.addEntity(properties); + SelectionManager.addEntity(entityID, false, this); + SelectionManager.saveProperties(); + pushCommandForSelections([{ + entityID: entityID, + properties: properties + }], [], true); + + var POST_ADJUST_ENTITY_TYPES = ["Model"]; + if (POST_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { + // Adjust position of entity per bounding box after it has been created and auto-resized. + var initialDimensions = Entities.getEntityProperties(entityID, ["dimensions"]).dimensions; + var DIMENSIONS_CHECK_INTERVAL = 200; + var MAX_DIMENSIONS_CHECKS = 10; + var dimensionsCheckCount = 0; + var dimensionsCheckFunction = function () { + dimensionsCheckCount++; + var properties = Entities.getEntityProperties(entityID, ["dimensions", "registrationPoint", "rotation"]); + if (!Vec3.equal(properties.dimensions, initialDimensions)) { + position = adjustPositionPerBoundingBox(position, direction, properties.registrationPoint, + properties.dimensions, properties.rotation); + position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions), + properties.dimensions); + Entities.editEntity(entityID, { + position: position + }); + selectionManager._update(false, this); + } else if (dimensionsCheckCount < MAX_DIMENSIONS_CHECKS) { + Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); + } + }; + Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); + } + } else { + Window.notifyEditError("Can't create " + properties.type + ": " + + properties.type + " would be out of bounds."); + } + + selectionManager.clearSelections(this); + entityListTool.sendUpdate(); + selectionManager.setSelections([entityID], this); + + Window.setFocus(); + + return entityID; + } + + function closeExistingDialogWindow() { + if (dialogWindow) { + dialogWindow.close(); + dialogWindow = null; + } + } + + function cleanup() { + that.setActive(false); + if (tablet) { + tablet.removeButton(activeButton); + } + if (systemToolbar) { + systemToolbar.removeButton(EDIT_TOGGLE_BUTTON); + } + } + + var buttonHandlers = {}; // only used to tablet mode + + function addButton(name, handler) { + buttonHandlers[name] = handler; + } + + var SHAPE_TYPE_NONE = 0; + var SHAPE_TYPE_SIMPLE_HULL = 1; + var SHAPE_TYPE_SIMPLE_COMPOUND = 2; + var SHAPE_TYPE_STATIC_MESH = 3; + var SHAPE_TYPE_BOX = 4; + var SHAPE_TYPE_SPHERE = 5; + var DYNAMIC_DEFAULT = false; + + var MATERIAL_MODE_UV = 0; + var MATERIAL_MODE_PROJECTED = 1; + + function handleNewModelDialogResult(result) { + if (result) { + var url = result.url; + var shapeType; + switch (result.collisionShapeIndex) { + case SHAPE_TYPE_SIMPLE_HULL: + shapeType = "simple-hull"; + break; + case SHAPE_TYPE_SIMPLE_COMPOUND: + shapeType = "simple-compound"; + break; + case SHAPE_TYPE_STATIC_MESH: + shapeType = "static-mesh"; + break; + case SHAPE_TYPE_BOX: + shapeType = "box"; + break; + case SHAPE_TYPE_SPHERE: + shapeType = "sphere"; + break; + default: + shapeType = "none"; + } + + var dynamic = result.dynamic !== null ? result.dynamic : DYNAMIC_DEFAULT; + if (shapeType === "static-mesh" && dynamic) { + // The prompt should prevent this case + print("Error: model cannot be both static mesh and dynamic. This should never happen."); + } else if (url) { + createNewEntity({ + type: "Model", + modelURL: url, + shapeType: shapeType, + grab: { + grabbable: result.grabbable + }, + dynamic: dynamic, + }); + } + } + } + + function handleNewMaterialDialogResult(result) { + if (result) { + var materialURL = result.textInput; + //var materialMappingMode; + //switch (result.comboBox) { + // case MATERIAL_MODE_PROJECTED: + // materialMappingMode = "projected"; + // break; + // default: + // shapeType = "uv"; + //} + var materialData = ""; + if (materialURL.startsWith("materialData")) { + materialData = JSON.stringify({ + "materials": {} + }); + } + + var DEFAULT_LAYERED_MATERIAL_PRIORITY = 1; + if (materialURL) { + createNewEntity({ + type: "Material", + materialURL: materialURL, + //materialMappingMode: materialMappingMode, + priority: DEFAULT_LAYERED_MATERIAL_PRIORITY, + materialData: materialData + }); + } + } + } + + function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + tablet.popFromStack(); + switch (message.method) { + case "newModelDialogAdd": + handleNewModelDialogResult(message.params); + closeExistingDialogWindow(); + break; + case "newModelDialogCancel": + closeExistingDialogWindow(); + break; + case "newEntityButtonClicked": + buttonHandlers[message.params.buttonName](); + break; + case "newMaterialDialogAdd": + handleNewMaterialDialogResult(message.params); + closeExistingDialogWindow(); + break; + case "newMaterialDialogCancel": + closeExistingDialogWindow(); + break; + } + } + + var entitiesToDelete = []; + var deletedEntityTimer = null; + var DELETE_ENTITY_TIMER_TIMEOUT = 100; + + function checkDeletedEntityAndUpdate(entityID) { + // Allow for multiple entity deletes before updating the entities selected. + entitiesToDelete.push(entityID); + if (deletedEntityTimer !== null) { + Script.clearTimeout(deletedEntityTimer); + } + deletedEntityTimer = Script.setTimeout(function () { + if (entitiesToDelete.length > 0) { + selectionManager.removeEntities(entitiesToDelete, this); + } + entityListTool.removeEntities(entitiesToDelete, selectionManager.selections); + entitiesToDelete = []; + deletedEntityTimer = null; + }, DELETE_ENTITY_TIMER_TIMEOUT); + } + + function initialize() { + Script.scriptEnding.connect(cleanup); + Window.domainChanged.connect(function () { + if (isActive) { + tablet.gotoHomeScreen(); + } + that.setActive(false); + that.clearEntityList(); + checkEditPermissionsAndUpdate(); + }); + + HMD.displayModeChanged.connect(function() { + if (isActive) { + tablet.gotoHomeScreen(); + } + that.setActive(false); + }); + + Entities.canAdjustLocksChanged.connect(function (canAdjustLocks) { + if (isActive && !canAdjustLocks) { + that.setActive(false); + } + checkEditPermissionsAndUpdate(); + }); + + Entities.canRezChanged.connect(checkEditPermissionsAndUpdate); + Entities.canRezTmpChanged.connect(checkEditPermissionsAndUpdate); + Entities.canRezCertifiedChanged.connect(checkEditPermissionsAndUpdate); + Entities.canRezTmpCertifiedChanged.connect(checkEditPermissionsAndUpdate); + var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()); + + Entities.deletingEntity.connect(checkDeletedEntityAndUpdate); + + var createButtonIconRsrc = (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON); + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + activeButton = tablet.addButton({ + captionColor: hasRezPermissions ? "#ffffff" : "#888888", + icon: createButtonIconRsrc, + activeIcon: "icons/tablet-icons/edit-a.svg", + text: "CREATE", + sortOrder: 10 + }); + createButton = activeButton; + tablet.screenChanged.connect(function (type, url) { + var isGoingToHomescreenOnDesktop = (!shouldUseEditTabletApp() && + (url === 'hifi/tablet/TabletHome.qml' || url === '')); + if (isActive && (type !== "QML" || url !== Script.resolvePath("qml/Edit.qml")) && !isGoingToHomescreenOnDesktop) { + that.setActive(false); + } + }); + tablet.fromQml.connect(fromQml); + createToolsWindow.fromQml.addListener(fromQml); + + createButton.clicked.connect(function() { + if ( ! (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()) ) { + Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG); + return; + } + + that.toggle(); + }); + + addButton("importEntitiesButton", function() { + Window.browseChanged.connect(onFileOpenChanged); + Window.browseAsync("Select Model to Import", "", "*.json"); + }); + + addButton("openAssetBrowserButton", function() { + Window.showAssetServer(); + }); + function createNewEntityDialogButtonCallback(entityType) { + return function() { + if (shouldUseEditTabletApp()) { + // tablet version of new-model dialog + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + tablet.pushOntoStack(Script.resolvePath("qml/New" + entityType + "Dialog.qml")); + } else { + closeExistingDialogWindow(); + var qmlPath = Script.resolvePath("qml/New" + entityType + "Window.qml"); + var DIALOG_WINDOW_SIZE = { x: 500, y: 300 }; + dialogWindow = Desktop.createWindow(qmlPath, { + title: "New " + entityType + " Entity", + flags: Desktop.ALWAYS_ON_TOP | Desktop.CLOSE_BUTTON_HIDES, + presentationMode: Desktop.PresentationMode.NATIVE, + size: DIALOG_WINDOW_SIZE, + visible: true + }); + dialogWindow.fromQml.connect(fromQml); + } + }; + } + + addButton("newModelButton", createNewEntityDialogButtonCallback("Model")); + + addButton("newShapeButton", function () { + createNewEntity({ + type: "Shape", + shape: "Cube", + }); + }); + + addButton("newLightButton", function () { + createNewEntity({ + type: "Light", + }); + }); + + addButton("newTextButton", function () { + createNewEntity({ + type: "Text", + }); + }); + + addButton("newImageButton", function () { + createNewEntity({ + type: "Image", + }); + }); + + addButton("newWebButton", function () { + createNewEntity({ + type: "Web", + }); + }); + + addButton("newZoneButton", function () { + createNewEntity({ + type: "Zone", + }); + }); + + addButton("newParticleButton", function () { + createNewEntity({ + type: "ParticleEffect", + }); + }); + + addButton("newMaterialButton", createNewEntityDialogButtonCallback("Material")); + + var deactivateCreateIfDesktopWindowsHidden = function() { + if (!shouldUseEditTabletApp() && !entityListTool.isVisible() && !createToolsWindow.isVisible()) { + that.setActive(false); + } + }; + entityListTool.interactiveWindowHidden.addListener(this, deactivateCreateIfDesktopWindowsHidden); + createToolsWindow.interactiveWindowHidden.addListener(this, deactivateCreateIfDesktopWindowsHidden); + + that.setActive(false); + } + + that.clearEntityList = function () { + entityListTool.clearEntityList(); + }; + + that.toggle = function () { + that.setActive(!isActive); + if (!isActive) { + tablet.gotoHomeScreen(); + } + }; + + that.setActive = function (active) { + ContextOverlay.enabled = !active; + Settings.setValue(EDIT_SETTING, active); + if (active) { + Controller.captureEntityClickEvents(); + } else { + Controller.releaseEntityClickEvents(); + + closeExistingDialogWindow(); + } + if (active === isActive) { + return; + } + if (active && !Entities.canRez() && !Entities.canRezTmp() && !Entities.canRezCertified() && !Entities.canRezTmpCertified()) { + Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG); + return; + } + Messages.sendLocalMessage("edit-events", JSON.stringify({ + enabled: active + })); + isActive = active; + activeButton.editProperties({isActive: isActive}); + undoHistory.setEnabled(isActive); + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + + if (!isActive) { + entityListTool.setVisible(false); + gridTool.setVisible(false); + grid.setEnabled(false); + propertiesTool.setVisible(false); + selectionManager.clearSelections(this); + cameraManager.disable(); + selectionDisplay.disableTriggerMapping(); + tablet.landscape = false; + Controller.disableMapping(CONTROLLER_MAPPING_NAME); + } else { + if (shouldUseEditTabletApp()) { + tablet.loadQMLSource(Script.resolvePath("qml/Edit.qml"), true); + } else { + // make other apps inactive while in desktop mode + tablet.gotoHomeScreen(); + } + UserActivityLogger.enabledEdit(); + entityListTool.setVisible(true); + entityListTool.sendUpdate(); + gridTool.setVisible(true); + grid.setEnabled(true); + propertiesTool.setVisible(true); + selectionDisplay.enableTriggerMapping(); + print("starting tablet in landscape mode"); + tablet.landscape = true; + Controller.enableMapping(CONTROLLER_MAPPING_NAME); + // Not sure what the following was meant to accomplish, but it currently causes + // everybody else to think that Interface has lost focus overall. fogbugzid:558 + // Window.setFocus(); + } + entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE)); + Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); + }; + + initialize(); + return that; +})(); + +var selectedEntityID; +var orientation; +var intersection; + + +function rayPlaneIntersection(pickRay, point, normal) { // + // + // This version of the test returns the intersection of a line with a plane + // + var collides = Vec3.dot(pickRay.direction, normal); + + var d = -Vec3.dot(point, normal); + var t = -(Vec3.dot(pickRay.origin, normal) + d) / collides; + + return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t)); +} + +function rayPlaneIntersection2(pickRay, point, normal) { + // + // This version of the test returns false if the ray is directed away from the plane + // + var collides = Vec3.dot(pickRay.direction, normal); + var d = -Vec3.dot(point, normal); + var t = -(Vec3.dot(pickRay.origin, normal) + d) / collides; + if (t < 0.0) { + return false; + } else { + return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t)); + } +} + +function findClickedEntity(event) { + var pickZones = event.isControl; + + if (pickZones) { + Entities.setZonesArePickable(true); + } + + var pickRay = Camera.computePickRay(event.x, event.y); + var tabletIDs = getMainTabletIDs(); + if (tabletIDs.length > 0) { + var overlayResult = Overlays.findRayIntersection(pickRay, true, tabletIDs); + if (overlayResult.intersects) { + return null; + } + } + + var entityResult = Entities.findRayIntersection(pickRay, true); // want precision picking + var iconResult = entityIconOverlayManager.findRayIntersection(pickRay); + iconResult.accurate = true; + + if (pickZones) { + Entities.setZonesArePickable(false); + } + + var result; + + if (iconResult.intersects) { + result = iconResult; + } else if (entityResult.intersects) { + result = entityResult; + } else { + return null; + } + + if (!result.accurate) { + return null; + } + + var foundEntity = result.entityID; + return { + pickRay: pickRay, + entityID: foundEntity, + intersection: result.intersection + }; +} + +// Handles selections on overlays while in edit mode by querying entities from +// entityIconOverlayManager. +function handleOverlaySelectionToolUpdates(channel, message, sender) { + var wantDebug = false; + if (sender !== MyAvatar.sessionUUID || channel !== 'entityToolUpdates') + return; + + var data = JSON.parse(message); + + if (data.method === "selectOverlay") { + if (!selectionDisplay.triggered() || selectionDisplay.triggeredHand === data.hand) { + if (wantDebug) { + print("setting selection to overlay " + data.overlayID); + } + var entity = entityIconOverlayManager.findEntity(data.overlayID); + + if (entity !== null) { + selectionManager.setSelections([entity], this); + } + } + } +} + +function handleMessagesReceived(channel, message, sender) { + switch( channel ){ + case 'entityToolUpdates': { + handleOverlaySelectionToolUpdates( channel, message, sender ); + break; + } + default: { + return; + } + } +} + +Messages.subscribe("entityToolUpdates"); +Messages.messageReceived.connect(handleMessagesReceived); + +var mouseHasMovedSincePress = false; +var mousePressStartTime = 0; +var mousePressStartPosition = { + x: 0, + y: 0 +}; +var mouseDown = false; + +function mousePressEvent(event) { + mouseDown = true; + mousePressStartPosition = { + x: event.x, + y: event.y + }; + mousePressStartTime = Date.now(); + mouseHasMovedSincePress = false; + mouseCapturedByTool = false; + + if (propertyMenu.mousePressEvent(event) || progressDialog.mousePressEvent(event)) { + mouseCapturedByTool = true; + return; + } + if (isActive) { + if (cameraManager.mousePressEvent(event) || selectionDisplay.mousePressEvent(event)) { + // Event handled; do nothing. + return; + } + } +} + +var mouseCapturedByTool = false; +var lastMousePosition = null; +var CLICK_TIME_THRESHOLD = 500 * 1000; // 500 ms +var CLICK_MOVE_DISTANCE_THRESHOLD = 20; +var IDLE_MOUSE_TIMEOUT = 200; + +var lastMouseMoveEvent = null; + +function mouseMoveEventBuffered(event) { + lastMouseMoveEvent = event; +} + +function mouseMove(event) { + if (mouseDown && !mouseHasMovedSincePress) { + var timeSincePressMicro = Date.now() - mousePressStartTime; + + var dX = mousePressStartPosition.x - event.x; + var dY = mousePressStartPosition.y - event.y; + var sqDist = (dX * dX) + (dY * dY); + + // If less than CLICK_TIME_THRESHOLD has passed since the mouse click AND the mouse has moved + // less than CLICK_MOVE_DISTANCE_THRESHOLD distance, then don't register this as a mouse move + // yet. The goal is to provide mouse clicks that are more lenient to small movements. + if (timeSincePressMicro < CLICK_TIME_THRESHOLD && sqDist < CLICK_MOVE_DISTANCE_THRESHOLD) { + return; + } + mouseHasMovedSincePress = true; + } + + if (!isActive) { + return; + } + + // allow the selectionDisplay and cameraManager to handle the event first, if it doesn't handle it, then do our own thing + if (selectionDisplay.mouseMoveEvent(event) || propertyMenu.mouseMoveEvent(event) || cameraManager.mouseMoveEvent(event)) { + return; + } + + lastMousePosition = { + x: event.x, + y: event.y + }; +} + +function mouseReleaseEvent(event) { + mouseDown = false; + + if (lastMouseMoveEvent) { + mouseMove(lastMouseMoveEvent); + lastMouseMoveEvent = null; + } + if (propertyMenu.mouseReleaseEvent(event)) { + return true; + } + if (isActive && selectionManager.hasSelection()) { + tooltip.show(false); + } + if (mouseCapturedByTool) { + + return; + } + + cameraManager.mouseReleaseEvent(event); + + if (!mouseHasMovedSincePress) { + mouseClickEvent(event); + } +} + +function wasTabletOrEditHandleClicked(event) { + var rayPick = Camera.computePickRay(event.x, event.y); + var result = Overlays.findRayIntersection(rayPick, true); + if (result.intersects) { + var overlayID = result.overlayID; + var tabletIDs = getMainTabletIDs(); + if (tabletIDs.indexOf(overlayID) >= 0) { + return true; + } else if (selectionDisplay.isEditHandle(overlayID)) { + return true; + } + } + return false; +} + +function mouseClickEvent(event) { + var wantDebug = false; + var result, properties, tabletClicked; + if (isActive && event.isLeftButton) { + result = findClickedEntity(event); + var tabletOrEditHandleClicked = wasTabletOrEditHandleClicked(event); + if (tabletOrEditHandleClicked) { + return; + } + + if (result === null || result === undefined) { + if (!event.isShifted) { + selectionManager.clearSelections(this); + } + return; + } + toolBar.setActive(true); + var pickRay = result.pickRay; + var foundEntity = result.entityID; + if (HMD.tabletID && foundEntity === HMD.tabletID) { + return; + } + properties = Entities.getEntityProperties(foundEntity); + var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; + + if (wantDebug) { + print("Checking properties: " + properties.id + " " + " - Half Diagonal:" + halfDiagonal); + } + // P P - Model + // /| A - Palm + // / | d B - unit vector toward tip + // / | X - base of the perpendicular line + // A---X----->B d - distance fom axis + // x x - distance from A + // + // |X-A| = (P-A).B + // X === A + ((P-A).B)B + // d = |P-X| + + var A = pickRay.origin; + var B = Vec3.normalize(pickRay.direction); + var P = properties.position; + + var x = Vec3.dot(Vec3.subtract(P, A), B); + + var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * + 180 / Math.PI; + + var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) && + (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); + + if (0 < x && sizeOK) { + selectedEntityID = foundEntity; + orientation = MyAvatar.orientation; + intersection = rayPlaneIntersection(pickRay, P, Quat.getForward(orientation)); + + if (!event.isShifted) { + selectionManager.setSelections([foundEntity], this); + } else { + selectionManager.addEntity(foundEntity, true, this); + } + selectionManager.saveProperties(); + + if (wantDebug) { + print("Model selected: " + foundEntity); + } + selectionDisplay.select(selectedEntityID, event); + + if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) { + cameraManager.enable(); + cameraManager.focus(selectionManager.worldPosition, + selectionManager.worldDimensions, + Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + } + } + } else if (event.isRightButton) { + result = findClickedEntity(event); + if (result) { + if (SHOULD_SHOW_PROPERTY_MENU !== true) { + return; + } + properties = Entities.getEntityProperties(result.entityID); + if (properties.marketplaceID) { + propertyMenu.marketplaceID = properties.marketplaceID; + propertyMenu.updateMenuItemText(showMenuItem, "Show in Marketplace"); + } else { + propertyMenu.marketplaceID = null; + propertyMenu.updateMenuItemText(showMenuItem, "No marketplace info"); + } + propertyMenu.setPosition(event.x, event.y); + propertyMenu.show(); + } else { + propertyMenu.hide(); + } + } +} + +Controller.mousePressEvent.connect(mousePressEvent); +Controller.mouseMoveEvent.connect(mouseMoveEventBuffered); +Controller.mouseReleaseEvent.connect(mouseReleaseEvent); + + +// In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already +// exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that +// added it. +var modelMenuAddedDelete = false; +var originalLightsArePickable = Entities.getLightsArePickable(); + +function setupModelMenus() { + // adj our menuitems + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Undo", + shortcutKey: 'Ctrl+Z', + position: 0, + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Redo", + shortcutKey: 'Ctrl+Y', + position: 1, + }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Entities", + isSeparator: true + }); + if (!Menu.menuItemExists("Edit", "Delete")) { + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Delete", + shortcutKeyEvent: { + text: "delete" + }, + afterItem: "Entities", + }); + modelMenuAddedDelete = true; + } + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Parent Entity to Last", + afterItem: "Entities" + }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Unparent Entity", + afterItem: "Parent Entity to Last" + }); + + Menu.addMenuItem({ + menuName: GRABBABLE_ENTITIES_MENU_CATEGORY, + menuItemName: MENU_CREATE_ENTITIES_GRABBABLE, + afterItem: "Unparent Entity", + isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_CREATE_ENTITIES_GRABBABLE, true) + }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_ALLOW_SELECTION_LARGE, + afterItem: MENU_CREATE_ENTITIES_GRABBABLE, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, true) + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_ALLOW_SELECTION_SMALL, + afterItem: MENU_ALLOW_SELECTION_LARGE, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, true) + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_ALLOW_SELECTION_LIGHTS, + afterItem: MENU_ALLOW_SELECTION_SMALL, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, false) + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Select All Entities In Box", + afterItem: "Allow Selecting of Lights" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Select All Entities Touching Box", + afterItem: "Select All Entities In Box" + }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Export Entities", + afterItem: "Entities" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Import Entities", + afterItem: "Export Entities" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "Import Entities from URL", + afterItem: "Import Entities" + }); + + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_AUTO_FOCUS_ON_SELECT, + isCheckable: true, + isChecked: Settings.getValue(SETTING_AUTO_FOCUS_ON_SELECT) === "true" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_EASE_ON_FOCUS, + afterItem: MENU_AUTO_FOCUS_ON_SELECT, + isCheckable: true, + isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) === "true" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, + afterItem: MENU_EASE_ON_FOCUS, + isCheckable: true, + isChecked: Settings.getValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE) !== "false" + }); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: MENU_SHOW_ZONES_IN_EDIT_MODE, + afterItem: MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, + isCheckable: true, + isChecked: Settings.getValue(SETTING_SHOW_ZONES_IN_EDIT_MODE) !== "false" + }); + + Entities.setLightsArePickable(false); +} + +setupModelMenus(); // do this when first running our script. + +function cleanupModelMenus() { + Menu.removeMenuItem("Edit", "Undo"); + Menu.removeMenuItem("Edit", "Redo"); + + Menu.removeSeparator("Edit", "Entities"); + if (modelMenuAddedDelete) { + // delete our menuitems + Menu.removeMenuItem("Edit", "Delete"); + } + + Menu.removeMenuItem("Edit", "Parent Entity to Last"); + Menu.removeMenuItem("Edit", "Unparent Entity"); + Menu.removeMenuItem("Edit", "Allow Selecting of Large Models"); + Menu.removeMenuItem("Edit", "Allow Selecting of Small Models"); + Menu.removeMenuItem("Edit", "Allow Selecting of Lights"); + Menu.removeMenuItem("Edit", "Select All Entities In Box"); + Menu.removeMenuItem("Edit", "Select All Entities Touching Box"); + + Menu.removeMenuItem("Edit", "Export Entities"); + Menu.removeMenuItem("Edit", "Import Entities"); + Menu.removeMenuItem("Edit", "Import Entities from URL"); + + Menu.removeMenuItem("Edit", MENU_AUTO_FOCUS_ON_SELECT); + Menu.removeMenuItem("Edit", MENU_EASE_ON_FOCUS); + Menu.removeMenuItem("Edit", MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE); + Menu.removeMenuItem("Edit", MENU_SHOW_ZONES_IN_EDIT_MODE); + Menu.removeMenuItem("Edit", MENU_CREATE_ENTITIES_GRABBABLE); +} + +Script.scriptEnding.connect(function () { + toolBar.setActive(false); + Settings.setValue(SETTING_AUTO_FOCUS_ON_SELECT, Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)); + Settings.setValue(SETTING_EASE_ON_FOCUS, Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + Settings.setValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE)); + Settings.setValue(SETTING_SHOW_ZONES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); + + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE)); + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL)); + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LIGHTS)); + + + progressDialog.cleanup(); + cleanupModelMenus(); + tooltip.cleanup(); + selectionDisplay.cleanup(); + entityShapeVisualizer.cleanup(); + Entities.setLightsArePickable(originalLightsArePickable); + + Overlays.deleteOverlay(importingSVOImageOverlay); + Overlays.deleteOverlay(importingSVOTextOverlay); + + Controller.keyReleaseEvent.disconnect(keyReleaseEvent); + Controller.keyPressEvent.disconnect(keyPressEvent); + + Controller.mousePressEvent.disconnect(mousePressEvent); + Controller.mouseMoveEvent.disconnect(mouseMoveEventBuffered); + Controller.mouseReleaseEvent.disconnect(mouseReleaseEvent); + + Messages.messageReceived.disconnect(handleMessagesReceived); + Messages.unsubscribe("entityToolUpdates"); + createButton = null; +}); + +var lastOrientation = null; +var lastPosition = null; + +// Do some stuff regularly, like check for placement of various overlays +Script.update.connect(function (deltaTime) { + progressDialog.move(); + selectionDisplay.checkControllerMove(); + var dOrientation = Math.abs(Quat.dot(Camera.orientation, lastOrientation) - 1); + var dPosition = Vec3.distance(Camera.position, lastPosition); + if (dOrientation > 0.001 || dPosition > 0.001) { + propertyMenu.hide(); + lastOrientation = Camera.orientation; + lastPosition = Camera.position; + } + if (lastMouseMoveEvent) { + mouseMove(lastMouseMoveEvent); + lastMouseMoveEvent = null; + } +}); + +function insideBox(center, dimensions, point) { + return (Math.abs(point.x - center.x) <= (dimensions.x / 2.0)) && + (Math.abs(point.y - center.y) <= (dimensions.y / 2.0)) && + (Math.abs(point.z - center.z) <= (dimensions.z / 2.0)); +} + +function selectAllEntitiesInCurrentSelectionBox(keepIfTouching) { + if (selectionManager.hasSelection()) { + // Get all entities touching the bounding box of the current selection + var boundingBoxCorner = Vec3.subtract(selectionManager.worldPosition, + Vec3.multiply(selectionManager.worldDimensions, 0.5)); + var entities = Entities.findEntitiesInBox(boundingBoxCorner, selectionManager.worldDimensions); + + if (!keepIfTouching) { + var isValid; + if (selectionManager.localPosition === null || selectionManager.localPosition === undefined) { + isValid = function (position) { + return insideBox(selectionManager.worldPosition, selectionManager.worldDimensions, position); + }; + } else { + isValid = function (position) { + var localPosition = Vec3.multiplyQbyV(Quat.inverse(selectionManager.localRotation), + Vec3.subtract(position, + selectionManager.localPosition)); + return insideBox(Vec3.ZERO, selectionManager.localDimensions, localPosition); + }; + } + for (var i = 0; i < entities.length; ++i) { + var properties = Entities.getEntityProperties(entities[i]); + if (!isValid(properties.position)) { + entities.splice(i, 1); + --i; + } + } + } + selectionManager.setSelections(entities, this); + } +} + +function sortSelectedEntities(selected) { + var sortedEntities = selected.slice(); + var begin = 0; + while (begin < sortedEntities.length) { + var elementRemoved = false; + var next = begin + 1; + while (next < sortedEntities.length) { + var beginID = sortedEntities[begin]; + var nextID = sortedEntities[next]; + + if (Entities.isChildOfParent(beginID, nextID)) { + sortedEntities[begin] = nextID; + sortedEntities[next] = beginID; + sortedEntities.splice(next, 1); + elementRemoved = true; + break; + } else if (Entities.isChildOfParent(nextID, beginID)) { + sortedEntities.splice(next, 1); + elementRemoved = true; + break; + } + next++; + } + if (!elementRemoved) { + begin++; + } + } + return sortedEntities; +} + +function recursiveDelete(entities, childrenList, deletedIDs, entityHostType) { + var wantDebug = false; + var entitiesLength = entities.length; + var initialPropertySets = Entities.getMultipleEntityProperties(entities); + var entityHostTypes = Entities.getMultipleEntityProperties(entities, 'entityHostType'); + for (var i = 0; i < entitiesLength; ++i) { + var entityID = entities[i]; + + if (entityHostTypes[i].entityHostType !== entityHostType) { + if (wantDebug) { + console.log("Skipping deletion of entity " + entityID + " with conflicting entityHostType: " + + entityHostTypes[i].entityHostType + ", expected: " + entityHostType); + } + continue; + } + + var children = Entities.getChildrenIDs(entityID); + var grandchildrenList = []; + recursiveDelete(children, grandchildrenList, deletedIDs, entityHostType); + childrenList.push({ + entityID: entityID, + properties: initialPropertySets[i], + children: grandchildrenList + }); + deletedIDs.push(entityID); + Entities.deleteEntity(entityID); + } +} + +function unparentSelectedEntities() { + if (SelectionManager.hasSelection()) { + var selectedEntities = selectionManager.selections; + var parentCheck = false; + + if (selectedEntities.length < 1) { + Window.notifyEditError("You must have an entity selected in order to unparent it."); + return; + } + selectedEntities.forEach(function (id, index) { + var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID; + if (parentId !== null && parentId.length > 0 && parentId !== Uuid.NULL) { + parentCheck = true; + } + Entities.editEntity(id, {parentID: null}); + return true; + }); + if (parentCheck) { + if (selectedEntities.length > 1) { + Window.notify("Entities unparented"); + } else { + Window.notify("Entity unparented"); + } + } else { + if (selectedEntities.length > 1) { + Window.notify("Selected Entities have no parents"); + } else { + Window.notify("Selected Entity does not have a parent"); + } + } + } else { + Window.notifyEditError("You have nothing selected to unparent"); + } +} +function parentSelectedEntities() { + if (SelectionManager.hasSelection()) { + var selectedEntities = selectionManager.selections; + if (selectedEntities.length <= 1) { + Window.notifyEditError("You must have multiple entities selected in order to parent them"); + return; + } + var parentCheck = false; + var lastEntityId = selectedEntities[selectedEntities.length - 1]; + selectedEntities.forEach(function (id, index) { + if (lastEntityId !== id) { + var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID; + if (parentId !== lastEntityId) { + parentCheck = true; + } + Entities.editEntity(id, {parentID: lastEntityId}); + } + }); + + if (parentCheck) { + Window.notify("Entities parented"); + } else { + Window.notify("Entities are already parented to last"); + } + } else { + Window.notifyEditError("You have nothing selected to parent"); + } +} +function deleteSelectedEntities() { + if (SelectionManager.hasSelection()) { + var deletedIDs = []; + + SelectionManager.saveProperties(); + var savedProperties = []; + var newSortedSelection = sortSelectedEntities(selectionManager.selections); + var entityHostTypes = Entities.getMultipleEntityProperties(newSortedSelection, 'entityHostType'); + for (var i = 0; i < newSortedSelection.length; ++i) { + var entityID = newSortedSelection[i]; + var initialProperties = SelectionManager.savedProperties[entityID]; + if (initialProperties.locked || + (initialProperties.avatarEntity && initialProperties.owningAvatarID !== MyAvatar.sessionUUID)) { + continue; + } + var children = Entities.getChildrenIDs(entityID); + var childList = []; + recursiveDelete(children, childList, deletedIDs, entityHostTypes[i].entityHostType); + savedProperties.push({ + entityID: entityID, + properties: initialProperties, + children: childList + }); + deletedIDs.push(entityID); + Entities.deleteEntity(entityID); + } + + if (savedProperties.length > 0) { + SelectionManager.clearSelections(this); + pushCommandForSelections([], savedProperties); + entityListTool.deleteEntities(deletedIDs); + } + } +} + +function toggleSelectedEntitiesLocked() { + if (SelectionManager.hasSelection()) { + var locked = !Entities.getEntityProperties(SelectionManager.selections[0], ["locked"]).locked; + for (var i = 0; i < selectionManager.selections.length; i++) { + var entityID = SelectionManager.selections[i]; + Entities.editEntity(entityID, { + locked: locked + }); + } + entityListTool.sendUpdate(); + selectionManager._update(false, this); + } +} + +function toggleSelectedEntitiesVisible() { + if (SelectionManager.hasSelection()) { + var visible = !Entities.getEntityProperties(SelectionManager.selections[0], ["visible"]).visible; + for (var i = 0; i < selectionManager.selections.length; i++) { + var entityID = SelectionManager.selections[i]; + Entities.editEntity(entityID, { + visible: visible + }); + } + entityListTool.sendUpdate(); + selectionManager._update(false, this); + } +} + +function onFileSaveChanged(filename) { + Window.saveFileChanged.disconnect(onFileSaveChanged); + if (filename !== "") { + var success = Clipboard.exportEntities(filename, selectionManager.selections); + if (!success) { + Window.notifyEditError("Export failed."); + } + } +} + +function onFileOpenChanged(filename) { + // disconnect the event, otherwise the requests will stack up + try { + // Not all calls to onFileOpenChanged() connect an event. + Window.browseChanged.disconnect(onFileOpenChanged); + } catch (e) { + // Ignore. + } + + var importURL = null; + if (filename !== "") { + importURL = filename; + if (!/^(http|https):\/\//.test(filename)) { + importURL = "file:///" + importURL; + } + } + if (importURL) { + if (!isActive && (Entities.canRez() && Entities.canRezTmp() && Entities.canRezCertified() && Entities.canRezTmpCertified())) { + toolBar.toggle(); + } + importSVO(importURL); + } +} + +function onPromptTextChanged(prompt) { + Window.promptTextChanged.disconnect(onPromptTextChanged); + if (prompt !== "") { + if (!isActive && (Entities.canRez() && Entities.canRezTmp() && Entities.canRezCertified() && Entities.canRezTmpCertified())) { + toolBar.toggle(); + } + importSVO(prompt); + } +} + +function handleMenuEvent(menuItem) { + if (menuItem === "Allow Selecting of Small Models") { + allowSmallModels = Menu.isOptionChecked("Allow Selecting of Small Models"); + } else if (menuItem === "Allow Selecting of Large Models") { + allowLargeModels = Menu.isOptionChecked("Allow Selecting of Large Models"); + } else if (menuItem === "Allow Selecting of Lights") { + Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights")); + } else if (menuItem === "Delete") { + deleteSelectedEntities(); + } else if (menuItem === "Undo") { + undoHistory.undo(); + } else if (menuItem === "Redo") { + undoHistory.redo(); + } else if (menuItem === "Parent Entity to Last") { + parentSelectedEntities(); + } else if (menuItem === "Unparent Entity") { + unparentSelectedEntities(); + } else if (menuItem === "Export Entities") { + if (!selectionManager.hasSelection()) { + Window.notifyEditError("No entities have been selected."); + } else { + Window.saveFileChanged.connect(onFileSaveChanged); + Window.saveAsync("Select Where to Save", "", "*.json"); + } + } else if (menuItem === "Import Entities" || menuItem === "Import Entities from URL") { + if (menuItem === "Import Entities") { + Window.browseChanged.connect(onFileOpenChanged); + Window.browseAsync("Select Model to Import", "", "*.json"); + } else { + Window.promptTextChanged.connect(onPromptTextChanged); + Window.promptAsync("URL of SVO to import", ""); + } + } else if (menuItem === "Select All Entities In Box") { + selectAllEntitiesInCurrentSelectionBox(false); + } else if (menuItem === "Select All Entities Touching Box") { + selectAllEntitiesInCurrentSelectionBox(true); + } else if (menuItem === MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE) { + entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE)); + } else if (menuItem === MENU_SHOW_ZONES_IN_EDIT_MODE) { + Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); + } else if (menuItem === MENU_CREATE_ENTITIES_GRABBABLE) { + Settings.setValue(SETTING_EDIT_PREFIX + menuItem, Menu.isOptionChecked(menuItem)); + } + tooltip.show(false); +} + +var HALF_TREE_SCALE = 16384; + +function getPositionToCreateEntity(extra) { + var CREATE_DISTANCE = 2; + var position; + var delta = extra !== undefined ? extra : 0; + if (Camera.mode === "entity" || Camera.mode === "independent") { + position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta)); + } else { + position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta)); + } + + if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) { + return null; + } + return position; +} + +function importSVO(importURL) { + if (!Entities.canRez() && !Entities.canRezTmp() && + !Entities.canRezCertified() && !Entities.canRezTmpCertified()) { + Window.notifyEditError(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG); + return; + } + + Overlays.editOverlay(importingSVOTextOverlay, { + visible: true + }); + Overlays.editOverlay(importingSVOImageOverlay, { + visible: true + }); + + var success = Clipboard.importEntities(importURL); + + if (success) { + var VERY_LARGE = 10000; + var isLargeImport = Clipboard.getClipboardContentsLargestDimension() >= VERY_LARGE; + var position = Vec3.ZERO; + if (!isLargeImport) { + position = getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2); + } + if (position !== null && position !== undefined) { + var pastedEntityIDs = Clipboard.pasteEntities(position); + if (!isLargeImport) { + // The first entity in Clipboard gets the specified position with the rest being relative to it. Therefore, move + // entities after they're imported so that they're all the correct distance in front of and with geometric mean + // centered on the avatar/camera direction. + var deltaPosition = Vec3.ZERO; + var entityPositions = []; + var entityParentIDs = []; + + var propType = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]).type; + var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"]; + if (NO_ADJUST_ENTITY_TYPES.indexOf(propType) === -1) { + var targetDirection; + if (Camera.mode === "entity" || Camera.mode === "independent") { + targetDirection = Camera.orientation; + } else { + targetDirection = MyAvatar.orientation; + } + targetDirection = Vec3.multiplyQbyV(targetDirection, Vec3.UNIT_Z); + + var targetPosition = getPositionToCreateEntity(); + var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection. + var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection. + for (var i = 0, length = pastedEntityIDs.length; i < length; i++) { + var curLoopEntityProps = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions", + "registrationPoint", "rotation", "parentID"]); + var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection, + curLoopEntityProps.registrationPoint, curLoopEntityProps.dimensions, curLoopEntityProps.rotation); + var delta = Vec3.subtract(adjustedPosition, curLoopEntityProps.position); + var distance = Vec3.dot(delta, targetDirection); + deltaParallel = Math.min(distance, deltaParallel); + deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)), + deltaPerpendicular); + entityPositions[i] = curLoopEntityProps.position; + entityParentIDs[i] = curLoopEntityProps.parentID; + } + deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular); + deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular); + } + + if (grid.getSnapToGrid()) { + var firstEntityProps = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions", + "registrationPoint"]); + var positionPreSnap = Vec3.sum(deltaPosition, firstEntityProps.position); + position = grid.snapToSurface(grid.snapToGrid(positionPreSnap, false, firstEntityProps.dimensions, + firstEntityProps.registrationPoint), firstEntityProps.dimensions, firstEntityProps.registrationPoint); + deltaPosition = Vec3.subtract(position, firstEntityProps.position); + } + + if (!Vec3.equal(deltaPosition, Vec3.ZERO)) { + for (var editEntityIndex = 0, numEntities = pastedEntityIDs.length; editEntityIndex < numEntities; editEntityIndex++) { + if (Uuid.isNull(entityParentIDs[editEntityIndex])) { + Entities.editEntity(pastedEntityIDs[editEntityIndex], { + position: Vec3.sum(deltaPosition, entityPositions[editEntityIndex]) + }); + } + } + } + } + + if (isActive) { + selectionManager.setSelections(pastedEntityIDs, this); + } + } else { + Window.notifyEditError("Can't import entities: entities would be out of bounds."); + } + } else { + Window.notifyEditError("There was an error importing the entity file."); + } + + Overlays.editOverlay(importingSVOTextOverlay, { + visible: false + }); + Overlays.editOverlay(importingSVOImageOverlay, { + visible: false + }); +} +Window.svoImportRequested.connect(importSVO); + +Menu.menuItemEvent.connect(handleMenuEvent); + +var keyPressEvent = function (event) { + if (isActive) { + cameraManager.keyPressEvent(event); + } +}; +var keyReleaseEvent = function (event) { + if (isActive) { + cameraManager.keyReleaseEvent(event); + } +}; +Controller.keyReleaseEvent.connect(keyReleaseEvent); +Controller.keyPressEvent.connect(keyPressEvent); + +function deleteKey(value) { + if (value === 0) { // on release + deleteSelectedEntities(); + } +} +function deselectKey(value) { + if (value === 0) { // on release + selectionManager.clearSelections(this); + } +} +function toggleKey(value) { + if (value === 0) { // on release + selectionDisplay.toggleSpaceMode(); + } +} +function focusKey(value) { + if (value === 0) { // on release + cameraManager.enable(); + if (selectionManager.hasSelection()) { + cameraManager.focus(selectionManager.worldPosition, selectionManager.worldDimensions, + Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + } + } +} +function gridKey(value) { + if (value === 0) { // on release + if (selectionManager.hasSelection()) { + grid.moveToSelection(); + } + } +} +function recursiveAdd(newParentID, parentData) { + if (parentData.children !== undefined) { + var children = parentData.children; + for (var i = 0; i < children.length; i++) { + var childProperties = children[i].properties; + childProperties.parentID = newParentID; + var newChildID = Entities.addEntity(childProperties); + recursiveAdd(newChildID, children[i]); + } + } +} + +var UndoHistory = function(onUpdate) { + this.history = []; + // The current position is the index of the last executed action in the history array. + // + // -1 0 1 2 3 <- position + // A B C D <- actions in history + // + // If our lastExecutedIndex is 1, the last executed action is B. + // If we undo, we undo B (index 1). If we redo, we redo C (index 2). + this.lastExecutedIndex = -1; + this.enabled = true; + this.onUpdate = onUpdate; +}; + +UndoHistory.prototype.pushCommand = function(undoFn, undoArgs, redoFn, redoArgs) { + if (!this.enabled) { + return; + } + // Delete any history following the last executed action. + this.history.splice(this.lastExecutedIndex + 1); + this.history.push({ + undoFn: undoFn, + undoArgs: undoArgs, + redoFn: redoFn, + redoArgs: redoArgs + }); + this.lastExecutedIndex++; + + if (this.onUpdate) { + this.onUpdate(); + } +}; +UndoHistory.prototype.setEnabled = function(enabled) { + this.enabled = enabled; + if (this.onUpdate) { + this.onUpdate(); + } +}; +UndoHistory.prototype.canUndo = function() { + return this.enabled && this.lastExecutedIndex >= 0; +}; +UndoHistory.prototype.canRedo = function() { + return this.enabled && this.lastExecutedIndex < this.history.length - 1; +}; +UndoHistory.prototype.undo = function() { + if (!this.canUndo()) { + console.warn("Cannot undo action"); + return; + } + + var command = this.history[this.lastExecutedIndex]; + command.undoFn(command.undoArgs); + this.lastExecutedIndex--; + + if (this.onUpdate) { + this.onUpdate(); + } +}; +UndoHistory.prototype.redo = function() { + if (!this.canRedo()) { + console.warn("Cannot redo action"); + return; + } + + var command = this.history[this.lastExecutedIndex + 1]; + command.redoFn(command.redoArgs); + this.lastExecutedIndex++; + + if (this.onUpdate) { + this.onUpdate(); + } +}; + +function updateUndoRedoMenuItems() { + Menu.setMenuEnabled("Edit > Undo", undoHistory.canUndo()); + Menu.setMenuEnabled("Edit > Redo", undoHistory.canRedo()); +} +var undoHistory = new UndoHistory(updateUndoRedoMenuItems); +updateUndoRedoMenuItems(); + +// When an entity has been deleted we need a way to "undo" this deletion. Because it's not currently +// possible to create an entity with a specific id, earlier undo commands to the deleted entity +// will fail if there isn't a way to find the new entity id. +var DELETED_ENTITY_MAP = {}; + +function applyEntityProperties(data) { + var editEntities = data.editEntities; + var createEntities = data.createEntities; + var deleteEntities = data.deleteEntities; + var selectedEntityIDs = []; + var selectEdits = createEntities.length === 0 || !data.selectCreated; + var i, entityID, entityProperties; + for (i = 0; i < createEntities.length; i++) { + entityID = createEntities[i].entityID; + entityProperties = createEntities[i].properties; + var newEntityID = Entities.addEntity(entityProperties); + recursiveAdd(newEntityID, createEntities[i]); + DELETED_ENTITY_MAP[entityID] = newEntityID; + if (data.selectCreated) { + selectedEntityIDs.push(newEntityID); + } + } + for (i = 0; i < deleteEntities.length; i++) { + entityID = deleteEntities[i].entityID; + if (DELETED_ENTITY_MAP[entityID] !== undefined) { + entityID = DELETED_ENTITY_MAP[entityID]; + } + Entities.deleteEntity(entityID); + var index = selectedEntityIDs.indexOf(entityID); + if (index >= 0) { + selectedEntityIDs.splice(index, 1); + } + } + for (i = 0; i < editEntities.length; i++) { + entityID = editEntities[i].entityID; + if (DELETED_ENTITY_MAP[entityID] !== undefined) { + entityID = DELETED_ENTITY_MAP[entityID]; + } + entityProperties = editEntities[i].properties; + if (entityProperties !== null) { + Entities.editEntity(entityID, entityProperties); + } + if (selectEdits) { + selectedEntityIDs.push(entityID); + } + } + + // We might be getting an undo while edit.js is disabled. If that is the case, don't set + // our selections, causing the edit widgets to display. + if (isActive) { + selectionManager.setSelections(selectedEntityIDs, this); + selectionManager.saveProperties(); + } +} + +// For currently selected entities, push a command to the UndoStack that uses the current entity properties for the +// redo command, and the saved properties for the undo command. Also, include create and delete entity data. +function pushCommandForSelections(createdEntityData, deletedEntityData, doNotSaveEditProperties) { + doNotSaveEditProperties = false; + var undoData = { + editEntities: [], + createEntities: deletedEntityData || [], + deleteEntities: createdEntityData || [], + selectCreated: true + }; + var redoData = { + editEntities: [], + createEntities: createdEntityData || [], + deleteEntities: deletedEntityData || [], + selectCreated: true + }; + for (var i = 0; i < SelectionManager.selections.length; i++) { + var entityID = SelectionManager.selections[i]; + var initialProperties = SelectionManager.savedProperties[entityID]; + var currentProperties = null; + if (!initialProperties) { + continue; + } + + if (doNotSaveEditProperties) { + initialProperties = null; + } else { + currentProperties = Entities.getEntityProperties(entityID); + } + + undoData.editEntities.push({ + entityID: entityID, + properties: initialProperties + }); + redoData.editEntities.push({ + entityID: entityID, + properties: currentProperties + }); + } + undoHistory.pushCommand(applyEntityProperties, undoData, applyEntityProperties, redoData); +} + +var ServerScriptStatusMonitor = function(entityID, statusCallback) { + var self = this; + + self.entityID = entityID; + self.active = true; + self.sendRequestTimerID = null; + + var onStatusReceived = function(success, isRunning, status, errorInfo) { + if (self.active) { + statusCallback({ + statusRetrieved: success, + isRunning: isRunning, + status: status, + errorInfo: errorInfo + }); + self.sendRequestTimerID = Script.setTimeout(function() { + if (self.active) { + Entities.getServerScriptStatus(entityID, onStatusReceived); + } + }, 1000); + } + }; + self.stop = function() { + self.active = false; + }; + + Entities.getServerScriptStatus(entityID, onStatusReceived); +}; + +var PropertiesTool = function (opts) { + var that = {}; + + var webView = null; + webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + webView.setVisible = function(value) {}; + + var visible = false; + + // This keeps track of the last entity ID that was selected. If multiple entities + // are selected or if no entity is selected this will be `null`. + var currentSelectedEntityID = null; + var statusMonitor = null; + var blockPropertyUpdates = false; + + that.setVisible = function (newVisible) { + visible = newVisible; + webView.setVisible(shouldUseEditTabletApp() && visible); + createToolsWindow.setVisible(!shouldUseEditTabletApp() && visible); + }; + + that.setVisible(false); + + function emitScriptEvent(data) { + var dataString = JSON.stringify(data); + webView.emitScriptEvent(dataString); + createToolsWindow.emitScriptEvent(dataString); + } + + function updateScriptStatus(info) { + info.type = "server_script_status"; + emitScriptEvent(info); + } + + function resetScriptStatus() { + updateScriptStatus({ + statusRetrieved: undefined, + isRunning: undefined, + status: "", + errorInfo: "" + }); + } + + that.setSpaceMode = function(spaceMode) { + emitScriptEvent({ + type: 'setSpaceMode', + spaceMode: spaceMode + }) + }; + + function updateSelections(selectionUpdated, caller) { + if (blockPropertyUpdates) { + return; + } + + var data = { + type: 'update', + spaceMode: selectionDisplay.getSpaceMode(), + isPropertiesToolUpdate: caller === this, + }; + + if (selectionUpdated) { + resetScriptStatus(); + + if (selectionManager.selections.length !== 1) { + if (statusMonitor !== null) { + statusMonitor.stop(); + statusMonitor = null; + } + currentSelectedEntityID = null; + } else if (currentSelectedEntityID !== selectionManager.selections[0]) { + if (statusMonitor !== null) { + statusMonitor.stop(); + } + var entityID = selectionManager.selections[0]; + currentSelectedEntityID = entityID; + statusMonitor = new ServerScriptStatusMonitor(entityID, updateScriptStatus); + } + } + + var selections = []; + for (var i = 0; i < selectionManager.selections.length; i++) { + var entity = {}; + entity.id = selectionManager.selections[i]; + entity.properties = Entities.getEntityProperties(selectionManager.selections[i]); + if (entity.properties.rotation !== undefined) { + entity.properties.rotation = Quat.safeEulerAngles(entity.properties.rotation); + } + if (entity.properties.localRotation !== undefined) { + entity.properties.localRotation = Quat.safeEulerAngles(entity.properties.localRotation); + } + if (entity.properties.emitOrientation !== undefined) { + entity.properties.emitOrientation = Quat.safeEulerAngles(entity.properties.emitOrientation); + } + if (entity.properties.keyLight !== undefined && entity.properties.keyLight.direction !== undefined) { + entity.properties.keyLight.direction = Vec3.toPolar(entity.properties.keyLight.direction); + entity.properties.keyLight.direction.z = 0.0; + } + selections.push(entity); + } + data.selections = selections; + + emitScriptEvent(data); + } + selectionManager.addEventListener(updateSelections, this); + + + var onWebEventReceived = function(data) { + try { + data = JSON.parse(data); + } catch(e) { + return; + } + var i, properties, dY, diff, newPosition; + if (data.type === "update") { + + if (data.properties || data.propertiesMap) { + var propertiesMap = data.propertiesMap; + if (propertiesMap === undefined) { + propertiesMap = [{ + entityIDs: data.ids, + properties: data.properties, + }]; + } + + var sendListUpdate = false; + propertiesMap.forEach(function(propertiesObject) { + var properties = propertiesObject.properties; + var updateEntityIDs = propertiesObject.entityIDs; + if (properties.dynamic === false) { + // this object is leaving dynamic, so we zero its velocities + properties.localVelocity = Vec3.ZERO; + properties.localAngularVelocity = Vec3.ZERO; + } + if (properties.rotation !== undefined) { + properties.rotation = Quat.fromVec3Degrees(properties.rotation); + } + if (properties.localRotation !== undefined) { + properties.localRotation = Quat.fromVec3Degrees(properties.localRotation); + } + if (properties.emitOrientation !== undefined) { + properties.emitOrientation = Quat.fromVec3Degrees(properties.emitOrientation); + } + if (properties.keyLight !== undefined && properties.keyLight.direction !== undefined) { + var currentKeyLightDirection = Vec3.toPolar(Entities.getEntityProperties(selectionManager.selections[0], ['keyLight.direction']).keyLight.direction); + if (properties.keyLight.direction.x === undefined) { + properties.keyLight.direction.x = currentKeyLightDirection.x; + } + if (properties.keyLight.direction.y === undefined) { + properties.keyLight.direction.y = currentKeyLightDirection.y; + } + properties.keyLight.direction = Vec3.fromPolar(properties.keyLight.direction.x, properties.keyLight.direction.y); + } + + updateEntityIDs.forEach(function (entityID) { + Entities.editEntity(entityID, properties); + }); + + if (properties.name !== undefined || properties.modelURL !== undefined || properties.materialURL !== undefined || + properties.visible !== undefined || properties.locked !== undefined) { + + sendListUpdate = true; + } + + }); + if (sendListUpdate) { + entityListTool.sendUpdate(); + } + } + + + if (data.onlyUpdateEntities) { + blockPropertyUpdates = true; + } else { + pushCommandForSelections(); + SelectionManager.saveProperties(); + } + selectionManager._update(false, this); + blockPropertyUpdates = false; + } else if (data.type === 'saveUserData' || data.type === 'saveMaterialData') { + data.ids.forEach(function(entityID) { + Entities.editEntity(entityID, data.properties); + }); + } else if (data.type === "showMarketplace") { + showMarketplace(); + } else if (data.type === "action") { + if (data.action === "moveSelectionToGrid") { + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + dY = grid.getOrigin().y - (selectionManager.worldPosition.y - selectionManager.worldDimensions.y / 2); + diff = { + x: 0, + y: dY, + z: 0 + }; + for (i = 0; i < selectionManager.selections.length; i++) { + properties = selectionManager.savedProperties[selectionManager.selections[i]]; + newPosition = Vec3.sum(properties.position, diff); + Entities.editEntity(selectionManager.selections[i], { + position: newPosition + }); + } + pushCommandForSelections(); + selectionManager._update(false, this); + } + } else if (data.action === "moveAllToGrid") { + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + for (i = 0; i < selectionManager.selections.length; i++) { + properties = selectionManager.savedProperties[selectionManager.selections[i]]; + var bottomY = properties.boundingBox.center.y - properties.boundingBox.dimensions.y / 2; + dY = grid.getOrigin().y - bottomY; + diff = { + x: 0, + y: dY, + z: 0 + }; + newPosition = Vec3.sum(properties.position, diff); + Entities.editEntity(selectionManager.selections[i], { + position: newPosition + }); + } + pushCommandForSelections(); + selectionManager._update(false, this); + } + } else if (data.action === "resetToNaturalDimensions") { + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + for (i = 0; i < selectionManager.selections.length; i++) { + properties = selectionManager.savedProperties[selectionManager.selections[i]]; + var naturalDimensions = properties.naturalDimensions; + + // If any of the natural dimensions are not 0, resize + if (properties.type === "Model" && naturalDimensions.x === 0 && naturalDimensions.y === 0 && + naturalDimensions.z === 0) { + Window.notifyEditError("Cannot reset entity to its natural dimensions: Model URL" + + " is invalid or the model has not yet been loaded."); + } else { + Entities.editEntity(selectionManager.selections[i], { + dimensions: properties.naturalDimensions + }); + } + } + pushCommandForSelections(); + selectionManager._update(false, this); + } + } else if (data.action === "previewCamera") { + if (selectionManager.hasSelection()) { + Camera.mode = "entity"; + Camera.cameraEntity = selectionManager.selections[0]; + } + } else if (data.action === "rescaleDimensions") { + var multiplier = data.percentage / 100.0; + if (selectionManager.hasSelection()) { + selectionManager.saveProperties(); + for (i = 0; i < selectionManager.selections.length; i++) { + properties = selectionManager.savedProperties[selectionManager.selections[i]]; + Entities.editEntity(selectionManager.selections[i], { + dimensions: Vec3.multiply(multiplier, properties.dimensions) + }); + } + pushCommandForSelections(); + selectionManager._update(false, this); + } + } else if (data.action === "reloadClientScripts") { + if (selectionManager.hasSelection()) { + var timestamp = Date.now(); + for (i = 0; i < selectionManager.selections.length; i++) { + Entities.editEntity(selectionManager.selections[i], { + scriptTimestamp: timestamp + }); + } + } + } else if (data.action === "reloadServerScripts") { + if (selectionManager.hasSelection()) { + for (i = 0; i < selectionManager.selections.length; i++) { + Entities.reloadServerScripts(selectionManager.selections[i]); + } + } + } + } else if (data.type === "propertiesPageReady") { + updateSelections(true); + } else if (data.type === "tooltipsRequest") { + emitScriptEvent({ + type: 'tooltipsReply', + tooltips: Script.require('./assets/data/createAppTooltips.json'), + hmdActive: HMD.active, + }); + } else if (data.type === "propertyRangeRequest") { + var propertyRanges = {}; + data.properties.forEach(function (property) { + propertyRanges[property] = Entities.getPropertyInfo(property); + }); + emitScriptEvent({ + type: 'propertyRangeReply', + propertyRanges: propertyRanges, + }); + } else if (data.type === "materialTargetRequest") { + var parentModelData; + var properties = Entities.getEntityProperties(data.entityID, ["type", "parentID"]); + if (properties.type === "Material" && properties.parentID !== Uuid.NULL) { + var parentType = Entities.getEntityProperties(properties.parentID, ["type"]).type; + if (parentType === "Model" || Entities.getNestableType(properties.parentID) === "avatar") { + parentModelData = Graphics.getModel(properties.parentID); + } else if (parentType === "Shape" || parentType === "Box" || parentType === "Sphere") { + parentModelData = {}; + parentModelData.numMeshes = 1; + parentModelData.materialNames = []; + } + } + emitScriptEvent({ + type: 'materialTargetReply', + entityID: data.entityID, + materialTargetData: parentModelData, + }); + } + }; + + HMD.displayModeChanged.connect(function() { + emitScriptEvent({ + type: 'hmdActiveChanged', + hmdActive: HMD.active, + }); + }); + + createToolsWindow.webEventReceived.addListener(this, onWebEventReceived); + + webView.webEventReceived.connect(this, onWebEventReceived); + + return that; +}; + + +var PopupMenu = function () { + var self = this; + + var MENU_ITEM_HEIGHT = 21; + var MENU_ITEM_SPACING = 1; + var TEXT_MARGIN = 7; + + var overlays = []; + var overlayInfo = {}; + + var visible = false; + + var upColor = { + red: 0, + green: 0, + blue: 0 + }; + var downColor = { + red: 192, + green: 192, + blue: 192 + }; + var overColor = { + red: 128, + green: 128, + blue: 128 + }; + + self.onSelectMenuItem = function () {}; + + self.addMenuItem = function (name) { + var id = Overlays.addOverlay("text", { + text: name, + backgroundAlpha: 1.0, + backgroundColor: upColor, + topMargin: TEXT_MARGIN, + leftMargin: TEXT_MARGIN, + width: 210, + height: MENU_ITEM_HEIGHT, + font: { + size: 12 + }, + visible: false + }); + overlays.push(id); + overlayInfo[id] = { + name: name + }; + return id; + }; + + self.updateMenuItemText = function (id, newText) { + Overlays.editOverlay(id, { + text: newText + }); + }; + + self.setPosition = function (x, y) { + for (var key in overlayInfo) { + Overlays.editOverlay(key, { + x: x, + y: y + }); + y += MENU_ITEM_HEIGHT + MENU_ITEM_SPACING; + } + }; + + self.onSelected = function () {}; + + var pressingOverlay = null; + var hoveringOverlay = null; + + self.mousePressEvent = function (event) { + if (event.isLeftButton) { + var overlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (overlay in overlayInfo) { + pressingOverlay = overlay; + Overlays.editOverlay(pressingOverlay, { + backgroundColor: downColor + }); + } else { + self.hide(); + } + return false; + } + }; + self.mouseMoveEvent = function (event) { + if (visible) { + var overlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (!pressingOverlay) { + if (hoveringOverlay !== null && overlay !== hoveringOverlay) { + Overlays.editOverlay(hoveringOverlay, { + backgroundColor: upColor + }); + hoveringOverlay = null; + } + if (overlay !== hoveringOverlay && overlay in overlayInfo) { + Overlays.editOverlay(overlay, { + backgroundColor: overColor + }); + hoveringOverlay = overlay; + } + } + } + return false; + }; + self.mouseReleaseEvent = function (event) { + var overlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (pressingOverlay !== null && pressingOverlay !== undefined) { + if (overlay === pressingOverlay) { + self.onSelectMenuItem(overlayInfo[overlay].name); + } + Overlays.editOverlay(pressingOverlay, { + backgroundColor: upColor + }); + pressingOverlay = null; + self.hide(); + } + }; + + self.setVisible = function (newVisible) { + if (newVisible !== visible) { + visible = newVisible; + for (var key in overlayInfo) { + Overlays.editOverlay(key, { + visible: newVisible + }); + } + } + }; + self.show = function () { + self.setVisible(true); + }; + self.hide = function () { + self.setVisible(false); + }; + + function cleanup() { + ContextOverlay.enabled = true; + for (var i = 0; i < overlays.length; i++) { + Overlays.deleteOverlay(overlays[i]); + } + Controller.mousePressEvent.disconnect(self.mousePressEvent); + Controller.mouseMoveEvent.disconnect(self.mouseMoveEvent); + Controller.mouseReleaseEvent.disconnect(self.mouseReleaseEvent); + + Entities.canRezChanged.disconnect(checkEditPermissionsAndUpdate); + Entities.canRezTmpChanged.disconnect(checkEditPermissionsAndUpdate); + Entities.canRezCertifiedChanged.disconnect(checkEditPermissionsAndUpdate); + Entities.canRezTmpCertifiedChanged.disconnect(checkEditPermissionsAndUpdate); + } + + Controller.mousePressEvent.connect(self.mousePressEvent); + Controller.mouseMoveEvent.connect(self.mouseMoveEvent); + Controller.mouseReleaseEvent.connect(self.mouseReleaseEvent); + Script.scriptEnding.connect(cleanup); + + return this; +}; + +function whenPressed(fn) { + return function(value) { + if (value > 0) { + fn(); + } + }; +} + +function whenReleased(fn) { + return function(value) { + if (value === 0) { + fn(); + } + }; +} + +var isOnMacPlatform = Controller.getValue(Controller.Hardware.Application.PlatformMac); + +var mapping = Controller.newMapping(CONTROLLER_MAPPING_NAME); +if (isOnMacPlatform) { + mapping.from([Controller.Hardware.Keyboard.Backspace]).to(deleteKey); +} else { + mapping.from([Controller.Hardware.Keyboard.Delete]).to(deleteKey); +} +mapping.from([Controller.Hardware.Keyboard.T]).to(toggleKey); +mapping.from([Controller.Hardware.Keyboard.F]).to(focusKey); +mapping.from([Controller.Hardware.Keyboard.G]).to(gridKey); +mapping.from([Controller.Hardware.Keyboard.X]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.cutSelectedEntities() })); +mapping.from([Controller.Hardware.Keyboard.C]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.copySelectedEntities() })); +mapping.from([Controller.Hardware.Keyboard.V]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.pasteEntities() })); +mapping.from([Controller.Hardware.Keyboard.D]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.duplicateSelection() })); + +// Bind undo to ctrl-shift-z to maintain backwards-compatibility +mapping.from([Controller.Hardware.Keyboard.Z]) + .when([Controller.Hardware.Keyboard.Control, Controller.Hardware.Keyboard.Shift]) + .to(whenPressed(function() { undoHistory.redo() })); + + +mapping.from([Controller.Hardware.Keyboard.P]) + .when([Controller.Hardware.Keyboard.Control, Controller.Hardware.Keyboard.Shift]) + .to(whenReleased(function() { unparentSelectedEntities(); })); + +mapping.from([Controller.Hardware.Keyboard.P]) + .when([Controller.Hardware.Keyboard.Control, !Controller.Hardware.Keyboard.Shift]) + .to(whenReleased(function() { parentSelectedEntities(); })); + +keyUpEventFromUIWindow = function(keyUpEvent) { + var WANT_DEBUG_MISSING_SHORTCUTS = false; + + var pressedValue = 0.0; + + if ((!isOnMacPlatform && keyUpEvent.keyCodeString === "Delete") + || (isOnMacPlatform && keyUpEvent.keyCodeString === "Backspace")) { + + deleteKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "T") { + toggleKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "F") { + focusKey(pressedValue); + } else if (keyUpEvent.keyCodeString === "G") { + gridKey(pressedValue); + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "X") { + selectionManager.cutSelectedEntities(); + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "C") { + selectionManager.copySelectedEntities(); + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "V") { + selectionManager.pasteEntities(); + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "D") { + selectionManager.duplicateSelection(); + } else if (!isOnMacPlatform && keyUpEvent.controlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") { + undoHistory.undo(); // undo is only handled via handleMenuItem on Mac + } else if (keyUpEvent.controlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") { + parentSelectedEntities(); + } else if (keyUpEvent.controlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") { + unparentSelectedEntities(); + } else if (!isOnMacPlatform && + ((keyUpEvent.controlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") || + (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "Y"))) { + undoHistory.redo(); // redo is only handled via handleMenuItem on Mac + } else if (WANT_DEBUG_MISSING_SHORTCUTS) { + console.warn("unhandled key event: " + JSON.stringify(keyUpEvent)) + } +}; + +var propertyMenu = new PopupMenu(); + +propertyMenu.onSelectMenuItem = function (name) { + + if (propertyMenu.marketplaceID) { + showMarketplace(propertyMenu.marketplaceID); + } +}; + +var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace"); + +var propertiesTool = new PropertiesTool(); + +selectionDisplay.onSpaceModeChange = function(spaceMode) { + entityListTool.setSpaceMode(spaceMode); + propertiesTool.setSpaceMode(spaceMode); +}; + +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/system/create/entityList/entityList.js b/scripts/simplifiedUI/system/create/entityList/entityList.js new file mode 100644 index 0000000000..06e100f457 --- /dev/null +++ b/scripts/simplifiedUI/system/create/entityList/entityList.js @@ -0,0 +1,330 @@ +"use strict"; + +// entityList.js +// +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/* global EntityListTool, Tablet, selectionManager, Entities, Camera, MyAvatar, Vec3, Menu, Messages, + cameraManager, MENU_EASE_ON_FOCUS, deleteSelectedEntities, toggleSelectedEntitiesLocked, toggleSelectedEntitiesVisible, + keyUpEventFromUIWindow, Script, SelectionDisplay, SelectionManager, Clipboard */ + +var PROFILING_ENABLED = false; +var profileIndent = ''; +const PROFILE_NOOP = function(_name, fn, args) { + fn.apply(this, args); +}; +const PROFILE = !PROFILING_ENABLED ? PROFILE_NOOP : function(name, fn, args) { + console.log("PROFILE-Script " + profileIndent + "(" + name + ") Begin"); + var previousIndent = profileIndent; + profileIndent += ' '; + var before = Date.now(); + fn.apply(this, args); + var delta = Date.now() - before; + profileIndent = previousIndent; + console.log("PROFILE-Script " + profileIndent + "(" + name + ") End " + delta + "ms"); +}; + +EntityListTool = function(shouldUseEditTabletApp) { + var that = {}; + + var CreateWindow = Script.require('../modules/createWindow.js'); + + var TITLE_OFFSET = 60; + var ENTITY_LIST_WIDTH = 495; + var MAX_DEFAULT_CREATE_TOOLS_HEIGHT = 778; + var entityListWindow = new CreateWindow( + Script.resolvePath("./qml/EditEntityList.qml"), + 'Entity List', + 'com.highfidelity.create.entityListWindow', + function () { + var windowHeight = Window.innerHeight - TITLE_OFFSET; + if (windowHeight > MAX_DEFAULT_CREATE_TOOLS_HEIGHT) { + windowHeight = MAX_DEFAULT_CREATE_TOOLS_HEIGHT; + } + return { + size: { + x: ENTITY_LIST_WIDTH, + y: windowHeight + }, + position: { + x: Window.x, + y: Window.y + TITLE_OFFSET + } + }; + }, + false + ); + + var webView = null; + webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + webView.setVisible = function(value){ }; + + var filterInView = false; + var searchRadius = 100; + + var visible = false; + + that.webView = webView; + + that.setVisible = function(newVisible) { + visible = newVisible; + webView.setVisible(shouldUseEditTabletApp() && visible); + entityListWindow.setVisible(!shouldUseEditTabletApp() && visible); + }; + + that.isVisible = function() { + return entityListWindow.isVisible(); + }; + + that.setVisible(false); + + function emitJSONScriptEvent(data) { + var dataString; + PROFILE("Script-JSON.stringify", function() { + dataString = JSON.stringify(data); + }); + PROFILE("Script-emitScriptEvent", function() { + webView.emitScriptEvent(dataString); + if (entityListWindow.window) { + entityListWindow.window.emitScriptEvent(dataString); + } + }); + } + + that.toggleVisible = function() { + that.setVisible(!visible); + }; + + selectionManager.addEventListener(function(isSelectionUpdate, caller) { + if (caller === that) { + // ignore events that we emitted from the entity list itself + return; + } + var selectedIDs = []; + + for (var i = 0; i < selectionManager.selections.length; i++) { + selectedIDs.push(selectionManager.selections[i]); + } + + emitJSONScriptEvent({ + type: 'selectionUpdate', + selectedIDs: selectedIDs + }); + }); + + that.setSpaceMode = function(spaceMode) { + emitJSONScriptEvent({ + type: 'setSpaceMode', + spaceMode: spaceMode + }); + }; + + that.clearEntityList = function() { + emitJSONScriptEvent({ + type: 'clearEntityList' + }); + }; + + that.removeEntities = function (deletedIDs, selectedIDs) { + emitJSONScriptEvent({ + type: 'removeEntities', + deletedIDs: deletedIDs, + selectedIDs: selectedIDs + }); + }; + + that.deleteEntities = function (deletedIDs) { + emitJSONScriptEvent({ + type: "deleted", + ids: deletedIDs + }); + }; + + function valueIfDefined(value) { + return value !== undefined ? value : ""; + } + + function entityIsBaked(properties) { + if (properties.type === "Model") { + var lowerModelURL = properties.modelURL.toLowerCase(); + return lowerModelURL.endsWith(".baked.fbx") || lowerModelURL.endsWith(".baked.fst"); + } else if (properties.type === "Zone") { + var lowerSkyboxURL = properties.skybox ? properties.skybox.url.toLowerCase() : ""; + var lowerAmbientURL = properties.ambientLight ? properties.ambientLight.ambientURL.toLowerCase() : ""; + return (lowerSkyboxURL === "" || lowerSkyboxURL.endsWith(".texmeta.json")) && + (lowerAmbientURL === "" || lowerAmbientURL.endsWith(".texmeta.json")); + } else { + return false; + } + } + + that.sendUpdate = function() { + PROFILE('Script-sendUpdate', function() { + var entities = []; + + var ids; + PROFILE("findEntities", function() { + if (filterInView) { + ids = Entities.findEntitiesInFrustum(Camera.frustum); + } else { + ids = Entities.findEntities(MyAvatar.position, searchRadius); + } + }); + + var cameraPosition = Camera.position; + PROFILE("getMultipleProperties", function () { + var multipleProperties = Entities.getMultipleEntityProperties(ids, ['name', 'type', 'locked', + 'visible', 'renderInfo', 'modelURL', 'materialURL', 'imageURL', 'script', 'certificateID', + 'skybox.url', 'ambientLight.url']); + for (var i = 0; i < multipleProperties.length; i++) { + var properties = multipleProperties[i]; + + if (!filterInView || Vec3.distance(properties.position, cameraPosition) <= searchRadius) { + var url = ""; + if (properties.type === "Model") { + url = properties.modelURL; + } else if (properties.type === "Material") { + url = properties.materialURL; + } else if (properties.type === "Image") { + url = properties.imageURL; + } + entities.push({ + id: ids[i], + name: properties.name, + type: properties.type, + url: url, + locked: properties.locked, + visible: properties.visible, + certificateID: properties.certificateID, + verticesCount: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.verticesCount) : ""), + texturesCount: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.texturesCount) : ""), + texturesSize: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.texturesSize) : ""), + hasTransparent: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.hasTransparent) : ""), + isBaked: entityIsBaked(properties), + drawCalls: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.drawCalls) : ""), + hasScript: properties.script !== "" + }); + } + } + }); + + var selectedIDs = []; + for (var j = 0; j < selectionManager.selections.length; j++) { + selectedIDs.push(selectionManager.selections[j]); + } + + emitJSONScriptEvent({ + type: "update", + entities: entities, + selectedIDs: selectedIDs, + spaceMode: SelectionDisplay.getSpaceMode(), + }); + }); + }; + + function onFileSaveChanged(filename) { + Window.saveFileChanged.disconnect(onFileSaveChanged); + if (filename !== "") { + var success = Clipboard.exportEntities(filename, selectionManager.selections); + if (!success) { + Window.notifyEditError("Export failed."); + } + } + } + + var onWebEventReceived = function(data) { + try { + data = JSON.parse(data); + } catch(e) { + print("entityList.js: Error parsing JSON"); + return; + } + + if (data.type === "selectionUpdate") { + var ids = data.entityIds; + var entityIDs = []; + for (var i = 0; i < ids.length; i++) { + entityIDs.push(ids[i]); + } + selectionManager.setSelections(entityIDs, that); + if (data.focus) { + cameraManager.enable(); + cameraManager.focus(selectionManager.worldPosition, + selectionManager.worldDimensions, + Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); + } + } else if (data.type === "refresh") { + that.sendUpdate(); + } else if (data.type === "teleport") { + if (selectionManager.hasSelection()) { + MyAvatar.position = selectionManager.worldPosition; + } + } else if (data.type === "export") { + if (!selectionManager.hasSelection()) { + Window.notifyEditError("No entities have been selected."); + } else { + Window.saveFileChanged.connect(onFileSaveChanged); + Window.saveAsync("Select Where to Save", "", "*.json"); + } + } else if (data.type === "pal") { + var sessionIds = {}; // Collect the sessionsIds of all selected entities, w/o duplicates. + selectionManager.selections.forEach(function (id) { + var lastEditedBy = Entities.getEntityProperties(id, 'lastEditedBy').lastEditedBy; + if (lastEditedBy) { + sessionIds[lastEditedBy] = true; + } + }); + var dedupped = Object.keys(sessionIds); + if (!selectionManager.selections.length) { + Window.alert('No objects selected.'); + } else if (!dedupped.length) { + Window.alert('There were no recent users of the ' + selectionManager.selections.length + ' selected objects.'); + } else { + // No need to subscribe if we're just sending. + Messages.sendMessage('com.highfidelity.pal', JSON.stringify({method: 'select', params: [dedupped, true, false]}), 'local'); + } + } else if (data.type === "delete") { + deleteSelectedEntities(); + } else if (data.type === "toggleLocked") { + toggleSelectedEntitiesLocked(); + } else if (data.type === "toggleVisible") { + toggleSelectedEntitiesVisible(); + } else if (data.type === "filterInView") { + filterInView = data.filterInView === true; + } else if (data.type === "radius") { + searchRadius = data.radius; + } else if (data.type === "cut") { + SelectionManager.cutSelectedEntities(); + } else if (data.type === "copy") { + SelectionManager.copySelectedEntities(); + } else if (data.type === "paste") { + SelectionManager.pasteEntities(); + } else if (data.type === "duplicate") { + SelectionManager.duplicateSelection(); + that.sendUpdate(); + } else if (data.type === "rename") { + Entities.editEntity(data.entityID, {name: data.name}); + // make sure that the name also gets updated in the properties window + SelectionManager._update(); + } else if (data.type === "toggleSpaceMode") { + SelectionDisplay.toggleSpaceMode(); + } else if (data.type === 'keyUpEvent') { + keyUpEventFromUIWindow(data.keyUpEvent); + } + }; + + webView.webEventReceived.connect(onWebEventReceived); + entityListWindow.webEventReceived.addListener(onWebEventReceived); + that.interactiveWindowHidden = entityListWindow.interactiveWindowHidden; + + return that; +}; diff --git a/scripts/simplifiedUI/system/create/entityList/html/entityList.html b/scripts/simplifiedUI/system/create/entityList/html/entityList.html new file mode 100644 index 0000000000..3e17a66df5 --- /dev/null +++ b/scripts/simplifiedUI/system/create/entityList/html/entityList.html @@ -0,0 +1,93 @@ + + + + Entity List + + + + + + + + + + + + + +
+ +
+ + +
+ + + +
+
+
+
+
+ +
+
+
+ +
+ + +
+
+
+
+ Y +
+ +
+ + +
+
+
+ + + + +
+
+
+ +
+
+
+ +
+
+
+ There are no entities to display. Please check your filters or create an entity to begin. +
+
+
+ + + diff --git a/scripts/simplifiedUI/system/html/js/entityList.js b/scripts/simplifiedUI/system/create/entityList/html/js/entityList.js similarity index 100% rename from scripts/simplifiedUI/system/html/js/entityList.js rename to scripts/simplifiedUI/system/create/entityList/html/js/entityList.js diff --git a/scripts/simplifiedUI/system/html/js/entityListContextMenu.js b/scripts/simplifiedUI/system/create/entityList/html/js/entityListContextMenu.js similarity index 100% rename from scripts/simplifiedUI/system/html/js/entityListContextMenu.js rename to scripts/simplifiedUI/system/create/entityList/html/js/entityListContextMenu.js diff --git a/scripts/simplifiedUI/system/html/js/listView.js b/scripts/simplifiedUI/system/create/entityList/html/js/listView.js similarity index 100% rename from scripts/simplifiedUI/system/html/js/listView.js rename to scripts/simplifiedUI/system/create/entityList/html/js/listView.js diff --git a/scripts/simplifiedUI/system/create/EditEntityList.qml b/scripts/simplifiedUI/system/create/entityList/qml/EditEntityList.qml similarity index 82% rename from scripts/simplifiedUI/system/create/EditEntityList.qml rename to scripts/simplifiedUI/system/create/entityList/qml/EditEntityList.qml index 94935c7bb5..1d5beb9914 100644 --- a/scripts/simplifiedUI/system/create/EditEntityList.qml +++ b/scripts/simplifiedUI/system/create/entityList/qml/EditEntityList.qml @@ -10,7 +10,7 @@ import stylesUit 1.0 WebView { id: entityListToolWebView - url: Paths.defaultScripts + "/system/html/entityList.html" + url: Qt.resolvedUrl("../html/entityList.html") enabled: true blurOnCtrlShift: false } diff --git a/scripts/simplifiedUI/system/create/EntityList.qml b/scripts/simplifiedUI/system/create/entityList/qml/EntityList.qml similarity index 58% rename from scripts/simplifiedUI/system/create/EntityList.qml rename to scripts/simplifiedUI/system/create/entityList/qml/EntityList.qml index 2f8a8863be..8f92ffe6ce 100644 --- a/scripts/simplifiedUI/system/create/EntityList.qml +++ b/scripts/simplifiedUI/system/create/entityList/qml/EntityList.qml @@ -1,6 +1,6 @@ WebView { id: entityListToolWebView - url: Paths.defaultScripts + "/system/html/entityList.html" + url: Qt.resolvedUrl("../html/entityList.html") enabled: true blurOnCtrlShift: false } diff --git a/scripts/simplifiedUI/system/create/entityProperties/html/entityProperties.html b/scripts/simplifiedUI/system/create/entityProperties/html/entityProperties.html new file mode 100644 index 0000000000..876e75ec35 --- /dev/null +++ b/scripts/simplifiedUI/system/create/entityProperties/html/entityProperties.html @@ -0,0 +1,52 @@ + + + + + Properties + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+ + + diff --git a/scripts/simplifiedUI/system/create/entityProperties/html/js/createAppTooltip.js b/scripts/simplifiedUI/system/create/entityProperties/html/js/createAppTooltip.js new file mode 100644 index 0000000000..3eb206d8a3 --- /dev/null +++ b/scripts/simplifiedUI/system/create/entityProperties/html/js/createAppTooltip.js @@ -0,0 +1,116 @@ +// createAppTooltip.js +// +// Created by Thijs Wenker on 17 Oct 2018 +// Copyright 2018 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 + +const CREATE_APP_TOOLTIP_OFFSET = 20; +const TOOLTIP_DELAY = 500; // ms +const TOOLTIP_DEBUG = false; + +function CreateAppTooltip() { + this._tooltipData = null; + this._tooltipDiv = null; + this._delayTimeout = null; + this._isEnabled = false; +} + +CreateAppTooltip.prototype = { + _tooltipData: null, + _tooltipDiv: null, + _delayTimeout: null, + _isEnabled: null, + + _removeTooltipIfExists: function() { + if (this._delayTimeout !== null) { + window.clearTimeout(this._delayTimeout); + this._delayTimeout = null; + } + + if (this._tooltipDiv !== null) { + this._tooltipDiv.remove(); + this._tooltipDiv = null; + } + }, + + setIsEnabled: function(isEnabled) { + this._isEnabled = isEnabled; + }, + + setTooltipData: function(tooltipData) { + this._tooltipData = tooltipData; + }, + + registerTooltipElement: function(element, tooltipID, jsPropertyName) { + element.addEventListener("mouseover", function() { + if (!this._isEnabled) { + return; + } + + this._removeTooltipIfExists(); + + this._delayTimeout = window.setTimeout(function() { + let tooltipData = this._tooltipData[tooltipID]; + + if (!tooltipData || tooltipData.tooltip === "") { + if (!TOOLTIP_DEBUG) { + return; + } + tooltipData = { tooltip: 'PLEASE SET THIS TOOLTIP' }; + } + + let elementRect = element.getBoundingClientRect(); + let elTip = document.createElement("div"); + elTip.className = "create-app-tooltip"; + + let elTipDescription = document.createElement("div"); + elTipDescription.className = "create-app-tooltip-description"; + elTipDescription.innerText = tooltipData.tooltip; + elTip.appendChild(elTipDescription); + + let jsAttribute = jsPropertyName; + if (tooltipData.jsPropertyName) { + jsAttribute = tooltipData.jsPropertyName; + } + + if (!tooltipData.skipJSProperty) { + let elTipJSAttribute = document.createElement("div"); + elTipJSAttribute.className = "create-app-tooltip-js-attribute"; + elTipJSAttribute.innerText = `JS Attribute: ${jsAttribute}`; + elTip.appendChild(elTipJSAttribute); + } + + document.body.appendChild(elTip); + + let elementTop = window.pageYOffset + elementRect.top; + + let desiredTooltipTop = elementTop + element.clientHeight + CREATE_APP_TOOLTIP_OFFSET; + let desiredTooltipLeft = window.pageXOffset + elementRect.left; + + if ((window.innerHeight + window.pageYOffset) < (desiredTooltipTop + elTip.clientHeight)) { + // show above when otherwise out of bounds + elTip.style.top = elementTop - CREATE_APP_TOOLTIP_OFFSET - elTip.clientHeight; + } else { + // show tooltip below by default + elTip.style.top = desiredTooltipTop; + } + if ((window.innerWidth + window.pageXOffset) < (desiredTooltipLeft + elTip.clientWidth)) { + elTip.style.left = document.body.clientWidth + window.pageXOffset - elTip.offsetWidth; + } else { + elTip.style.left = desiredTooltipLeft; + } + + this._tooltipDiv = elTip; + }.bind(this), TOOLTIP_DELAY); + }.bind(this), false); + element.addEventListener("mouseout", function() { + if (!this._isEnabled) { + return; + } + + this._removeTooltipIfExists(); + }.bind(this), false); + } +}; diff --git a/scripts/simplifiedUI/system/html/js/draggableNumber.js b/scripts/simplifiedUI/system/create/entityProperties/html/js/draggableNumber.js similarity index 100% rename from scripts/simplifiedUI/system/html/js/draggableNumber.js rename to scripts/simplifiedUI/system/create/entityProperties/html/js/draggableNumber.js diff --git a/scripts/simplifiedUI/system/html/js/entityProperties.js b/scripts/simplifiedUI/system/create/entityProperties/html/js/entityProperties.js similarity index 100% rename from scripts/simplifiedUI/system/html/js/entityProperties.js rename to scripts/simplifiedUI/system/create/entityProperties/html/js/entityProperties.js diff --git a/scripts/simplifiedUI/system/html/js/underscore-min.js b/scripts/simplifiedUI/system/create/entityProperties/html/js/underscore-min.js similarity index 100% rename from scripts/simplifiedUI/system/html/js/underscore-min.js rename to scripts/simplifiedUI/system/create/entityProperties/html/js/underscore-min.js diff --git a/scripts/simplifiedUI/system/create/entitySelectionTool/entitySelectionTool.js b/scripts/simplifiedUI/system/create/entitySelectionTool/entitySelectionTool.js new file mode 100644 index 0000000000..9c993d6d73 --- /dev/null +++ b/scripts/simplifiedUI/system/create/entitySelectionTool/entitySelectionTool.js @@ -0,0 +1,2925 @@ +// +// entitySelectionTool.js +// examples +// +// Created by Brad hefta-Gaub on 10/1/14. +// Modified by Daniela Fontes * @DanielaFifo and Tiago Andrade @TagoWill on 4/7/2017 +// Modified by David Back on 1/9/2018 +// Copyright 2014 High Fidelity, Inc. +// +// This script implements a class useful for building tools for editing entities. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/* global SelectionManager, SelectionDisplay, grid, rayPlaneIntersection, rayPlaneIntersection2, pushCommandForSelections, + getMainTabletIDs, getControllerWorldLocation, TRIGGER_ON_VALUE */ + +const SPACE_LOCAL = "local"; +const SPACE_WORLD = "world"; +const HIGHLIGHT_LIST_NAME = "editHandleHighlightList"; + +Script.include([ + "../../libraries/controllers.js", + "../../libraries/controllerDispatcherUtils.js", + "../../libraries/utils.js" +]); + + +function deepCopy(v) { + return JSON.parse(JSON.stringify(v)); +} + +SelectionManager = (function() { + var that = {}; + + // FUNCTION: SUBSCRIBE TO UPDATE MESSAGES + function subscribeToUpdateMessages() { + Messages.subscribe("entityToolUpdates"); + Messages.messageReceived.connect(handleEntitySelectionToolUpdates); + } + + // FUNCTION: HANDLE ENTITY SELECTION TOOL UPDATES + function handleEntitySelectionToolUpdates(channel, message, sender) { + if (channel !== 'entityToolUpdates') { + return; + } + if (sender !== MyAvatar.sessionUUID) { + return; + } + + var wantDebug = false; + var messageParsed; + try { + messageParsed = JSON.parse(message); + } catch (err) { + print("ERROR: entitySelectionTool.handleEntitySelectionToolUpdates - got malformed message"); + return; + } + + if (messageParsed.method === "selectEntity") { + if (!SelectionDisplay.triggered() || SelectionDisplay.triggeredHand === messageParsed.hand) { + if (wantDebug) { + print("setting selection to " + messageParsed.entityID); + } + that.setSelections([messageParsed.entityID], that); + } + } else if (messageParsed.method === "clearSelection") { + if (!SelectionDisplay.triggered() || SelectionDisplay.triggeredHand === messageParsed.hand) { + that.clearSelections(); + } + } else if (messageParsed.method === "pointingAt") { + if (messageParsed.hand === Controller.Standard.RightHand) { + that.pointingAtDesktopWindowRight = messageParsed.desktopWindow; + that.pointingAtTabletRight = messageParsed.tablet; + } else { + that.pointingAtDesktopWindowLeft = messageParsed.desktopWindow; + that.pointingAtTabletLeft = messageParsed.tablet; + } + } + } + + subscribeToUpdateMessages(); + + // disabling this for now as it is causing rendering issues with the other handle overlays + /* + var COLOR_ORANGE_HIGHLIGHT = { red: 255, green: 99, blue: 9 }; + var editHandleOutlineStyle = { + outlineUnoccludedColor: COLOR_ORANGE_HIGHLIGHT, + outlineOccludedColor: COLOR_ORANGE_HIGHLIGHT, + fillUnoccludedColor: COLOR_ORANGE_HIGHLIGHT, + fillOccludedColor: COLOR_ORANGE_HIGHLIGHT, + outlineUnoccludedAlpha: 1, + outlineOccludedAlpha: 0, + fillUnoccludedAlpha: 0, + fillOccludedAlpha: 0, + outlineWidth: 3, + isOutlineSmooth: true + }; + Selection.enableListHighlight(HIGHLIGHT_LIST_NAME, editHandleOutlineStyle); + */ + + that.savedProperties = {}; + that.selections = []; + var listeners = []; + + that.localRotation = Quat.IDENTITY; + that.localPosition = Vec3.ZERO; + that.localDimensions = Vec3.ZERO; + that.localRegistrationPoint = Vec3.HALF; + + that.worldRotation = Quat.IDENTITY; + that.worldPosition = Vec3.ZERO; + that.worldDimensions = Vec3.ZERO; + that.worldRegistrationPoint = Vec3.HALF; + that.centerPosition = Vec3.ZERO; + + that.pointingAtDesktopWindowLeft = false; + that.pointingAtDesktopWindowRight = false; + that.pointingAtTabletLeft = false; + that.pointingAtTabletRight = false; + + that.saveProperties = function() { + that.savedProperties = {}; + for (var i = 0; i < that.selections.length; i++) { + var entityID = that.selections[i]; + that.savedProperties[entityID] = Entities.getEntityProperties(entityID); + } + }; + + that.addEventListener = function(func, thisContext) { + listeners.push({ + callback: func, + thisContext: thisContext + }); + }; + + that.hasSelection = function() { + return that.selections.length > 0; + }; + + that.setSelections = function(entityIDs, caller) { + that.selections = []; + for (var i = 0; i < entityIDs.length; i++) { + var entityID = entityIDs[i]; + that.selections.push(entityID); + Selection.addToSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID); + } + + that._update(true, caller); + }; + + that.addEntity = function(entityID, toggleSelection, caller) { + if (entityID) { + var idx = -1; + for (var i = 0; i < that.selections.length; i++) { + if (entityID === that.selections[i]) { + idx = i; + break; + } + } + if (idx === -1) { + that.selections.push(entityID); + Selection.addToSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID); + } else if (toggleSelection) { + that.selections.splice(idx, 1); + Selection.removeFromSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID); + } + } + + that._update(true, caller); + }; + + function removeEntityByID(entityID) { + var idx = that.selections.indexOf(entityID); + if (idx >= 0) { + that.selections.splice(idx, 1); + Selection.removeFromSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID); + } + } + + that.removeEntity = function (entityID, caller) { + removeEntityByID(entityID); + that._update(true, caller); + }; + + that.removeEntities = function(entityIDs, caller) { + for (var i = 0, length = entityIDs.length; i < length; i++) { + removeEntityByID(entityIDs[i]); + } + that._update(true, caller); + }; + + that.clearSelections = function(caller) { + that.selections = []; + that._update(true, caller); + }; + + that.addChildrenEntities = function(parentEntityID, entityList, entityHostType) { + var wantDebug = false; + var children = Entities.getChildrenIDs(parentEntityID); + var entityHostTypes = Entities.getMultipleEntityProperties(children, 'entityHostType'); + for (var i = 0; i < children.length; i++) { + var childID = children[i]; + + if (entityHostTypes[i].entityHostType !== entityHostType) { + if (wantDebug) { + console.log("Skipping addition of entity " + childID + " with conflicting entityHostType: " + + entityHostTypes[i].entityHostType + ", expected: " + entityHostType); + } + continue; + } + + if (entityList.indexOf(childID) < 0) { + entityList.push(childID); + } + that.addChildrenEntities(childID, entityList, entityHostType); + } + }; + + // Determine if an entity is being grabbed. + // This is mostly a heuristic - there is no perfect way to know if an entity is being + // grabbed. + // + // @return {boolean} true if the given entity with `properties` is being grabbed by an avatar + function nonDynamicEntityIsBeingGrabbedByAvatar(properties) { + if (properties.dynamic || Uuid.isNull(properties.parentID)) { + return false; + } + + var avatar = AvatarList.getAvatar(properties.parentID); + if (Uuid.isNull(avatar.sessionUUID)) { + return false; + } + + var grabJointNames = [ + 'RightHand', 'LeftHand', + '_CONTROLLER_RIGHTHAND', '_CONTROLLER_LEFTHAND', + '_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND', '_CAMERA_RELATIVE_CONTROLLER_LEFTHAND', + '_FARGRAB_RIGHTHAND', '_FARGRAB_LEFTHAND', '_FARGRAB_MOUSE' + ]; + + for (var i = 0; i < grabJointNames.length; ++i) { + if (avatar.getJointIndex(grabJointNames[i]) === properties.parentJointIndex) { + return true; + } + } + + return false; + } + + var entityClipboard = { + entities: {}, // Map of id -> properties for copied entities + position: { x: 0, y: 0, z: 0 }, + dimensions: { x: 0, y: 0, z: 0 }, + }; + + that.duplicateSelection = function() { + var entitiesToDuplicate = []; + var duplicatedEntityIDs = []; + var duplicatedChildrenWithOldParents = []; + var originalEntityToNewEntityID = []; + + SelectionManager.saveProperties(); + + // build list of entities to duplicate by including any unselected children of selected parent entities + var originalEntityIDs = Object.keys(that.savedProperties); + var entityHostTypes = Entities.getMultipleEntityProperties(originalEntityIDs, 'entityHostType'); + for (var i = 0; i < originalEntityIDs.length; i++) { + var originalEntityID = originalEntityIDs[i]; + if (entitiesToDuplicate.indexOf(originalEntityID) === -1) { + entitiesToDuplicate.push(originalEntityID); + } + that.addChildrenEntities(originalEntityID, entitiesToDuplicate, entityHostTypes[i].entityHostType); + } + + // duplicate entities from above and store their original to new entity mappings and children needing re-parenting + for (var i = 0; i < entitiesToDuplicate.length; i++) { + var originalEntityID = entitiesToDuplicate[i]; + var properties = that.savedProperties[originalEntityID]; + if (properties === undefined) { + properties = Entities.getEntityProperties(originalEntityID); + } + if (!properties.locked && (!properties.avatarEntity || properties.owningAvatarID === MyAvatar.sessionUUID)) { + if (nonDynamicEntityIsBeingGrabbedByAvatar(properties)) { + properties.parentID = null; + properties.parentJointIndex = null; + properties.localPosition = properties.position; + properties.localRotation = properties.rotation; + } + + properties.localVelocity = Vec3.ZERO; + properties.localAngularVelocity = Vec3.ZERO; + + delete properties.actionData; + var newEntityID = Entities.addEntity(properties); + + // Re-apply actions from the original entity + var actionIDs = Entities.getActionIDs(properties.id); + for (var j = 0; j < actionIDs.length; ++j) { + var actionID = actionIDs[j]; + var actionArguments = Entities.getActionArguments(properties.id, actionID); + if (actionArguments) { + var type = actionArguments.type; + if (type === 'hold' || type === 'far-grab') { + continue; + } + delete actionArguments.ttl; + Entities.addAction(type, newEntityID, actionArguments); + } + } + + duplicatedEntityIDs.push({ + entityID: newEntityID, + properties: properties + }); + if (properties.parentID !== Uuid.NULL) { + duplicatedChildrenWithOldParents[newEntityID] = properties.parentID; + } + originalEntityToNewEntityID[originalEntityID] = newEntityID; + } + } + + // re-parent duplicated children to the duplicate entities of their original parents (if they were duplicated) + Object.keys(duplicatedChildrenWithOldParents).forEach(function(childIDNeedingNewParent) { + var originalParentID = duplicatedChildrenWithOldParents[childIDNeedingNewParent]; + var newParentID = originalEntityToNewEntityID[originalParentID]; + if (newParentID) { + Entities.editEntity(childIDNeedingNewParent, { parentID: newParentID }); + for (var i = 0; i < duplicatedEntityIDs.length; i++) { + var duplicatedEntity = duplicatedEntityIDs[i]; + if (duplicatedEntity.entityID === childIDNeedingNewParent) { + duplicatedEntity.properties.parentID = newParentID; + } + } + } + }); + + return duplicatedEntityIDs; + }; + + // Create the entities in entityProperties, maintaining parent-child relationships. + // @param entityProperties {array} - Array of entity property objects + that.createEntities = function(entityProperties) { + var entitiesToCreate = []; + var createdEntityIDs = []; + var createdChildrenWithOldParents = []; + var originalEntityToNewEntityID = []; + + that.saveProperties(); + + for (var i = 0; i < entityProperties.length; ++i) { + var properties = entityProperties[i]; + if (properties.parentID in originalEntityToNewEntityID) { + properties.parentID = originalEntityToNewEntityID[properties.parentID]; + } else { + delete properties.parentID; + } + + delete properties.actionData; + var newEntityID = Entities.addEntity(properties); + + if (newEntityID) { + createdEntityIDs.push({ + entityID: newEntityID, + properties: properties + }); + if (properties.parentID !== Uuid.NULL) { + createdChildrenWithOldParents[newEntityID] = properties.parentID; + } + originalEntityToNewEntityID[properties.id] = newEntityID; + properties.id = newEntityID; + } + } + + return createdEntityIDs; + }; + + that.cutSelectedEntities = function() { + that.copySelectedEntities(); + deleteSelectedEntities(); + }; + + that.copySelectedEntities = function() { + var entityProperties = Entities.getMultipleEntityProperties(that.selections); + var entityHostTypes = Entities.getMultipleEntityProperties(that.selections, 'entityHostType'); + var entities = {}; + entityProperties.forEach(function(props) { + entities[props.id] = props; + }); + + function appendChildren(entityID, entities, entityHostType) { + var wantDebug = false; + var childrenIDs = Entities.getChildrenIDs(entityID); + var entityHostTypes = Entities.getMultipleEntityProperties(childrenIDs, 'entityHostType'); + for (var i = 0; i < childrenIDs.length; ++i) { + var id = childrenIDs[i]; + + if (entityHostTypes[i].entityHostType !== entityHostType) { + if (wantDebug) { + console.warn("Skipping addition of entity " + id + " with conflicting entityHostType: " + + entityHostTypes[i].entityHostType + ", expected: " + entityHostType); + } + continue; + } + + if (!(id in entities)) { + entities[id] = Entities.getEntityProperties(id); + appendChildren(id, entities, entityHostType); + } + } + } + + var len = entityProperties.length; + for (var i = 0; i < len; ++i) { + appendChildren(entityProperties[i].id, entities, entityHostTypes[i].entityHostType); + } + + for (var id in entities) { + var parentID = entities[id].parentID; + entities[id].root = !(parentID in entities); + } + + entityClipboard.entities = []; + + var ids = Object.keys(entities); + while (ids.length > 0) { + // Go through all remaining entities. + // If an entity does not have a parent left, move it into the list + for (var i = 0; i < ids.length; ++i) { + var id = ids[i]; + var parentID = entities[id].parentID; + if (parentID in entities) { + continue; + } + entityClipboard.entities.push(entities[id]); + delete entities[id]; + } + ids = Object.keys(entities); + } + + // Calculate size + if (entityClipboard.entities.length === 0) { + entityClipboard.dimensions = { x: 0, y: 0, z: 0 }; + entityClipboard.position = { x: 0, y: 0, z: 0 }; + } else { + var properties = entityClipboard.entities; + var brn = properties[0].boundingBox.brn; + var tfl = properties[0].boundingBox.tfl; + for (var i = 1; i < properties.length; i++) { + var bb = properties[i].boundingBox; + brn.x = Math.min(bb.brn.x, brn.x); + brn.y = Math.min(bb.brn.y, brn.y); + brn.z = Math.min(bb.brn.z, brn.z); + tfl.x = Math.max(bb.tfl.x, tfl.x); + tfl.y = Math.max(bb.tfl.y, tfl.y); + tfl.z = Math.max(bb.tfl.z, tfl.z); + } + entityClipboard.dimensions = { + x: tfl.x - brn.x, + y: tfl.y - brn.y, + z: tfl.z - brn.z + }; + entityClipboard.position = { + x: brn.x + entityClipboard.dimensions.x / 2, + y: brn.y + entityClipboard.dimensions.y / 2, + z: brn.z + entityClipboard.dimensions.z / 2 + }; + } + }; + + that.pasteEntities = function() { + var dimensions = entityClipboard.dimensions; + var maxDimension = Math.max(dimensions.x, dimensions.y, dimensions.z); + var pastePosition = getPositionToCreateEntity(maxDimension); + var deltaPosition = Vec3.subtract(pastePosition, entityClipboard.position); + + var copiedProperties = []; + var ids = []; + entityClipboard.entities.forEach(function(originalProperties) { + var properties = deepCopy(originalProperties); + if (properties.root) { + properties.position = Vec3.sum(properties.position, deltaPosition); + delete properties.localPosition; + } else { + delete properties.position; + } + copiedProperties.push(properties); + }); + + var currentSelections = deepCopy(SelectionManager.selections); + + function redo(copiedProperties) { + var created = that.createEntities(copiedProperties); + var ids = []; + for (var i = 0; i < created.length; ++i) { + ids.push(created[i].entityID); + } + SelectionManager.setSelections(ids); + } + + function undo(copiedProperties) { + for (var i = 0; i < copiedProperties.length; ++i) { + Entities.deleteEntity(copiedProperties[i].id); + } + SelectionManager.setSelections(currentSelections); + } + + redo(copiedProperties); + undoHistory.pushCommand(undo, copiedProperties, redo, copiedProperties); + }; + + that._update = function(selectionUpdated, caller) { + var properties = null; + if (that.selections.length === 0) { + that.localDimensions = null; + that.localPosition = null; + that.worldDimensions = null; + that.worldPosition = null; + that.worldRotation = null; + } else if (that.selections.length === 1) { + properties = Entities.getEntityProperties(that.selections[0], + ['dimensions', 'position', 'rotation', 'registrationPoint', 'boundingBox', 'type']); + that.localDimensions = properties.dimensions; + that.localPosition = properties.position; + that.localRotation = properties.rotation; + that.localRegistrationPoint = properties.registrationPoint; + + that.worldDimensions = properties.boundingBox.dimensions; + that.worldPosition = properties.boundingBox.center; + that.worldRotation = Quat.IDENTITY; + + that.entityType = properties.type; + + if (selectionUpdated) { + SelectionDisplay.useDesiredSpaceMode(); + } + } else { + properties = Entities.getEntityProperties(that.selections[0], ['type', 'boundingBox']); + + that.entityType = properties.type; + + var brn = properties.boundingBox.brn; + var tfl = properties.boundingBox.tfl; + + for (var i = 1; i < that.selections.length; i++) { + properties = Entities.getEntityProperties(that.selections[i], 'boundingBox'); + var bb = properties.boundingBox; + brn.x = Math.min(bb.brn.x, brn.x); + brn.y = Math.min(bb.brn.y, brn.y); + brn.z = Math.min(bb.brn.z, brn.z); + tfl.x = Math.max(bb.tfl.x, tfl.x); + tfl.y = Math.max(bb.tfl.y, tfl.y); + tfl.z = Math.max(bb.tfl.z, tfl.z); + } + + that.localRotation = null; + that.localDimensions = null; + that.localPosition = null; + that.worldDimensions = { + x: tfl.x - brn.x, + y: tfl.y - brn.y, + z: tfl.z - brn.z + }; + that.worldRotation = Quat.IDENTITY; + that.worldPosition = { + x: brn.x + (that.worldDimensions.x / 2), + y: brn.y + (that.worldDimensions.y / 2), + z: brn.z + (that.worldDimensions.z / 2) + }; + + // For 1+ selections we can only modify selections in world space + SelectionDisplay.setSpaceMode(SPACE_WORLD, false); + } + + for (var j = 0; j < listeners.length; j++) { + try { + listeners[j].callback.call(listeners[j].thisContext, selectionUpdated === true, caller); + } catch (e) { + print("ERROR: entitySelectionTool.update got exception: " + JSON.stringify(e)); + } + } + }; + + return that; +})(); + +// Normalize degrees to be in the range (-180, 180) +function normalizeDegrees(degrees) { + var maxDegrees = 360; + var halfMaxDegrees = maxDegrees / 2; + degrees = ((degrees + halfMaxDegrees) % maxDegrees) - halfMaxDegrees; + if (degrees <= -halfMaxDegrees) { + degrees += maxDegrees; + } + return degrees; +} + +// SELECTION DISPLAY DEFINITION +SelectionDisplay = (function() { + var that = {}; + + const COLOR_GREEN = { red: 31, green: 198, blue: 166 }; + const COLOR_BLUE = { red: 0, green: 147, blue: 197 }; + const COLOR_RED = { red: 226, green: 51, blue: 77 }; + const COLOR_HOVER = { red: 227, green: 227, blue: 227 }; + const COLOR_ROTATE_CURRENT_RING = { red: 255, green: 99, blue: 9 }; + const COLOR_BOUNDING_EDGE = { red: 87, green: 87, blue: 87 }; + const COLOR_SCALE_CUBE = { red: 106, green: 106, blue: 106 }; + const COLOR_SCALE_CUBE_SELECTED = { red: 18, green: 18, blue: 18 }; + const COLOR_DEBUG_PICK_PLANE = { red: 255, green: 255, blue: 255 }; + const COLOR_DEBUG_PICK_PLANE_HIT = { red: 255, green: 165, blue: 0 }; + + const TRANSLATE_ARROW_CYLINDER_OFFSET = 0.1; + const TRANSLATE_ARROW_CYLINDER_CAMERA_DISTANCE_MULTIPLE = 0.005; + const TRANSLATE_ARROW_CYLINDER_Y_MULTIPLE = 7.5; + const TRANSLATE_ARROW_CONE_CAMERA_DISTANCE_MULTIPLE = 0.025; + const TRANSLATE_ARROW_CONE_OFFSET_CYLINDER_DIMENSION_MULTIPLE = 0.83; + + const ROTATE_RING_CAMERA_DISTANCE_MULTIPLE = 0.15; + const ROTATE_CTRL_SNAP_ANGLE = 22.5; + const ROTATE_DEFAULT_SNAP_ANGLE = 1; + const ROTATE_DEFAULT_TICK_MARKS_ANGLE = 5; + const ROTATE_RING_IDLE_INNER_RADIUS = 0.92; + const ROTATE_RING_SELECTED_INNER_RADIUS = 0.9; + + // These are multipliers for sizing the rotation degrees display while rotating an entity + const ROTATE_DISPLAY_DISTANCE_MULTIPLIER = 2; + const ROTATE_DISPLAY_SIZE_X_MULTIPLIER = 0.2; + const ROTATE_DISPLAY_SIZE_Y_MULTIPLIER = 0.09; + const ROTATE_DISPLAY_LINE_HEIGHT_MULTIPLIER = 0.07; + + const STRETCH_CUBE_OFFSET = 0.06; + const STRETCH_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.02; + const STRETCH_PANEL_WIDTH = 0.01; + + const SCALE_OVERLAY_CAMERA_DISTANCE_MULTIPLE = 0.02; + const SCALE_DIMENSIONS_CAMERA_DISTANCE_MULTIPLE = 0.5; + + const BOUNDING_EDGE_OFFSET = 0.5; + + const DUPLICATOR_OFFSET = { x: 0.9, y: -0.9, z: 0.9 }; + + const CTRL_KEY_CODE = 16777249; + + const RAIL_AXIS_LENGTH = 10000; + + const NEGATE_VECTOR = -1; + const NO_HAND = -1; + + const DEBUG_PICK_PLANE_HIT_LIMIT = 200; + const DEBUG_PICK_PLANE_HIT_CAMERA_DISTANCE_MULTIPLE = 0.01; + + const TRANSLATE_DIRECTION = { + X: 0, + Y: 1, + Z: 2 + }; + + const STRETCH_DIRECTION = { + X: 0, + Y: 1, + Z: 2, + ALL: 3 + }; + + const ROTATE_DIRECTION = { + PITCH: 0, + YAW: 1, + ROLL: 2 + }; + + const INEDIT_STATUS_CHANNEL = "Hifi-InEdit-Status"; + + /** + * The current space mode, this could have been a forced space mode since we do not support multi selection while in + * local space mode. + * @type {string} - should only be set to SPACE_LOCAL or SPACE_WORLD + */ + var spaceMode = SPACE_LOCAL; + + /** + * The desired space mode, this is the user set space mode, which should be respected whenever it is possible. In the case + * of multi entity selection this space mode may differ from the actual spaceMode. + * @type {string} - should only be set to SPACE_LOCAL or SPACE_WORLD + */ + var desiredSpaceMode = SPACE_LOCAL; + + var overlayNames = []; + var lastControllerPoses = [ + getControllerWorldLocation(Controller.Standard.LeftHand, true), + getControllerWorldLocation(Controller.Standard.RightHand, true) + ]; + + var worldRotationX; + var worldRotationY; + var worldRotationZ; + + var activeStretchCubePanelOffset = null; + + var previousHandle = null; + var previousHandleHelper = null; + var previousHandleColor; + + var ctrlPressed = false; + + that.replaceCollisionsAfterStretch = false; + + var handlePropertiesTranslateArrowCones = { + alpha: 1, + shape: "Cone", + solid: true, + visible: false, + ignorePickIntersection: true, + drawInFront: true + }; + var handlePropertiesTranslateArrowCylinders = { + alpha: 1, + shape: "Cylinder", + solid: true, + visible: false, + ignorePickIntersection: true, + drawInFront: true + }; + var handleTranslateXCone = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCones); + var handleTranslateXCylinder = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCylinders); + Overlays.editOverlay(handleTranslateXCone, { color: COLOR_RED }); + Overlays.editOverlay(handleTranslateXCylinder, { color: COLOR_RED }); + var handleTranslateYCone = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCones); + var handleTranslateYCylinder = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCylinders); + Overlays.editOverlay(handleTranslateYCone, { color: COLOR_GREEN }); + Overlays.editOverlay(handleTranslateYCylinder, { color: COLOR_GREEN }); + var handleTranslateZCone = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCones); + var handleTranslateZCylinder = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCylinders); + Overlays.editOverlay(handleTranslateZCone, { color: COLOR_BLUE }); + Overlays.editOverlay(handleTranslateZCylinder, { color: COLOR_BLUE }); + + var handlePropertiesRotateRings = { + alpha: 1, + solid: true, + startAt: 0, + endAt: 360, + innerRadius: ROTATE_RING_IDLE_INNER_RADIUS, + majorTickMarksAngle: ROTATE_DEFAULT_TICK_MARKS_ANGLE, + majorTickMarksLength: 0.1, + visible: false, + ignorePickIntersection: true, + drawInFront: true + }; + var handleRotatePitchRing = Overlays.addOverlay("circle3d", handlePropertiesRotateRings); + Overlays.editOverlay(handleRotatePitchRing, { + color: COLOR_RED, + majorTickMarksColor: COLOR_RED + }); + var handleRotateYawRing = Overlays.addOverlay("circle3d", handlePropertiesRotateRings); + Overlays.editOverlay(handleRotateYawRing, { + color: COLOR_GREEN, + majorTickMarksColor: COLOR_GREEN + }); + var handleRotateRollRing = Overlays.addOverlay("circle3d", handlePropertiesRotateRings); + Overlays.editOverlay(handleRotateRollRing, { + color: COLOR_BLUE, + majorTickMarksColor: COLOR_BLUE + }); + + var handleRotateCurrentRing = Overlays.addOverlay("circle3d", { + alpha: 1, + color: COLOR_ROTATE_CURRENT_RING, + solid: true, + innerRadius: 0.9, + visible: false, + ignorePickIntersection: true, + drawInFront: true + }); + + var rotationDegreesDisplay = Overlays.addOverlay("text3d", { + text: "", + color: { red: 0, green: 0, blue: 0 }, + backgroundColor: { red: 255, green: 255, blue: 255 }, + alpha: 0.7, + backgroundAlpha: 0.7, + visible: false, + isFacingAvatar: true, + drawInFront: true, + ignorePickIntersection: true, + dimensions: { x: 0, y: 0 }, + lineHeight: 0.0, + topMargin: 0, + rightMargin: 0, + bottomMargin: 0, + leftMargin: 0 + }); + + var handlePropertiesStretchCubes = { + solid: true, + visible: false, + ignorePickIntersection: true, + drawInFront: true + }; + var handleStretchXCube = Overlays.addOverlay("cube", handlePropertiesStretchCubes); + Overlays.editOverlay(handleStretchXCube, { color: COLOR_RED }); + var handleStretchYCube = Overlays.addOverlay("cube", handlePropertiesStretchCubes); + Overlays.editOverlay(handleStretchYCube, { color: COLOR_GREEN }); + var handleStretchZCube = Overlays.addOverlay("cube", handlePropertiesStretchCubes); + Overlays.editOverlay(handleStretchZCube, { color: COLOR_BLUE }); + + var handlePropertiesStretchPanel = { + alpha: 0.5, + solid: true, + visible: false, + ignorePickIntersection: true, + drawInFront: true + }; + var handleStretchXPanel = Overlays.addOverlay("cube", handlePropertiesStretchPanel); + Overlays.editOverlay(handleStretchXPanel, { color: COLOR_RED }); + var handleStretchYPanel = Overlays.addOverlay("cube", handlePropertiesStretchPanel); + Overlays.editOverlay(handleStretchYPanel, { color: COLOR_GREEN }); + var handleStretchZPanel = Overlays.addOverlay("cube", handlePropertiesStretchPanel); + Overlays.editOverlay(handleStretchZPanel, { color: COLOR_BLUE }); + + var handleScaleCube = Overlays.addOverlay("cube", { + size: 0.025, + color: COLOR_SCALE_CUBE, + solid: true, + visible: false, + ignorePickIntersection: true, + drawInFront: true, + borderSize: 1.4 + }); + + var handleBoundingBox = Overlays.addOverlay("cube", { + alpha: 1, + color: COLOR_BOUNDING_EDGE, + visible: false, + ignorePickIntersection: true, + drawInFront: true, + isSolid: false + }); + + var handleDuplicator = Overlays.addOverlay("cube", { + alpha: 1, + size: 0.05, + color: COLOR_GREEN, + solid: true, + visible: false, + ignorePickIntersection: true, + drawInFront: true, + borderSize: 1.4 + }); + + // setting to 0 alpha for now to keep this hidden vs using visible false + // because its used as the translate xz tool handle overlay + var selectionBox = Overlays.addOverlay("cube", { + size: 1, + color: COLOR_RED, + alpha: 0, + solid: false, + visible: false, + ignorePickIntersection: true, + dashed: false + }); + + // Handle for x-z translation of particle effect and light entities while inside the bounding box. + // Limitation: If multiple entities are selected, only the first entity's icon translates the selection. + var iconSelectionBox = Overlays.addOverlay("cube", { + size: 0.3, // Match entity icon size. + color: COLOR_RED, + alpha: 0, + solid: false, + visible: false, + ignorePickIntersection: true, + dashed: false + }); + + var xRailOverlay = Overlays.addOverlay("line3d", { + visible: false, + start: Vec3.ZERO, + end: Vec3.ZERO, + color: { + red: 255, + green: 0, + blue: 0 + }, + ignorePickIntersection: true // always ignore this + }); + var yRailOverlay = Overlays.addOverlay("line3d", { + visible: false, + start: Vec3.ZERO, + end: Vec3.ZERO, + color: { + red: 0, + green: 255, + blue: 0 + }, + ignorePickIntersection: true // always ignore this + }); + var zRailOverlay = Overlays.addOverlay("line3d", { + visible: false, + start: Vec3.ZERO, + end: Vec3.ZERO, + color: { + red: 0, + green: 0, + blue: 255 + }, + ignorePickIntersection: true // always ignore this + }); + + var allOverlays = [ + handleTranslateXCone, + handleTranslateXCylinder, + handleTranslateYCone, + handleTranslateYCylinder, + handleTranslateZCone, + handleTranslateZCylinder, + handleRotatePitchRing, + handleRotateYawRing, + handleRotateRollRing, + handleRotateCurrentRing, + rotationDegreesDisplay, + handleStretchXCube, + handleStretchYCube, + handleStretchZCube, + handleStretchXPanel, + handleStretchYPanel, + handleStretchZPanel, + handleScaleCube, + handleBoundingBox, + handleDuplicator, + selectionBox, + iconSelectionBox, + xRailOverlay, + yRailOverlay, + zRailOverlay + ]; + + const nonLayeredOverlays = [selectionBox, iconSelectionBox]; + + var maximumHandleInAllOverlays = handleDuplicator; + + overlayNames[handleTranslateXCone] = "handleTranslateXCone"; + overlayNames[handleTranslateXCylinder] = "handleTranslateXCylinder"; + overlayNames[handleTranslateYCone] = "handleTranslateYCone"; + overlayNames[handleTranslateYCylinder] = "handleTranslateYCylinder"; + overlayNames[handleTranslateZCone] = "handleTranslateZCone"; + overlayNames[handleTranslateZCylinder] = "handleTranslateZCylinder"; + + overlayNames[handleRotatePitchRing] = "handleRotatePitchRing"; + overlayNames[handleRotateYawRing] = "handleRotateYawRing"; + overlayNames[handleRotateRollRing] = "handleRotateRollRing"; + overlayNames[handleRotateCurrentRing] = "handleRotateCurrentRing"; + overlayNames[rotationDegreesDisplay] = "rotationDegreesDisplay"; + + overlayNames[handleStretchXCube] = "handleStretchXCube"; + overlayNames[handleStretchYCube] = "handleStretchYCube"; + overlayNames[handleStretchZCube] = "handleStretchZCube"; + overlayNames[handleStretchXPanel] = "handleStretchXPanel"; + overlayNames[handleStretchYPanel] = "handleStretchYPanel"; + overlayNames[handleStretchZPanel] = "handleStretchZPanel"; + + overlayNames[handleScaleCube] = "handleScaleCube"; + + overlayNames[handleBoundingBox] = "handleBoundingBox"; + + overlayNames[handleDuplicator] = "handleDuplicator"; + overlayNames[selectionBox] = "selectionBox"; + overlayNames[iconSelectionBox] = "iconSelectionBox"; + + var activeTool = null; + var handleTools = {}; + + var debugPickPlaneEnabled = false; + var debugPickPlane = Overlays.addOverlay("shape", { + shape: "Quad", + alpha: 0.25, + color: COLOR_DEBUG_PICK_PLANE, + solid: true, + visible: false, + ignorePickIntersection: true, + drawInFront: false + }); + var debugPickPlaneHits = []; + + // We get mouseMoveEvents from the handControllers, via handControllerPointer. + // But we dont' get mousePressEvents. + that.triggerClickMapping = Controller.newMapping(Script.resolvePath('') + '-click'); + that.triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press'); + that.triggeredHand = NO_HAND; + that.pressedHand = NO_HAND; + that.editingHand = NO_HAND; + that.triggered = function() { + return that.triggeredHand !== NO_HAND; + }; + function pointingAtDesktopWindowOrTablet(hand) { + var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand && + SelectionManager.pointingAtDesktopWindowRight) || + (hand === Controller.Standard.LeftHand && + SelectionManager.pointingAtDesktopWindowLeft); + var pointingAtTablet = (hand === Controller.Standard.RightHand && SelectionManager.pointingAtTabletRight) || + (hand === Controller.Standard.LeftHand && SelectionManager.pointingAtTabletLeft); + return pointingAtDesktopWindow || pointingAtTablet; + } + function makeClickHandler(hand) { + return function (clicked) { + // Don't allow both hands to trigger at the same time + if (that.triggered() && hand !== that.triggeredHand) { + return; + } + if (!that.triggered() && clicked && !pointingAtDesktopWindowOrTablet(hand)) { + that.triggeredHand = hand; + that.mousePressEvent({}); + } else if (that.triggered() && !clicked) { + that.triggeredHand = NO_HAND; + that.mouseReleaseEvent({}); + } + }; + } + function makePressHandler(hand) { + return function (value) { + if (value >= TRIGGER_ON_VALUE && !that.triggered() && !pointingAtDesktopWindowOrTablet(hand)) { + that.pressedHand = hand; + that.updateHighlight({}); + } else { + that.pressedHand = NO_HAND; + that.resetPreviousHandleColor(); + } + } + } + that.triggerClickMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); + that.triggerClickMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); + that.triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); + that.triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); + that.enableTriggerMapping = function() { + that.triggerClickMapping.enable(); + that.triggerPressMapping.enable(); + }; + that.disableTriggerMapping = function() { + that.triggerClickMapping.disable(); + that.triggerPressMapping.disable(); + }; + Script.scriptEnding.connect(that.disableTriggerMapping); + + // FUNCTION DEF(s): Intersection Check Helpers + function testRayIntersect(queryRay, overlayIncludes, overlayExcludes) { + var wantDebug = false; + if ((queryRay === undefined) || (queryRay === null)) { + if (wantDebug) { + print("testRayIntersect - EARLY EXIT -> queryRay is undefined OR null!"); + } + return null; + } + + // We want to first check the drawInFront overlays (i.e. the handles, but really everything except the selectionBoxes) + // so that you can click on them even when they're behind things + var overlayIncludesLayered = []; + var overlayIncludesNonLayered = []; + for (var i = 0; i < overlayIncludes.length; i++) { + var value = overlayIncludes[i]; + var contains = false; + for (var j = 0; j < nonLayeredOverlays.length; j++) { + if (nonLayeredOverlays[j] === value) { + contains = true; + break; + } + } + if (contains) { + overlayIncludesNonLayered.push(value); + } else { + overlayIncludesLayered.push(value); + } + } + + var intersectObj = Overlays.findRayIntersection(queryRay, true, overlayIncludesLayered, overlayExcludes); + + if (!intersectObj.intersects && overlayIncludesNonLayered.length > 0) { + intersectObj = Overlays.findRayIntersection(queryRay, true, overlayIncludesNonLayered, overlayExcludes); + } + + if (wantDebug) { + if (!overlayIncludes) { + print("testRayIntersect - no overlayIncludes provided."); + } + if (!overlayExcludes) { + print("testRayIntersect - no overlayExcludes provided."); + } + print("testRayIntersect - Hit: " + intersectObj.intersects); + print(" intersectObj.overlayID:" + intersectObj.overlayID + "[" + overlayNames[intersectObj.overlayID] + "]"); + print(" OverlayName: " + overlayNames[intersectObj.overlayID]); + print(" intersectObj.distance:" + intersectObj.distance); + print(" intersectObj.face:" + intersectObj.face); + Vec3.print(" intersectObj.intersection:", intersectObj.intersection); + } + + return intersectObj; + } + + function isPointInsideBox(point, box) { + var position = Vec3.subtract(point, box.position); + position = Vec3.multiplyQbyV(Quat.inverse(box.rotation), position); + return Math.abs(position.x) <= box.dimensions.x / 2 && Math.abs(position.y) <= box.dimensions.y / 2 + && Math.abs(position.z) <= box.dimensions.z / 2; + } + + that.isEditHandle = function(overlayID) { + var overlayIndex = allOverlays.indexOf(overlayID); + var maxHandleIndex = allOverlays.indexOf(maximumHandleInAllOverlays); + return overlayIndex >= 0 && overlayIndex <= maxHandleIndex; + }; + + // FUNCTION: MOUSE PRESS EVENT + that.mousePressEvent = function (event) { + var wantDebug = false; + if (wantDebug) { + print("=============== eST::MousePressEvent BEG ======================="); + } + if (!event.isLeftButton && !that.triggered()) { + // EARLY EXIT-(if another mouse button than left is pressed ignore it) + return false; + } + + var pickRay = generalComputePickRay(event.x, event.y); + // TODO_Case6491: Move this out to setup just to make it once + var interactiveOverlays = getMainTabletIDs(); + for (var key in handleTools) { + if (handleTools.hasOwnProperty(key)) { + interactiveOverlays.push(key); + } + } + + // Start with unknown mode, in case no tool can handle this. + activeTool = null; + + var results = testRayIntersect(pickRay, interactiveOverlays); + if (results.intersects) { + var hitOverlayID = results.overlayID; + if ((HMD.tabletID && hitOverlayID === HMD.tabletID) || (HMD.tabletScreenID && hitOverlayID === HMD.tabletScreenID) + || (HMD.homeButtonID && hitOverlayID === HMD.homeButtonID)) { + // EARLY EXIT-(mouse clicks on the tablet should override the edit affordances) + return false; + } + + var hitTool = handleTools[ hitOverlayID ]; + if (hitTool) { + activeTool = hitTool; + that.clearDebugPickPlane(); + if (activeTool.onBegin) { + that.editingHand = that.triggeredHand; + Messages.sendLocalMessage(INEDIT_STATUS_CHANNEL, JSON.stringify({ + method: "editing", + hand: that.editingHand === Controller.Standard.LeftHand ? LEFT_HAND : RIGHT_HAND, + editing: true + })); + activeTool.onBegin(event, pickRay, results); + } else { + print("ERROR: entitySelectionTool.mousePressEvent - ActiveTool(" + activeTool.mode + ") missing onBegin"); + } + } else { + print("ERROR: entitySelectionTool.mousePressEvent - Hit unexpected object, check interactiveOverlays"); + }// End_if (hitTool) + }// End_If(results.intersects) + + if (wantDebug) { + print(" DisplayMode: " + getMode()); + print("=============== eST::MousePressEvent END ======================="); + } + + // If mode is known then we successfully handled this; + // otherwise, we're missing a tool. + return activeTool; + }; + + that.resetPreviousHandleColor = function() { + if (previousHandle !== null) { + Overlays.editOverlay(previousHandle, { color: previousHandleColor }); + previousHandle = null; + } + if (previousHandleHelper !== null) { + Overlays.editOverlay(previousHandleHelper, { color: previousHandleColor }); + previousHandleHelper = null; + } + }; + + that.getHandleHelper = function(overlay) { + if (overlay === handleTranslateXCone) { + return handleTranslateXCylinder; + } else if (overlay === handleTranslateXCylinder) { + return handleTranslateXCone; + } else if (overlay === handleTranslateYCone) { + return handleTranslateYCylinder; + } else if (overlay === handleTranslateYCylinder) { + return handleTranslateYCone; + } else if (overlay === handleTranslateZCone) { + return handleTranslateZCylinder; + } else if (overlay === handleTranslateZCylinder) { + return handleTranslateZCone; + } + return Uuid.NULL; + }; + + that.updateHighlight = function(event) { + // if no tool is active, then just look for handles to highlight... + var pickRay = generalComputePickRay(event.x, event.y); + var result = testRayIntersect(pickRay, allOverlays); + var pickedColor; + var highlightNeeded = false; + + if (result.intersects) { + switch (result.overlayID) { + case handleTranslateXCone: + case handleTranslateXCylinder: + case handleRotatePitchRing: + case handleStretchXCube: + pickedColor = COLOR_RED; + highlightNeeded = true; + break; + case handleTranslateYCone: + case handleTranslateYCylinder: + case handleRotateYawRing: + case handleStretchYCube: + pickedColor = COLOR_GREEN; + highlightNeeded = true; + break; + case handleTranslateZCone: + case handleTranslateZCylinder: + case handleRotateRollRing: + case handleStretchZCube: + pickedColor = COLOR_BLUE; + highlightNeeded = true; + break; + case handleScaleCube: + pickedColor = COLOR_SCALE_CUBE; + highlightNeeded = true; + break; + default: + that.resetPreviousHandleColor(); + break; + } + + if (highlightNeeded) { + that.resetPreviousHandleColor(); + Overlays.editOverlay(result.overlayID, { color: COLOR_HOVER }); + previousHandle = result.overlayID; + previousHandleHelper = that.getHandleHelper(result.overlayID); + if (previousHandleHelper !== null) { + Overlays.editOverlay(previousHandleHelper, { color: COLOR_HOVER }); + } + previousHandleColor = pickedColor; + } + + } else { + that.resetPreviousHandleColor(); + } + }; + + // FUNCTION: MOUSE MOVE EVENT + var lastMouseEvent = null; + that.mouseMoveEvent = function(event) { + var wantDebug = false; + if (wantDebug) { + print("=============== eST::MouseMoveEvent BEG ======================="); + } + lastMouseEvent = event; + if (activeTool) { + if (wantDebug) { + print(" Trigger ActiveTool(" + activeTool.mode + ")'s onMove"); + } + activeTool.onMove(event); + + if (wantDebug) { + print(" Trigger SelectionManager::update"); + } + SelectionManager._update(false, that); + + if (wantDebug) { + print("=============== eST::MouseMoveEvent END ======================="); + } + // EARLY EXIT--(Move handled via active tool) + return true; + } + + that.updateHighlight(event); + + if (wantDebug) { + print("=============== eST::MouseMoveEvent END ======================="); + } + return false; + }; + + // FUNCTION: MOUSE RELEASE EVENT + that.mouseReleaseEvent = function(event) { + var wantDebug = false; + if (wantDebug) { + print("=============== eST::MouseReleaseEvent BEG ======================="); + } + var showHandles = false; + if (activeTool) { + if (activeTool.onEnd) { + if (wantDebug) { + print(" Triggering ActiveTool(" + activeTool.mode + ")'s onEnd"); + } + Messages.sendLocalMessage(INEDIT_STATUS_CHANNEL, JSON.stringify({ + method: "editing", + hand: that.editingHand === Controller.Standard.LeftHand ? LEFT_HAND : RIGHT_HAND, + editing: false + })); + that.editingHand = NO_HAND; + activeTool.onEnd(event); + } else if (wantDebug) { + print(" ActiveTool(" + activeTool.mode + ")'s missing onEnd"); + } + } + + showHandles = activeTool; // base on prior tool value + activeTool = null; + + // if something is selected, then reset the "original" properties for any potential next click+move operation + if (SelectionManager.hasSelection()) { + if (showHandles) { + if (wantDebug) { + print(" Triggering that.select"); + } + that.select(SelectionManager.selections[0], event); + } + } + + if (wantDebug) { + print("=============== eST::MouseReleaseEvent END ======================="); + } + }; + + // Control key remains active only while key is held down + that.keyReleaseEvent = function(event) { + if (event.key === CTRL_KEY_CODE) { + ctrlPressed = false; + that.updateActiveRotateRing(); + } + that.updateLastMouseEvent(event); + }; + + // Triggers notification on specific key driven events + that.keyPressEvent = function(event) { + if (event.key === CTRL_KEY_CODE) { + ctrlPressed = true; + that.updateActiveRotateRing(); + } + that.updateLastMouseEvent(event); + }; + + that.updateLastMouseEvent = function(event) { + if (activeTool && lastMouseEvent !== null) { + var change = lastMouseEvent.isShifted !== event.isShifted || lastMouseEvent.isMeta !== event.isMeta || + lastMouseEvent.isControl !== event.isControl || lastMouseEvent.isAlt !== event.isAlt; + lastMouseEvent.isShifted = event.isShifted; + lastMouseEvent.isMeta = event.isMeta; + lastMouseEvent.isControl = event.isControl; + lastMouseEvent.isAlt = event.isAlt; + if (change) { + activeTool.onMove(lastMouseEvent); + } + } + }; + + // NOTE: mousePressEvent and mouseMoveEvent from the main script should call us., so we don't hook these: + // Controller.mousePressEvent.connect(that.mousePressEvent); + // Controller.mouseMoveEvent.connect(that.mouseMoveEvent); + Controller.mouseReleaseEvent.connect(that.mouseReleaseEvent); + Controller.keyPressEvent.connect(that.keyPressEvent); + Controller.keyReleaseEvent.connect(that.keyReleaseEvent); + + that.checkControllerMove = function() { + if (SelectionManager.hasSelection()) { + var controllerPose = getControllerWorldLocation(that.triggeredHand, true); + var hand = (that.triggeredHand === Controller.Standard.LeftHand) ? 0 : 1; + if (controllerPose.valid && lastControllerPoses[hand].valid && that.triggered()) { + if (!Vec3.equal(controllerPose.position, lastControllerPoses[hand].position) || + !Vec3.equal(controllerPose.rotation, lastControllerPoses[hand].rotation)) { + that.mouseMoveEvent({}); + } + } + lastControllerPoses[hand] = controllerPose; + } + }; + + function controllerComputePickRay() { + var hand = that.triggered() ? that.triggeredHand : that.pressedHand; + var controllerPose = getControllerWorldLocation(hand, true); + if (controllerPose.valid) { + var controllerPosition = controllerPose.translation; + // This gets point direction right, but if you want general quaternion it would be more complicated: + var controllerDirection = Quat.getUp(controllerPose.rotation); + return {origin: controllerPosition, direction: controllerDirection}; + } + } + + function generalComputePickRay(x, y) { + return controllerComputePickRay() || Camera.computePickRay(x, y); + } + + function getControllerAvatarFramePositionFromPickRay(pickRay) { + var controllerPosition = Vec3.subtract(pickRay.origin, MyAvatar.position); + controllerPosition = Vec3.multiplyQbyV(Quat.inverse(MyAvatar.orientation), controllerPosition); + return controllerPosition; + } + + function getDistanceToCamera(position) { + var cameraPosition = Camera.getPosition(); + var toCameraDistance = Vec3.length(Vec3.subtract(cameraPosition, position)); + return toCameraDistance; + } + + function usePreviousPickRay(pickRayDirection, previousPickRayDirection, normal) { + return (Vec3.dot(pickRayDirection, normal) > 0 && Vec3.dot(previousPickRayDirection, normal) < 0) || + (Vec3.dot(pickRayDirection, normal) < 0 && Vec3.dot(previousPickRayDirection, normal) > 0); + } + + // @return string - The mode of the currently active tool; + // otherwise, "UNKNOWN" if there's no active tool. + function getMode() { + return (activeTool ? activeTool.mode : "UNKNOWN"); + } + + that.cleanup = function() { + for (var i = 0; i < allOverlays.length; i++) { + Overlays.deleteOverlay(allOverlays[i]); + } + that.clearDebugPickPlane(); + }; + + that.select = function(entityID, event) { + var properties = Entities.getEntityProperties(SelectionManager.selections[0]); + + if (event !== false) { + var wantDebug = false; + if (wantDebug) { + print("select() with EVENT...... "); + print(" event.y:" + event.y); + Vec3.print(" current position:", properties.position); + } + } + + that.updateHandles(); + }; + + + /** + * This callback is used for spaceMode changes. + * @callback spaceModeChangedCallback + * @param {string} spaceMode + */ + + /** + * set this property with a callback to keep track of spaceMode changes. + * @type {spaceModeChangedCallback} + */ + that.onSpaceModeChange = null; + + // FUNCTION: SET SPACE MODE + that.setSpaceMode = function(newSpaceMode, isDesiredChange) { + var wantDebug = false; + if (wantDebug) { + print("======> SetSpaceMode called. ========"); + } + + if (spaceMode !== newSpaceMode) { + if (wantDebug) { + print(" Updating SpaceMode From: " + spaceMode + " To: " + newSpaceMode); + } + if (isDesiredChange) { + desiredSpaceMode = newSpaceMode; + } + spaceMode = newSpaceMode; + + if (that.onSpaceModeChange !== null) { + that.onSpaceModeChange(newSpaceMode); + } + + that.updateHandles(); + } else if (wantDebug) { + print("WARNING: entitySelectionTool.setSpaceMode - Can't update SpaceMode. CurrentMode: " + + spaceMode + " DesiredMode: " + newSpaceMode); + } + if (wantDebug) { + print("====== SetSpaceMode called. <========"); + } + }; + + // FUNCTION: TOGGLE SPACE MODE + that.toggleSpaceMode = function() { + var wantDebug = false; + if (wantDebug) { + print("========> ToggleSpaceMode called. ========="); + } + if ((spaceMode === SPACE_WORLD) && (SelectionManager.selections.length > 1)) { + if (wantDebug) { + print("Local space editing is not available with multiple selections"); + } + return; + } + if (wantDebug) { + print("PreToggle: " + spaceMode); + } + that.setSpaceMode((spaceMode === SPACE_LOCAL) ? SPACE_WORLD : SPACE_LOCAL, true); + if (wantDebug) { + print("PostToggle: " + spaceMode); + print("======== ToggleSpaceMode called. <========="); + } + }; + + /** + * Switches the display mode back to the set desired display mode + */ + that.useDesiredSpaceMode = function() { + var wantDebug = false; + if (wantDebug) { + print("========> UseDesiredSpaceMode called. ========="); + } + that.setSpaceMode(desiredSpaceMode, false); + if (wantDebug) { + print("PostToggle: " + spaceMode); + print("======== UseDesiredSpaceMode called. <========="); + } + }; + + /** + * Get the currently set SpaceMode + * @returns {string} spaceMode + */ + that.getSpaceMode = function() { + return spaceMode; + }; + + function addHandleTool(overlay, tool) { + handleTools[overlay] = tool; + return tool; + } + + // @param: toolHandle: The overlayID associated with the tool + // that correlates to the tool you wish to query. + // @note: If toolHandle is null or undefined then activeTool + // will be checked against those values as opposed to + // the tool registered under toolHandle. Null & Undefined + // are treated as separate values. + // @return: bool - Indicates if the activeTool is that queried. + function isActiveTool(toolHandle) { + if (!toolHandle) { + // Allow isActiveTool(null) and similar to return true if there's + // no active tool + return (activeTool === toolHandle); + } + + if (!handleTools.hasOwnProperty(toolHandle)) { + print("WARNING: entitySelectionTool.isActiveTool - Encountered unknown grabberToolHandle: " + + toolHandle + ". Tools should be registered via addHandleTool."); + // EARLY EXIT + return false; + } + + return (activeTool === handleTools[ toolHandle ]); + } + + // FUNCTION: UPDATE HANDLES + that.updateHandles = function() { + var wantDebug = false; + if (wantDebug) { + print("======> Update Handles ======="); + print(" Selections Count: " + SelectionManager.selections.length); + print(" SpaceMode: " + spaceMode); + print(" DisplayMode: " + getMode()); + } + + if (SelectionManager.selections.length === 0) { + that.setOverlaysVisible(false); + that.clearDebugPickPlane(); + return; + } + + if (SelectionManager.hasSelection()) { + var position = SelectionManager.worldPosition; + var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation; + var dimensions = spaceMode === SPACE_LOCAL ? SelectionManager.localDimensions : SelectionManager.worldDimensions; + var rotationInverse = Quat.inverse(rotation); + var toCameraDistance = getDistanceToCamera(position); + + var rotationDegrees = 90; + var localRotationX = Quat.fromPitchYawRollDegrees(0, 0, -rotationDegrees); + var rotationX = Quat.multiply(rotation, localRotationX); + worldRotationX = rotationX; + var localRotationY = Quat.fromPitchYawRollDegrees(0, rotationDegrees, 0); + var rotationY = Quat.multiply(rotation, localRotationY); + worldRotationY = rotationY; + var localRotationZ = Quat.fromPitchYawRollDegrees(rotationDegrees, 0, 0); + var rotationZ = Quat.multiply(rotation, localRotationZ); + worldRotationZ = rotationZ; + + var selectionBoxGeometry = { + position: position, + rotation: rotation, + dimensions: dimensions + }; + var isCameraInsideBox = isPointInsideBox(Camera.position, selectionBoxGeometry); + + // in HMD if outside the bounding box clamp the overlays to the bounding box for now so lasers can hit them + var maxHandleDimension = 0; + if (HMD.active && !isCameraInsideBox) { + maxHandleDimension = Math.max(dimensions.x, dimensions.y, dimensions.z); + } + + // UPDATE ROTATION RINGS + // rotateDimension is used as the base dimension for all overlays + var rotateDimension = Math.max(maxHandleDimension, toCameraDistance * ROTATE_RING_CAMERA_DISTANCE_MULTIPLE); + var rotateDimensions = { x: rotateDimension, y: rotateDimension, z: rotateDimension }; + if (!isActiveTool(handleRotatePitchRing)) { + Overlays.editOverlay(handleRotatePitchRing, { + position: position, + rotation: rotationY, + dimensions: rotateDimensions, + majorTickMarksAngle: ROTATE_DEFAULT_TICK_MARKS_ANGLE + }); + } + if (!isActiveTool(handleRotateYawRing)) { + Overlays.editOverlay(handleRotateYawRing, { + position: position, + rotation: rotationZ, + dimensions: rotateDimensions, + majorTickMarksAngle: ROTATE_DEFAULT_TICK_MARKS_ANGLE + }); + } + if (!isActiveTool(handleRotateRollRing)) { + Overlays.editOverlay(handleRotateRollRing, { + position: position, + rotation: rotationX, + dimensions: rotateDimensions, + majorTickMarksAngle: ROTATE_DEFAULT_TICK_MARKS_ANGLE + }); + } + Overlays.editOverlay(handleRotateCurrentRing, { dimensions: rotateDimensions }); + that.updateActiveRotateRing(); + + // UPDATE TRANSLATION ARROWS + var arrowCylinderDimension = rotateDimension * TRANSLATE_ARROW_CYLINDER_CAMERA_DISTANCE_MULTIPLE / + ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; + var arrowCylinderDimensions = { + x: arrowCylinderDimension, + y: arrowCylinderDimension * TRANSLATE_ARROW_CYLINDER_Y_MULTIPLE, + z: arrowCylinderDimension + }; + var arrowConeDimension = rotateDimension * TRANSLATE_ARROW_CONE_CAMERA_DISTANCE_MULTIPLE / + ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; + var arrowConeDimensions = { x: arrowConeDimension, y: arrowConeDimension, z: arrowConeDimension }; + var arrowCylinderOffset = rotateDimension * TRANSLATE_ARROW_CYLINDER_OFFSET / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; + var arrowConeOffset = arrowCylinderDimensions.y * TRANSLATE_ARROW_CONE_OFFSET_CYLINDER_DIMENSION_MULTIPLE; + var cylinderXPosition = { x: arrowCylinderOffset, y: 0, z: 0 }; + cylinderXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, cylinderXPosition)); + Overlays.editOverlay(handleTranslateXCylinder, { + position: cylinderXPosition, + rotation: rotationX, + dimensions: arrowCylinderDimensions + }); + var cylinderXOffset = Vec3.subtract(cylinderXPosition, position); + var coneXPosition = Vec3.sum(cylinderXPosition, Vec3.multiply(Vec3.normalize(cylinderXOffset), arrowConeOffset)); + Overlays.editOverlay(handleTranslateXCone, { + position: coneXPosition, + rotation: rotationX, + dimensions: arrowConeDimensions + }); + var cylinderYPosition = { x: 0, y: arrowCylinderOffset, z: 0 }; + cylinderYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, cylinderYPosition)); + Overlays.editOverlay(handleTranslateYCylinder, { + position: cylinderYPosition, + rotation: rotationY, + dimensions: arrowCylinderDimensions + }); + var cylinderYOffset = Vec3.subtract(cylinderYPosition, position); + var coneYPosition = Vec3.sum(cylinderYPosition, Vec3.multiply(Vec3.normalize(cylinderYOffset), arrowConeOffset)); + Overlays.editOverlay(handleTranslateYCone, { + position: coneYPosition, + rotation: rotationY, + dimensions: arrowConeDimensions + }); + var cylinderZPosition = { x: 0, y: 0, z: arrowCylinderOffset }; + cylinderZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, cylinderZPosition)); + Overlays.editOverlay(handleTranslateZCylinder, { + position: cylinderZPosition, + rotation: rotationZ, + dimensions: arrowCylinderDimensions + }); + var cylinderZOffset = Vec3.subtract(cylinderZPosition, position); + var coneZPosition = Vec3.sum(cylinderZPosition, Vec3.multiply(Vec3.normalize(cylinderZOffset), arrowConeOffset)); + Overlays.editOverlay(handleTranslateZCone, { + position: coneZPosition, + rotation: rotationZ, + dimensions: arrowConeDimensions + }); + + // UPDATE SCALE CUBE + var scaleCubeRotation = spaceMode === SPACE_LOCAL ? rotation : Quat.IDENTITY; + var scaleCubeDimension = rotateDimension * SCALE_OVERLAY_CAMERA_DISTANCE_MULTIPLE / + ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; + var scaleCubeDimensions = { x: scaleCubeDimension, y: scaleCubeDimension, z: scaleCubeDimension }; + Overlays.editOverlay(handleScaleCube, { + position: position, + rotation: scaleCubeRotation, + dimensions: scaleCubeDimensions + }); + + // UPDATE BOUNDING BOX + Overlays.editOverlay(handleBoundingBox, { + position: position, + rotation: rotation, + dimensions: dimensions + }); + + // UPDATE STRETCH HIGHLIGHT PANELS + var edgeOffsetX = BOUNDING_EDGE_OFFSET * dimensions.x; + var edgeOffsetY = BOUNDING_EDGE_OFFSET * dimensions.y; + var edgeOffsetZ = BOUNDING_EDGE_OFFSET * dimensions.z; + var RBFPosition = { x: edgeOffsetX, y: -edgeOffsetY, z: edgeOffsetZ }; + RBFPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RBFPosition)); + var RTFPosition = { x: edgeOffsetX, y: edgeOffsetY, z: edgeOffsetZ }; + RTFPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RTFPosition)); + var LTNPosition = { x: -edgeOffsetX, y: edgeOffsetY, z: -edgeOffsetZ }; + LTNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, LTNPosition)); + var RTNPosition = { x: edgeOffsetX, y: edgeOffsetY, z: -edgeOffsetZ }; + RTNPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, RTNPosition)); + + var RBFPositionRotated = Vec3.multiplyQbyV(rotationInverse, RBFPosition); + var RTFPositionRotated = Vec3.multiplyQbyV(rotationInverse, RTFPosition); + var LTNPositionRotated = Vec3.multiplyQbyV(rotationInverse, LTNPosition); + var RTNPositionRotated = Vec3.multiplyQbyV(rotationInverse, RTNPosition); + var stretchPanelXDimensions = Vec3.subtract(RTNPositionRotated, RBFPositionRotated); + var tempY = Math.abs(stretchPanelXDimensions.y); + stretchPanelXDimensions.x = STRETCH_PANEL_WIDTH; + stretchPanelXDimensions.y = Math.abs(stretchPanelXDimensions.z); + stretchPanelXDimensions.z = tempY; + var stretchPanelXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x: dimensions.x / 2, y: 0, z: 0 })); + Overlays.editOverlay(handleStretchXPanel, { + position: stretchPanelXPosition, + rotation: rotationZ, + dimensions: stretchPanelXDimensions + }); + var stretchPanelYDimensions = Vec3.subtract(LTNPositionRotated, RTFPositionRotated); + var tempX = Math.abs(stretchPanelYDimensions.x); + stretchPanelYDimensions.x = Math.abs(stretchPanelYDimensions.z); + stretchPanelYDimensions.y = STRETCH_PANEL_WIDTH; + stretchPanelYDimensions.z = tempX; + var stretchPanelYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x: 0, y: dimensions.y / 2, z: 0 })); + Overlays.editOverlay(handleStretchYPanel, { + position: stretchPanelYPosition, + rotation: rotationY, + dimensions: stretchPanelYDimensions + }); + var stretchPanelZDimensions = Vec3.subtract(LTNPositionRotated, RBFPositionRotated); + tempX = Math.abs(stretchPanelZDimensions.x); + stretchPanelZDimensions.x = Math.abs(stretchPanelZDimensions.y); + stretchPanelZDimensions.y = tempX; + stretchPanelZDimensions.z = STRETCH_PANEL_WIDTH; + var stretchPanelZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x: 0, y: 0, z: dimensions.z / 2 })); + Overlays.editOverlay(handleStretchZPanel, { + position: stretchPanelZPosition, + rotation: rotationX, + dimensions: stretchPanelZDimensions + }); + + // UPDATE STRETCH CUBES + var stretchCubeDimension = rotateDimension * STRETCH_CUBE_CAMERA_DISTANCE_MULTIPLE / + ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; + var stretchCubeDimensions = { x: stretchCubeDimension, y: stretchCubeDimension, z: stretchCubeDimension }; + var stretchCubeOffset = rotateDimension * STRETCH_CUBE_OFFSET / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; + var stretchXPosition, stretchYPosition, stretchZPosition; + if (isActiveTool(handleStretchXCube)) { + stretchXPosition = Vec3.subtract(stretchPanelXPosition, activeStretchCubePanelOffset); + } else { + stretchXPosition = { x: stretchCubeOffset, y: 0, z: 0 }; + stretchXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchXPosition)); + } + if (isActiveTool(handleStretchYCube)) { + stretchYPosition = Vec3.subtract(stretchPanelYPosition, activeStretchCubePanelOffset); + } else { + stretchYPosition = { x: 0, y: stretchCubeOffset, z: 0 }; + stretchYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchYPosition)); + } + if (isActiveTool(handleStretchZCube)) { + stretchZPosition = Vec3.subtract(stretchPanelZPosition, activeStretchCubePanelOffset); + } else { + stretchZPosition = { x: 0, y: 0, z: stretchCubeOffset }; + stretchZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchZPosition)); + } + Overlays.editOverlay(handleStretchXCube, { + position: stretchXPosition, + rotation: rotationX, + dimensions: stretchCubeDimensions + }); + Overlays.editOverlay(handleStretchYCube, { + position: stretchYPosition, + rotation: rotationY, + dimensions: stretchCubeDimensions + }); + Overlays.editOverlay(handleStretchZCube, { + position: stretchZPosition, + rotation: rotationZ, + dimensions: stretchCubeDimensions + }); + + // UPDATE SELECTION BOX (CURRENTLY INVISIBLE WITH 0 ALPHA FOR TRANSLATE XZ TOOL) + var inModeRotate = isActiveTool(handleRotatePitchRing) || + isActiveTool(handleRotateYawRing) || + isActiveTool(handleRotateRollRing); + selectionBoxGeometry.visible = !inModeRotate && !isCameraInsideBox; + selectionBoxGeometry.ignorePickIntersection = !selectionBoxGeometry.visible; + Overlays.editOverlay(selectionBox, selectionBoxGeometry); + + // UPDATE ICON TRANSLATE HANDLE + if (SelectionManager.entityType === "ParticleEffect" || SelectionManager.entityType === "Light") { + var iconSelectionBoxGeometry = { + position: position, + rotation: rotation + }; + iconSelectionBoxGeometry.visible = !inModeRotate && isCameraInsideBox; + iconSelectionBoxGeometry.ignorePickIntersection = !iconSelectionBoxGeometry.visible; + Overlays.editOverlay(iconSelectionBox, iconSelectionBoxGeometry); + } else { + Overlays.editOverlay(iconSelectionBox, { + visible: false, + ignorePickIntersection: true + }); + } + + // UPDATE DUPLICATOR (CURRENTLY HIDDEN FOR NOW) + var handleDuplicatorOffset = { + x: DUPLICATOR_OFFSET.x * dimensions.x, + y: DUPLICATOR_OFFSET.y * dimensions.y, + z: DUPLICATOR_OFFSET.z * dimensions.z + }; + var handleDuplicatorPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, handleDuplicatorOffset)); + Overlays.editOverlay(handleDuplicator, { + position: handleDuplicatorPos, + rotation: rotation, + dimensions: scaleCubeDimensions + }); + } + + that.setHandleTranslateXVisible(!activeTool || isActiveTool(handleTranslateXCone) || + isActiveTool(handleTranslateXCylinder)); + that.setHandleTranslateYVisible(!activeTool || isActiveTool(handleTranslateYCone) || + isActiveTool(handleTranslateYCylinder)); + that.setHandleTranslateZVisible(!activeTool || isActiveTool(handleTranslateZCone) || + isActiveTool(handleTranslateZCylinder)); + that.setHandleRotatePitchVisible(!activeTool || isActiveTool(handleRotatePitchRing)); + that.setHandleRotateYawVisible(!activeTool || isActiveTool(handleRotateYawRing)); + that.setHandleRotateRollVisible(!activeTool || isActiveTool(handleRotateRollRing)); + + var showScaleStretch = !activeTool && SelectionManager.selections.length === 1 && spaceMode === SPACE_LOCAL; + that.setHandleStretchXVisible(showScaleStretch || isActiveTool(handleStretchXCube)); + that.setHandleStretchYVisible(showScaleStretch || isActiveTool(handleStretchYCube)); + that.setHandleStretchZVisible(showScaleStretch || isActiveTool(handleStretchZCube)); + that.setHandleScaleVisible(showScaleStretch || isActiveTool(handleScaleCube)); + + var showOutlineForZone = (SelectionManager.selections.length === 1 && + typeof SelectionManager.savedProperties[SelectionManager.selections[0]] !== "undefined" && + SelectionManager.savedProperties[SelectionManager.selections[0]].type === "Zone"); + that.setHandleBoundingBoxVisible(showOutlineForZone || (!isActiveTool(handleRotatePitchRing) && + !isActiveTool(handleRotateYawRing) && + !isActiveTool(handleRotateRollRing))); + + // keep duplicator always hidden for now since you can hold Alt to duplicate while + // translating an entity - we may bring duplicator back for HMD only later + // that.setHandleDuplicatorVisible(!activeTool || isActiveTool(handleDuplicator)); + + if (wantDebug) { + print("====== Update Handles <======="); + } + }; + Script.update.connect(that.updateHandles); + + // FUNCTION: UPDATE ACTIVE ROTATE RING + that.updateActiveRotateRing = function() { + var activeRotateRing = null; + if (isActiveTool(handleRotatePitchRing)) { + activeRotateRing = handleRotatePitchRing; + } else if (isActiveTool(handleRotateYawRing)) { + activeRotateRing = handleRotateYawRing; + } else if (isActiveTool(handleRotateRollRing)) { + activeRotateRing = handleRotateRollRing; + } + if (activeRotateRing !== null) { + var tickMarksAngle = ctrlPressed ? ROTATE_CTRL_SNAP_ANGLE : ROTATE_DEFAULT_TICK_MARKS_ANGLE; + Overlays.editOverlay(activeRotateRing, { majorTickMarksAngle: tickMarksAngle }); + } + }; + + // FUNCTION: SET OVERLAYS VISIBLE + that.setOverlaysVisible = function(isVisible) { + for (var i = 0, length = allOverlays.length; i < length; i++) { + Overlays.editOverlay(allOverlays[i], { visible: isVisible, ignorePickIntersection: !isVisible }); + } + }; + + // FUNCTION: SET HANDLE TRANSLATE VISIBLE + that.setHandleTranslateVisible = function(isVisible) { + that.setHandleTranslateXVisible(isVisible); + that.setHandleTranslateYVisible(isVisible); + that.setHandleTranslateZVisible(isVisible); + }; + + that.setHandleTranslateXVisible = function(isVisible) { + Overlays.editOverlay(handleTranslateXCone, { visible: isVisible, ignorePickIntersection: !isVisible }); + Overlays.editOverlay(handleTranslateXCylinder, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + that.setHandleTranslateYVisible = function(isVisible) { + Overlays.editOverlay(handleTranslateYCone, { visible: isVisible, ignorePickIntersection: !isVisible }); + Overlays.editOverlay(handleTranslateYCylinder, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + that.setHandleTranslateZVisible = function(isVisible) { + Overlays.editOverlay(handleTranslateZCone, { visible: isVisible, ignorePickIntersection: !isVisible }); + Overlays.editOverlay(handleTranslateZCylinder, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + // FUNCTION: SET HANDLE ROTATE VISIBLE + that.setHandleRotateVisible = function(isVisible) { + that.setHandleRotatePitchVisible(isVisible); + that.setHandleRotateYawVisible(isVisible); + that.setHandleRotateRollVisible(isVisible); + }; + + that.setHandleRotatePitchVisible = function(isVisible) { + Overlays.editOverlay(handleRotatePitchRing, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + that.setHandleRotateYawVisible = function(isVisible) { + Overlays.editOverlay(handleRotateYawRing, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + that.setHandleRotateRollVisible = function(isVisible) { + Overlays.editOverlay(handleRotateRollRing, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + // FUNCTION: SET HANDLE STRETCH VISIBLE + that.setHandleStretchVisible = function(isVisible) { + that.setHandleStretchXVisible(isVisible); + that.setHandleStretchYVisible(isVisible); + that.setHandleStretchZVisible(isVisible); + }; + + that.setHandleStretchXVisible = function(isVisible) { + Overlays.editOverlay(handleStretchXCube, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + that.setHandleStretchYVisible = function(isVisible) { + Overlays.editOverlay(handleStretchYCube, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + that.setHandleStretchZVisible = function(isVisible) { + Overlays.editOverlay(handleStretchZCube, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + // FUNCTION: SET HANDLE SCALE VISIBLE + that.setHandleScaleVisible = function(isVisible) { + that.setHandleScaleVisible(isVisible); + that.setHandleBoundingBoxVisible(isVisible); + }; + + that.setHandleScaleVisible = function(isVisible) { + Overlays.editOverlay(handleScaleCube, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + that.setHandleBoundingBoxVisible = function(isVisible) { + Overlays.editOverlay(handleBoundingBox, { visible: isVisible, ignorePickIntersection: true }); + }; + + // FUNCTION: SET HANDLE DUPLICATOR VISIBLE + that.setHandleDuplicatorVisible = function(isVisible) { + Overlays.editOverlay(handleDuplicator, { visible: isVisible, ignorePickIntersection: !isVisible }); + }; + + // FUNCTION: DEBUG PICK PLANE + that.showDebugPickPlane = function(pickPlanePosition, pickPlaneNormal) { + var planePlusNormal = Vec3.sum(pickPlanePosition, pickPlaneNormal); + var rotation = Quat.lookAtSimple(planePlusNormal, pickPlanePosition); + var dimensionXZ = getDistanceToCamera(pickPlanePosition) * 1.25; + var dimensions = { x:dimensionXZ, y:dimensionXZ, z:STRETCH_PANEL_WIDTH }; + Overlays.editOverlay(debugPickPlane, { + position: pickPlanePosition, + rotation: rotation, + dimensions: dimensions, + visible: true + }); + }; + + that.showDebugPickPlaneHit = function(pickHitPosition) { + var dimension = getDistanceToCamera(pickHitPosition) * DEBUG_PICK_PLANE_HIT_CAMERA_DISTANCE_MULTIPLE; + var pickPlaneHit = Overlays.addOverlay("shape", { + alpha: 0.5, + shape: "Sphere", + solid: true, + visible: true, + ignorePickIntersection: true, + drawInFront: false, + color: COLOR_DEBUG_PICK_PLANE_HIT, + position: pickHitPosition, + dimensions: { x: dimension, y: dimension, z: dimension } + }); + debugPickPlaneHits.push(pickPlaneHit); + if (debugPickPlaneHits.length > DEBUG_PICK_PLANE_HIT_LIMIT) { + var removedPickPlaneHit = debugPickPlaneHits.shift(); + Overlays.deleteOverlay(removedPickPlaneHit); + } + }; + + that.clearDebugPickPlane = function() { + Overlays.editOverlay(debugPickPlane, { visible: false }); + for (var i = 0; i < debugPickPlaneHits.length; i++) { + Overlays.deleteOverlay(debugPickPlaneHits[i]); + } + debugPickPlaneHits = []; + }; + + // TOOL DEFINITION: HANDLE TRANSLATE XZ TOOL + function addHandleTranslateXZTool(overlay, mode, doDuplicate) { + var initialPick = null; + var isConstrained = false; + var constrainMajorOnly = false; + var startPosition = null; + var duplicatedEntityIDs = null; + var pickPlanePosition = null; + var pickPlaneNormal = { x: 0, y: 1, z: 0 }; + var greatestDimension = 0.0; + var startingDistance = 0.0; + var startingElevation = 0.0; + addHandleTool(overlay, { + mode: mode, + onBegin: function(event, pickRay, pickResult) { + var wantDebug = false; + if (wantDebug) { + print("================== TRANSLATE_XZ(Beg) -> ======================="); + Vec3.print(" pickRay", pickRay); + Vec3.print(" pickRay.origin", pickRay.origin); + Vec3.print(" pickResult.intersection", pickResult.intersection); + } + + // Duplicate entities if alt is pressed. This will make a + // copy of the selected entities and move the _original_ entities, not + // the new ones. + if (event.isAlt || doDuplicate) { + duplicatedEntityIDs = SelectionManager.duplicateSelection(); + var ids = []; + for (var i = 0; i < duplicatedEntityIDs.length; ++i) { + ids.push(duplicatedEntityIDs[i].entityID); + } + SelectionManager.setSelections(ids); + } else { + duplicatedEntityIDs = null; + } + + SelectionManager.saveProperties(); + that.resetPreviousHandleColor(); + + that.setHandleTranslateVisible(false); + that.setHandleRotateVisible(false); + that.setHandleScaleVisible(false); + that.setHandleStretchVisible(false); + that.setHandleDuplicatorVisible(false); + + startPosition = SelectionManager.worldPosition; + pickPlanePosition = pickResult.intersection; + greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, + SelectionManager.worldDimensions.y), + SelectionManager.worldDimensions.z); + startingDistance = Vec3.distance(pickRay.origin, SelectionManager.position); + startingElevation = this.elevation(pickRay.origin, pickPlanePosition); + if (wantDebug) { + print(" longest dimension: " + greatestDimension); + print(" starting distance: " + startingDistance); + print(" starting elevation: " + startingElevation); + } + + initialPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + + if (debugPickPlaneEnabled) { + that.showDebugPickPlane(pickPlanePosition, pickPlaneNormal); + that.showDebugPickPlaneHit(initialPick); + } + + isConstrained = false; + if (wantDebug) { + print("================== TRANSLATE_XZ(End) <- ======================="); + } + }, + onEnd: function(event, reason) { + pushCommandForSelections(duplicatedEntityIDs); + if (isConstrained) { + Overlays.editOverlay(xRailOverlay, { + visible: false, + ignorePickIntersection: true + }); + Overlays.editOverlay(zRailOverlay, { + visible: false, + ignorePickIntersection: true + }); + } + }, + elevation: function(origin, intersection) { + return (origin.y - intersection.y) / Vec3.distance(origin, intersection); + }, + onMove: function(event) { + var wantDebug = false; + var pickRay = generalComputePickRay(event.x, event.y); + + var newPick = rayPlaneIntersection2(pickRay, pickPlanePosition, pickPlaneNormal); + + // If the pick ray doesn't hit the pick plane in this direction, do nothing. + // this will happen when someone drags across the horizon from the side they started on. + if (!newPick) { + if (wantDebug) { + print(" "+ mode + "Pick ray does not intersect XZ plane."); + } + + // EARLY EXIT--(Invalid ray detected.) + return; + } + + if (debugPickPlaneEnabled) { + that.showDebugPickPlaneHit(newPick); + } + + var vector = Vec3.subtract(newPick, initialPick); + + // If the mouse is too close to the horizon of the pick plane, stop moving + var MIN_ELEVATION = 0.02; // largest dimension of object divided by distance to it + var elevation = this.elevation(pickRay.origin, newPick); + if (wantDebug) { + print("Start Elevation: " + startingElevation + ", elevation: " + elevation); + } + if ((startingElevation > 0.0 && elevation < MIN_ELEVATION) || + (startingElevation < 0.0 && elevation > -MIN_ELEVATION)) { + if (wantDebug) { + print(" "+ mode + " - too close to horizon!"); + } + + // EARLY EXIT--(Don't proceed past the reached limit.) + return; + } + + // If the angular size of the object is too small, stop moving + var MIN_ANGULAR_SIZE = 0.01; // Radians + if (greatestDimension > 0) { + var angularSize = Math.atan(greatestDimension / Vec3.distance(pickRay.origin, newPick)); + if (wantDebug) { + print("Angular size = " + angularSize); + } + if (angularSize < MIN_ANGULAR_SIZE) { + return; + } + } + + // If shifted, constrain to one axis + if (event.isShifted) { + if (Math.abs(vector.x) > Math.abs(vector.z)) { + vector.z = 0; + } else { + vector.x = 0; + } + if (!isConstrained) { + var xStart = Vec3.sum(startPosition, { + x: -RAIL_AXIS_LENGTH, + y: 0, + z: 0 + }); + var xEnd = Vec3.sum(startPosition, { + x: RAIL_AXIS_LENGTH, + y: 0, + z: 0 + }); + var zStart = Vec3.sum(startPosition, { + x: 0, + y: 0, + z: -RAIL_AXIS_LENGTH + }); + var zEnd = Vec3.sum(startPosition, { + x: 0, + y: 0, + z: RAIL_AXIS_LENGTH + }); + Overlays.editOverlay(xRailOverlay, { + start: xStart, + end: xEnd, + visible: true, + ignorePickIntersection: true + }); + Overlays.editOverlay(zRailOverlay, { + start: zStart, + end: zEnd, + visible: true, + ignorePickIntersection: true + }); + isConstrained = true; + } + } else { + if (isConstrained) { + Overlays.editOverlay(xRailOverlay, { + visible: false, + ignorePickIntersection: true + }); + Overlays.editOverlay(zRailOverlay, { + visible: false, + ignorePickIntersection: true + }); + isConstrained = false; + } + } + + constrainMajorOnly = event.isControl; + var negateAndHalve = -0.5; + var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(negateAndHalve, SelectionManager.worldDimensions)); + vector = Vec3.subtract( + grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly), + cornerPosition); + + // editing a parent will cause all the children to automatically follow along, so don't + // edit any entity who has an ancestor in SelectionManager.selections + var toMove = SelectionManager.selections.filter(function (selection) { + if (SelectionManager.selections.indexOf(SelectionManager.savedProperties[selection].parentID) >= 0) { + return false; // a parent is also being moved, so don't issue an edit for this entity + } else { + return true; + } + }); + + for (var i = 0; i < toMove.length; i++) { + var properties = SelectionManager.savedProperties[toMove[i]]; + if (!properties) { + continue; + } + var newPosition = Vec3.sum(properties.position, { + x: vector.x, + y: 0, + z: vector.z + }); + Entities.editEntity(toMove[i], { + position: newPosition + }); + + if (wantDebug) { + print("translateXZ... "); + Vec3.print(" vector:", vector); + Vec3.print(" newPosition:", properties.position); + Vec3.print(" newPosition:", newPosition); + } + } + + SelectionManager._update(false, this); + } + }); + } + + // TOOL DEFINITION: HANDLE TRANSLATE TOOL + function addHandleTranslateTool(overlay, mode, direction) { + var pickPlanePosition = null; + var pickPlaneNormal = null; + var initialPick = null; + var projectionVector = null; + var previousPickRay = null; + var rotation = null; + addHandleTool(overlay, { + mode: mode, + onBegin: function(event, pickRay, pickResult) { + // Duplicate entities if alt is pressed. This will make a + // copy of the selected entities and move the _original_ entities, not + // the new ones. + if (event.isAlt) { + duplicatedEntityIDs = SelectionManager.duplicateSelection(); + var ids = []; + for (var i = 0; i < duplicatedEntityIDs.length; ++i) { + ids.push(duplicatedEntityIDs[i].entityID); + } + SelectionManager.setSelections(ids); + } else { + duplicatedEntityIDs = null; + } + + var axisVector; + if (direction === TRANSLATE_DIRECTION.X) { + axisVector = { x: 1, y: 0, z: 0 }; + } else if (direction === TRANSLATE_DIRECTION.Y) { + axisVector = { x: 0, y: 1, z: 0 }; + } else if (direction === TRANSLATE_DIRECTION.Z) { + axisVector = { x: 0, y: 0, z: 1 }; + } + + rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation; + axisVector = Vec3.multiplyQbyV(rotation, axisVector); + pickPlaneNormal = Vec3.cross(Vec3.cross(pickRay.direction, axisVector), axisVector); + pickPlanePosition = SelectionManager.worldPosition; + initialPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + + SelectionManager.saveProperties(); + that.resetPreviousHandleColor(); + + that.setHandleTranslateXVisible(direction === TRANSLATE_DIRECTION.X); + that.setHandleTranslateYVisible(direction === TRANSLATE_DIRECTION.Y); + that.setHandleTranslateZVisible(direction === TRANSLATE_DIRECTION.Z); + that.setHandleRotateVisible(false); + that.setHandleStretchVisible(false); + that.setHandleScaleVisible(false); + that.setHandleDuplicatorVisible(false); + + previousPickRay = pickRay; + + if (debugPickPlaneEnabled) { + that.showDebugPickPlane(pickPlanePosition, pickPlaneNormal); + that.showDebugPickPlaneHit(initialPick); + } + }, + onEnd: function(event, reason) { + pushCommandForSelections(duplicatedEntityIDs); + }, + onMove: function(event) { + var pickRay = generalComputePickRay(event.x, event.y); + + // Use previousPickRay if new pickRay will cause resulting rayPlaneIntersection values to wrap around + if (usePreviousPickRay(pickRay.direction, previousPickRay.direction, pickPlaneNormal)) { + pickRay = previousPickRay; + } + + var newPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + if (debugPickPlaneEnabled) { + that.showDebugPickPlaneHit(newPick); + } + + var vector = Vec3.subtract(newPick, initialPick); + + if (direction === TRANSLATE_DIRECTION.X) { + projectionVector = { x: 1, y: 0, z: 0 }; + } else if (direction === TRANSLATE_DIRECTION.Y) { + projectionVector = { x: 0, y: 1, z: 0 }; + } else if (direction === TRANSLATE_DIRECTION.Z) { + projectionVector = { x: 0, y: 0, z: 1 }; + } + projectionVector = Vec3.multiplyQbyV(rotation, projectionVector); + + var dotVector = Vec3.dot(vector, projectionVector); + vector = Vec3.multiply(dotVector, projectionVector); + var gridOrigin = grid.getOrigin(); + vector = Vec3.subtract(grid.snapToGrid(Vec3.sum(vector, gridOrigin)), gridOrigin); + + var wantDebug = false; + if (wantDebug) { + print("translateUpDown... "); + print(" event.y:" + event.y); + Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + } + + // editing a parent will cause all the children to automatically follow along, so don't + // edit any entity who has an ancestor in SelectionManager.selections + var toMove = SelectionManager.selections.filter(function (selection) { + if (SelectionManager.selections.indexOf(SelectionManager.savedProperties[selection].parentID) >= 0) { + return false; // a parent is also being moved, so don't issue an edit for this entity + } else { + return true; + } + }); + + for (var i = 0; i < toMove.length; i++) { + var id = toMove[i]; + var properties = SelectionManager.savedProperties[id]; + var newPosition = Vec3.sum(properties.position, vector); + Entities.editEntity(id, { position: newPosition }); + } + + previousPickRay = pickRay; + + SelectionManager._update(false, this); + } + }); + } + + // TOOL DEFINITION: HANDLE STRETCH TOOL + function addHandleStretchTool(overlay, mode, directionEnum) { + var initialPick = null; + var initialPosition = null; + var initialDimensions = null; + var rotation = null; + var registrationPoint = null; + var pickPlanePosition = null; + var pickPlaneNormal = null; + var previousPickRay = null; + var directionVector = null; + var axisVector = null; + var signs = null; + var mask = null; + var stretchPanel = null; + var handleStretchCube = null; + var deltaPivot = null; + addHandleTool(overlay, { + mode: mode, + onBegin: function(event, pickRay, pickResult) { + if (directionEnum === STRETCH_DIRECTION.X) { + stretchPanel = handleStretchXPanel; + handleStretchCube = handleStretchXCube; + directionVector = { x: -1, y: 0, z: 0 }; + } else if (directionEnum === STRETCH_DIRECTION.Y) { + stretchPanel = handleStretchYPanel; + handleStretchCube = handleStretchYCube; + directionVector = { x: 0, y: -1, z: 0 }; + } else if (directionEnum === STRETCH_DIRECTION.Z) { + stretchPanel = handleStretchZPanel; + handleStretchCube = handleStretchZCube; + directionVector = { x: 0, y: 0, z: -1 }; + } + + rotation = SelectionManager.localRotation; + initialPosition = SelectionManager.localPosition; + initialDimensions = SelectionManager.localDimensions; + registrationPoint = SelectionManager.localRegistrationPoint; + + axisVector = Vec3.multiply(NEGATE_VECTOR, directionVector); + axisVector = Vec3.multiplyQbyV(rotation, axisVector); + + signs = { + x: directionVector.x < 0 ? -1 : (directionVector.x > 0 ? 1 : 0), + y: directionVector.y < 0 ? -1 : (directionVector.y > 0 ? 1 : 0), + z: directionVector.z < 0 ? -1 : (directionVector.z > 0 ? 1 : 0) + }; + mask = { + x: Math.abs(directionVector.x) > 0 ? 1 : 0, + y: Math.abs(directionVector.y) > 0 ? 1 : 0, + z: Math.abs(directionVector.z) > 0 ? 1 : 0 + }; + + var pivot = directionVector; + var offset = Vec3.multiply(directionVector, NEGATE_VECTOR); + + // Modify range of registrationPoint to be [-0.5, 0.5] + var centeredRP = Vec3.subtract(registrationPoint, { + x: 0.5, + y: 0.5, + z: 0.5 + }); + + // Scale pivot to be in the same range as registrationPoint + var scaledPivot = Vec3.multiply(0.5, pivot); + deltaPivot = Vec3.subtract(centeredRP, scaledPivot); + + var scaledOffset = Vec3.multiply(0.5, offset); + + // Offset from the registration point + var offsetRP = Vec3.subtract(scaledOffset, centeredRP); + + // Scaled offset in world coordinates + var scaledOffsetWorld = Vec3.multiplyVbyV(initialDimensions, offsetRP); + + pickPlaneNormal = Vec3.cross(Vec3.cross(pickRay.direction, axisVector), axisVector); + pickPlanePosition = Vec3.sum(initialPosition, Vec3.multiplyQbyV(rotation, scaledOffsetWorld)); + initialPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + + that.setHandleTranslateVisible(false); + that.setHandleRotateVisible(false); + that.setHandleScaleVisible(true); + that.setHandleStretchXVisible(directionEnum === STRETCH_DIRECTION.X); + that.setHandleStretchYVisible(directionEnum === STRETCH_DIRECTION.Y); + that.setHandleStretchZVisible(directionEnum === STRETCH_DIRECTION.Z); + that.setHandleDuplicatorVisible(false); + + SelectionManager.saveProperties(); + that.resetPreviousHandleColor(); + + var collisionToRemove = "myAvatar"; + var properties = Entities.getEntityProperties(SelectionManager.selections[0]); + if (properties.collidesWith.indexOf(collisionToRemove) > -1) { + var newCollidesWith = properties.collidesWith.replace(collisionToRemove, ""); + Entities.editEntity(SelectionManager.selections[0], {collidesWith: newCollidesWith}); + that.replaceCollisionsAfterStretch = true; + } + + if (stretchPanel !== null) { + Overlays.editOverlay(stretchPanel, { visible: true, ignorePickIntersection: false }); + } + var stretchCubePosition = Overlays.getProperty(handleStretchCube, "position"); + var stretchPanelPosition = Overlays.getProperty(stretchPanel, "position"); + activeStretchCubePanelOffset = Vec3.subtract(stretchPanelPosition, stretchCubePosition); + + previousPickRay = pickRay; + + if (debugPickPlaneEnabled) { + that.showDebugPickPlane(pickPlanePosition, pickPlaneNormal); + that.showDebugPickPlaneHit(initialPick); + } + }, + onEnd: function(event, reason) { + if (that.replaceCollisionsAfterStretch) { + var newCollidesWith = SelectionManager.savedProperties[SelectionManager.selections[0]].collidesWith; + Entities.editEntity(SelectionManager.selections[0], {collidesWith: newCollidesWith}); + that.replaceCollisionsAfterStretch = false; + } + + if (stretchPanel !== null) { + Overlays.editOverlay(stretchPanel, { visible: false, ignorePickIntersection: true }); + } + activeStretchCubePanelOffset = null; + + pushCommandForSelections(); + }, + onMove: function(event) { + var pickRay = generalComputePickRay(event.x, event.y); + + // Use previousPickRay if new pickRay will cause resulting rayPlaneIntersection values to wrap around + if (usePreviousPickRay(pickRay.direction, previousPickRay.direction, pickPlaneNormal)) { + pickRay = previousPickRay; + } + + var newPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + if (debugPickPlaneEnabled) { + that.showDebugPickPlaneHit(newPick); + } + + var changeInDimensions = Vec3.subtract(newPick, initialPick); + var dotVector = Vec3.dot(changeInDimensions, axisVector); + changeInDimensions = Vec3.multiply(dotVector, axisVector); + changeInDimensions = Vec3.multiplyQbyV(Quat.inverse(rotation), changeInDimensions); + changeInDimensions = Vec3.multiplyVbyV(mask, changeInDimensions); + changeInDimensions = grid.snapToSpacing(changeInDimensions); + changeInDimensions = Vec3.multiply(NEGATE_VECTOR, Vec3.multiplyVbyV(signs, changeInDimensions)); + + var newDimensions = Vec3.sum(initialDimensions, changeInDimensions); + + var minimumDimension = Entities.getPropertyInfo("dimensions").minimum; + if (newDimensions.x < minimumDimension) { + newDimensions.x = minimumDimension; + changeInDimensions.x = minimumDimension - initialDimensions.x; + } + if (newDimensions.y < minimumDimension) { + newDimensions.y = minimumDimension; + changeInDimensions.y = minimumDimension - initialDimensions.y; + } + if (newDimensions.z < minimumDimension) { + newDimensions.z = minimumDimension; + changeInDimensions.z = minimumDimension - initialDimensions.z; + } + + var changeInPosition = Vec3.multiplyQbyV(rotation, Vec3.multiplyVbyV(deltaPivot, changeInDimensions)); + var newPosition = Vec3.sum(initialPosition, changeInPosition); + + Entities.editEntity(SelectionManager.selections[0], { + position: newPosition, + dimensions: newDimensions + }); + + var wantDebug = false; + if (wantDebug) { + print(mode); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + Vec3.print(" changeInPosition:", changeInPosition); + Vec3.print(" newPosition:", newPosition); + } + + previousPickRay = pickRay; + + SelectionManager._update(false, this); + } + }); + } + + // TOOL DEFINITION: HANDLE SCALE TOOL + function addHandleScaleTool(overlay, mode) { + var initialPick = null; + var initialPosition = null; + var initialDimensions = null; + var pickPlanePosition = null; + var pickPlaneNormal = null; + var previousPickRay = null; + addHandleTool(overlay, { + mode: mode, + onBegin: function(event, pickRay, pickResult) { + initialPosition = SelectionManager.localPosition; + initialDimensions = SelectionManager.localDimensions; + + pickPlanePosition = initialPosition; + pickPlaneNormal = Vec3.subtract(pickRay.origin, pickPlanePosition); + initialPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + + that.setHandleTranslateVisible(false); + that.setHandleRotateVisible(false); + that.setHandleScaleVisible(true); + that.setHandleStretchVisible(false); + that.setHandleDuplicatorVisible(false); + + SelectionManager.saveProperties(); + that.resetPreviousHandleColor(); + + var collisionToRemove = "myAvatar"; + var properties = Entities.getEntityProperties(SelectionManager.selections[0]); + if (properties.collidesWith.indexOf(collisionToRemove) > -1) { + var newCollidesWith = properties.collidesWith.replace(collisionToRemove, ""); + Entities.editEntity(SelectionManager.selections[0], {collidesWith: newCollidesWith}); + that.replaceCollisionsAfterStretch = true; + } + + previousPickRay = pickRay; + + if (debugPickPlaneEnabled) { + that.showDebugPickPlane(pickPlanePosition, pickPlaneNormal); + that.showDebugPickPlaneHit(initialPick); + } + }, + onEnd: function(event, reason) { + if (that.replaceCollisionsAfterStretch) { + var newCollidesWith = SelectionManager.savedProperties[SelectionManager.selections[0]].collidesWith; + Entities.editEntity(SelectionManager.selections[0], {collidesWith: newCollidesWith}); + that.replaceCollisionsAfterStretch = false; + } + + pushCommandForSelections(); + }, + onMove: function(event) { + var pickRay = generalComputePickRay(event.x, event.y); + + // Use previousPickRay if new pickRay will cause resulting rayPlaneIntersection values to wrap around + if (usePreviousPickRay(pickRay.direction, previousPickRay.direction, pickPlaneNormal)) { + pickRay = previousPickRay; + } + + var newPick = rayPlaneIntersection(pickRay, pickPlanePosition, pickPlaneNormal); + if (debugPickPlaneEnabled) { + that.showDebugPickPlaneHit(newPick); + } + + var toCameraDistance = getDistanceToCamera(initialPosition); + var dimensionsMultiple = toCameraDistance * SCALE_DIMENSIONS_CAMERA_DISTANCE_MULTIPLE; + var changeInDimensions = Vec3.subtract(newPick, initialPick); + changeInDimensions = Vec3.multiplyQbyV(Quat.inverse(Camera.orientation), changeInDimensions); + changeInDimensions = grid.snapToSpacing(changeInDimensions); + changeInDimensions = Vec3.multiply(changeInDimensions, dimensionsMultiple); + + var averageDimensionChange = (changeInDimensions.x + changeInDimensions.y + changeInDimensions.z) / 3; + var averageInitialDimension = (initialDimensions.x + initialDimensions.y + initialDimensions.z) / 3; + percentChange = averageDimensionChange / averageInitialDimension; + percentChange += 1.0; + + var newDimensions = Vec3.multiply(percentChange, initialDimensions); + newDimensions.x = Math.abs(newDimensions.x); + newDimensions.y = Math.abs(newDimensions.y); + newDimensions.z = Math.abs(newDimensions.z); + + var minimumDimension = Entities.getPropertyInfo("dimensions").minimum; + if (newDimensions.x < minimumDimension) { + newDimensions.x = minimumDimension; + changeInDimensions.x = minimumDimension - initialDimensions.x; + } + if (newDimensions.y < minimumDimension) { + newDimensions.y = minimumDimension; + changeInDimensions.y = minimumDimension - initialDimensions.y; + } + if (newDimensions.z < minimumDimension) { + newDimensions.z = minimumDimension; + changeInDimensions.z = minimumDimension - initialDimensions.z; + } + + Entities.editEntity(SelectionManager.selections[0], { dimensions: newDimensions }); + + var wantDebug = false; + if (wantDebug) { + print(mode); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + } + + previousPickRay = pickRay; + + SelectionManager._update(false, this); + } + }); + } + + // FUNCTION: UPDATE ROTATION DEGREES OVERLAY + function updateRotationDegreesOverlay(angleFromZero, position) { + var toCameraDistance = getDistanceToCamera(position); + var overlayProps = { + position: position, + dimensions: { + x: toCameraDistance * ROTATE_DISPLAY_SIZE_X_MULTIPLIER, + y: toCameraDistance * ROTATE_DISPLAY_SIZE_Y_MULTIPLIER + }, + lineHeight: toCameraDistance * ROTATE_DISPLAY_LINE_HEIGHT_MULTIPLIER, + text: normalizeDegrees(-angleFromZero) + "°" + }; + Overlays.editOverlay(rotationDegreesDisplay, overlayProps); + } + + // FUNCTION DEF: updateSelectionsRotation + // Helper func used by rotation handle tools + function updateSelectionsRotation(rotationChange, initialPosition) { + if (!rotationChange) { + print("ERROR: entitySelectionTool.updateSelectionsRotation - Invalid arg specified!!"); + + // EARLY EXIT + return; + } + + // Entities should only reposition if we are rotating multiple selections around + // the selections center point. Otherwise, the rotation will be around the entities + // registration point which does not need repositioning. + var reposition = (SelectionManager.selections.length > 1); + + // editing a parent will cause all the children to automatically follow along, so don't + // edit any entity who has an ancestor in SelectionManager.selections + var toRotate = SelectionManager.selections.filter(function (selection) { + if (SelectionManager.selections.indexOf(SelectionManager.savedProperties[selection].parentID) >= 0) { + return false; // a parent is also being moved, so don't issue an edit for this entity + } else { + return true; + } + }); + + for (var i = 0; i < toRotate.length; i++) { + var entityID = toRotate[i]; + var initialProperties = SelectionManager.savedProperties[entityID]; + + var newProperties = { + rotation: Quat.multiply(rotationChange, initialProperties.rotation) + }; + + if (reposition) { + var dPos = Vec3.subtract(initialProperties.position, initialPosition); + dPos = Vec3.multiplyQbyV(rotationChange, dPos); + newProperties.position = Vec3.sum(initialPosition, dPos); + } + + Entities.editEntity(entityID, newProperties); + } + } + + // TOOL DEFINITION: HANDLE ROTATION TOOL + function addHandleRotateTool(overlay, mode, direction) { + var selectedHandle = null; + var worldRotation = null; + var initialRotation = null; + var rotationCenter = null; + var rotationNormal = null; + var rotationZero = null; + var rotationDegreesPosition = null; + addHandleTool(overlay, { + mode: mode, + onBegin: function(event, pickRay, pickResult) { + var wantDebug = false; + if (wantDebug) { + print("================== " + getMode() + "(addHandleRotateTool onBegin) -> ======================="); + } + + if (direction === ROTATE_DIRECTION.PITCH) { + rotationNormal = { x: 1, y: 0, z: 0 }; + worldRotation = worldRotationY; + selectedHandle = handleRotatePitchRing; + } else if (direction === ROTATE_DIRECTION.YAW) { + rotationNormal = { x: 0, y: 1, z: 0 }; + worldRotation = worldRotationZ; + selectedHandle = handleRotateYawRing; + } else if (direction === ROTATE_DIRECTION.ROLL) { + rotationNormal = { x: 0, y: 0, z: 1 }; + worldRotation = worldRotationX; + selectedHandle = handleRotateRollRing; + } + + initialRotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation; + rotationNormal = Vec3.multiplyQbyV(initialRotation, rotationNormal); + rotationCenter = SelectionManager.worldPosition; + + SelectionManager.saveProperties(); + that.resetPreviousHandleColor(); + + that.setHandleTranslateVisible(false); + that.setHandleRotatePitchVisible(direction === ROTATE_DIRECTION.PITCH); + that.setHandleRotateYawVisible(direction === ROTATE_DIRECTION.YAW); + that.setHandleRotateRollVisible(direction === ROTATE_DIRECTION.ROLL); + that.setHandleStretchVisible(false); + that.setHandleScaleVisible(false); + that.setHandleDuplicatorVisible(false); + + Overlays.editOverlay(selectedHandle, { + hasTickMarks: true, + solid: false, + innerRadius: ROTATE_RING_SELECTED_INNER_RADIUS + }); + + Overlays.editOverlay(rotationDegreesDisplay, { visible: true }); + Overlays.editOverlay(handleRotateCurrentRing, { + position: rotationCenter, + rotation: worldRotation, + startAt: 0, + endAt: 0, + visible: true, + ignorePickIntersection: false + }); + + // editOverlays may not have committed rotation changes. + // Compute zero position based on where the overlay will be eventually. + var initialPick = rayPlaneIntersection(pickRay, rotationCenter, rotationNormal); + // In case of a parallel ray, this will be null, which will cause early-out + // in the onMove helper. + rotationZero = initialPick; + + var rotationCenterToZero = Vec3.subtract(rotationZero, rotationCenter); + var rotationCenterToZeroLength = Vec3.length(rotationCenterToZero); + rotationDegreesPosition = Vec3.sum(rotationCenter, Vec3.multiply(Vec3.normalize(rotationCenterToZero), + rotationCenterToZeroLength * ROTATE_DISPLAY_DISTANCE_MULTIPLIER)); + updateRotationDegreesOverlay(0, rotationDegreesPosition); + + if (debugPickPlaneEnabled) { + that.showDebugPickPlane(rotationCenter, rotationNormal); + that.showDebugPickPlaneHit(initialPick); + } + + if (wantDebug) { + print("================== " + getMode() + "(addHandleRotateTool onBegin) <- ======================="); + } + }, + onEnd: function(event, reason) { + var wantDebug = false; + if (wantDebug) { + print("================== " + getMode() + "(addHandleRotateTool onEnd) -> ======================="); + } + Overlays.editOverlay(rotationDegreesDisplay, { visible: false, ignorePickIntersection: true }); + Overlays.editOverlay(selectedHandle, { + hasTickMarks: false, + solid: true, + innerRadius: ROTATE_RING_IDLE_INNER_RADIUS + }); + Overlays.editOverlay(handleRotateCurrentRing, { visible: false, ignorePickIntersection: true }); + pushCommandForSelections(); + if (wantDebug) { + print("================== " + getMode() + "(addHandleRotateTool onEnd) <- ======================="); + } + }, + onMove: function(event) { + if (!rotationZero) { + print("ERROR: entitySelectionTool.addHandleRotateTool.onMove - " + + "Invalid RotationZero Specified (missed rotation target plane?)"); + + // EARLY EXIT + return; + } + + var wantDebug = false; + if (wantDebug) { + print("================== "+ getMode() + "(addHandleRotateTool onMove) -> ======================="); + Vec3.print(" rotationZero: ", rotationZero); + } + + var pickRay = generalComputePickRay(event.x, event.y); + var result = rayPlaneIntersection(pickRay, rotationCenter, rotationNormal); + if (result) { + var centerToZero = Vec3.subtract(rotationZero, rotationCenter); + var centerToIntersect = Vec3.subtract(result, rotationCenter); + + if (wantDebug) { + Vec3.print(" RotationNormal: ", rotationNormal); + Vec3.print(" rotationZero: ", rotationZero); + Vec3.print(" rotationCenter: ", rotationCenter); + Vec3.print(" intersect: ", result); + Vec3.print(" centerToZero: ", centerToZero); + Vec3.print(" centerToIntersect: ", centerToIntersect); + } + + // Note: orientedAngle which wants normalized centerToZero and centerToIntersect + // handles that internally, so it's to pass unnormalized vectors here. + var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); + var snapAngle = ctrlPressed ? ROTATE_CTRL_SNAP_ANGLE : ROTATE_DEFAULT_SNAP_ANGLE; + angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; + var rotationChange = Quat.angleAxis(angleFromZero, rotationNormal); + updateSelectionsRotation(rotationChange, rotationCenter); + updateRotationDegreesOverlay(-angleFromZero, rotationDegreesPosition); + + if (direction === ROTATE_DIRECTION.YAW) { + angleFromZero *= -1; + } + + var startAtCurrent = 0; + var endAtCurrent = angleFromZero; + var maxDegrees = 360; + if (angleFromZero < 0) { + startAtCurrent = maxDegrees + angleFromZero; + endAtCurrent = maxDegrees; + } + Overlays.editOverlay(handleRotateCurrentRing, { + startAt: startAtCurrent, + endAt: endAtCurrent + }); + + if (debugPickPlaneEnabled) { + that.showDebugPickPlaneHit(result); + } + } + + if (wantDebug) { + print("================== "+ getMode() + "(addHandleRotateTool onMove) <- ======================="); + } + } + }); + } + + addHandleTranslateXZTool(selectionBox, "TRANSLATE_XZ", false); + addHandleTranslateXZTool(iconSelectionBox, "TRANSLATE_XZ", false); + addHandleTranslateXZTool(handleDuplicator, "DUPLICATE", true); + + addHandleTranslateTool(handleTranslateXCone, "TRANSLATE_X", TRANSLATE_DIRECTION.X); + addHandleTranslateTool(handleTranslateXCylinder, "TRANSLATE_X", TRANSLATE_DIRECTION.X); + addHandleTranslateTool(handleTranslateYCone, "TRANSLATE_Y", TRANSLATE_DIRECTION.Y); + addHandleTranslateTool(handleTranslateYCylinder, "TRANSLATE_Y", TRANSLATE_DIRECTION.Y); + addHandleTranslateTool(handleTranslateZCone, "TRANSLATE_Z", TRANSLATE_DIRECTION.Z); + addHandleTranslateTool(handleTranslateZCylinder, "TRANSLATE_Z", TRANSLATE_DIRECTION.Z); + + addHandleRotateTool(handleRotatePitchRing, "ROTATE_PITCH", ROTATE_DIRECTION.PITCH); + addHandleRotateTool(handleRotateYawRing, "ROTATE_YAW", ROTATE_DIRECTION.YAW); + addHandleRotateTool(handleRotateRollRing, "ROTATE_ROLL", ROTATE_DIRECTION.ROLL); + + addHandleStretchTool(handleStretchXCube, "STRETCH_X", STRETCH_DIRECTION.X); + addHandleStretchTool(handleStretchYCube, "STRETCH_Y", STRETCH_DIRECTION.Y); + addHandleStretchTool(handleStretchZCube, "STRETCH_Z", STRETCH_DIRECTION.Z); + + addHandleScaleTool(handleScaleCube, "SCALE"); + + return that; +}()); diff --git a/scripts/simplifiedUI/system/modules/createWindow.js b/scripts/simplifiedUI/system/create/modules/createWindow.js similarity index 100% rename from scripts/simplifiedUI/system/modules/createWindow.js rename to scripts/simplifiedUI/system/create/modules/createWindow.js diff --git a/scripts/simplifiedUI/system/modules/entityShapeVisualizer.js b/scripts/simplifiedUI/system/create/modules/entityShapeVisualizer.js similarity index 98% rename from scripts/simplifiedUI/system/modules/entityShapeVisualizer.js rename to scripts/simplifiedUI/system/create/modules/entityShapeVisualizer.js index da28369cdd..dbf09a1cb7 100644 --- a/scripts/simplifiedUI/system/modules/entityShapeVisualizer.js +++ b/scripts/simplifiedUI/system/create/modules/entityShapeVisualizer.js @@ -146,8 +146,8 @@ EntityShape.prototype = { parentID: this.entity, priority: 1, materialMappingMode: PROJECTED_MATERIALS ? "projected" : "uv", - materialURL: Script.resolvePath("../assets/images/materials/GridPattern.json"), - ignorePickIntersection: true, + materialURL: Script.resolvePath("../../assets/images/materials/GridPattern.json"), + ignorePickIntersection: true }, "local"); }, update: function() { diff --git a/scripts/simplifiedUI/system/create/Edit.qml b/scripts/simplifiedUI/system/create/qml/Edit.qml similarity index 95% rename from scripts/simplifiedUI/system/create/Edit.qml rename to scripts/simplifiedUI/system/create/qml/Edit.qml index ca18388def..13e7874ca8 100644 --- a/scripts/simplifiedUI/system/create/Edit.qml +++ b/scripts/simplifiedUI/system/create/qml/Edit.qml @@ -34,7 +34,7 @@ StackView { } function pushSource(path) { - var item = Qt.createComponent(Qt.resolvedUrl("../../" + path)); + var item = Qt.createComponent(path); editRoot.push(item, itemProperties, StackView.Immediate); editRoot.currentItem.sendToScript.connect(editRoot.sendToScript); diff --git a/scripts/simplifiedUI/system/create/EditTabButton.qml b/scripts/simplifiedUI/system/create/qml/EditTabButton.qml similarity index 100% rename from scripts/simplifiedUI/system/create/EditTabButton.qml rename to scripts/simplifiedUI/system/create/qml/EditTabButton.qml diff --git a/scripts/simplifiedUI/system/create/EditTabView.qml b/scripts/simplifiedUI/system/create/qml/EditTabView.qml similarity index 93% rename from scripts/simplifiedUI/system/create/EditTabView.qml rename to scripts/simplifiedUI/system/create/qml/EditTabView.qml index 7e8789487c..a0cff70d50 100644 --- a/scripts/simplifiedUI/system/create/EditTabView.qml +++ b/scripts/simplifiedUI/system/create/qml/EditTabView.qml @@ -72,7 +72,7 @@ TabBar { NewEntityButton { - icon: "create-icons/94-model-01.svg" + icon: "icons/94-model-01.svg" text: "MODEL" onClicked: { editRoot.sendToScript({ @@ -84,7 +84,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/21-cube-01.svg" + icon: "icons/21-cube-01.svg" text: "SHAPE" onClicked: { editRoot.sendToScript({ @@ -96,7 +96,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/24-light-01.svg" + icon: "icons/24-light-01.svg" text: "LIGHT" onClicked: { editRoot.sendToScript({ @@ -108,7 +108,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/20-text-01.svg" + icon: "icons/20-text-01.svg" text: "TEXT" onClicked: { editRoot.sendToScript({ @@ -120,7 +120,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/image.svg" + icon: "icons/image.svg" text: "IMAGE" onClicked: { editRoot.sendToScript({ @@ -132,7 +132,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/25-web-1-01.svg" + icon: "icons/25-web-1-01.svg" text: "WEB" onClicked: { editRoot.sendToScript({ @@ -144,7 +144,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/23-zone-01.svg" + icon: "icons/23-zone-01.svg" text: "ZONE" onClicked: { editRoot.sendToScript({ @@ -156,7 +156,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/90-particles-01.svg" + icon: "icons/90-particles-01.svg" text: "PARTICLE" onClicked: { editRoot.sendToScript({ @@ -168,7 +168,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/126-material-01.svg" + icon: "icons/126-material-01.svg" text: "MATERIAL" onClicked: { editRoot.sendToScript({ @@ -231,7 +231,7 @@ TabBar { property Component visualItem: Component { WebView { id: entityListToolWebView - url: Paths.defaultScripts + "/system/html/entityList.html" + url: Qt.resolvedUrl("../entityList/html/entityList.html") enabled: true blurOnCtrlShift: false } @@ -247,7 +247,7 @@ TabBar { property Component visualItem: Component { WebView { id: entityPropertiesWebView - url: Paths.defaultScripts + "/system/html/entityProperties.html" + url: Qt.resolvedUrl("../entityProperties/html/entityProperties.html") enabled: true blurOnCtrlShift: false } @@ -263,7 +263,7 @@ TabBar { property Component visualItem: Component { WebView { id: gridControlsWebView - url: Paths.defaultScripts + "/system/html/gridControls.html" + url: Qt.resolvedUrl("../../html/gridControls.html") enabled: true blurOnCtrlShift: false } diff --git a/scripts/simplifiedUI/system/create/EditTools.qml b/scripts/simplifiedUI/system/create/qml/EditTools.qml similarity index 100% rename from scripts/simplifiedUI/system/create/EditTools.qml rename to scripts/simplifiedUI/system/create/qml/EditTools.qml diff --git a/scripts/simplifiedUI/system/create/EditToolsTabView.qml b/scripts/simplifiedUI/system/create/qml/EditToolsTabView.qml similarity index 93% rename from scripts/simplifiedUI/system/create/EditToolsTabView.qml rename to scripts/simplifiedUI/system/create/qml/EditToolsTabView.qml index a333acc586..0ce8d8e8d4 100644 --- a/scripts/simplifiedUI/system/create/EditToolsTabView.qml +++ b/scripts/simplifiedUI/system/create/qml/EditToolsTabView.qml @@ -78,7 +78,7 @@ TabBar { NewEntityButton { - icon: "create-icons/94-model-01.svg" + icon: "icons/94-model-01.svg" text: "MODEL" onClicked: { editRoot.sendToScript({ @@ -90,7 +90,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/21-cube-01.svg" + icon: "icons/21-cube-01.svg" text: "SHAPE" onClicked: { editRoot.sendToScript({ @@ -102,7 +102,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/24-light-01.svg" + icon: "icons/24-light-01.svg" text: "LIGHT" onClicked: { editRoot.sendToScript({ @@ -114,7 +114,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/20-text-01.svg" + icon: "icons/20-text-01.svg" text: "TEXT" onClicked: { editRoot.sendToScript({ @@ -126,7 +126,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/image.svg" + icon: "icons/image.svg" text: "IMAGE" onClicked: { editRoot.sendToScript({ @@ -138,7 +138,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/25-web-1-01.svg" + icon: "icons/25-web-1-01.svg" text: "WEB" onClicked: { editRoot.sendToScript({ @@ -150,7 +150,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/23-zone-01.svg" + icon: "icons/23-zone-01.svg" text: "ZONE" onClicked: { editRoot.sendToScript({ @@ -162,7 +162,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/90-particles-01.svg" + icon: "icons/90-particles-01.svg" text: "PARTICLE" onClicked: { editRoot.sendToScript({ @@ -174,7 +174,7 @@ TabBar { } NewEntityButton { - icon: "create-icons/126-material-01.svg" + icon: "icons/126-material-01.svg" text: "MATERIAL" onClicked: { editRoot.sendToScript({ @@ -237,7 +237,7 @@ TabBar { property Component visualItem: Component { WebView { id: entityPropertiesWebView - url: Paths.defaultScripts + "/system/html/entityProperties.html" + url: Qt.resolvedUrl("../entityProperties/html/entityProperties.html") enabled: true blurOnCtrlShift: false } @@ -253,7 +253,7 @@ TabBar { property Component visualItem: Component { WebView { id: gridControlsWebView - url: Paths.defaultScripts + "/system/html/gridControls.html" + url: Qt.resolvedUrl("../../html/gridControls.html") enabled: true blurOnCtrlShift: false } diff --git a/scripts/simplifiedUI/system/create/NewEntityButton.qml b/scripts/simplifiedUI/system/create/qml/NewEntityButton.qml similarity index 100% rename from scripts/simplifiedUI/system/create/NewEntityButton.qml rename to scripts/simplifiedUI/system/create/qml/NewEntityButton.qml diff --git a/scripts/simplifiedUI/system/create/NewMaterialDialog.qml b/scripts/simplifiedUI/system/create/qml/NewMaterialDialog.qml similarity index 99% rename from scripts/simplifiedUI/system/create/NewMaterialDialog.qml rename to scripts/simplifiedUI/system/create/qml/NewMaterialDialog.qml index 75570327e0..1631632fb4 100644 --- a/scripts/simplifiedUI/system/create/NewMaterialDialog.qml +++ b/scripts/simplifiedUI/system/create/qml/NewMaterialDialog.qml @@ -15,7 +15,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs import stylesUit 1.0 import controlsUit 1.0 -import dialogs 1.0 +import hifi.dialogs 1.0 Rectangle { id: newMaterialDialog diff --git a/scripts/simplifiedUI/system/create/NewMaterialWindow.qml b/scripts/simplifiedUI/system/create/qml/NewMaterialWindow.qml similarity index 100% rename from scripts/simplifiedUI/system/create/NewMaterialWindow.qml rename to scripts/simplifiedUI/system/create/qml/NewMaterialWindow.qml diff --git a/scripts/simplifiedUI/system/create/NewModelDialog.qml b/scripts/simplifiedUI/system/create/qml/NewModelDialog.qml similarity index 95% rename from scripts/simplifiedUI/system/create/NewModelDialog.qml rename to scripts/simplifiedUI/system/create/qml/NewModelDialog.qml index 1ded00d701..741902fa7f 100644 --- a/scripts/simplifiedUI/system/create/NewModelDialog.qml +++ b/scripts/simplifiedUI/system/create/qml/NewModelDialog.qml @@ -14,7 +14,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs import stylesUit 1.0 import controlsUit 1.0 -import dialogs 1.0 +import hifi.dialogs 1.0 Rectangle { id: newModelDialog @@ -135,19 +135,12 @@ Rectangle { height: 400 spacing: 20 - Image { - id: image1 - width: 30 - height: 30 - source: "qrc:/qtquickplugin/images/template_image.png" - } - Text { id: text2 width: 160 x: dynamic.width / 2 color: "#ffffff" - text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic, and should not be used as floors") + text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic.") wrapMode: Text.WordWrap font.pixelSize: 12 } diff --git a/scripts/simplifiedUI/system/create/NewModelWindow.qml b/scripts/simplifiedUI/system/create/qml/NewModelWindow.qml similarity index 100% rename from scripts/simplifiedUI/system/create/NewModelWindow.qml rename to scripts/simplifiedUI/system/create/qml/NewModelWindow.qml diff --git a/scripts/simplifiedUI/system/create/create-icons/126-material-01.svg b/scripts/simplifiedUI/system/create/qml/icons/126-material-01.svg similarity index 100% rename from scripts/simplifiedUI/system/create/create-icons/126-material-01.svg rename to scripts/simplifiedUI/system/create/qml/icons/126-material-01.svg diff --git a/scripts/simplifiedUI/system/create/create-icons/20-text-01.svg b/scripts/simplifiedUI/system/create/qml/icons/20-text-01.svg similarity index 100% rename from scripts/simplifiedUI/system/create/create-icons/20-text-01.svg rename to scripts/simplifiedUI/system/create/qml/icons/20-text-01.svg diff --git a/scripts/simplifiedUI/system/create/create-icons/21-cube-01.svg b/scripts/simplifiedUI/system/create/qml/icons/21-cube-01.svg similarity index 100% rename from scripts/simplifiedUI/system/create/create-icons/21-cube-01.svg rename to scripts/simplifiedUI/system/create/qml/icons/21-cube-01.svg diff --git a/scripts/simplifiedUI/system/create/create-icons/22-sphere-01.svg b/scripts/simplifiedUI/system/create/qml/icons/22-sphere-01.svg similarity index 100% rename from scripts/simplifiedUI/system/create/create-icons/22-sphere-01.svg rename to scripts/simplifiedUI/system/create/qml/icons/22-sphere-01.svg diff --git a/scripts/simplifiedUI/system/create/create-icons/23-zone-01.svg b/scripts/simplifiedUI/system/create/qml/icons/23-zone-01.svg similarity index 100% rename from scripts/simplifiedUI/system/create/create-icons/23-zone-01.svg rename to scripts/simplifiedUI/system/create/qml/icons/23-zone-01.svg diff --git a/scripts/simplifiedUI/system/create/create-icons/24-light-01.svg b/scripts/simplifiedUI/system/create/qml/icons/24-light-01.svg similarity index 100% rename from scripts/simplifiedUI/system/create/create-icons/24-light-01.svg rename to scripts/simplifiedUI/system/create/qml/icons/24-light-01.svg diff --git a/scripts/simplifiedUI/system/create/create-icons/25-web-1-01.svg b/scripts/simplifiedUI/system/create/qml/icons/25-web-1-01.svg similarity index 100% rename from scripts/simplifiedUI/system/create/create-icons/25-web-1-01.svg rename to scripts/simplifiedUI/system/create/qml/icons/25-web-1-01.svg diff --git a/scripts/simplifiedUI/system/create/qml/icons/90-particles-01.svg b/scripts/simplifiedUI/system/create/qml/icons/90-particles-01.svg new file mode 100644 index 0000000000..5e0105d7cd --- /dev/null +++ b/scripts/simplifiedUI/system/create/qml/icons/90-particles-01.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + diff --git a/scripts/simplifiedUI/system/create/create-icons/94-model-01.svg b/scripts/simplifiedUI/system/create/qml/icons/94-model-01.svg similarity index 100% rename from scripts/simplifiedUI/system/create/create-icons/94-model-01.svg rename to scripts/simplifiedUI/system/create/qml/icons/94-model-01.svg diff --git a/scripts/simplifiedUI/system/create/create-icons/image.svg b/scripts/simplifiedUI/system/create/qml/icons/image.svg similarity index 100% rename from scripts/simplifiedUI/system/create/create-icons/image.svg rename to scripts/simplifiedUI/system/create/qml/icons/image.svg diff --git a/scripts/simplifiedUI/system/keyboardShortcuts/keyboardShortcuts.js b/scripts/simplifiedUI/system/keyboardShortcuts/keyboardShortcuts.js new file mode 100644 index 0000000000..cf3927ac2d --- /dev/null +++ b/scripts/simplifiedUI/system/keyboardShortcuts/keyboardShortcuts.js @@ -0,0 +1,29 @@ +"use strict"; + +// +// keyboardShortcuts.js +// scripts/system/keyboardShortcuts +// +// Created by Preston Bezos on 06/28/2019 +// Copyright 2019 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 +// + +(function () { // BEGIN LOCAL_SCOPE + function keyPressEvent(event) { + if (event.text.toUpperCase() === "B" && event.isControl) { + Window.openWebBrowser(); + } else if (event.text.toUpperCase() === "N" && event.isControl) { + Users.toggleIgnoreRadius(); + } + } + + function scriptEnding() { + Controller.keyPressEvent.disconnect(keyPressEvent); + } + + Controller.keyPressEvent.connect(keyPressEvent); + Script.scriptEnding.connect(scriptEnding); +}()); // END LOCAL_SCOPE diff --git a/scripts/simplifiedUI/ui/simplifiedUI.js b/scripts/simplifiedUI/ui/simplifiedUI.js index 81f917e1c9..2402895f11 100644 --- a/scripts/simplifiedUI/ui/simplifiedUI.js +++ b/scripts/simplifiedUI/ui/simplifiedUI.js @@ -483,6 +483,25 @@ function maybeUpdateOutputDeviceMutedOverlay() { } +var oldAutomaticLODAdjust; +var oldLODLevel; +var DEFAULT_AUTO_LOD_ADJUST = false; +var DEFAULT_LOD_LEVEL = 0.5; +function modifyLODSettings() { + oldAutomaticLODAdjust = LODManager.automaticLODAdjust; + oldLODLevel = LODManager.lodQualityLevel; + + LODManager.automaticLODAdjust = DEFAULT_AUTO_LOD_ADJUST; + LODManager.lodQualityLevel = DEFAULT_LOD_LEVEL; +} + + +function restoreLODSettings() { + LODManager.automaticLODAdjust = oldAutomaticLODAdjust; + LODManager.lodQualityLevel = oldLODLevel; +} + + var simplifiedNametag = Script.require("./simplifiedNametag/simplifiedNametag.js?" + Date.now()); var SimplifiedStatusIndicator = Script.require("./simplifiedStatusIndicator/simplifiedStatusIndicator.js?" + Date.now()); var si; @@ -491,6 +510,7 @@ var oldShowBubbleTools; var keepExistingUIAndScriptsSetting = Settings.getValue("simplifiedUI/keepExistingUIAndScripts", false); function startup() { maybeRemoveDesktopMenu(); + modifyLODSettings(); if (!keepExistingUIAndScriptsSetting) { pauseCurrentScripts(); @@ -541,6 +561,7 @@ function restoreScripts() { function shutdown() { restoreScripts(); + restoreLODSettings(); if (!keepExistingUIAndScriptsSetting) { console.log("The Simplified UI script has been shut down. If you notice any strangeness with user interface, please restart this application."); diff --git a/scripts/system/create/entityList/qml/EntityList.qml b/scripts/system/create/entityList/qml/EntityList.qml index a70fc6d15d..8f92ffe6ce 100644 --- a/scripts/system/create/entityList/qml/EntityList.qml +++ b/scripts/system/create/entityList/qml/EntityList.qml @@ -1,6 +1,6 @@ WebView { id: entityListToolWebView - url: QT.resolvedURL("../html/entityList.html") + url: Qt.resolvedUrl("../html/entityList.html") enabled: true blurOnCtrlShift: false } diff --git a/scripts/system/create/qml/Edit.qml b/scripts/system/create/qml/Edit.qml index ca18388def..13e7874ca8 100644 --- a/scripts/system/create/qml/Edit.qml +++ b/scripts/system/create/qml/Edit.qml @@ -34,7 +34,7 @@ StackView { } function pushSource(path) { - var item = Qt.createComponent(Qt.resolvedUrl("../../" + path)); + var item = Qt.createComponent(path); editRoot.push(item, itemProperties, StackView.Immediate); editRoot.currentItem.sendToScript.connect(editRoot.sendToScript); diff --git a/scripts/system/create/qml/EditTabView.qml b/scripts/system/create/qml/EditTabView.qml index f90a962f7a..a0cff70d50 100644 --- a/scripts/system/create/qml/EditTabView.qml +++ b/scripts/system/create/qml/EditTabView.qml @@ -247,7 +247,7 @@ TabBar { property Component visualItem: Component { WebView { id: entityPropertiesWebView - url: Qt.resolvedURL("../entityProperties/html/entityProperties.html") + url: Qt.resolvedUrl("../entityProperties/html/entityProperties.html") enabled: true blurOnCtrlShift: false } @@ -263,7 +263,7 @@ TabBar { property Component visualItem: Component { WebView { id: gridControlsWebView - url: Qt.resolvedURL("../../html/gridControls.html") + url: Qt.resolvedUrl("../../html/gridControls.html") enabled: true blurOnCtrlShift: false } diff --git a/scripts/system/create/qml/NewModelDialog.qml b/scripts/system/create/qml/NewModelDialog.qml index 92a08df10d..741902fa7f 100644 --- a/scripts/system/create/qml/NewModelDialog.qml +++ b/scripts/system/create/qml/NewModelDialog.qml @@ -135,19 +135,12 @@ Rectangle { height: 400 spacing: 20 - Image { - id: image1 - width: 30 - height: 30 - source: "qrc:/qtquickplugin/images/template_image.png" - } - Text { id: text2 width: 160 x: dynamic.width / 2 color: "#ffffff" - text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic, and should not be used as floors") + text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic.") wrapMode: Text.WordWrap font.pixelSize: 12 } diff --git a/tools/shadergen.py b/tools/shadergen.py index f82b471f17..1f4acae915 100644 --- a/tools/shadergen.py +++ b/tools/shadergen.py @@ -66,7 +66,6 @@ def getExtensionsHeader(dialect, variant, extensions): extensionsHeaderMutex.release() return extensionHeader - def getDialectAndVariantHeaders(dialect, variant, extensions=None): result = [] headerPath = args.source_dir + '/libraries/shaders/headers/' @@ -80,6 +79,14 @@ def getDialectAndVariantHeaders(dialect, variant, extensions=None): result.append(variantHeader) return result +def getDefines(defines): + definesList = [] + if defines: + definesSplit = defines.split("_") + for define in definesSplit: + definesList.append('HIFI_USE_{} 1'.format(define.upper())) + return definesList + class ScribeDependenciesCache: cache = {} lock = Lock() @@ -99,9 +106,9 @@ class ScribeDependenciesCache: with open(self.filename, "w") as f: f.write(json.dumps(self.cache)) - def get(self, scribefile, dialect, variant): + def get(self, scribefile, dialect, variant, defines): self.lock.acquire() - key = self.key(scribefile, dialect, variant) + key = self.key(scribefile, dialect, variant, defines) try: if key in self.cache: return self.cache[key].copy() @@ -109,25 +116,26 @@ class ScribeDependenciesCache: self.lock.release() return None - def key(self, scribeFile, dialect, variant): - return ':'.join([scribeFile, dialect, variant]) + def key(self, scribeFile, dialect, variant, defines): + return ':'.join([scribeFile, dialect, variant, defines]) - def getOrGen(self, scribefile, includeLibs, dialect, variant): - result = self.get(scribefile, dialect, variant) - if (None == result): - result = self.gen(scribefile, includeLibs, dialect, variant) + def getOrGen(self, scribefile, includeLibs, dialect, variant, defines): + result = self.get(scribefile, dialect, variant, defines) + if result is None: + result = self.gen(scribefile, includeLibs, dialect, variant, defines) return result - def gen(self, scribefile, includeLibs, dialect, variant): + def gen(self, scribefile, includeLibs, dialect, variant, defines): scribeArgs = getCommonScribeArgs(scribefile, includeLibs) scribeArgs.extend(['-M']) processResult = subprocess.run(scribeArgs, stdout=subprocess.PIPE) if (0 != processResult.returncode): - raise RuntimeError("Unable to parse scribe dependencies") + raise RuntimeError("Unable to parse scribe dependencies for file {} with defines: {}".format(scribefile, defines)) result = processResult.stdout.decode("utf-8").splitlines(False) result.append(scribefile) result.extend(getDialectAndVariantHeaders(dialect, variant)) - key = self.key(scribefile, dialect, variant) + result.extend(getDefines(defines)) + key = self.key(scribefile, dialect, variant, defines) self.lock.acquire() self.cache[key] = result.copy() self.lock.release() @@ -166,6 +174,10 @@ def processCommand(line): variant = params.pop(0) scribeFile = args.source_dir + '/' + params.pop(0) unoptGlslFile = args.source_dir + '/' + params.pop(0) + defines = "" + if len(params) > 1 and params[0].startswith("defines:"): + defines = params.pop(0) + defines = defines[len("defines:"):] libs = params upoptSpirvFile = unoptGlslFile + '.spv' @@ -184,19 +196,23 @@ def processCommand(line): os.makedirs(scribeOutputDir) folderMutex.release() - scribeDeps = scribeDepCache.getOrGen(scribeFile, libs, dialect, variant) + scribeDeps = scribeDepCache.getOrGen(scribeFile, libs, dialect, variant, defines) # if the scribe sources (slv, slf, slh, etc), or the dialect/ variant headers are out of date # regenerate the scribe GLSL output if args.force or outOfDate(scribeDeps, outputFiles): - print('Processing file {} dialect {} variant {}'.format(scribeFile, dialect, variant)) + print('Processing file {} dialect {} variant {} defines {}'.format(scribeFile, dialect, variant, defines)) if args.dry_run: return True - scribeDepCache.gen(scribeFile, libs, dialect, variant) + scribeDepCache.gen(scribeFile, libs, dialect, variant, defines) scribeArgs = getCommonScribeArgs(scribeFile, libs) for header in getDialectAndVariantHeaders(dialect, variant, args.extensions): scribeArgs.extend(['-H', header]) + for define in getDefines(defines): + defineArgs = ['-D'] + defineArgs.extend(define.split(' ')) + scribeArgs.extend(defineArgs) scribeArgs.extend(['-o', unoptGlslFile]) executeSubprocess(scribeArgs)