diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index d63e8e6620..81e25cc7ba 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -1172,15 +1172,15 @@ void OctreeServer::domainSettingsRequestComplete() { pathToCopyFrom = oldDefaultPersistPath; } + QDir persistFileDirectory = QDir(persistPath).filePath(".."); + if (!persistFileDirectory.exists()) { + qDebug() << "Creating data directory " << persistFileDirectory.absolutePath(); + persistFileDirectory.mkpath("."); + } + if (shouldCopy) { qDebug() << "Old persist file found, copying from " << pathToCopyFrom << " to " << persistPath; - QDir persistFileDirectory = QDir(persistPath).filePath(".."); - - if (!persistFileDirectory.exists()) { - qDebug() << "Creating data directory " << persistFileDirectory.path(); - persistFileDirectory.mkpath("."); - } QFile::copy(pathToCopyFrom, persistPath); } else { qDebug() << "No existing persist file found"; diff --git a/cmake/macros/GenerateInstallers.cmake b/cmake/macros/GenerateInstallers.cmake index e0523f32d3..b4744aa172 100644 --- a/cmake/macros/GenerateInstallers.cmake +++ b/cmake/macros/GenerateInstallers.cmake @@ -28,6 +28,15 @@ macro(GENERATE_INSTALLERS) # include CMake module that will install compiler system libraries # so that we have msvcr120 and msvcp120 installed with targets set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION ${INTERFACE_INSTALL_DIR}) + + # as long as we're including sixense plugin with installer + # we need re-distributables for VS 2011 as well + # this should be removed if/when sixense support is pulled + set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS + "${EXTERNALS_BINARY_DIR}/sixense/project/src/sixense/samples/win64/msvcr100.dll" + "${EXTERNALS_BINARY_DIR}/sixense/project/src/sixense/samples/win64/msvcp100.dll" + ) + include(InstallRequiredSystemLibraries) set(CPACK_NSIS_MUI_ICON "${HF_CMAKE_DIR}/installer/installer.ico") diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index b0f093977a..92e3273f67 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -82,10 +82,14 @@ macro(SET_PACKAGING_PARAMETERS) set(DS_EXEC_NAME "domain-server.exe") set(AC_EXEC_NAME "assignment-client.exe") - # start menu shortcuts - set(INTERFACE_SM_SHORTCUT_NAME "High Fidelity") - set(CONSOLE_SM_SHORTCUT_NAME "Server Console") - + # shortcut names + if (PRODUCTION_BUILD) + set(INTERFACE_SHORTCUT_NAME "High Fidelity") + set(CONSOLE_SHORTCUT_NAME "Server Console") + else () + set(INTERFACE_SHORTCUT_NAME "High Fidelity - ${BUILD_VERSION}") + set(CONSOLE_SHORTCUT_NAME "Server Console - ${BUILD_VERSION}") + endif () # check if we need to find signtool if (PRODUCTION_BUILD OR PR_BUILD) find_program(SIGNTOOL_EXECUTABLE signtool PATHS "C:/Program Files (x86)/Windows Kits/8.1" PATH_SUFFIXES "bin/x64") diff --git a/cmake/templates/CPackProperties.cmake.in b/cmake/templates/CPackProperties.cmake.in index 285de84d39..164e432706 100644 --- a/cmake/templates/CPackProperties.cmake.in +++ b/cmake/templates/CPackProperties.cmake.in @@ -9,10 +9,10 @@ # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html # -set(INTERFACE_SHORTCUT_NAME "@INTERFACE_SM_SHORTCUT_NAME@") +set(INTERFACE_SHORTCUT_NAME "@INTERFACE_SHORTCUT_NAME@") set(INTERFACE_WIN_EXEC_NAME "@INTERFACE_EXEC_PREFIX@.exe") set(CONSOLE_INSTALL_SUBDIR "@CONSOLE_INSTALL_DIR@") -set(CONSOLE_SHORTCUT_NAME "@CONSOLE_SM_SHORTCUT_NAME@") +set(CONSOLE_SHORTCUT_NAME "@CONSOLE_SHORTCUT_NAME@") set(CONSOLE_WIN_EXEC_NAME "@CONSOLE_EXEC_NAME@") set(DS_EXEC_NAME "@DS_EXEC_NAME@") set(AC_EXEC_NAME "@AC_EXEC_NAME@") diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index 0b37cd5853..b5699cb3b3 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -12,13 +12,7 @@ Var MUI_TEMP Var STARTMENU_FOLDER - Var SV_ALLUSERS Var START_MENU - Var DO_NOT_ADD_TO_PATH - Var ADD_TO_PATH_ALL_USERS - Var ADD_TO_PATH_CURRENT_USER - Var INSTALL_DESKTOP - Var IS_DEFAULT_INSTALLDIR ;-------------------------------- ;Include Modern UI @@ -205,346 +199,15 @@ Var AR_RegFlags !define MUI_HEADERIMAGE_UNBITMAP "@UNINSTALLER_HEADER_IMAGE@" !define MUI_ABORTWARNING -;-------------------------------- -; path functions - -!verbose 3 -!include "WinMessages.NSH" -!verbose 4 - -;---------------------------------------- -; based upon a script of "Written by KiCHiK 2003-01-18 05:57:02" -;---------------------------------------- -!verbose 3 -!include "WinMessages.NSH" -!verbose 4 -;==================================================== -; get_NT_environment -; Returns: the selected environment -; Output : head of the stack -;==================================================== -!macro select_NT_profile UN -Function ${UN}select_NT_profile - StrCmp $ADD_TO_PATH_ALL_USERS "1" 0 environment_single - DetailPrint "Selected environment for all users" - Push "all" - Return - environment_single: - DetailPrint "Selected environment for current user only." - Push "current" - Return -FunctionEnd -!macroend -!insertmacro select_NT_profile "" -!insertmacro select_NT_profile "un." -;---------------------------------------------------- -!define NT_current_env 'HKCU "Environment"' -!define NT_all_env 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' - -!ifndef WriteEnvStr_RegKey - !ifdef ALL_USERS - !define WriteEnvStr_RegKey \ - 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' - !else - !define WriteEnvStr_RegKey 'HKCU "Environment"' - !endif -!endif - -; AddToPath - Adds the given dir to the search path. -; Input - head of the stack -; Note - Win9x systems requires reboot - -Function AddToPath - Exch $0 - Push $1 - Push $2 - Push $3 - - # don't add if the path doesn't exist - IfFileExists "$0\*.*" "" AddToPath_done - - ReadEnvStr $1 PATH - ; if the path is too long for a NSIS variable NSIS will return a 0 - ; length string. If we find that, then warn and skip any path - ; modification as it will trash the existing path. - StrLen $2 $1 - IntCmp $2 0 CheckPathLength_ShowPathWarning CheckPathLength_Done CheckPathLength_Done - CheckPathLength_ShowPathWarning: - Messagebox MB_OK|MB_ICONEXCLAMATION "Warning! PATH too long installer unable to modify PATH!" - Goto AddToPath_done - CheckPathLength_Done: - Push "$1;" - Push "$0;" - Call StrStr - Pop $2 - StrCmp $2 "" "" AddToPath_done - Push "$1;" - Push "$0\;" - Call StrStr - Pop $2 - StrCmp $2 "" "" AddToPath_done - GetFullPathName /SHORT $3 $0 - Push "$1;" - Push "$3;" - Call StrStr - Pop $2 - StrCmp $2 "" "" AddToPath_done - Push "$1;" - Push "$3\;" - Call StrStr - Pop $2 - StrCmp $2 "" "" AddToPath_done - - Call IsNT - Pop $1 - StrCmp $1 1 AddToPath_NT - ; Not on NT - StrCpy $1 $WINDIR 2 - FileOpen $1 "$1\autoexec.bat" a - FileSeek $1 -1 END - FileReadByte $1 $2 - IntCmp $2 26 0 +2 +2 # DOS EOF - FileSeek $1 -1 END # write over EOF - FileWrite $1 "$\r$\nSET PATH=%PATH%;$3$\r$\n" - FileClose $1 - SetRebootFlag true - Goto AddToPath_done - - AddToPath_NT: - StrCmp $ADD_TO_PATH_ALL_USERS "1" ReadAllKey - ReadRegStr $1 ${NT_current_env} "PATH" - Goto DoTrim - ReadAllKey: - ReadRegStr $1 ${NT_all_env} "PATH" - DoTrim: - StrCmp $1 "" AddToPath_NTdoIt - Push $1 - Call Trim - Pop $1 - StrCpy $0 "$1;$0" - AddToPath_NTdoIt: - StrCmp $ADD_TO_PATH_ALL_USERS "1" WriteAllKey - WriteRegExpandStr ${NT_current_env} "PATH" $0 - Goto DoSend - WriteAllKey: - WriteRegExpandStr ${NT_all_env} "PATH" $0 - DoSend: - SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 - - AddToPath_done: - Pop $3 - Pop $2 - Pop $1 - Pop $0 -FunctionEnd - - -; RemoveFromPath - Remove a given dir from the path -; Input: head of the stack - -Function un.RemoveFromPath - Exch $0 - Push $1 - Push $2 - Push $3 - Push $4 - Push $5 - Push $6 - - IntFmt $6 "%c" 26 # DOS EOF - - Call un.IsNT - Pop $1 - StrCmp $1 1 unRemoveFromPath_NT - ; Not on NT - StrCpy $1 $WINDIR 2 - FileOpen $1 "$1\autoexec.bat" r - GetTempFileName $4 - FileOpen $2 $4 w - GetFullPathName /SHORT $0 $0 - StrCpy $0 "SET PATH=%PATH%;$0" - Goto unRemoveFromPath_dosLoop - - unRemoveFromPath_dosLoop: - FileRead $1 $3 - StrCpy $5 $3 1 -1 # read last char - StrCmp $5 $6 0 +2 # if DOS EOF - StrCpy $3 $3 -1 # remove DOS EOF so we can compare - StrCmp $3 "$0$\r$\n" unRemoveFromPath_dosLoopRemoveLine - StrCmp $3 "$0$\n" unRemoveFromPath_dosLoopRemoveLine - StrCmp $3 "$0" unRemoveFromPath_dosLoopRemoveLine - StrCmp $3 "" unRemoveFromPath_dosLoopEnd - FileWrite $2 $3 - Goto unRemoveFromPath_dosLoop - unRemoveFromPath_dosLoopRemoveLine: - SetRebootFlag true - Goto unRemoveFromPath_dosLoop - - unRemoveFromPath_dosLoopEnd: - FileClose $2 - FileClose $1 - StrCpy $1 $WINDIR 2 - Delete "$1\autoexec.bat" - CopyFiles /SILENT $4 "$1\autoexec.bat" - Delete $4 - Goto unRemoveFromPath_done - - unRemoveFromPath_NT: - StrCmp $ADD_TO_PATH_ALL_USERS "1" unReadAllKey - ReadRegStr $1 ${NT_current_env} "PATH" - Goto unDoTrim - unReadAllKey: - ReadRegStr $1 ${NT_all_env} "PATH" - unDoTrim: - StrCpy $5 $1 1 -1 # copy last char - StrCmp $5 ";" +2 # if last char != ; - StrCpy $1 "$1;" # append ; - Push $1 - Push "$0;" - Call un.StrStr ; Find `$0;` in $1 - Pop $2 ; pos of our dir - StrCmp $2 "" unRemoveFromPath_done - ; else, it is in path - # $0 - path to add - # $1 - path var - StrLen $3 "$0;" - StrLen $4 $2 - StrCpy $5 $1 -$4 # $5 is now the part before the path to remove - StrCpy $6 $2 "" $3 # $6 is now the part after the path to remove - StrCpy $3 $5$6 - - StrCpy $5 $3 1 -1 # copy last char - StrCmp $5 ";" 0 +2 # if last char == ; - StrCpy $3 $3 -1 # remove last char - - StrCmp $ADD_TO_PATH_ALL_USERS "1" unWriteAllKey - WriteRegExpandStr ${NT_current_env} "PATH" $3 - Goto unDoSend - unWriteAllKey: - WriteRegExpandStr ${NT_all_env} "PATH" $3 - unDoSend: - SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 - - unRemoveFromPath_done: - Pop $6 - Pop $5 - Pop $4 - Pop $3 - Pop $2 - Pop $1 - Pop $0 -FunctionEnd - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -; Uninstall sutff -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ########################################### # Utility Functions # ########################################### -;==================================================== -; IsNT - Returns 1 if the current system is NT, 0 -; otherwise. -; Output: head of the stack -;==================================================== -; IsNT -; no input -; output, top of the stack = 1 if NT or 0 if not -; -; Usage: -; Call IsNT -; Pop $R0 -; ($R0 at this point is 1 or 0) - -!macro IsNT un -Function ${un}IsNT - Push $0 - ReadRegStr $0 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" CurrentVersion - StrCmp $0 "" 0 IsNT_yes - ; we are not NT. - Pop $0 - Push 0 - Return - - IsNT_yes: - ; NT!!! - Pop $0 - Push 1 -FunctionEnd -!macroend -!insertmacro IsNT "" -!insertmacro IsNT "un." - -; StrStr -; input, top of stack = string to search for -; top of stack-1 = string to search in -; output, top of stack (replaces with the portion of the string remaining) -; modifies no other variables. -; -; Usage: -; Push "this is a long ass string" -; Push "ass" -; Call StrStr -; Pop $R0 -; ($R0 at this point is "ass string") - -!macro StrStr un -Function ${un}StrStr -Exch $R1 ; st=haystack,old$R1, $R1=needle - Exch ; st=old$R1,haystack - Exch $R2 ; st=old$R1,old$R2, $R2=haystack - Push $R3 - Push $R4 - Push $R5 - StrLen $R3 $R1 - StrCpy $R4 0 - ; $R1=needle - ; $R2=haystack - ; $R3=len(needle) - ; $R4=cnt - ; $R5=tmp - loop: - StrCpy $R5 $R2 $R3 $R4 - StrCmp $R5 $R1 done - StrCmp $R5 "" done - IntOp $R4 $R4 + 1 - Goto loop -done: - StrCpy $R1 $R2 "" $R4 - Pop $R5 - Pop $R4 - Pop $R3 - Pop $R2 - Exch $R1 -FunctionEnd -!macroend -!insertmacro StrStr "" -!insertmacro StrStr "un." - -Function Trim ; Added by Pelaca - Exch $R1 - Push $R2 -Loop: - StrCpy $R2 "$R1" 1 -1 - StrCmp "$R2" " " RTrim - StrCmp "$R2" "$\n" RTrim - StrCmp "$R2" "$\r" RTrim - StrCmp "$R2" ";" RTrim - GoTo Done -RTrim: - StrCpy $R1 "$R1" -1 - Goto Loop -Done: - Pop $R2 - Exch $R1 -FunctionEnd - Function ConditionalAddToRegisty Pop $0 Pop $1 StrCmp "$0" "" ConditionalAddToRegisty_EmptyString - WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" \ + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" \ "$1" "$0" ;MessageBox MB_OK "Set Registry: '$1' to '$0'" DetailPrint "Set install registry entry: '$1' to '$0'" @@ -598,11 +261,10 @@ FunctionEnd !insertmacro MUI_PAGE_WELCOME !insertmacro MUI_PAGE_LICENSE "@CPACK_RESOURCE_FILE_LICENSE@" - Page custom InstallOptionsPage !insertmacro MUI_PAGE_DIRECTORY ;Start Menu Folder Page Configuration - !define MUI_STARTMENUPAGE_REGISTRY_ROOT "SHCTX" + !define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKLM" !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder" !insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER @@ -678,7 +340,6 @@ FunctionEnd ;Keep these lines before any File command ;Only for solid compression (by default, solid compression is enabled for BZIP2 and LZMA) - ReserveFile "NSIS.InstallOptions.ini" ReserveFile "@POST_INSTALL_OPTIONS_PATH@" ;-------------------------------- @@ -717,7 +378,7 @@ Section "-Core installation" @CPACK_NSIS_FULL_INSTALL@ ;Store installation folder - WriteRegStr SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "" $INSTDIR + WriteRegStr HKLM "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "" $INSTDIR ;Package the signed uninstaller produced by the inner loop !ifndef INNER @@ -768,10 +429,10 @@ Section "-Core installation" Push "Contact" Push "@CPACK_NSIS_CONTACT@" Call ConditionalAddToRegisty - !insertmacro INSTALLOPTIONS_READ $INSTALL_DESKTOP "NSIS.InstallOptions.ini" "Field 5" "State" !insertmacro MUI_STARTMENU_WRITE_BEGIN Application ;Create shortcuts + CreateDirectory "$SMPROGRAMS\$STARTMENU_FOLDER" @CPACK_NSIS_CREATE_ICONS@ @CPACK_NSIS_CREATE_ICONS_EXTRA@ @@ -799,27 +460,10 @@ Section "-Core installation" CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Uninstall.lnk" "$INSTDIR\@UNINSTALLER_NAME@" - ;Read a value from an InstallOptions INI file - !insertmacro INSTALLOPTIONS_READ $DO_NOT_ADD_TO_PATH "NSIS.InstallOptions.ini" "Field 2" "State" - !insertmacro INSTALLOPTIONS_READ $ADD_TO_PATH_ALL_USERS "NSIS.InstallOptions.ini" "Field 3" "State" - !insertmacro INSTALLOPTIONS_READ $ADD_TO_PATH_CURRENT_USER "NSIS.InstallOptions.ini" "Field 4" "State" - ; Write special uninstall registry entries Push "StartMenu" Push "$STARTMENU_FOLDER" Call ConditionalAddToRegisty - Push "DoNotAddToPath" - Push "$DO_NOT_ADD_TO_PATH" - Call ConditionalAddToRegisty - Push "AddToPathAllUsers" - Push "$ADD_TO_PATH_ALL_USERS" - Call ConditionalAddToRegisty - Push "AddToPathCurrentUser" - Push "$ADD_TO_PATH_CURRENT_USER" - Call ConditionalAddToRegisty - Push "InstallToDesktop" - Push "$INSTALL_DESKTOP" - Call ConditionalAddToRegisty !insertmacro MUI_STARTMENU_WRITE_END @@ -827,22 +471,6 @@ Section "-Core installation" SectionEnd -Section "-Add to path" - Push $INSTDIR\bin - StrCmp "@CPACK_NSIS_MODIFY_PATH@" "ON" 0 doNotAddToPath - StrCmp $DO_NOT_ADD_TO_PATH "1" doNotAddToPath 0 - Call AddToPath - doNotAddToPath: -SectionEnd - -;-------------------------------- -; Create custom pages -Function InstallOptionsPage - !insertmacro MUI_HEADER_TEXT "Install Options" "Choose options for installing @CPACK_NSIS_PACKAGE_NAME@" - !insertmacro INSTALLOPTIONS_DISPLAY "NSIS.InstallOptions.ini" - -FunctionEnd - ; Make sure nsDialogs is included before we use it !include "nsdialogs.nsh" @@ -908,7 +536,6 @@ Function PostInstallOptionsPage ; set the checkbox state depending on what is present in the registry !insertmacro SetPostInstallOption $DesktopClientCheckbox @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ ${BST_CHECKED} - ${EndIf} ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} @@ -941,6 +568,16 @@ Function PostInstallOptionsPage !insertmacro SetPostInstallOption $LaunchNowCheckbox @LAUNCH_NOW_REG_KEY@ ${BST_CHECKED} ${If} @PR_BUILD@ == 1 + ; a PR build defaults all install options expect LaunchNowCheckbox and the settings copy to unchecked + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ${NSD_SetState} $DesktopClientCheckbox ${BST_UNCHECKED} + ${EndIf} + + ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + ${NSD_SetState} $DesktopServerCheckbox ${BST_UNCHECKED} + ${NSD_SetState} $ServerStartupCheckbox ${BST_UNCHECKED} + ${EndIf} + ; push the offset IntOp $CurrentOffset $CurrentOffset + 15 @@ -993,8 +630,17 @@ Function HandlePostInstallOptions ${NSD_GetState} $ServerStartupCheckbox $ServerStartupState ${If} $ServerStartupState == ${BST_CHECKED} + ; in case we added a shortcut in the global context, pull that now + SetShellVarContext all + Delete "$SMSTARTUP\@CONSOLE_SHORTCUT_NAME@.lnk" + + ; make a startup shortcut in this user's current context + SetShellVarContext current CreateShortCut "$SMSTARTUP\@CONSOLE_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" + ; reset the shell var context back + SetShellVarContext all + !insertmacro WritePostInstallOption @CONSOLE_STARTUP_REG_KEY@ YES ${Else} !insertmacro WritePostInstallOption @CONSOLE_STARTUP_REG_KEY@ NO @@ -1002,6 +648,7 @@ Function HandlePostInstallOptions ${EndIf} ${If} @PR_BUILD@ == 1 + ; check if we need to copy settings/content from production for this PR build ${NSD_GetState} $CopyFromProductionCheckbox $CopyFromProductionState @@ -1025,6 +672,8 @@ Function HandlePostInstallOptions "There was a problem copying your production content and settings to $0 for this PR build.$\r$\n$\r$\nPlease copy them manually." NoError: + + SetShellVarContext all ${EndIf} ${EndIf} @@ -1050,19 +699,30 @@ FunctionEnd !include nsProcess.nsh !macro PromptForRunningApplication applicationName displayName action prompter - ${nsProcess::FindProcess} ${applicationName} $R0 + !define UniqueID ${__LINE__} - ${If} $R0 == 0 - ; the process is running, ask the user if they want us to close it - MessageBox MB_OK|MB_ICONEXCLAMATION \ - "${displayName} cannot be ${action} while ${displayName} is running.$\r$\nPlease close it and try again." - Abort - ${EndIf} + Prompt_${UniqueID}: + + ${nsProcess::FindProcess} ${applicationName} $R0 + + ${If} $R0 == 0 + + ; the process is running, ask the user to close it + MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION \ + "${displayName} cannot be ${action} while ${displayName} is running.$\r$\nPlease close it and click Retry to continue." \ + /SD IDCANCEL IDRETRY Prompt_${UniqueID} IDCANCEL 0 + + ; If the user decided to cancel, stop the current installer/uninstaller + Abort + + ${EndIf} + + !undef UniqueID !macroend !macro CheckForRunningApplications action prompter - !insertmacro PromptForRunningApplication "@INTERFACE_WIN_EXEC_NAME@" "@INTERFACE_SHORTCUT_NAME@" ${action} ${prompter} - !insertmacro PromptForRunningApplication "@CONSOLE_WIN_EXEC_NAME@" "@CONSOLE_SHORTCUT_NAME@" ${action} ${prompter} + !insertmacro PromptForRunningApplication "@INTERFACE_WIN_EXEC_NAME@" "High Fidelity client" ${action} ${prompter} + !insertmacro PromptForRunningApplication "@CONSOLE_WIN_EXEC_NAME@" "Server Console" ${action} ${prompter} !insertmacro PromptForRunningApplication "@DS_EXEC_NAME@" "Domain Server" ${action} ${prompter} !insertmacro PromptForRunningApplication "@AC_EXEC_NAME@" "Assignment Client" ${action} ${prompter} !macroend @@ -1109,26 +769,6 @@ Function un.onInit Quit ${EndSwitch} - ClearErrors - UserInfo::GetName - IfErrors noLM - Pop $0 - UserInfo::GetAccountType - Pop $1 - StrCmp $1 "Admin" 0 +3 - SetShellVarContext all - ;MessageBox MB_OK 'User "$0" is in the Admin group' - Goto done - StrCmp $1 "Power" 0 +3 - SetShellVarContext all - ;MessageBox MB_OK 'User "$0" is in the Power Users group' - Goto done - - noLM: - ;Get installation folder from registry if available - - done: - FunctionEnd ;--- Add/Remove callback functions: --- @@ -1186,19 +826,12 @@ FunctionEnd !ifdef INNER Section "Uninstall" - ReadRegStr $START_MENU SHCTX \ + ; use all users context for data/startup folders + SetShellVarContext all + + ReadRegStr $START_MENU HKLM \ "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "StartMenu" ;MessageBox MB_OK "Start menu is in: $START_MENU" - ReadRegStr $DO_NOT_ADD_TO_PATH SHCTX \ - "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "DoNotAddToPath" - ReadRegStr $ADD_TO_PATH_ALL_USERS SHCTX \ - "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "AddToPathAllUsers" - ReadRegStr $ADD_TO_PATH_CURRENT_USER SHCTX \ - "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "AddToPathCurrentUser" - ;MessageBox MB_OK "Add to path: $DO_NOT_ADD_TO_PATH all users: $ADD_TO_PATH_ALL_USERS" - ReadRegStr $INSTALL_DESKTOP SHCTX \ - "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "InstallToDesktop" - ;MessageBox MB_OK "Install to desktop: $INSTALL_DESKTOP " @CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS@ @@ -1217,13 +850,13 @@ Section "Uninstall" ;Remove the uninstaller itself. Delete "$INSTDIR\@UNINSTALLER_NAME@" - DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" ;Remove the installation directory if it is empty. RMDir "$INSTDIR" ; Remove the registry entries. - DeleteRegKey SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + DeleteRegKey HKLM "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" ; Removes all optional components !insertmacro SectionList "RemoveSection_CPack" @@ -1235,7 +868,12 @@ Section "Uninstall" Delete "$SMPROGRAMS\$MUI_TEMP\@CONSOLE_SHORTCUT_NAME@.lnk" Delete "$DESKTOP\@INTERFACE_SHORTCUT_NAME@.lnk" Delete "$DESKTOP\@CONSOLE_SHORTCUT_NAME@.lnk" + + ; if it exists, delete the startup shortcut for the current user + SetShellVarContext current Delete "$SMSTARTUP\@CONSOLE_SHORTCUT_NAME@.lnk" + SetShellVarContext all + @CPACK_NSIS_DELETE_ICONS@ @CPACK_NSIS_DELETE_ICONS_EXTRA@ @@ -1264,6 +902,8 @@ Section "Uninstall" ; try to fix it. StrCpy $MUI_TEMP "$START_MENU" Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk" + Delete "$SMPROGRAMS\$MUI_TEMP\@INTERFACE_SHORTCUT_NAME@.lnk" + Delete "$SMPROGRAMS\$MUI_TEMP\@CONSOLE_SHORTCUT_NAME@.lnk" @CPACK_NSIS_DELETE_ICONS_EXTRA@ ;Delete empty start menu parent diretories @@ -1279,24 +919,12 @@ Section "Uninstall" StrCmp "$MUI_TEMP" "$SMPROGRAMS" secondStartMenuDeleteLoopDone secondStartMenuDeleteLoop secondStartMenuDeleteLoopDone: - DeleteRegKey /ifempty SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + DeleteRegKey /ifempty HKLM "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" - Push $INSTDIR\bin - StrCmp $DO_NOT_ADD_TO_PATH_ "1" doNotRemoveFromPath 0 - Call un.RemoveFromPath - doNotRemoveFromPath: SectionEnd !endif -;-------------------------------- -; determine admin versus local install -; Is install for "AllUsers" or "JustMe"? -; Default to "JustMe" - set to "AllUsers" if admin or on Win9x -; This function is used for the very first "custom page" of the installer. -; This custom page does not show up visibly, but it executes prior to the -; first visible page and sets up $INSTDIR properly... -; Choose different default installation folder based on SV_ALLUSERS... -; "Program Files" for AllUsers, "My Documents" for JustMe... +InstallDirRegKey HKLM "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "" Function .onInit @@ -1342,48 +970,6 @@ inst: ; Reads components status for registry !insertmacro SectionList "InitSection" - ; check to see if /D has been used to change - ; the install directory by comparing it to the - ; install directory that is expected to be the - ; default - StrCpy $IS_DEFAULT_INSTALLDIR 0 - StrCmp "$INSTDIR" "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@" 0 +2 - StrCpy $IS_DEFAULT_INSTALLDIR 1 - - StrCpy $SV_ALLUSERS "JustMe" - ; if default install dir then change the default - ; if it is installed for JustMe - StrCmp "$IS_DEFAULT_INSTALLDIR" "1" 0 +2 - StrCpy $INSTDIR "$DOCUMENTS\@CPACK_PACKAGE_INSTALL_DIRECTORY@" - - ClearErrors - UserInfo::GetName - IfErrors noLM - Pop $0 - UserInfo::GetAccountType - Pop $1 - StrCmp $1 "Admin" 0 +4 - SetShellVarContext all - ;MessageBox MB_OK 'User "$0" is in the Admin group' - StrCpy $SV_ALLUSERS "AllUsers" - Goto done - StrCmp $1 "Power" 0 +4 - SetShellVarContext all - ;MessageBox MB_OK 'User "$0" is in the Power Users group' - StrCpy $SV_ALLUSERS "AllUsers" - Goto done - - noLM: - StrCpy $SV_ALLUSERS "AllUsers" - ;Get installation folder from registry if available - - done: - StrCmp $SV_ALLUSERS "AllUsers" 0 +3 - StrCmp "$IS_DEFAULT_INSTALLDIR" "1" 0 +2 - StrCpy $INSTDIR "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@" - - StrCmp "@CPACK_NSIS_MODIFY_PATH@" "ON" 0 noOptionsPage - !insertmacro INSTALLOPTIONS_EXTRACT "NSIS.InstallOptions.ini" - - noOptionsPage: + ; use all users for context of data/startup folders + SetShellVarContext all FunctionEnd diff --git a/examples/junkyard/junkyardClientReset.js b/examples/junkyard/junkyardClientReset.js new file mode 100644 index 0000000000..c4dfa19c4f --- /dev/null +++ b/examples/junkyard/junkyardClientReset.js @@ -0,0 +1,31 @@ +// +// junkyardClientReset.js +// examples/junkyard +// +// Created by Eric Levin on 1/21/16. +// Copyright 2016 High Fidelity, Inc. +// +// This script resets the junkyard scene +// +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var IMPORT_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/arfs/junkyard.json"; +var PASTE_ENTITIES_LOCATION = {x: 0, y: 0, z: 0}; +reset(); + +function reset() { + // Delete everything and re-import the junkyard arf + var e = Entities.findEntities(MyAvatar.position, 1000); + for (i = 0; i < e.length; i++) { + Entities.deleteEntity(e[i]); + } + importAssetResourceFile(); +} + +function importAssetResourceFile() { + Clipboard.importEntities(IMPORT_URL); + Clipboard.pasteEntities(PASTE_ENTITIES_LOCATION); +} \ No newline at end of file diff --git a/examples/junkyard/junkyardResetEntityScript.js b/examples/junkyard/junkyardResetEntityScript.js new file mode 100644 index 0000000000..9a330d0cdc --- /dev/null +++ b/examples/junkyard/junkyardResetEntityScript.js @@ -0,0 +1,53 @@ +// junkyardResetEntityScript.js +// +// Script Type: Entity +// Created by Eric Levin on 1/20/16. +// Copyright 2016 High Fidelity, Inc. +// +// This entity script resets the junkyard when triggered +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + Script.include("../libraries/utils.js"); + var _this; + var IMPORT_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/arfs/junkyard.json"; + var PASTE_ENTITIES_LOCATION = {x: 0, y: 0, z: 0}; + var JunkyardResetter = function() { + _this = this; + }; + + JunkyardResetter.prototype = { + + clickReleaseOnEntity: function(entityId, mouseEvent) { + if (!mouseEvent.isLeftButton) { + return; + } + this.reset(); + + }, + + reset: function() { + // Delete everything and re-import the junkyard arf + var e = Entities.findEntities(MyAvatar.position, 1000); + for (i = 0; i < e.length; i++) { + // Don't delete our reset entity + if (JSON.stringify(this.entityID) !== JSON.stringify(e[i])) { + Entities.deleteEntity(e[i]); + } + } + this.importAssetResourceFile(); + }, + + importAssetResourceFile: function() { + Clipboard.importEntities(IMPORT_URL); + Clipboard.pasteEntities(PASTE_ENTITIES_LOCATION); + }, + + preload: function(entityID) { + this.entityID = entityID; + }, + }; + return new JunkyardResetter(); +}); diff --git a/examples/junkyard/junkyardResetEntitySpawner.js b/examples/junkyard/junkyardResetEntitySpawner.js new file mode 100644 index 0000000000..f467e0e22f --- /dev/null +++ b/examples/junkyard/junkyardResetEntitySpawner.js @@ -0,0 +1,45 @@ + // + // junkyardResetEntitySpawner.js + // examples/junkyard + // + // Created by Eric Levin on 1/20/16. + // Copyright 2016 High Fidelity, Inc. + // + // This script spawns an entity which, when triggered, will reset the junkyard + // + // + // Distributed under the Apache License, Version 2.0. + // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + // + + var orientation = Camera.getOrientation(); + orientation = Quat.safeEulerAngles(orientation); + orientation.x = 0; + orientation = Quat.fromVec3Degrees(orientation); + var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(orientation))); + + + var SCRIPT_URL = Script.resolvePath("junkyardResetEntityScript.js"); + var MODEL_URL = "http://hifi-content.s3.amazonaws.com/caitlyn/dev/Blueprint%20Objects/Asylum/Asylum_Table/Asylum_Table.fbx"; + var resetEntity = Entities.addEntity({ + type: "Model", + modelURL: MODEL_URL, + position: center, + script: SCRIPT_URL, + dimensions: { + x: 2.8, + y: 1.76, + z: 1.32 + }, + color: { + red: 200, + green: 10, + blue: 200 + } + }); + + function cleanup() { + Entities.deleteEntity(resetEntity); + } + + Script.scriptEnding.connect(cleanup); \ No newline at end of file diff --git a/interface/resources/qml/ToolWindow.qml b/interface/resources/qml/ToolWindow.qml index 8e42f67ebc..9aad639ff3 100644 --- a/interface/resources/qml/ToolWindow.qml +++ b/interface/resources/qml/ToolWindow.qml @@ -103,6 +103,8 @@ Windows.Window { if (index < 0) { return; } + var tab = tabView.getTab(index); + tab.enabledChanged.disconnect(updateVisiblity); tabView.removeTab(index); console.log("Updating visibility based on child tab removed"); updateVisiblity(); @@ -137,10 +139,7 @@ Windows.Window { } console.log("Updating visibility based on child tab added"); - newTab.enabledChanged.connect(function() { - console.log("Updating visibility based on child tab enabled change"); - updateVisiblity(); - }) + newTab.enabledChanged.connect(updateVisiblity) updateVisiblity(); return newTab } diff --git a/interface/resources/qml/controls/ComboBox.qml b/interface/resources/qml/controls/ComboBox.qml new file mode 100644 index 0000000000..e22bc8e664 --- /dev/null +++ b/interface/resources/qml/controls/ComboBox.qml @@ -0,0 +1,148 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +import "." as VrControls + +FocusScope { + id: root + property alias model: comboBox.model; + readonly property alias currentText: comboBox.currentText; + property alias currentIndex: comboBox.currentIndex; + implicitHeight: comboBox.height; + focus: true + + readonly property ComboBox control: comboBox + + Rectangle { + id: background + gradient: Gradient { + GradientStop {color: control.pressed ? "#bababa" : "#fefefe" ; position: 0} + GradientStop {color: control.pressed ? "#ccc" : "#e3e3e3" ; position: 1} + } + anchors.fill: parent + border.color: control.activeFocus ? "#47b" : "#999" + Rectangle { + anchors.fill: parent + radius: parent.radius + color: control.activeFocus ? "#47b" : "white" + opacity: control.hovered || control.activeFocus ? 0.1 : 0 + Behavior on opacity {NumberAnimation{ duration: 100 }} + } + } + + SystemPalette { id: palette } + + ComboBox { + id: comboBox + anchors.fill: parent + visible: false + } + + Text { + id: textField + anchors { left: parent.left; leftMargin: 2; right: dropIcon.left; verticalCenter: parent.verticalCenter } + text: comboBox.currentText + elide: Text.ElideRight + } + + Item { + id: dropIcon + anchors { right: parent.right; verticalCenter: parent.verticalCenter } + width: 20 + height: textField.height + VrControls.FontAwesome { + anchors.centerIn: parent; size: 16; + text: "\uf0d7" + } + } + + MouseArea { + anchors.fill: parent + onClicked: toggleList(); + } + + function toggleList() { + if (popup.visible) { + hideList(); + } else { + showList(); + } + } + + function showList() { + var r = desktop.mapFromItem(root, 0, 0, root.width, root.height); + listView.currentIndex = root.currentIndex + scrollView.x = r.x; + scrollView.y = r.y + r.height; + var bottom = scrollView.y + scrollView.height; + if (bottom > desktop.height) { + scrollView.y -= bottom - desktop.height + 8; + } + popup.visible = true; + popup.forceActiveFocus(); + } + + function hideList() { + popup.visible = false; + } + + FocusScope { + id: popup + parent: desktop + anchors.fill: parent + visible: false + focus: true + MouseArea { + anchors.fill: parent + onClicked: hideList(); + } + + function previousItem() { listView.currentIndex = (listView.currentIndex + listView.count - 1) % listView.count; } + function nextItem() { listView.currentIndex = (listView.currentIndex + listView.count + 1) % listView.count; } + function selectCurrentItem() { root.currentIndex = listView.currentIndex; hideList(); } + + Keys.onUpPressed: previousItem(); + Keys.onDownPressed: nextItem(); + Keys.onSpacePressed: selectCurrentItem(); + Keys.onRightPressed: selectCurrentItem(); + Keys.onReturnPressed: selectCurrentItem(); + Keys.onEscapePressed: hideList(); + + ScrollView { + id: scrollView + height: 480 + width: root.width + + ListView { + id: listView + height: textView.height * count + model: root.model + highlight: Rectangle{ + width: listView.currentItem ? listView.currentItem.width : 0 + height: listView.currentItem ? listView.currentItem.height : 0 + color: "red" + } + delegate: Rectangle { + width: root.width + height: popupText.implicitHeight * 1.4 + color: ListView.isCurrentItem ? palette.highlight : palette.base + Text { + anchors.verticalCenter: parent.verticalCenter + id: popupText + x: 3 + text: listView.model[index] + } + MouseArea { + id: popupHover + anchors.fill: parent; + hoverEnabled: true + onEntered: listView.currentIndex = index; + onClicked: popup.selectCurrentItem() + } + } + } + } + } + +} diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 96c84b49c0..8c4f73c200 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -13,6 +13,9 @@ FocusScope { anchors.fill: parent; objectName: "desktop" + onHeightChanged: d.repositionAll(); + onWidthChanged: d.repositionAll(); + // Allows QML/JS to find the desktop through the parent chain property bool desktopRoot: true @@ -50,10 +53,6 @@ FocusScope { return item; } - function isDesktop(item) { - return item.desktopRoot; - } - function isTopLevelWindow(item) { return item.topLevelWindow; } @@ -147,6 +146,53 @@ FocusScope { var windows = getTopLevelWindows(predicate); fixupZOrder(windows, zBasis, targetWindow); } + + Component.onCompleted: { + offscreenWindow.activeFocusItemChanged.connect(onWindowFocusChanged); + focusHack.start(); + } + + function onWindowFocusChanged() { + console.log("Focus item is " + offscreenWindow.activeFocusItem); + + // FIXME this needs more testing before it can go into production + // and I already cant produce any way to have a modal dialog lose focus + // to a non-modal one. + /* + var focusedWindow = getDesktopWindow(offscreenWindow.activeFocusItem); + + if (isModalWindow(focusedWindow)) { + return; + } + + // new focused window is not modal... check if there are any modal windows + var windows = getTopLevelWindows(isModalWindow); + if (0 === windows.length) { + return; + } + + // There are modal windows present, force focus back to the top-most modal window + windows.sort(function(a, b){ return a.z - b.z; }); + windows[windows.length - 1].focus = true; + */ + +// var focusedItem = offscreenWindow.activeFocusItem ; +// if (DebugQML && focusedItem) { +// var rect = desktop.mapFromItem(focusedItem, 0, 0, focusedItem.width, focusedItem.height); +// focusDebugger.x = rect.x; +// focusDebugger.y = rect.y; +// focusDebugger.width = rect.width +// focusDebugger.height = rect.height +// } + } + + + function repositionAll() { + var windows = d.getTopLevelWindows(); + for (var i = 0; i < windows.length; ++i) { + reposition(windows[i]); + } + } } function raise(item) { @@ -167,7 +213,7 @@ FocusScope { } if (setFocus) { - focus = true; + targetWindow.focus = true; } reposition(targetWindow); @@ -187,27 +233,20 @@ FocusScope { var windowRect = targetWindow.framedRect(); var minPosition = Qt.vector2d(-windowRect.x, -windowRect.y); var maxPosition = Qt.vector2d(desktop.width - windowRect.width, desktop.height - windowRect.height); - var newPosition; - if (targetWindow.x === -1 && targetWindow.y === -1) { + var newPosition = Qt.vector2d(targetWindow.x, targetWindow.y); + if (newPosition.x === -1 && newPosition.y === -1) { // Set initial window position - newPosition = Utils.randomPosition(minPosition, maxPosition); - } else { - newPosition = Utils.clampVector(Qt.vector2d(targetWindow.x, targetWindow.y), minPosition, maxPosition); + // newPosition = Utils.randomPosition(minPosition, maxPosition); + console.log("Target has no defined position, putting in center of the screen") + newPosition = Qt.vector2d(desktop.width / 2 - windowRect.width / 2, + desktop.height / 2 - windowRect.height / 2); } + + newPosition = Utils.clampVector(newPosition, minPosition, maxPosition); targetWindow.x = newPosition.x; targetWindow.y = newPosition.y; } - function repositionAll() { - var windows = d.getTopLevelWindows(); - for (var i = 0; i < windows.length; ++i) { - reposition(windows[i]); - } - } - - onHeightChanged: repositionAll(); - onWidthChanged: repositionAll(); - Component { id: messageDialogBuilder; MessageDialog { } } function messageBox(properties) { return messageDialogBuilder.createObject(desktop, properties); @@ -252,39 +291,21 @@ FocusScope { desktop.focus = true; } - // Debugging help for figuring out focus issues - property var offscreenWindow; - onOffscreenWindowChanged: { - offscreenWindow.activeFocusItemChanged.connect(onWindowFocusChanged); - focusHack.start(); - } - FocusHack { id: focusHack; } - function onWindowFocusChanged() { - console.log("Focus item is " + offscreenWindow.activeFocusItem); - var focusedItem = offscreenWindow.activeFocusItem ; - if (DebugQML && focusedItem) { - var rect = desktop.mapFromItem(focusedItem, 0, 0, focusedItem.width, focusedItem.height); - focusDebugger.x = rect.x; - focusDebugger.y = rect.y; - focusDebugger.width = rect.width - focusDebugger.height = rect.height - } - } - Rectangle { id: focusDebugger; z: 9999; visible: false; color: "red" ColorAnimation on color { from: "#7fffff00"; to: "#7f0000ff"; duration: 1000; loops: 9999 } } - + Action { text: "Toggle Focus Debugger" shortcut: "Ctrl+Shift+F" enabled: DebugQML onTriggered: focusDebugger.visible = !focusDebugger.visible } + } diff --git a/interface/resources/qml/dialogs/RunningScripts.qml b/interface/resources/qml/dialogs/RunningScripts.qml index 059c765f0e..eb56b59b66 100644 --- a/interface/resources/qml/dialogs/RunningScripts.qml +++ b/interface/resources/qml/dialogs/RunningScripts.qml @@ -184,9 +184,28 @@ Window { anchors.bottom: filterEdit.top anchors.bottomMargin: 8 anchors.right: parent.right + + // For some reason trigginer an API that enters + // an internal event loop directly from the button clicked + // trigger below causes the appliction to behave oddly. + // Most likely because the button onClicked handling is never + // completed until the function returns. + // FIXME find a better way of handling the input dialogs that + // doesn't trigger this. + Timer { + id: asyncAction + interval: 50 + repeat: false + running: false + onTriggered: ApplicationInterface.loadScriptURLDialog(); + } + Button { text: "from URL"; - onClicked: ApplicationInterface.loadScriptURLDialog(); + onClicked: { + focus = false; + asyncAction.running = true; + } } Button { text: "from Disk" diff --git a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml new file mode 100644 index 0000000000..1c70f06efd --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml @@ -0,0 +1,124 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import Qt.labs.settings 1.0 + +import "../../windows" +import "attachments" + +Window { + id: root + title: "Edit Attachments" + objectName: "AttachmentsDialog" + width: 600 + height: 600 + resizable: true + // User must click OK or cancel to close the window + closable: false + + readonly property var originalAttachments: MyAvatar.getAttachmentsVariant(); + property var attachments: []; + + property var settings: Settings { + category: "AttachmentsDialog" + property alias x: root.x + property alias y: root.y + property alias width: root.width + property alias height: root.height + } + + Component.onCompleted: { + for (var i = 0; i < originalAttachments.length; ++i) { + var attachment = originalAttachments[i]; + root.attachments.push(attachment); + listView.model.append({}); + } + } + + Rectangle { + anchors.fill: parent + radius: 4 + + Rectangle { + id: attachmentsBackground + anchors { left: parent.left; right: parent.right; top: parent.top; bottom: newAttachmentButton.top; margins: 8 } + color: "gray" + radius: 4 + + ScrollView{ + id: scrollView + anchors.fill: parent + anchors.margins: 4 + ListView { + id: listView + model: ListModel {} + delegate: Item { + implicitHeight: attachmentView.height + 8; + implicitWidth: attachmentView.width; + Attachment { + id: attachmentView + width: scrollView.width + attachment: root.attachments[index] + onDeleteAttachment: { + attachments.splice(index, 1); + listView.model.remove(index, 1); + } + onUpdateAttachment: MyAvatar.setAttachmentsVariant(attachments); + } + } + onCountChanged: MyAvatar.setAttachmentsVariant(attachments); + } + } + } + + Button { + id: newAttachmentButton + anchors { left: parent.left; right: parent.right; bottom: buttonRow.top; margins: 8 } + text: "New Attachment" + + onClicked: { + var template = { + modelUrl: "", + translation: { x: 0, y: 0, z: 0 }, + rotation: { x: 0, y: 0, z: 0 }, + scale: 1, + jointName: MyAvatar.jointNames[0], + soft: false + }; + attachments.push(template); + listView.model.append({}); + MyAvatar.setAttachmentsVariant(attachments); + } + } + + Row { + id: buttonRow + spacing: 8 + anchors { right: parent.right; bottom: parent.bottom; margins: 8 } + Button { action: cancelAction } + Button { action: okAction } + } + + Action { + id: cancelAction + text: "Cancel" + onTriggered: { + MyAvatar.setAttachmentsVariant(originalAttachments); + root.destroy() + } + } + + Action { + id: okAction + text: "OK" + onTriggered: { + for (var i = 0; i < attachments.length; ++i) { + console.log("Attachment " + i + ": " + attachments[i]); + } + + MyAvatar.setAttachmentsVariant(attachments); + root.destroy() + } + } + } +} + diff --git a/interface/resources/qml/hifi/dialogs/AvatarAttachmentsDialog.qml b/interface/resources/qml/hifi/dialogs/AvatarAttachmentsDialog.qml new file mode 100644 index 0000000000..252e4c629e --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/AvatarAttachmentsDialog.qml @@ -0,0 +1,128 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.XmlListModel 2.0 + +import "../../windows" +import "../../js/Utils.js" as Utils +import "../models" + +ModalWindow { + id: root + resizable: true + width: 640 + height: 480 + + property var result; + + signal selected(var modelUrl); + signal canceled(); + + Rectangle { + anchors.fill: parent + color: "white" + + Item { + anchors { fill: parent; margins: 8 } + + TextField { + id: filterEdit + anchors { left: parent.left; right: parent.right; top: parent.top } + placeholderText: "filter" + onTextChanged: tableView.model.filter = text + } + + TableView { + id: tableView + anchors { left: parent.left; right: parent.right; top: filterEdit.bottom; topMargin: 8; bottom: buttonRow.top; bottomMargin: 8 } + model: S3Model{} + onCurrentRowChanged: { + if (currentRow == -1) { + root.result = null; + return; + } + result = model.baseUrl + "/" + model.get(tableView.currentRow).key; + } + itemDelegate: Component { + Item { + clip: true + Text { + x: 3 + anchors.verticalCenter: parent.verticalCenter + color: tableView.activeFocus && styleData.row === tableView.currentRow ? "yellow" : styleData.textColor + elide: styleData.elideMode + text: getText() + + function getText() { + switch(styleData.column) { + case 1: + return Utils.formatSize(styleData.value) + default: + return styleData.value; + } + } + + } + } + } + TableViewColumn { + role: "name" + title: "Name" + width: 200 + } + TableViewColumn { + role: "size" + title: "Size" + width: 100 + } + TableViewColumn { + role: "modified" + title: "Last Modified" + width: 200 + } + Rectangle { + anchors.fill: parent + visible: tableView.model.status !== XmlListModel.Ready + color: "#7fffffff" + BusyIndicator { + anchors.centerIn: parent + width: 48; height: 48 + running: true + } + } + } + + Row { + id: buttonRow + anchors { right: parent.right; bottom: parent.bottom } + Button { action: acceptAction } + Button { action: cancelAction } + } + + Action { + id: acceptAction + text: qsTr("OK") + enabled: root.result ? true : false + shortcut: Qt.Key_Return + onTriggered: { + root.selected(root.result); + root.destroy(); + } + } + + Action { + id: cancelAction + text: qsTr("Cancel") + shortcut: Qt.Key_Escape + onTriggered: { + root.canceled(); + root.destroy(); + } + } + } + + } +} + + + + diff --git a/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml b/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml new file mode 100644 index 0000000000..252e4c629e --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml @@ -0,0 +1,128 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.XmlListModel 2.0 + +import "../../windows" +import "../../js/Utils.js" as Utils +import "../models" + +ModalWindow { + id: root + resizable: true + width: 640 + height: 480 + + property var result; + + signal selected(var modelUrl); + signal canceled(); + + Rectangle { + anchors.fill: parent + color: "white" + + Item { + anchors { fill: parent; margins: 8 } + + TextField { + id: filterEdit + anchors { left: parent.left; right: parent.right; top: parent.top } + placeholderText: "filter" + onTextChanged: tableView.model.filter = text + } + + TableView { + id: tableView + anchors { left: parent.left; right: parent.right; top: filterEdit.bottom; topMargin: 8; bottom: buttonRow.top; bottomMargin: 8 } + model: S3Model{} + onCurrentRowChanged: { + if (currentRow == -1) { + root.result = null; + return; + } + result = model.baseUrl + "/" + model.get(tableView.currentRow).key; + } + itemDelegate: Component { + Item { + clip: true + Text { + x: 3 + anchors.verticalCenter: parent.verticalCenter + color: tableView.activeFocus && styleData.row === tableView.currentRow ? "yellow" : styleData.textColor + elide: styleData.elideMode + text: getText() + + function getText() { + switch(styleData.column) { + case 1: + return Utils.formatSize(styleData.value) + default: + return styleData.value; + } + } + + } + } + } + TableViewColumn { + role: "name" + title: "Name" + width: 200 + } + TableViewColumn { + role: "size" + title: "Size" + width: 100 + } + TableViewColumn { + role: "modified" + title: "Last Modified" + width: 200 + } + Rectangle { + anchors.fill: parent + visible: tableView.model.status !== XmlListModel.Ready + color: "#7fffffff" + BusyIndicator { + anchors.centerIn: parent + width: 48; height: 48 + running: true + } + } + } + + Row { + id: buttonRow + anchors { right: parent.right; bottom: parent.bottom } + Button { action: acceptAction } + Button { action: cancelAction } + } + + Action { + id: acceptAction + text: qsTr("OK") + enabled: root.result ? true : false + shortcut: Qt.Key_Return + onTriggered: { + root.selected(root.result); + root.destroy(); + } + } + + Action { + id: cancelAction + text: qsTr("Cancel") + shortcut: Qt.Key_Escape + onTriggered: { + root.canceled(); + root.destroy(); + } + } + } + + } +} + + + + diff --git a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml new file mode 100644 index 0000000000..31a1895e58 --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml @@ -0,0 +1,165 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +import "../../../windows" +import "../../../controls" as VrControls +import "." +import ".." + +Item { + height: column.height + 2 * 8 + + property var attachment; + + signal deleteAttachment(var attachment); + signal updateAttachment(); + property bool completed: false; + + Rectangle { color: "white"; anchors.fill: parent; radius: 4 } + + Component.onCompleted: { + completed = true; + } + + Column { + y: 8 + id: column + anchors { left: parent.left; right: parent.right; margins: 8 } + spacing: 8 + + Item { + height: modelChooserButton.height + anchors { left: parent.left; right: parent.right; } + Text { id: urlLabel; text: "Model URL:"; width: 80; anchors.verticalCenter: modelUrl.verticalCenter } + TextField { + id: modelUrl; + height: jointChooser.height; + anchors { left: urlLabel.right; leftMargin: 8; rightMargin: 8; right: modelChooserButton.left } + text: attachment ? attachment.modelUrl : "" + onTextChanged: { + if (completed && attachment && attachment.modelUrl !== text) { + attachment.modelUrl = text; + updateAttachment(); + } + } + } + Button { + id: modelChooserButton; + text: "Choose"; + anchors { right: parent.right; verticalCenter: modelUrl.verticalCenter } + Component { + id: modelBrowserBuiler; + ModelBrowserDialog {} + } + + onClicked: { + var browser = modelBrowserBuiler.createObject(desktop); + browser.selected.connect(function(newModelUrl){ + modelUrl.text = newModelUrl; + }) + } + } + } + + Item { + height: jointChooser.height + anchors { left: parent.left; right: parent.right; } + Text { + id: jointLabel; + width: 80; + text: "Joint:"; + anchors.verticalCenter: jointChooser.verticalCenter; + } + VrControls.ComboBox { + id: jointChooser; + anchors { left: jointLabel.right; leftMargin: 8; right: parent.right } + model: MyAvatar.jointNames + currentIndex: attachment ? model.indexOf(attachment.jointName) : -1 + onCurrentIndexChanged: { + if (completed && attachment && currentIndex != -1 && currentText && currentText !== attachment.jointName) { + attachment.jointName = currentText; + updateAttachment(); + } + } + } + } + + Item { + height: translation.height + anchors { left: parent.left; right: parent.right; } + Text { id: translationLabel; width: 80; text: "Translation:"; anchors.verticalCenter: translation.verticalCenter; } + Translation { + id: translation; + anchors { left: translationLabel.right; leftMargin: 8; right: parent.right } + vector: attachment ? attachment.translation : {x: 0, y: 0, z: 0}; + onValueChanged: { + if (completed && attachment) { + attachment.translation = vector; + updateAttachment(); + } + } + } + } + + Item { + height: rotation.height + anchors { left: parent.left; right: parent.right; } + Text { id: rotationLabel; width: 80; text: "Rotation:"; anchors.verticalCenter: rotation.verticalCenter; } + Rotation { + id: rotation; + anchors { left: rotationLabel.right; leftMargin: 8; right: parent.right } + vector: attachment ? attachment.rotation : {x: 0, y: 0, z: 0}; + onValueChanged: { + if (completed && attachment) { + attachment.rotation = vector; + updateAttachment(); + } + } + } + } + + Item { + height: scaleSpinner.height + anchors { left: parent.left; right: parent.right; } + Text { id: scaleLabel; width: 80; text: "Scale:"; anchors.verticalCenter: scale.verticalCenter; } + SpinBox { + id: scaleSpinner; + anchors { left: scaleLabel.right; leftMargin: 8; right: parent.right } + decimals: 1; + minimumValue: 0.1 + maximumValue: 10 + stepSize: 0.1; + value: attachment ? attachment.scale : 1.0 + onValueChanged: { + if (completed && attachment && attachment.scale !== value) { + attachment.scale = value; + updateAttachment(); + } + } + } + } + + Item { + height: soft.height + anchors { left: parent.left; right: parent.right; } + Text { id: softLabel; width: 80; text: "Is soft:"; anchors.verticalCenter: soft.verticalCenter; } + CheckBox { + id: soft; + anchors { left: softLabel.right; leftMargin: 8; right: parent.right } + checked: attachment ? attachment.soft : false + onCheckedChanged: { + if (completed && attachment && attachment.soft !== checked) { + attachment.soft = checked; + updateAttachment(); + } + } + } + } + + Button { + anchors { left: parent.left; right: parent.right; } + text: "Delete" + onClicked: deleteAttachment(root.attachment); + } + } +} diff --git a/interface/resources/qml/hifi/dialogs/attachments/Rotation.qml b/interface/resources/qml/hifi/dialogs/attachments/Rotation.qml new file mode 100644 index 0000000000..6061efc4c8 --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/attachments/Rotation.qml @@ -0,0 +1,9 @@ +import "." + +Vector3 { + decimals: 1; + stepSize: 1; + maximumValue: 180 + minimumValue: -180 +} + diff --git a/interface/resources/qml/hifi/dialogs/attachments/Translation.qml b/interface/resources/qml/hifi/dialogs/attachments/Translation.qml new file mode 100644 index 0000000000..f3a90cdf94 --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/attachments/Translation.qml @@ -0,0 +1,9 @@ +import "." + +Vector3 { + decimals: 2; + stepSize: 0.01; + maximumValue: 10 + minimumValue: -10 +} + diff --git a/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml b/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml new file mode 100644 index 0000000000..02382749bd --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml @@ -0,0 +1,71 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +Item { + id: root + implicitHeight: xspinner.height + readonly property real spacing: 8 + property real spinboxWidth: (width / 3) - spacing + property var vector; + property real decimals: 0 + property real stepSize: 1 + property real maximumValue: 99 + property real minimumValue: 0 + + signal valueChanged(); + + SpinBox { + id: xspinner + width: root.spinboxWidth + anchors { left: parent.left } + value: root.vector.x + + decimals: root.decimals + stepSize: root.stepSize + maximumValue: root.maximumValue + minimumValue: root.minimumValue + onValueChanged: { + if (value !== vector.x) { + vector.x = value + root.valueChanged(); + } + } + } + + SpinBox { + id: yspinner + width: root.spinboxWidth + anchors { horizontalCenter: parent.horizontalCenter } + value: root.vector.y + + decimals: root.decimals + stepSize: root.stepSize + maximumValue: root.maximumValue + minimumValue: root.minimumValue + onValueChanged: { + if (value !== vector.y) { + vector.y = value + root.valueChanged(); + } + } + } + + SpinBox { + id: zspinner + width: root.spinboxWidth + anchors { right: parent.right; } + value: root.vector.z + + decimals: root.decimals + stepSize: root.stepSize + maximumValue: root.maximumValue + minimumValue: root.minimumValue + onValueChanged: { + if (value !== vector.z) { + vector.z = value + root.valueChanged(); + } + } + } +} + diff --git a/interface/resources/qml/hifi/models/S3Model.qml b/interface/resources/qml/hifi/models/S3Model.qml new file mode 100644 index 0000000000..565965c124 --- /dev/null +++ b/interface/resources/qml/hifi/models/S3Model.qml @@ -0,0 +1,41 @@ +import QtQuick 2.0 +import QtQuick.XmlListModel 2.0 + +//http://s3.amazonaws.com/hifi-public?prefix=models/attachments +/* + + + + models/attachments/guitar.fst + 2015-11-10T00:28:22.000Z + "236c00c4802ba9c2605cabd5601d138e" + 2992 + STANDARD + + +*/ + +// FIXME how to deal with truncated results? Store the marker? +XmlListModel { + id: xmlModel + property string prefix: "models/attachments/" + property string extension: "fst" + property string filter; + + readonly property string realPrefix: prefix.match('.*/$') ? prefix : (prefix + "/") + readonly property string nameRegex: realPrefix + (filter ? (".*" + filter) : "") + ".*\." + extension + readonly property string nameQuery: "Key/substring-before(substring-after(string(), '" + prefix + "'), '." + extension + "')" + readonly property string baseUrl: "http://s3.amazonaws.com/hifi-public" + + // FIXME need to urlencode prefix? + source: baseUrl + "?prefix=" + realPrefix + query: "/ListBucketResult/Contents[matches(Key, '" + nameRegex + "')]" + namespaceDeclarations: "declare default element namespace 'http://s3.amazonaws.com/doc/2006-03-01/';" + + XmlRole { name: "name"; query: nameQuery } + XmlRole { name: "size"; query: "Size/string()" } + XmlRole { name: "tag"; query: "ETag/string()" } + XmlRole { name: "modified"; query: "LastModified/string()" } + XmlRole { name: "key"; query: "Key/string()" } +} + diff --git a/interface/resources/qml/js/Utils.js b/interface/resources/qml/js/Utils.js index 417c8c1641..49f85fcc91 100644 --- a/interface/resources/qml/js/Utils.js +++ b/interface/resources/qml/js/Utils.js @@ -14,3 +14,17 @@ function randomPosition(min, max) { Math.random() * (max.x - min.x), Math.random() * (max.y - min.y)); } + +function formatSize(size) { + var suffixes = [ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ]; + var suffixIndex = 0 + while ((size / 1024.0) > 1.1) { + size /= 1024.0; + ++suffixIndex; + } + + size = Math.round(size*1000)/1000; + size = size.toLocaleString() + + return size + " " + suffixes[suffixIndex]; +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a295cd8a6e..27f9d81ab2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1186,6 +1186,7 @@ void Application::initializeUi() { UpdateDialog::registerType(); qmlRegisterType("Hifi", 1, 0, "Preference"); + auto offscreenUi = DependencyManager::get(); offscreenUi->create(_offscreenContext->getContext()); offscreenUi->setProxyWindow(_window->windowHandle()); @@ -1380,10 +1381,12 @@ void Application::paintGL() { } + glm::vec3 boomOffset; { PerformanceTimer perfTimer("CameraUpdates"); auto myAvatar = getMyAvatar(); + boomOffset = myAvatar->getScale() * myAvatar->getBoomLength() * -IDENTITY_FRONT; myAvatar->startCapture(); if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON || _myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { @@ -1411,18 +1414,16 @@ void Application::paintGL() { if (isHMDMode()) { auto hmdWorldMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix(); _myCamera.setRotation(glm::normalize(glm::quat_cast(hmdWorldMat))); - auto worldBoomOffset = myAvatar->getOrientation() * (myAvatar->getScale() * myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f)); - _myCamera.setPosition(extractTranslation(hmdWorldMat) + worldBoomOffset); + _myCamera.setPosition(extractTranslation(hmdWorldMat) + + myAvatar->getOrientation() * boomOffset); } else { _myCamera.setRotation(myAvatar->getHead()->getOrientation()); if (Menu::getInstance()->isOptionChecked(MenuOption::CenterPlayerInView)) { _myCamera.setPosition(myAvatar->getDefaultEyePosition() - + _myCamera.getRotation() - * (myAvatar->getScale() * myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f))); + + _myCamera.getRotation() * boomOffset); } else { _myCamera.setPosition(myAvatar->getDefaultEyePosition() - + myAvatar->getOrientation() - * (myAvatar->getScale() * myAvatar->getBoomLength() * glm::vec3(0.0f, 0.0f, 1.0f))); + + myAvatar->getOrientation() * boomOffset); } } } else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { @@ -1488,6 +1489,7 @@ void Application::paintGL() { { PROFILE_RANGE(__FUNCTION__ "/mainRender"); PerformanceTimer perfTimer("mainRender"); + renderArgs._boomOffset = boomOffset; // Viewport is assigned to the size of the framebuffer renderArgs._viewport = ivec4(0, 0, size.width(), size.height()); if (displayPlugin->isStereo()) { @@ -1841,6 +1843,9 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_X: if (isShifted && isMeta) { + auto offscreenUi = DependencyManager::get(); + offscreenUi->getRootContext()->engine()->clearComponentCache(); + OffscreenUi::information("Debugging", "Component cache cleared"); // placeholder for dialogs being converted to QML. } break; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 9cbb031a61..720d8993ab 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -70,8 +70,8 @@ Menu::Menu() { } // File > Update -- FIXME: needs implementation - auto updateAction = addActionToQMenuAndActionHash(fileMenu, "Update"); - updateAction->setDisabled(true); + auto action = addActionToQMenuAndActionHash(fileMenu, "Update"); + action->setDisabled(true); // File > Help addActionToQMenuAndActionHash(fileMenu, MenuOption::Help, 0, qApp, SLOT(showHelp())); @@ -166,8 +166,11 @@ Menu::Menu() { QObject* avatar = avatarManager->getMyAvatar(); // Avatar > Attachments... - addActionToQMenuAndActionHash(avatarMenu, MenuOption::Attachments, 0, - dialogsManager.data(), SLOT(editAttachments())); + action = addActionToQMenuAndActionHash(avatarMenu, MenuOption::Attachments); + connect(action, &QAction::triggered, [] { + DependencyManager::get()->show(QString("hifi/dialogs/AttachmentsDialog.qml"), "AttachmentsDialog"); + }); + // Avatar > Size MenuWrapper* avatarSizeMenu = avatarMenu->addMenu("Size"); @@ -285,7 +288,7 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(settingsMenu, "Developer Menus", 0, false, this, SLOT(toggleDeveloperMenus())); // Settings > General... - auto action = addActionToQMenuAndActionHash(settingsMenu, MenuOption::Preferences, Qt::CTRL | Qt::Key_Comma, nullptr, nullptr, QAction::PreferencesRole); + action = addActionToQMenuAndActionHash(settingsMenu, MenuOption::Preferences, Qt::CTRL | Qt::Key_Comma, nullptr, nullptr, QAction::PreferencesRole); connect(action, &QAction::triggered, [] { DependencyManager::get()->toggle(QString("hifi/dialogs/GeneralPreferencesDialog.qml"), "GeneralPreferencesDialog"); }); @@ -479,6 +482,10 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::TurnWithHead, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ComfortMode, 0, true); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::UseAnimPreAndPostRotations, 0, false, + avatar, SLOT(setUseAnimPreAndPostRotations(bool))); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableInverseKinematics, 0, true, + avatar, SLOT(setEnableInverseKinematics(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::KeyboardMotorControl, Qt::CTRL | Qt::SHIFT | Qt::Key_K, true, avatar, SLOT(updateMotionBehaviorFromMenu()), diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 408ab15d5e..7b9cca2c63 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -208,6 +208,7 @@ namespace MenuOption { const QString EchoServerAudio = "Echo Server Audio"; const QString Enable3DTVMode = "Enable 3DTV Mode"; const QString EnableCharacterController = "Enable avatar collisions"; + const QString EnableInverseKinematics = "Enable Inverse Kinematics"; const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation"; const QString ExpandMyAvatarTiming = "Expand /myAvatar"; const QString ExpandOtherAvatarTiming = "Expand /otherAvatar"; @@ -302,6 +303,7 @@ namespace MenuOption { const QString UploadAsset = "Upload File to Asset Server"; const QString UseAudioForMouth = "Use Audio for Mouth"; const QString UseCamera = "Use Camera"; + const QString UseAnimPreAndPostRotations = "Use Anim Pre and Post Rotations"; const QString VelocityFilter = "Velocity Filter"; const QString VisibleToEveryone = "Everyone"; const QString VisibleToFriends = "Friends"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e06dce2324..f5765f9259 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -49,6 +50,8 @@ #include "Util.h" #include "InterfaceLogging.h" #include "DebugDraw.h" +#include "EntityEditPacketSender.h" +#include "MovingEntitiesOperator.h" using namespace std; @@ -362,6 +365,37 @@ void MyAvatar::simulate(float deltaTime) { // consider updating our billboard maybeUpdateBillboard(); + + locationChanged(); + // if a entity-child of this avatar has moved outside of its queryAACube, update the cube and tell the entity server. + EntityTreeRenderer* entityTreeRenderer = qApp->getEntities(); + EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr; + if (entityTree) { + EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender(); + MovingEntitiesOperator moveOperator(entityTree); + forEachDescendant([&](SpatiallyNestablePointer object) { + // if the queryBox has changed, tell the entity-server + if (object->computePuffedQueryAACube() && object->getNestableType() == NestableType::Entity) { + EntityItemPointer entity = std::static_pointer_cast(object); + bool success; + AACube newCube = entity->getQueryAACube(success); + if (success) { + moveOperator.addEntityToMoveList(entity, newCube); + } + if (packetSender) { + EntityItemProperties properties = entity->getProperties(); + properties.setQueryAACubeDirty(); + packetSender->queueEditEntityMessage(PacketType::EntityEdit, entity->getID(), properties); + entity->setLastBroadcast(usecTimestampNow()); + } + } + }); + // also update the position of children in our local octree + if (moveOperator.hasMovingEntities()) { + PerformanceTimer perfTimer("recurseTreeWithOperator"); + entityTree->recurseTreeWithOperator(&moveOperator); + } + } } glm::mat4 MyAvatar::getSensorToWorldMatrix() const { @@ -647,6 +681,15 @@ void MyAvatar::setEnableMeshVisible(bool isEnabled) { _skeletonModel.setVisibleInScene(isEnabled, scene); } +void MyAvatar::setUseAnimPreAndPostRotations(bool isEnabled) { + AnimClip::usePreAndPostPoseFromAnim = isEnabled; + reset(true); +} + +void MyAvatar::setEnableInverseKinematics(bool isEnabled) { + _rig->setEnableInverseKinematics(isEnabled); +} + void MyAvatar::loadData() { Settings settings; settings.beginGroup("Avatar"); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 10ce962821..ed6c3cb883 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -260,6 +260,8 @@ public slots: void setEnableDebugDrawPosition(bool isEnabled); bool getEnableMeshVisible() const { return _skeletonModel.isVisible(); } void setEnableMeshVisible(bool isEnabled); + void setUseAnimPreAndPostRotations(bool isEnabled); + void setEnableInverseKinematics(bool isEnabled); Q_INVOKABLE void setAnimGraphUrl(const QUrl& url); glm::vec3 getPositionForAudio(); diff --git a/interface/src/ui/AttachmentsDialog.cpp b/interface/src/ui/AttachmentsDialog.cpp deleted file mode 100644 index d718b52d6d..0000000000 --- a/interface/src/ui/AttachmentsDialog.cpp +++ /dev/null @@ -1,239 +0,0 @@ -// -// AttachmentsDialog.cpp -// interface/src/ui -// -// Created by Andrzej Kapolka on 5/4/14. -// 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 -// - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "AttachmentsDialog.h" -#include "ModelsBrowser.h" - -AttachmentsDialog::AttachmentsDialog(QWidget* parent) : - QDialog(parent) { - - setWindowTitle("Edit Attachments"); - setAttribute(Qt::WA_DeleteOnClose); - - QVBoxLayout* layout = new QVBoxLayout(); - setLayout(layout); - - QScrollArea* area = new QScrollArea(); - layout->addWidget(area); - area->setWidgetResizable(true); - QWidget* container = new QWidget(); - container->setLayout(_attachments = new QVBoxLayout()); - container->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); - area->setWidget(container); - _attachments->addStretch(1); - - foreach (const AttachmentData& data, DependencyManager::get()->getMyAvatar()->getAttachmentData()) { - addAttachment(data); - } - - QPushButton* newAttachment = new QPushButton("New Attachment"); - connect(newAttachment, SIGNAL(clicked(bool)), SLOT(addAttachment())); - layout->addWidget(newAttachment); - - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok); - layout->addWidget(buttons); - connect(buttons, SIGNAL(accepted()), SLOT(deleteLater())); - _ok = buttons->button(QDialogButtonBox::Ok); - - setMinimumSize(600, 600); -} - -void AttachmentsDialog::setVisible(bool visible) { - QDialog::setVisible(visible); - - // un-default the OK button - if (visible) { - _ok->setDefault(false); - } -} - -void AttachmentsDialog::updateAttachmentData() { - QVector data; - for (int i = 0; i < _attachments->count() - 1; i++) { - data.append(static_cast(_attachments->itemAt(i)->widget())->getAttachmentData()); - } - DependencyManager::get()->getMyAvatar()->setAttachmentData(data); -} - -void AttachmentsDialog::addAttachment(const AttachmentData& data) { - _attachments->insertWidget(_attachments->count() - 1, new AttachmentPanel(this, data)); -} - -static QDoubleSpinBox* createTranslationBox(AttachmentPanel* panel, float value) { - QDoubleSpinBox* box = new QDoubleSpinBox(); - box->setSingleStep(0.01); - box->setMinimum(-FLT_MAX); - box->setMaximum(FLT_MAX); - box->setValue(value); - panel->connect(box, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData())); - return box; -} - -static QDoubleSpinBox* createRotationBox(AttachmentPanel* panel, float value) { - QDoubleSpinBox* box = new QDoubleSpinBox(); - box->setMinimum(-180.0); - box->setMaximum(180.0); - box->setWrapping(true); - box->setValue(value); - panel->connect(box, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData())); - return box; -} - -AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data) : - _dialog(dialog), - _applying(false) { - setFrameStyle(QFrame::StyledPanel); - - QFormLayout* layout = new QFormLayout(); - layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); - setLayout(layout); - - QHBoxLayout* urlBox = new QHBoxLayout(); - layout->addRow("Model URL:", urlBox); - urlBox->addWidget(_modelURL = new QLineEdit(data.modelURL.toString()), 1); - _modelURL->setText(data.modelURL.toString()); - connect(_modelURL, SIGNAL(editingFinished()), SLOT(modelURLChanged())); - QPushButton* chooseURL = new QPushButton("Choose"); - urlBox->addWidget(chooseURL); - connect(chooseURL, SIGNAL(clicked(bool)), SLOT(chooseModelURL())); - - layout->addRow("Joint:", _jointName = new QComboBox()); - QSharedPointer geometry = DependencyManager::get()->getMyAvatar()->getSkeletonModel().getGeometry(); - if (geometry && geometry->isLoaded()) { - foreach (const FBXJoint& joint, geometry->getFBXGeometry().joints) { - _jointName->addItem(joint.name); - } - } - _jointName->setCurrentText(data.jointName); - connect(_jointName, SIGNAL(currentIndexChanged(int)), SLOT(jointNameChanged())); - - QHBoxLayout* translationBox = new QHBoxLayout(); - translationBox->addWidget(_translationX = createTranslationBox(this, data.translation.x)); - translationBox->addWidget(_translationY = createTranslationBox(this, data.translation.y)); - translationBox->addWidget(_translationZ = createTranslationBox(this, data.translation.z)); - layout->addRow("Translation:", translationBox); - - QHBoxLayout* rotationBox = new QHBoxLayout(); - glm::vec3 eulers = glm::degrees(safeEulerAngles(data.rotation)); - rotationBox->addWidget(_rotationX = createRotationBox(this, eulers.x)); - rotationBox->addWidget(_rotationY = createRotationBox(this, eulers.y)); - rotationBox->addWidget(_rotationZ = createRotationBox(this, eulers.z)); - layout->addRow("Rotation:", rotationBox); - - layout->addRow("Scale:", _scale = new QDoubleSpinBox()); - _scale->setSingleStep(0.01); - _scale->setMaximum(FLT_MAX); - _scale->setValue(data.scale); - connect(_scale, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData())); - - layout->addRow("Is Soft:", _isSoft = new QCheckBox()); - _isSoft->setChecked(data.isSoft); - connect(_isSoft, SIGNAL(stateChanged(int)), SLOT(updateAttachmentData())); - - QPushButton* remove = new QPushButton("Delete"); - layout->addRow(remove); - connect(remove, SIGNAL(clicked(bool)), SLOT(deleteLater())); - dialog->connect(remove, SIGNAL(clicked(bool)), SLOT(updateAttachmentData()), Qt::QueuedConnection); -} - -AttachmentData AttachmentPanel::getAttachmentData() const { - AttachmentData data; - data.modelURL = _modelURL->text(); - data.jointName = _jointName->currentText(); - data.translation = glm::vec3(_translationX->value(), _translationY->value(), _translationZ->value()); - data.rotation = glm::quat(glm::radians(glm::vec3(_rotationX->value(), _rotationY->value(), _rotationZ->value()))); - data.scale = _scale->value(); - data.isSoft = _isSoft->isChecked(); - return data; -} - -void AttachmentPanel::chooseModelURL() { - ModelsBrowser modelBrowser(FSTReader::ATTACHMENT_MODEL, this); - connect(&modelBrowser, SIGNAL(selected(QString)), SLOT(setModelURL(const QString&))); - modelBrowser.browse(); -} - -void AttachmentPanel::setModelURL(const QString& url) { - _modelURL->setText(url); - modelURLChanged(); -} - -void AttachmentPanel::modelURLChanged() { - // check for saved attachment data - if (_modelURL->text().isEmpty()) { - _dialog->updateAttachmentData(); - return; - } - AttachmentData attachment = DependencyManager::get()->getMyAvatar()->loadAttachmentData(_modelURL->text()); - if (attachment.isValid()) { - _applying = true; - _jointName->setCurrentText(attachment.jointName); - applyAttachmentData(attachment); - } - _dialog->updateAttachmentData(); -} - -void AttachmentPanel::jointNameChanged() { - if (_applying) { - return; - } - // check for saved attachment data specific to this joint - if (_modelURL->text().isEmpty()) { - _dialog->updateAttachmentData(); - return; - } - AttachmentData attachment = DependencyManager::get()->getMyAvatar()->loadAttachmentData( - _modelURL->text(), _jointName->currentText()); - if (attachment.isValid()) { - applyAttachmentData(attachment); - } - updateAttachmentData(); -} - -void AttachmentPanel::updateAttachmentData() { - if (_applying) { - return; - } - // save the attachment data under the model URL (if any) - if (!_modelURL->text().isEmpty()) { - DependencyManager::get()->getMyAvatar()->saveAttachmentData(getAttachmentData()); - } - _dialog->updateAttachmentData(); -} - -void AttachmentPanel::applyAttachmentData(const AttachmentData& attachment) { - _applying = true; - _translationX->setValue(attachment.translation.x); - _translationY->setValue(attachment.translation.y); - _translationZ->setValue(attachment.translation.z); - glm::vec3 eulers = glm::degrees(safeEulerAngles(attachment.rotation)); - _rotationX->setValue(eulers.x); - _rotationY->setValue(eulers.y); - _rotationZ->setValue(eulers.z); - _scale->setValue(attachment.scale); - _isSoft->setChecked(attachment.isSoft); - _applying = false; - _dialog->updateAttachmentData(); -} diff --git a/interface/src/ui/AttachmentsDialog.h b/interface/src/ui/AttachmentsDialog.h deleted file mode 100644 index 43ba5f8f3e..0000000000 --- a/interface/src/ui/AttachmentsDialog.h +++ /dev/null @@ -1,84 +0,0 @@ -// -// AttachmentsDialog.h -// interface/src/ui -// -// Created by Andrzej Kapolka on 5/4/14. -// 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 -// - -#ifndef hifi_AttachmentsDialog_h -#define hifi_AttachmentsDialog_h - -#include -#include - -#include - -class QComboBox; -class QDoubleSpinner; -class QLineEdit; -class QVBoxLayout; - -/// Allows users to edit the avatar attachments. -class AttachmentsDialog : public QDialog { - Q_OBJECT - -public: - AttachmentsDialog(QWidget* parent = nullptr); - - virtual void setVisible(bool visible); - -public slots: - - void updateAttachmentData(); - -private slots: - - void addAttachment(const AttachmentData& data = AttachmentData()); - -private: - - QVBoxLayout* _attachments; - QPushButton* _ok; -}; - -/// A panel controlling a single attachment. -class AttachmentPanel : public QFrame { - Q_OBJECT - -public: - - AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data = AttachmentData()); - - AttachmentData getAttachmentData() const; - -private slots: - - void chooseModelURL(); - void setModelURL(const QString& url); - void modelURLChanged(); - void jointNameChanged(); - void updateAttachmentData(); - -private: - - void applyAttachmentData(const AttachmentData& attachment); - - AttachmentsDialog* _dialog; - QLineEdit* _modelURL; - QComboBox* _jointName; - QDoubleSpinBox* _translationX; - QDoubleSpinBox* _translationY; - QDoubleSpinBox* _translationZ; - QDoubleSpinBox* _rotationX; - QDoubleSpinBox* _rotationY; - QDoubleSpinBox* _rotationZ; - QDoubleSpinBox* _scale; - QCheckBox* _isSoft; - bool _applying; -}; - -#endif // hifi_AttachmentsDialog_h diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index ef0ec5792f..41d7a0eb13 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -19,7 +19,6 @@ #include #include "AddressBarDialog.h" -#include "AttachmentsDialog.h" #include "BandwidthDialog.h" #include "CachesSizeDialog.h" #include "DiskCacheEditor.h" @@ -91,15 +90,6 @@ void DialogsManager::cachesSizeDialog() { _cachesSizeDialog->raise(); } -void DialogsManager::editAttachments() { - if (!_attachmentsDialog) { - maybeCreateDialog(_attachmentsDialog); - _attachmentsDialog->show(); - } else { - _attachmentsDialog->close(); - } -} - void DialogsManager::audioStatsDetails() { if (! _audioStatsDialog) { _audioStatsDialog = new AudioStatsDialog(qApp->getWindow()); diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index 72a24e032c..b8fa22ec83 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -48,7 +48,6 @@ public slots: void showLoginDialog(); void octreeStatsDetails(); void cachesSizeDialog(); - void editAttachments(); void audioStatsDetails(); void bandwidthDetails(); void lodTools(); diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 986bb0a30e..90cd85e727 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -13,6 +13,8 @@ #include "AnimationLogging.h" #include "AnimUtil.h" +bool AnimClip::usePreAndPostPoseFromAnim = false; + AnimClip::AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag) : AnimNode(AnimNode::Type::Clip, id), _startFrame(startFrame), @@ -109,6 +111,8 @@ void AnimClip::copyFromNetworkAnim() { for (int frame = 0; frame < frameCount; frame++) { + const FBXAnimationFrame& fbxAnimFrame = geom.animationFrames[frame]; + // init all joints in animation to default pose // this will give us a resonable result for bones in the model skeleton but not in the animation. _anim[frame].reserve(skeletonJointCount); @@ -119,36 +123,41 @@ void AnimClip::copyFromNetworkAnim() { for (int animJoint = 0; animJoint < animJointCount; animJoint++) { int skeletonJoint = jointMap[animJoint]; + const glm::vec3& fbxAnimTrans = fbxAnimFrame.translations[animJoint]; + const glm::quat& fbxAnimRot = fbxAnimFrame.rotations[animJoint]; + // skip joints that are in the animation but not in the skeleton. if (skeletonJoint >= 0 && skeletonJoint < skeletonJointCount) { - const glm::vec3& fbxZeroTrans = geom.animationFrames[0].translations[animJoint]; -#ifdef USE_PRE_ROT_FROM_ANIM - // TODO: This is the correct way to apply the pre rotations from maya, however - // the current animation set has incorrect preRotations for the left wrist and thumb - // so it looks wrong if we enable this code. - glm::quat preRotation = animSkeleton.getPreRotation(animJoint); -#else - // TODO: This is the legacy approach, this does not work when animations and models do not - // have the same set of pre rotations. For example when mixing maya models with blender animations. - glm::quat preRotation = _skeleton->getRelativeBindPose(skeletonJoint).rot; -#endif - const AnimPose& relDefaultPose = _skeleton->getRelativeDefaultPose(skeletonJoint); + AnimPose preRot, postRot; + if (usePreAndPostPoseFromAnim) { + preRot = animSkeleton.getPreRotationPose(animJoint); + postRot = animSkeleton.getPostRotationPose(animJoint); + } else { + // In order to support Blender, which does not have preRotation FBX support, we use the models defaultPose as the reference frame for the animations. + preRot = AnimPose(glm::vec3(1.0f), _skeleton->getRelativeBindPose(skeletonJoint).rot, glm::vec3()); + postRot = AnimPose::identity; + } - // used to adjust translation offsets, so large translation animatons on the reference skeleton + // cancel out scale + preRot.scale = glm::vec3(1.0f); + postRot.scale = glm::vec3(1.0f); + + AnimPose rot(glm::vec3(1.0f), fbxAnimRot, glm::vec3()); + + // adjust translation offsets, so large translation animatons on the reference skeleton // will be adjusted when played on a skeleton with short limbs. - float limbLengthScale = fabsf(glm::length(fbxZeroTrans)) <= 0.0001f ? 1.0f : (glm::length(relDefaultPose.trans) / glm::length(fbxZeroTrans)); + const glm::vec3& fbxZeroTrans = geom.animationFrames[0].translations[animJoint]; + const AnimPose& relDefaultPose = _skeleton->getRelativeDefaultPose(skeletonJoint); + float boneLengthScale = 1.0f; + const float EPSILON = 0.0001f; + if (fabsf(glm::length(fbxZeroTrans)) > EPSILON) { + boneLengthScale = glm::length(relDefaultPose.trans) / glm::length(fbxZeroTrans); + } - AnimPose& pose = _anim[frame][skeletonJoint]; - const FBXAnimationFrame& fbxAnimFrame = geom.animationFrames[frame]; + AnimPose trans = AnimPose(glm::vec3(1.0f), glm::quat(), relDefaultPose.trans + boneLengthScale * (fbxAnimTrans - fbxZeroTrans)); - // rotation in fbxAnimationFrame is a delta from its preRotation. - pose.rot = preRotation * fbxAnimFrame.rotations[animJoint]; - - // translation in fbxAnimationFrame is not a delta. - // convert it into a delta by subtracting from the first frame. - const glm::vec3& fbxTrans = fbxAnimFrame.translations[animJoint]; - pose.trans = relDefaultPose.trans + limbLengthScale * (fbxTrans - fbxZeroTrans); + _anim[frame][skeletonJoint] = trans * preRot * rot * postRot; } } } diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index 934f3f3ed8..7d58ae4f9a 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -25,6 +25,8 @@ class AnimClip : public AnimNode { public: friend class AnimTests; + static bool usePreAndPostPoseFromAnim; + AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag); virtual ~AnimClip() override; diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 415ee72d7b..e47a12960e 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -153,7 +153,6 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vectorgetParentIndex(tipIndex); if (pivotIndex == -1 || pivotIndex == _hipsIndex) { @@ -165,12 +164,30 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vectorapply(tipRelativeRotation); + if (constrained) { + tipOrientation = glm::normalize(tipRelativeRotation * tipParentOrientation); + } + } + } + + // cache tip absolute position + glm::vec3 tipPosition = absolutePoses[tipIndex].trans; // descend toward root, pivoting each joint to get tip closer to target while (pivotIndex != _hipsIndex && pivotsParentIndex != -1) { @@ -201,9 +218,9 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vectorapply(newRot); if (constrained) { - // the constraint will modify the movement of the tip so we have to compute the modified - // model-frame deltaRotation + // the constraint will modify the local rotation of the tip so we must + // compute the corresponding model-frame deltaRotation // Q' = Qp^ * dQ * Q --> dQ = Qp * Q' * Q^ deltaRotation = absolutePoses[pivotsParentIndex].rot * - newRot * - glm::inverse(absolutePoses[pivotIndex].rot); + newRot * glm::inverse(absolutePoses[pivotIndex].rot); } } @@ -264,8 +280,8 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vectorgetParentIndex(pivotIndex); @@ -629,11 +645,11 @@ void AnimInverseKinematics::initConstraints() { } else if (0 == baseName.compare("Neck", Qt::CaseInsensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot); - const float MAX_NECK_TWIST = PI / 4.0f; + const float MAX_NECK_TWIST = PI / 6.0f; stConstraint->setTwistLimits(-MAX_NECK_TWIST, MAX_NECK_TWIST); std::vector minDots; - const float MAX_NECK_SWING = PI / 3.0f; + const float MAX_NECK_SWING = PI / 4.0f; minDots.push_back(cosf(MAX_NECK_SWING)); stConstraint->setSwingLimits(minDots); @@ -641,11 +657,11 @@ void AnimInverseKinematics::initConstraints() { } else if (0 == baseName.compare("Head", Qt::CaseInsensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot); - const float MAX_HEAD_TWIST = PI / 4.0f; + const float MAX_HEAD_TWIST = PI / 8.0f; stConstraint->setTwistLimits(-MAX_HEAD_TWIST, MAX_HEAD_TWIST); std::vector minDots; - const float MAX_HEAD_SWING = PI / 4.0f; + const float MAX_HEAD_SWING = PI / 6.0f; minDots.push_back(cosf(MAX_HEAD_SWING)); stConstraint->setSwingLimits(minDots); diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 8e3d716aac..8f45b785d1 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -59,8 +59,14 @@ const AnimPose& AnimSkeleton::getAbsoluteDefaultPose(int jointIndex) const { return _absoluteDefaultPoses[jointIndex]; } -const glm::quat AnimSkeleton::getPreRotation(int jointIndex) const { - return _joints[jointIndex].preRotation; +// get pre multiplied transform which should include FBX pre potations +const AnimPose& AnimSkeleton::getPreRotationPose(int jointIndex) const { + return _relativePreRotationPoses[jointIndex]; +} + +// get post multiplied transform which might include FBX offset transformations +const AnimPose& AnimSkeleton::getPostRotationPose(int jointIndex) const { + return _relativePostRotationPoses[jointIndex]; } int AnimSkeleton::getParentIndex(int jointIndex) const { @@ -99,13 +105,20 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints) // build a chache of default poses _absoluteDefaultPoses.reserve(joints.size()); _relativeDefaultPoses.reserve(joints.size()); + _relativePreRotationPoses.reserve(joints.size()); + _relativePostRotationPoses.reserve(joints.size()); // iterate over FBXJoints and extract the bind pose information. for (int i = 0; i < (int)joints.size(); i++) { + // build pre and post transforms + glm::mat4 preRotationTransform = _joints[i].preTransform * glm::mat4_cast(_joints[i].preRotation); + glm::mat4 postRotationTransform = glm::mat4_cast(_joints[i].postRotation) * _joints[i].postTransform; + _relativePreRotationPoses.push_back(AnimPose(preRotationTransform)); + _relativePostRotationPoses.push_back(AnimPose(postRotationTransform)); + // build relative and absolute default poses - glm::mat4 rotTransform = glm::mat4_cast(_joints[i].preRotation * _joints[i].rotation * _joints[i].postRotation); - glm::mat4 relDefaultMat = glm::translate(_joints[i].translation) * _joints[i].preTransform * rotTransform * _joints[i].postTransform; + glm::mat4 relDefaultMat = glm::translate(_joints[i].translation) * preRotationTransform * glm::mat4_cast(_joints[i].rotation) * postRotationTransform; AnimPose relDefaultPose(relDefaultMat); _relativeDefaultPoses.push_back(relDefaultPose); int parentIndex = getParentIndex(i); diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 7312fea6b1..757f4e5c3e 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -42,8 +42,11 @@ public: const AnimPose& getAbsoluteDefaultPose(int jointIndex) const; const AnimPoseVec& getAbsoluteDefaultPoses() const { return _absoluteDefaultPoses; } - // get pre-rotation aka Maya's joint orientation. - const glm::quat getPreRotation(int jointIndex) const; + // get pre transform which should include FBX pre potations + const AnimPose& getPreRotationPose(int jointIndex) const; + + // get post transform which might include FBX offset transformations + const AnimPose& getPostRotationPose(int jointIndex) const; int getParentIndex(int jointIndex) const; @@ -64,6 +67,8 @@ protected: AnimPoseVec _relativeBindPoses; AnimPoseVec _relativeDefaultPoses; AnimPoseVec _absoluteDefaultPoses; + AnimPoseVec _relativePreRotationPoses; + AnimPoseVec _relativePostRotationPoses; // no copies AnimSkeleton(const AnimSkeleton&) = delete; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index df92d7d912..4d72bfbaea 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -464,6 +464,10 @@ void Rig::computeEyesInRootFrame(const AnimPoseVec& poses) { } } +void Rig::setEnableInverseKinematics(bool enable) { + _enableInverseKinematics = enable; +} + AnimPose Rig::getAbsoluteDefaultPose(int index) const { if (_animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints()) { return _absoluteDefaultPoses[index]; @@ -705,6 +709,12 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos } t += deltaTime; + + if (_enableInverseKinematics) { + _animVars.set("ikOverlayAlpha", 1.0f); + } else { + _animVars.set("ikOverlayAlpha", 0.0f); + } } _lastFront = front; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index d26be83d36..cf96c6dc16 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -208,6 +208,8 @@ public: void computeAvatarBoundingCapsule(const FBXGeometry& geometry, float& radiusOut, float& heightOut, glm::vec3& offsetOut) const; + void setEnableInverseKinematics(bool enable); + protected: bool isIndexValid(int index) const { return _animSkeleton && index >= 0 && index < _animSkeleton->getNumJoints(); } void updateAnimationStateHandlers(); @@ -290,6 +292,8 @@ public: std::map _origRoleAnimations; std::vector _prefetchedAnimations; + bool _enableInverseKinematics { true }; + private: QMap _stateHandlers; int _nextStateHandlerId { 0 }; diff --git a/libraries/audio/src/AudioConstants.h b/libraries/audio/src/AudioConstants.h index b1c810710e..6c12c6aa15 100644 --- a/libraries/audio/src/AudioConstants.h +++ b/libraries/audio/src/AudioConstants.h @@ -29,7 +29,7 @@ namespace AudioConstants { const int NETWORK_FRAME_SAMPLES_PER_CHANNEL = NETWORK_FRAME_BYTES_PER_CHANNEL / sizeof(AudioSample); const float NETWORK_FRAME_MSECS = (AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL / (float)AudioConstants::SAMPLE_RATE) * 1000.0f; - const unsigned int NETWORK_FRAME_USECS = (unsigned int)floorf(NETWORK_FRAME_MSECS * 1000.0f); + const int NETWORK_FRAME_USECS = static_cast(NETWORK_FRAME_MSECS * 1000.0f); const int MIN_SAMPLE_VALUE = std::numeric_limits::min(); const int MAX_SAMPLE_VALUE = std::numeric_limits::max(); } diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 1f15007339..661509c3d2 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -36,7 +36,7 @@ AudioInjector::AudioInjector(Sound* sound, const AudioInjectorOptions& injectorO _audioData(sound->getByteArray()), _options(injectorOptions) { - + } AudioInjector::AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions) : @@ -49,15 +49,15 @@ AudioInjector::AudioInjector(const QByteArray& audioData, const AudioInjectorOpt void AudioInjector::finish() { bool shouldDelete = (_state == State::NotFinishedWithPendingDelete); _state = State::Finished; - + emit finished(); - + if (_localBuffer) { _localBuffer->stop(); _localBuffer->deleteLater(); _localBuffer = NULL; } - + if (shouldDelete) { // we've been asked to delete after finishing, trigger a deleteLater here deleteLater(); @@ -67,7 +67,7 @@ void AudioInjector::finish() { void AudioInjector::setupInjection() { if (!_hasSetup) { _hasSetup = true; - + // check if we need to offset the sound by some number of seconds if (_options.secondOffset > 0.0f) { @@ -87,31 +87,31 @@ void AudioInjector::setupInjection() { void AudioInjector::restart() { // grab the AudioInjectorManager auto injectorManager = DependencyManager::get(); - + if (thread() != QThread::currentThread()) { QMetaObject::invokeMethod(this, "restart"); - + if (!_options.localOnly) { // notify the AudioInjectorManager to wake up in case it's waiting for new injectors injectorManager->notifyInjectorReadyCondition(); } - + return; } - + // reset the current send offset to zero _currentSendOffset = 0; - + // check our state to decide if we need extra handling for the restart request if (_state == State::Finished) { // we finished playing, need to reset state so we can get going again _hasSetup = false; _shouldStop = false; _state = State::NotFinished; - + // call inject audio to start injection over again setupInjection(); - + // if we're a local injector call inject locally to start injecting again if (_options.localOnly) { injectLocally(); @@ -153,155 +153,167 @@ bool AudioInjector::injectLocally() { // we never started so we are finished, call our stop method stop(); } - + return success; } const uchar MAX_INJECTOR_VOLUME = 0xFF; -static const uint64_t NEXT_FRAME_DELTA_ERROR_OR_FINISHED = 0; -static const uint64_t NEXT_FRAME_DELTA_IMMEDIATELY = 1; +static const int64_t NEXT_FRAME_DELTA_ERROR_OR_FINISHED = -1; +static const int64_t NEXT_FRAME_DELTA_IMMEDIATELY = 0; -uint64_t AudioInjector::injectNextFrame() { - +int64_t AudioInjector::injectNextFrame() { if (_state == AudioInjector::State::Finished) { qDebug() << "AudioInjector::injectNextFrame called but AudioInjector has finished and was not restarted. Returning."; return NEXT_FRAME_DELTA_ERROR_OR_FINISHED; } - + // if we haven't setup the packet to send then do so now static int positionOptionOffset = -1; static int volumeOptionOffset = -1; static int audioDataOffset = -1; - + if (!_currentPacket) { if (_currentSendOffset < 0 || _currentSendOffset >= _audioData.size()) { _currentSendOffset = 0; } - + // make sure we actually have samples downloaded to inject if (_audioData.size()) { - + _outgoingSequenceNumber = 0; _nextFrame = 0; - + if (!_frameTimer) { _frameTimer = std::unique_ptr(new QElapsedTimer); } - + _frameTimer->restart(); - + _currentPacket = NLPacket::create(PacketType::InjectAudio); - + // setup the packet for injected audio QDataStream audioPacketStream(_currentPacket.get()); - + // pack some placeholder sequence number for now audioPacketStream << (quint16) 0; - + // pack stream identifier (a generated UUID) audioPacketStream << QUuid::createUuid(); - + // pack the stereo/mono type of the stream audioPacketStream << _options.stereo; - + // pack the flag for loopback - uchar loopbackFlag = (uchar) true; + uchar loopbackFlag = (uchar)true; audioPacketStream << loopbackFlag; - + // pack the position for injected audio positionOptionOffset = _currentPacket->pos(); audioPacketStream.writeRawData(reinterpret_cast(&_options.position), sizeof(_options.position)); - + // pack our orientation for injected audio audioPacketStream.writeRawData(reinterpret_cast(&_options.orientation), sizeof(_options.orientation)); - + // pack zero for radius float radius = 0; audioPacketStream << radius; - + // pack 255 for attenuation byte volumeOptionOffset = _currentPacket->pos(); quint8 volume = MAX_INJECTOR_VOLUME; audioPacketStream << volume; - + audioPacketStream << _options.ignorePenumbra; - + audioDataOffset = _currentPacket->pos(); - + } else { // no samples to inject, return immediately qDebug() << "AudioInjector::injectNextFrame() called with no samples to inject. Returning."; return NEXT_FRAME_DELTA_ERROR_OR_FINISHED; } } - - int bytesToCopy = std::min((_options.stereo ? 2 : 1) * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL, - _audioData.size() - _currentSendOffset); - + + int totalBytesLeftToCopy = (_options.stereo ? 2 : 1) * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL; + if (!_options.loop) { + // If we aren't looping, let's make sure we don't read past the end + totalBytesLeftToCopy = std::min(totalBytesLeftToCopy, _audioData.size() - _currentSendOffset); + } + // Measure the loudness of this frame _loudness = 0.0f; - for (int i = 0; i < bytesToCopy; i += sizeof(int16_t)) { - _loudness += abs(*reinterpret_cast(_audioData.data() + _currentSendOffset + i)) / - (AudioConstants::MAX_SAMPLE_VALUE / 2.0f); + for (int i = 0; i < totalBytesLeftToCopy; i += sizeof(int16_t)) { + _loudness += abs(*reinterpret_cast(_audioData.data() + ((_currentSendOffset + i) % _audioData.size()))) / + (AudioConstants::MAX_SAMPLE_VALUE / 2.0f); } - _loudness /= (float)(bytesToCopy / sizeof(int16_t)); - + _loudness /= (float)(totalBytesLeftToCopy/ sizeof(int16_t)); + _currentPacket->seek(0); - + // pack the sequence number _currentPacket->writePrimitive(_outgoingSequenceNumber); - + _currentPacket->seek(positionOptionOffset); _currentPacket->writePrimitive(_options.position); _currentPacket->writePrimitive(_options.orientation); - + quint8 volume = MAX_INJECTOR_VOLUME * _options.volume; _currentPacket->seek(volumeOptionOffset); _currentPacket->writePrimitive(volume); - + _currentPacket->seek(audioDataOffset); - - // copy the next NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL bytes to the packet - _currentPacket->write(_audioData.data() + _currentSendOffset, bytesToCopy); - + + while (totalBytesLeftToCopy > 0) { + int bytesToCopy = std::min(totalBytesLeftToCopy, _audioData.size() - _currentSendOffset); + + _currentPacket->write(_audioData.data() + _currentSendOffset, bytesToCopy); + _currentSendOffset += bytesToCopy; + totalBytesLeftToCopy -= bytesToCopy; + if (_currentSendOffset >= _audioData.size()) { + _currentSendOffset = 0; + } + } + // set the correct size used for this packet _currentPacket->setPayloadSize(_currentPacket->pos()); - + // grab our audio mixer from the NodeList, if it exists auto nodeList = DependencyManager::get(); SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); - + if (audioMixer) { // send off this audio packet nodeList->sendUnreliablePacket(*_currentPacket, *audioMixer); _outgoingSequenceNumber++; } - - _currentSendOffset += bytesToCopy; - - if (_currentSendOffset >= _audioData.size()) { - // we're at the end of the audio data to send - if (_options.loop) { - // we were asked to loop, set our send offset to 0 - _currentSendOffset = 0; - } else { - // we weren't to loop, say that we're done now - finish(); - return NEXT_FRAME_DELTA_ERROR_OR_FINISHED; - } + + if (_currentSendOffset >= _audioData.size() && !_options.loop) { + finish(); + return NEXT_FRAME_DELTA_ERROR_OR_FINISHED; } - - if (_currentSendOffset == bytesToCopy) { + + if (!_hasSentFirstFrame) { + _hasSentFirstFrame = true; // ask AudioInjectorManager to call us right away again to // immediately send the first two frames so the mixer can start using the audio right away return NEXT_FRAME_DELTA_IMMEDIATELY; - } else { - return (++_nextFrame * AudioConstants::NETWORK_FRAME_USECS) - _frameTimer->nsecsElapsed() / 1000; } - + + const int MAX_ALLOWED_FRAMES_TO_FALL_BEHIND = 7; + int64_t currentTime = _frameTimer->nsecsElapsed() / 1000; + auto currentFrameBasedOnElapsedTime = currentTime / AudioConstants::NETWORK_FRAME_USECS; + if (currentFrameBasedOnElapsedTime - _nextFrame > MAX_ALLOWED_FRAMES_TO_FALL_BEHIND) { + // If we are falling behind by more frames than our threshold, let's skip the frames ahead + qDebug() << "AudioInjector::injectNextFrame() skipping ahead, fell behind by " << (currentFrameBasedOnElapsedTime - _nextFrame) << " frames"; + _nextFrame = currentFrameBasedOnElapsedTime; + _currentSendOffset = _nextFrame * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL * (_options.stereo ? 2 : 1) % _audioData.size(); + } + + int64_t playNextFrameAt = ++_nextFrame * AudioConstants::NETWORK_FRAME_USECS; + return std::max(INT64_C(0), playNextFrameAt - currentTime); } void AudioInjector::stop() { @@ -315,7 +327,7 @@ void AudioInjector::triggerDeleteAfterFinish() { QMetaObject::invokeMethod(this, "triggerDeleteAfterFinish", Qt::QueuedConnection); return; } - + if (_state == State::Finished) { stopAndDeleteLater(); } else { @@ -371,11 +383,11 @@ AudioInjector* AudioInjector::playSound(const QString& soundUrl, const float vol AudioInjector* AudioInjector::playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface) { AudioInjector* sound = playSound(buffer, options, localInterface); - + if (sound) { sound->_state = AudioInjector::State::NotFinishedWithPendingDelete; } - + return sound; } @@ -383,13 +395,13 @@ AudioInjector* AudioInjector::playSoundAndDelete(const QByteArray& buffer, const AudioInjector* AudioInjector::playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface) { AudioInjector* injector = new AudioInjector(buffer, options); injector->setLocalAudioInterface(localInterface); - + // grab the AudioInjectorManager auto injectorManager = DependencyManager::get(); - + // setup parameters required for injection injector->setupInjection(); - + if (options.localOnly) { if (injector->injectLocally()) { // local injection succeeded, return the pointer to injector diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index f815b6fe3a..acfbc2b04a 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -84,16 +84,17 @@ private slots: private: void setupInjection(); - uint64_t injectNextFrame(); + int64_t injectNextFrame(); bool injectLocally(); QByteArray _audioData; AudioInjectorOptions _options; State _state { State::NotFinished }; - bool _hasSetup = false; - bool _shouldStop = false; - float _loudness = 0.0f; - int _currentSendOffset = 0; + bool _hasSentFirstFrame { false }; + bool _hasSetup { false }; + bool _shouldStop { false }; + float _loudness { 0.0f }; + int _currentSendOffset { 0 }; std::unique_ptr _currentPacket { nullptr }; AbstractAudioInterface* _localAudioInterface { nullptr }; AudioInjectorLocalBuffer* _localBuffer { nullptr }; diff --git a/libraries/audio/src/AudioInjectorManager.cpp b/libraries/audio/src/AudioInjectorManager.cpp index f504b31907..b91bddc553 100644 --- a/libraries/audio/src/AudioInjectorManager.cpp +++ b/libraries/audio/src/AudioInjectorManager.cpp @@ -88,7 +88,7 @@ void AudioInjectorManager::run() { // this is an injector that's ready to go, have it send a frame now auto nextCallDelta = injector->injectNextFrame(); - if (nextCallDelta > 0 && !injector->isFinished()) { + if (nextCallDelta >= 0 && !injector->isFinished()) { // re-enqueue the injector with the correct timing _injectors.emplace(usecTimestampNow() + nextCallDelta, injector); } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 5fd69128eb..3933d705fc 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -1673,3 +1674,65 @@ glm::vec3 AvatarData::getAbsoluteJointTranslationInObjectFrame(int index) const assert(false); return glm::vec3(); } + +QVariant AttachmentData::toVariant() const { + QVariantMap result; + result["modelUrl"] = modelURL; + result["jointName"] = jointName; + result["translation"] = glmToQMap(translation); + result["rotation"] = glmToQMap(glm::degrees(safeEulerAngles(rotation))); + result["scale"] = scale; + result["soft"] = isSoft; + return result; +} + +glm::vec3 variantToVec3(const QVariant& var) { + auto map = var.toMap(); + glm::vec3 result; + result.x = map["x"].toFloat(); + result.y = map["y"].toFloat(); + result.z = map["z"].toFloat(); + return result; +} + +void AttachmentData::fromVariant(const QVariant& variant) { + auto map = variant.toMap(); + if (map.contains("modelUrl")) { + auto urlString = map["modelUrl"].toString(); + modelURL = urlString; + } + if (map.contains("jointName")) { + jointName = map["jointName"].toString(); + } + if (map.contains("translation")) { + translation = variantToVec3(map["translation"]); + } + if (map.contains("rotation")) { + rotation = glm::quat(glm::radians(variantToVec3(map["rotation"]))); + } + if (map.contains("scale")) { + scale = map["scale"].toFloat(); + } + if (map.contains("soft")) { + isSoft = map["soft"].toBool(); + } +} + +QVariantList AvatarData::getAttachmentsVariant() const { + QVariantList result; + for (const auto& attachment : getAttachmentData()) { + result.append(attachment.toVariant()); + } + return result; +} + +void AvatarData::setAttachmentsVariant(const QVariantList& variant) { + QVector newAttachments; + newAttachments.reserve(variant.size()); + for (const auto& attachmentVar : variant) { + AttachmentData attachment; + attachment.fromVariant(attachmentVar); + newAttachments.append(attachment); + } + setAttachmentData(newAttachments); +} diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 3b8214f226..eac1917533 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -277,6 +277,9 @@ public: Q_INVOKABLE void setBlendshape(QString name, float val) { _headData->setBlendshape(name, val); } + Q_INVOKABLE QVariantList getAttachmentsVariant() const; + Q_INVOKABLE void setAttachmentsVariant(const QVariantList& variant); + void setForceFaceTrackerConnected(bool connected) { _forceFaceTrackerConnected = connected; } // key state @@ -448,6 +451,9 @@ public: QJsonObject toJson() const; void fromJson(const QJsonObject& json); + + QVariant toVariant() const; + void fromVariant(const QVariant& variant); }; QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index ff288fbe95..2cfef17c1b 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -252,6 +252,8 @@ public: void setLinePointsDirty() {_linePointsChanged = true; } + void setQueryAACubeDirty() { _queryAACubeChanged = true; } + void setCreated(QDateTime& v); bool hasTerseUpdateChanges() const; diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index 813386132c..3a72d98402 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -338,6 +338,7 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { _updateTimer.start(); _qmlComponent = new QQmlComponent(_qmlEngine); + _qmlEngine->rootContext()->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow())); } void OffscreenQmlSurface::resize(const QSize& newSize_) { diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 485100da0a..3ea3175390 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -959,7 +959,7 @@ void LimitedNodeList::putLocalPortIntoSharedMemory(const QString key, QObject* p bool LimitedNodeList::getLocalServerPortFromSharedMemory(const QString key, quint16& localPort) { QSharedMemory sharedMem(key); if (!sharedMem.attach(QSharedMemory::ReadOnly)) { - qWarning() << "Could not attach to shared memory at key" << key; + qCWarning(networking) << "Could not attach to shared memory at key" << key; return false; } else { sharedMem.lock(); diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 5cffd031d0..84d2282e7a 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -144,7 +144,12 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const { return MOTION_TYPE_STATIC; } assert(entityTreeIsLocked()); + if (_entity->getDynamic()) { + if (!_entity->getParentID().isNull()) { + // if something would have been dynamic but is a child of something else, force it to be kinematic, instead. + return MOTION_TYPE_KINEMATIC; + } return MOTION_TYPE_DYNAMIC; } return (_entity->isMoving() || _entity->hasActions()) ? MOTION_TYPE_KINEMATIC : MOTION_TYPE_STATIC; diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 7240fba105..3fd8e022f5 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -146,9 +146,9 @@ void RenderShadowTask::run(const SceneContextPointer& sceneContext, const render RenderArgs::RenderMode mode = args->_renderMode; auto nearClip = viewFrustum->getNearClip(); - const int SHADOW_NEAR_DEPTH = -2; + float nearDepth = -args->_boomOffset.z; const int SHADOW_FAR_DEPTH = 20; - globalLight->shadow.setKeylightFrustum(viewFrustum, nearClip + SHADOW_NEAR_DEPTH, nearClip + SHADOW_FAR_DEPTH); + globalLight->shadow.setKeylightFrustum(viewFrustum, nearDepth, nearClip + SHADOW_FAR_DEPTH); // Set the keylight render args args->_viewFrustum = globalLight->shadow.getFrustum().get(); diff --git a/libraries/shared/src/RenderArgs.h b/libraries/shared/src/RenderArgs.h index 7012c78c8f..f9fc6eb66b 100644 --- a/libraries/shared/src/RenderArgs.h +++ b/libraries/shared/src/RenderArgs.h @@ -105,7 +105,8 @@ public: std::shared_ptr _pipeline = nullptr; OctreeRenderer* _renderer = nullptr; ViewFrustum* _viewFrustum = nullptr; - glm::ivec4 _viewport{ 0, 0, 1, 1 }; + glm::ivec4 _viewport{ 0.0f, 0.0f, 1.0f, 1.0f }; + glm::vec3 _boomOffset{ 0.0f, 0.0f, 1.0f }; float _sizeScale = 1.0f; int _boundaryLevelAdjust = 0; RenderMode _renderMode = DEFAULT_RENDER_MODE; diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 8790c07f62..d7d28bef84 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -150,7 +150,9 @@ protected: } ~ModalDialogListener() { - disconnect(_dialog); + if (_dialog) { + disconnect(_dialog); + } } virtual QVariant waitForResult() { @@ -164,10 +166,11 @@ protected slots: void onDestroyed() { _finished = true; disconnect(_dialog); + _dialog = nullptr; } protected: - QQuickItem* const _dialog; + QQuickItem* _dialog; bool _finished { false }; QVariant _result; }; @@ -372,6 +375,7 @@ void OffscreenUi::createDesktop(const QUrl& url) { qDebug() << "Desktop already created"; return; } + #ifdef DEBUG getRootContext()->setContextProperty("DebugQML", QVariant(true)); #else @@ -382,9 +386,6 @@ void OffscreenUi::createDesktop(const QUrl& url) { Q_ASSERT(_desktop); getRootContext()->setContextProperty("desktop", _desktop); - // Enable focus debugging - _desktop->setProperty("offscreenWindow", QVariant::fromValue(getWindow())); - _toolWindow = _desktop->findChild("ToolWindow"); new VrMenu(this); diff --git a/libraries/ui/src/VrMenu.cpp b/libraries/ui/src/VrMenu.cpp index 103eb7660e..2006b22860 100644 --- a/libraries/ui/src/VrMenu.cpp +++ b/libraries/ui/src/VrMenu.cpp @@ -142,7 +142,6 @@ void VrMenu::addAction(QMenu* menu, QAction* action) { Q_ASSERT(menuQml); QQuickMenuItem* returnedValue { nullptr }; - qDebug() << menuQml; bool invokeResult = QMetaObject::invokeMethod(menuQml, "addItem", Qt::DirectConnection, Q_RETURN_ARG(QQuickMenuItem*, returnedValue), Q_ARG(QString, action->text())); diff --git a/tests/ui/qml/UI.js b/tests/ui/qml/UI.js deleted file mode 100644 index 0286ac56a6..0000000000 --- a/tests/ui/qml/UI.js +++ /dev/null @@ -1,45 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Quick Controls module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -.pragma library - -var margin = 2 -var tabPosition = Qt.TopEdge -var label = "" diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index c970cd711b..d20b580b5a 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -7,6 +7,7 @@ import "../../../interface/resources/qml" import "../../../interface/resources/qml/windows" import "../../../interface/resources/qml/dialogs" import "../../../interface/resources/qml/hifi" +import "../../../interface/resources/qml/hifi/dialogs" ApplicationWindow { id: appWindow @@ -196,4 +197,15 @@ ApplicationWindow { } */ } + + Action { + text: "Open Browser" + shortcut: "Ctrl+Shift+X" + onTriggered: { + builder.createObject(desktop); + } + property var builder: Component { + ModelBrowserDialog{} + } + } } diff --git a/tests/ui/qmlscratch.pro b/tests/ui/qmlscratch.pro index b3c8be0f22..7a1b5ab652 100644 --- a/tests/ui/qmlscratch.pro +++ b/tests/ui/qmlscratch.pro @@ -7,108 +7,24 @@ CONFIG += c++11 SOURCES += src/main.cpp \ ../../libraries/ui/src/FileDialogHelper.cpp -# Additional import path used to resolve QML modules in Qt Creator's code model -QML_IMPORT_PATH = - -DISTFILES += \ - qml/UI.js \ - qml/main.qml \ - qml/Palettes.qml \ - qml/StubMenu.qml \ - qml/Stubs.qml \ - qml/TestControllers.qml \ - qml/TestDialog.qml \ - qml/TestMenu.qml \ - qml/TestRoot.qml \ - qml/controlDemo/ButtonPage.qml \ - qml/controlDemo/InputPage.qml \ - qml/controlDemo/main.qml \ - qml/controlDemo/ProgressPage.qml \ - ../../interface/resources/qml/controller/hydra/HydraButtons.qml \ - ../../interface/resources/qml/controller/hydra/HydraStick.qml \ - ../../interface/resources/qml/controller/xbox/DPad.qml \ - ../../interface/resources/qml/controller/xbox/LeftAnalogStick.qml \ - ../../interface/resources/qml/controller/xbox/RightAnalogStick.qml \ - ../../interface/resources/qml/controller/xbox/XboxButtons.qml \ - ../../interface/resources/qml/controller/AnalogButton.qml \ - ../../interface/resources/qml/controller/AnalogStick.qml \ - ../../interface/resources/qml/controller/Hydra.qml \ - ../../interface/resources/qml/controller/Standard.qml \ - ../../interface/resources/qml/controller/ToggleButton.qml \ - ../../interface/resources/qml/controller/Xbox.qml \ - ../../interface/resources/qml/controls/Button.qml \ - ../../interface/resources/qml/controls/ButtonAwesome.qml \ - ../../interface/resources/qml/controls/CheckBox.qml \ - ../../interface/resources/qml/controls/FontAwesome.qml \ - ../../interface/resources/qml/controls/Player.qml \ - ../../interface/resources/qml/controls/Slider.qml \ - ../../interface/resources/qml/controls/Spacer.qml \ - ../../interface/resources/qml/controls/SpinBox.qml \ - ../../interface/resources/qml/controls/Text.qml \ - ../../interface/resources/qml/controls/TextAndSlider.qml \ - ../../interface/resources/qml/controls/TextAndSpinBox.qml \ - ../../interface/resources/qml/controls/TextArea.qml \ - ../../interface/resources/qml/controls/TextEdit.qml \ - ../../interface/resources/qml/controls/TextHeader.qml \ - ../../interface/resources/qml/controls/TextInput.qml \ - ../../interface/resources/qml/controls/TextInputAndButton.qml \ - ../../interface/resources/qml/controls/WebView.qml \ - ../../interface/resources/qml/dialogs/FileDialog.qml \ - ../../interface/resources/qml/dialogs/MessageDialog.qml \ - ../../interface/resources/qml/dialogs/RunningScripts.qml \ - ../../interface/resources/qml/hifi/MenuOption.qml \ - ../../interface/resources/qml/styles/Border.qml \ - ../../interface/resources/qml/styles/HifiConstants.qml \ - ../../interface/resources/qml/windows/DefaultFrame.qml \ - ../../interface/resources/qml/windows/Fadable.qml \ - ../../interface/resources/qml/windows/Frame.qml \ - ../../interface/resources/qml/windows/ModalFrame.qml \ - ../../interface/resources/qml/windows/Window.qml \ - ../../interface/resources/qml/AddressBarDialog.qml \ - ../../interface/resources/qml/AvatarInputs.qml \ - ../../interface/resources/qml/Browser.qml \ - ../../interface/resources/qml/InfoView.qml \ - ../../interface/resources/qml/LoginDialog.qml \ - ../../interface/resources/qml/QmlWebWindow.qml \ - ../../interface/resources/qml/QmlWindow.qml \ - ../../interface/resources/qml/Root.qml \ - ../../interface/resources/qml/ScrollingGraph.qml \ - ../../interface/resources/qml/Stats.qml \ - ../../interface/resources/qml/TextOverlayElement.qml \ - ../../interface/resources/qml/Tooltip.qml \ - ../../interface/resources/qml/ToolWindow.qml \ - ../../interface/resources/qml/UpdateDialog.qml \ - ../../interface/resources/qml/VrMenu.qml \ - ../../interface/resources/qml/VrMenuItem.qml \ - ../../interface/resources/qml/VrMenuView.qml \ - ../../interface/resources/qml/WebEntity.qml \ - ../../interface/resources/qml/desktop/Desktop.qml \ - ../../interface/resources/qml/hifi/Desktop.qml \ - ../../interface/resources/qml/menus/MenuMouseHandler.qml \ - ../../interface/resources/qml/menus/VrMenuItem.qml \ - ../../interface/resources/qml/menus/VrMenuView.qml \ - ../../interface/resources/qml/windows/ModalWindow.qml \ - ../../interface/resources/qml/desktop/FocusHack.qml \ - ../../interface/resources/qml/hifi/dialogs/PreferencesDialog.qml \ - ../../interface/resources/qml/hifi/dialogs/Section.qml \ - ../../interface/resources/qml/hifi/dialogs/preferences/Browsable.qml \ - ../../interface/resources/qml/hifi/dialogs/preferences/Section.qml \ - ../../interface/resources/qml/hifi/dialogs/preferences/Editable.qml \ - ../../interface/resources/qml/hifi/dialogs/preferences/Slider.qml \ - ../../interface/resources/qml/hifi/dialogs/preferences/Preference.qml \ - ../../interface/resources/qml/hifi/dialogs/preferences/CheckBox.qml \ - ../../interface/resources/qml/dialogs/fileDialog/FileTableView.qml \ - ../../interface/resources/qml/hifi/dialogs/preferences/Avatar.qml \ - ../../interface/resources/qml/hifi/dialogs/preferences/AvatarBrowser.qml \ - ../../interface/resources/qml/dialogs/QueryDialog.qml \ - ../../interface/resources/qml/hifi/dialogs/preferences/Button.qml \ - ../../interface/resources/qml/hifi/dialogs/preferences/AvatarPreference.qml \ - ../../interface/resources/qml/hifi/dialogs/preferences/BrowsablePreference.qml \ - ../../interface/resources/qml/hifi/dialogs/preferences/ButtonPreference.qml \ - ../../interface/resources/qml/hifi/dialogs/preferences/CheckBoxPreference.qml \ - ../../interface/resources/qml/hifi/dialogs/preferences/EditablePreference.qml \ - ../../interface/resources/qml/hifi/dialogs/preferences/SliderPreference.qml \ - ../../interface/resources/qml/hifi/dialogs/preferences/SpinBoxPreference.qml - HEADERS += \ ../../libraries/ui/src/FileDialogHelper.h + +# Additional import path used to resolve QML modules in Qt Creator's code model +QML_IMPORT_PATH = ../../interface/resources/qml + + +DISTFILES += \ + qml/*.qml \ + ../../interface/resources/qml/*.qml \ + ../../interface/resources/qml/controls/*.qml \ + ../../interface/resources/qml/dialogs/*.qml \ + ../../interface/resources/qml/dialogs/fileDialog/*.qml \ + ../../interface/resources/qml/desktop/*.qml \ + ../../interface/resources/qml/menus/*.qml \ + ../../interface/resources/qml/styles/*.qml \ + ../../interface/resources/qml/windows/*.qml \ + ../../interface/resources/qml/hifi/*.qml \ + ../../interface/resources/qml/hifi/dialogs/*.qml \ + ../../interface/resources/qml/hifi/dialogs/preferences/*.qml +