mirror of
https://github.com/JulianGro/overte.git
synced 2025-05-01 04:23:00 +02:00
Merge branch 'master' into 20812
This commit is contained in:
commit
247ba7d3dd
89 changed files with 3954 additions and 1681 deletions
assignment-client/src
cmake/templates
domain-server/resources
examples
acScripts
audioExamples/acAudioSearching
controllers
html
libraries
particle_explorer
playa/fireworks
interface/src
libraries
controllers/src/controllers
entities-renderer/src
EntityTreeRenderer.cppEntityTreeRenderer.hRenderableBoxEntityItem.cppRenderableLightEntityItem.cppRenderableZoneEntityItem.cppRenderableZoneEntityItem.h
entities/src
EntityItemProperties.cppEntityItemProperties.hEntityItemPropertiesDefaults.hEntityPropertyFlags.hEntityTreeElement.cppKeyLightPropertyGroup.cppLightEntityItem.cppLightEntityItem.h
gpu/src/gpu
model/src/model
networking/src/udt
octree/src
Octree.cppOctree.hOctreeElement.cppOctreeElement.hOctreeHeadlessViewer.cppOctreeHeadlessViewer.hOctreeQuery.cppOctreeQuery.hViewFrustum.cppViewFrustum.h
render-utils/src
render/src/render
script-engine/src
shared/src
ui/src
tests
octree/src
shared/src
unpublishedScripts/DomainContent/Home/tiltMaze
|
@ -835,41 +835,40 @@ void AudioMixer::parseSettingsObject(const QJsonObject &settingsObject) {
|
|||
if (audioEnvGroupObject[AUDIO_ZONES].isObject()) {
|
||||
const QJsonObject& zones = audioEnvGroupObject[AUDIO_ZONES].toObject();
|
||||
|
||||
const QString X_RANGE = "x_range";
|
||||
const QString Y_RANGE = "y_range";
|
||||
const QString Z_RANGE = "z_range";
|
||||
const QString X_MIN = "x_min";
|
||||
const QString X_MAX = "x_max";
|
||||
const QString Y_MIN = "y_min";
|
||||
const QString Y_MAX = "y_max";
|
||||
const QString Z_MIN = "z_min";
|
||||
const QString Z_MAX = "z_max";
|
||||
foreach (const QString& zone, zones.keys()) {
|
||||
QJsonObject zoneObject = zones[zone].toObject();
|
||||
|
||||
if (zoneObject.contains(X_RANGE) && zoneObject.contains(Y_RANGE) && zoneObject.contains(Z_RANGE)) {
|
||||
QStringList xRange = zoneObject.value(X_RANGE).toString().split("-", QString::SkipEmptyParts);
|
||||
QStringList yRange = zoneObject.value(Y_RANGE).toString().split("-", QString::SkipEmptyParts);
|
||||
QStringList zRange = zoneObject.value(Z_RANGE).toString().split("-", QString::SkipEmptyParts);
|
||||
if (zoneObject.contains(X_MIN) && zoneObject.contains(X_MAX) && zoneObject.contains(Y_MIN) &&
|
||||
zoneObject.contains(Y_MAX) && zoneObject.contains(Z_MIN) && zoneObject.contains(Z_MAX)) {
|
||||
|
||||
if (xRange.size() == 2 && yRange.size() == 2 && zRange.size() == 2) {
|
||||
float xMin, xMax, yMin, yMax, zMin, zMax;
|
||||
bool ok, allOk = true;
|
||||
xMin = xRange[0].toFloat(&ok);
|
||||
allOk &= ok;
|
||||
xMax = xRange[1].toFloat(&ok);
|
||||
allOk &= ok;
|
||||
yMin = yRange[0].toFloat(&ok);
|
||||
allOk &= ok;
|
||||
yMax = yRange[1].toFloat(&ok);
|
||||
allOk &= ok;
|
||||
zMin = zRange[0].toFloat(&ok);
|
||||
allOk &= ok;
|
||||
zMax = zRange[1].toFloat(&ok);
|
||||
allOk &= ok;
|
||||
float xMin, xMax, yMin, yMax, zMin, zMax;
|
||||
bool ok, allOk = true;
|
||||
xMin = zoneObject.value(X_MIN).toString().toFloat(&ok);
|
||||
allOk &= ok;
|
||||
xMax = zoneObject.value(X_MAX).toString().toFloat(&ok);
|
||||
allOk &= ok;
|
||||
yMin = zoneObject.value(Y_MIN).toString().toFloat(&ok);
|
||||
allOk &= ok;
|
||||
yMax = zoneObject.value(Y_MAX).toString().toFloat(&ok);
|
||||
allOk &= ok;
|
||||
zMin = zoneObject.value(Z_MIN).toString().toFloat(&ok);
|
||||
allOk &= ok;
|
||||
zMax = zoneObject.value(Z_MAX).toString().toFloat(&ok);
|
||||
allOk &= ok;
|
||||
|
||||
if (allOk) {
|
||||
glm::vec3 corner(xMin, yMin, zMin);
|
||||
glm::vec3 dimensions(xMax - xMin, yMax - yMin, zMax - zMin);
|
||||
AABox zoneAABox(corner, dimensions);
|
||||
_audioZones.insert(zone, zoneAABox);
|
||||
qDebug() << "Added zone:" << zone << "(corner:" << corner
|
||||
<< ", dimensions:" << dimensions << ")";
|
||||
}
|
||||
if (allOk) {
|
||||
glm::vec3 corner(xMin, yMin, zMin);
|
||||
glm::vec3 dimensions(xMax - xMin, yMax - yMin, zMax - zMin);
|
||||
AABox zoneAABox(corner, dimensions);
|
||||
_audioZones.insert(zone, zoneAABox);
|
||||
qDebug() << "Added zone:" << zone << "(corner:" << corner
|
||||
<< ", dimensions:" << dimensions << ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -230,22 +230,40 @@
|
|||
},
|
||||
"columns": [
|
||||
{
|
||||
"name": "x_range",
|
||||
"label": "X range",
|
||||
"name": "x_min",
|
||||
"label": "X start",
|
||||
"can_set": true,
|
||||
"placeholder": "0-16384"
|
||||
"placeholder": "-16384.0"
|
||||
},
|
||||
{
|
||||
"name": "y_range",
|
||||
"label": "Y range",
|
||||
"name": "x_max",
|
||||
"label": "X end",
|
||||
"can_set": true,
|
||||
"placeholder": "0-16384"
|
||||
"placeholder": "16384.0"
|
||||
},
|
||||
{
|
||||
"name": "y_min",
|
||||
"label": "Y start",
|
||||
"can_set": true,
|
||||
"placeholder": "-16384.0"
|
||||
},
|
||||
{
|
||||
"name": "z_range",
|
||||
"label": "Z range",
|
||||
"name": "y_max",
|
||||
"label": "Y end",
|
||||
"can_set": true,
|
||||
"placeholder": "0-16384"
|
||||
"placeholder": "16384.0"
|
||||
},
|
||||
{
|
||||
"name": "z_min",
|
||||
"label": "Z start",
|
||||
"can_set": true,
|
||||
"placeholder": "-16384.0"
|
||||
},
|
||||
{
|
||||
"name": "z_max",
|
||||
"label": "Z end",
|
||||
"can_set": true,
|
||||
"placeholder": "16384.0"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -199,7 +199,7 @@ pointInExtents = function(point, minPoint, maxPoint) {
|
|||
* @param Number l The lightness
|
||||
* @return Array The RGB representation
|
||||
*/
|
||||
hslToRgb = function(hsl, hueOffset) {
|
||||
hslToRgb = function(hsl) {
|
||||
var r, g, b;
|
||||
if (hsl.s == 0) {
|
||||
r = g = b = hsl.l; // achromatic
|
||||
|
|
|
@ -43,10 +43,10 @@ var keysToAllow = [
|
|||
'emitSpeed',
|
||||
'speedSpread',
|
||||
'emitOrientation',
|
||||
'emitDimensios',
|
||||
'emitRadiusStart',
|
||||
'emitDimensions',
|
||||
'polarStart',
|
||||
'polarFinish',
|
||||
'azimuthStart',
|
||||
'azimuthFinish',
|
||||
'emitAcceleration',
|
||||
'accelerationSpread',
|
||||
|
|
229
examples/playa/fireworks/fireworksLaunchButtonEntityScript.js
Normal file
229
examples/playa/fireworks/fireworksLaunchButtonEntityScript.js
Normal file
|
@ -0,0 +1,229 @@
|
|||
//
|
||||
// fireworksLaunchEntityScript.js
|
||||
// examples/playa/fireworks
|
||||
//
|
||||
// Created by Eric Levin on 2/24/16.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Run this script to spawn a big fireworks launch button that a user can press
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
(function() {
|
||||
Script.include("../../libraries/utils.js");
|
||||
var _this;
|
||||
Fireworks = function() {
|
||||
_this = this;
|
||||
_this.launchSound = SoundCache.getSound("https://s3-us-west-1.amazonaws.com/hifi-content/eric/Sounds/missle+launch.wav");
|
||||
_this.explosionSound = SoundCache.getSound("https://s3-us-west-1.amazonaws.com/hifi-content/eric/Sounds/fireworksExplosion.wav");
|
||||
_this.timeToExplosionRange = {
|
||||
min: 2500,
|
||||
max: 4500
|
||||
};
|
||||
};
|
||||
|
||||
Fireworks.prototype = {
|
||||
|
||||
startNearTrigger: function() {
|
||||
_this.shootFireworks();
|
||||
},
|
||||
|
||||
startFarTrigger: function() {
|
||||
_this.shootFireworks();
|
||||
},
|
||||
|
||||
clickReleaseOnEntity: function() {
|
||||
_this.shootFireworks();
|
||||
},
|
||||
|
||||
shootFireworks: function() {
|
||||
// Get launch position
|
||||
var launchPosition = getEntityUserData(_this.entityID).launchPosition || _this.position;
|
||||
|
||||
var numMissles = randInt(1, 3);
|
||||
for(var i = 0; i < numMissles; i++) {
|
||||
_this.shootMissle(launchPosition);
|
||||
}
|
||||
},
|
||||
|
||||
shootMissle: function(launchPosition) {
|
||||
Audio.playSound(_this.launchSound, {
|
||||
position: launchPosition,
|
||||
volume: 0.5
|
||||
});
|
||||
|
||||
var MODEL_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/Rocket-2.fbx";
|
||||
var missleDimensions = Vec3.multiply({
|
||||
x: 0.24,
|
||||
y: 0.7,
|
||||
z: 0.24
|
||||
}, randFloat(0.2, 1.5));
|
||||
var missleRotation = Quat.fromPitchYawRollDegrees(randInt(-60, 60), 0, randInt(-60, 60));
|
||||
var missleVelocity = Vec3.multiply(Quat.getUp(missleRotation), randFloat(2, 4));
|
||||
var missleAcceleration = Vec3.multiply(Quat.getUp(missleRotation), randFloat(1, 3));
|
||||
var missle = Entities.addEntity({
|
||||
type: "Model",
|
||||
modelURL: MODEL_URL,
|
||||
position: launchPosition,
|
||||
rotation: missleRotation,
|
||||
dimensions: missleDimensions,
|
||||
damping: 0,
|
||||
dynamic: true,
|
||||
lifetime: 20, // Just in case
|
||||
velocity: missleVelocity,
|
||||
acceleration: missleAcceleration,
|
||||
angularVelocity: {
|
||||
x: 0,
|
||||
y: randInt(-1, 1),
|
||||
z: 0
|
||||
},
|
||||
angularDamping: 0,
|
||||
visible: false
|
||||
});
|
||||
|
||||
var smokeTrailPosition = Vec3.sum(launchPosition, Vec3.multiply(-missleDimensions.y / 2 + 0.1, Quat.getUp(missleRotation)));
|
||||
var smoke = Entities.addEntity({
|
||||
type: "ParticleEffect",
|
||||
position: smokeTrailPosition,
|
||||
lifespan: 10,
|
||||
lifetime: 20,
|
||||
name: "Smoke Trail",
|
||||
maxParticles: 3000,
|
||||
emitRate: 80,
|
||||
emitSpeed: 0,
|
||||
speedSpread: 0,
|
||||
dimensions: {
|
||||
x: 1000,
|
||||
y: 1000,
|
||||
z: 1000
|
||||
},
|
||||
polarStart: 0,
|
||||
polarFinish: 0,
|
||||
azimuthStart: -3.14,
|
||||
azimuthFinish: 3.14,
|
||||
emitAcceleration: {
|
||||
x: 0,
|
||||
y: 0.01,
|
||||
z: 0
|
||||
},
|
||||
accelerationSpread: {
|
||||
x: 0.01,
|
||||
y: 0,
|
||||
z: 0.01
|
||||
},
|
||||
radiusSpread: 0.03,
|
||||
particleRadius: 0.3,
|
||||
radiusStart: 0.06,
|
||||
radiusFinish: 0.9,
|
||||
alpha: 0.1,
|
||||
alphaSpread: 0,
|
||||
alphaStart: 0.7,
|
||||
alphaFinish: 0,
|
||||
textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png",
|
||||
emitterShouldTrail: true,
|
||||
parentID: missle,
|
||||
});
|
||||
|
||||
|
||||
Script.setTimeout(function() {
|
||||
Entities.editEntity(smoke, {
|
||||
parentID: null,
|
||||
isEmitting: false
|
||||
});
|
||||
|
||||
var explodeBasePosition = Entities.getEntityProperties(missle, "position").position;
|
||||
|
||||
Entities.deleteEntity(missle);
|
||||
// Explode 1 firework immediately
|
||||
_this.explodeFirework(explodeBasePosition);
|
||||
var numAdditionalFireworks = randInt(1, 5);
|
||||
for (var i = 0; i < numAdditionalFireworks; i++) {
|
||||
Script.setTimeout(function() {
|
||||
var explodePosition = Vec3.sum(explodeBasePosition, {x: randFloat(-3, 3), y: randFloat(-3, 3), z: randFloat(-3, 3)});
|
||||
_this.explodeFirework(explodePosition);
|
||||
}, randInt(0, 1000))
|
||||
}
|
||||
}, randFloat(_this.timeToExplosionRange.min, _this.timeToExplosionRange.max));
|
||||
|
||||
|
||||
},
|
||||
|
||||
explodeFirework: function(explodePosition) {
|
||||
// We just exploded firework, so stop emitting its fire and smoke
|
||||
|
||||
Audio.playSound(_this.explosionSound, {
|
||||
position: explodePosition
|
||||
});
|
||||
var firework = Entities.addEntity({
|
||||
name: "fireworks emitter",
|
||||
position: explodePosition,
|
||||
type: "ParticleEffect",
|
||||
colorStart: hslToRgb({
|
||||
h: Math.random(),
|
||||
s: 0.5,
|
||||
l: 0.7
|
||||
}),
|
||||
color: hslToRgb({
|
||||
h: Math.random(),
|
||||
s: 0.5,
|
||||
l: 0.5
|
||||
}),
|
||||
colorFinish: hslToRgb({
|
||||
h: Math.random(),
|
||||
s: 0.5,
|
||||
l: 0.7
|
||||
}),
|
||||
maxParticles: 10000,
|
||||
lifetime: 20,
|
||||
lifespan: randFloat(1.5, 3),
|
||||
emitRate: randInt(500, 5000),
|
||||
emitSpeed: randFloat(0.5, 2),
|
||||
speedSpread: 0.2,
|
||||
emitOrientation: Quat.fromPitchYawRollDegrees(randInt(0, 360), randInt(0, 360), randInt(0, 360)),
|
||||
polarStart: 1,
|
||||
polarFinish: randFloat(1.2, 3),
|
||||
azimuthStart: -Math.PI,
|
||||
azimuthFinish: Math.PI,
|
||||
emitAcceleration: {
|
||||
x: 0,
|
||||
y: randFloat(-1, -0.2),
|
||||
z: 0
|
||||
},
|
||||
accelerationSpread: {
|
||||
x: Math.random(),
|
||||
y: 0,
|
||||
z: Math.random()
|
||||
},
|
||||
particleRadius: randFloat(0.001, 0.1),
|
||||
radiusSpread: Math.random() * 0.1,
|
||||
radiusStart: randFloat(0.001, 0.1),
|
||||
radiusFinish: randFloat(0.001, 0.1),
|
||||
alpha: randFloat(0.8, 1.0),
|
||||
alphaSpread: randFloat(0.1, 0.2),
|
||||
alphaStart: randFloat(0.7, 1.0),
|
||||
alphaFinish: randFloat(0.7, 1.0),
|
||||
textures: "http://ericrius1.github.io/PlatosCave/assets/star.png",
|
||||
});
|
||||
|
||||
|
||||
Script.setTimeout(function() {
|
||||
Entities.editEntity(firework, {
|
||||
isEmitting: false
|
||||
});
|
||||
}, randInt(500, 1000));
|
||||
|
||||
},
|
||||
|
||||
preload: function(entityID) {
|
||||
_this.entityID = entityID;
|
||||
_this.position = Entities.getEntityProperties(_this.entityID, "position").position;
|
||||
print("EBL RELOAD ENTITY SCRIPT!!!");
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// entity scripts always need to return a newly constructed object of our type
|
||||
return new Fireworks();
|
||||
});
|
46
examples/playa/fireworks/fireworksLaunchButtonSpawner.js
Normal file
46
examples/playa/fireworks/fireworksLaunchButtonSpawner.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// fireworksLaunchButtonSpawner.js
|
||||
// examples/playa/fireworks
|
||||
//
|
||||
// Created by Eric Levina on 2/24/16.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Run this script to spawn a big fireworks launch button that a user can press
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
var orientation = Camera.getOrientation();
|
||||
orientation = Quat.safeEulerAngles(orientation);
|
||||
orientation.x = 0;
|
||||
orientation = Quat.fromVec3Degrees(orientation);
|
||||
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(orientation)));
|
||||
|
||||
// Math.random ensures no caching of script
|
||||
var SCRIPT_URL = Script.resolvePath("fireworksLaunchButtonEntityScript.js");
|
||||
var MODEL_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/Launch-Button.fbx";
|
||||
var launchButton = Entities.addEntity({
|
||||
type: "Model",
|
||||
name: "launch pad",
|
||||
modelURL: MODEL_URL,
|
||||
position: center,
|
||||
dimensions: {
|
||||
x: 0.98,
|
||||
y: 1.16,
|
||||
z: 0.98
|
||||
},
|
||||
script: SCRIPT_URL,
|
||||
userData: JSON.stringify({
|
||||
launchPosition: {x: 1, y: 1.8, z: -20.9},
|
||||
grabbableKey: {
|
||||
wantsTrigger: true
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
function cleanup() {
|
||||
Entities.deleteEntity(launchButton);
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
|
@ -210,8 +210,6 @@ static const QString INFO_EDIT_ENTITIES_PATH = "html/edit-commands.html";
|
|||
|
||||
static const unsigned int THROTTLED_SIM_FRAMERATE = 15;
|
||||
static const int THROTTLED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / THROTTLED_SIM_FRAMERATE;
|
||||
static const unsigned int CAPPED_SIM_FRAMERATE = 120;
|
||||
static const int CAPPED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / CAPPED_SIM_FRAMERATE;
|
||||
|
||||
static const uint32_t INVALID_FRAME = UINT32_MAX;
|
||||
|
||||
|
@ -425,11 +423,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 +558,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 +602,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 +623,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);
|
||||
|
@ -817,6 +815,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
} else if (action == controller::toInt(controller::Action::RETICLE_Y)) {
|
||||
auto oldPos = _compositor.getReticlePosition();
|
||||
_compositor.setReticlePosition({ oldPos.x, oldPos.y + state });
|
||||
} else if (action == controller::toInt(controller::Action::TOGGLE_OVERLAY)) {
|
||||
toggleOverlays();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -967,6 +967,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
|
||||
connect(this, &Application::applicationStateChanged, this, &Application::activeChanged);
|
||||
qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0);
|
||||
|
||||
_idleTimer = new QTimer(this);
|
||||
connect(_idleTimer, &QTimer::timeout, [=] {
|
||||
idle(usecTimestampNow());
|
||||
|
@ -975,7 +976,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 +1024,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
|
||||
|
@ -1198,7 +1199,6 @@ void Application::initializeUi() {
|
|||
// OffscreenUi is a subclass of OffscreenQmlSurface specifically designed to
|
||||
// support the window management and scripting proxies for VR use
|
||||
offscreenUi->createDesktop(QString("hifi/Desktop.qml"));
|
||||
connect(offscreenUi.data(), &OffscreenUi::showDesktop, this, &Application::showDesktop);
|
||||
|
||||
// FIXME either expose so that dialogs can set this themselves or
|
||||
// do better detection in the offscreen UI of what has focus
|
||||
|
@ -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());
|
||||
|
@ -1296,6 +1296,7 @@ void Application::initializeUi() {
|
|||
}
|
||||
|
||||
void Application::paintGL() {
|
||||
|
||||
// paintGL uses a queued connection, so we can get messages from the queue even after we've quit
|
||||
// and the plugins have shutdown
|
||||
if (_aboutToQuit) {
|
||||
|
@ -1321,10 +1322,6 @@ void Application::paintGL() {
|
|||
_lastFramesPerSecondUpdate = now;
|
||||
}
|
||||
|
||||
if (_isGLInitialized) {
|
||||
idle(now);
|
||||
}
|
||||
|
||||
PROFILE_RANGE(__FUNCTION__);
|
||||
PerformanceTimer perfTimer("paintGL");
|
||||
|
||||
|
@ -1619,7 +1616,6 @@ void Application::paintGL() {
|
|||
}
|
||||
|
||||
_lastInstantaneousFps = instantaneousFps;
|
||||
_pendingPaint = false;
|
||||
}
|
||||
|
||||
void Application::runTests() {
|
||||
|
@ -2030,9 +2026,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, !Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson));
|
||||
cameraMenuChanged();
|
||||
break;
|
||||
case Qt::Key_O:
|
||||
_overlayConductor.setEnabled(!_overlayConductor.getEnabled());
|
||||
break;
|
||||
|
||||
case Qt::Key_Slash:
|
||||
Menu::getInstance()->triggerOption(MenuOption::Stats);
|
||||
break;
|
||||
|
@ -2432,10 +2426,24 @@ bool Application::acceptSnapshot(const QString& urlString) {
|
|||
static uint32_t _renderedFrameIndex { INVALID_FRAME };
|
||||
|
||||
void Application::idle(uint64_t now) {
|
||||
|
||||
if (_aboutToQuit) {
|
||||
return; // bail early, nothing to do here.
|
||||
}
|
||||
|
||||
|
||||
Stats::getInstance()->updateStats();
|
||||
AvatarInputs::getInstance()->update();
|
||||
|
||||
// These tasks need to be done on our first idle, because we don't want the showing of
|
||||
// overlay subwindows to do a showDesktop() until after the first time through
|
||||
static bool firstIdle = true;
|
||||
if (firstIdle) {
|
||||
firstIdle = false;
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
connect(offscreenUi.data(), &OffscreenUi::showDesktop, this, &Application::showDesktop);
|
||||
_overlayConductor.setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Overlays));
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -2456,30 +2464,16 @@ void Application::idle(uint64_t now) {
|
|||
_renderedFrameIndex = INVALID_FRAME;
|
||||
}
|
||||
|
||||
// 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
|
||||
// (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
|
||||
_renderedFrameIndex = presentCount;
|
||||
// Don't allow paint requests to stack up in the event queue
|
||||
_pendingPaint = true;
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// Don't saturate the main thread with rendering and simulation,
|
||||
// unless display plugin has increased by at least one frame
|
||||
if (_renderedFrameIndex == INVALID_FRAME || presentCount > _renderedFrameIndex) {
|
||||
// Record what present frame we're on
|
||||
_renderedFrameIndex = presentCount;
|
||||
|
||||
// request a paint, get to it as soon as possible: high priority
|
||||
postEvent(this, new QEvent(static_cast<QEvent::Type>(Paint)), Qt::HighEventPriority);
|
||||
} else {
|
||||
// there's no use in simulating or rendering faster then the present rate.
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2993,6 +2987,16 @@ void Application::updateThreads(float deltaTime) {
|
|||
}
|
||||
}
|
||||
|
||||
void Application::toggleOverlays() {
|
||||
auto newOverlaysVisible = !_overlayConductor.getEnabled();
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, newOverlaysVisible);
|
||||
_overlayConductor.setEnabled(newOverlaysVisible);
|
||||
}
|
||||
|
||||
void Application::setOverlaysVisible(bool visible) {
|
||||
_overlayConductor.setEnabled(visible);
|
||||
}
|
||||
|
||||
void Application::cycleCamera() {
|
||||
auto menu = Menu::getInstance();
|
||||
if (menu->isOptionChecked(MenuOption::FullscreenMirror)) {
|
||||
|
@ -3387,7 +3391,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 +3427,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 +3493,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!";
|
||||
|
@ -3818,18 +3815,10 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
|
|||
});
|
||||
}
|
||||
|
||||
// Setup the current Zone Entity lighting and skybox
|
||||
// Setup the current Zone Entity lighting
|
||||
{
|
||||
// FIXME: Use a zone setting to determine the ambient light mode
|
||||
DependencyManager::get<DeferredLightingEffect>()->setAmbientLightMode(-1);
|
||||
auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage();
|
||||
DependencyManager::get<DeferredLightingEffect>()->setGlobalLight(skyStage->getSunLight()->getDirection(), skyStage->getSunLight()->getColor(), skyStage->getSunLight()->getIntensity(), skyStage->getSunLight()->getAmbientIntensity());
|
||||
|
||||
auto skybox = model::SkyboxPointer();
|
||||
if (skyStage->getBackgroundMode() == model::SunSkyStage::SKY_BOX) {
|
||||
skybox = skyStage->getSkybox();
|
||||
}
|
||||
DependencyManager::get<DeferredLightingEffect>()->setGlobalSkybox(skybox);
|
||||
auto stage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage();
|
||||
DependencyManager::get<DeferredLightingEffect>()->setGlobalLight(stage->getSunLight(), stage->getSkybox()->getCubemap());
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -4760,7 +4749,7 @@ static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool acti
|
|||
groupingMenu = "Developer";
|
||||
break;
|
||||
default:
|
||||
groupingMenu = "Standard";
|
||||
groupingMenu = "Standard";
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -149,6 +149,7 @@ public:
|
|||
const ApplicationOverlay& getApplicationOverlay() const { return _applicationOverlay; }
|
||||
ApplicationCompositor& getApplicationCompositor() { return _compositor; }
|
||||
const ApplicationCompositor& getApplicationCompositor() const { return _compositor; }
|
||||
|
||||
Overlays& getOverlays() { return _overlays; }
|
||||
|
||||
bool isForeground() const { return _isForeground; }
|
||||
|
@ -270,6 +271,8 @@ public slots:
|
|||
|
||||
void cycleCamera();
|
||||
void cameraMenuChanged();
|
||||
void toggleOverlays();
|
||||
void setOverlaysVisible(bool visible);
|
||||
|
||||
void reloadResourceCaches();
|
||||
|
||||
|
@ -509,7 +512,6 @@ private:
|
|||
int _avatarAttachmentRequest = 0;
|
||||
|
||||
bool _settingsLoaded { false };
|
||||
bool _pendingPaint { false };
|
||||
QTimer* _idleTimer { nullptr };
|
||||
|
||||
bool _fakedMouseEvent { false };
|
||||
|
|
|
@ -70,17 +70,9 @@ Menu::Menu() {
|
|||
dialogsManager.data(), &DialogsManager::toggleLoginDialog);
|
||||
}
|
||||
|
||||
// File > Update -- FIXME: needs implementation
|
||||
auto action = addActionToQMenuAndActionHash(fileMenu, "Update");
|
||||
action->setDisabled(true);
|
||||
|
||||
// File > Help
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::Help, 0, qApp, SLOT(showHelp()));
|
||||
|
||||
// File > Crash Reporter...-- FIXME: needs implementation
|
||||
auto crashReporterAction = addActionToQMenuAndActionHash(fileMenu, "Crash Reporter...");
|
||||
crashReporterAction->setDisabled(true);
|
||||
|
||||
// File > About
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::AboutApp, 0, qApp, SLOT(aboutApp()), QAction::AboutRole);
|
||||
|
||||
|
@ -167,7 +159,7 @@ Menu::Menu() {
|
|||
QObject* avatar = avatarManager->getMyAvatar();
|
||||
|
||||
// Avatar > Attachments...
|
||||
action = addActionToQMenuAndActionHash(avatarMenu, MenuOption::Attachments);
|
||||
auto action = addActionToQMenuAndActionHash(avatarMenu, MenuOption::Attachments);
|
||||
connect(action, &QAction::triggered, [] {
|
||||
DependencyManager::get<OffscreenUi>()->show(QString("hifi/dialogs/AttachmentsDialog.qml"), "AttachmentsDialog");
|
||||
});
|
||||
|
@ -255,20 +247,17 @@ Menu::Menu() {
|
|||
0, true, qApp, SLOT(rotationModeChanged()),
|
||||
UNSPECIFIED_POSITION, "Advanced");
|
||||
|
||||
// View > Overlays
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true,
|
||||
qApp, SLOT(setOverlaysVisible(bool)));
|
||||
|
||||
// Navigate menu ----------------------------------
|
||||
MenuWrapper* navigateMenu = addMenu("Navigate");
|
||||
|
||||
// Navigate > Home -- FIXME: needs implementation
|
||||
auto homeAction = addActionToQMenuAndActionHash(navigateMenu, "Home");
|
||||
homeAction->setDisabled(true);
|
||||
|
||||
// Navigate > Show Address Bar
|
||||
addActionToQMenuAndActionHash(navigateMenu, MenuOption::AddressBar, Qt::CTRL | Qt::Key_L,
|
||||
dialogsManager.data(), SLOT(toggleAddressBar()));
|
||||
|
||||
// Navigate > Directory -- FIXME: needs implementation
|
||||
addActionToQMenuAndActionHash(navigateMenu, "Directory");
|
||||
|
||||
// Navigate > Bookmark related menus -- Note: the Bookmark class adds its own submenus here.
|
||||
qApp->getBookmarks()->setupMenus(this, navigateMenu);
|
||||
|
||||
|
@ -299,20 +288,19 @@ Menu::Menu() {
|
|||
DependencyManager::get<OffscreenUi>()->toggle(QString("hifi/dialogs/GeneralPreferencesDialog.qml"), "GeneralPreferencesDialog");
|
||||
});
|
||||
|
||||
|
||||
// Settings > Avatar...-- FIXME: needs implementation
|
||||
// Settings > Avatar...
|
||||
action = addActionToQMenuAndActionHash(settingsMenu, "Avatar...");
|
||||
connect(action, &QAction::triggered, [] {
|
||||
DependencyManager::get<OffscreenUi>()->toggle(QString("hifi/dialogs/AvatarPreferencesDialog.qml"), "AvatarPreferencesDialog");
|
||||
});
|
||||
|
||||
// Settings > Audio...-- FIXME: needs implementation
|
||||
// Settings > Audio...
|
||||
action = addActionToQMenuAndActionHash(settingsMenu, "Audio...");
|
||||
connect(action, &QAction::triggered, [] {
|
||||
DependencyManager::get<OffscreenUi>()->toggle(QString("hifi/dialogs/AudioPreferencesDialog.qml"), "AudioPreferencesDialog");
|
||||
});
|
||||
|
||||
// Settings > LOD...-- FIXME: needs implementation
|
||||
// Settings > LOD...
|
||||
action = addActionToQMenuAndActionHash(settingsMenu, "LOD...");
|
||||
connect(action, &QAction::triggered, [] {
|
||||
DependencyManager::get<OffscreenUi>()->toggle(QString("hifi/dialogs/LodPreferencesDialog.qml"), "LodPreferencesDialog");
|
||||
|
|
|
@ -247,6 +247,7 @@ namespace MenuOption {
|
|||
const QString OnePointCalibration = "1 Point Calibration";
|
||||
const QString OnlyDisplayTopTen = "Only Display Top Ten";
|
||||
const QString OutputMenu = "Display";
|
||||
const QString Overlays = "Overlays";
|
||||
const QString PackageModel = "Package Model...";
|
||||
const QString Pair = "Pair";
|
||||
const QString PhysicsShowHulls = "Draw Collision Hulls";
|
||||
|
|
|
@ -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:
|
||||
|
@ -131,7 +131,7 @@ private:
|
|||
float _textureAspectRatio { 1.0f };
|
||||
int _hemiVerticesID { GeometryCache::UNKNOWN_ID };
|
||||
|
||||
float _alpha { 1.0f };
|
||||
float _alpha { 0.0f }; // hidden by default
|
||||
float _prevAlpha { 1.0f };
|
||||
float _fadeInAlpha { true };
|
||||
float _oculusUIRadius { 1.0f };
|
||||
|
|
|
@ -58,10 +58,6 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) {
|
|||
CHECK_GL_ERROR();
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "ApplicationOverlay::displayOverlay()");
|
||||
|
||||
// TODO move to Application::idle()?
|
||||
Stats::getInstance()->updateStats();
|
||||
AvatarInputs::getInstance()->update();
|
||||
|
||||
buildFramebufferObject();
|
||||
|
||||
if (!_overlayFramebuffer) {
|
||||
|
|
|
@ -110,19 +110,12 @@ void OverlayConductor::setEnabled(bool enabled) {
|
|||
return;
|
||||
}
|
||||
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, enabled);
|
||||
|
||||
_enabled = enabled; // set the new value
|
||||
|
||||
// if the new state is visible/enabled...
|
||||
if (_enabled) {
|
||||
// alpha fadeOut the overlay mesh.
|
||||
qApp->getApplicationCompositor().fadeOut();
|
||||
|
||||
// disable mouse clicks from script
|
||||
qApp->getOverlays().disable();
|
||||
|
||||
// disable QML events
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
offscreenUi->getRootItem()->setEnabled(false);
|
||||
|
||||
_enabled = false;
|
||||
} else {
|
||||
// alpha fadeIn the overlay mesh.
|
||||
qApp->getApplicationCompositor().fadeIn();
|
||||
|
||||
|
@ -142,8 +135,16 @@ void OverlayConductor::setEnabled(bool enabled) {
|
|||
t.setRotation(glm::quat_cast(camMat));
|
||||
qApp->getApplicationCompositor().setModelTransform(t);
|
||||
}
|
||||
} else { // other wise, if the new state is hidden/not enabled
|
||||
// alpha fadeOut the overlay mesh.
|
||||
qApp->getApplicationCompositor().fadeOut();
|
||||
|
||||
_enabled = true;
|
||||
// disable mouse clicks from script
|
||||
qApp->getOverlays().disable();
|
||||
|
||||
// disable QML events
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
offscreenUi->getRootItem()->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,8 +29,8 @@ private:
|
|||
STANDING
|
||||
};
|
||||
|
||||
Mode _mode = FLAT;
|
||||
bool _enabled = true;
|
||||
Mode _mode { FLAT };
|
||||
bool _enabled { false };
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -61,6 +61,7 @@ namespace controller {
|
|||
makeButtonPair(Action::CONTEXT_MENU, "ContextMenu"),
|
||||
makeButtonPair(Action::TOGGLE_MUTE, "ToggleMute"),
|
||||
makeButtonPair(Action::CYCLE_CAMERA, "CycleCamera"),
|
||||
makeButtonPair(Action::TOGGLE_OVERLAY, "ToggleOverlay"),
|
||||
|
||||
makeAxisPair(Action::RETICLE_CLICK, "ReticleClick"),
|
||||
makeAxisPair(Action::RETICLE_X, "ReticleX"),
|
||||
|
|
|
@ -52,6 +52,7 @@ enum class Action {
|
|||
CONTEXT_MENU,
|
||||
TOGGLE_MUTE,
|
||||
CYCLE_CAMERA,
|
||||
TOGGLE_OVERLAY,
|
||||
|
||||
SHIFT,
|
||||
|
||||
|
|
|
@ -134,13 +134,14 @@ void EntityTreeRenderer::update() {
|
|||
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
|
||||
tree->update();
|
||||
|
||||
// check to see if the avatar has moved and if we need to handle enter/leave entity logic
|
||||
checkEnterLeaveEntities();
|
||||
// Handle enter/leave entity logic
|
||||
bool updated = checkEnterLeaveEntities();
|
||||
|
||||
// even if we haven't changed positions, if we previously attempted to set the skybox, but
|
||||
// have a pending download of the skybox texture, then we should attempt to reapply to
|
||||
// get the correct texture.
|
||||
if (_pendingSkyboxTexture && _skyboxTexture && _skyboxTexture->isLoaded()) {
|
||||
// If we haven't already updated and previously attempted to load a texture,
|
||||
// check if the texture loaded and apply it
|
||||
if (!updated && (
|
||||
(_pendingSkyboxTexture && _skyboxTexture && _skyboxTexture->isLoaded()) ||
|
||||
(_pendingAmbientTexture && _ambientTexture && _ambientTexture->isLoaded()))) {
|
||||
applyZonePropertiesToScene(_bestZone);
|
||||
}
|
||||
|
||||
|
@ -156,7 +157,7 @@ void EntityTreeRenderer::update() {
|
|||
deleteReleasedModels();
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::checkEnterLeaveEntities() {
|
||||
bool EntityTreeRenderer::checkEnterLeaveEntities() {
|
||||
if (_tree && !_shuttingDown) {
|
||||
glm::vec3 avatarPosition = _viewState->getAvatarPosition();
|
||||
|
||||
|
@ -171,7 +172,7 @@ void EntityTreeRenderer::checkEnterLeaveEntities() {
|
|||
std::static_pointer_cast<EntityTree>(_tree)->findEntities(avatarPosition, radius, foundEntities);
|
||||
|
||||
// Whenever you're in an intersection between zones, we will always choose the smallest zone.
|
||||
_bestZone = NULL; // NOTE: Is this what we want?
|
||||
_bestZone = nullptr; // NOTE: Is this what we want?
|
||||
_bestZoneVolume = std::numeric_limits<float>::max();
|
||||
|
||||
// create a list of entities that actually contain the avatar's position
|
||||
|
@ -204,7 +205,6 @@ void EntityTreeRenderer::checkEnterLeaveEntities() {
|
|||
}
|
||||
|
||||
applyZonePropertiesToScene(_bestZone);
|
||||
|
||||
});
|
||||
|
||||
// Note: at this point we don't need to worry about the tree being locked, because we only deal with
|
||||
|
@ -228,8 +228,11 @@ void EntityTreeRenderer::checkEnterLeaveEntities() {
|
|||
}
|
||||
_currentEntitiesInside = entitiesContainingAvatar;
|
||||
_lastAvatarPosition = avatarPosition;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::leaveAllEntities() {
|
||||
|
@ -253,6 +256,7 @@ void EntityTreeRenderer::forceRecheckEntities() {
|
|||
|
||||
|
||||
void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptr<ZoneEntityItem> zone) {
|
||||
auto textureCache = DependencyManager::get<TextureCache>();
|
||||
auto scene = DependencyManager::get<SceneScriptingInterface>();
|
||||
auto sceneStage = scene->getStage();
|
||||
auto skyStage = scene->getSkyStage();
|
||||
|
@ -264,7 +268,11 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptr<ZoneEntityIt
|
|||
_pendingSkyboxTexture = false;
|
||||
_skyboxTexture.clear();
|
||||
|
||||
_pendingAmbientTexture = false;
|
||||
_ambientTexture.clear();
|
||||
|
||||
if (_hasPreviousZone) {
|
||||
sceneKeyLight->resetAmbientSphere();
|
||||
sceneKeyLight->setColor(_previousKeyLightColor);
|
||||
sceneKeyLight->setIntensity(_previousKeyLightIntensity);
|
||||
sceneKeyLight->setAmbientIntensity(_previousKeyLightAmbientIntensity);
|
||||
|
@ -274,6 +282,7 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptr<ZoneEntityIt
|
|||
_previousStageAltitude);
|
||||
sceneTime->setHour(_previousStageHour);
|
||||
sceneTime->setDay(_previousStageDay);
|
||||
|
||||
_hasPreviousZone = false;
|
||||
}
|
||||
|
||||
|
@ -306,6 +315,23 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptr<ZoneEntityIt
|
|||
sceneTime->setHour(zone->getStageProperties().calculateHour());
|
||||
sceneTime->setDay(zone->getStageProperties().calculateDay());
|
||||
|
||||
bool isAmbientTextureSet = false;
|
||||
if (zone->getKeyLightProperties().getAmbientURL().isEmpty()) {
|
||||
_pendingAmbientTexture = false;
|
||||
_ambientTexture.clear();
|
||||
} else {
|
||||
_ambientTexture = textureCache->getTexture(zone->getKeyLightProperties().getAmbientURL(), CUBE_TEXTURE);
|
||||
if (_ambientTexture->getGPUTexture()) {
|
||||
_pendingAmbientTexture = false;
|
||||
if (_ambientTexture->getGPUTexture()->getIrradiance()) {
|
||||
sceneKeyLight->setAmbientSphere(_ambientTexture->getGPUTexture()->getIrradiance());
|
||||
isAmbientTextureSet = true;
|
||||
}
|
||||
} else {
|
||||
_pendingAmbientTexture = true;
|
||||
}
|
||||
}
|
||||
|
||||
switch (zone->getBackgroundMode()) {
|
||||
case BACKGROUND_MODE_SKYBOX: {
|
||||
auto skybox = std::dynamic_pointer_cast<ProceduralSkybox>(skyStage->getSkybox());
|
||||
|
@ -326,12 +352,16 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptr<ZoneEntityIt
|
|||
_skyboxTexture.clear();
|
||||
} else {
|
||||
// Update the Texture of the Skybox with the one pointed by this zone
|
||||
auto textureCache = DependencyManager::get<TextureCache>();
|
||||
_skyboxTexture = textureCache->getTexture(zone->getSkyboxProperties().getURL(), CUBE_TEXTURE);
|
||||
|
||||
if (_skyboxTexture->getGPUTexture()) {
|
||||
skybox->setCubemap(_skyboxTexture->getGPUTexture());
|
||||
auto texture = _skyboxTexture->getGPUTexture();
|
||||
skybox->setCubemap(texture);
|
||||
_pendingSkyboxTexture = false;
|
||||
if (!isAmbientTextureSet && texture->getIrradiance()) {
|
||||
sceneKeyLight->setAmbientSphere(texture->getIrradiance());
|
||||
isAmbientTextureSet = true;
|
||||
}
|
||||
} else {
|
||||
_pendingSkyboxTexture = true;
|
||||
}
|
||||
|
@ -348,6 +378,10 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptr<ZoneEntityIt
|
|||
_skyboxTexture.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isAmbientTextureSet) {
|
||||
sceneKeyLight->resetAmbientSphere();
|
||||
}
|
||||
}
|
||||
|
||||
const FBXGeometry* EntityTreeRenderer::getGeometryForEntity(EntityItemPointer entityItem) {
|
||||
|
@ -819,3 +853,19 @@ void EntityTreeRenderer::updateEntityRenderStatus(bool shouldRenderEntities) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::updateZone(const EntityItemID& id) {
|
||||
if (!_bestZone) {
|
||||
// Get in the zone!
|
||||
auto zone = getTree()->findEntityByEntityItemID(id);
|
||||
if (zone && zone->contains(_lastAvatarPosition)) {
|
||||
_currentEntitiesInside << id;
|
||||
emit enterEntity(id);
|
||||
_entitiesScriptEngine->callEntityScriptMethod(id, "enterEntity");
|
||||
_bestZone = std::dynamic_pointer_cast<ZoneEntityItem>(zone);
|
||||
}
|
||||
}
|
||||
if (_bestZone && _bestZone->getID() == id) {
|
||||
applyZonePropertiesToScene(_bestZone);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,6 +109,7 @@ public slots:
|
|||
void entitySciptChanging(const EntityItemID& entityID, const bool reload);
|
||||
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
|
||||
void updateEntityRenderStatus(bool shouldRenderEntities);
|
||||
void updateZone(const EntityItemID& id);
|
||||
|
||||
// optional slots that can be wired to menu items
|
||||
void setDisplayModelBounds(bool value) { _displayModelBounds = value; }
|
||||
|
@ -136,16 +137,19 @@ private:
|
|||
EntityItemID _currentClickingOnEntityID;
|
||||
|
||||
QScriptValueList createEntityArgs(const EntityItemID& entityID);
|
||||
void checkEnterLeaveEntities();
|
||||
bool checkEnterLeaveEntities();
|
||||
void leaveAllEntities();
|
||||
void forceRecheckEntities();
|
||||
|
||||
glm::vec3 _lastAvatarPosition;
|
||||
glm::vec3 _lastAvatarPosition { 0.0f };
|
||||
QVector<EntityItemID> _currentEntitiesInside;
|
||||
|
||||
bool _pendingSkyboxTexture { false };
|
||||
NetworkTexturePointer _skyboxTexture;
|
||||
|
||||
bool _pendingAmbientTexture { false };
|
||||
NetworkTexturePointer _ambientTexture;
|
||||
|
||||
bool _wantScripts;
|
||||
ScriptEngine* _entitiesScriptEngine;
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ void RenderableBoxEntityItem::render(RenderArgs* args) {
|
|||
}
|
||||
|
||||
batch.setModelTransform(transToCenter); // we want to include the scale as well
|
||||
if (_procedural->ready()) {
|
||||
if (_procedural && _procedural->ready()) {
|
||||
_procedural->prepare(batch, getPosition(), getDimensions());
|
||||
auto color = _procedural->getColor(cubeColor);
|
||||
batch._glColor4f(color.r, color.g, color.b, color.a);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <GeometryCache.h>
|
||||
#include <PerfStat.h>
|
||||
|
||||
#include "EntityTreeRenderer.h"
|
||||
#include "RenderableEntityItem.h"
|
||||
|
||||
// Sphere entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1
|
||||
|
@ -62,6 +63,10 @@ bool RenderableZoneEntityItem::setProperties(const EntityItemProperties& propert
|
|||
return somethingChanged;
|
||||
}
|
||||
|
||||
void RenderableZoneEntityItem::somethingChangedNotification() {
|
||||
DependencyManager::get<EntityTreeRenderer>()->updateZone(_id);
|
||||
}
|
||||
|
||||
int RenderableZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args,
|
||||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
|
||||
|
|
|
@ -28,6 +28,8 @@ public:
|
|||
{ }
|
||||
|
||||
virtual bool setProperties(const EntityItemProperties& properties);
|
||||
virtual void somethingChangedNotification() override;
|
||||
|
||||
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args,
|
||||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
|
||||
|
|
|
@ -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() {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ void KeyLightPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desired
|
|||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_INTENSITY, KeyLight, keyLight, Intensity, intensity);
|
||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_AMBIENT_INTENSITY, KeyLight, keyLight, AmbientIntensity, ambientIntensity);
|
||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_DIRECTION, KeyLight, keyLight, Direction, direction);
|
||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_AMBIENT_URL, KeyLight, keyLight, AmbientURL, ambientUrl);
|
||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_AMBIENT_URL, KeyLight, keyLight, AmbientURL, ambientURL);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -142,6 +142,8 @@ SunSkyStage::SunSkyStage() :
|
|||
_skybox(std::make_shared<Skybox>())
|
||||
{
|
||||
_sunLight->setType(Light::SUN);
|
||||
// Default ambient sphere (for lack of skybox)
|
||||
_sunLight->setAmbientSpherePreset(gpu::SphericalHarmonics::Preset::OLD_TOWN_SQUARE);
|
||||
|
||||
setSunIntensity(1.0f);
|
||||
setSunAmbientIntensity(0.5f);
|
||||
|
@ -202,14 +204,13 @@ void SunSkyStage::setSunModelEnable(bool isEnabled) {
|
|||
invalidate();
|
||||
}
|
||||
|
||||
void SunSkyStage::setSunColor(const Vec3& color) {
|
||||
_sunLight->setColor(color);
|
||||
}
|
||||
void SunSkyStage::setSunIntensity(float intensity) {
|
||||
_sunLight->setIntensity(intensity);
|
||||
}
|
||||
void SunSkyStage::setSunAmbientIntensity(float intensity) {
|
||||
_sunLight->setAmbientIntensity(intensity);
|
||||
void SunSkyStage::setSunAmbientSphere(const gpu::SHPointer& sphere) {
|
||||
if (sphere) {
|
||||
_sunLight->setAmbientSphere(*sphere);
|
||||
} else {
|
||||
const gpu::SphericalHarmonics::Preset DEFAULT_AMBIENT_SPHERE = gpu::SphericalHarmonics::OLD_TOWN_SQUARE;
|
||||
_sunLight->setAmbientSpherePreset(DEFAULT_AMBIENT_SPHERE);
|
||||
}
|
||||
}
|
||||
|
||||
void SunSkyStage::setSunDirection(const Vec3& direction) {
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#ifndef hifi_model_Stage_h
|
||||
#define hifi_model_Stage_h
|
||||
|
||||
#include "gpu/Pipeline.h"
|
||||
#include <gpu/Pipeline.h>
|
||||
|
||||
#include "Light.h"
|
||||
#include "Skybox.h"
|
||||
|
@ -143,12 +143,13 @@ public:
|
|||
bool isSunModelEnabled() const { return _sunModelEnable; }
|
||||
|
||||
// Sun properties
|
||||
void setSunColor(const Vec3& color);
|
||||
void setSunColor(const Vec3& color) { _sunLight->setColor(color); }
|
||||
const Vec3& getSunColor() const { return getSunLight()->getColor(); }
|
||||
void setSunIntensity(float intensity);
|
||||
void setSunIntensity(float intensity) { _sunLight->setIntensity(intensity); }
|
||||
float getSunIntensity() const { return getSunLight()->getIntensity(); }
|
||||
void setSunAmbientIntensity(float intensity);
|
||||
void setSunAmbientIntensity(float intensity) { _sunLight->setAmbientIntensity(intensity); }
|
||||
float getSunAmbientIntensity() const { return getSunLight()->getAmbientIntensity(); }
|
||||
void setSunAmbientSphere(const gpu::SHPointer& sphere);
|
||||
|
||||
// The sun direction is expressed in the world space
|
||||
void setSunDirection(const Vec3& direction);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -87,23 +87,20 @@ void DeferredLightingEffect::init() {
|
|||
_allocatedLights.push_back(std::make_shared<model::Light>());
|
||||
|
||||
model::LightPointer lp = _allocatedLights[0];
|
||||
lp->setType(model::Light::SUN);
|
||||
|
||||
// Add the global light to the light stage (for later shadow rendering)
|
||||
_lightStage.addLight(lp);
|
||||
|
||||
lp->setDirection(-glm::vec3(1.0f, 1.0f, 1.0f));
|
||||
lp->setColor(glm::vec3(1.0f));
|
||||
lp->setIntensity(1.0f);
|
||||
lp->setType(model::Light::SUN);
|
||||
lp->setAmbientSpherePreset(gpu::SphericalHarmonics::Preset(_ambientLightMode % gpu::SphericalHarmonics::NUM_PRESET));
|
||||
}
|
||||
|
||||
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 +112,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);
|
||||
|
@ -312,15 +309,13 @@ void DeferredLightingEffect::render(const render::RenderContextPointer& renderCo
|
|||
|
||||
// First Global directional light and ambient pass
|
||||
{
|
||||
bool useSkyboxCubemap = (_skybox) && (_skybox->getCubemap());
|
||||
|
||||
auto& program = _shadowMapEnabled ? _directionalLightShadow : _directionalLight;
|
||||
LightLocationsPtr locations = _shadowMapEnabled ? _directionalLightShadowLocations : _directionalLightLocations;
|
||||
|
||||
// Setup the global directional pass pipeline
|
||||
{
|
||||
if (_shadowMapEnabled) {
|
||||
if (useSkyboxCubemap) {
|
||||
if (_skyboxTexture) {
|
||||
program = _directionalSkyboxLightShadow;
|
||||
locations = _directionalSkyboxLightShadowLocations;
|
||||
} else if (_ambientLightMode > -1) {
|
||||
|
@ -328,7 +323,7 @@ void DeferredLightingEffect::render(const render::RenderContextPointer& renderCo
|
|||
locations = _directionalAmbientSphereLightShadowLocations;
|
||||
}
|
||||
} else {
|
||||
if (useSkyboxCubemap) {
|
||||
if (_skyboxTexture) {
|
||||
program = _directionalSkyboxLight;
|
||||
locations = _directionalSkyboxLightLocations;
|
||||
} else if (_ambientLightMode > -1) {
|
||||
|
@ -356,7 +351,7 @@ void DeferredLightingEffect::render(const render::RenderContextPointer& renderCo
|
|||
geometryCache->renderQuad(batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, color);
|
||||
}
|
||||
|
||||
if (useSkyboxCubemap) {
|
||||
if (_skyboxTexture) {
|
||||
batch.setResourceTexture(SKYBOX_MAP_UNIT, nullptr);
|
||||
}
|
||||
}
|
||||
|
@ -501,9 +496,8 @@ void DeferredLightingEffect::setupKeyLightBatch(gpu::Batch& batch, int lightBuff
|
|||
batch.setUniformBuffer(lightBufferUnit, globalLight->getSchemaBuffer());
|
||||
}
|
||||
|
||||
bool useSkyboxCubemap = (_skybox) && (_skybox->getCubemap());
|
||||
if (useSkyboxCubemap && (skyboxCubemapUnit >= 0)) {
|
||||
batch.setResourceTexture(skyboxCubemapUnit, _skybox->getCubemap());
|
||||
if (_skyboxTexture && (skyboxCubemapUnit >= 0)) {
|
||||
batch.setResourceTexture(skyboxCubemapUnit, _skyboxTexture);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -562,32 +556,15 @@ static void loadLightProgram(const char* vertSource, const char* fragSource, boo
|
|||
|
||||
}
|
||||
|
||||
void DeferredLightingEffect::setAmbientLightMode(int preset) {
|
||||
if ((preset >= 0) && (preset < gpu::SphericalHarmonics::NUM_PRESET)) {
|
||||
_ambientLightMode = preset;
|
||||
auto light = _allocatedLights.front();
|
||||
light->setAmbientSpherePreset(gpu::SphericalHarmonics::Preset(preset % gpu::SphericalHarmonics::NUM_PRESET));
|
||||
} else {
|
||||
// force to preset 0
|
||||
setAmbientLightMode(0);
|
||||
}
|
||||
}
|
||||
void DeferredLightingEffect::setGlobalLight(const model::LightPointer& light, const gpu::TexturePointer& skyboxTexture) {
|
||||
auto globalLight = _allocatedLights.front();
|
||||
globalLight->setDirection(light->getDirection());
|
||||
globalLight->setColor(light->getColor());
|
||||
globalLight->setIntensity(light->getIntensity());
|
||||
globalLight->setAmbientIntensity(light->getAmbientIntensity());
|
||||
globalLight->setAmbientSphere(light->getAmbientSphere());
|
||||
|
||||
void DeferredLightingEffect::setGlobalLight(const glm::vec3& direction, const glm::vec3& diffuse, float intensity, float ambientIntensity) {
|
||||
auto light = _allocatedLights.front();
|
||||
light->setDirection(direction);
|
||||
light->setColor(diffuse);
|
||||
light->setIntensity(intensity);
|
||||
light->setAmbientIntensity(ambientIntensity);
|
||||
}
|
||||
|
||||
void DeferredLightingEffect::setGlobalSkybox(const model::SkyboxPointer& skybox) {
|
||||
_skybox = skybox;
|
||||
auto light = _allocatedLights.front();
|
||||
|
||||
if (_skybox && _skybox->getCubemap() && _skybox->getCubemap()->isDefined() && _skybox->getCubemap()->getIrradiance()) {
|
||||
light->setAmbientSphere( (*_skybox->getCubemap()->getIrradiance()) );
|
||||
}
|
||||
_skyboxTexture = skyboxTexture;
|
||||
}
|
||||
|
||||
model::MeshPointer DeferredLightingEffect::getSpotLightMesh() {
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
#include <NumericalConstants.h>
|
||||
|
||||
#include "model/Light.h"
|
||||
#include "model/Stage.h"
|
||||
#include "model/Geometry.h"
|
||||
|
||||
#include "render/Context.h"
|
||||
|
@ -37,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);
|
||||
|
@ -49,9 +49,7 @@ public:
|
|||
void setupKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int skyboxCubemapUnit);
|
||||
|
||||
// update global lighting
|
||||
void setAmbientLightMode(int preset);
|
||||
void setGlobalLight(const glm::vec3& direction, const glm::vec3& diffuse, float intensity, float ambientIntensity);
|
||||
void setGlobalSkybox(const model::SkyboxPointer& skybox);
|
||||
void setGlobalLight(const model::LightPointer& light, const gpu::TexturePointer& skyboxTexture);
|
||||
|
||||
const LightStage& getLightStage() { return _lightStage; }
|
||||
void setShadowMapEnabled(bool enable) { _shadowMapEnabled = enable; };
|
||||
|
@ -98,7 +96,7 @@ private:
|
|||
std::vector<int> _spotLights;
|
||||
|
||||
int _ambientLightMode = 0;
|
||||
model::SkyboxPointer _skybox;
|
||||
gpu::TexturePointer _skyboxTexture;
|
||||
|
||||
// Class describing the uniform buffer with all the parameters common to the deferred shaders
|
||||
class DeferredTransform {
|
||||
|
|
|
@ -413,9 +413,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);
|
||||
|
@ -426,7 +426,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);
|
||||
|
@ -474,17 +474,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());
|
||||
|
@ -492,7 +489,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);
|
||||
|
||||
|
@ -500,23 +497,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;
|
||||
}
|
||||
|
|
|
@ -77,6 +77,10 @@ void SceneScripting::KeyLight::setAmbientIntensity(float intensity) {
|
|||
_skyStage->setSunAmbientIntensity(intensity);
|
||||
}
|
||||
|
||||
void SceneScripting::KeyLight::setAmbientSphere(const gpu::SHPointer& sphere) {
|
||||
_skyStage->setSunAmbientSphere(sphere);
|
||||
}
|
||||
|
||||
glm::vec3 SceneScripting::KeyLight::getDirection() const {
|
||||
return _skyStage->getSunDirection();
|
||||
}
|
||||
|
|
|
@ -81,6 +81,10 @@ namespace SceneScripting {
|
|||
// setDirection is only effective if stage Sun model is disabled
|
||||
void setDirection(const glm::vec3& direction);
|
||||
|
||||
// AmbientTexture is unscriptable - it must be set through the zone entity
|
||||
void setAmbientSphere(const gpu::SHPointer& sphere);
|
||||
void resetAmbientSphere() { setAmbientSphere(nullptr); }
|
||||
|
||||
protected:
|
||||
model::SunSkyStagePointer _skyStage;
|
||||
};
|
||||
|
|
|
@ -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 << ")"
|
||||
|
|
|
@ -26,9 +26,7 @@ LogHandler& LogHandler::getInstance() {
|
|||
return staticInstance;
|
||||
}
|
||||
|
||||
LogHandler::LogHandler() :
|
||||
_shouldOutputProcessID(false),
|
||||
_shouldOutputThreadID(false)
|
||||
LogHandler::LogHandler()
|
||||
{
|
||||
// setup our timer to flush the verbose logs every 5 seconds
|
||||
QTimer* logFlushTimer = new QTimer(this);
|
||||
|
@ -62,6 +60,9 @@ const char* stringForLogType(LogMsgType msgType) {
|
|||
// the following will produce 11/18 13:55:36
|
||||
const QString DATE_STRING_FORMAT = "MM/dd hh:mm:ss";
|
||||
|
||||
// the following will produce 11/18 13:55:36.999
|
||||
const QString DATE_STRING_FORMAT_WITH_MILLISECONDS = "MM/dd hh:mm:ss.zzz";
|
||||
|
||||
void LogHandler::flushRepeatedMessages() {
|
||||
QMutexLocker locker(&_repeatedMessageLock);
|
||||
QHash<QString, int>::iterator message = _repeatMessageCountHash.begin();
|
||||
|
@ -132,7 +133,12 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont
|
|||
// log prefix is in the following format
|
||||
// [TIMESTAMP] [DEBUG] [PID] [TID] [TARGET] logged string
|
||||
|
||||
QString prefixString = QString("[%1]").arg(QDateTime::currentDateTime().toString(DATE_STRING_FORMAT));
|
||||
const QString* dateFormatPtr = &DATE_STRING_FORMAT;
|
||||
if (_shouldDisplayMilliseconds) {
|
||||
dateFormatPtr = &DATE_STRING_FORMAT_WITH_MILLISECONDS;
|
||||
}
|
||||
|
||||
QString prefixString = QString("[%1]").arg(QDateTime::currentDateTime().toString(*dateFormatPtr));
|
||||
|
||||
prefixString.append(QString(" [%1]").arg(stringForLogType(type)));
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ public:
|
|||
|
||||
void setShouldOutputProcessID(bool shouldOutputProcessID) { _shouldOutputProcessID = shouldOutputProcessID; }
|
||||
void setShouldOutputThreadID(bool shouldOutputThreadID) { _shouldOutputThreadID = shouldOutputThreadID; }
|
||||
void setShouldDisplayMilliseconds(bool shouldDisplayMilliseconds) { _shouldDisplayMilliseconds = shouldDisplayMilliseconds; }
|
||||
|
||||
QString printMessage(LogMsgType type, const QMessageLogContext& context, const QString &message);
|
||||
|
||||
|
@ -57,8 +58,9 @@ private:
|
|||
void flushRepeatedMessages();
|
||||
|
||||
QString _targetName;
|
||||
bool _shouldOutputProcessID;
|
||||
bool _shouldOutputThreadID;
|
||||
bool _shouldOutputProcessID { false };
|
||||
bool _shouldOutputThreadID { false };
|
||||
bool _shouldDisplayMilliseconds { false };
|
||||
QSet<QString> _repeatedMessageRegexes;
|
||||
QHash<QString, int> _repeatMessageCountHash;
|
||||
QHash<QString, QString> _lastRepeatedMessage;
|
||||
|
|
|
@ -460,6 +460,7 @@ void OffscreenUi::unfocusWindows() {
|
|||
}
|
||||
|
||||
void OffscreenUi::toggleMenu(const QPoint& screenPosition) {
|
||||
emit showDesktop(); // we really only want to do this if you're showing the menu, but for now this works
|
||||
auto virtualPos = mapToVirtualScreen(screenPosition, nullptr);
|
||||
QMetaObject::invokeMethod(_desktop, "toggleMenu", Q_ARG(QVariant, virtualPos));
|
||||
}
|
||||
|
|
|
@ -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