mirror of
https://github.com/AleziaKurdis/overte.git
synced 2025-04-17 14:58:25 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into orange
This commit is contained in:
commit
3ee6f9d6f2
62 changed files with 3422 additions and 1481 deletions
|
@ -153,7 +153,7 @@ bool OctreeQueryNode::updateCurrentViewFrustum() {
|
|||
newestViewFrustum.setPosition(getCameraPosition());
|
||||
newestViewFrustum.setOrientation(getCameraOrientation());
|
||||
|
||||
newestViewFrustum.setKeyholeRadius(getKeyholeRadius());
|
||||
newestViewFrustum.setCenterRadius(getCameraCenterRadius());
|
||||
|
||||
// Also make sure it's got the correct lens details from the camera
|
||||
float originalFOV = getCameraFov();
|
||||
|
|
|
@ -271,8 +271,9 @@ FunctionEnd
|
|||
|
||||
@CPACK_NSIS_PAGE_COMPONENTS@
|
||||
|
||||
Page custom PostInstallOptionsPage ReadPostInstallOptions
|
||||
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
Page custom PostInstallOptionsPage HandlePostInstallOptions
|
||||
|
||||
!insertmacro MUI_UNPAGE_CONFIRM
|
||||
!insertmacro MUI_UNPAGE_INSTFILES
|
||||
|
@ -341,6 +342,227 @@ FunctionEnd
|
|||
;Only for solid compression (by default, solid compression is enabled for BZIP2 and LZMA)
|
||||
|
||||
ReserveFile "@POST_INSTALL_OPTIONS_PATH@"
|
||||
; Make sure nsDialogs is included before we use it
|
||||
!include "nsdialogs.nsh"
|
||||
|
||||
;--------------------------------
|
||||
; Post Install Options
|
||||
|
||||
Var PostInstallDialog
|
||||
Var DesktopClientCheckbox
|
||||
Var DesktopServerCheckbox
|
||||
Var ServerStartupCheckbox
|
||||
Var LaunchNowCheckbox
|
||||
Var CurrentOffset
|
||||
Var OffsetUnits
|
||||
Var CopyFromProductionCheckbox
|
||||
|
||||
!macro SetPostInstallOption Checkbox OptionName Default
|
||||
; reads the value for the given post install option to the registry
|
||||
ReadRegStr $0 HKLM "@REGISTRY_HKLM_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\@POST_INSTALL_OPTIONS_REG_GROUP@" "${OptionName}"
|
||||
|
||||
${If} $0 == "NO"
|
||||
; the value in the registry says it should not be checked
|
||||
${NSD_SetState} ${Checkbox} ${BST_UNCHECKED}
|
||||
${ElseIf} $0 == "YES"
|
||||
; the value in the registry says it should be checked
|
||||
${NSD_SetState} ${Checkbox} ${BST_CHECKED}
|
||||
${Else}
|
||||
; the value in the registry was not in the expected format, use default
|
||||
${NSD_SetState} ${Checkbox} ${Default}
|
||||
${EndIf}
|
||||
!macroend
|
||||
|
||||
Function PostInstallOptionsPage
|
||||
!insertmacro MUI_HEADER_TEXT "Setup Options" ""
|
||||
|
||||
nsDialogs::Create 1018
|
||||
Pop $PostInstallDialog
|
||||
|
||||
${If} $PostInstallDialog == error
|
||||
Abort
|
||||
${EndIf}
|
||||
|
||||
StrCpy $CurrentOffset 0
|
||||
StrCpy $OffsetUnits u
|
||||
|
||||
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
|
||||
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @INTERFACE_SHORTCUT_NAME@"
|
||||
Pop $DesktopClientCheckbox
|
||||
IntOp $CurrentOffset $CurrentOffset + 15
|
||||
|
||||
; 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@}
|
||||
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for High Fidelity @CONSOLE_SHORTCUT_NAME@"
|
||||
Pop $DesktopServerCheckbox
|
||||
|
||||
; set the checkbox state depending on what is present in the registry
|
||||
!insertmacro SetPostInstallOption $DesktopServerCheckbox @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ ${BST_UNCHECKED}
|
||||
|
||||
IntOp $CurrentOffset $CurrentOffset + 15
|
||||
|
||||
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity @CONSOLE_SHORTCUT_NAME@ on startup"
|
||||
Pop $ServerStartupCheckbox
|
||||
|
||||
; set the checkbox state depending on what is present in the registry
|
||||
!insertmacro SetPostInstallOption $ServerStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_CHECKED}
|
||||
|
||||
IntOp $CurrentOffset $CurrentOffset + 15
|
||||
${EndIf}
|
||||
|
||||
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
|
||||
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity Server Console after install"
|
||||
${Else}
|
||||
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity after install"
|
||||
${EndIf}
|
||||
|
||||
Pop $LaunchNowCheckbox
|
||||
|
||||
; set the checkbox state depending on what is present in the registry
|
||||
!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
|
||||
|
||||
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Copy settings and content from production install"
|
||||
Pop $CopyFromProductionCheckbox
|
||||
|
||||
${NSD_SetState} $CopyFromProductionCheckbox ${BST_CHECKED}
|
||||
${EndIf}
|
||||
|
||||
nsDialogs::Show
|
||||
FunctionEnd
|
||||
|
||||
!macro WritePostInstallOption OptionName Option
|
||||
; writes the value for the given post install option to the registry
|
||||
WriteRegStr HKLM "@REGISTRY_HKLM_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\@POST_INSTALL_OPTIONS_REG_GROUP@" "${OptionName}" ${Option}
|
||||
!macroend
|
||||
|
||||
Var DesktopClientState
|
||||
Var DesktopServerState
|
||||
Var ServerStartupState
|
||||
Var LaunchNowState
|
||||
Var CopyFromProductionState
|
||||
|
||||
Function ReadPostInstallOptions
|
||||
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
|
||||
; check if the user asked for a desktop shortcut to High Fidelity
|
||||
${NSD_GetState} $DesktopClientCheckbox $DesktopClientState
|
||||
${EndIf}
|
||||
|
||||
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
|
||||
; check if the user asked for a desktop shortcut to Server Console
|
||||
${NSD_GetState} $DesktopServerCheckbox $DesktopServerState
|
||||
|
||||
; check if the user asked to have Server Console launched every startup
|
||||
${NSD_GetState} $ServerStartupCheckbox $ServerStartupState
|
||||
${EndIf}
|
||||
|
||||
${If} @PR_BUILD@ == 1
|
||||
; check if we need to copy settings/content from production for this PR build
|
||||
${NSD_GetState} $CopyFromProductionCheckbox $CopyFromProductionState
|
||||
${EndIf}
|
||||
|
||||
; check if we need to launch an application post-install
|
||||
${NSD_GetState} $LaunchNowCheckbox $LaunchNowState
|
||||
FunctionEnd
|
||||
|
||||
Function HandlePostInstallOptions
|
||||
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
|
||||
; check if the user asked for a desktop shortcut to High Fidelity
|
||||
${If} $DesktopClientState == ${BST_CHECKED}
|
||||
CreateShortCut "$DESKTOP\@INTERFACE_SHORTCUT_NAME@.lnk" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@"
|
||||
!insertmacro WritePostInstallOption "@CLIENT_DESKTOP_SHORTCUT_REG_KEY@" YES
|
||||
${Else}
|
||||
!insertmacro WritePostInstallOption @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ NO
|
||||
${EndIf}
|
||||
|
||||
${EndIf}
|
||||
|
||||
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
|
||||
; check if the user asked for a desktop shortcut to Server Console
|
||||
${If} $DesktopServerState == ${BST_CHECKED}
|
||||
CreateShortCut "$DESKTOP\@CONSOLE_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"
|
||||
!insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ YES
|
||||
${Else}
|
||||
!insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ NO
|
||||
${EndIf}
|
||||
|
||||
; check if the user asked to have Server Console launched every startup
|
||||
${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
|
||||
${EndIf}
|
||||
${EndIf}
|
||||
|
||||
${If} @PR_BUILD@ == 1
|
||||
|
||||
; check if we need to copy settings/content from production for this PR build
|
||||
${If} $CopyFromProductionState == ${BST_CHECKED}
|
||||
SetShellVarContext current
|
||||
|
||||
StrCpy $0 "$APPDATA\@BUILD_ORGANIZATION@"
|
||||
|
||||
; we need to copy whatever is in the data folder for production build to the data folder for this build
|
||||
CreateDirectory $0
|
||||
|
||||
ClearErrors
|
||||
|
||||
; copy the data from production build to this PR build
|
||||
CopyFiles "$APPDATA\High Fidelity\*" $0
|
||||
|
||||
; handle an error in copying files
|
||||
IfErrors 0 NoError
|
||||
|
||||
MessageBox mb_IconStop|mb_TopMost|mb_SetForeground \
|
||||
"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}
|
||||
|
||||
${If} $LaunchNowState == ${BST_CHECKED}
|
||||
!insertmacro WritePostInstallOption @LAUNCH_NOW_REG_KEY@ YES
|
||||
|
||||
; both launches use the explorer trick in case the user has elevated permissions for the installer
|
||||
; it won't be possible to use this approach if either application should be launched with a command line param
|
||||
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
|
||||
Exec '"$WINDIR\explorer.exe" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"'
|
||||
${Else}
|
||||
Exec '"$WINDIR\explorer.exe" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@"'
|
||||
${EndIf}
|
||||
${Else}
|
||||
!insertmacro WritePostInstallOption @LAUNCH_NOW_REG_KEY@ NO
|
||||
${EndIf}
|
||||
FunctionEnd
|
||||
|
||||
;--------------------------------
|
||||
;Installer Sections
|
||||
|
@ -469,233 +691,11 @@ Section "-Core installation"
|
|||
|
||||
@CPACK_NSIS_EXTRA_INSTALL_COMMANDS@
|
||||
|
||||
; Handle whichever post install options were set
|
||||
Call HandlePostInstallOptions
|
||||
|
||||
SectionEnd
|
||||
|
||||
; Make sure nsDialogs is included before we use it
|
||||
!include "nsdialogs.nsh"
|
||||
|
||||
Var PostInstallDialog
|
||||
Var OptionsLabel
|
||||
Var DesktopClientCheckbox
|
||||
Var DesktopServerCheckbox
|
||||
Var ServerStartupCheckbox
|
||||
Var LaunchNowCheckbox
|
||||
Var CurrentOffset
|
||||
Var OffsetUnits
|
||||
Var CopyFromProductionCheckbox
|
||||
|
||||
!macro SetPostInstallOption Checkbox OptionName Default
|
||||
; reads the value for the given post install option to the registry
|
||||
ReadRegStr $0 HKLM "@REGISTRY_HKLM_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\@POST_INSTALL_OPTIONS_REG_GROUP@" "${OptionName}"
|
||||
|
||||
${If} $0 == "NO"
|
||||
; the value in the registry says it should not be checked
|
||||
${NSD_SetState} ${Checkbox} ${BST_UNCHECKED}
|
||||
${ElseIf} $0 == "YES"
|
||||
; the value in the registry says it should be checked
|
||||
${NSD_SetState} ${Checkbox} ${BST_CHECKED}
|
||||
${Else}
|
||||
; the value in the registry was not in the expected format, use default
|
||||
${NSD_SetState} ${Checkbox} ${Default}
|
||||
${EndIf}
|
||||
!macroend
|
||||
|
||||
Function PostInstallOptionsPage
|
||||
; Set the text on the dialog button to match finish, hide the back and cancel buttons
|
||||
GetDlgItem $R1 $hwndparent 1
|
||||
SendMessage $R1 ${WM_SETTEXT} 0 "STR:&Finish"
|
||||
|
||||
GetDlgItem $R3 $hwndparent 3
|
||||
ShowWindow $R3 0
|
||||
|
||||
nsDialogs::Create 1018
|
||||
Pop $PostInstallDialog
|
||||
|
||||
${If} $PostInstallDialog == error
|
||||
Abort
|
||||
${EndIf}
|
||||
|
||||
${NSD_CreateLabel} 0 0 100% 12u "Setup Options"
|
||||
Pop $OptionsLabel
|
||||
|
||||
; Set label to bold
|
||||
CreateFont $R2 "Arial" 10 700
|
||||
SendMessage $OptionsLabel ${WM_SETFONT} $R2 0
|
||||
|
||||
; Force label redraw
|
||||
ShowWindow $OptionsLabel ${SW_HIDE}
|
||||
ShowWindow $OptionsLabel ${SW_SHOW}
|
||||
|
||||
StrCpy $CurrentOffset 15
|
||||
StrCpy $OffsetUnits u
|
||||
|
||||
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
|
||||
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @INTERFACE_SHORTCUT_NAME@"
|
||||
Pop $DesktopClientCheckbox
|
||||
IntOp $CurrentOffset $CurrentOffset + 15
|
||||
|
||||
; 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@}
|
||||
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for High Fidelity @CONSOLE_SHORTCUT_NAME@"
|
||||
Pop $DesktopServerCheckbox
|
||||
|
||||
; set the checkbox state depending on what is present in the registry
|
||||
!insertmacro SetPostInstallOption $DesktopServerCheckbox @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ ${BST_UNCHECKED}
|
||||
|
||||
IntOp $CurrentOffset $CurrentOffset + 15
|
||||
|
||||
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity @CONSOLE_SHORTCUT_NAME@ on startup"
|
||||
Pop $ServerStartupCheckbox
|
||||
|
||||
; set the checkbox state depending on what is present in the registry
|
||||
!insertmacro SetPostInstallOption $ServerStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_CHECKED}
|
||||
|
||||
IntOp $CurrentOffset $CurrentOffset + 15
|
||||
${EndIf}
|
||||
|
||||
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
|
||||
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity Server Console Now"
|
||||
${Else}
|
||||
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity Now"
|
||||
${EndIf}
|
||||
|
||||
Pop $LaunchNowCheckbox
|
||||
|
||||
; set the checkbox state depending on what is present in the registry
|
||||
!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
|
||||
|
||||
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Copy settings and content from production install"
|
||||
Pop $CopyFromProductionCheckbox
|
||||
|
||||
${NSD_SetState} $CopyFromProductionCheckbox ${BST_CHECKED}
|
||||
${EndIf}
|
||||
|
||||
nsDialogs::Show
|
||||
FunctionEnd
|
||||
|
||||
!macro WritePostInstallOption OptionName Option
|
||||
; writes the value for the given post install option to the registry
|
||||
WriteRegStr HKLM "@REGISTRY_HKLM_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\@POST_INSTALL_OPTIONS_REG_GROUP@" "${OptionName}" ${Option}
|
||||
!macroend
|
||||
|
||||
Var DesktopClientState
|
||||
Var DesktopServerState
|
||||
Var ServerStartupState
|
||||
Var LaunchNowState
|
||||
Var CopyFromProductionState
|
||||
|
||||
Function HandlePostInstallOptions
|
||||
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
|
||||
; check if the user asked for a desktop shortcut to High Fidelity
|
||||
${NSD_GetState} $DesktopClientCheckbox $DesktopClientState
|
||||
|
||||
${If} $DesktopClientState == ${BST_CHECKED}
|
||||
CreateShortCut "$DESKTOP\@INTERFACE_SHORTCUT_NAME@.lnk" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@"
|
||||
!insertmacro WritePostInstallOption "@CLIENT_DESKTOP_SHORTCUT_REG_KEY@" YES
|
||||
${Else}
|
||||
!insertmacro WritePostInstallOption @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ NO
|
||||
${EndIf}
|
||||
|
||||
${EndIf}
|
||||
|
||||
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
|
||||
; check if the user asked for a desktop shortcut to Server Console
|
||||
${NSD_GetState} $DesktopServerCheckbox $DesktopServerState
|
||||
|
||||
${If} $DesktopServerState == ${BST_CHECKED}
|
||||
CreateShortCut "$DESKTOP\@CONSOLE_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"
|
||||
!insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ YES
|
||||
${Else}
|
||||
!insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ NO
|
||||
${EndIf}
|
||||
|
||||
; check if the user asked to have Server Console launched every startup
|
||||
${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
|
||||
${EndIf}
|
||||
${EndIf}
|
||||
|
||||
${If} @PR_BUILD@ == 1
|
||||
|
||||
; check if we need to copy settings/content from production for this PR build
|
||||
${NSD_GetState} $CopyFromProductionCheckbox $CopyFromProductionState
|
||||
|
||||
${If} $CopyFromProductionState == ${BST_CHECKED}
|
||||
SetShellVarContext current
|
||||
|
||||
StrCpy $0 "$APPDATA\@BUILD_ORGANIZATION@"
|
||||
|
||||
; we need to copy whatever is in the data folder for production build to the data folder for this build
|
||||
CreateDirectory $0
|
||||
|
||||
ClearErrors
|
||||
|
||||
; copy the data from production build to this PR build
|
||||
CopyFiles "$APPDATA\High Fidelity\*" $0
|
||||
|
||||
; handle an error in copying files
|
||||
IfErrors 0 NoError
|
||||
|
||||
MessageBox mb_IconStop|mb_TopMost|mb_SetForeground \
|
||||
"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}
|
||||
|
||||
; check if we need to launch an application post-install
|
||||
${NSD_GetState} $LaunchNowCheckbox $LaunchNowState
|
||||
|
||||
${If} $LaunchNowState == ${BST_CHECKED}
|
||||
!insertmacro WritePostInstallOption @LAUNCH_NOW_REG_KEY@ YES
|
||||
|
||||
; both launches use the explorer trick in case the user has elevated permissions for the installer
|
||||
; it won't be possible to use this approach if either application should be launched with a command line param
|
||||
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
|
||||
Exec '"$WINDIR\explorer.exe" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"'
|
||||
${Else}
|
||||
Exec '"$WINDIR\explorer.exe" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@"'
|
||||
${EndIf}
|
||||
${Else}
|
||||
!insertmacro WritePostInstallOption @LAUNCH_NOW_REG_KEY@ NO
|
||||
${EndIf}
|
||||
|
||||
FunctionEnd
|
||||
|
||||
!include nsProcess.nsh
|
||||
|
||||
!macro PromptForRunningApplication applicationName displayName action prompter
|
||||
|
|
|
@ -95,9 +95,9 @@ EntityViewer.setPosition({
|
|||
y: 0,
|
||||
z: 0
|
||||
});
|
||||
EntityViewer.setKeyholeRadius(60000);
|
||||
EntityViewer.setCenterRadius(60000);
|
||||
var octreeQueryInterval = Script.setInterval(function() {
|
||||
EntityViewer.queryOctree();
|
||||
}, 1000);
|
||||
|
||||
Script.update.connect(update);
|
||||
Script.update.connect(update);
|
||||
|
|
|
@ -1,218 +1,260 @@
|
|||
"use strict";
|
||||
/*jslint nomen: true, plusplus: true, vars: true*/
|
||||
/*global AvatarList, Entities, EntityViewer, Script, SoundCache, Audio, print, randFloat*/
|
||||
//
|
||||
// ACAudioSearchAndInject.js
|
||||
// audio
|
||||
//
|
||||
// Created by Eric Levin 2/1/2016
|
||||
// Created by Eric Levin and Howard Stearns 2/1/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
|
||||
// This AC script searches for special sound entities nearby avatars and plays those sounds based off information specified in the entity's
|
||||
// user data field ( see acAudioSearchAndCompatibilityEntitySpawner.js for an example)
|
||||
//
|
||||
// Keeps track of all sounds within QUERY_RADIUS of an avatar, where a "sound" is specified in entity userData.
|
||||
// Inject as many as practical into the audio mixer.
|
||||
// See acAudioSearchAndCompatibilityEntitySpawner.js.
|
||||
//
|
||||
// This implementation takes some precautions to scale well:
|
||||
// - It doesn't hastle the entity server because it issues at most one octree query every UPDATE_TIME period, regardless of the number of avatars.
|
||||
// - It does not load itself because it only gathers entities once every UPDATE_TIME period, and only
|
||||
// checks entity properties for those small number of entities that are currently playing (plus a RECHECK_TIME period examination of all entities).
|
||||
// This implementation tries to use all the available injectors.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
Script.include("https://rawgit.com/highfidelity/hifi/master/examples/libraries/utils.js");
|
||||
|
||||
var SOUND_DATA_KEY = "soundKey";
|
||||
|
||||
var QUERY_RADIUS = 50;
|
||||
|
||||
EntityViewer.setKeyholeRadius(QUERY_RADIUS);
|
||||
Entities.setPacketsPerSecond(6000);
|
||||
|
||||
Agent.isAvatar = true;
|
||||
var MSEC_PER_SEC = 1000;
|
||||
var SOUND_DATA_KEY = "io.highfidelity.soundKey"; // Sound data is specified in userData under this key.
|
||||
var old_sound_data_key = "soundKey"; // For backwards compatibility.
|
||||
var QUERY_RADIUS = 50; // meters
|
||||
var UPDATE_TIME = 100; // ms. We'll update just one thing on this period.
|
||||
var EXPIRATION_TIME = 5 * MSEC_PER_SEC; // ms. Remove sounds that have been out of range for this time.
|
||||
var RECHECK_TIME = 10 * MSEC_PER_SEC; // ms. Check for new userData properties this often when not currently playing.
|
||||
// (By not checking most of the time when not playing, we can efficiently go through all entities without getEntityProperties.)
|
||||
var UPDATES_PER_STATS_LOG = RECHECK_TIME / UPDATE_TIME; // (It's nice to smooth out the results by straddling a recheck.)
|
||||
|
||||
var DEFAULT_SOUND_DATA = {
|
||||
volume: 0.5,
|
||||
loop: false,
|
||||
playbackGap: 1000, // in ms
|
||||
volume: 0.5, // userData cannot specify zero volume with our current method of defaulting.
|
||||
loop: false, // Default must be false with our current method of defaulting, else there's no way to get a false value.
|
||||
playbackGap: MSEC_PER_SEC, // in ms
|
||||
playbackGapRange: 0 // in ms
|
||||
};
|
||||
var MIN_PLAYBACK_GAP = 0;
|
||||
|
||||
var UPDATE_TIME = 100;
|
||||
var EXPIRATION_TIME = 5000;
|
||||
Script.include("../../libraries/utils.js");
|
||||
Agent.isAvatar = true; // This puts a robot at 0,0,0, but is currently necessary in order to use AvatarList.
|
||||
function ignore() {}
|
||||
function debug() { // Display the arguments not just [Object object].
|
||||
//print.apply(null, [].map.call(arguments, JSON.stringify));
|
||||
}
|
||||
|
||||
var soundEntityMap = {};
|
||||
var soundUrls = {};
|
||||
EntityViewer.setCenterRadius(QUERY_RADIUS);
|
||||
|
||||
var avatarPositions = [];
|
||||
|
||||
|
||||
function update() {
|
||||
var avatars = AvatarList.getAvatarIdentifiers();
|
||||
for (var i = 0; i < avatars.length; i++) {
|
||||
var avatar = AvatarList.getAvatar(avatars[i]);
|
||||
var avatarPosition = avatar.position;
|
||||
if (!avatarPosition) {
|
||||
continue;
|
||||
// ENTITY DATA CACHE
|
||||
//
|
||||
var entityCache = {}; // A dictionary of unexpired EntityData objects.
|
||||
var examinationCount = 0;
|
||||
function EntityDatum(entityIdentifier) { // Just the data of an entity that we need to know about.
|
||||
// This data is only use for our sound injection. There is no need to store such info in the replicated entity on everyone's computer.
|
||||
var that = this;
|
||||
that.lastUserDataUpdate = 0; // new entity is in need of rechecking user data
|
||||
// State Transitions:
|
||||
// no data => no data | sound data | expired
|
||||
// expired => stop => remove
|
||||
// sound data => downloading
|
||||
// downloading => downloading | waiting
|
||||
// waiting => playing | waiting (if too many already playing)
|
||||
// playing => update position etc | no data
|
||||
that.stop = function stop() {
|
||||
if (!that.sound) {
|
||||
return;
|
||||
}
|
||||
EntityViewer.setPosition(avatarPosition);
|
||||
EntityViewer.queryOctree();
|
||||
avatarPositions.push(avatarPosition);
|
||||
print("stopping sound", entityIdentifier, that.url);
|
||||
delete that.sound;
|
||||
delete that.url;
|
||||
if (!that.injector) {
|
||||
return;
|
||||
}
|
||||
that.injector.stop();
|
||||
delete that.injector;
|
||||
};
|
||||
this.update = function stateTransitions(expirationCutoff, userDataCutoff, now) {
|
||||
if (that.timestamp < expirationCutoff) { // EXPIRED => STOP => REMOVE
|
||||
that.stop(); // Alternatively, we could fade out and then stop...
|
||||
delete entityCache[entityIdentifier];
|
||||
return;
|
||||
}
|
||||
var properties, soundData; // Latest data, pulled from local octree.
|
||||
// getEntityProperties locks the tree, which competes with the asynchronous processing of queryOctree results.
|
||||
// Most entity updates are fast and only a very few do getEntityProperties.
|
||||
function ensureSoundData() { // We only getEntityProperities when we need to.
|
||||
if (properties) {
|
||||
return;
|
||||
}
|
||||
properties = Entities.getEntityProperties(entityIdentifier, ['userData', 'position']);
|
||||
examinationCount++; // Collect statistics on how many getEntityProperties we do.
|
||||
debug("updating", that, properties);
|
||||
try {
|
||||
var userData = properties.userData && JSON.parse(properties.userData);
|
||||
soundData = userData && (userData[SOUND_DATA_KEY] || userData[old_sound_data_key]); // Don't store soundData yet. Let state changes compare.
|
||||
that.lastUserDataUpdate = now; // But do update these ...
|
||||
that.url = soundData && soundData.url;
|
||||
that.playAfter = that.url && now;
|
||||
} catch (err) {
|
||||
print(err, properties.userData);
|
||||
}
|
||||
}
|
||||
// Stumbling on big new pile of entities will do a lot of getEntityProperties. Once.
|
||||
if (that.lastUserDataUpdate < userDataCutoff) { // NO DATA => SOUND DATA
|
||||
ensureSoundData();
|
||||
}
|
||||
if (!that.url) { // NO DATA => NO DATA
|
||||
return that.stop();
|
||||
}
|
||||
if (!that.sound) { // SOUND DATA => DOWNLOADING
|
||||
that.sound = SoundCache.getSound(soundData.url); // SoundCache can manage duplicates better than we can.
|
||||
}
|
||||
if (!that.sound.downloaded) { // DOWNLOADING => DOWNLOADING
|
||||
return;
|
||||
}
|
||||
if (that.playAfter > now) { // DOWNLOADING | WAITING => WAITING
|
||||
return;
|
||||
}
|
||||
ensureSoundData(); // We'll try to play/setOptions and will need position, so we might as well get soundData, too.
|
||||
if (soundData.url !== that.url) { // WAITING => NO DATA (update next time around)
|
||||
return that.stop();
|
||||
}
|
||||
var options = {
|
||||
position: properties.position,
|
||||
loop: soundData.loop || DEFAULT_SOUND_DATA.loop,
|
||||
volume: soundData.volume || DEFAULT_SOUND_DATA.volume
|
||||
};
|
||||
function repeat() {
|
||||
return !options.loop && (soundData.playbackGap >= 0);
|
||||
}
|
||||
function randomizedNextPlay() { // time of next play or recheck, randomized to distribute the work
|
||||
var range = soundData.playbackGapRange || DEFAULT_SOUND_DATA.playbackGapRange,
|
||||
base = repeat() ? ((that.sound.duration * MSEC_PER_SEC) + (soundData.playbackGap || DEFAULT_SOUND_DATA.playbackGap)) : RECHECK_TIME;
|
||||
return now + base + randFloat(-Math.min(base, range), range);
|
||||
}
|
||||
if (!that.injector) { // WAITING => PLAYING | WAITING
|
||||
debug("starting", that, options);
|
||||
that.injector = Audio.playSound(that.sound, options); // Might be null if at at injector limit. Will try again later.
|
||||
if (that.injector) {
|
||||
print("started", entityIdentifier, that.url);
|
||||
} else { // Don't hammer ensureSoundData or injector manager.
|
||||
that.playAfter = randomizedNextPlay();
|
||||
}
|
||||
return;
|
||||
}
|
||||
that.injector.setOptions(options); // PLAYING => UPDATE POSITION ETC
|
||||
if (!that.injector.isPlaying) { // Subtle: a looping sound will not check playbackGap.
|
||||
if (repeat()) { // WAITING => PLAYING
|
||||
// Setup next play just once, now. Changes won't be looked at while we wait.
|
||||
that.playAfter = randomizedNextPlay();
|
||||
// Subtle: if the restart fails b/c we're at injector limit, we won't try again until next playAfter.
|
||||
that.injector.restart();
|
||||
} else { // PLAYING => NO DATA
|
||||
that.playAfter = Infinity; // was one-shot and we're finished
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
function internEntityDatum(entityIdentifier, timestamp, avatarPosition, avatar) {
|
||||
ignore(avatarPosition, avatar); // We could use avatars and/or avatarPositions to prioritize which ones to play.
|
||||
var entitySound = entityCache[entityIdentifier];
|
||||
if (!entitySound) {
|
||||
entitySound = entityCache[entityIdentifier] = new EntityDatum(entityIdentifier);
|
||||
}
|
||||
Script.setTimeout(function() {
|
||||
avatarPositions.forEach(function(avatarPosition) {
|
||||
var entities = Entities.findEntities(avatarPosition, QUERY_RADIUS);
|
||||
handleFoundSoundEntities(entities);
|
||||
entitySound.timestamp = timestamp; // Might be updated for multiple avatars. That's fine.
|
||||
}
|
||||
var nUpdates = UPDATES_PER_STATS_LOG, lastStats = Date.now();
|
||||
function updateAllEntityData() { // A fast update of all entities we know about. A few make sounds.
|
||||
var now = Date.now(),
|
||||
expirationCutoff = now - EXPIRATION_TIME,
|
||||
userDataRecheckCutoff = now - RECHECK_TIME;
|
||||
Object.keys(entityCache).forEach(function (entityIdentifier) {
|
||||
entityCache[entityIdentifier].update(expirationCutoff, userDataRecheckCutoff, now);
|
||||
});
|
||||
if (nUpdates-- <= 0) { // Report statistics.
|
||||
// For example, with:
|
||||
// injector-limit = 40 (in C++ code)
|
||||
// N_SOUNDS = 1000 (from userData in, e.g., acAudioSearchCompatibleEntitySpawner.js)
|
||||
// replay-period = 3 + 20 = 23 (seconds, ditto)
|
||||
// stats-period = UPDATES_PER_STATS_LOG * UPDATE_TIME / MSEC_PER_SEC = 10 seconds
|
||||
// The log should show between each stats report:
|
||||
// "start" lines ~= injector-limit * P(finish) = injector-limit * stats-period/replay-period = 17 ?
|
||||
// total attempts at starting ("start" lines + "could not thread" lines) ~= N_SOUNDS = 1000 ?
|
||||
// entities > N_SOUNDS * (1+ N_SILENT_ENTITIES_PER_SOUND) = 11000 + whatever was in the scene before running spawner
|
||||
// sounds = N_SOUNDS = 1000
|
||||
// getEntityPropertiesPerUpdate ~= playing + failed-starts/UPDATES_PER_STATS_LOG + other-rechecks-each-update
|
||||
// = injector-limit + (total attempts - "start" lines)/UPDATES_PER_STATS__LOG
|
||||
// + (entities - playing - failed-starts/UPDATES_PER_STATS_LOG) * P(recheck-in-update)
|
||||
// where failed-starts/UPDATES_PER_STATS_LOG = (1000-17)/100 = 10
|
||||
// = 40 + 10 + (11000 - 40 - 10)*UPDATE_TIME/RECHECK_TIME
|
||||
// = 40 + 10 + 10950*0.01 = 159 (mostly proportional to enties/RECHECK_TIME)
|
||||
// millisecondsPerUpdate ~= UPDATE_TIME = 100 (+ some timer machinery time)
|
||||
// this assignment client activity monitor < 100% cpu
|
||||
var stats = {
|
||||
entities: 0,
|
||||
sounds: 0,
|
||||
playing: 0,
|
||||
getEntityPropertiesPerUpdate: examinationCount / UPDATES_PER_STATS_LOG,
|
||||
millisecondsPerUpdate: (now - lastStats) / UPDATES_PER_STATS_LOG
|
||||
};
|
||||
nUpdates = UPDATES_PER_STATS_LOG;
|
||||
lastStats = now;
|
||||
examinationCount = 0;
|
||||
Object.keys(entityCache).forEach(function (entityIdentifier) {
|
||||
var datum = entityCache[entityIdentifier];
|
||||
stats.entities++;
|
||||
if (datum.url) {
|
||||
stats.sounds++;
|
||||
if (datum.injector && datum.injector.isPlaying) {
|
||||
stats.playing++;
|
||||
}
|
||||
}
|
||||
});
|
||||
//Now wipe list for next query;
|
||||
avatarPositions = [];
|
||||
}, UPDATE_TIME);
|
||||
handleActiveSoundEntities();
|
||||
}
|
||||
|
||||
function handleActiveSoundEntities() {
|
||||
// Go through all our sound entities, if they have passed expiration time, remove them from map
|
||||
for (var potentialSoundEntity in soundEntityMap) {
|
||||
if (!soundEntityMap.hasOwnProperty(potentialSoundEntity)) {
|
||||
// The current property is not a direct property of soundEntityMap so ignore it
|
||||
continue;
|
||||
}
|
||||
var soundEntity = potentialSoundEntity;
|
||||
var soundProperties = soundEntityMap[soundEntity];
|
||||
soundProperties.timeWithoutAvatarInRange += UPDATE_TIME;
|
||||
if (soundProperties.timeWithoutAvatarInRange > EXPIRATION_TIME && soundProperties.soundInjector) {
|
||||
// An avatar hasn't been within range of this sound entity recently, so remove it from map
|
||||
soundProperties.soundInjector.stop();
|
||||
delete soundEntityMap[soundEntity];
|
||||
} else if (soundProperties.isDownloaded) {
|
||||
// If this sound hasn't expired yet, we want to potentially play it!
|
||||
if (soundProperties.readyToPlay) {
|
||||
var newPosition = Entities.getEntityProperties(soundEntity, "position").position;
|
||||
if (!soundProperties.soundInjector) {
|
||||
soundProperties.soundInjector = Audio.playSound(soundProperties.sound, {
|
||||
volume: soundProperties.volume,
|
||||
position: newPosition,
|
||||
loop: soundProperties.loop
|
||||
});
|
||||
} else {
|
||||
soundProperties.soundInjector.restart();
|
||||
}
|
||||
soundProperties.readyToPlay = false;
|
||||
} else if (soundProperties.sound && soundProperties.loop === false) {
|
||||
// We need to check all of our entities that are not looping but have an interval associated with them
|
||||
// to see if it's time for them to play again
|
||||
soundProperties.timeSinceLastPlay += UPDATE_TIME;
|
||||
if (soundProperties.timeSinceLastPlay > soundProperties.clipDuration + soundProperties.currentPlaybackGap) {
|
||||
soundProperties.readyToPlay = true;
|
||||
soundProperties.timeSinceLastPlay = 0;
|
||||
// Now let's get our new current interval
|
||||
soundProperties.currentPlaybackGap = soundProperties.playbackGap + randFloat(-soundProperties.playbackGapRange, soundProperties.playbackGapRange);
|
||||
soundProperties.currentPlaybackGap = Math.max(MIN_PLAYBACK_GAP, soundProperties.currentPlaybackGap);
|
||||
}
|
||||
}
|
||||
}
|
||||
print(JSON.stringify(stats));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleFoundSoundEntities(entities) {
|
||||
entities.forEach(function(entity) {
|
||||
var soundData = getEntityCustomData(SOUND_DATA_KEY, entity);
|
||||
if (soundData && soundData.url) {
|
||||
//check sound entities list- if it's not in, add it
|
||||
if (!soundEntityMap[entity]) {
|
||||
var soundProperties = {
|
||||
url: soundData.url,
|
||||
volume: soundData.volume || DEFAULT_SOUND_DATA.volume,
|
||||
loop: soundData.loop || DEFAULT_SOUND_DATA.loop,
|
||||
playbackGap: soundData.playbackGap || DEFAULT_SOUND_DATA.playbackGap,
|
||||
playbackGapRange: soundData.playbackGapRange || DEFAULT_SOUND_DATA.playbackGapRange,
|
||||
readyToPlay: false,
|
||||
position: Entities.getEntityProperties(entity, "position").position,
|
||||
timeSinceLastPlay: 0,
|
||||
timeWithoutAvatarInRange: 0,
|
||||
isDownloaded: false
|
||||
};
|
||||
|
||||
|
||||
soundProperties.currentPlaybackGap = soundProperties.playbackGap + randFloat(-soundProperties.playbackGapRange, soundProperties.playbackGapRange);
|
||||
soundProperties.currentPlaybackGap = Math.max(MIN_PLAYBACK_GAP, soundProperties.currentPlaybackGap);
|
||||
|
||||
|
||||
soundEntityMap[entity] = soundProperties;
|
||||
if (!soundUrls[soundData.url]) {
|
||||
// We need to download sound before we add it to our map
|
||||
var sound = SoundCache.getSound(soundData.url);
|
||||
// Only add it to map once it's downloaded
|
||||
soundUrls[soundData.url] = sound;
|
||||
sound.ready.connect(function() {
|
||||
soundProperties.sound = sound;
|
||||
soundProperties.readyToPlay = true;
|
||||
soundProperties.isDownloaded = true;
|
||||
soundProperties.clipDuration = sound.duration * 1000;
|
||||
soundEntityMap[entity] = soundProperties;
|
||||
|
||||
});
|
||||
} else {
|
||||
// We already have sound downloaded, so just add it to map right away
|
||||
soundProperties.sound = soundUrls[soundData.url];
|
||||
soundProperties.clipDuration = soundProperties.sound.duration * 1000;
|
||||
soundProperties.readyToPlay = true;
|
||||
soundProperties.isDownloaded = true;
|
||||
soundEntityMap[entity] = soundProperties;
|
||||
}
|
||||
} else {
|
||||
//If this sound is in our map already, we want to reset timeWithoutAvatarInRange
|
||||
// Also we want to check to see if the entity has been updated with new sound data- if so we want to update!
|
||||
soundEntityMap[entity].timeWithoutAvatarInRange = 0;
|
||||
checkForSoundPropertyChanges(soundEntityMap[entity], soundData);
|
||||
}
|
||||
}
|
||||
// Update the set of which EntityData we know about.
|
||||
//
|
||||
function updateEntiesForAvatar(avatarIdentifier) { // Just one piece of update work.
|
||||
// This does at most:
|
||||
// one queryOctree request of the entity server, and
|
||||
// one findEntities geometry query of our own octree, and
|
||||
// a quick internEntityDatum of each of what may be a large number of entityIdentifiers.
|
||||
// The idea is that this is a nice bounded piece of work that should not be done too frequently.
|
||||
// However, it means that we won't learn about new entities until, on average (nAvatars * UPDATE_TIME) + query round trip.
|
||||
var avatar = AvatarList.getAvatar(avatarIdentifier), avatarPosition = avatar && avatar.position;
|
||||
if (!avatarPosition) { // No longer here.
|
||||
return;
|
||||
}
|
||||
var timestamp = Date.now();
|
||||
EntityViewer.setPosition(avatarPosition);
|
||||
EntityViewer.queryOctree(); // Requests an update, but there's no telling when we'll actually see different results.
|
||||
var entities = Entities.findEntities(avatarPosition, QUERY_RADIUS);
|
||||
debug("found", entities.length, "entities near", avatar.name || "unknown", "at", avatarPosition);
|
||||
entities.forEach(function (entityIdentifier) {
|
||||
internEntityDatum(entityIdentifier, timestamp, avatarPosition, avatar);
|
||||
});
|
||||
}
|
||||
|
||||
function checkForSoundPropertyChanges(currentProps, newProps) {
|
||||
var needsNewInjector = false;
|
||||
|
||||
if (currentProps.playbackGap !== newProps.playbackGap && !currentProps.loop) {
|
||||
// playbackGap only applies to non looping sounds
|
||||
currentProps.playbackGap = newProps.playbackGap;
|
||||
currentProps.currentPlaybackGap = currentProps.playbackGap + randFloat(-currentProps.playbackGapRange, currentProps.playbackGapRange);
|
||||
currentProps.currentPlaybackGap = Math.max(MIN_PLAYBACK_GAP, currentProps.currentPlaybackGap);
|
||||
currentProps.readyToPlay = true;
|
||||
}
|
||||
|
||||
if (currentProps.playbackGapRange !== currentProps.playbackGapRange) {
|
||||
currentProps.playbackGapRange = newProps.playbackGapRange;
|
||||
currentProps.currentPlaybackGap = currentProps.playbackGap + randFloat(-currentProps.playbackGapRange, currentProps.playbackGapRange);
|
||||
currentProps.currentPlaybackGap = Math.max(MIN_PLAYBACK_GAP, currentProps.currentPlaybackGap);
|
||||
currentProps.readyToPlay = true;
|
||||
}
|
||||
if (currentProps.volume !== newProps.volume) {
|
||||
currentProps.volume = newProps.volume;
|
||||
needsNewInjector = true;
|
||||
}
|
||||
if (currentProps.url !== newProps.url) {
|
||||
currentProps.url = newProps.url;
|
||||
currentProps.sound = null;
|
||||
if (!soundUrls[currentProps.url]) {
|
||||
var sound = SoundCache.getSound(currentProps.url);
|
||||
currentProps.isDownloaded = false;
|
||||
sound.ready.connect(function() {
|
||||
currentProps.sound = sound;
|
||||
currentProps.clipDuration = sound.duration * 1000;
|
||||
currentProps.isDownloaded = true;
|
||||
});
|
||||
} else {
|
||||
currentProps.sound = sound;
|
||||
currentProps.clipDuration = sound.duration * 1000;
|
||||
}
|
||||
needsNewInjector = true;
|
||||
}
|
||||
|
||||
if (currentProps.loop !== newProps.loop) {
|
||||
currentProps.loop = newProps.loop;
|
||||
needsNewInjector = true;
|
||||
}
|
||||
if (needsNewInjector) {
|
||||
// If we were looping we need to stop that so new changes are applied
|
||||
currentProps.soundInjector.stop();
|
||||
currentProps.soundInjector = null;
|
||||
currentProps.readyToPlay = true;
|
||||
}
|
||||
|
||||
// Slowly update the set of data we have to work with.
|
||||
//
|
||||
var workQueue = [];
|
||||
function updateWorkQueueForAvatarsPresent() { // when nothing else to do, fill queue with individual avatar updates
|
||||
workQueue = AvatarList.getAvatarIdentifiers().map(function (avatarIdentifier) {
|
||||
return function () {
|
||||
updateEntiesForAvatar(avatarIdentifier);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
Script.setInterval(update, UPDATE_TIME);
|
||||
Script.setInterval(function () {
|
||||
// There might be thousands of EntityData known to us, but only a few will require any work to update.
|
||||
updateAllEntityData(); // i.e., this better be pretty fast.
|
||||
// Each interval, we do no more than one updateEntitiesforAvatar.
|
||||
if (!workQueue.length) {
|
||||
workQueue = [updateWorkQueueForAvatarsPresent];
|
||||
}
|
||||
workQueue.pop()(); // There's always one
|
||||
}, UPDATE_TIME);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
//
|
||||
"use strict";
|
||||
/*jslint nomen: true, plusplus: true, vars: true*/
|
||||
/*global Entities, Script, Quat, Vec3, Camera, MyAvatar, print, randFloat*/
|
||||
// acAudioSearchCompatibleEntitySpawner.js
|
||||
// audio/acAudioSearching
|
||||
//
|
||||
|
@ -13,6 +15,10 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var N_SOUNDS = 1000;
|
||||
var N_SILENT_ENTITIES_PER_SOUND = 10;
|
||||
var ADD_PERIOD = 50; // ms between adding 1 sound + N_SILENT_ENTITIES_PER_SOUND, to not overrun entity server.
|
||||
var SPATIAL_DISTRIBUTION = 10; // meters spread over how far to randomly distribute enties.
|
||||
Script.include("../../libraries/utils.js");
|
||||
var orientation = Camera.getOrientation();
|
||||
orientation = Quat.safeEulerAngles(orientation);
|
||||
|
@ -20,20 +26,21 @@ orientation.x = 0;
|
|||
orientation = Quat.fromVec3Degrees(orientation);
|
||||
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(orientation)));
|
||||
// http://hifi-public.s3.amazonaws.com/ryan/demo/0619_Fireplace__Tree_B.L.wav
|
||||
var SOUND_DATA_KEY = "soundKey";
|
||||
var SOUND_DATA_KEY = "io.highfidelity.soundKey";
|
||||
var userData = {
|
||||
soundKey: {
|
||||
url: "http://hifi-content.s3.amazonaws.com/DomainContent/Junkyard/Sounds/ClothSail/cloth_sail3.L.wav",
|
||||
volume: 0.3,
|
||||
loop: false,
|
||||
playbackGap: 2000, // In ms - time to wait in between clip plays
|
||||
playbackGapRange: 500 // In ms - the range to wait in between clip plays
|
||||
playbackGap: 20000, // In ms - time to wait in between clip plays
|
||||
playbackGapRange: 5000 // In ms - the range to wait in between clip plays
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var userDataString = JSON.stringify(userData);
|
||||
var entityProps = {
|
||||
type: "Box",
|
||||
position: center,
|
||||
name: 'audioSearchEntity',
|
||||
color: {
|
||||
red: 200,
|
||||
green: 10,
|
||||
|
@ -43,15 +50,41 @@ var entityProps = {
|
|||
x: 0.1,
|
||||
y: 0.1,
|
||||
z: 0.1
|
||||
},
|
||||
userData: JSON.stringify(userData)
|
||||
}
|
||||
};
|
||||
|
||||
var entities = [], nSounds = 0;
|
||||
Script.include("../../libraries/utils.js");
|
||||
function addOneSet() {
|
||||
function randomizeDimension(coordinate) {
|
||||
return coordinate + randFloat(-SPATIAL_DISTRIBUTION / 2, SPATIAL_DISTRIBUTION / 2);
|
||||
}
|
||||
function randomize() {
|
||||
return {x: randomizeDimension(center.x), y: randomizeDimension(center.y), z: randomizeDimension(center.z)};
|
||||
}
|
||||
function addOne() {
|
||||
entityProps.position = randomize();
|
||||
entities.push(Entities.addEntity(entityProps));
|
||||
}
|
||||
var i;
|
||||
entityProps.userData = userDataString;
|
||||
entityProps.color.red = 200;
|
||||
addOne();
|
||||
delete entityProps.userData;
|
||||
entityProps.color.red = 10;
|
||||
for (i = 0; i < N_SILENT_ENTITIES_PER_SOUND; i++) {
|
||||
addOne();
|
||||
}
|
||||
if (++nSounds < N_SOUNDS) {
|
||||
Script.setTimeout(addOneSet, ADD_PERIOD);
|
||||
}
|
||||
}
|
||||
|
||||
var soundEntity = Entities.addEntity(entityProps);
|
||||
|
||||
addOneSet();
|
||||
|
||||
function cleanup() {
|
||||
Entities.deleteEntity(soundEntity);
|
||||
entities.forEach(Entities.deleteEntity);
|
||||
}
|
||||
// In console:
|
||||
// Entities.findEntities(MyAvatar.position, 100).forEach(function (id) { if (Entities.getEntityProperties(id).name === 'audioSearchEntity') Entities.deleteEntity(id); })
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
|
|
|
@ -171,9 +171,8 @@ var STATE_WAITING_FOR_BUMPER_RELEASE = 15;
|
|||
var COLLIDES_WITH_WHILE_GRABBED = "dynamic,otherAvatar";
|
||||
var COLLIDES_WITH_WHILE_MULTI_GRABBED = "dynamic";
|
||||
|
||||
var HEART_BEAT_INTERVAL = 5; // seconds
|
||||
var HEART_BEAT_TIMEOUT = 15;
|
||||
|
||||
var HEART_BEAT_INTERVAL = 5 * MSECS_PER_SEC;
|
||||
var HEART_BEAT_TIMEOUT = 15 * MSECS_PER_SEC;
|
||||
|
||||
function stateToName(state) {
|
||||
switch (state) {
|
||||
|
|
|
@ -11,6 +11,21 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var leftTriggerValue = 0;
|
||||
var rightTriggerValue = 0;
|
||||
|
||||
var LEAP_TRIGGER_START_ANGLE = 15.0;
|
||||
var LEAP_TRIGGER_END_ANGLE = 40.0;
|
||||
|
||||
function getLeapMotionLeftTrigger() {
|
||||
//print("left trigger = " + leftTriggerValue);
|
||||
return leftTriggerValue;
|
||||
}
|
||||
function getLeapMotionRightTrigger() {
|
||||
//print("right trigger = " + rightTriggerValue);
|
||||
return rightTriggerValue;
|
||||
}
|
||||
|
||||
var leapHands = (function () {
|
||||
|
||||
var isOnHMD,
|
||||
|
@ -61,78 +76,6 @@ var leapHands = (function () {
|
|||
print(i + ": " + jointNames[i]);
|
||||
}
|
||||
print("... skeleton joint names");
|
||||
|
||||
/*
|
||||
http://public.highfidelity.io/models/skeletons/ron_standing.fst
|
||||
Skeleton joint names ...
|
||||
0: Hips
|
||||
1: RightUpLeg
|
||||
2: RightLeg
|
||||
3: RightFoot
|
||||
4: RightToeBase
|
||||
5: RightToe_End
|
||||
6: LeftUpLeg
|
||||
7: LeftLeg
|
||||
8: LeftFoot
|
||||
9: LeftToeBase
|
||||
10: LeftToe_End
|
||||
11: Spine
|
||||
12: Spine1
|
||||
13: Spine2
|
||||
14: RightShoulder
|
||||
15: RightArm
|
||||
16: RightForeArm
|
||||
17: RightHand
|
||||
18: RightHandPinky1
|
||||
19: RightHandPinky2
|
||||
20: RightHandPinky3
|
||||
21: RightHandPinky4
|
||||
22: RightHandRing1
|
||||
23: RightHandRing2
|
||||
24: RightHandRing3
|
||||
25: RightHandRing4
|
||||
26: RightHandMiddle1
|
||||
27: RightHandMiddle2
|
||||
28: RightHandMiddle3
|
||||
29: RightHandMiddle4
|
||||
30: RightHandIndex1
|
||||
31: RightHandIndex2
|
||||
32: RightHandIndex3
|
||||
33: RightHandIndex4
|
||||
34: RightHandThumb1
|
||||
35: RightHandThumb2
|
||||
36: RightHandThumb3
|
||||
37: RightHandThumb4
|
||||
38: LeftShoulder
|
||||
39: LeftArm
|
||||
40: LeftForeArm
|
||||
41: LeftHand
|
||||
42: LeftHandPinky1
|
||||
43: LeftHandPinky2
|
||||
44: LeftHandPinky3
|
||||
45: LeftHandPinky4
|
||||
46: LeftHandRing1
|
||||
47: LeftHandRing2
|
||||
48: LeftHandRing3
|
||||
49: LeftHandRing4
|
||||
50: LeftHandMiddle1
|
||||
51: LeftHandMiddle2
|
||||
52: LeftHandMiddle3
|
||||
53: LeftHandMiddle4
|
||||
54: LeftHandIndex1
|
||||
55: LeftHandIndex2
|
||||
56: LeftHandIndex3
|
||||
57: LeftHandIndex4
|
||||
58: LeftHandThumb1
|
||||
59: LeftHandThumb2
|
||||
60: LeftHandThumb3
|
||||
61: LeftHandThumb4
|
||||
62: Neck
|
||||
63: Head
|
||||
64: HeadTop_End
|
||||
65: body
|
||||
... skeleton joint names
|
||||
*/
|
||||
}
|
||||
|
||||
function animateLeftHand() {
|
||||
|
@ -357,6 +300,13 @@ var leapHands = (function () {
|
|||
settingsTimer = Script.setInterval(checkSettings, 2000);
|
||||
|
||||
calibrationStatus = UNCALIBRATED;
|
||||
|
||||
{
|
||||
var mapping = Controller.newMapping("LeapmotionTrigger");
|
||||
mapping.from(getLeapMotionLeftTrigger).to(Controller.Standard.LT);
|
||||
mapping.from(getLeapMotionRightTrigger).to(Controller.Standard.RT);
|
||||
mapping.enable();
|
||||
}
|
||||
}
|
||||
|
||||
function moveHands() {
|
||||
|
@ -469,10 +419,17 @@ var leapHands = (function () {
|
|||
hands[h].rotation = handRotation;
|
||||
|
||||
// Set finger joints ...
|
||||
var summed = 0;
|
||||
var closeAngle = 0;
|
||||
for (i = 0; i < NUM_FINGERS; i += 1) {
|
||||
for (j = 0; j < NUM_FINGER_JOINTS; j += 1) {
|
||||
if (fingers[h][i][j].controller !== null) {
|
||||
locRotation = fingers[h][i][j].controller.getLocRotation();
|
||||
var eulers = Quat.safeEulerAngles(locRotation);
|
||||
closeAngle += eulers.x;
|
||||
|
||||
summed++;
|
||||
|
||||
if (i === THUMB) {
|
||||
locRotation = {
|
||||
x: side * locRotation.y,
|
||||
|
@ -496,8 +453,21 @@ var leapHands = (function () {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
hands[h].inactiveCount = 0;
|
||||
if (summed > 0) {
|
||||
closeAngle /= summed;
|
||||
}
|
||||
|
||||
var triggerValue = (-closeAngle - LEAP_TRIGGER_START_ANGLE) / (LEAP_TRIGGER_END_ANGLE - LEAP_TRIGGER_START_ANGLE);
|
||||
triggerValue = Math.max(0.0, Math.min(triggerValue, 1.0));
|
||||
|
||||
if (h == 0) {
|
||||
leftTriggerValue = triggerValue;
|
||||
} else {
|
||||
rightTriggerValue = triggerValue;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
|
@ -509,6 +479,8 @@ var leapHands = (function () {
|
|||
if (handAnimationStateHandlers[h] !== null) {
|
||||
MyAvatar.removeAnimationStateHandler(handAnimationStateHandlers[h]);
|
||||
handAnimationStateHandlers[h] = null;
|
||||
leftTriggerValue = 0.0;
|
||||
rightTriggerValue = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,10 +72,13 @@
|
|||
};
|
||||
}
|
||||
|
||||
function createEmitNumberPropertyUpdateFunction(propertyName) {
|
||||
function createEmitNumberPropertyUpdateFunction(propertyName, decimals) {
|
||||
decimals = decimals == undefined ? 4 : decimals;
|
||||
return function() {
|
||||
var value = parseFloat(this.value).toFixed(decimals);
|
||||
|
||||
EventBridge.emitWebEvent(
|
||||
'{ "type":"update", "properties":{"' + propertyName + '":' + parseFloat(this.value).toFixed(4) + '}}'
|
||||
'{ "type":"update", "properties":{"' + propertyName + '":' + value + '}}'
|
||||
);
|
||||
};
|
||||
}
|
||||
|
@ -323,6 +326,7 @@
|
|||
var elLightColorBlue = document.getElementById("property-light-color-blue");
|
||||
|
||||
var elLightIntensity = document.getElementById("property-light-intensity");
|
||||
var elLightFalloffRadius = document.getElementById("property-light-falloff-radius");
|
||||
var elLightExponent = document.getElementById("property-light-exponent");
|
||||
var elLightCutoff = document.getElementById("property-light-cutoff");
|
||||
|
||||
|
@ -604,9 +608,10 @@
|
|||
elLightColorGreen.value = properties.color.green;
|
||||
elLightColorBlue.value = properties.color.blue;
|
||||
|
||||
elLightIntensity.value = properties.intensity;
|
||||
elLightExponent.value = properties.exponent;
|
||||
elLightCutoff.value = properties.cutoff;
|
||||
elLightIntensity.value = properties.intensity.toFixed(1);
|
||||
elLightFalloffRadius.value = properties.falloffRadius.toFixed(1);
|
||||
elLightExponent.value = properties.exponent.toFixed(2);
|
||||
elLightCutoff.value = properties.cutoff.toFixed(2);
|
||||
} else if (properties.type == "Zone") {
|
||||
for (var i = 0; i < elZoneSections.length; i++) {
|
||||
elZoneSections[i].style.display = 'block';
|
||||
|
@ -795,9 +800,10 @@
|
|||
}
|
||||
})
|
||||
|
||||
elLightIntensity.addEventListener('change', createEmitNumberPropertyUpdateFunction('intensity'));
|
||||
elLightExponent.addEventListener('change', createEmitNumberPropertyUpdateFunction('exponent'));
|
||||
elLightCutoff.addEventListener('change', createEmitNumberPropertyUpdateFunction('cutoff'));
|
||||
elLightIntensity.addEventListener('change', createEmitNumberPropertyUpdateFunction('intensity', 1));
|
||||
elLightFalloffRadius.addEventListener('change', createEmitNumberPropertyUpdateFunction('falloffRadius', 1));
|
||||
elLightExponent.addEventListener('change', createEmitNumberPropertyUpdateFunction('exponent', 2));
|
||||
elLightCutoff.addEventListener('change', createEmitNumberPropertyUpdateFunction('cutoff', 2));
|
||||
|
||||
elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl'));
|
||||
|
||||
|
@ -1043,25 +1049,25 @@
|
|||
<div class="text-section property">
|
||||
<div class="label">Line Height</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-text-line-height" min="0" step="0.005">
|
||||
<input class="coord" type="number" id="property-text-line-height" min="0" step="0.005">
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-section property">
|
||||
<div class="label">Text Color</div>
|
||||
<div class="value">
|
||||
<div class='color-picker' id="property-text-text-color"></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-text-text-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-text-text-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-text-text-color-blue"></div>
|
||||
<div class="input-area">R <input class="coord" type="number" id="property-text-text-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type="number" id="property-text-text-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type="number" id="property-text-text-color-blue"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-section property">
|
||||
<div class="label">Background Color</div>
|
||||
<div class="value">
|
||||
<div class='color-picker' id="property-text-background-color"></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-text-background-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-text-background-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-text-background-color-blue"></div>
|
||||
<div class="input-area">R <input class="coord" type="number" id="property-text-background-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type="number" id="property-text-background-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type="number" id="property-text-background-color-blue"></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1085,32 +1091,32 @@
|
|||
<div class="label">Light Color</div>
|
||||
<div class="value">
|
||||
<div class='color-picker' id="property-zone-key-light-color"></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-zone-key-light-color-red" min="0" max="255" step="1"></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-zone-key-light-color-green" min="0" max="255" step="1"></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-zone-key-light-color-blue" min="0" max="255" step="1"></div>
|
||||
<div class="input-area">R <input class="coord" type="number" id="property-zone-key-light-color-red" min="0" max="255" step="1"></div>
|
||||
<div class="input-area">G <input class="coord" type="number" id="property-zone-key-light-color-green" min="0" max="255" step="1"></div>
|
||||
<div class="input-area">B <input class="coord" type="number" id="property-zone-key-light-color-blue" min="0" max="255" step="1"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="zone-section keyLight-section property">
|
||||
<div class="label">Light Intensity</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-key-intensity" min="0" max="10" step="0.1">
|
||||
<input class="coord" type="number" id="property-zone-key-intensity" min="0" max="10" step="0.1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="zone-section keyLight-section property">
|
||||
<div class="label">Light Direction</div>
|
||||
<div class="value">
|
||||
<div class="input-area">Pitch <input class="coord" type='number' id="property-zone-key-light-direction-x"></div>
|
||||
<div class="input-area">Yaw <input class="coord" type='number' id="property-zone-key-light-direction-y"></div>
|
||||
<div class="input-area">Roll <input class="coord" type='number' id="property-zone-key-light-direction-z"></div>
|
||||
<div class="input-area">Pitch <input class="coord" type="number" id="property-zone-key-light-direction-x"></div>
|
||||
<div class="input-area">Yaw <input class="coord" type="number" id="property-zone-key-light-direction-y"></div>
|
||||
<div class="input-area">Roll <input class="coord" type="number" id="property-zone-key-light-direction-z"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="zone-section keyLight-section property">
|
||||
<div class="label">Ambient Intensity</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-key-ambient-intensity" min="0" max="10" step="0.1">
|
||||
<input class="coord" type="number" id="property-zone-key-ambient-intensity" min="0" max="10" step="0.1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1129,19 +1135,19 @@
|
|||
<div class="zone-section stage-section property">
|
||||
<div class="label">Stage Latitude</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-stage-latitude" min="-90" max="90" step="1">
|
||||
<input class="coord" type="number" id="property-zone-stage-latitude" min="-90" max="90" step="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section stage-section property">
|
||||
<div class="label">Stage Longitude</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-stage-longitude" min="-180" max="180" step="1">
|
||||
<input class="coord" type="number" id="property-zone-stage-longitude" min="-180" max="180" step="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section stage-section property">
|
||||
<div class="label">Stage Altitude</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-stage-altitude" step="1">
|
||||
<input class="coord" type="number" id="property-zone-stage-altitude" step="1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1155,13 +1161,13 @@
|
|||
<div class="zone-section stage-section property">
|
||||
<div class="label">Stage Day</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-stage-day" min="0" max="365" step="1">
|
||||
<input class="coord" type="number" id="property-zone-stage-day" min="0" max="365" step="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section stage-section property">
|
||||
<div class="label">Stage Hour</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-stage-hour" min="0" max="24" step="0.5">
|
||||
<input class="coord" type="number" id="property-zone-stage-hour" min="0" max="24" step="0.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1186,9 +1192,9 @@
|
|||
<div class="label">Skybox Color</div>
|
||||
<div class="value">
|
||||
<div class='color-picker' id="property-zone-skybox-color"></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-zone-skybox-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-zone-skybox-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-zone-skybox-color-blue"></div>
|
||||
<div class="input-area">R <input class="coord" type="number" id="property-zone-skybox-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type="number" id="property-zone-skybox-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type="number" id="property-zone-skybox-color-blue"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1264,23 +1270,23 @@
|
|||
<div class="property">
|
||||
<div class="label">Registration</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-reg-x"><div class="prop-x"></div></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-reg-y"><div class="prop-y"></div></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-reg-z"><div class="prop-z"></div></div>
|
||||
<div class="input-area">X <input class="coord" type="number" id="property-reg-x"><div class="prop-x"></div></div>
|
||||
<div class="input-area">Y <input class="coord" type="number" id="property-reg-y"><div class="prop-y"></div></div>
|
||||
<div class="input-area">Z <input class="coord" type="number" id="property-reg-z"><div class="prop-z"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<div class="label">Dimensions</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-dim-x" step="0.1"><div class="prop-x"></div></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-dim-y" step="0.1"><div class="prop-y"></div></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-dim-z" step="0.1"><div class="prop-z"></div></div>
|
||||
<div class="input-area">X <input class="coord" type="number" id="property-dim-x" step="0.1"><div class="prop-x"></div></div>
|
||||
<div class="input-area">Y <input class="coord" type="number" id="property-dim-y" step="0.1"><div class="prop-y"></div></div>
|
||||
<div class="input-area">Z <input class="coord" type="number" id="property-dim-z" step="0.1"><div class="prop-z"></div></div>
|
||||
<div>
|
||||
<input type="button" id="reset-to-natural-dimensions" value="Reset to Natural Dimensions">
|
||||
</div>
|
||||
<div class="input-area">
|
||||
<input class="" type='number' id="dimension-rescale-pct" value=100>%
|
||||
<input class="" type="number" id="dimension-rescale-pct" value=100>%
|
||||
</div>
|
||||
<span>
|
||||
<input type="button" id="dimension-rescale-button" value="Rescale">
|
||||
|
@ -1291,9 +1297,9 @@
|
|||
<div class="poly-vox-section property">
|
||||
<div class="label">Voxel Volume Size</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <br> <input class="coord" type='number' id="property-voxel-volume-size-x"></div>
|
||||
<div class="input-area">Y <br><input class="coord" type='number' id="property-voxel-volume-size-y"></div>
|
||||
<div class="input-area">Z <br><input class="coord" type='number' id="property-voxel-volume-size-z"></div>
|
||||
<div class="input-area">X <br> <input class="coord" type="number" id="property-voxel-volume-size-x"></div>
|
||||
<div class="input-area">Y <br><input class="coord" type="number" id="property-voxel-volume-size-y"></div>
|
||||
<div class="input-area">Z <br><input class="coord" type="number" id="property-voxel-volume-size-z"></div>
|
||||
</div>
|
||||
|
||||
<div class="label">Surface Extractor</div>
|
||||
|
@ -1325,9 +1331,9 @@
|
|||
<div class="property">
|
||||
<div class="label">Rotation</div>
|
||||
<div class="value">
|
||||
<div class="input-area">Pitch <input class="coord" type='number' id="property-rot-x" step="0.1"></div>
|
||||
<div class="input-area">Yaw <input class="coord" type='number' id="property-rot-y" step="0.1"></div>
|
||||
<div class="input-area">Roll <input class="coord" type='number' id="property-rot-z"step="0.1"></div>
|
||||
<div class="input-area">Pitch <input class="coord" type="number" id="property-rot-x" step="0.1"></div>
|
||||
<div class="input-area">Yaw <input class="coord" type="number" id="property-rot-y" step="0.1"></div>
|
||||
<div class="input-area">Roll <input class="coord" type="number" id="property-rot-z"step="0.1"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1339,66 +1345,66 @@
|
|||
<div class="property">
|
||||
<div class="label">Linear Velocity</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-lvel-x"><div class="prop-x"></div></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-lvel-y"><div class="prop-y"></div></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-lvel-z"><div class="prop-z"></div></div>
|
||||
<div class="input-area">X <input class="coord" type="number" id="property-lvel-x"><div class="prop-x"></div></div>
|
||||
<div class="input-area">Y <input class="coord" type="number" id="property-lvel-y"><div class="prop-y"></div></div>
|
||||
<div class="input-area">Z <input class="coord" type="number" id="property-lvel-z"><div class="prop-z"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="label">Linear Damping</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-ldamping">
|
||||
<input class="coord" type="number" id="property-ldamping">
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="label">Angular Velocity</div>
|
||||
<div class="value">
|
||||
<div class="input-area">Pitch <input class="coord" type='number' id="property-avel-x"></div>
|
||||
<div class="input-area">Yaw <input class="coord" type='number' id="property-avel-y"></div>
|
||||
<div class="input-area">Roll <input class="coord" type='number' id="property-avel-z"></div>
|
||||
<div class="input-area">Pitch <input class="coord" type="number" id="property-avel-x"></div>
|
||||
<div class="input-area">Yaw <input class="coord" type="number" id="property-avel-y"></div>
|
||||
<div class="input-area">Roll <input class="coord" type="number" id="property-avel-z"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="label">Angular Damping</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-adamping">
|
||||
<input class="coord" type="number" id="property-adamping">
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="label">Restitution</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-restitution">
|
||||
<input class="coord" type="number" id="property-restitution">
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="label">Friction</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-friction">
|
||||
<input class="coord" type="number" id="property-friction">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<div class="label">Gravity</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-grav-x"><div class="prop-x"></div></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-grav-y"><div class="prop-y"></div></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-grav-z"><div class="prop-z"></div></div>
|
||||
<div class="input-area">X <input class="coord" type="number" id="property-grav-x"><div class="prop-x"></div></div>
|
||||
<div class="input-area">Y <input class="coord" type="number" id="property-grav-y"><div class="prop-y"></div></div>
|
||||
<div class="input-area">Z <input class="coord" type="number" id="property-grav-z"><div class="prop-z"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<div class="label">Acceleration</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-lacc-x"><div class="prop-x"></div></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-lacc-y"><div class="prop-y"></div></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-lacc-z"><div class="prop-z"></div></div>
|
||||
<div class="input-area">X <input class="coord" type="number" id="property-lacc-x"><div class="prop-x"></div></div>
|
||||
<div class="input-area">Y <input class="coord" type="number" id="property-lacc-y"><div class="prop-y"></div></div>
|
||||
<div class="input-area">Z <input class="coord" type="number" id="property-lacc-z"><div class="prop-z"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<div class="label">Density</div>
|
||||
<div>
|
||||
<input type='number' id="property-density">
|
||||
<input type="number" id="property-density">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1406,9 +1412,9 @@
|
|||
<div class="label">Color</div>
|
||||
<div class="value">
|
||||
<div id="property-color" class='color-picker'></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-color-blue"></div>
|
||||
<div class="input-area">R <input class="coord" type="number" id="property-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type="number" id="property-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type="number" id="property-color-blue"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1483,7 +1489,7 @@
|
|||
<div class="property">
|
||||
<div class="label">Lifetime</div>
|
||||
<div class="value">
|
||||
<input type='number' id="property-lifetime">
|
||||
<input type="number" id="property-lifetime">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1541,25 +1547,25 @@
|
|||
<div class="model-section property">
|
||||
<div class="label">Animation FPS</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-model-animation-fps">
|
||||
<input class="coord" type="number" id="property-model-animation-fps">
|
||||
</div>
|
||||
</div>
|
||||
<div class="model-section property">
|
||||
<div class="label">Animation Frame</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-model-animation-frame">
|
||||
<input class="coord" type="number" id="property-model-animation-frame">
|
||||
</div>
|
||||
</div>
|
||||
<div class="model-section property">
|
||||
<div class="label">Animation First Frame</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-model-animation-first-frame">
|
||||
<input class="coord" type="number" id="property-model-animation-first-frame">
|
||||
</div>
|
||||
</div>
|
||||
<div class="model-section property">
|
||||
<div class="label">Animation Last Frame</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-model-animation-last-frame">
|
||||
<input class="coord" type="number" id="property-model-animation-last-frame">
|
||||
</div>
|
||||
</div>
|
||||
<div class="model-section property">
|
||||
|
@ -1591,37 +1597,43 @@
|
|||
<label>Light</label>
|
||||
</div>
|
||||
|
||||
<div class="light-section property">
|
||||
<div class="label">Color</div>
|
||||
<div class="value">
|
||||
<div class='color-picker' id="property-light-color"></div>
|
||||
<div class="input-area">R <input class="coord" type="number" id="property-light-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type="number" id="property-light-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type="number" id="property-light-color-blue"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="light-section property">
|
||||
<div class="label">Intensity</div>
|
||||
<div class="value">
|
||||
<input class="coord" type="number" id="property-light-intensity" min="0" step="0.1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="light-section property">
|
||||
<div class="label">Falloff Radius</div>
|
||||
<div class="value">
|
||||
<input class="coord" type="number" id="property-light-falloff-radius" min="0" step="0.1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="light-section property">
|
||||
<span class="label">Spot Light</span>
|
||||
<span class="value">
|
||||
<input type='checkbox' id="property-light-spot-light">
|
||||
</span>
|
||||
</div>
|
||||
<div class="light-section property">
|
||||
<div class="label">Color</div>
|
||||
<div class="value">
|
||||
<div class='color-picker' id="property-light-color"></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-light-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-light-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-light-color-blue"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="light-section property">
|
||||
<div class="label">Intensity</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-light-intensity">
|
||||
</div>
|
||||
</div>
|
||||
<div class="light-section property">
|
||||
<div class="label">Spot Light Exponent</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-light-exponent">
|
||||
<input class="coord" type="number" id="property-light-exponent" step="0.01">
|
||||
</div>
|
||||
</div>
|
||||
<div class="light-section property">
|
||||
<div class="label">Spot Light Cutoff (degrees)</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-light-cutoff">
|
||||
<input class="coord" type="number" id="property-light-cutoff" step="0.01">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -425,11 +425,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
_maxOctreePPS(maxOctreePacketsPerSecond.get()),
|
||||
_lastFaceTrackerUpdate(0)
|
||||
{
|
||||
// FIXME this may be excessivly conservative. On the other hand
|
||||
// FIXME this may be excessivly conservative. On the other hand
|
||||
// maybe I'm used to having an 8-core machine
|
||||
// Perhaps find the ideal thread count and subtract 2 or 3
|
||||
// Perhaps find the ideal thread count and subtract 2 or 3
|
||||
// (main thread, present thread, random OS load)
|
||||
// More threads == faster concurrent loads, but also more concurrent
|
||||
// More threads == faster concurrent loads, but also more concurrent
|
||||
// load on the GPU until we can serialize GPU transfers (off the main thread)
|
||||
QThreadPool::globalInstance()->setMaxThreadCount(2);
|
||||
thread()->setPriority(QThread::HighPriority);
|
||||
|
@ -560,7 +560,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
|
||||
auto discoverabilityManager = DependencyManager::get<DiscoverabilityManager>();
|
||||
connect(&locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation);
|
||||
connect(&locationUpdateTimer, &QTimer::timeout,
|
||||
connect(&locationUpdateTimer, &QTimer::timeout,
|
||||
DependencyManager::get<AddressManager>().data(), &AddressManager::storeCurrentAddress);
|
||||
locationUpdateTimer.start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS);
|
||||
|
||||
|
@ -604,7 +604,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
|
||||
connect(addressManager.data(), &AddressManager::hostChanged, this, &Application::updateWindowTitle);
|
||||
connect(this, &QCoreApplication::aboutToQuit, addressManager.data(), &AddressManager::storeCurrentAddress);
|
||||
|
||||
|
||||
// Save avatar location immediately after a teleport.
|
||||
connect(getMyAvatar(), &MyAvatar::positionGoneTo,
|
||||
DependencyManager::get<AddressManager>().data(), &AddressManager::storeCurrentAddress);
|
||||
|
@ -625,7 +625,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
getEntities()->reloadEntityScripts();
|
||||
}, Qt::QueuedConnection);
|
||||
|
||||
connect(scriptEngines, &ScriptEngines::scriptLoadError,
|
||||
connect(scriptEngines, &ScriptEngines::scriptLoadError,
|
||||
scriptEngines, [](const QString& filename, const QString& error){
|
||||
OffscreenUi::warning(nullptr, "Error Loading Script", filename + " failed to load.");
|
||||
}, Qt::QueuedConnection);
|
||||
|
@ -975,7 +975,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
disconnect(_idleTimer);
|
||||
});
|
||||
// Setting the interval to zero forces this to get called whenever there are no messages
|
||||
// in the queue, which can be pretty damn frequent. Hence the idle function has a bunch
|
||||
// in the queue, which can be pretty damn frequent. Hence the idle function has a bunch
|
||||
// of logic to abort early if it's being called too often.
|
||||
_idleTimer->start(0);
|
||||
}
|
||||
|
@ -1023,7 +1023,7 @@ void Application::cleanupBeforeQuit() {
|
|||
getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts
|
||||
DependencyManager::get<ScriptEngines>()->saveScripts();
|
||||
DependencyManager::get<ScriptEngines>()->shutdownScripting(); // stop all currently running global scripts
|
||||
DependencyManager::destroy<ScriptEngines>();
|
||||
DependencyManager::destroy<ScriptEngines>();
|
||||
|
||||
// first stop all timers directly or by invokeMethod
|
||||
// depending on what thread they run in
|
||||
|
@ -1212,10 +1212,10 @@ void Application::initializeUi() {
|
|||
|
||||
setupPreferences();
|
||||
|
||||
// For some reason there is already an "Application" object in the QML context,
|
||||
// For some reason there is already an "Application" object in the QML context,
|
||||
// though I can't find it. Hence, "ApplicationInterface"
|
||||
rootContext->setContextProperty("SnapshotUploader", new SnapshotUploader());
|
||||
rootContext->setContextProperty("ApplicationInterface", this);
|
||||
rootContext->setContextProperty("ApplicationInterface", this);
|
||||
rootContext->setContextProperty("AnimationCache", DependencyManager::get<AnimationCache>().data());
|
||||
rootContext->setContextProperty("Audio", &AudioScriptingInterface::getInstance());
|
||||
rootContext->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
|
||||
|
@ -2435,7 +2435,7 @@ void Application::idle(uint64_t now) {
|
|||
if (_aboutToQuit) {
|
||||
return; // bail early, nothing to do here.
|
||||
}
|
||||
|
||||
|
||||
auto displayPlugin = getActiveDisplayPlugin();
|
||||
// depending on whether we're throttling or not.
|
||||
// Once rendering is off on another thread we should be able to have Application::idle run at start(0) in
|
||||
|
@ -2459,7 +2459,7 @@ void Application::idle(uint64_t now) {
|
|||
// Nested ifs are for clarity in the logic. Don't collapse them into a giant single if.
|
||||
// Don't saturate the main thread with rendering, no paint calls until the last one is complete
|
||||
if (!_pendingPaint) {
|
||||
// Also no paint calls until the display plugin has increased by at least one frame
|
||||
// Also no paint calls until the display plugin has increased by at least one frame
|
||||
// (don't render at 90fps if the display plugin only goes at 60)
|
||||
if (_renderedFrameIndex == INVALID_FRAME || presentCount > _renderedFrameIndex) {
|
||||
// Record what present frame we're on
|
||||
|
@ -2469,14 +2469,14 @@ void Application::idle(uint64_t now) {
|
|||
// But when we DO request a paint, get to it as soon as possible: high priority
|
||||
postEvent(this, new QEvent(static_cast<QEvent::Type>(Paint)), Qt::HighEventPriority);
|
||||
}
|
||||
}
|
||||
|
||||
// For the rest of idle, we want to cap at the max sim rate, so we might not call
|
||||
}
|
||||
|
||||
// For the rest of idle, we want to cap at the max sim rate, so we might not call
|
||||
// the remaining idle work every paint frame, or vice versa
|
||||
// In theory this means we could call idle processing more often than painting,
|
||||
// but in practice, when the paintGL calls aren't keeping up, there's no room left
|
||||
// in the main thread to call idle more often than paint.
|
||||
// This check is mostly to keep idle from burning up CPU cycles by running at
|
||||
// This check is mostly to keep idle from burning up CPU cycles by running at
|
||||
// hundreds of idles per second when the rendering is that fast
|
||||
if ((timeSinceLastUpdateUs / USECS_PER_MSEC) < CAPPED_SIM_FRAME_PERIOD_MS) {
|
||||
// No paint this round, but might be time for a new idle, otherwise return
|
||||
|
@ -3387,7 +3387,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
|
|||
_octreeQuery.setCameraNearClip(_viewFrustum.getNearClip());
|
||||
_octreeQuery.setCameraFarClip(_viewFrustum.getFarClip());
|
||||
_octreeQuery.setCameraEyeOffsetPosition(glm::vec3());
|
||||
_octreeQuery.setKeyholeRadius(_viewFrustum.getKeyholeRadius());
|
||||
_octreeQuery.setCameraCenterRadius(_viewFrustum.getCenterRadius());
|
||||
auto lodManager = DependencyManager::get<LODManager>();
|
||||
_octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale());
|
||||
_octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust());
|
||||
|
@ -3423,9 +3423,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
|
|||
rootDetails.y * TREE_SCALE,
|
||||
rootDetails.z * TREE_SCALE) - glm::vec3(HALF_TREE_SCALE),
|
||||
rootDetails.s * TREE_SCALE);
|
||||
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
|
||||
|
||||
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
|
||||
if (_viewFrustum.cubeIntersectsKeyhole(serverBounds)) {
|
||||
inViewServers++;
|
||||
}
|
||||
}
|
||||
|
@ -3491,12 +3489,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
|
|||
rootDetails.s * TREE_SCALE);
|
||||
|
||||
|
||||
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
|
||||
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
|
||||
inView = true;
|
||||
} else {
|
||||
inView = false;
|
||||
}
|
||||
inView = _viewFrustum.cubeIntersectsKeyhole(serverBounds);
|
||||
} else {
|
||||
if (wantExtraDebugging) {
|
||||
qCDebug(interfaceapp) << "Jurisdiction without RootCode for node " << *node << ". That's unusual!";
|
||||
|
@ -4752,7 +4745,7 @@ static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool acti
|
|||
groupingMenu = "Developer";
|
||||
break;
|
||||
default:
|
||||
groupingMenu = "Standard";
|
||||
groupingMenu = "Standard";
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -136,7 +136,7 @@ glm::quat Avatar::getWorldAlignedOrientation () const {
|
|||
|
||||
AABox Avatar::getBounds() const {
|
||||
// Our skeleton models are rigged, and this method call safely produces the static bounds of the model.
|
||||
// Except, that getPartBounds produces an infinite, uncentered bounding box when the model is not yet parsed,
|
||||
// Except, that getPartBounds produces an infinite, uncentered bounding box when the model is not yet parsed,
|
||||
// and we want a centered one. NOTE: There is code that may never try to render, and thus never load and get the
|
||||
// real model bounds, if this is unrealistically small.
|
||||
if (!_skeletonModel.isRenderable()) {
|
||||
|
@ -188,15 +188,14 @@ void Avatar::simulate(float deltaTime) {
|
|||
|
||||
// simple frustum check
|
||||
float boundingRadius = getBoundingRadius();
|
||||
bool inViewFrustum = qApp->getViewFrustum()->sphereInFrustum(getPosition(), boundingRadius) !=
|
||||
ViewFrustum::OUTSIDE;
|
||||
bool inView = qApp->getViewFrustum()->sphereIntersectsFrustum(getPosition(), boundingRadius);
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("hand");
|
||||
getHand()->simulate(deltaTime, false);
|
||||
}
|
||||
|
||||
if (_shouldAnimate && !_shouldSkipRender && inViewFrustum) {
|
||||
if (_shouldAnimate && !_shouldSkipRender && inView) {
|
||||
{
|
||||
PerformanceTimer perfTimer("skeleton");
|
||||
_skeletonModel.getRig()->copyJointsFromJointData(_jointData);
|
||||
|
@ -401,7 +400,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
frustum = qApp->getDisplayViewFrustum();
|
||||
}
|
||||
|
||||
if (frustum->sphereInFrustum(getPosition(), boundingRadius) == ViewFrustum::OUTSIDE) {
|
||||
if (frustum->sphereIntersectsFrustum(getPosition(), boundingRadius)) {
|
||||
endRender();
|
||||
return;
|
||||
}
|
||||
|
@ -430,6 +429,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE) {
|
||||
// add local lights
|
||||
const float BASE_LIGHT_DISTANCE = 2.0f;
|
||||
const float LIGHT_FALLOFF_RADIUS = 0.01f;
|
||||
const float LIGHT_EXPONENT = 1.0f;
|
||||
const float LIGHT_CUTOFF = glm::radians(80.0f);
|
||||
float distance = BASE_LIGHT_DISTANCE * getUniformScale();
|
||||
|
@ -438,7 +438,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
foreach (const AvatarManager::LocalLight& light, DependencyManager::get<AvatarManager>()->getLocalLights()) {
|
||||
glm::vec3 direction = orientation * light.direction;
|
||||
DependencyManager::get<DeferredLightingEffect>()->addSpotLight(position - direction * distance,
|
||||
distance * 2.0f, light.color, 0.5f, orientation, LIGHT_EXPONENT, LIGHT_CUTOFF);
|
||||
distance * 2.0f, light.color, 0.5f, LIGHT_FALLOFF_RADIUS, orientation, LIGHT_EXPONENT, LIGHT_CUTOFF);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -516,7 +516,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
auto& frustum = *renderArgs->_viewFrustum;
|
||||
auto textPosition = getDisplayNamePosition();
|
||||
|
||||
if (frustum.pointInFrustum(textPosition, true) == ViewFrustum::INSIDE) {
|
||||
if (frustum.pointIntersectsFrustum(textPosition)) {
|
||||
renderDisplayName(batch, frustum, textPosition);
|
||||
}
|
||||
}
|
||||
|
@ -669,10 +669,10 @@ glm::vec3 Avatar::getDisplayNamePosition() const {
|
|||
return namePosition;
|
||||
}
|
||||
|
||||
Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, const glm::vec3& textPosition) const {
|
||||
Q_ASSERT_X(frustum.pointInFrustum(textPosition, true) == ViewFrustum::INSIDE,
|
||||
Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& view, const glm::vec3& textPosition) const {
|
||||
Q_ASSERT_X(view.pointIntersectsFrustum(textPosition),
|
||||
"Avatar::calculateDisplayNameTransform", "Text not in viewfrustum.");
|
||||
glm::vec3 toFrustum = frustum.getPosition() - textPosition;
|
||||
glm::vec3 toFrustum = view.getPosition() - textPosition;
|
||||
|
||||
// Compute orientation
|
||||
// If x and z are 0, atan(x, z) adais undefined, so default to 0 degrees
|
||||
|
@ -694,7 +694,7 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, cons
|
|||
return result;
|
||||
}
|
||||
|
||||
void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, const glm::vec3& textPosition) const {
|
||||
void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition) const {
|
||||
PROFILE_RANGE_BATCH(batch, __FUNCTION__);
|
||||
|
||||
bool shouldShowReceiveStats = DependencyManager::get<AvatarManager>()->shouldShowReceiveStats() && !isMyAvatar();
|
||||
|
@ -702,7 +702,7 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, co
|
|||
// If we have nothing to draw, or it's totally transparent, or it's too close or behind the camera, return
|
||||
static const float CLIP_DISTANCE = 0.2f;
|
||||
if ((_displayName.isEmpty() && !shouldShowReceiveStats) || _displayNameAlpha == 0.0f
|
||||
|| (glm::dot(frustum.getDirection(), getDisplayNamePosition() - frustum.getPosition()) <= CLIP_DISTANCE)) {
|
||||
|| (glm::dot(view.getDirection(), getDisplayNamePosition() - view.getPosition()) <= CLIP_DISTANCE)) {
|
||||
return;
|
||||
}
|
||||
auto renderer = textRenderer(DISPLAYNAME);
|
||||
|
@ -743,7 +743,7 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, co
|
|||
(_displayNameAlpha / DISPLAYNAME_ALPHA) * DISPLAYNAME_BACKGROUND_ALPHA);
|
||||
|
||||
// Compute display name transform
|
||||
auto textTransform = calculateDisplayNameTransform(frustum, textPosition);
|
||||
auto textTransform = calculateDisplayNameTransform(view, textPosition);
|
||||
// Test on extent above insures abs(height) > 0.0f
|
||||
textTransform.postScale(1.0f / height);
|
||||
batch.setModelTransform(textTransform);
|
||||
|
|
|
@ -231,8 +231,8 @@ protected:
|
|||
float getPelvisFloatingHeight() const;
|
||||
glm::vec3 getDisplayNamePosition() const;
|
||||
|
||||
Transform calculateDisplayNameTransform(const ViewFrustum& frustum, const glm::vec3& textPosition) const;
|
||||
void renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, const glm::vec3& textPosition) const;
|
||||
Transform calculateDisplayNameTransform(const ViewFrustum& view, const glm::vec3& textPosition) const;
|
||||
void renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition) const;
|
||||
virtual void renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, float glowLevel = 0.0f);
|
||||
virtual bool shouldRenderHead(const RenderArgs* renderArgs) const;
|
||||
virtual void fixupModelsInScene();
|
||||
|
|
|
@ -656,7 +656,7 @@ void MyAvatar::saveData() {
|
|||
|
||||
settings.setValue("displayName", _displayName);
|
||||
settings.setValue("collisionSoundURL", _collisionSoundURL);
|
||||
settings.setValue("snapTurn", _useSnapTurn);
|
||||
settings.setValue("useSnapTurn", _useSnapTurn);
|
||||
|
||||
settings.endGroup();
|
||||
}
|
||||
|
@ -750,7 +750,7 @@ void MyAvatar::loadData() {
|
|||
|
||||
setDisplayName(settings.value("displayName").toString());
|
||||
setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString());
|
||||
setSnapTurn(settings.value("snapTurn").toBool());
|
||||
setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool());
|
||||
|
||||
settings.endGroup();
|
||||
|
||||
|
|
|
@ -403,6 +403,22 @@ glm::vec2 ApplicationCompositor::getReticlePosition() const {
|
|||
return toGlm(QCursor::pos());
|
||||
}
|
||||
|
||||
bool ApplicationCompositor::getReticleOverDesktop() const {
|
||||
// if the QML/Offscreen UI thinks we're over the desktop, then we are...
|
||||
// but... if we're outside of the overlay area, we also want to call ourselves
|
||||
// as being over the desktop.
|
||||
if (qApp->isHMDMode()) {
|
||||
QMutexLocker locker(&_reticleLock);
|
||||
glm::vec2 maxOverlayPosition = qApp->getUiSize();
|
||||
if (_reticlePositionInHMD.x < 0 || _reticlePositionInHMD.y < 0 ||
|
||||
_reticlePositionInHMD.x > maxOverlayPosition.x || _reticlePositionInHMD.y > maxOverlayPosition.y) {
|
||||
return true; // we are outside the overlay area, consider ourselves over the desktop
|
||||
}
|
||||
}
|
||||
return _isOverDesktop;
|
||||
}
|
||||
|
||||
|
||||
void ApplicationCompositor::setReticlePosition(glm::vec2 position, bool sendFakeEvent) {
|
||||
if (qApp->isHMDMode()) {
|
||||
QMutexLocker locker(&_reticleLock);
|
||||
|
|
|
@ -107,7 +107,7 @@ public:
|
|||
bool shouldCaptureMouse() const;
|
||||
|
||||
/// if the reticle is pointing to a system overlay (a dialog box for example) then the function returns true otherwise false
|
||||
bool getReticleOverDesktop() const { return _isOverDesktop; }
|
||||
bool getReticleOverDesktop() const;
|
||||
void setReticleOverDesktop(bool value) { _isOverDesktop = value; }
|
||||
|
||||
private:
|
||||
|
|
|
@ -36,15 +36,16 @@ void RenderableLightEntityItem::render(RenderArgs* args) {
|
|||
glm::vec3 color = toGlm(getXColor());
|
||||
|
||||
float intensity = getIntensity();
|
||||
float falloffRadius = getFalloffRadius();
|
||||
float exponent = getExponent();
|
||||
float cutoff = glm::radians(getCutoff());
|
||||
|
||||
if (_isSpotlight) {
|
||||
DependencyManager::get<DeferredLightingEffect>()->addSpotLight(position, largestDiameter / 2.0f,
|
||||
color, intensity, rotation, exponent, cutoff);
|
||||
color, intensity, falloffRadius, rotation, exponent, cutoff);
|
||||
} else {
|
||||
DependencyManager::get<DeferredLightingEffect>()->addPointLight(position, largestDiameter / 2.0f,
|
||||
color, intensity);
|
||||
color, intensity, falloffRadius);
|
||||
}
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
|
|
|
@ -245,6 +245,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
CHECK_PROPERTY_CHANGE(PROP_DYNAMIC, dynamic);
|
||||
CHECK_PROPERTY_CHANGE(PROP_IS_SPOTLIGHT, isSpotlight);
|
||||
CHECK_PROPERTY_CHANGE(PROP_INTENSITY, intensity);
|
||||
CHECK_PROPERTY_CHANGE(PROP_FALLOFF_RADIUS, falloffRadius);
|
||||
CHECK_PROPERTY_CHANGE(PROP_EXPONENT, exponent);
|
||||
CHECK_PROPERTY_CHANGE(PROP_CUTOFF, cutoff);
|
||||
CHECK_PROPERTY_CHANGE(PROP_LOCKED, locked);
|
||||
|
@ -445,6 +446,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
if (_type == EntityTypes::Light) {
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IS_SPOTLIGHT, isSpotlight);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_INTENSITY, intensity);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FALLOFF_RADIUS, falloffRadius);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EXPONENT, exponent);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CUTOFF, cutoff);
|
||||
}
|
||||
|
@ -597,6 +599,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
|
|||
COPY_PROPERTY_FROM_QSCRIPTVALUE(dynamic, bool, setDynamic);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(isSpotlight, bool, setIsSpotlight);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(intensity, float, setIntensity);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(falloffRadius, float, setFalloffRadius);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(exponent, float, setExponent);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(cutoff, float, setCutoff);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(locked, bool, setLocked);
|
||||
|
@ -762,6 +765,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
|
|||
ADD_PROPERTY_TO_MAP(PROP_DYNAMIC, unused, dynamic, unused);
|
||||
ADD_PROPERTY_TO_MAP(PROP_IS_SPOTLIGHT, IsSpotlight, isSpotlight, bool);
|
||||
ADD_PROPERTY_TO_MAP(PROP_INTENSITY, Intensity, intensity, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_FALLOFF_RADIUS, FalloffRadius, falloffRadius, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_EXPONENT, Exponent, exponent, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_CUTOFF, Cutoff, cutoff, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_LOCKED, Locked, locked, bool);
|
||||
|
@ -1043,6 +1047,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
|||
APPEND_ENTITY_PROPERTY(PROP_IS_SPOTLIGHT, properties.getIsSpotlight());
|
||||
APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor());
|
||||
APPEND_ENTITY_PROPERTY(PROP_INTENSITY, properties.getIntensity());
|
||||
APPEND_ENTITY_PROPERTY(PROP_FALLOFF_RADIUS, properties.getFalloffRadius());
|
||||
APPEND_ENTITY_PROPERTY(PROP_EXPONENT, properties.getExponent());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CUTOFF, properties.getCutoff());
|
||||
}
|
||||
|
@ -1332,6 +1337,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IS_SPOTLIGHT, bool, setIsSpotlight);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, xColor, setColor);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_INTENSITY, float, setIntensity);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FALLOFF_RADIUS, float, setFalloffRadius);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EXPONENT, float, setExponent);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CUTOFF, float, setCutoff);
|
||||
}
|
||||
|
@ -1477,6 +1483,7 @@ void EntityItemProperties::markAllChanged() {
|
|||
_dynamicChanged = true;
|
||||
|
||||
_intensityChanged = true;
|
||||
_falloffRadiusChanged = true;
|
||||
_exponentChanged = true;
|
||||
_cutoffChanged = true;
|
||||
_lockedChanged = true;
|
||||
|
@ -1719,6 +1726,9 @@ QList<QString> EntityItemProperties::listChangedProperties() {
|
|||
if (intensityChanged()) {
|
||||
out += "intensity";
|
||||
}
|
||||
if (falloffRadiusChanged()) {
|
||||
out += "falloffRadius";
|
||||
}
|
||||
if (exponentChanged()) {
|
||||
out += "exponent";
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include "EntityItemPropertiesMacros.h"
|
||||
#include "EntityTypes.h"
|
||||
#include "EntityPropertyFlags.h"
|
||||
#include "LightEntityItem.h"
|
||||
#include "LineEntityItem.h"
|
||||
#include "ParticleEffectEntityItem.h"
|
||||
#include "PolyVoxEntityItem.h"
|
||||
|
@ -129,10 +130,11 @@ public:
|
|||
DEFINE_PROPERTY(PROP_COLLISIONLESS, Collisionless, collisionless, bool, ENTITY_ITEM_DEFAULT_COLLISIONLESS);
|
||||
DEFINE_PROPERTY(PROP_COLLISION_MASK, CollisionMask, collisionMask, uint8_t, ENTITY_COLLISION_MASK_DEFAULT);
|
||||
DEFINE_PROPERTY(PROP_DYNAMIC, Dynamic, dynamic, bool, ENTITY_ITEM_DEFAULT_DYNAMIC);
|
||||
DEFINE_PROPERTY(PROP_IS_SPOTLIGHT, IsSpotlight, isSpotlight, bool, false);
|
||||
DEFINE_PROPERTY(PROP_INTENSITY, Intensity, intensity, float, 1.0f);
|
||||
DEFINE_PROPERTY(PROP_EXPONENT, Exponent, exponent, float, 0.0f);
|
||||
DEFINE_PROPERTY(PROP_CUTOFF, Cutoff, cutoff, float, ENTITY_ITEM_DEFAULT_CUTOFF);
|
||||
DEFINE_PROPERTY(PROP_IS_SPOTLIGHT, IsSpotlight, isSpotlight, bool, LightEntityItem::DEFAULT_IS_SPOTLIGHT);
|
||||
DEFINE_PROPERTY(PROP_INTENSITY, Intensity, intensity, float, LightEntityItem::DEFAULT_INTENSITY);
|
||||
DEFINE_PROPERTY(PROP_FALLOFF_RADIUS, FalloffRadius, falloffRadius, float, LightEntityItem::DEFAULT_FALLOFF_RADIUS);
|
||||
DEFINE_PROPERTY(PROP_EXPONENT, Exponent, exponent, float, LightEntityItem::DEFAULT_EXPONENT);
|
||||
DEFINE_PROPERTY(PROP_CUTOFF, Cutoff, cutoff, float, LightEntityItem::DEFAULT_CUTOFF);
|
||||
DEFINE_PROPERTY(PROP_LOCKED, Locked, locked, bool, ENTITY_ITEM_DEFAULT_LOCKED);
|
||||
DEFINE_PROPERTY_REF(PROP_TEXTURES, Textures, textures, QString, "");
|
||||
DEFINE_PROPERTY_REF(PROP_USER_DATA, UserData, userData, QString, ENTITY_ITEM_DEFAULT_USER_DATA);
|
||||
|
@ -359,6 +361,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
|
|||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Dynamic, dynamic, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, IsSpotlight, isSpotlight, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Intensity, intensity, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, FalloffRadius, falloffRadius, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Exponent, exponent, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Cutoff, cutoff, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Locked, locked, "");
|
||||
|
|
|
@ -72,8 +72,6 @@ const bool ENTITY_ITEM_DEFAULT_COLLISIONLESS = false;
|
|||
const bool ENTITY_ITEM_DEFAULT_DYNAMIC = false;
|
||||
const bool ENTITY_ITEM_DEFAULT_BILLBOARDED = false;
|
||||
|
||||
const float ENTITY_ITEM_DEFAULT_CUTOFF = PI / 2;
|
||||
|
||||
const QString ENTITY_ITEM_DEFAULT_NAME = QString("");
|
||||
|
||||
#endif // hifi_EntityItemPropertiesDefaults_h
|
||||
|
|
|
@ -167,6 +167,8 @@ enum EntityPropertyList {
|
|||
|
||||
PROP_COLLISION_MASK, // one byte of collision group flags
|
||||
|
||||
PROP_FALLOFF_RADIUS, // for Light entity
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ATTENTION: add new properties to end of list just ABOVE this line
|
||||
PROP_AFTER_LAST_ITEM,
|
||||
|
|
|
@ -85,7 +85,7 @@ void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params)
|
|||
forEachEntity([&](EntityItemPointer entity) {
|
||||
entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params));
|
||||
});
|
||||
|
||||
|
||||
// TODO: some of these inserts might be redundant!!!
|
||||
extraEncodeData->insert(this, entityTreeElementExtraEncodeData);
|
||||
}
|
||||
|
@ -96,39 +96,39 @@ bool EntityTreeElement::shouldIncludeChildData(int childIndex, EncodeBitstreamPa
|
|||
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
|
||||
|
||||
if (extraEncodeData->contains(this)) {
|
||||
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
|
||||
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
|
||||
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this));
|
||||
|
||||
|
||||
bool childCompleted = entityTreeElementExtraEncodeData->childCompleted[childIndex];
|
||||
|
||||
|
||||
// If we haven't completely sent the child yet, then we should include it
|
||||
return !childCompleted;
|
||||
}
|
||||
|
||||
|
||||
// I'm not sure this should ever happen, since we should have the extra encode data if we're considering
|
||||
// the child data for this element
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EntityTreeElement::shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const {
|
||||
bool EntityTreeElement::shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const {
|
||||
EntityTreeElementPointer childElement = getChildAtIndex(childIndex);
|
||||
if (childElement->alreadyFullyEncoded(params)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true; // if we don't know otherwise than recurse!
|
||||
}
|
||||
|
||||
bool EntityTreeElement::alreadyFullyEncoded(EncodeBitstreamParams& params) const {
|
||||
bool EntityTreeElement::alreadyFullyEncoded(EncodeBitstreamParams& params) const {
|
||||
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
|
||||
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
|
||||
|
||||
if (extraEncodeData->contains(this)) {
|
||||
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
|
||||
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
|
||||
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this));
|
||||
|
||||
// If we know that ALL subtrees below us have already been recursed, then we don't
|
||||
// If we know that ALL subtrees below us have already been recursed, then we don't
|
||||
// need to recurse this child.
|
||||
return entityTreeElementExtraEncodeData->subtreeCompleted;
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ void EntityTreeElement::updateEncodedData(int childIndex, AppendState childAppen
|
|||
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
|
||||
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
|
||||
if (extraEncodeData->contains(this)) {
|
||||
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
|
||||
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
|
||||
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this));
|
||||
|
||||
if (childAppendState == OctreeElement::COMPLETED) {
|
||||
|
@ -155,7 +155,7 @@ void EntityTreeElement::updateEncodedData(int childIndex, AppendState childAppen
|
|||
|
||||
void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) const {
|
||||
const bool wantDebug = false;
|
||||
|
||||
|
||||
if (wantDebug) {
|
||||
qCDebug(entities) << "EntityTreeElement::elementEncodeComplete() element:" << _cube;
|
||||
}
|
||||
|
@ -188,7 +188,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con
|
|||
// If we've encoding this element before... but we're coming back a second time in an attempt to
|
||||
// encoud our parent... this might happen.
|
||||
if (extraEncodeData->contains(childElement.get())) {
|
||||
EntityTreeElementExtraEncodeData* childExtraEncodeData
|
||||
EntityTreeElementExtraEncodeData* childExtraEncodeData
|
||||
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(childElement.get()));
|
||||
|
||||
if (wantDebug) {
|
||||
|
@ -197,7 +197,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con
|
|||
qCDebug(entities) << " childExtraEncodeData->elementCompleted:" << childExtraEncodeData->elementCompleted;
|
||||
qCDebug(entities) << " childExtraEncodeData->subtreeCompleted:" << childExtraEncodeData->subtreeCompleted;
|
||||
}
|
||||
|
||||
|
||||
if (childElement->isLeaf() && childExtraEncodeData->elementCompleted) {
|
||||
if (wantDebug) {
|
||||
qCDebug(entities) << " CHILD IS LEAF -- AND CHILD ELEMENT DATA COMPLETED!!!";
|
||||
|
@ -217,24 +217,24 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con
|
|||
qCDebug(entities) << " WAS elementCompleted:" << thisExtraEncodeData->elementCompleted;
|
||||
qCDebug(entities) << " WAS subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted;
|
||||
}
|
||||
|
||||
|
||||
thisExtraEncodeData->subtreeCompleted = !someChildTreeNotComplete;
|
||||
|
||||
if (wantDebug) {
|
||||
qCDebug(entities) << " NOW elementCompleted:" << thisExtraEncodeData->elementCompleted;
|
||||
qCDebug(entities) << " NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted;
|
||||
|
||||
|
||||
if (thisExtraEncodeData->subtreeCompleted) {
|
||||
qCDebug(entities) << " YEAH!!!!! >>>>>>>>>>>>>> NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData* packetData,
|
||||
OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData* packetData,
|
||||
EncodeBitstreamParams& params) const {
|
||||
|
||||
OctreeElement::AppendState appendElementState = OctreeElement::COMPLETED; // assume the best...
|
||||
|
||||
|
||||
// first, check the params.extraEncodeData to see if there's any partial re-encode data for this element
|
||||
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
|
||||
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData = NULL;
|
||||
|
@ -280,7 +280,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
|
|||
QVector<uint16_t> indexesOfEntitiesToInclude;
|
||||
|
||||
// It's possible that our element has been previous completed. In this case we'll simply not include any of our
|
||||
// entities for encoding. This is needed because we encode the element data at the "parent" level, and so we
|
||||
// entities for encoding. This is needed because we encode the element data at the "parent" level, and so we
|
||||
// need to handle the case where our sibling elements need encoding but we don't.
|
||||
if (!entityTreeElementExtraEncodeData->elementCompleted) {
|
||||
for (uint16_t i = 0; i < _entityItems.size(); i++) {
|
||||
|
@ -304,15 +304,13 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
|
|||
// frustum culling on rendering.
|
||||
bool success;
|
||||
AACube entityCube = entity->getQueryAACube(success);
|
||||
if (!success || params.viewFrustum->cubeInFrustum(entityCube) == ViewFrustum::OUTSIDE) {
|
||||
if (!success || !params.viewFrustum->cubeIntersectsKeyhole(entityCube)) {
|
||||
includeThisEntity = false; // out of view, don't include it
|
||||
}
|
||||
|
||||
// Now check the size of the entity, it's possible that a "too small to see" entity is included in a
|
||||
// larger octree cell because of its position (for example if it crosses the boundary of a cell it
|
||||
// pops to the next higher cell. So we want to check to see that the entity is large enough to be seen
|
||||
// before we consider including it.
|
||||
if (includeThisEntity) {
|
||||
} else {
|
||||
// Check the size of the entity, it's possible that a "too small to see" entity is included in a
|
||||
// larger octree cell because of its position (for example if it crosses the boundary of a cell it
|
||||
// pops to the next higher cell. So we want to check to see that the entity is large enough to be seen
|
||||
// before we consider including it.
|
||||
success = true;
|
||||
// we can't cull a parent-entity by its dimensions because the child may be larger. we need to
|
||||
// avoid sending details about a child but not the parent. the parent's queryAACube should have
|
||||
|
@ -397,7 +395,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
|
|||
// this octree element.
|
||||
if (extraEncodeData && entityTreeElementExtraEncodeData) {
|
||||
|
||||
// After processing, if we are PARTIAL or COMPLETED then we need to re-include our extra data.
|
||||
// After processing, if we are PARTIAL or COMPLETED then we need to re-include our extra data.
|
||||
// Only our parent can remove our extra data in these cases and only after it knows that all of its
|
||||
// children have been encoded.
|
||||
// If we weren't able to encode ANY data about ourselves, then we go ahead and remove our element data
|
||||
|
@ -412,7 +410,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
|
|||
extraEncodeData->insert(this, entityTreeElementExtraEncodeData);
|
||||
}
|
||||
} else {
|
||||
|
||||
|
||||
// If we weren't previously completed, check to see if we are
|
||||
if (!entityTreeElementExtraEncodeData->elementCompleted) {
|
||||
// If all of our items have been encoded, then we are complete as an element.
|
||||
|
@ -426,9 +424,9 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
|
|||
}
|
||||
}
|
||||
|
||||
// Determine if no entities at all were able to fit
|
||||
// Determine if no entities at all were able to fit
|
||||
bool noEntitiesFit = (numberOfEntities > 0 && actualNumberOfEntities == 0);
|
||||
|
||||
|
||||
// If we wrote fewer entities than we expected, update the number of entities in our packet
|
||||
bool successUpdateEntityCount = true;
|
||||
if (numberOfEntities != actualNumberOfEntities) {
|
||||
|
@ -504,7 +502,7 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3
|
|||
glm::vec3 clampedMax = glm::clamp(maxPoint, (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE);
|
||||
|
||||
if (_cube.contains(clampedMin) && _cube.contains(clampedMax)) {
|
||||
|
||||
|
||||
// If our child would be smaller than our smallest reasonable element, then we are the best fit.
|
||||
float childScale = _cube.getScale() / 2.0f;
|
||||
if (childScale <= SMALLEST_REASONABLE_OCTREE_ELEMENT_SCALE) {
|
||||
|
@ -524,7 +522,7 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3
|
|||
|
||||
bool EntityTreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
||||
const QVector<EntityItemID>& entityIdsToDiscard, void** intersectedObject, bool precisionPicking) {
|
||||
|
||||
keepSearching = true; // assume that we will continue searching after this.
|
||||
|
@ -607,7 +605,7 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con
|
|||
|
||||
// we can use the AABox's ray intersection by mapping our origin and direction into the entity frame
|
||||
// and testing intersection there.
|
||||
if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance,
|
||||
if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance,
|
||||
localFace, localSurfaceNormal)) {
|
||||
if (localDistance < distance) {
|
||||
// now ask the entity if we actually intersect
|
||||
|
@ -862,12 +860,12 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
|
|||
if (this == _myTree->getRoot().get() && args.bitstreamVersion < VERSION_ROOT_ELEMENT_HAS_DATA) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
const unsigned char* dataAt = data;
|
||||
int bytesRead = 0;
|
||||
uint16_t numberOfEntities = 0;
|
||||
int expectedBytesPerEntity = EntityItem::expectedBytes();
|
||||
|
||||
|
||||
args.elementsPerPacket++;
|
||||
|
||||
if (bytesLeftToRead >= (int)sizeof(numberOfEntities)) {
|
||||
|
@ -947,7 +945,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
|
|||
entityItem->recordCreationTime();
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Recieved packet for previously deleted entity [" <<
|
||||
qDebug() << "Recieved packet for previously deleted entity [" <<
|
||||
entityItem->getID() << "] ignoring. (inside " << __FUNCTION__ << ")";
|
||||
}
|
||||
}
|
||||
|
@ -959,7 +957,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
|
@ -990,7 +988,7 @@ bool EntityTreeElement::pruneChildren() {
|
|||
bool somethingPruned = false;
|
||||
for (int childIndex = 0; childIndex < NUMBER_OF_CHILDREN; childIndex++) {
|
||||
EntityTreeElementPointer child = getChildAtIndex(childIndex);
|
||||
|
||||
|
||||
// if my child is a leaf, but has no entities, then it's safe to delete my child
|
||||
if (child && child->isLeaf() && !child->hasEntities()) {
|
||||
deleteChildAtIndex(childIndex);
|
||||
|
@ -1040,4 +1038,4 @@ void EntityTreeElement::debugDump() {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -21,6 +21,12 @@
|
|||
#include "EntityTreeElement.h"
|
||||
#include "LightEntityItem.h"
|
||||
|
||||
const bool LightEntityItem::DEFAULT_IS_SPOTLIGHT = false;
|
||||
const float LightEntityItem::DEFAULT_INTENSITY = 1.0f;
|
||||
const float LightEntityItem::DEFAULT_FALLOFF_RADIUS = 0.1f;
|
||||
const float LightEntityItem::DEFAULT_EXPONENT = 0.0f;
|
||||
const float LightEntityItem::DEFAULT_CUTOFF = PI / 2.0f;
|
||||
|
||||
bool LightEntityItem::_lightsArePickable = false;
|
||||
|
||||
EntityItemPointer LightEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
|
@ -32,12 +38,7 @@ EntityItemPointer LightEntityItem::factory(const EntityItemID& entityID, const E
|
|||
// our non-pure virtual subclass for now...
|
||||
LightEntityItem::LightEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) {
|
||||
_type = EntityTypes::Light;
|
||||
|
||||
// default property values
|
||||
_color[RED_INDEX] = _color[GREEN_INDEX] = _color[BLUE_INDEX] = 0;
|
||||
_intensity = 1.0f;
|
||||
_exponent = 0.0f;
|
||||
_cutoff = PI;
|
||||
}
|
||||
|
||||
void LightEntityItem::setDimensions(const glm::vec3& value) {
|
||||
|
@ -62,10 +63,15 @@ EntityItemProperties LightEntityItem::getProperties(EntityPropertyFlags desiredP
|
|||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(intensity, getIntensity);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(exponent, getExponent);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cutoff, getCutoff);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(falloffRadius, getFalloffRadius);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
void LightEntityItem::setFalloffRadius(float value) {
|
||||
_falloffRadius = glm::max(value, 0.0f);
|
||||
}
|
||||
|
||||
void LightEntityItem::setIsSpotlight(bool value) {
|
||||
if (value != _isSpotlight) {
|
||||
_isSpotlight = value;
|
||||
|
@ -101,6 +107,7 @@ bool LightEntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(intensity, setIntensity);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(exponent, setExponent);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cutoff, setCutoff);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(falloffRadius, setFalloffRadius);
|
||||
|
||||
if (somethingChanged) {
|
||||
bool wantDebug = false;
|
||||
|
@ -150,6 +157,7 @@ int LightEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
|||
READ_ENTITY_PROPERTY(PROP_INTENSITY, float, setIntensity);
|
||||
READ_ENTITY_PROPERTY(PROP_EXPONENT, float, setExponent);
|
||||
READ_ENTITY_PROPERTY(PROP_CUTOFF, float, setCutoff);
|
||||
READ_ENTITY_PROPERTY(PROP_FALLOFF_RADIUS, float, setFalloffRadius);
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
|
@ -164,6 +172,7 @@ EntityPropertyFlags LightEntityItem::getEntityProperties(EncodeBitstreamParams&
|
|||
requestedProperties += PROP_INTENSITY;
|
||||
requestedProperties += PROP_EXPONENT;
|
||||
requestedProperties += PROP_CUTOFF;
|
||||
requestedProperties += PROP_FALLOFF_RADIUS;
|
||||
return requestedProperties;
|
||||
}
|
||||
|
||||
|
@ -181,4 +190,5 @@ void LightEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit
|
|||
APPEND_ENTITY_PROPERTY(PROP_INTENSITY, getIntensity());
|
||||
APPEND_ENTITY_PROPERTY(PROP_EXPONENT, getExponent());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CUTOFF, getCutoff());
|
||||
APPEND_ENTITY_PROPERTY(PROP_FALLOFF_RADIUS, getFalloffRadius());
|
||||
}
|
||||
|
|
|
@ -16,6 +16,12 @@
|
|||
|
||||
class LightEntityItem : public EntityItem {
|
||||
public:
|
||||
static const bool DEFAULT_IS_SPOTLIGHT;
|
||||
static const float DEFAULT_INTENSITY;
|
||||
static const float DEFAULT_FALLOFF_RADIUS;
|
||||
static const float DEFAULT_EXPONENT;
|
||||
static const float DEFAULT_CUTOFF;
|
||||
|
||||
static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
|
||||
LightEntityItem(const EntityItemID& entityItemID);
|
||||
|
@ -65,6 +71,9 @@ public:
|
|||
float getIntensity() const { return _intensity; }
|
||||
void setIntensity(float value) { _intensity = value; }
|
||||
|
||||
float getFalloffRadius() const { return _falloffRadius; }
|
||||
void setFalloffRadius(float value);
|
||||
|
||||
float getExponent() const { return _exponent; }
|
||||
void setExponent(float value) { _exponent = value; }
|
||||
|
||||
|
@ -78,10 +87,11 @@ protected:
|
|||
|
||||
// properties of a light
|
||||
rgbColor _color;
|
||||
bool _isSpotlight;
|
||||
float _intensity;
|
||||
float _exponent;
|
||||
float _cutoff;
|
||||
bool _isSpotlight { DEFAULT_IS_SPOTLIGHT };
|
||||
float _intensity { DEFAULT_INTENSITY };
|
||||
float _falloffRadius { DEFAULT_FALLOFF_RADIUS };
|
||||
float _exponent { DEFAULT_EXPONENT };
|
||||
float _cutoff { DEFAULT_CUTOFF };
|
||||
|
||||
static bool _lightsArePickable;
|
||||
};
|
||||
|
|
|
@ -21,6 +21,7 @@ Framebuffer::~Framebuffer() {
|
|||
Framebuffer* Framebuffer::create() {
|
||||
auto framebuffer = new Framebuffer();
|
||||
framebuffer->_renderBuffers.resize(MAX_NUM_RENDER_BUFFERS);
|
||||
framebuffer->_colorStamps.resize(MAX_NUM_RENDER_BUFFERS, 0);
|
||||
return framebuffer;
|
||||
}
|
||||
|
||||
|
@ -174,6 +175,8 @@ int Framebuffer::setRenderBuffer(uint32 slot, const TexturePointer& texture, uin
|
|||
}
|
||||
}
|
||||
|
||||
++_colorStamps[slot];
|
||||
|
||||
updateSize(texture);
|
||||
|
||||
// assign the new one
|
||||
|
@ -190,6 +193,7 @@ int Framebuffer::setRenderBuffer(uint32 slot, const TexturePointer& texture, uin
|
|||
}
|
||||
|
||||
void Framebuffer::removeRenderBuffers() {
|
||||
|
||||
if (isSwapchain()) {
|
||||
return;
|
||||
}
|
||||
|
@ -230,6 +234,7 @@ uint32 Framebuffer::getRenderBufferSubresource(uint32 slot) const {
|
|||
}
|
||||
|
||||
bool Framebuffer::setDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource) {
|
||||
++_depthStamp;
|
||||
if (isSwapchain()) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -135,10 +135,15 @@ public:
|
|||
static uint32 getMaxNumRenderBuffers() { return MAX_NUM_RENDER_BUFFERS; }
|
||||
|
||||
const GPUObjectPointer gpuObject {};
|
||||
|
||||
|
||||
Stamp getDepthStamp() const { return _depthStamp; }
|
||||
const std::vector<Stamp>& getColorStamps() const { return _colorStamps; }
|
||||
|
||||
protected:
|
||||
SwapchainPointer _swapchain;
|
||||
|
||||
Stamp _depthStamp { 0 };
|
||||
std::vector<Stamp> _colorStamps;
|
||||
TextureViews _renderBuffers;
|
||||
TextureView _depthStencilBuffer;
|
||||
|
||||
|
|
|
@ -173,6 +173,9 @@ public:
|
|||
public:
|
||||
GLuint _fbo = 0;
|
||||
std::vector<GLenum> _colorBuffers;
|
||||
Stamp _depthStamp { 0 };
|
||||
std::vector<Stamp> _colorStamps;
|
||||
|
||||
|
||||
GLFramebuffer();
|
||||
~GLFramebuffer();
|
||||
|
|
|
@ -27,8 +27,15 @@ GLBackend::GLFramebuffer::~GLFramebuffer() {
|
|||
GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffer) {
|
||||
GLFramebuffer* object = Backend::getGPUObject<GLBackend::GLFramebuffer>(framebuffer);
|
||||
|
||||
bool needsUpate { false };
|
||||
if (!object ||
|
||||
framebuffer.getDepthStamp() != object->_depthStamp ||
|
||||
framebuffer.getColorStamps() != object->_colorStamps) {
|
||||
needsUpate = true;
|
||||
}
|
||||
|
||||
// If GPU object already created and in sync
|
||||
if (object) {
|
||||
if (!needsUpate) {
|
||||
return object;
|
||||
} else if (framebuffer.isEmpty()) {
|
||||
// NO framebuffer definition yet so let's avoid thinking
|
||||
|
@ -37,94 +44,112 @@ GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffe
|
|||
|
||||
// need to have a gpu object?
|
||||
if (!object) {
|
||||
GLint currentFBO;
|
||||
// All is green, assign the gpuobject to the Framebuffer
|
||||
object = new GLFramebuffer();
|
||||
Backend::setGPUObject(framebuffer, object);
|
||||
glGenFramebuffers(1, &object->_fbo);
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
if (needsUpate) {
|
||||
GLint currentFBO = -1;
|
||||
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤tFBO);
|
||||
|
||||
GLuint fbo;
|
||||
glGenFramebuffers(1, &fbo);
|
||||
(void) CHECK_GL_ERROR();
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, object->_fbo);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
GLTexture* gltexture = nullptr;
|
||||
TexturePointer surface;
|
||||
if (framebuffer.getColorStamps() != object->_colorStamps) {
|
||||
if (framebuffer.hasColor()) {
|
||||
object->_colorBuffers.clear();
|
||||
static const GLenum colorAttachments[] = {
|
||||
GL_COLOR_ATTACHMENT0,
|
||||
GL_COLOR_ATTACHMENT1,
|
||||
GL_COLOR_ATTACHMENT2,
|
||||
GL_COLOR_ATTACHMENT3,
|
||||
GL_COLOR_ATTACHMENT4,
|
||||
GL_COLOR_ATTACHMENT5,
|
||||
GL_COLOR_ATTACHMENT6,
|
||||
GL_COLOR_ATTACHMENT7,
|
||||
GL_COLOR_ATTACHMENT8,
|
||||
GL_COLOR_ATTACHMENT9,
|
||||
GL_COLOR_ATTACHMENT10,
|
||||
GL_COLOR_ATTACHMENT11,
|
||||
GL_COLOR_ATTACHMENT12,
|
||||
GL_COLOR_ATTACHMENT13,
|
||||
GL_COLOR_ATTACHMENT14,
|
||||
GL_COLOR_ATTACHMENT15 };
|
||||
|
||||
std::vector<GLenum> colorBuffers;
|
||||
if (framebuffer.hasColor()) {
|
||||
static const GLenum colorAttachments[] = {
|
||||
GL_COLOR_ATTACHMENT0,
|
||||
GL_COLOR_ATTACHMENT1,
|
||||
GL_COLOR_ATTACHMENT2,
|
||||
GL_COLOR_ATTACHMENT3,
|
||||
GL_COLOR_ATTACHMENT4,
|
||||
GL_COLOR_ATTACHMENT5,
|
||||
GL_COLOR_ATTACHMENT6,
|
||||
GL_COLOR_ATTACHMENT7,
|
||||
GL_COLOR_ATTACHMENT8,
|
||||
GL_COLOR_ATTACHMENT9,
|
||||
GL_COLOR_ATTACHMENT10,
|
||||
GL_COLOR_ATTACHMENT11,
|
||||
GL_COLOR_ATTACHMENT12,
|
||||
GL_COLOR_ATTACHMENT13,
|
||||
GL_COLOR_ATTACHMENT14,
|
||||
GL_COLOR_ATTACHMENT15 };
|
||||
int unit = 0;
|
||||
for (auto& b : framebuffer.getRenderBuffers()) {
|
||||
surface = b._texture;
|
||||
if (surface) {
|
||||
gltexture = GLBackend::syncGPUObject(*surface);
|
||||
} else {
|
||||
gltexture = nullptr;
|
||||
}
|
||||
|
||||
int unit = 0;
|
||||
for (auto& b : framebuffer.getRenderBuffers()) {
|
||||
auto surface = b._texture;
|
||||
if (surface) {
|
||||
auto gltexture = GLBackend::syncGPUObject(*surface);
|
||||
if (gltexture) {
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, gltexture->_texture, 0);
|
||||
object->_colorBuffers.push_back(colorAttachments[unit]);
|
||||
} else {
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, 0, 0);
|
||||
}
|
||||
colorBuffers.push_back(colorAttachments[unit]);
|
||||
unit++;
|
||||
}
|
||||
}
|
||||
}
|
||||
#if (GPU_FEATURE_PROFILE == GPU_LEGACY)
|
||||
// for reasons that i don't understand yet, it seems that on mac gl, a fbo must have a color buffer...
|
||||
else {
|
||||
GLuint renderBuffer = 0;
|
||||
glGenRenderbuffers(1, &renderBuffer);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, framebuffer.getWidth(), framebuffer.getHeight());
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer);
|
||||
(void) CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
#endif
|
||||
|
||||
|
||||
if (framebuffer.hasDepthStencil()) {
|
||||
auto surface = framebuffer.getDepthStencilBuffer();
|
||||
if (surface) {
|
||||
auto gltexture = GLBackend::syncGPUObject(*surface);
|
||||
if (gltexture) {
|
||||
GLenum attachement = GL_DEPTH_STENCIL_ATTACHMENT;
|
||||
if (!framebuffer.hasStencil()) {
|
||||
attachement = GL_DEPTH_ATTACHMENT;
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0);
|
||||
} else if (!framebuffer.hasDepth()) {
|
||||
attachement = GL_STENCIL_ATTACHMENT;
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0);
|
||||
} else {
|
||||
attachement = GL_DEPTH_STENCIL_ATTACHMENT;
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0);
|
||||
}
|
||||
(void) CHECK_GL_ERROR();
|
||||
}
|
||||
#if (GPU_FEATURE_PROFILE == GPU_LEGACY)
|
||||
// for reasons that i don't understand yet, it seems that on mac gl, a fbo must have a color buffer...
|
||||
else {
|
||||
GLuint renderBuffer = 0;
|
||||
glGenRenderbuffers(1, &renderBuffer);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, framebuffer.getWidth(), framebuffer.getHeight());
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer);
|
||||
(void) CHECK_GL_ERROR();
|
||||
}
|
||||
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
#endif
|
||||
object->_colorStamps = framebuffer.getColorStamps();
|
||||
}
|
||||
|
||||
GLenum attachement = GL_DEPTH_STENCIL_ATTACHMENT;
|
||||
if (!framebuffer.hasStencil()) {
|
||||
attachement = GL_DEPTH_ATTACHMENT;
|
||||
} else if (!framebuffer.hasDepth()) {
|
||||
attachement = GL_STENCIL_ATTACHMENT;
|
||||
}
|
||||
|
||||
if (framebuffer.getDepthStamp() != object->_depthStamp) {
|
||||
auto surface = framebuffer.getDepthStencilBuffer();
|
||||
if (framebuffer.hasDepthStencil() && surface) {
|
||||
gltexture = GLBackend::syncGPUObject(*surface);
|
||||
}
|
||||
|
||||
if (gltexture) {
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0);
|
||||
} else {
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, 0, 0);
|
||||
}
|
||||
object->_depthStamp = framebuffer.getDepthStamp();
|
||||
}
|
||||
|
||||
|
||||
// Last but not least, define where we draw
|
||||
if (!colorBuffers.empty()) {
|
||||
glDrawBuffers((GLsizei)colorBuffers.size(), colorBuffers.data());
|
||||
if (!object->_colorBuffers.empty()) {
|
||||
glDrawBuffers((GLsizei)object->_colorBuffers.size(), object->_colorBuffers.data());
|
||||
} else {
|
||||
glDrawBuffer( GL_NONE );
|
||||
}
|
||||
|
||||
// Now check for completness
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
|
||||
// restore the current framebuffer
|
||||
if (currentFBO != -1) {
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, currentFBO);
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
switch (status) {
|
||||
case GL_FRAMEBUFFER_COMPLETE :
|
||||
|
@ -147,20 +172,10 @@ GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffe
|
|||
qCDebug(gpulogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED.";
|
||||
break;
|
||||
}
|
||||
if (!result && fbo) {
|
||||
glDeleteFramebuffers( 1, &fbo );
|
||||
if (!result && object->_fbo) {
|
||||
glDeleteFramebuffers(1, &object->_fbo);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
// All is green, assign the gpuobject to the Framebuffer
|
||||
object = new GLFramebuffer();
|
||||
object->_fbo = fbo;
|
||||
object->_colorBuffers = colorBuffers;
|
||||
Backend::setGPUObject(framebuffer, object);
|
||||
|
||||
// restore the current framebuffer
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, currentFBO);
|
||||
}
|
||||
|
||||
return object;
|
||||
|
|
|
@ -12,19 +12,18 @@
|
|||
|
||||
using namespace model;
|
||||
|
||||
Light::Light() :
|
||||
_flags(0),
|
||||
_schemaBuffer(),
|
||||
_transform() {
|
||||
Light::Light() {
|
||||
// only if created from nothing shall we create the Buffer to store the properties
|
||||
Schema schema;
|
||||
_schemaBuffer = std::make_shared<gpu::Buffer>(sizeof(Schema), (const gpu::Byte*) &schema);
|
||||
updateLightRadius();
|
||||
}
|
||||
|
||||
Light::Light(const Light& light) :
|
||||
_flags(light._flags),
|
||||
_schemaBuffer(light._schemaBuffer),
|
||||
_transform(light._transform) {
|
||||
_transform(light._transform)
|
||||
{
|
||||
}
|
||||
|
||||
Light& Light::operator= (const Light& light) {
|
||||
|
@ -70,18 +69,36 @@ void Light::setAmbientIntensity(float intensity) {
|
|||
editSchema()._ambientIntensity = intensity;
|
||||
}
|
||||
|
||||
void Light::setFalloffRadius(float radius) {
|
||||
if (radius <= 0.0f) {
|
||||
radius = 0.1f;
|
||||
}
|
||||
editSchema()._attenuation.x = radius;
|
||||
updateLightRadius();
|
||||
}
|
||||
void Light::setMaximumRadius(float radius) {
|
||||
if (radius <= 0.f) {
|
||||
radius = 1.0f;
|
||||
}
|
||||
editSchema()._attenuation.w = radius;
|
||||
editSchema()._attenuation.y = radius;
|
||||
updateLightRadius();
|
||||
}
|
||||
|
||||
void Light::updateLightRadius() {
|
||||
float CutOffIntensityRatio = 0.05f;
|
||||
float surfaceRadius = getMaximumRadius() / (sqrtf((getIntensity() * std::max(std::max(getColor().x, getColor().y), getColor().z)) / CutOffIntensityRatio) - 1.0f);
|
||||
editSchema()._attenuation = Vec4(surfaceRadius, 1.0f/surfaceRadius, CutOffIntensityRatio, getMaximumRadius());
|
||||
// This function relies on the attenuation equation:
|
||||
// I = Li / (1 + (d + Lr)/Lr)^2
|
||||
// where I = calculated intensity, Li = light intensity, Lr = light falloff radius, d = distance from surface
|
||||
// see: https://imdoingitwrong.wordpress.com/2011/01/31/light-attenuation/
|
||||
// note that falloff radius replaces surface radius in linked example
|
||||
// This equation is biased back by Lr so that all lights act as true points, regardless of surface radii
|
||||
|
||||
const float MIN_CUTOFF_INTENSITY = 0.001f;
|
||||
// Get cutoff radius at minimum intensity
|
||||
float intensity = getIntensity() * std::max(std::max(getColor().x, getColor().y), getColor().z);
|
||||
float cutoffRadius = getFalloffRadius() * ((glm::sqrt(intensity / MIN_CUTOFF_INTENSITY) - 1) - 1);
|
||||
|
||||
// If it is less than max radius, store it to buffer to avoid extra shading
|
||||
editSchema()._attenuation.z = std::min(getMaximumRadius(), cutoffRadius);
|
||||
}
|
||||
|
||||
#include <math.h>
|
||||
|
|
|
@ -74,8 +74,17 @@ public:
|
|||
|
||||
bool isRanged() const { return (getType() == POINT) || (getType() == SPOT ); }
|
||||
|
||||
// FalloffRradius is the physical radius of the light sphere through which energy shines,
|
||||
// expressed in meters. It is used only to calculate the falloff curve of the light.
|
||||
// Actual rendered lights will all have surface radii approaching 0.
|
||||
void setFalloffRadius(float radius);
|
||||
float getFalloffRadius() const { return getSchema()._attenuation.x; }
|
||||
|
||||
// Maximum radius is the cutoff radius of the light energy, expressed in meters.
|
||||
// It is used to bound light entities, and *will not* affect the falloff curve of the light.
|
||||
// Setting it low will result in a noticeable cutoff.
|
||||
void setMaximumRadius(float radius);
|
||||
float getMaximumRadius() const { return getSchema()._attenuation.w; }
|
||||
float getMaximumRadius() const { return getSchema()._attenuation.y; }
|
||||
|
||||
// Spot properties
|
||||
bool isSpot() const { return getType() == SPOT; }
|
||||
|
@ -107,7 +116,7 @@ public:
|
|||
float _ambientIntensity{0.0f};
|
||||
Color _color{1.0f};
|
||||
float _intensity{1.0f};
|
||||
Vec4 _attenuation{1.0f};
|
||||
Vec4 _attenuation{0.1f, 1.0f, 0.0f, 0.0f};
|
||||
Vec4 _spot{0.0f, 0.0f, 0.0f, 0.0f};
|
||||
Vec4 _shadow{0.0f};
|
||||
|
||||
|
@ -120,7 +129,7 @@ public:
|
|||
|
||||
protected:
|
||||
|
||||
Flags _flags;
|
||||
Flags _flags{ 0 };
|
||||
UniformBufferView _schemaBuffer;
|
||||
Transform _transform;
|
||||
|
||||
|
|
|
@ -66,14 +66,6 @@ vec3 getLightColor(Light l) { return l._color.rgb; }
|
|||
float getLightIntensity(Light l) { return l._color.w; }
|
||||
float getLightAmbientIntensity(Light l) { return l._direction.w; }
|
||||
|
||||
float evalLightAttenuation(Light l, float r) {
|
||||
float d = max(r - l._attenuation.x, 0.0);
|
||||
float denom = d * l._attenuation.y + 1.0;
|
||||
float attenuation = 1.0 / (denom * denom);
|
||||
return max((attenuation - l._attenuation.z)/(1.0 - l._attenuation.z), 0.0);
|
||||
// return clamp(1.0/(l._attenuation.x + l._attenuation.y * r + l._attenuation.z * r * r), 0.0, 1.0);
|
||||
}
|
||||
|
||||
float getLightSpotAngleCos(Light l) {
|
||||
return l._spot.x;
|
||||
}
|
||||
|
@ -86,22 +78,33 @@ float evalLightSpotAttenuation(Light l, float cosA) {
|
|||
return pow(cosA, l._spot.w);
|
||||
}
|
||||
|
||||
float getLightSquareRadius(Light l) {
|
||||
return l._attenuation.w * l._attenuation.w;
|
||||
}
|
||||
|
||||
float getLightRadius(Light l) {
|
||||
return l._attenuation.w;
|
||||
return l._attenuation.x;
|
||||
}
|
||||
|
||||
float getLightAttenuationCutoff(Light l) {
|
||||
float getLightSquareRadius(Light l) {
|
||||
return getLightRadius(l) * getLightRadius(l);
|
||||
}
|
||||
|
||||
float getLightCutoffRadius(Light l) {
|
||||
return l._attenuation.z;
|
||||
}
|
||||
|
||||
float getLightCutoffSquareRadius(Light l) {
|
||||
return getLightCutoffRadius(l) * getLightCutoffRadius(l);
|
||||
}
|
||||
|
||||
float getLightShowContour(Light l) {
|
||||
return l._control.w;
|
||||
}
|
||||
|
||||
float evalLightAttenuation(Light l, float d) {
|
||||
float radius = getLightRadius(l);
|
||||
float denom = d / radius + 1.0;
|
||||
float attenuation = min(1.0, 1.0 / (denom * denom));
|
||||
return attenuation;
|
||||
}
|
||||
|
||||
SphericalHarmonics getLightAmbientSphere(Light l) {
|
||||
return l._ambientSphere;
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::EntityAdd:
|
||||
case PacketType::EntityEdit:
|
||||
case PacketType::EntityData:
|
||||
return VERSION_ATMOSPHERE_REMOVED;
|
||||
return VERSION_LIGHT_HAS_FALLOFF_RADIUS;
|
||||
case PacketType::AvatarData:
|
||||
case PacketType::BulkAvatarData:
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::SoftAttachmentSupport);
|
||||
|
|
|
@ -166,6 +166,7 @@ const PacketVersion VERSION_MODEL_ENTITIES_JOINTS_ON_WIRE = 53;
|
|||
const PacketVersion VERSION_ENTITITES_HAVE_QUERY_BOX = 54;
|
||||
const PacketVersion VERSION_ENTITITES_HAVE_COLLISION_MASK = 55;
|
||||
const PacketVersion VERSION_ATMOSPHERE_REMOVED = 56;
|
||||
const PacketVersion VERSION_LIGHT_HAS_FALLOFF_RADIUS = 57;
|
||||
|
||||
enum class AvatarMixerPacketVersion : PacketVersion {
|
||||
TranslationSupport = 17,
|
||||
|
|
|
@ -937,7 +937,7 @@ int Octree::encodeTreeBitstream(OctreeElementPointer element,
|
|||
params.stats->traversed(element);
|
||||
}
|
||||
|
||||
ViewFrustum::location parentLocationThisView = ViewFrustum::INTERSECT; // assume parent is in view, but not fully
|
||||
ViewFrustum::intersection parentLocationThisView = ViewFrustum::INTERSECT; // assume parent is in view, but not fully
|
||||
|
||||
int childBytesWritten = encodeTreeBitstreamRecursion(element, packetData, bag, params,
|
||||
currentEncodeLevel, parentLocationThisView);
|
||||
|
@ -974,7 +974,7 @@ int Octree::encodeTreeBitstream(OctreeElementPointer element,
|
|||
int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
||||
OctreePacketData* packetData, OctreeElementBag& bag,
|
||||
EncodeBitstreamParams& params, int& currentEncodeLevel,
|
||||
const ViewFrustum::location& parentLocationThisView) const {
|
||||
const ViewFrustum::intersection& parentLocationThisView) const {
|
||||
|
||||
|
||||
const bool wantDebug = false;
|
||||
|
@ -1013,7 +1013,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
}
|
||||
}
|
||||
|
||||
ViewFrustum::location nodeLocationThisView = ViewFrustum::INSIDE; // assume we're inside
|
||||
ViewFrustum::intersection nodeLocationThisView = ViewFrustum::INSIDE; // assume we're inside
|
||||
|
||||
// caller can pass NULL as viewFrustum if they want everything
|
||||
if (params.viewFrustum) {
|
||||
|
@ -1034,7 +1034,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
// if we are INSIDE, INTERSECT, or OUTSIDE
|
||||
if (parentLocationThisView != ViewFrustum::INSIDE) {
|
||||
assert(parentLocationThisView != ViewFrustum::OUTSIDE); // we shouldn't be here if our parent was OUTSIDE!
|
||||
nodeLocationThisView = element->inFrustum(*params.viewFrustum);
|
||||
nodeLocationThisView = element->computeViewIntersection(*params.viewFrustum);
|
||||
}
|
||||
|
||||
// If we're at a element that is out of view, then we can return, because no nodes below us will be in view!
|
||||
|
@ -1053,7 +1053,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
bool wasInView = false;
|
||||
|
||||
if (params.deltaViewFrustum && params.lastViewFrustum) {
|
||||
ViewFrustum::location location = element->inFrustum(*params.lastViewFrustum);
|
||||
ViewFrustum::intersection location = element->computeViewIntersection(*params.lastViewFrustum);
|
||||
|
||||
// If we're a leaf, then either intersect or inside is considered "formerly in view"
|
||||
if (element->isLeaf()) {
|
||||
|
@ -1237,7 +1237,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
bool childWasInView = false;
|
||||
|
||||
if (childElement && params.deltaViewFrustum && params.lastViewFrustum) {
|
||||
ViewFrustum::location location = childElement->inFrustum(*params.lastViewFrustum);
|
||||
ViewFrustum::intersection location = childElement->computeViewIntersection(*params.lastViewFrustum);
|
||||
|
||||
// If we're a leaf, then either intersect or inside is considered "formerly in view"
|
||||
if (childElement->isLeaf()) {
|
||||
|
|
|
@ -367,7 +367,7 @@ protected:
|
|||
int encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
||||
OctreePacketData* packetData, OctreeElementBag& bag,
|
||||
EncodeBitstreamParams& params, int& currentEncodeLevel,
|
||||
const ViewFrustum::location& parentLocationThisView) const;
|
||||
const ViewFrustum::intersection& parentLocationThisView) const;
|
||||
|
||||
static bool countOctreeElementsOperation(OctreeElementPointer element, void* extraData);
|
||||
|
||||
|
|
|
@ -458,8 +458,8 @@ float OctreeElement::getEnclosingRadius() const {
|
|||
return getScale() * sqrtf(3.0f) / 2.0f;
|
||||
}
|
||||
|
||||
ViewFrustum::location OctreeElement::inFrustum(const ViewFrustum& viewFrustum) const {
|
||||
return viewFrustum.cubeInFrustum(_cube);
|
||||
ViewFrustum::intersection OctreeElement::computeViewIntersection(const ViewFrustum& viewFrustum) const {
|
||||
return viewFrustum.calculateCubeKeyholeIntersection(_cube);
|
||||
}
|
||||
|
||||
// There are two types of nodes for which we want to "render"
|
||||
|
|
|
@ -49,20 +49,20 @@ protected:
|
|||
OctreeElement();
|
||||
|
||||
virtual OctreeElementPointer createNewElement(unsigned char * octalCode = NULL) = 0;
|
||||
|
||||
|
||||
public:
|
||||
virtual void init(unsigned char * octalCode); /// Your subclass must call init on construction.
|
||||
virtual ~OctreeElement();
|
||||
|
||||
// methods you can and should override to implement your tree functionality
|
||||
|
||||
|
||||
/// Adds a child to the current element. Override this if there is additional child initialization your class needs.
|
||||
virtual OctreeElementPointer addChildAtIndex(int childIndex);
|
||||
|
||||
/// Override this to implement LOD averaging on changes to the tree.
|
||||
/// Override this to implement LOD averaging on changes to the tree.
|
||||
virtual void calculateAverageFromChildren() { }
|
||||
|
||||
/// Override this to implement LOD collapsing and identical child pruning on changes to the tree.
|
||||
/// Override this to implement LOD collapsing and identical child pruning on changes to the tree.
|
||||
virtual bool collapseChildren() { return false; }
|
||||
|
||||
/// Should this element be considered to have content in it. This will be used in collision and ray casting methods.
|
||||
|
@ -72,12 +72,12 @@ public:
|
|||
/// Should this element be considered to have detailed content in it. Specifically should it be rendered.
|
||||
/// By default we assume that only leaves have detailed content, but some octrees may have different semantics.
|
||||
virtual bool hasDetailedContent() const { return isLeaf(); }
|
||||
|
||||
|
||||
/// Override this to break up large octree elements when an edit operation is performed on a smaller octree element.
|
||||
/// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the
|
||||
/// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the
|
||||
/// meaningful split would be to break the larger cube into smaller cubes of the same color/texture.
|
||||
virtual void splitChildren() { }
|
||||
|
||||
|
||||
/// Override to indicate that this element requires a split before editing lower elements in the octree
|
||||
virtual bool requiresSplit() const { return false; }
|
||||
|
||||
|
@ -88,17 +88,17 @@ public:
|
|||
virtual void initializeExtraEncodeData(EncodeBitstreamParams& params) { }
|
||||
virtual bool shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const { return true; }
|
||||
virtual bool shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const { return true; }
|
||||
|
||||
|
||||
virtual void updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const { }
|
||||
virtual void elementEncodeComplete(EncodeBitstreamParams& params) const { }
|
||||
|
||||
/// Override to serialize the state of this element. This is used for persistance and for transmission across the network.
|
||||
virtual AppendState appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const
|
||||
virtual AppendState appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const
|
||||
{ return COMPLETED; }
|
||||
|
||||
|
||||
/// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading
|
||||
/// from the network.
|
||||
virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args)
|
||||
virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args)
|
||||
{ return 0; }
|
||||
|
||||
/// Override to indicate that the item is currently rendered in the rendering engine. By default we assume that if
|
||||
|
@ -106,7 +106,7 @@ public:
|
|||
/// where an element is not actually rendering all should render elements. If the isRendered() state doesn't match the
|
||||
/// shouldRender() state, the tree will remark elements as changed even in cases there the elements have not changed.
|
||||
virtual bool isRendered() const { return getShouldRender(); }
|
||||
|
||||
|
||||
virtual bool deleteApproved() const { return true; }
|
||||
|
||||
virtual bool canRayIntersect() const { return isLeaf(); }
|
||||
|
@ -114,7 +114,7 @@ public:
|
|||
/// \param radius radius of sphere in meters
|
||||
/// \param[out] penetration pointing into cube from sphere
|
||||
/// \param penetratedObject unused
|
||||
virtual bool findSpherePenetration(const glm::vec3& center, float radius,
|
||||
virtual bool findSpherePenetration(const glm::vec3& center, float radius,
|
||||
glm::vec3& penetration, void** penetratedObject) const;
|
||||
|
||||
// Base class methods you don't need to implement
|
||||
|
@ -125,7 +125,7 @@ public:
|
|||
bool isParentOf(OctreeElementPointer possibleChild) const;
|
||||
|
||||
/// handles deletion of all descendants, returns false if delete not approved
|
||||
bool safeDeepDeleteChildAtIndex(int childIndex, int recursionCount = 0);
|
||||
bool safeDeepDeleteChildAtIndex(int childIndex, int recursionCount = 0);
|
||||
|
||||
|
||||
const AACube& getAACube() const { return _cube; }
|
||||
|
@ -134,8 +134,8 @@ public:
|
|||
int getLevel() const { return numberOfThreeBitSectionsInCode(getOctalCode()) + 1; }
|
||||
|
||||
float getEnclosingRadius() const;
|
||||
bool isInView(const ViewFrustum& viewFrustum) const { return inFrustum(viewFrustum) != ViewFrustum::OUTSIDE; }
|
||||
ViewFrustum::location inFrustum(const ViewFrustum& viewFrustum) const;
|
||||
bool isInView(const ViewFrustum& viewFrustum) const { return computeViewIntersection(viewFrustum) != ViewFrustum::OUTSIDE; }
|
||||
ViewFrustum::intersection computeViewIntersection(const ViewFrustum& viewFrustum) const;
|
||||
float distanceToCamera(const ViewFrustum& viewFrustum) const;
|
||||
float furthestDistanceToCamera(const ViewFrustum& viewFrustum) const;
|
||||
|
||||
|
@ -257,7 +257,7 @@ protected:
|
|||
static std::map<QString, uint16_t> _mapSourceUUIDsToKeys;
|
||||
static std::map<uint16_t, QString> _mapKeysToSourceUUIDs;
|
||||
|
||||
unsigned char _childBitmask; // 1 byte
|
||||
unsigned char _childBitmask; // 1 byte
|
||||
|
||||
bool _falseColored : 1, /// Client only, is this voxel false colored, 1 bit
|
||||
_isDirty : 1, /// Client only, has this voxel changed since being rendered, 1 bit
|
||||
|
|
|
@ -27,9 +27,9 @@ void OctreeHeadlessViewer::init() {
|
|||
void OctreeHeadlessViewer::queryOctree() {
|
||||
char serverType = getMyNodeType();
|
||||
PacketType packetType = getMyQueryMessageType();
|
||||
|
||||
|
||||
NodeToJurisdictionMap& jurisdictions = *_jurisdictionListener->getJurisdictions();
|
||||
|
||||
|
||||
bool wantExtraDebugging = false;
|
||||
|
||||
if (wantExtraDebugging) {
|
||||
|
@ -52,7 +52,7 @@ void OctreeHeadlessViewer::queryOctree() {
|
|||
_octreeQuery.setCameraNearClip(_viewFrustum.getNearClip());
|
||||
_octreeQuery.setCameraFarClip(_viewFrustum.getFarClip());
|
||||
_octreeQuery.setCameraEyeOffsetPosition(glm::vec3());
|
||||
_octreeQuery.setKeyholeRadius(_viewFrustum.getKeyholeRadius());
|
||||
_octreeQuery.setCameraCenterRadius(_viewFrustum.getCenterRadius());
|
||||
_octreeQuery.setOctreeSizeScale(_voxelSizeScale);
|
||||
_octreeQuery.setBoundaryLevelAdjust(_boundaryLevelAdjust);
|
||||
|
||||
|
@ -77,7 +77,7 @@ void OctreeHeadlessViewer::queryOctree() {
|
|||
if (jurisdictions.find(nodeUUID) == jurisdictions.end()) {
|
||||
unknownJurisdictionServers++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
const JurisdictionMap& map = (jurisdictions)[nodeUUID];
|
||||
|
||||
unsigned char* rootCode = map.getRootOctalCode();
|
||||
|
@ -91,9 +91,7 @@ void OctreeHeadlessViewer::queryOctree() {
|
|||
|
||||
if (foundRootDetails) {
|
||||
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
|
||||
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
|
||||
|
||||
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
|
||||
if ((bool)(_viewFrustum.calculateCubeKeyholeIntersection(serverBounds))) {
|
||||
inViewServers++;
|
||||
}
|
||||
}
|
||||
|
@ -164,13 +162,7 @@ void OctreeHeadlessViewer::queryOctree() {
|
|||
|
||||
if (foundRootDetails) {
|
||||
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
|
||||
|
||||
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
|
||||
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
|
||||
inView = true;
|
||||
} else {
|
||||
inView = false;
|
||||
}
|
||||
inView = (bool)(_viewFrustum.calculateCubeKeyholeIntersection(serverBounds));
|
||||
}
|
||||
|
||||
if (inView) {
|
||||
|
@ -208,7 +200,7 @@ void OctreeHeadlessViewer::queryOctree() {
|
|||
|
||||
// setup the query packet
|
||||
auto queryPacket = NLPacket::create(packetType);
|
||||
|
||||
|
||||
// read the data to our packet and set the payload size to fit the query
|
||||
int querySize = _octreeQuery.getBroadcastData(reinterpret_cast<unsigned char*>(queryPacket->getPayload()));
|
||||
queryPacket->setPayloadSize(querySize);
|
||||
|
|
|
@ -46,7 +46,8 @@ public slots:
|
|||
// setters for camera attributes
|
||||
void setPosition(const glm::vec3& position) { _viewFrustum.setPosition(position); }
|
||||
void setOrientation(const glm::quat& orientation) { _viewFrustum.setOrientation(orientation); }
|
||||
void setKeyholeRadius(float keyholdRadius) { _viewFrustum.setKeyholeRadius(keyholdRadius); }
|
||||
void setCenterRadius(float radius) { _viewFrustum.setCenterRadius(radius); }
|
||||
void setKeyholeRadius(float radius) { _viewFrustum.setCenterRadius(radius); } // TODO: remove this legacy support
|
||||
|
||||
// setters for LOD and PPS
|
||||
void setVoxelSizeScale(float sizeScale) { _voxelSizeScale = sizeScale; }
|
||||
|
|
|
@ -64,8 +64,8 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) {
|
|||
memcpy(destinationBuffer, &_boundaryLevelAdjust, sizeof(_boundaryLevelAdjust));
|
||||
destinationBuffer += sizeof(_boundaryLevelAdjust);
|
||||
|
||||
memcpy(destinationBuffer, &_keyholeRadius, sizeof(_keyholeRadius));
|
||||
destinationBuffer += sizeof(_keyholeRadius);
|
||||
memcpy(destinationBuffer, &_cameraCenterRadius, sizeof(_cameraCenterRadius));
|
||||
destinationBuffer += sizeof(_cameraCenterRadius);
|
||||
|
||||
return destinationBuffer - bufferStart;
|
||||
}
|
||||
|
@ -109,9 +109,9 @@ int OctreeQuery::parseData(ReceivedMessage& message) {
|
|||
|
||||
auto bytesRead = sourceBuffer - startPosition;
|
||||
auto bytesLeft = message.getSize() - bytesRead;
|
||||
if (bytesLeft >= (int)sizeof(_keyholeRadius)) {
|
||||
memcpy(&_keyholeRadius, sourceBuffer, sizeof(_keyholeRadius));
|
||||
sourceBuffer += sizeof(_keyholeRadius);
|
||||
if (bytesLeft >= (int)sizeof(_cameraCenterRadius)) {
|
||||
memcpy(&_cameraCenterRadius, sourceBuffer, sizeof(_cameraCenterRadius));
|
||||
sourceBuffer += sizeof(_cameraCenterRadius);
|
||||
}
|
||||
return sourceBuffer - startPosition;
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ public:
|
|||
float getCameraNearClip() const { return _cameraNearClip; }
|
||||
float getCameraFarClip() const { return _cameraFarClip; }
|
||||
const glm::vec3& getCameraEyeOffsetPosition() const { return _cameraEyeOffsetPosition; }
|
||||
float getKeyholeRadius() const { return _keyholeRadius; }
|
||||
float getCameraCenterRadius() const { return _cameraCenterRadius; }
|
||||
|
||||
glm::vec3 calculateCameraDirection() const;
|
||||
|
||||
|
@ -70,7 +70,7 @@ public:
|
|||
void setCameraNearClip(float nearClip) { _cameraNearClip = nearClip; }
|
||||
void setCameraFarClip(float farClip) { _cameraFarClip = farClip; }
|
||||
void setCameraEyeOffsetPosition(const glm::vec3& eyeOffsetPosition) { _cameraEyeOffsetPosition = eyeOffsetPosition; }
|
||||
void setKeyholeRadius(float keyholeRadius) { _keyholeRadius = keyholeRadius; }
|
||||
void setCameraCenterRadius(float radius) { _cameraCenterRadius = radius; }
|
||||
|
||||
// related to Octree Sending strategies
|
||||
int getMaxQueryPacketsPerSecond() const { return _maxQueryPPS; }
|
||||
|
@ -90,7 +90,7 @@ protected:
|
|||
float _cameraAspectRatio = 1.0f;
|
||||
float _cameraNearClip = 0.0f;
|
||||
float _cameraFarClip = 0.0f;
|
||||
float _keyholeRadius { 0.0f };
|
||||
float _cameraCenterRadius { 0.0f };
|
||||
glm::vec3 _cameraEyeOffsetPosition = glm::vec3(0.0f);
|
||||
|
||||
// octree server sending items
|
||||
|
|
|
@ -115,10 +115,6 @@ void ViewFrustum::calculate() {
|
|||
|
||||
// Our ModelViewProjection : multiplication of our 3 matrices (note: model is identity, so we can drop it)
|
||||
_ourModelViewProjectionMatrix = _projection * view; // Remember, matrix multiplication is the other way around
|
||||
|
||||
// Set up our keyhole bounding box...
|
||||
glm::vec3 corner = _position - _keyholeRadius;
|
||||
_keyholeBoundingCube = AACube(corner,(_keyholeRadius * 2.0f));
|
||||
}
|
||||
|
||||
//enum { TOP_PLANE = 0, BOTTOM_PLANE, LEFT_PLANE, RIGHT_PLANE, NEAR_PLANE, FAR_PLANE };
|
||||
|
@ -134,231 +130,129 @@ const char* ViewFrustum::debugPlaneName (int plane) const {
|
|||
return "Unknown";
|
||||
}
|
||||
|
||||
ViewFrustum::location ViewFrustum::pointInKeyhole(const glm::vec3& point) const {
|
||||
|
||||
ViewFrustum::location result = INTERSECT;
|
||||
|
||||
float distance = glm::distance(point, _position);
|
||||
if (distance > _keyholeRadius) {
|
||||
result = OUTSIDE;
|
||||
} else if (distance < _keyholeRadius) {
|
||||
result = INSIDE;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// To determine if two spheres intersect, simply calculate the distance between the centers of the two spheres.
|
||||
// If the distance is greater than the sum of the two sphere radii, they don’t intersect. Otherwise they intersect.
|
||||
// If the distance plus the radius of sphere A is less than the radius of sphere B then, sphere A is inside of sphere B
|
||||
ViewFrustum::location ViewFrustum::sphereInKeyhole(const glm::vec3& center, float radius) const {
|
||||
ViewFrustum::location result = INTERSECT;
|
||||
|
||||
float distance = glm::distance(center, _position);
|
||||
if (distance > (radius + _keyholeRadius)) {
|
||||
result = OUTSIDE;
|
||||
} else if ((distance + radius) < _keyholeRadius) {
|
||||
result = INSIDE;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// A box is inside a sphere if all of its corners are inside the sphere
|
||||
// A box intersects a sphere if any of its edges (as rays) interesect the sphere
|
||||
// A box is outside a sphere if none of its edges (as rays) interesect the sphere
|
||||
ViewFrustum::location ViewFrustum::cubeInKeyhole(const AACube& cube) const {
|
||||
|
||||
// First check to see if the cube is in the bounding cube for the sphere, if it's not, then we can short circuit
|
||||
// this and not check with sphere penetration which is more expensive
|
||||
if (!_keyholeBoundingCube.contains(cube)) {
|
||||
return OUTSIDE;
|
||||
}
|
||||
|
||||
glm::vec3 penetration;
|
||||
bool intersects = cube.findSpherePenetration(_position, _keyholeRadius, penetration);
|
||||
|
||||
ViewFrustum::location result = OUTSIDE;
|
||||
|
||||
// if the cube intersects the sphere, then it may also be inside... calculate further
|
||||
if (intersects) {
|
||||
result = INTERSECT;
|
||||
|
||||
// test all the corners, if they are all inside the sphere, the entire cube is in the sphere
|
||||
bool allPointsInside = true; // assume the best
|
||||
for (int v = BOTTOM_LEFT_NEAR; v < TOP_LEFT_FAR; v++) {
|
||||
glm::vec3 vertex = cube.getVertex((BoxVertex)v);
|
||||
if (!pointInKeyhole(vertex)) {
|
||||
allPointsInside = false;
|
||||
break;
|
||||
ViewFrustum::intersection ViewFrustum::calculateCubeFrustumIntersection(const AACube& cube) const {
|
||||
// only check against frustum
|
||||
ViewFrustum::intersection result = INSIDE;
|
||||
for(int i=0; i < 6; i++) {
|
||||
const glm::vec3& normal = _planes[i].getNormal();
|
||||
// check distance to farthest cube point
|
||||
if ( _planes[i].distance(cube.getFarthestVertex(normal)) < 0.0f) {
|
||||
return OUTSIDE;
|
||||
} else {
|
||||
// check distance to nearest cube point
|
||||
if (_planes[i].distance(cube.getNearestVertex(normal)) < 0.0f) {
|
||||
// cube straddles the plane
|
||||
result = INTERSECT;
|
||||
}
|
||||
}
|
||||
|
||||
if (allPointsInside) {
|
||||
result = INSIDE;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// A box is inside a sphere if all of its corners are inside the sphere
|
||||
// A box intersects a sphere if any of its edges (as rays) interesect the sphere
|
||||
// A box is outside a sphere if none of its edges (as rays) interesect the sphere
|
||||
ViewFrustum::location ViewFrustum::boxInKeyhole(const AABox& box) const {
|
||||
const float HALF_SQRT_THREE = 0.8660254f;
|
||||
|
||||
// First check to see if the box is in the bounding box for the sphere, if it's not, then we can short circuit
|
||||
// this and not check with sphere penetration which is more expensive
|
||||
if (!_keyholeBoundingCube.contains(box)) {
|
||||
return OUTSIDE;
|
||||
ViewFrustum::intersection ViewFrustum::calculateCubeKeyholeIntersection(const AACube& cube) const {
|
||||
// check against centeral sphere
|
||||
ViewFrustum::intersection sphereResult = INTERSECT;
|
||||
glm::vec3 cubeOffset = cube.calcCenter() - _position;
|
||||
float distance = glm::length(cubeOffset);
|
||||
if (distance > EPSILON) {
|
||||
glm::vec3 vertex = cube.getFarthestVertex(cubeOffset) - _position;
|
||||
if (glm::dot(vertex, cubeOffset) < _centerSphereRadius * distance) {
|
||||
// the most outward cube vertex is inside central sphere
|
||||
return INSIDE;
|
||||
}
|
||||
if (!cube.touchesSphere(_position, _centerSphereRadius)) {
|
||||
sphereResult = OUTSIDE;
|
||||
}
|
||||
} else if (_centerSphereRadius > HALF_SQRT_THREE * cube.getScale()) {
|
||||
// the cube is in center of sphere and its bounding radius is inside
|
||||
return INSIDE;
|
||||
}
|
||||
|
||||
glm::vec3 penetration;
|
||||
bool intersects = box.findSpherePenetration(_position, _keyholeRadius, penetration);
|
||||
// check against frustum
|
||||
ViewFrustum::intersection frustumResult = calculateCubeFrustumIntersection(cube);
|
||||
|
||||
ViewFrustum::location result = OUTSIDE;
|
||||
|
||||
// if the box intersects the sphere, then it may also be inside... calculate further
|
||||
if (intersects) {
|
||||
result = INTERSECT;
|
||||
|
||||
// test all the corners, if they are all inside the sphere, the entire box is in the sphere
|
||||
bool allPointsInside = true; // assume the best
|
||||
for (int v = BOTTOM_LEFT_NEAR; v < TOP_LEFT_FAR; v++) {
|
||||
glm::vec3 vertex = box.getVertex((BoxVertex)v);
|
||||
if (!pointInKeyhole(vertex)) {
|
||||
allPointsInside = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allPointsInside) {
|
||||
result = INSIDE;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return (frustumResult == OUTSIDE) ? sphereResult : frustumResult;
|
||||
}
|
||||
|
||||
ViewFrustum::location ViewFrustum::pointInFrustum(const glm::vec3& point, bool ignoreKeyhole) const {
|
||||
ViewFrustum::location regularResult = INSIDE;
|
||||
ViewFrustum::location keyholeResult = OUTSIDE;
|
||||
|
||||
// If we have a keyholeRadius, check that first, since it's cheaper
|
||||
if (!ignoreKeyhole && _keyholeRadius >= 0.0f) {
|
||||
keyholeResult = pointInKeyhole(point);
|
||||
|
||||
if (keyholeResult == INSIDE) {
|
||||
return keyholeResult;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're not known to be INSIDE the keyhole, then check the regular frustum
|
||||
bool ViewFrustum::pointIntersectsFrustum(const glm::vec3& point) const {
|
||||
// only check against frustum
|
||||
for(int i = 0; i < 6; ++i) {
|
||||
float distance = _planes[i].distance(point);
|
||||
if (distance < 0) {
|
||||
return keyholeResult; // escape early will be the value from checking the keyhole
|
||||
if (distance < 0.0f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return regularResult;
|
||||
return true;
|
||||
}
|
||||
|
||||
ViewFrustum::location ViewFrustum::sphereInFrustum(const glm::vec3& center, float radius) const {
|
||||
ViewFrustum::location regularResult = INSIDE;
|
||||
ViewFrustum::location keyholeResult = OUTSIDE;
|
||||
|
||||
// If we have a keyholeRadius, check that first, since it's cheaper
|
||||
if (_keyholeRadius >= 0.0f) {
|
||||
keyholeResult = sphereInKeyhole(center, radius);
|
||||
}
|
||||
if (keyholeResult == INSIDE) {
|
||||
return keyholeResult;
|
||||
}
|
||||
|
||||
float distance;
|
||||
bool ViewFrustum::sphereIntersectsFrustum(const glm::vec3& center, float radius) const {
|
||||
// only check against frustum
|
||||
for(int i=0; i < 6; i++) {
|
||||
distance = _planes[i].distance(center);
|
||||
float distance = _planes[i].distance(center);
|
||||
if (distance < -radius) {
|
||||
// This is outside the regular frustum, so just return the value from checking the keyhole
|
||||
return keyholeResult;
|
||||
} else if (distance < radius) {
|
||||
regularResult = INTERSECT;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return regularResult;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
ViewFrustum::location ViewFrustum::cubeInFrustum(const AACube& cube) const {
|
||||
|
||||
ViewFrustum::location regularResult = INSIDE;
|
||||
ViewFrustum::location keyholeResult = OUTSIDE;
|
||||
|
||||
// If we have a keyholeRadius, check that first, since it's cheaper
|
||||
if (_keyholeRadius >= 0.0f) {
|
||||
keyholeResult = cubeInKeyhole(cube);
|
||||
}
|
||||
if (keyholeResult == INSIDE) {
|
||||
return keyholeResult;
|
||||
}
|
||||
|
||||
// TODO: These calculations are expensive, taking up 80% of our time in this function.
|
||||
// This appears to be expensive because we have to test the distance to each plane.
|
||||
// One suggested optimization is to first check against the approximated cone. We might
|
||||
// also be able to test against the cone to the bounding sphere of the box.
|
||||
bool ViewFrustum::boxIntersectsFrustum(const AABox& box) const {
|
||||
// only check against frustum
|
||||
for(int i=0; i < 6; i++) {
|
||||
const glm::vec3& normal = _planes[i].getNormal();
|
||||
const glm::vec3& boxVertexP = cube.getVertexP(normal);
|
||||
float planeToBoxVertexPDistance = _planes[i].distance(boxVertexP);
|
||||
|
||||
const glm::vec3& boxVertexN = cube.getVertexN(normal);
|
||||
float planeToBoxVertexNDistance = _planes[i].distance(boxVertexN);
|
||||
|
||||
if (planeToBoxVertexPDistance < 0) {
|
||||
// This is outside the regular frustum, so just return the value from checking the keyhole
|
||||
return keyholeResult;
|
||||
} else if (planeToBoxVertexNDistance < 0) {
|
||||
regularResult = INTERSECT;
|
||||
// check distance to farthest box point
|
||||
if ( _planes[i].distance(box.getFarthestVertex(normal)) < 0.0f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return regularResult;
|
||||
return true;
|
||||
}
|
||||
|
||||
ViewFrustum::location ViewFrustum::boxInFrustum(const AABox& box) const {
|
||||
|
||||
ViewFrustum::location regularResult = INSIDE;
|
||||
ViewFrustum::location keyholeResult = OUTSIDE;
|
||||
|
||||
// If we have a keyholeRadius, check that first, since it's cheaper
|
||||
if (_keyholeRadius >= 0.0f) {
|
||||
keyholeResult = boxInKeyhole(box);
|
||||
bool ViewFrustum::sphereIntersectsKeyhole(const glm::vec3& center, float radius) const {
|
||||
// check positive touch against central sphere
|
||||
if (glm::length(center - _position) <= (radius + _centerSphereRadius)) {
|
||||
return true;
|
||||
}
|
||||
if (keyholeResult == INSIDE) {
|
||||
return keyholeResult;
|
||||
}
|
||||
|
||||
// TODO: These calculations are expensive, taking up 80% of our time in this function.
|
||||
// This appears to be expensive because we have to test the distance to each plane.
|
||||
// One suggested optimization is to first check against the approximated cone. We might
|
||||
// also be able to test against the cone to the bounding sphere of the box.
|
||||
// check negative touches against frustum planes
|
||||
for(int i=0; i < 6; i++) {
|
||||
const glm::vec3& normal = _planes[i].getNormal();
|
||||
const glm::vec3& boxVertexP = box.getVertexP(normal);
|
||||
float planeToBoxVertexPDistance = _planes[i].distance(boxVertexP);
|
||||
|
||||
const glm::vec3& boxVertexN = box.getVertexN(normal);
|
||||
float planeToBoxVertexNDistance = _planes[i].distance(boxVertexN);
|
||||
|
||||
if (planeToBoxVertexPDistance < 0) {
|
||||
// This is outside the regular frustum, so just return the value from checking the keyhole
|
||||
return keyholeResult;
|
||||
} else if (planeToBoxVertexNDistance < 0) {
|
||||
regularResult = INTERSECT;
|
||||
if ( _planes[i].distance(center) < -radius) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return regularResult;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ViewFrustum::cubeIntersectsKeyhole(const AACube& cube) const {
|
||||
// check positive touch against central sphere
|
||||
if (cube.touchesSphere(_position, _centerSphereRadius)) {
|
||||
return true;
|
||||
}
|
||||
// check negative touches against frustum planes
|
||||
for(int i=0; i < 6; i++) {
|
||||
const glm::vec3& normal = _planes[i].getNormal();
|
||||
if ( _planes[i].distance(cube.getFarthestVertex(normal)) < 0.0f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ViewFrustum::boxIntersectsKeyhole(const AABox& box) const {
|
||||
// check positive touch against central sphere
|
||||
if (box.touchesSphere(_position, _centerSphereRadius)) {
|
||||
return true;
|
||||
}
|
||||
// check negative touches against frustum planes
|
||||
for(int i=0; i < 6; i++) {
|
||||
const glm::vec3& normal = _planes[i].getNormal();
|
||||
if ( _planes[i].distance(box.getFarthestVertex(normal)) < 0.0f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool testMatches(glm::quat lhs, glm::quat rhs, float epsilon = EPSILON) {
|
||||
|
@ -490,7 +384,7 @@ PickRay ViewFrustum::computePickRay(float x, float y) {
|
|||
}
|
||||
|
||||
void ViewFrustum::computePickRay(float x, float y, glm::vec3& origin, glm::vec3& direction) const {
|
||||
origin = _cornersWorld[TOP_LEFT_NEAR] + x * (_cornersWorld[TOP_RIGHT_NEAR] - _cornersWorld[TOP_LEFT_NEAR]) +
|
||||
origin = _cornersWorld[TOP_LEFT_NEAR] + x * (_cornersWorld[TOP_RIGHT_NEAR] - _cornersWorld[TOP_LEFT_NEAR]) +
|
||||
y * (_cornersWorld[BOTTOM_LEFT_NEAR] - _cornersWorld[TOP_LEFT_NEAR]);
|
||||
direction = glm::normalize(origin - _position);
|
||||
}
|
||||
|
@ -540,7 +434,7 @@ void ViewFrustum::printDebugDetails() const {
|
|||
qCDebug(octree, "_right=%f,%f,%f", (double)_right.x, (double)_right.y, (double)_right.z );
|
||||
qCDebug(octree, "_fieldOfView=%f", (double)_fieldOfView);
|
||||
qCDebug(octree, "_aspectRatio=%f", (double)_aspectRatio);
|
||||
qCDebug(octree, "_keyHoleRadius=%f", (double)_keyholeRadius);
|
||||
qCDebug(octree, "_centerSphereRadius=%f", (double)_centerSphereRadius);
|
||||
qCDebug(octree, "_nearClip=%f", (double)_nearClip);
|
||||
qCDebug(octree, "_farClip=%f", (double)_farClip);
|
||||
qCDebug(octree, "_focalLength=%f", (double)_focalLength);
|
||||
|
@ -804,7 +698,7 @@ float ViewFrustum::calculateRenderAccuracy(const AABox& bounds, float octreeSize
|
|||
// FIXME - for now, it's either visible or not visible. We want to adjust this to eventually return
|
||||
// a floating point for objects that have small angular size to indicate that they may be rendered
|
||||
// with lower preciscion
|
||||
return (distanceToCamera <= visibleDistanceAtClosestScale) ? 1.0f : 0.0f;
|
||||
return (distanceToCamera <= visibleDistanceAtClosestScale) ? 1.0f : 0.0f;
|
||||
}
|
||||
|
||||
float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale) {
|
||||
|
|
|
@ -27,12 +27,15 @@
|
|||
#include "OctreeConstants.h"
|
||||
#include "OctreeProjectedPolygon.h"
|
||||
|
||||
const float DEFAULT_KEYHOLE_RADIUS = 3.0f;
|
||||
const float DEFAULT_CENTER_SPHERE_RADIUS = 3.0f;
|
||||
const float DEFAULT_FIELD_OF_VIEW_DEGREES = 45.0f;
|
||||
const float DEFAULT_ASPECT_RATIO = 16.0f/9.0f;
|
||||
const float DEFAULT_NEAR_CLIP = 0.08f;
|
||||
const float DEFAULT_FAR_CLIP = (float)HALF_TREE_SCALE;
|
||||
|
||||
// the "ViewFrustum" has a "keyhole" shape: a regular frustum for stuff that is "visible" with
|
||||
// a central sphere for stuff that is nearby (for physics simulation).
|
||||
|
||||
class ViewFrustum {
|
||||
public:
|
||||
// setters for camera attributes
|
||||
|
@ -83,18 +86,26 @@ public:
|
|||
const glm::vec3& getNearBottomLeft() const { return _cornersWorld[BOTTOM_LEFT_NEAR]; }
|
||||
const glm::vec3& getNearBottomRight() const { return _cornersWorld[BOTTOM_RIGHT_NEAR]; }
|
||||
|
||||
// get/set for keyhole attribute
|
||||
void setKeyholeRadius(float keyholdRadius) { _keyholeRadius = keyholdRadius; }
|
||||
float getKeyholeRadius() const { return _keyholeRadius; }
|
||||
// get/set for central spherek attribute
|
||||
void setCenterRadius(float radius) { _centerSphereRadius = radius; }
|
||||
float getCenterRadius() const { return _centerSphereRadius; }
|
||||
|
||||
void calculate();
|
||||
|
||||
typedef enum {OUTSIDE, INTERSECT, INSIDE} location;
|
||||
typedef enum { OUTSIDE = 0, INTERSECT, INSIDE } intersection;
|
||||
|
||||
ViewFrustum::location pointInFrustum(const glm::vec3& point, bool ignoreKeyhole = false) const;
|
||||
ViewFrustum::location sphereInFrustum(const glm::vec3& center, float radius) const;
|
||||
ViewFrustum::location cubeInFrustum(const AACube& cube) const;
|
||||
ViewFrustum::location boxInFrustum(const AABox& box) const;
|
||||
/// @return INSIDE, INTERSECT, or OUTSIDE depending on how cube intersects the keyhole shape
|
||||
ViewFrustum::intersection calculateCubeFrustumIntersection(const AACube& cube) const;
|
||||
ViewFrustum::intersection calculateCubeKeyholeIntersection(const AACube& cube) const;
|
||||
|
||||
bool pointIntersectsFrustum(const glm::vec3& point) const;
|
||||
bool sphereIntersectsFrustum(const glm::vec3& center, float radius) const;
|
||||
bool cubeIntersectsFrustum(const AACube& box) const;
|
||||
bool boxIntersectsFrustum(const AABox& box) const;
|
||||
|
||||
bool sphereIntersectsKeyhole(const glm::vec3& center, float radius) const;
|
||||
bool cubeIntersectsKeyhole(const AACube& cube) const;
|
||||
bool boxIntersectsKeyhole(const AABox& box) const;
|
||||
|
||||
// some frustum comparisons
|
||||
bool matches(const ViewFrustum& compareTo, bool debug = false) const;
|
||||
|
@ -114,15 +125,15 @@ public:
|
|||
glm::vec2 projectPoint(glm::vec3 point, bool& pointInView) const;
|
||||
OctreeProjectedPolygon getProjectedPolygon(const AACube& box) const;
|
||||
void getFurthestPointFromCamera(const AACube& box, glm::vec3& furthestPoint) const;
|
||||
|
||||
|
||||
float distanceToCamera(const glm::vec3& point) const;
|
||||
|
||||
|
||||
void evalProjectionMatrix(glm::mat4& proj) const;
|
||||
void evalViewTransform(Transform& view) const;
|
||||
|
||||
/// renderAccuracy represents a floating point "visibility" of an object based on it's view from the camera. At a simple
|
||||
/// level it returns 0.0f for things that are so small for the current settings that they could not be visible.
|
||||
float calculateRenderAccuracy(const AABox& bounds, float octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE,
|
||||
float calculateRenderAccuracy(const AABox& bounds, float octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE,
|
||||
int boundaryLevelAdjust = 0) const;
|
||||
|
||||
float getAccuracyAngle(float octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE, int boundaryLevelAdjust = 0) const;
|
||||
|
@ -131,12 +142,6 @@ public:
|
|||
|
||||
const ::Plane* getPlanes() const { return _planes; }
|
||||
private:
|
||||
// Used for keyhole calculations
|
||||
ViewFrustum::location pointInKeyhole(const glm::vec3& point) const;
|
||||
ViewFrustum::location sphereInKeyhole(const glm::vec3& center, float radius) const;
|
||||
ViewFrustum::location cubeInKeyhole(const AACube& cube) const;
|
||||
ViewFrustum::location boxInKeyhole(const AABox& box) const;
|
||||
|
||||
// camera location/orientation attributes
|
||||
glm::vec3 _position; // the position in world-frame
|
||||
glm::quat _orientation;
|
||||
|
@ -150,9 +155,7 @@ private:
|
|||
glm::vec3 _up = IDENTITY_UP;
|
||||
glm::vec3 _right = IDENTITY_RIGHT;
|
||||
|
||||
// keyhole attributes
|
||||
float _keyholeRadius = DEFAULT_KEYHOLE_RADIUS;
|
||||
AACube _keyholeBoundingCube;
|
||||
float _centerSphereRadius = DEFAULT_CENTER_SPHERE_RADIUS;
|
||||
|
||||
// Calculated values
|
||||
glm::mat4 _inverseProjection;
|
||||
|
@ -165,7 +168,7 @@ private:
|
|||
float _fieldOfView = DEFAULT_FIELD_OF_VIEW_DEGREES;
|
||||
glm::vec4 _corners[8];
|
||||
glm::vec3 _cornersWorld[8];
|
||||
::Plane _planes[6]; // How will this be used?
|
||||
::Plane _planes[6]; // plane normals point inside frustum
|
||||
|
||||
const char* debugPlaneName (int plane) const;
|
||||
|
||||
|
|
|
@ -98,12 +98,12 @@ void DeferredLightingEffect::init() {
|
|||
}
|
||||
|
||||
void DeferredLightingEffect::addPointLight(const glm::vec3& position, float radius, const glm::vec3& color,
|
||||
float intensity) {
|
||||
addSpotLight(position, radius, color, intensity);
|
||||
float intensity, float falloffRadius) {
|
||||
addSpotLight(position, radius, color, intensity, falloffRadius);
|
||||
}
|
||||
|
||||
void DeferredLightingEffect::addSpotLight(const glm::vec3& position, float radius, const glm::vec3& color,
|
||||
float intensity, const glm::quat& orientation, float exponent, float cutoff) {
|
||||
float intensity, float falloffRadius, const glm::quat& orientation, float exponent, float cutoff) {
|
||||
|
||||
unsigned int lightID = (unsigned int)(_pointLights.size() + _spotLights.size() + _globalLights.size());
|
||||
if (lightID >= _allocatedLights.size()) {
|
||||
|
@ -115,7 +115,7 @@ void DeferredLightingEffect::addSpotLight(const glm::vec3& position, float radiu
|
|||
lp->setMaximumRadius(radius);
|
||||
lp->setColor(color);
|
||||
lp->setIntensity(intensity);
|
||||
//lp->setShowContour(quadraticAttenuation);
|
||||
lp->setFalloffRadius(falloffRadius);
|
||||
|
||||
if (exponent == 0.0f && cutoff == PI) {
|
||||
lp->setType(model::Light::POINT);
|
||||
|
|
|
@ -36,11 +36,12 @@ public:
|
|||
|
||||
/// Adds a point light to render for the current frame.
|
||||
void addPointLight(const glm::vec3& position, float radius, const glm::vec3& color = glm::vec3(0.0f, 0.0f, 0.0f),
|
||||
float intensity = 0.5f);
|
||||
float intensity = 0.5f, float falloffRadius = 0.01f);
|
||||
|
||||
/// Adds a spot light to render for the current frame.
|
||||
void addSpotLight(const glm::vec3& position, float radius, const glm::vec3& color = glm::vec3(1.0f, 1.0f, 1.0f),
|
||||
float intensity = 0.5f, const glm::quat& orientation = glm::quat(), float exponent = 0.0f, float cutoff = PI);
|
||||
float intensity = 0.5f, float falloffRadius = 0.01f,
|
||||
const glm::quat& orientation = glm::quat(), float exponent = 0.0f, float cutoff = PI);
|
||||
|
||||
void prepare(RenderArgs* args);
|
||||
void render(const render::RenderContextPointer& renderContext);
|
||||
|
|
|
@ -450,9 +450,9 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const {
|
|||
void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) const {
|
||||
if (!_isBlendShaped) {
|
||||
batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
|
||||
|
||||
|
||||
batch.setInputFormat((_drawMesh->getVertexFormat()));
|
||||
|
||||
|
||||
batch.setInputStream(0, _drawMesh->getVertexStream());
|
||||
} else {
|
||||
batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
|
||||
|
@ -463,7 +463,7 @@ void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) const {
|
|||
batch.setInputBuffer(1, _model->_blendedVertexBuffers[_meshIndex], _drawMesh->getNumVertices() * sizeof(glm::vec3), sizeof(glm::vec3));
|
||||
batch.setInputStream(2, _drawMesh->getVertexStream().makeRangedStream(2));
|
||||
}
|
||||
|
||||
|
||||
// TODO: Get rid of that extra call
|
||||
if (!_hasColorAttrib) {
|
||||
batch._glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
|
@ -511,17 +511,14 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
|
|||
#ifdef DEBUG_BOUNDING_PARTS
|
||||
{
|
||||
AABox partBounds = getPartBounds(_meshIndex, partIndex);
|
||||
bool inView = args->_viewFrustum->boxInFrustum(partBounds) != ViewFrustum::OUTSIDE;
|
||||
|
||||
glm::vec4 cubeColor;
|
||||
|
||||
glm::vec4 cubeColor(1.0f, 1.0f, 0.0f, 1.0f);
|
||||
if (isSkinned) {
|
||||
cubeColor = glm::vec4(0.0f, 1.0f, 1.0f, 1.0f);
|
||||
} else if (inView) {
|
||||
} else if (args->_viewFrustum->boxIntersectsFrustum(partBounds)) {
|
||||
cubeColor = glm::vec4(1.0f, 0.0f, 1.0f, 1.0f);
|
||||
} else {
|
||||
cubeColor = glm::vec4(1.0f, 1.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
|
||||
Transform transform;
|
||||
transform.setTranslation(partBounds.calcCenter());
|
||||
transform.setScale(partBounds.getDimensions());
|
||||
|
@ -529,7 +526,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
|
|||
DependencyManager::get<GeometryCache>()->renderWireCube(batch, 1.0f, cubeColor);
|
||||
}
|
||||
#endif //def DEBUG_BOUNDING_PARTS
|
||||
|
||||
|
||||
auto locations = args->_pipeline->locations;
|
||||
assert(locations);
|
||||
|
||||
|
@ -537,23 +534,23 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
|
|||
bool canCauterize = args->_renderMode != RenderArgs::SHADOW_RENDER_MODE;
|
||||
_model->updateClusterMatrices(_transform.getTranslation(), _transform.getRotation());
|
||||
bindTransform(batch, locations, canCauterize);
|
||||
|
||||
|
||||
//Bind the index buffer and vertex buffer and Blend shapes if needed
|
||||
bindMesh(batch);
|
||||
|
||||
|
||||
// apply material properties
|
||||
bindMaterial(batch, locations);
|
||||
|
||||
|
||||
if (args) {
|
||||
args->_details._materialSwitches++;
|
||||
}
|
||||
|
||||
|
||||
// Draw!
|
||||
{
|
||||
PerformanceTimer perfTimer("batch.drawIndexed()");
|
||||
drawCall(batch);
|
||||
}
|
||||
|
||||
|
||||
if (args) {
|
||||
const int INDICES_PER_TRIANGLE = 3;
|
||||
args->_details._trianglesRendered += _drawPart._numIndices / INDICES_PER_TRIANGLE;
|
||||
|
|
|
@ -48,7 +48,7 @@ void main(void) {
|
|||
vec3 fragLightVec = getLightPosition(light) - fragPos.xyz;
|
||||
|
||||
// Kill if too far from the light center
|
||||
if (dot(fragLightVec, fragLightVec) > getLightSquareRadius(light)) {
|
||||
if (dot(fragLightVec, fragLightVec) > getLightCutoffSquareRadius(light)) {
|
||||
discard;
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ void main(void) {
|
|||
vec3 fragLightVec = getLightPosition(light) - fragPos.xyz;
|
||||
|
||||
// Kill if too far from the light center
|
||||
if (dot(fragLightVec, fragLightVec) > getLightSquareRadius(light)) {
|
||||
if (dot(fragLightVec, fragLightVec) > getLightCutoffSquareRadius(light)) {
|
||||
discard;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ void render::cullItems(const RenderContextPointer& renderContext, const CullFunc
|
|||
ViewFrustum* frustum = args->_viewFrustum;
|
||||
|
||||
details._considered += (int)inItems.size();
|
||||
|
||||
|
||||
// Culling / LOD
|
||||
for (auto item : inItems) {
|
||||
if (item.bound.isNull()) {
|
||||
|
@ -39,12 +39,12 @@ void render::cullItems(const RenderContextPointer& renderContext, const CullFunc
|
|||
|
||||
// TODO: some entity types (like lights) might want to be rendered even
|
||||
// when they are outside of the view frustum...
|
||||
bool outOfView;
|
||||
bool inView;
|
||||
{
|
||||
PerformanceTimer perfTimer("boxInFrustum");
|
||||
outOfView = frustum->boxInFrustum(item.bound) == ViewFrustum::OUTSIDE;
|
||||
PerformanceTimer perfTimer("boxIntersectsFrustum");
|
||||
inView = frustum->boxIntersectsFrustum(item.bound);
|
||||
}
|
||||
if (!outOfView) {
|
||||
if (inView) {
|
||||
bool bigEnoughToRender;
|
||||
{
|
||||
PerformanceTimer perfTimer("shouldRender");
|
||||
|
@ -88,10 +88,10 @@ struct BackToFrontSort {
|
|||
void render::depthSortItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, bool frontToBack, const ItemBounds& inItems, ItemBounds& outItems) {
|
||||
assert(renderContext->args);
|
||||
assert(renderContext->args->_viewFrustum);
|
||||
|
||||
|
||||
auto& scene = sceneContext->_scene;
|
||||
RenderArgs* args = renderContext->args;
|
||||
|
||||
|
||||
|
||||
// Allocate and simply copy
|
||||
outItems.clear();
|
||||
|
@ -238,7 +238,7 @@ void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const Re
|
|||
}
|
||||
|
||||
bool frustumTest(const AABox& bound) {
|
||||
if (_args->_viewFrustum->boxInFrustum(bound) == ViewFrustum::OUTSIDE) {
|
||||
if (!_args->_viewFrustum->boxIntersectsFrustum(bound)) {
|
||||
_renderDetails._outOfView++;
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -75,32 +75,32 @@ void AABox::setBox(const glm::vec3& corner, const glm::vec3& scale) {
|
|||
_scale = scale;
|
||||
}
|
||||
|
||||
glm::vec3 AABox::getVertexP(const glm::vec3& normal) const {
|
||||
glm::vec3 AABox::getFarthestVertex(const glm::vec3& normal) const {
|
||||
glm::vec3 result = _corner;
|
||||
if (normal.x > 0) {
|
||||
if (normal.x > 0.0f) {
|
||||
result.x += _scale.x;
|
||||
}
|
||||
if (normal.y > 0) {
|
||||
if (normal.y > 0.0f) {
|
||||
result.y += _scale.y;
|
||||
}
|
||||
if (normal.z > 0) {
|
||||
if (normal.z > 0.0f) {
|
||||
result.z += _scale.z;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::vec3 AABox::getVertexN(const glm::vec3& normal) const {
|
||||
glm::vec3 AABox::getNearestVertex(const glm::vec3& normal) const {
|
||||
glm::vec3 result = _corner;
|
||||
|
||||
if (normal.x < 0) {
|
||||
if (normal.x < 0.0f) {
|
||||
result.x += _scale.x;
|
||||
}
|
||||
|
||||
if (normal.y < 0) {
|
||||
if (normal.y < 0.0f) {
|
||||
result.y += _scale.y;
|
||||
}
|
||||
|
||||
if (normal.z < 0) {
|
||||
if (normal.z < 0.0f) {
|
||||
result.z += _scale.z;
|
||||
}
|
||||
|
||||
|
@ -217,7 +217,7 @@ bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& e
|
|||
isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x));
|
||||
}
|
||||
|
||||
bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal) const {
|
||||
// handle the trivial case where the box contains the origin
|
||||
if (contains(origin)) {
|
||||
|
@ -281,6 +281,12 @@ bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direct
|
|||
return false;
|
||||
}
|
||||
|
||||
bool AABox::touchesSphere(const glm::vec3& center, float radius) const {
|
||||
// Avro's algorithm from this paper: http://www.mrtc.mdh.se/projects/3Dgraphics/paperF.pdf
|
||||
glm::vec3 e = glm::max(_corner - center, Vectors::ZERO) + glm::max(center - _corner - _scale, Vectors::ZERO);
|
||||
return glm::length2(e) <= radius * radius;
|
||||
}
|
||||
|
||||
bool AABox::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const {
|
||||
glm::vec4 center4 = glm::vec4(center, 1.0f);
|
||||
|
||||
|
@ -537,4 +543,4 @@ void AABox::transform(const Transform& transform) {
|
|||
scale(transform.getScale());
|
||||
rotate(transform.getRotation());
|
||||
translate(transform.getTranslation());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,12 +35,12 @@ public:
|
|||
AABox(const glm::vec3& corner, const glm::vec3& dimensions);
|
||||
AABox();
|
||||
~AABox() {};
|
||||
|
||||
|
||||
void setBox(const glm::vec3& corner, const glm::vec3& scale);
|
||||
|
||||
void setBox(const glm::vec3& corner, float scale);
|
||||
glm::vec3 getVertexP(const glm::vec3& normal) const;
|
||||
glm::vec3 getVertexN(const glm::vec3& normal) const;
|
||||
glm::vec3 getFarthestVertex(const glm::vec3& normal) const; // return vertex most parallel to normal
|
||||
glm::vec3 getNearestVertex(const glm::vec3& normal) const; // return vertex most anti-parallel to normal
|
||||
|
||||
const glm::vec3& getCorner() const { return _corner; }
|
||||
const glm::vec3& getScale() const { return _scale; }
|
||||
|
@ -68,11 +68,12 @@ public:
|
|||
|
||||
bool expandedContains(const glm::vec3& point, float expansion) const;
|
||||
bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const;
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal) const;
|
||||
bool touchesSphere(const glm::vec3& center, float radius) const; // fast but may generate false positives
|
||||
bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const;
|
||||
bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const;
|
||||
|
||||
|
||||
bool isNull() const { return _scale == glm::vec3(0.0f, 0.0f, 0.0f); }
|
||||
|
||||
AABox clamp(const glm::vec3& min, const glm::vec3& max) const;
|
||||
|
@ -113,7 +114,7 @@ inline bool operator==(const AABox& a, const AABox& b) {
|
|||
}
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const AABox& box) {
|
||||
debug << "AABox[ ("
|
||||
debug << "AABox[ ("
|
||||
<< box.getCorner().x << "," << box.getCorner().y << "," << box.getCorner().z << " ) to ("
|
||||
<< box.calcTopFarLeft().x << "," << box.calcTopFarLeft().y << "," << box.calcTopFarLeft().z << ") size: ("
|
||||
<< box.getDimensions().x << "," << box.getDimensions().y << "," << box.getDimensions().z << ")"
|
||||
|
|
|
@ -79,32 +79,32 @@ void AACube::setBox(const glm::vec3& corner, float scale) {
|
|||
_scale = scale;
|
||||
}
|
||||
|
||||
glm::vec3 AACube::getVertexP(const glm::vec3& normal) const {
|
||||
glm::vec3 AACube::getFarthestVertex(const glm::vec3& normal) const {
|
||||
glm::vec3 result = _corner;
|
||||
if (normal.x > 0) {
|
||||
if (normal.x > 0.0f) {
|
||||
result.x += _scale;
|
||||
}
|
||||
if (normal.y > 0) {
|
||||
if (normal.y > 0.0f) {
|
||||
result.y += _scale;
|
||||
}
|
||||
if (normal.z > 0) {
|
||||
if (normal.z > 0.0f) {
|
||||
result.z += _scale;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::vec3 AACube::getVertexN(const glm::vec3& normal) const {
|
||||
glm::vec3 AACube::getNearestVertex(const glm::vec3& normal) const {
|
||||
glm::vec3 result = _corner;
|
||||
|
||||
if (normal.x < 0) {
|
||||
if (normal.x < 0.0f) {
|
||||
result.x += _scale;
|
||||
}
|
||||
|
||||
if (normal.y < 0) {
|
||||
if (normal.y < 0.0f) {
|
||||
result.y += _scale;
|
||||
}
|
||||
|
||||
if (normal.z < 0) {
|
||||
if (normal.z < 0.0f) {
|
||||
result.z += _scale;
|
||||
}
|
||||
|
||||
|
@ -284,6 +284,12 @@ bool AACube::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc
|
|||
return false;
|
||||
}
|
||||
|
||||
bool AACube::touchesSphere(const glm::vec3& center, float radius) const {
|
||||
// Avro's algorithm from this paper: http://www.mrtc.mdh.se/projects/3Dgraphics/paperF.pdf
|
||||
glm::vec3 e = glm::max(_corner - center, Vectors::ZERO) + glm::max(center - _corner - glm::vec3(_scale), Vectors::ZERO);
|
||||
return glm::length2(e) <= radius * radius;
|
||||
}
|
||||
|
||||
bool AACube::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const {
|
||||
glm::vec4 center4 = glm::vec4(center, 1.0f);
|
||||
|
||||
|
|
|
@ -34,8 +34,8 @@ public:
|
|||
~AACube() {};
|
||||
|
||||
void setBox(const glm::vec3& corner, float scale);
|
||||
glm::vec3 getVertexP(const glm::vec3& normal) const;
|
||||
glm::vec3 getVertexN(const glm::vec3& normal) const;
|
||||
glm::vec3 getFarthestVertex(const glm::vec3& normal) const; // return vertex most parallel to normal
|
||||
glm::vec3 getNearestVertex(const glm::vec3& normal) const; // return vertex most anti-parallel to normal
|
||||
void scale(float scale);
|
||||
const glm::vec3& getCorner() const { return _corner; }
|
||||
float getScale() const { return _scale; }
|
||||
|
@ -56,8 +56,9 @@ public:
|
|||
bool touches(const AABox& otherBox) const;
|
||||
bool expandedContains(const glm::vec3& point, float expansion) const;
|
||||
bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const;
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal) const;
|
||||
bool touchesSphere(const glm::vec3& center, float radius) const;
|
||||
bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const;
|
||||
bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const;
|
||||
|
||||
|
@ -88,7 +89,7 @@ inline bool operator!=(const AACube& a, const AACube& b) {
|
|||
}
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const AACube& cube) {
|
||||
debug << "AACube[ ("
|
||||
debug << "AACube[ ("
|
||||
<< cube.getCorner().x << "," << cube.getCorner().y << "," << cube.getCorner().z << " ) to ("
|
||||
<< cube.calcTopFarLeft().x << "," << cube.calcTopFarLeft().y << "," << cube.calcTopFarLeft().z << ") size: ("
|
||||
<< cube.getDimensions().x << "," << cube.getDimensions().y << "," << cube.getDimensions().z << ")"
|
||||
|
|
|
@ -670,9 +670,8 @@ void OctreeTests::byteCountCodingTests() {
|
|||
}
|
||||
|
||||
void OctreeTests::modelItemTests() {
|
||||
bool verbose = true;
|
||||
|
||||
#if 0 // TODO - repair/replace these
|
||||
bool verbose = true;
|
||||
|
||||
//verbose = true;
|
||||
EntityTreeElementExtraEncodeData modelTreeElementExtraEncodeData;
|
||||
|
|
1347
tests/octree/src/ViewFrustumTests.cpp
Normal file
1347
tests/octree/src/ViewFrustumTests.cpp
Normal file
File diff suppressed because it is too large
Load diff
32
tests/octree/src/ViewFrustumTests.h
Normal file
32
tests/octree/src/ViewFrustumTests.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// ViewFrustumTests.h
|
||||
// tests/octree/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2016.02.19
|
||||
// Copyright 2016 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_ViewFruxtumTests_h
|
||||
#define hifi_ViewFruxtumTests_h
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
|
||||
class ViewFrustumTests : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void testInit();
|
||||
void testCubeFrustumIntersection();
|
||||
void testCubeKeyholeIntersection();
|
||||
void testPointIntersectsFrustum();
|
||||
void testSphereIntersectsFrustum();
|
||||
void testBoxIntersectsFrustum();
|
||||
void testSphereIntersectsKeyhole();
|
||||
void testCubeIntersectsKeyhole();
|
||||
void testBoxIntersectsKeyhole();
|
||||
};
|
||||
|
||||
#endif // hifi_ViewFruxtumTests_h
|
153
tests/shared/src/AABoxTests.cpp
Normal file
153
tests/shared/src/AABoxTests.cpp
Normal file
|
@ -0,0 +1,153 @@
|
|||
//
|
||||
// AABoxTests.cpp
|
||||
// tests/shared/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2016.02.19
|
||||
// Copyright 2016 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 <iostream>
|
||||
|
||||
#include "AABoxTests.h"
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <StreamUtils.h>
|
||||
|
||||
#include <../GLMTestUtils.h>
|
||||
#include <../QTestExtensions.h>
|
||||
|
||||
|
||||
QTEST_MAIN(AABoxTests)
|
||||
|
||||
void AABoxTests::testCtorsAndSetters() {
|
||||
const glm::vec3 corner(1.23f, 4.56f, 7.89f);
|
||||
const glm::vec3 scale(2.34f, 7.53f, 9.14f);
|
||||
|
||||
// test ctor
|
||||
AABox box(corner, scale);
|
||||
QCOMPARE_WITH_ABS_ERROR(box.getCorner(), corner, EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(box.getScale(), scale, EPSILON);
|
||||
|
||||
// test copy ctor
|
||||
AABox copyBox(box);
|
||||
QCOMPARE_WITH_ABS_ERROR(copyBox.getCorner(), corner, EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(copyBox.getScale(), scale, EPSILON);
|
||||
|
||||
// test setBox()
|
||||
const glm::vec3 newCorner(9.87f, 6.54f, 3.21f);
|
||||
const glm::vec3 newScale = glm::vec3(4.32f, 8.95f, 10.31f);
|
||||
box.setBox(newCorner, newScale);
|
||||
QCOMPARE_WITH_ABS_ERROR(box.getCorner(), newCorner, EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(box.getScale(), newScale, EPSILON);
|
||||
|
||||
// test misc
|
||||
QCOMPARE_WITH_ABS_ERROR(newCorner, box.getMinimumPoint(), EPSILON);
|
||||
|
||||
glm::vec3 expectedMaxCorner = newCorner + glm::vec3(newScale);
|
||||
QCOMPARE_WITH_ABS_ERROR(expectedMaxCorner, box.getMaximumPoint(), EPSILON);
|
||||
|
||||
glm::vec3 expectedCenter = newCorner + glm::vec3(0.5f * newScale);
|
||||
QCOMPARE_WITH_ABS_ERROR(expectedCenter, box.calcCenter(), EPSILON);
|
||||
}
|
||||
|
||||
void AABoxTests::testContainsPoint() {
|
||||
const glm::vec3 corner(4.56f, 7.89f, -1.35f);
|
||||
const glm::vec3 scale(2.34f, 7.53f, 9.14f);
|
||||
AABox box(corner, scale);
|
||||
|
||||
float delta = 0.00001f;
|
||||
glm::vec3 center = box.calcCenter();
|
||||
QCOMPARE(box.contains(center), true);
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
glm::vec3 halfScale = Vectors::ZERO;
|
||||
halfScale[i] = 0.5f * scale[i];
|
||||
glm::vec3 deltaOffset = Vectors::ZERO;
|
||||
deltaOffset[i] = delta;
|
||||
|
||||
QCOMPARE(box.contains(center + halfScale + deltaOffset), false); // outside +face
|
||||
QCOMPARE(box.contains(center + halfScale - deltaOffset), true); // inside +face
|
||||
QCOMPARE(box.contains(center - halfScale + deltaOffset), true); // inside -face
|
||||
QCOMPARE(box.contains(center - halfScale - deltaOffset), false); // outside -face
|
||||
}
|
||||
}
|
||||
|
||||
void AABoxTests::testTouchesSphere() {
|
||||
glm::vec3 corner(-4.56f, 7.89f, -1.35f);
|
||||
float scale = 1.23f;
|
||||
AABox box(corner, scale);
|
||||
|
||||
float delta = 0.00001f;
|
||||
glm::vec3 cubeCenter = box.calcCenter();
|
||||
float sphereRadius = 0.468f;
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
int j = (i + 1) % 3;
|
||||
int k = (j + 1) % 3;
|
||||
|
||||
{ // faces
|
||||
glm::vec3 scaleOffset = Vectors::ZERO;
|
||||
scaleOffset[i] = 0.5f * scale + sphereRadius;
|
||||
|
||||
glm::vec3 deltaOffset = Vectors::ZERO;
|
||||
deltaOffset[i] = delta;
|
||||
|
||||
// outside +face
|
||||
glm::vec3 sphereCenter = cubeCenter + scaleOffset + deltaOffset;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
|
||||
// inside +face
|
||||
sphereCenter = cubeCenter + scaleOffset - deltaOffset;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
|
||||
// inside -face
|
||||
sphereCenter = cubeCenter - scaleOffset + deltaOffset;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
|
||||
// outside -face
|
||||
sphereCenter = cubeCenter - scaleOffset - deltaOffset;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
}
|
||||
|
||||
{ // edges
|
||||
glm::vec3 edgeOffset = Vectors::ZERO;
|
||||
edgeOffset[i] = 0.5f * scale;
|
||||
edgeOffset[j] = 0.5f * scale;
|
||||
glm::vec3 edgeDirection = glm::normalize(edgeOffset);
|
||||
glm::vec3 sphereCenter;
|
||||
|
||||
// inside ij
|
||||
sphereCenter = cubeCenter + edgeOffset + (sphereRadius - delta) * edgeDirection;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
sphereCenter = cubeCenter - edgeOffset - (sphereRadius - delta) * edgeDirection;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
|
||||
// outside ij
|
||||
sphereCenter = cubeCenter + edgeOffset + (sphereRadius + delta) * edgeDirection;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
sphereCenter = cubeCenter - edgeOffset - (sphereRadius + delta) * edgeDirection;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
|
||||
edgeOffset[j] = 0.0f;
|
||||
edgeOffset[k] = 0.5f * scale;
|
||||
edgeDirection = glm::normalize(edgeOffset);
|
||||
|
||||
// inside ik
|
||||
sphereCenter = cubeCenter + edgeOffset + (sphereRadius - delta) * edgeDirection;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
sphereCenter = cubeCenter - edgeOffset - (sphereRadius - delta) * edgeDirection;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
|
||||
// outside ik
|
||||
sphereCenter = cubeCenter + edgeOffset + (sphereRadius + delta) * edgeDirection;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
sphereCenter = cubeCenter - edgeOffset - (sphereRadius + delta) * edgeDirection;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
28
tests/shared/src/AABoxTests.h
Normal file
28
tests/shared/src/AABoxTests.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// AABoxTests.h
|
||||
// tests/shared/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2016.02.19
|
||||
// Copyright 2016 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_AABoxTests_h
|
||||
#define hifi_AABoxTests_h
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <AABox.h>
|
||||
|
||||
class AABoxTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void testCtorsAndSetters();
|
||||
void testContainsPoint();
|
||||
void testTouchesSphere();
|
||||
};
|
||||
|
||||
#endif // hifi_AABoxTests_h
|
154
tests/shared/src/AACubeTests.cpp
Normal file
154
tests/shared/src/AACubeTests.cpp
Normal file
|
@ -0,0 +1,154 @@
|
|||
//
|
||||
// AACubeTests.cpp
|
||||
// tests/shared/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2016.02.19
|
||||
// Copyright 2016 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 <iostream>
|
||||
|
||||
#include "AACubeTests.h"
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <StreamUtils.h>
|
||||
|
||||
#include <../GLMTestUtils.h>
|
||||
#include <../QTestExtensions.h>
|
||||
|
||||
|
||||
QTEST_MAIN(AACubeTests)
|
||||
|
||||
void AACubeTests::ctorsAndSetters() {
|
||||
const glm::vec3 corner(1.23f, 4.56f, 7.89f);
|
||||
const float scale = 2.34f;
|
||||
|
||||
// test ctor
|
||||
AACube cube(corner, scale);
|
||||
QCOMPARE_WITH_ABS_ERROR(cube.getCorner(), corner, EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(cube.getScale(), scale, EPSILON);
|
||||
|
||||
// test copy ctor
|
||||
AACube copyCube(cube);
|
||||
QCOMPARE_WITH_ABS_ERROR(copyCube.getCorner(), corner, EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(copyCube.getScale(), scale, EPSILON);
|
||||
|
||||
// test setBox()
|
||||
const glm::vec3 newCorner(9.87f, 6.54f, 3.21f);
|
||||
const float newScale = 4.32f;
|
||||
cube.setBox(newCorner, newScale);
|
||||
QCOMPARE_WITH_ABS_ERROR(cube.getCorner(), newCorner, EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(cube.getScale(), newScale, EPSILON);
|
||||
|
||||
// test misc
|
||||
QCOMPARE_WITH_ABS_ERROR(cube.getMinimumPoint(), newCorner, EPSILON);
|
||||
|
||||
glm::vec3 expectedMaxCorner = newCorner + glm::vec3(newScale);
|
||||
QCOMPARE_WITH_ABS_ERROR(cube.getMaximumPoint(), expectedMaxCorner, EPSILON);
|
||||
|
||||
glm::vec3 expectedCenter = newCorner + glm::vec3(0.5f * newScale);
|
||||
QCOMPARE_WITH_ABS_ERROR(cube.calcCenter(), expectedCenter, EPSILON);
|
||||
}
|
||||
|
||||
void AACubeTests::containsPoint() {
|
||||
const glm::vec3 corner(4.56f, 7.89f, -1.35f);
|
||||
const float scale = 1.23f;
|
||||
AACube cube(corner, scale);
|
||||
|
||||
const float delta = scale / 1000.0f;
|
||||
const glm::vec3 center = cube.calcCenter();
|
||||
QCOMPARE(cube.contains(center), true);
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
glm::vec3 scaleOffset = Vectors::ZERO;
|
||||
scaleOffset[i] = 0.5f * scale;
|
||||
|
||||
glm::vec3 deltaOffset = Vectors::ZERO;
|
||||
deltaOffset[i] = delta;
|
||||
|
||||
QCOMPARE(cube.contains(center + scaleOffset + deltaOffset), false); // outside +face
|
||||
QCOMPARE(cube.contains(center + scaleOffset - deltaOffset), true); // inside +face
|
||||
QCOMPARE(cube.contains(center - scaleOffset + deltaOffset), true); // inside -face
|
||||
QCOMPARE(cube.contains(center - scaleOffset - deltaOffset), false); // outside -face
|
||||
}
|
||||
}
|
||||
|
||||
void AACubeTests::touchesSphere() {
|
||||
const glm::vec3 corner(-4.56f, 7.89f, -1.35f);
|
||||
const float scale = 1.23f;
|
||||
AACube cube(corner, scale);
|
||||
|
||||
const float delta = scale / 1000.0f;
|
||||
const glm::vec3 cubeCenter = cube.calcCenter();
|
||||
const float sphereRadius = 0.468f;
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
int j = (i + 1) % 3;
|
||||
int k = (j + 1) % 3;
|
||||
|
||||
{ // faces
|
||||
glm::vec3 scaleOffset = Vectors::ZERO;
|
||||
scaleOffset[i] = 0.5f * scale + sphereRadius;
|
||||
|
||||
glm::vec3 deltaOffset = Vectors::ZERO;
|
||||
deltaOffset[i] = delta;
|
||||
|
||||
// outside +face
|
||||
glm::vec3 sphereCenter = cubeCenter + scaleOffset + deltaOffset;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
|
||||
// inside +face
|
||||
sphereCenter = cubeCenter + scaleOffset - deltaOffset;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
|
||||
// inside -face
|
||||
sphereCenter = cubeCenter - scaleOffset + deltaOffset;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
|
||||
// outside -face
|
||||
sphereCenter = cubeCenter - scaleOffset - deltaOffset;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
}
|
||||
|
||||
{ // edges
|
||||
glm::vec3 edgeOffset = Vectors::ZERO;
|
||||
edgeOffset[i] = 0.5f * scale;
|
||||
edgeOffset[j] = 0.5f * scale;
|
||||
glm::vec3 edgeDirection = glm::normalize(edgeOffset);
|
||||
glm::vec3 sphereCenter;
|
||||
|
||||
// inside ij
|
||||
sphereCenter = cubeCenter + edgeOffset + (sphereRadius - delta) * edgeDirection;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
sphereCenter = cubeCenter - edgeOffset - (sphereRadius - delta) * edgeDirection;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
|
||||
// outside ij
|
||||
sphereCenter = cubeCenter + edgeOffset + (sphereRadius + delta) * edgeDirection;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
sphereCenter = cubeCenter - edgeOffset - (sphereRadius + delta) * edgeDirection;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
|
||||
edgeOffset[j] = 0.0f;
|
||||
edgeOffset[k] = 0.5f * scale;
|
||||
edgeDirection = glm::normalize(edgeOffset);
|
||||
|
||||
// inside ik
|
||||
sphereCenter = cubeCenter + edgeOffset + (sphereRadius - delta) * edgeDirection;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
sphereCenter = cubeCenter - edgeOffset - (sphereRadius - delta) * edgeDirection;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
|
||||
// outside ik
|
||||
sphereCenter = cubeCenter + edgeOffset + (sphereRadius + delta) * edgeDirection;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
sphereCenter = cubeCenter - edgeOffset - (sphereRadius + delta) * edgeDirection;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
28
tests/shared/src/AACubeTests.h
Normal file
28
tests/shared/src/AACubeTests.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// AACubeTests.h
|
||||
// tests/shared/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2016.02.19
|
||||
// Copyright 2016 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_AACubeTests_h
|
||||
#define hifi_AACubeTests_h
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <AACube.h>
|
||||
|
||||
class AACubeTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void ctorsAndSetters();
|
||||
void containsPoint();
|
||||
void touchesSphere();
|
||||
};
|
||||
|
||||
#endif // hifi_AACubeTests_h
|
|
@ -1,316 +0,0 @@
|
|||
//
|
||||
// AngularConstraintTests.cpp
|
||||
// tests/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.05.30
|
||||
// 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 "AngularConstraintTests.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <AngularConstraint.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <StreamUtils.h>
|
||||
|
||||
#include "../QTestExtensions.h"
|
||||
|
||||
|
||||
QTEST_MAIN(AngularConstraintTests)
|
||||
|
||||
void AngularConstraintTests::testHingeConstraint() {
|
||||
float minAngle = -PI;
|
||||
float maxAngle = 0.0f;
|
||||
glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
|
||||
glm::vec3 minAngles(0.0f, -PI, 0.0f);
|
||||
glm::vec3 maxAngles(0.0f, 0.0f, 0.0f);
|
||||
|
||||
AngularConstraint* c = AngularConstraint::newAngularConstraint(minAngles, maxAngles);
|
||||
QVERIFY2(c != nullptr, "newAngularConstraint should make a constraint");
|
||||
{ // test in middle of constraint
|
||||
float angle = 0.5f * (minAngle + maxAngle);
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
QVERIFY2(constrained == false, "HingeConstraint should not clamp()");
|
||||
QVERIFY2(rotation == newRotation, "HingeConstraint should not change rotation");
|
||||
}
|
||||
{ // test just inside min edge of constraint
|
||||
float angle = minAngle + 10.0f * EPSILON;
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
QVERIFY2(!constrained, "HingeConstraint should not clamp()");
|
||||
QVERIFY2(newRotation == rotation, "HingeConstraint should not change rotation");
|
||||
}
|
||||
{ // test just inside max edge of constraint
|
||||
float angle = maxAngle - 10.0f * EPSILON;
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
QVERIFY2(!constrained, "HingeConstraint should not clamp()");
|
||||
QVERIFY2(newRotation == rotation, "HingeConstraint should not change rotation");
|
||||
}
|
||||
{ // test just outside min edge of constraint
|
||||
float angle = minAngle - 0.001f;
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis);
|
||||
|
||||
QVERIFY2(constrained, "HingeConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
{ // test just outside max edge of constraint
|
||||
float angle = maxAngle + 0.001f;
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
QVERIFY2(constrained, "HingeConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, rotation, EPSILON);
|
||||
}
|
||||
{ // test far outside min edge of constraint (wraps around to max)
|
||||
float angle = minAngle - 0.75f * (TWO_PI - (maxAngle - minAngle));
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis);
|
||||
QVERIFY2(constrained, "HingeConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
{ // test far outside max edge of constraint (wraps around to min)
|
||||
float angle = maxAngle + 0.75f * (TWO_PI - (maxAngle - minAngle));
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis);
|
||||
|
||||
QVERIFY2(constrained, "HingeConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
|
||||
float ACCEPTABLE_ERROR = 1.0e-4f;
|
||||
{ // test nearby but off-axis rotation
|
||||
float offAngle = 0.1f;
|
||||
glm::quat offRotation(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
float angle = 0.5f * (maxAngle + minAngle);
|
||||
glm::quat rotation = offRotation * glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
glm::quat expectedRotation = glm::angleAxis(angle, yAxis);
|
||||
|
||||
QVERIFY2(constrained, "HingeConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, ACCEPTABLE_ERROR);
|
||||
}
|
||||
{ // test way off rotation > maxAngle
|
||||
float offAngle = 0.5f;
|
||||
glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
float angle = maxAngle + 0.2f * (TWO_PI - (maxAngle - minAngle));
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
rotation = offRotation * glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis);
|
||||
|
||||
QVERIFY2(constrained, "HingeConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
{ // test way off rotation < minAngle
|
||||
float offAngle = 0.5f;
|
||||
glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
float angle = minAngle - 0.2f * (TWO_PI - (maxAngle - minAngle));
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
rotation = offRotation * glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis);
|
||||
|
||||
QVERIFY2(constrained, "HingeConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
{ // test way off rotation > maxAngle with wrap over to minAngle
|
||||
float offAngle = -0.5f;
|
||||
glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
float angle = maxAngle + 0.6f * (TWO_PI - (maxAngle - minAngle));
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
rotation = offRotation * glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis);
|
||||
|
||||
QVERIFY2(constrained, "HingeConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
{ // test way off rotation < minAngle with wrap over to maxAngle
|
||||
float offAngle = -0.6f;
|
||||
glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
float angle = minAngle - 0.7f * (TWO_PI - (maxAngle - minAngle));
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
rotation = offRotation * glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis);
|
||||
|
||||
QVERIFY2(constrained, "HingeConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
delete c;
|
||||
}
|
||||
|
||||
void AngularConstraintTests::testConeRollerConstraint() {
|
||||
float minAngleX = -PI / 5.0f;
|
||||
float minAngleY = -PI / 5.0f;
|
||||
float minAngleZ = -PI / 8.0f;
|
||||
|
||||
float maxAngleX = PI / 4.0f;
|
||||
float maxAngleY = PI / 3.0f;
|
||||
float maxAngleZ = PI / 4.0f;
|
||||
|
||||
glm::vec3 minAngles(minAngleX, minAngleY, minAngleZ);
|
||||
glm::vec3 maxAngles(maxAngleX, maxAngleY, maxAngleZ);
|
||||
AngularConstraint* c = AngularConstraint::newAngularConstraint(minAngles, maxAngles);
|
||||
|
||||
float expectedConeAngle = 0.25f * (maxAngleX - minAngleX + maxAngleY - minAngleY);
|
||||
glm::vec3 middleAngles = 0.5f * (maxAngles + minAngles);
|
||||
glm::quat yaw = glm::angleAxis(middleAngles[1], glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
glm::quat pitch = glm::angleAxis(middleAngles[0], glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
glm::vec3 expectedConeAxis = pitch * yaw * glm::vec3(0.0f, 0.0f, 1.0f);
|
||||
|
||||
glm::vec3 xAxis(1.0f, 0.0f, 0.0f);
|
||||
glm::vec3 perpAxis = glm::normalize(xAxis - glm::dot(xAxis, expectedConeAxis) * expectedConeAxis);
|
||||
|
||||
QVERIFY2(c != nullptr, "newAngularConstraint() should make a constraint");
|
||||
{ // test in middle of constraint
|
||||
glm::vec3 angles(PI/20.0f, 0.0f, PI/10.0f);
|
||||
glm::quat rotation(angles);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
QVERIFY2(!constrained, "ConeRollerConstraint should not clamp()");
|
||||
QVERIFY2(newRotation == rotation, "ConeRollerConstraint should not change rotation");
|
||||
}
|
||||
float deltaAngle = 0.001f;
|
||||
{ // test just inside edge of cone
|
||||
glm::quat rotation = glm::angleAxis(expectedConeAngle - deltaAngle, perpAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
QVERIFY2(!constrained, "ConeRollerConstraint should not clamp()");
|
||||
QVERIFY2(newRotation == rotation, "ConeRollerConstraint should not change rotation");
|
||||
}
|
||||
{ // test just outside edge of cone
|
||||
glm::quat rotation = glm::angleAxis(expectedConeAngle + deltaAngle, perpAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
QVERIFY2(constrained, "ConeRollerConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation");
|
||||
}
|
||||
{ // test just inside min edge of roll
|
||||
glm::quat rotation = glm::angleAxis(minAngleZ + deltaAngle, expectedConeAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
QVERIFY2(!constrained, "ConeRollerConstraint should not clamp()");
|
||||
QVERIFY2(newRotation == rotation, "ConeRollerConstraint should not change rotation");
|
||||
}
|
||||
{ // test just inside max edge of roll
|
||||
glm::quat rotation = glm::angleAxis(maxAngleZ - deltaAngle, expectedConeAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
QVERIFY2(!constrained, "ConeRollerConstraint should not clamp()");
|
||||
QVERIFY2(newRotation == rotation, "ConeRollerConstraint should not change rotation");
|
||||
}
|
||||
{ // test just outside min edge of roll
|
||||
glm::quat rotation = glm::angleAxis(minAngleZ - deltaAngle, expectedConeAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
glm::quat expectedRotation = glm::angleAxis(minAngleZ, expectedConeAxis);
|
||||
|
||||
QVERIFY2(constrained, "ConeRollerConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
{ // test just outside max edge of roll
|
||||
glm::quat rotation = glm::angleAxis(maxAngleZ + deltaAngle, expectedConeAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
glm::quat expectedRotation = glm::angleAxis(maxAngleZ, expectedConeAxis);
|
||||
|
||||
QVERIFY2(constrained, "ConeRollerConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
deltaAngle = 0.25f * expectedConeAngle;
|
||||
{ // test far outside cone and min roll
|
||||
glm::quat roll = glm::angleAxis(minAngleZ - deltaAngle, expectedConeAxis);
|
||||
glm::quat pitchYaw = glm::angleAxis(expectedConeAngle + deltaAngle, perpAxis);
|
||||
glm::quat rotation = pitchYaw * roll;
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
glm::quat expectedRoll = glm::angleAxis(minAngleZ, expectedConeAxis);
|
||||
glm::quat expectedPitchYaw = glm::angleAxis(expectedConeAngle, perpAxis);
|
||||
glm::quat expectedRotation = expectedPitchYaw * expectedRoll;
|
||||
|
||||
QVERIFY2(constrained, "ConeRollerConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
{ // test far outside cone and max roll
|
||||
glm::quat roll = glm::angleAxis(maxAngleZ + deltaAngle, expectedConeAxis);
|
||||
glm::quat pitchYaw = glm::angleAxis(- expectedConeAngle - deltaAngle, perpAxis);
|
||||
glm::quat rotation = pitchYaw * roll;
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
glm::quat expectedRoll = glm::angleAxis(maxAngleZ, expectedConeAxis);
|
||||
glm::quat expectedPitchYaw = glm::angleAxis(- expectedConeAngle, perpAxis);
|
||||
glm::quat expectedRotation = expectedPitchYaw * expectedRoll;
|
||||
|
||||
QVERIFY2(constrained, "ConeRollerConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
delete c;
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
//
|
||||
// AngularConstraintTests.h
|
||||
// tests/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.05.30
|
||||
// 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_AngularConstraintTests_h
|
||||
#define hifi_AngularConstraintTests_h
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <QtTest/QtTest>
|
||||
|
||||
class AngularConstraintTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void testHingeConstraint();
|
||||
void testConeRollerConstraint();
|
||||
};
|
||||
|
||||
float getErrorDifference(const glm::quat& a, const glm::quat& b);
|
||||
|
||||
#endif // hifi_AngularConstraintTests_h
|
272
unpublishedScripts/DomainContent/Home/tiltMaze/createTiltMaze.js
Normal file
272
unpublishedScripts/DomainContent/Home/tiltMaze/createTiltMaze.js
Normal file
|
@ -0,0 +1,272 @@
|
|||
//
|
||||
// createTiltMaze.js
|
||||
//
|
||||
// Created by James B. Pollack @imgntn on 2/15/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// This script creates a maze with a ball that you can tilt to try to get to the end.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var ball, ballSpawningAnchor, ballDetector, tiltMaze, lightAtTheEnd;
|
||||
|
||||
var MAZE_MODEL_URL = "http://hifi-content.s3.amazonaws.com/DomainContent/Home/tiltMaze/newmaze_tex-4.fbx";
|
||||
var MAZE_COLLISION_HULL = "http://hifi-content.s3.amazonaws.com/DomainContent/Home/tiltMaze/newmaze_tex-3.obj";
|
||||
var MAZE_SCRIPT = Script.resolvePath('maze.js?' + Math.random());
|
||||
|
||||
var SCALE = 0.5;
|
||||
|
||||
var MAZE_DIMENSIONS = Vec3.multiply(SCALE, {
|
||||
x: 1,
|
||||
y: 0.15,
|
||||
z: 1
|
||||
});
|
||||
|
||||
var BALL_DIMENSIONS = Vec3.multiply(SCALE, {
|
||||
x: 0.035,
|
||||
y: 0.035,
|
||||
z: 0.035
|
||||
});
|
||||
|
||||
var BALL_SPAWNER_DIMENSIONS = Vec3.multiply(SCALE, {
|
||||
x: 0.05,
|
||||
y: 0.05,
|
||||
z: 0.05
|
||||
});
|
||||
|
||||
var BALL_DETECTOR_DIMENSIONS = Vec3.multiply(SCALE, {
|
||||
x: 0.1,
|
||||
y: 0.1,
|
||||
z: 0.1
|
||||
});
|
||||
|
||||
var BALL_COLOR = {
|
||||
red: 255,
|
||||
green: 0,
|
||||
blue: 0
|
||||
};
|
||||
|
||||
var DEBUG_COLOR = {
|
||||
red: 0,
|
||||
green: 255,
|
||||
blue: 0
|
||||
};
|
||||
|
||||
var center = Vec3.sum(Vec3.sum(MyAvatar.position, {
|
||||
x: 0,
|
||||
y: 0.5,
|
||||
z: 0
|
||||
}), Vec3.multiply(1.5, Quat.getFront(Camera.getOrientation())));
|
||||
|
||||
var CLEANUP = true;
|
||||
|
||||
var BALL_FORWARD_OFFSET = -0.2 * SCALE;
|
||||
var BALL_RIGHT_OFFSET = -0.4 * SCALE;
|
||||
var BALL_VERTICAL_OFFSET = 0.02 * SCALE;
|
||||
|
||||
var BALL_FRICTION = 0.7;
|
||||
var BALL_RESTITUTION = 0.1;
|
||||
var BALL_DAMPING = 0.6;
|
||||
var BALL_ANGULAR_DAMPING = 0.2;
|
||||
var BALL_DENSITY = 1000;
|
||||
var BALL_GRAVITY = {
|
||||
x: 0,
|
||||
y: -9.8,
|
||||
z: 0
|
||||
};
|
||||
|
||||
var MAZE_DENSITY = 1000;
|
||||
var MAZE_RESTITUTION = 0.1;
|
||||
var MAZE_DAMPING = 0.6;
|
||||
var MAZE_ANGULAR_DAMPING = 0.6;
|
||||
|
||||
var DETECTOR_VERTICAL_OFFSET = 0.0 * SCALE;
|
||||
var DETECTOR_FORWARD_OFFSET = 0.35 * SCALE;
|
||||
var DETECTOR_RIGHT_OFFSET = 0.35 * SCALE;
|
||||
|
||||
var END_LIGHT_COLOR = {
|
||||
red: 255,
|
||||
green: 0,
|
||||
blue: 0
|
||||
};
|
||||
|
||||
var END_LIGHT_DIMENSIONS = {
|
||||
x: 0.2,
|
||||
y: 0.2,
|
||||
z: 0.8
|
||||
};
|
||||
|
||||
var END_LIGHT_INTENSITY = 0.035;
|
||||
var END_LIGHT_CUTOFF = 30;
|
||||
var END_LIGHT_EXPONENT = 1;
|
||||
|
||||
var getBallStartLocation = function() {
|
||||
var mazeProps = Entities.getEntityProperties(tiltMaze);
|
||||
var right = Quat.getRight(mazeProps.rotation);
|
||||
var front = Quat.getFront(mazeProps.rotation);
|
||||
var vertical = {
|
||||
x: 0,
|
||||
y: BALL_VERTICAL_OFFSET,
|
||||
z: 0
|
||||
};
|
||||
|
||||
var finalOffset = Vec3.sum(vertical, Vec3.multiply(right, BALL_RIGHT_OFFSET));
|
||||
finalOffset = Vec3.sum(finalOffset, Vec3.multiply(front, BALL_FORWARD_OFFSET));
|
||||
var location = Vec3.sum(mazeProps.position, finalOffset);
|
||||
return location;
|
||||
};
|
||||
|
||||
var getBallFinishLocation = function() {
|
||||
var mazeProps = Entities.getEntityProperties(tiltMaze);
|
||||
var right = Quat.getRight(mazeProps.rotation);
|
||||
var forward = Quat.getFront(mazeProps.rotation);
|
||||
var up = Quat.getUp(mazeProps.rotation);
|
||||
|
||||
var position = Vec3.sum(mazeProps.position, Vec3.multiply(up, DETECTOR_VERTICAL_OFFSET));
|
||||
position = Vec3.sum(position, Vec3.multiply(right, DETECTOR_RIGHT_OFFSET));
|
||||
position = Vec3.sum(position, Vec3.multiply(forward, DETECTOR_FORWARD_OFFSET));
|
||||
|
||||
return position;
|
||||
};
|
||||
|
||||
|
||||
var createBall = function(position) {
|
||||
var properties = {
|
||||
name: 'Hifi Tilt Maze Ball',
|
||||
type: 'Sphere',
|
||||
position: getBallStartLocation(),
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
friction: BALL_FRICTION,
|
||||
restitution: BALL_RESTITUTION,
|
||||
angularDamping: BALL_ANGULAR_DAMPING,
|
||||
damping: BALL_DAMPING,
|
||||
gravity: BALL_GRAVITY,
|
||||
density: BALL_DENSITY,
|
||||
color: BALL_COLOR,
|
||||
dimensions: BALL_DIMENSIONS,
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
grabbable: false
|
||||
}
|
||||
})
|
||||
|
||||
};
|
||||
|
||||
ball = Entities.addEntity(properties);
|
||||
|
||||
};
|
||||
|
||||
var createBallSpawningAnchor = function() {
|
||||
var properties = {
|
||||
name: 'Hifi Tilt Maze Ball Detector',
|
||||
parentID: tiltMaze,
|
||||
type: 'Box',
|
||||
color: DEBUG_COLOR,
|
||||
dimensions: BALL_SPAWNER_DIMENSIONS,
|
||||
position: getBallStartLocation(),
|
||||
collisionless: true,
|
||||
visible: false,
|
||||
};
|
||||
|
||||
ballSpawningAnchor = Entities.addEntity(properties);
|
||||
};
|
||||
|
||||
|
||||
var createBallDetector = function() {
|
||||
|
||||
var properties = {
|
||||
name: 'Hifi Tilt Maze Ball Detector',
|
||||
parentID: tiltMaze,
|
||||
type: 'Box',
|
||||
color: DEBUG_COLOR,
|
||||
shapeType: 'none',
|
||||
dimensions: BALL_DETECTOR_DIMENSIONS,
|
||||
position: getBallFinishLocation(),
|
||||
collisionless: true,
|
||||
dynamic: false,
|
||||
visible: false,
|
||||
};
|
||||
|
||||
ballDetector = Entities.addEntity(properties);
|
||||
|
||||
};
|
||||
|
||||
var createTiltMaze = function(position) {
|
||||
var properties = {
|
||||
name: 'Hifi Tilt Maze',
|
||||
type: 'Model',
|
||||
modelURL: MAZE_MODEL_URL,
|
||||
compoundShapeURL: MAZE_COLLISION_HULL,
|
||||
dimensions: MAZE_DIMENSIONS,
|
||||
position: position,
|
||||
restitution: MAZE_RESTITUTION,
|
||||
damping: MAZE_DAMPING,
|
||||
angularDamping: MAZE_ANGULAR_DAMPING,
|
||||
dynamic: true,
|
||||
density: MAZE_DENSITY,
|
||||
script: MAZE_SCRIPT
|
||||
}
|
||||
|
||||
tiltMaze = Entities.addEntity(properties);
|
||||
|
||||
};
|
||||
|
||||
var createLightAtTheEnd = function() {
|
||||
|
||||
var mazeProps = Entities.getEntityProperties(tiltMaze, 'position');
|
||||
|
||||
var up = Quat.getUp(mazeProps.rotation);
|
||||
var down = Vec3.multiply(-1, up);
|
||||
|
||||
var emitOrientation = Quat.rotationBetween(Vec3.UNIT_NEG_Z, down);
|
||||
|
||||
var position = getBallFinishLocation();
|
||||
var lightProperties = {
|
||||
parentID: tiltMaze,
|
||||
name: 'Hifi Tilt Maze End Light',
|
||||
type: "Light",
|
||||
isSpotlight: true,
|
||||
dimensions: END_LIGHT_DIMENSIONS,
|
||||
color: END_LIGHT_COLOR,
|
||||
intensity: END_LIGHT_INTENSITY,
|
||||
exponent: END_LIGHT_EXPONENT,
|
||||
cutoff: END_LIGHT_CUTOFF,
|
||||
lifetime: -1,
|
||||
position: position,
|
||||
rotation: emitOrientation
|
||||
};
|
||||
|
||||
lightAtTheEnd = Entities.addEntity(lightProperties);
|
||||
};
|
||||
|
||||
var createAll = function() {
|
||||
createTiltMaze(center);
|
||||
createBallSpawningAnchor();
|
||||
createBallDetector(center);
|
||||
createBall(center);
|
||||
createLightAtTheEnd();
|
||||
Entities.editEntity(tiltMaze, {
|
||||
userData: JSON.stringify({
|
||||
tiltMaze: {
|
||||
firstBall: ball,
|
||||
ballSpawner: ballSpawningAnchor,
|
||||
detector: ballDetector,
|
||||
lightAtTheEnd: lightAtTheEnd
|
||||
}
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
createAll();
|
||||
|
||||
if (CLEANUP === true) {
|
||||
Script.scriptEnding.connect(function() {
|
||||
Entities.deleteEntity(tiltMaze);
|
||||
Entities.deleteEntity(ball);
|
||||
Entities.deleteEntity(ballSpawningAnchor);
|
||||
Entities.deleteEntity(lightAtTheEnd);
|
||||
});
|
||||
};
|
212
unpublishedScripts/DomainContent/Home/tiltMaze/maze.js
Normal file
212
unpublishedScripts/DomainContent/Home/tiltMaze/maze.js
Normal file
|
@ -0,0 +1,212 @@
|
|||
//
|
||||
// maze.js
|
||||
//
|
||||
//
|
||||
// Created by James B. Pollack @imgntn on 2/15/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// This script resets a ball to its original position when the ball enters it.
|
||||
//
|
||||
// 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 SCALE = 0.5;
|
||||
var VICTORY_SOUND;
|
||||
var BALL_DISTANCE_THRESHOLD = 1 * SCALE;
|
||||
|
||||
var BALL_DETECTOR_THRESHOLD = 0.075 * SCALE;
|
||||
var BALL_FORWARD_OFFSET = -0.2 * SCALE;
|
||||
var BALL_RIGHT_OFFSET = -0.4 * SCALE;
|
||||
var BALL_VERTICAL_OFFSET = 0.02 * SCALE;
|
||||
|
||||
|
||||
var BALL_FRICTION = 0.7;
|
||||
var BALL_RESTITUTION = 0.1;
|
||||
var BALL_DAMPING = 0.6;
|
||||
var BALL_ANGULAR_DAMPING = 0.2;
|
||||
var BALL_DENSITY = 1000;
|
||||
var BALL_GRAVITY = {
|
||||
x: 0,
|
||||
y: -9.8,
|
||||
z: 0
|
||||
};
|
||||
|
||||
var BALL_DIMENSIONS = Vec3.multiply(SCALE, {
|
||||
x: 0.05,
|
||||
y: 0.05,
|
||||
z: 0.05
|
||||
});
|
||||
|
||||
var BALL_COLOR = {
|
||||
red: 255,
|
||||
green: 0,
|
||||
blue: 0
|
||||
};
|
||||
|
||||
var _this;
|
||||
|
||||
function Maze() {
|
||||
_this = this;
|
||||
return;
|
||||
}
|
||||
|
||||
Maze.prototype = {
|
||||
ball: null,
|
||||
ballLocked: false,
|
||||
preload: function(entityID) {
|
||||
this.entityID = entityID;
|
||||
VICTORY_SOUND = SoundCache.getSound("http://hifi-content.s3.amazonaws.com/DomainContent/Home/tiltMaze/levelUp.wav");
|
||||
},
|
||||
startNearGrab: function() {
|
||||
//check to make sure a ball is in range, otherwise create one
|
||||
this.testBallDistance();
|
||||
},
|
||||
continueNearGrab: function() {
|
||||
this.testWinDistance();
|
||||
this.testBallDistance();
|
||||
},
|
||||
continueDistantGrab: function() {
|
||||
this.testBallDistance();
|
||||
this.testWinDistance();
|
||||
},
|
||||
releaseGrab: function() {
|
||||
this.testBallDistance();
|
||||
},
|
||||
getBallStartLocation: function() {
|
||||
var mazeProps = Entities.getEntityProperties(this.entityID);
|
||||
var right = Quat.getRight(mazeProps.rotation);
|
||||
var front = Quat.getFront(mazeProps.rotation);
|
||||
var vertical = {
|
||||
x: 0,
|
||||
y: BALL_VERTICAL_OFFSET,
|
||||
z: 0
|
||||
};
|
||||
|
||||
var finalOffset = Vec3.sum(vertical, Vec3.multiply(right, BALL_RIGHT_OFFSET));
|
||||
finalOffset = Vec3.sum(finalOffset, Vec3.multiply(front, BALL_FORWARD_OFFSET));
|
||||
var location = Vec3.sum(mazeProps.position, finalOffset);
|
||||
return location;
|
||||
},
|
||||
createBall: function() {
|
||||
if (this.ballLocked === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
var properties = {
|
||||
name: 'Hifi Tilt Maze Ball',
|
||||
type: 'Sphere',
|
||||
position: this.getBallStartLocation(),
|
||||
dynamic: true,
|
||||
collisionless: false,
|
||||
friction: BALL_FRICTION,
|
||||
restitution: BALL_RESTITUTION,
|
||||
angularDamping: BALL_ANGULAR_DAMPING,
|
||||
damping: BALL_DAMPING,
|
||||
gravity: BALL_GRAVITY,
|
||||
density: BALL_DENSITY,
|
||||
color: BALL_COLOR,
|
||||
dimensions: BALL_DIMENSIONS
|
||||
};
|
||||
|
||||
this.ball = Entities.addEntity(properties);
|
||||
},
|
||||
destroyBall: function() {
|
||||
var results = Entities.findEntities(MyAvatar.position, 10);
|
||||
results.forEach(function(result) {
|
||||
var props = Entities.getEntityProperties(result, ['name']);
|
||||
var isAMazeBall = props.name.indexOf('Maze Ball');
|
||||
if (isAMazeBall > -1 && result === _this.ball) {
|
||||
Entities.deleteEntity(result);
|
||||
}
|
||||
})
|
||||
},
|
||||
testBallDistance: function() {
|
||||
if (this.ballLocked === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
var userData = Entities.getEntityProperties(this.entityID, 'userData').userData;
|
||||
var data = null;
|
||||
try {
|
||||
data = JSON.parse(userData);
|
||||
} catch (e) {
|
||||
// print('error parsing json in maze userdata')
|
||||
}
|
||||
if (data === null) {
|
||||
// print('data is null in userData')
|
||||
return;
|
||||
}
|
||||
|
||||
var ballPosition;
|
||||
if (this.ball === null) {
|
||||
this.ball = data.tiltMaze.firstBall;
|
||||
ballPosition = Entities.getEntityProperties(data.tiltMaze.firstBall, 'position').position;
|
||||
|
||||
} else {
|
||||
ballPosition = Entities.getEntityProperties(this.ball, 'position').position;
|
||||
}
|
||||
|
||||
var ballSpawnerPosition = Entities.getEntityProperties(data.tiltMaze.ballSpawner, 'position').position;
|
||||
|
||||
var separation = Vec3.distance(ballPosition, ballSpawnerPosition);
|
||||
if (separation > BALL_DISTANCE_THRESHOLD) {
|
||||
this.destroyBall();
|
||||
this.createBall();
|
||||
}
|
||||
},
|
||||
testWinDistance: function() {
|
||||
if (this.ballLocked === true) {
|
||||
return;
|
||||
}
|
||||
// print('testing win distance')
|
||||
var userData = Entities.getEntityProperties(this.entityID, 'userData').userData;
|
||||
var data = null;
|
||||
try {
|
||||
data = JSON.parse(userData);
|
||||
} catch (e) {
|
||||
// print('error parsing json in maze userdata')
|
||||
}
|
||||
if (data === null) {
|
||||
// print('data is null in userData')
|
||||
return;
|
||||
}
|
||||
|
||||
var ballPosition;
|
||||
if (this.ball === null) {
|
||||
this.ball = data.tiltMaze.firstBall;
|
||||
ballPosition = Entities.getEntityProperties(data.tiltMaze.firstBall, 'position').position;
|
||||
} else {
|
||||
ballPosition = Entities.getEntityProperties(this.ball, 'position').position;
|
||||
}
|
||||
|
||||
var ballDetectorPosition = Entities.getEntityProperties(data.tiltMaze.detector, 'position').position;
|
||||
var separation = Vec3.distance(ballPosition, ballDetectorPosition);
|
||||
if (separation < BALL_DETECTOR_THRESHOLD) {
|
||||
this.ballLocked = true;
|
||||
this.destroyBall();
|
||||
this.playVictorySound();
|
||||
Script.setTimeout(function() {
|
||||
_this.ballLocked = false;
|
||||
_this.createBall();
|
||||
}, 1500)
|
||||
}
|
||||
},
|
||||
playVictorySound: function() {
|
||||
var position = Entities.getEntityProperties(this.entityID, "position").position;
|
||||
|
||||
var audioProperties = {
|
||||
volume: 0.25,
|
||||
position: position
|
||||
};
|
||||
Audio.playSound(VICTORY_SOUND, audioProperties);
|
||||
|
||||
},
|
||||
};
|
||||
|
||||
return new Maze();
|
||||
});
|
Loading…
Reference in a new issue