diff --git a/assignment-client/src/octree/OctreeQueryNode.cpp b/assignment-client/src/octree/OctreeQueryNode.cpp
index 78a049edd6..55eddf9e13 100644
--- a/assignment-client/src/octree/OctreeQueryNode.cpp
+++ b/assignment-client/src/octree/OctreeQueryNode.cpp
@@ -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();
diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in
index b5699cb3b3..ee59f4a3ac 100644
--- a/cmake/templates/NSIS.template.in
+++ b/cmake/templates/NSIS.template.in
@@ -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
diff --git a/examples/acScripts/entitySpawnerAC.js b/examples/acScripts/entitySpawnerAC.js
index 8fb2e259bb..2baf732258 100644
--- a/examples/acScripts/entitySpawnerAC.js
+++ b/examples/acScripts/entitySpawnerAC.js
@@ -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);
\ No newline at end of file
+Script.update.connect(update);
diff --git a/examples/audioExamples/acAudioSearching/ACAudioSearchAndInject.js b/examples/audioExamples/acAudioSearching/ACAudioSearchAndInject.js
index f7e983d683..de7baba267 100644
--- a/examples/audioExamples/acAudioSearching/ACAudioSearchAndInject.js
+++ b/examples/audioExamples/acAudioSearching/ACAudioSearchAndInject.js
@@ -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);
diff --git a/examples/audioExamples/acAudioSearching/acAudioSearchCompatibleEntitySpawner.js b/examples/audioExamples/acAudioSearching/acAudioSearchCompatibleEntitySpawner.js
index 126635ee7a..2a80a712b6 100644
--- a/examples/audioExamples/acAudioSearching/acAudioSearchCompatibleEntitySpawner.js
+++ b/examples/audioExamples/acAudioSearching/acAudioSearchCompatibleEntitySpawner.js
@@ -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);
\ No newline at end of file
+Script.scriptEnding.connect(cleanup);
diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js
index f2acfb9b47..04e93334cb 100644
--- a/examples/controllers/handControllerGrab.js
+++ b/examples/controllers/handControllerGrab.js
@@ -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) {
diff --git a/examples/controllers/leap/leapHands.js b/examples/controllers/leap/leapHands.js
index a77fa44e1c..1be0b1e5f6 100644
--- a/examples/controllers/leap/leapHands.js
+++ b/examples/controllers/leap/leapHands.js
@@ -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;
}
}
}
diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html
index e3ad77870d..ccbbc5557a 100644
--- a/examples/html/entityProperties.html
+++ b/examples/html/entityProperties.html
@@ -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 @@
Line Height
-
+
Text Color
-
R
-
G
-
B
+
R
+
G
+
B
Background Color
-
R
-
G
-
B
+
R
+
G
+
B
@@ -1085,32 +1091,32 @@
Light Color
-
R
-
G
-
B
+
R
+
G
+
B
Light Intensity
-
+
Light Direction
-
Pitch
-
Yaw
-
Roll
+
Pitch
+
Yaw
+
Roll
Ambient Intensity
-
+
@@ -1129,19 +1135,19 @@
Stage Latitude
-
+
Stage Longitude
-
+
Stage Altitude
-
+
@@ -1155,13 +1161,13 @@
Stage Day
-
+
Stage Hour
-
+
@@ -1186,9 +1192,9 @@
Skybox Color
-
R
-
G
-
B
+
R
+
G
+
B
@@ -1264,23 +1270,23 @@
Registration
-
X
-
Y
-
Z
+
X
+
Y
+
Z
Dimensions
-
X
-
Y
-
Z
+
X
+
Y
+
Z
- %
+ %
@@ -1291,9 +1297,9 @@
Voxel Volume Size
-
X
-
Y
-
Z
+
X
+
Y
+
Z
Surface Extractor
@@ -1325,9 +1331,9 @@
Rotation
-
Pitch
-
Yaw
-
Roll
+
Pitch
+
Yaw
+
Roll
@@ -1339,66 +1345,66 @@
Linear Velocity
-
X
-
Y
-
Z
+
X
+
Y
+
Z
Linear Damping
-
+
Angular Velocity
-
Pitch
-
Yaw
-
Roll
+
Pitch
+
Yaw
+
Roll
Angular Damping
-
+
Restitution
-
+
Friction
-
+
Gravity
-
X
-
Y
-
Z
+
X
+
Y
+
Z
Acceleration
-
X
-
Y
-
Z
+
X
+
Y
+
Z
Density
-
+
@@ -1406,9 +1412,9 @@
Color
-
R
-
G
-
B
+
R
+
G
+
B
@@ -1483,7 +1489,7 @@
Lifetime
-
+
@@ -1541,25 +1547,25 @@
Animation FPS
-
+
Animation Frame
-
+
Animation First Frame
-
+
Animation Last Frame
-
+
@@ -1591,37 +1597,43 @@
+
+
Color
+
+
+
R
+
G
+
B
+
+
+
+
Intensity
+
+
+
+
+
+
Falloff Radius
+
+
+
+
Spot Light
-
-
Color
-
-
-
R
-
G
-
B
-
-
-
-
Intensity
-
-
-
-
Spot Light Exponent
-
+
Spot Light Cutoff (degrees)
-
+
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 29c8e136af..f286085092 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -425,11 +425,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
_maxOctreePPS(maxOctreePacketsPerSecond.get()),
_lastFaceTrackerUpdate(0)
{
- // FIXME this may be excessivly conservative. On the other hand
+ // FIXME this may be excessivly conservative. On the other hand
// maybe I'm used to having an 8-core machine
- // Perhaps find the ideal thread count and subtract 2 or 3
+ // Perhaps find the ideal thread count and subtract 2 or 3
// (main thread, present thread, random OS load)
- // More threads == faster concurrent loads, but also more concurrent
+ // More threads == faster concurrent loads, but also more concurrent
// load on the GPU until we can serialize GPU transfers (off the main thread)
QThreadPool::globalInstance()->setMaxThreadCount(2);
thread()->setPriority(QThread::HighPriority);
@@ -560,7 +560,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
auto discoverabilityManager = DependencyManager::get();
connect(&locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation);
- connect(&locationUpdateTimer, &QTimer::timeout,
+ connect(&locationUpdateTimer, &QTimer::timeout,
DependencyManager::get().data(), &AddressManager::storeCurrentAddress);
locationUpdateTimer.start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS);
@@ -604,7 +604,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
connect(addressManager.data(), &AddressManager::hostChanged, this, &Application::updateWindowTitle);
connect(this, &QCoreApplication::aboutToQuit, addressManager.data(), &AddressManager::storeCurrentAddress);
-
+
// Save avatar location immediately after a teleport.
connect(getMyAvatar(), &MyAvatar::positionGoneTo,
DependencyManager::get().data(), &AddressManager::storeCurrentAddress);
@@ -625,7 +625,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
getEntities()->reloadEntityScripts();
}, Qt::QueuedConnection);
- connect(scriptEngines, &ScriptEngines::scriptLoadError,
+ connect(scriptEngines, &ScriptEngines::scriptLoadError,
scriptEngines, [](const QString& filename, const QString& error){
OffscreenUi::warning(nullptr, "Error Loading Script", filename + " failed to load.");
}, Qt::QueuedConnection);
@@ -975,7 +975,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
disconnect(_idleTimer);
});
// Setting the interval to zero forces this to get called whenever there are no messages
- // in the queue, which can be pretty damn frequent. Hence the idle function has a bunch
+ // in the queue, which can be pretty damn frequent. Hence the idle function has a bunch
// of logic to abort early if it's being called too often.
_idleTimer->start(0);
}
@@ -1023,7 +1023,7 @@ void Application::cleanupBeforeQuit() {
getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts
DependencyManager::get()->saveScripts();
DependencyManager::get()->shutdownScripting(); // stop all currently running global scripts
- DependencyManager::destroy();
+ DependencyManager::destroy();
// first stop all timers directly or by invokeMethod
// depending on what thread they run in
@@ -1212,10 +1212,10 @@ void Application::initializeUi() {
setupPreferences();
- // For some reason there is already an "Application" object in the QML context,
+ // For some reason there is already an "Application" object in the QML context,
// though I can't find it. Hence, "ApplicationInterface"
rootContext->setContextProperty("SnapshotUploader", new SnapshotUploader());
- rootContext->setContextProperty("ApplicationInterface", this);
+ rootContext->setContextProperty("ApplicationInterface", this);
rootContext->setContextProperty("AnimationCache", DependencyManager::get().data());
rootContext->setContextProperty("Audio", &AudioScriptingInterface::getInstance());
rootContext->setContextProperty("Controller", DependencyManager::get().data());
@@ -2435,7 +2435,7 @@ void Application::idle(uint64_t now) {
if (_aboutToQuit) {
return; // bail early, nothing to do here.
}
-
+
auto displayPlugin = getActiveDisplayPlugin();
// depending on whether we're throttling or not.
// Once rendering is off on another thread we should be able to have Application::idle run at start(0) in
@@ -2459,7 +2459,7 @@ void Application::idle(uint64_t now) {
// Nested ifs are for clarity in the logic. Don't collapse them into a giant single if.
// Don't saturate the main thread with rendering, no paint calls until the last one is complete
if (!_pendingPaint) {
- // Also no paint calls until the display plugin has increased by at least one frame
+ // Also no paint calls until the display plugin has increased by at least one frame
// (don't render at 90fps if the display plugin only goes at 60)
if (_renderedFrameIndex == INVALID_FRAME || presentCount > _renderedFrameIndex) {
// Record what present frame we're on
@@ -2469,14 +2469,14 @@ void Application::idle(uint64_t now) {
// But when we DO request a paint, get to it as soon as possible: high priority
postEvent(this, new QEvent(static_cast(Paint)), Qt::HighEventPriority);
}
- }
-
- // For the rest of idle, we want to cap at the max sim rate, so we might not call
+ }
+
+ // For the rest of idle, we want to cap at the max sim rate, so we might not call
// the remaining idle work every paint frame, or vice versa
// In theory this means we could call idle processing more often than painting,
// but in practice, when the paintGL calls aren't keeping up, there's no room left
// in the main thread to call idle more often than paint.
- // This check is mostly to keep idle from burning up CPU cycles by running at
+ // This check is mostly to keep idle from burning up CPU cycles by running at
// hundreds of idles per second when the rendering is that fast
if ((timeSinceLastUpdateUs / USECS_PER_MSEC) < CAPPED_SIM_FRAME_PERIOD_MS) {
// No paint this round, but might be time for a new idle, otherwise return
@@ -3387,7 +3387,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
_octreeQuery.setCameraNearClip(_viewFrustum.getNearClip());
_octreeQuery.setCameraFarClip(_viewFrustum.getFarClip());
_octreeQuery.setCameraEyeOffsetPosition(glm::vec3());
- _octreeQuery.setKeyholeRadius(_viewFrustum.getKeyholeRadius());
+ _octreeQuery.setCameraCenterRadius(_viewFrustum.getCenterRadius());
auto lodManager = DependencyManager::get();
_octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale());
_octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust());
@@ -3423,9 +3423,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
rootDetails.y * TREE_SCALE,
rootDetails.z * TREE_SCALE) - glm::vec3(HALF_TREE_SCALE),
rootDetails.s * TREE_SCALE);
- ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
-
- if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
+ if (_viewFrustum.cubeIntersectsKeyhole(serverBounds)) {
inViewServers++;
}
}
@@ -3491,12 +3489,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
rootDetails.s * TREE_SCALE);
- ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
- if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
- inView = true;
- } else {
- inView = false;
- }
+ inView = _viewFrustum.cubeIntersectsKeyhole(serverBounds);
} else {
if (wantExtraDebugging) {
qCDebug(interfaceapp) << "Jurisdiction without RootCode for node " << *node << ". That's unusual!";
@@ -4752,7 +4745,7 @@ static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool acti
groupingMenu = "Developer";
break;
default:
- groupingMenu = "Standard";
+ groupingMenu = "Standard";
break;
}
diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp
index ee0ef21ae0..83351d5188 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/interface/src/avatar/Avatar.cpp
@@ -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()->getLocalLights()) {
glm::vec3 direction = orientation * light.direction;
DependencyManager::get()->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()->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);
diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h
index 766a4734eb..cd9fc86e1e 100644
--- a/interface/src/avatar/Avatar.h
+++ b/interface/src/avatar/Avatar.h
@@ -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();
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index 6318a9bb1e..fc715eebe9 100644
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -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();
diff --git a/interface/src/ui/ApplicationCompositor.cpp b/interface/src/ui/ApplicationCompositor.cpp
index 5c6e4c9819..99f0b4fdc4 100644
--- a/interface/src/ui/ApplicationCompositor.cpp
+++ b/interface/src/ui/ApplicationCompositor.cpp
@@ -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);
diff --git a/interface/src/ui/ApplicationCompositor.h b/interface/src/ui/ApplicationCompositor.h
index 7bdb97727f..a3735a9766 100644
--- a/interface/src/ui/ApplicationCompositor.h
+++ b/interface/src/ui/ApplicationCompositor.h
@@ -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:
diff --git a/libraries/entities-renderer/src/RenderableLightEntityItem.cpp b/libraries/entities-renderer/src/RenderableLightEntityItem.cpp
index 39182f322c..fb6061e94f 100644
--- a/libraries/entities-renderer/src/RenderableLightEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableLightEntityItem.cpp
@@ -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()->addSpotLight(position, largestDiameter / 2.0f,
- color, intensity, rotation, exponent, cutoff);
+ color, intensity, falloffRadius, rotation, exponent, cutoff);
} else {
DependencyManager::get()->addPointLight(position, largestDiameter / 2.0f,
- color, intensity);
+ color, intensity, falloffRadius);
}
#ifdef WANT_DEBUG
diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp
index 8e0983f62a..550ec205c0 100644
--- a/libraries/entities/src/EntityItemProperties.cpp
+++ b/libraries/entities/src/EntityItemProperties.cpp
@@ -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 EntityItemProperties::listChangedProperties() {
if (intensityChanged()) {
out += "intensity";
}
+ if (falloffRadiusChanged()) {
+ out += "falloffRadius";
+ }
if (exponentChanged()) {
out += "exponent";
}
diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h
index 2cfef17c1b..c732d01fa5 100644
--- a/libraries/entities/src/EntityItemProperties.h
+++ b/libraries/entities/src/EntityItemProperties.h
@@ -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, "");
diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h
index 5375b1bc3a..aa4fb5c619 100644
--- a/libraries/entities/src/EntityItemPropertiesDefaults.h
+++ b/libraries/entities/src/EntityItemPropertiesDefaults.h
@@ -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
diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h
index b60fc6174c..90a7c1e2f7 100644
--- a/libraries/entities/src/EntityPropertyFlags.h
+++ b/libraries/entities/src/EntityPropertyFlags.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,
diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp
index 4abbe3c3fa..15ff531b06 100644
--- a/libraries/entities/src/EntityTreeElement.cpp
+++ b/libraries/entities/src/EntityTreeElement.cpp
@@ -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(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(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(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(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 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& entityIdsToInclude,
+ BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude,
const QVector& 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() {
}
});
}
-
+
diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp
index ac56fc9c1f..852b37a751 100644
--- a/libraries/entities/src/LightEntityItem.cpp
+++ b/libraries/entities/src/LightEntityItem.cpp
@@ -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());
}
diff --git a/libraries/entities/src/LightEntityItem.h b/libraries/entities/src/LightEntityItem.h
index 103c462809..4c84d3204c 100644
--- a/libraries/entities/src/LightEntityItem.h
+++ b/libraries/entities/src/LightEntityItem.h
@@ -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;
};
diff --git a/libraries/gpu/src/gpu/Framebuffer.cpp b/libraries/gpu/src/gpu/Framebuffer.cpp
index 779d70cc88..abaa0f9ef1 100755
--- a/libraries/gpu/src/gpu/Framebuffer.cpp
+++ b/libraries/gpu/src/gpu/Framebuffer.cpp
@@ -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;
}
diff --git a/libraries/gpu/src/gpu/Framebuffer.h b/libraries/gpu/src/gpu/Framebuffer.h
index e986e4a481..5d016645fe 100755
--- a/libraries/gpu/src/gpu/Framebuffer.h
+++ b/libraries/gpu/src/gpu/Framebuffer.h
@@ -135,10 +135,15 @@ public:
static uint32 getMaxNumRenderBuffers() { return MAX_NUM_RENDER_BUFFERS; }
const GPUObjectPointer gpuObject {};
-
+
+ Stamp getDepthStamp() const { return _depthStamp; }
+ const std::vector& getColorStamps() const { return _colorStamps; }
+
protected:
SwapchainPointer _swapchain;
+ Stamp _depthStamp { 0 };
+ std::vector _colorStamps;
TextureViews _renderBuffers;
TextureView _depthStencilBuffer;
diff --git a/libraries/gpu/src/gpu/GLBackend.h b/libraries/gpu/src/gpu/GLBackend.h
index 7f37a73c57..15338d7587 100644
--- a/libraries/gpu/src/gpu/GLBackend.h
+++ b/libraries/gpu/src/gpu/GLBackend.h
@@ -173,6 +173,9 @@ public:
public:
GLuint _fbo = 0;
std::vector _colorBuffers;
+ Stamp _depthStamp { 0 };
+ std::vector _colorStamps;
+
GLFramebuffer();
~GLFramebuffer();
diff --git a/libraries/gpu/src/gpu/GLBackendOutput.cpp b/libraries/gpu/src/gpu/GLBackendOutput.cpp
index 5f226141a8..37a10e670b 100755
--- a/libraries/gpu/src/gpu/GLBackendOutput.cpp
+++ b/libraries/gpu/src/gpu/GLBackendOutput.cpp
@@ -27,8 +27,15 @@ GLBackend::GLFramebuffer::~GLFramebuffer() {
GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffer) {
GLFramebuffer* object = Backend::getGPUObject(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 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;
diff --git a/libraries/model/src/model/Light.cpp b/libraries/model/src/model/Light.cpp
index 7ab8b77f5e..8a016c4d77 100755
--- a/libraries/model/src/model/Light.cpp
+++ b/libraries/model/src/model/Light.cpp
@@ -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(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
diff --git a/libraries/model/src/model/Light.h b/libraries/model/src/model/Light.h
index 96bf7fc427..dafbf90397 100755
--- a/libraries/model/src/model/Light.h
+++ b/libraries/model/src/model/Light.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;
diff --git a/libraries/model/src/model/Light.slh b/libraries/model/src/model/Light.slh
index 6529da1b61..af1c251ccb 100644
--- a/libraries/model/src/model/Light.slh
+++ b/libraries/model/src/model/Light.slh
@@ -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;
}
diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp
index 4e6418279d..88fabf1a5a 100644
--- a/libraries/networking/src/udt/PacketHeaders.cpp
+++ b/libraries/networking/src/udt/PacketHeaders.cpp
@@ -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(AvatarMixerPacketVersion::SoftAttachmentSupport);
diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h
index af3fd49710..0f586018db 100644
--- a/libraries/networking/src/udt/PacketHeaders.h
+++ b/libraries/networking/src/udt/PacketHeaders.h
@@ -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,
diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp
index a685c2580c..8959bd4aa7 100644
--- a/libraries/octree/src/Octree.cpp
+++ b/libraries/octree/src/Octree.cpp
@@ -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()) {
diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h
index 0939ae37f6..81a721c54e 100644
--- a/libraries/octree/src/Octree.h
+++ b/libraries/octree/src/Octree.h
@@ -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);
diff --git a/libraries/octree/src/OctreeElement.cpp b/libraries/octree/src/OctreeElement.cpp
index f16e1dc88d..db0948b8b6 100644
--- a/libraries/octree/src/OctreeElement.cpp
+++ b/libraries/octree/src/OctreeElement.cpp
@@ -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"
diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h
index 3c25ec0850..398f1827b7 100644
--- a/libraries/octree/src/OctreeElement.h
+++ b/libraries/octree/src/OctreeElement.h
@@ -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 _mapSourceUUIDsToKeys;
static std::map _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
diff --git a/libraries/octree/src/OctreeHeadlessViewer.cpp b/libraries/octree/src/OctreeHeadlessViewer.cpp
index 547b3ac32b..33c12b1fe5 100644
--- a/libraries/octree/src/OctreeHeadlessViewer.cpp
+++ b/libraries/octree/src/OctreeHeadlessViewer.cpp
@@ -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(queryPacket->getPayload()));
queryPacket->setPayloadSize(querySize);
diff --git a/libraries/octree/src/OctreeHeadlessViewer.h b/libraries/octree/src/OctreeHeadlessViewer.h
index bdfebd82f6..780b68b848 100644
--- a/libraries/octree/src/OctreeHeadlessViewer.h
+++ b/libraries/octree/src/OctreeHeadlessViewer.h
@@ -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; }
diff --git a/libraries/octree/src/OctreeQuery.cpp b/libraries/octree/src/OctreeQuery.cpp
index 0e7f565c00..a69d88c693 100644
--- a/libraries/octree/src/OctreeQuery.cpp
+++ b/libraries/octree/src/OctreeQuery.cpp
@@ -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;
}
diff --git a/libraries/octree/src/OctreeQuery.h b/libraries/octree/src/OctreeQuery.h
index 7e157c4252..e446e1abc7 100644
--- a/libraries/octree/src/OctreeQuery.h
+++ b/libraries/octree/src/OctreeQuery.h
@@ -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
diff --git a/libraries/octree/src/ViewFrustum.cpp b/libraries/octree/src/ViewFrustum.cpp
index 4c07a0c784..228e4c7d35 100644
--- a/libraries/octree/src/ViewFrustum.cpp
+++ b/libraries/octree/src/ViewFrustum.cpp
@@ -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) {
diff --git a/libraries/octree/src/ViewFrustum.h b/libraries/octree/src/ViewFrustum.h
index 89c632df8c..412d0f82f2 100644
--- a/libraries/octree/src/ViewFrustum.h
+++ b/libraries/octree/src/ViewFrustum.h
@@ -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;
diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp
index 36b065c2b8..e2b5721bd9 100644
--- a/libraries/render-utils/src/DeferredLightingEffect.cpp
+++ b/libraries/render-utils/src/DeferredLightingEffect.cpp
@@ -98,12 +98,12 @@ void DeferredLightingEffect::init() {
}
void DeferredLightingEffect::addPointLight(const glm::vec3& position, float radius, const glm::vec3& color,
- float intensity) {
- addSpotLight(position, radius, color, intensity);
+ float intensity, float falloffRadius) {
+ addSpotLight(position, radius, color, intensity, falloffRadius);
}
void DeferredLightingEffect::addSpotLight(const glm::vec3& position, float radius, const glm::vec3& color,
- float intensity, const glm::quat& orientation, float exponent, float cutoff) {
+ float intensity, float falloffRadius, const glm::quat& orientation, float exponent, float cutoff) {
unsigned int lightID = (unsigned int)(_pointLights.size() + _spotLights.size() + _globalLights.size());
if (lightID >= _allocatedLights.size()) {
@@ -115,7 +115,7 @@ void DeferredLightingEffect::addSpotLight(const glm::vec3& position, float radiu
lp->setMaximumRadius(radius);
lp->setColor(color);
lp->setIntensity(intensity);
- //lp->setShowContour(quadraticAttenuation);
+ lp->setFalloffRadius(falloffRadius);
if (exponent == 0.0f && cutoff == PI) {
lp->setType(model::Light::POINT);
diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h
index d19ce9929c..7fc6d99a95 100644
--- a/libraries/render-utils/src/DeferredLightingEffect.h
+++ b/libraries/render-utils/src/DeferredLightingEffect.h
@@ -36,11 +36,12 @@ public:
/// Adds a point light to render for the current frame.
void addPointLight(const glm::vec3& position, float radius, const glm::vec3& color = glm::vec3(0.0f, 0.0f, 0.0f),
- float intensity = 0.5f);
+ float intensity = 0.5f, float falloffRadius = 0.01f);
/// Adds a spot light to render for the current frame.
void addSpotLight(const glm::vec3& position, float radius, const glm::vec3& color = glm::vec3(1.0f, 1.0f, 1.0f),
- float intensity = 0.5f, const glm::quat& orientation = glm::quat(), float exponent = 0.0f, float cutoff = PI);
+ float intensity = 0.5f, float falloffRadius = 0.01f,
+ const glm::quat& orientation = glm::quat(), float exponent = 0.0f, float cutoff = PI);
void prepare(RenderArgs* args);
void render(const render::RenderContextPointer& renderContext);
diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp
index 8bba58eae2..bcddc72fa4 100644
--- a/libraries/render-utils/src/MeshPartPayload.cpp
+++ b/libraries/render-utils/src/MeshPartPayload.cpp
@@ -450,9 +450,9 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const {
void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) const {
if (!_isBlendShaped) {
batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
-
+
batch.setInputFormat((_drawMesh->getVertexFormat()));
-
+
batch.setInputStream(0, _drawMesh->getVertexStream());
} else {
batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
@@ -463,7 +463,7 @@ void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) const {
batch.setInputBuffer(1, _model->_blendedVertexBuffers[_meshIndex], _drawMesh->getNumVertices() * sizeof(glm::vec3), sizeof(glm::vec3));
batch.setInputStream(2, _drawMesh->getVertexStream().makeRangedStream(2));
}
-
+
// TODO: Get rid of that extra call
if (!_hasColorAttrib) {
batch._glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
@@ -511,17 +511,14 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
#ifdef DEBUG_BOUNDING_PARTS
{
AABox partBounds = getPartBounds(_meshIndex, partIndex);
- bool inView = args->_viewFrustum->boxInFrustum(partBounds) != ViewFrustum::OUTSIDE;
-
- glm::vec4 cubeColor;
+
+ glm::vec4 cubeColor(1.0f, 1.0f, 0.0f, 1.0f);
if (isSkinned) {
cubeColor = glm::vec4(0.0f, 1.0f, 1.0f, 1.0f);
- } else if (inView) {
+ } else if (args->_viewFrustum->boxIntersectsFrustum(partBounds)) {
cubeColor = glm::vec4(1.0f, 0.0f, 1.0f, 1.0f);
- } else {
- cubeColor = glm::vec4(1.0f, 1.0f, 0.0f, 1.0f);
}
-
+
Transform transform;
transform.setTranslation(partBounds.calcCenter());
transform.setScale(partBounds.getDimensions());
@@ -529,7 +526,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
DependencyManager::get()->renderWireCube(batch, 1.0f, cubeColor);
}
#endif //def DEBUG_BOUNDING_PARTS
-
+
auto locations = args->_pipeline->locations;
assert(locations);
@@ -537,23 +534,23 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
bool canCauterize = args->_renderMode != RenderArgs::SHADOW_RENDER_MODE;
_model->updateClusterMatrices(_transform.getTranslation(), _transform.getRotation());
bindTransform(batch, locations, canCauterize);
-
+
//Bind the index buffer and vertex buffer and Blend shapes if needed
bindMesh(batch);
-
+
// apply material properties
bindMaterial(batch, locations);
-
+
if (args) {
args->_details._materialSwitches++;
}
-
+
// Draw!
{
PerformanceTimer perfTimer("batch.drawIndexed()");
drawCall(batch);
}
-
+
if (args) {
const int INDICES_PER_TRIANGLE = 3;
args->_details._trianglesRendered += _drawPart._numIndices / INDICES_PER_TRIANGLE;
diff --git a/libraries/render-utils/src/point_light.slf b/libraries/render-utils/src/point_light.slf
index 298b3751b7..0cadf3a760 100644
--- a/libraries/render-utils/src/point_light.slf
+++ b/libraries/render-utils/src/point_light.slf
@@ -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;
}
diff --git a/libraries/render-utils/src/spot_light.slf b/libraries/render-utils/src/spot_light.slf
index a4077c0edb..69f8e836aa 100644
--- a/libraries/render-utils/src/spot_light.slf
+++ b/libraries/render-utils/src/spot_light.slf
@@ -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;
}
diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp
index 3fc6bffbd3..7824514649 100644
--- a/libraries/render/src/render/CullTask.cpp
+++ b/libraries/render/src/render/CullTask.cpp
@@ -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;
}
diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp
index d16bdafcec..0b453e6f36 100644
--- a/libraries/shared/src/AABox.cpp
+++ b/libraries/shared/src/AABox.cpp
@@ -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());
-}
\ No newline at end of file
+}
diff --git a/libraries/shared/src/AABox.h b/libraries/shared/src/AABox.h
index 24791445b3..ec06c60121 100644
--- a/libraries/shared/src/AABox.h
+++ b/libraries/shared/src/AABox.h
@@ -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 << ")"
diff --git a/libraries/shared/src/AACube.cpp b/libraries/shared/src/AACube.cpp
index bc97a6ff69..f8122ea4dc 100644
--- a/libraries/shared/src/AACube.cpp
+++ b/libraries/shared/src/AACube.cpp
@@ -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);
diff --git a/libraries/shared/src/AACube.h b/libraries/shared/src/AACube.h
index dab5dafdaa..87a38cb304 100644
--- a/libraries/shared/src/AACube.h
+++ b/libraries/shared/src/AACube.h
@@ -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 << ")"
diff --git a/tests/octree/src/OctreeTests.cpp b/tests/octree/src/OctreeTests.cpp
index d1f6fba891..ef08855d06 100644
--- a/tests/octree/src/OctreeTests.cpp
+++ b/tests/octree/src/OctreeTests.cpp
@@ -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;
diff --git a/tests/octree/src/ViewFrustumTests.cpp b/tests/octree/src/ViewFrustumTests.cpp
new file mode 100644
index 0000000000..2e9df54d83
--- /dev/null
+++ b/tests/octree/src/ViewFrustumTests.cpp
@@ -0,0 +1,1347 @@
+//
+// ViewFrustumTests.cpp
+// 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
+//
+
+#include "ViewFrustumTests.h"
+
+#include
+
+#include
+#include
+#include
+
+//#include
+#include <../GLMTestUtils.h>
+#include <../QTestExtensions.h>
+
+
+const float ACCEPTABLE_FLOAT_ERROR = 1.0e-6f;
+const float ACCEPTABLE_DOT_ERROR = 1.0e-5f;
+const float ACCEPTABLE_CLIP_ERROR = 3e-4f;
+
+const glm::vec3 localRight(1.0f, 0.0f, 0.0f);
+const glm::vec3 localUp(0.0f, 1.0f, 0.0f);
+const glm::vec3 localForward(0.0f, 0.0f, -1.0f);
+
+
+QTEST_MAIN(ViewFrustumTests)
+
+void ViewFrustumTests::testInit() {
+ float aspect = 1.0f;
+ float fovX = PI / 2.0f;
+ float nearClip = 1.0f;
+ float farClip = 100.0f;
+ float holeRadius = 10.0f;
+
+ glm::vec3 center = glm::vec3(12.3f, 4.56f, 89.7f);
+
+ float angle = PI / 7.0f;
+ glm::vec3 axis = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f));
+ glm::quat rotation = glm::angleAxis(angle, axis);
+
+ ViewFrustum view;
+ view.setProjection(glm::perspective(fovX, aspect, nearClip, farClip));
+ view.setPosition(center);
+ view.setOrientation(rotation);
+ view.setCenterRadius(holeRadius);
+ view.calculate();
+
+ // check frustum dimensions
+ QCOMPARE_WITH_ABS_ERROR(fovX, glm::radians(view.getFieldOfView()), ACCEPTABLE_FLOAT_ERROR);
+ QCOMPARE_WITH_ABS_ERROR(aspect, view.getAspectRatio(), ACCEPTABLE_FLOAT_ERROR);
+ QCOMPARE_WITH_ABS_ERROR(farClip, view.getFarClip(), ACCEPTABLE_CLIP_ERROR);
+ QCOMPARE_WITH_ABS_ERROR(nearClip, view.getNearClip(), ACCEPTABLE_CLIP_ERROR);
+
+ // check transform
+ QCOMPARE_WITH_ABS_ERROR(view.getPosition(), center, ACCEPTABLE_FLOAT_ERROR);
+ float rotationDot = glm::abs(glm::dot(rotation, view.getOrientation()));
+ QCOMPARE_WITH_ABS_ERROR(rotationDot, 1.0f, ACCEPTABLE_DOT_ERROR);
+
+ // check view directions
+ glm::vec3 expectedForward = rotation * localForward;
+ float forwardDot = glm::dot(expectedForward, view.getDirection());
+ QCOMPARE_WITH_ABS_ERROR(forwardDot, 1.0f, ACCEPTABLE_DOT_ERROR);
+
+ glm::vec3 expectedRight = rotation * localRight;
+ float rightDot = glm::dot(expectedRight, view.getRight());
+ QCOMPARE_WITH_ABS_ERROR(rightDot, 1.0f, ACCEPTABLE_DOT_ERROR);
+
+ glm::vec3 expectedUp = rotation * localUp;
+ float upDot = glm::dot(expectedUp, view.getUp());
+ QCOMPARE_WITH_ABS_ERROR(upDot, 1.0f, ACCEPTABLE_DOT_ERROR);
+}
+
+void ViewFrustumTests::testCubeKeyholeIntersection() {
+ float aspect = 1.0f;
+ float fovX = PI / 2.0f;
+ float fovY = 2.0f * asinf(sinf(0.5f * fovX) / aspect);
+ float nearClip = 1.0f;
+ float farClip = 100.0f;
+ float holeRadius = 10.0f;
+
+ glm::vec3 center = glm::vec3(12.3f, 4.56f, 89.7f);
+
+ float angle = PI / 7.0f;
+ glm::vec3 axis = Vectors::UNIT_Y;
+ glm::quat rotation = glm::angleAxis(angle, axis);
+
+ ViewFrustum view;
+ view.setProjection(glm::perspective(fovX, aspect, nearClip, farClip));
+ view.setPosition(center);
+ view.setOrientation(rotation);
+ view.setCenterRadius(holeRadius);
+ view.calculate();
+
+ float delta = 0.1f;
+ float deltaAngle = 0.01f;
+ glm::quat elevation, swing;
+ glm::vec3 cubeCenter, localOffset;
+
+ float cubeScale = 2.68f; // must be much smaller than cubeDistance for small angle approx below
+ glm::vec3 halfScaleOffset = 0.5f * glm::vec3(cubeScale);
+ float cubeDistance = farClip;
+ float cubeBoundingRadius = 0.5f * sqrtf(3.0f) * cubeScale;
+ float cubeAngle = cubeBoundingRadius / cubeDistance; // sine of small angles approximation
+ AACube cube(center, cubeScale);
+
+ // farPlane
+ localOffset = (cubeDistance - cubeBoundingRadius - delta) * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE);
+
+ localOffset = cubeDistance * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INTERSECT);
+
+ localOffset = (cubeDistance + cubeBoundingRadius + delta) * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::OUTSIDE);
+
+ // nearPlane
+ localOffset = (nearClip + 2.0f * cubeBoundingRadius + delta) * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE);
+
+ localOffset = (nearClip + delta) * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE);
+
+ // topPlane
+ angle = 0.5f * fovY;
+ elevation = glm::angleAxis(angle - cubeAngle - deltaAngle, localRight);
+ localOffset = elevation * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE);
+
+ elevation = glm::angleAxis(angle, localRight);
+ localOffset = elevation * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INTERSECT);
+
+ elevation = glm::angleAxis(angle + cubeAngle + deltaAngle, localRight);
+ localOffset = elevation * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::OUTSIDE);
+
+ // bottom plane
+ angle = -0.5f * fovY;
+ elevation = glm::angleAxis(angle + cubeAngle + deltaAngle, localRight);
+ localOffset = elevation * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE);
+
+ elevation = glm::angleAxis(angle, localRight);
+ localOffset = elevation * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INTERSECT);
+
+ elevation = glm::angleAxis(angle - cubeAngle - deltaAngle, localRight);
+ localOffset = elevation * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::OUTSIDE);
+
+ // right plane
+ angle = 0.5f * fovX;
+ swing = glm::angleAxis(angle - cubeAngle - deltaAngle, localUp);
+ localOffset = swing * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE);
+
+ swing = glm::angleAxis(angle, localUp);
+ localOffset = swing * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INTERSECT);
+
+ swing = glm::angleAxis(angle + cubeAngle + deltaAngle, localUp);
+ localOffset = swing * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::OUTSIDE);
+
+ // left plane
+ angle = -0.5f * fovX;
+ swing = glm::angleAxis(angle + cubeAngle + deltaAngle, localUp);
+ localOffset = swing * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE);
+
+ swing = glm::angleAxis(angle, localUp);
+ localOffset = swing * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INTERSECT);
+
+ swing = glm::angleAxis(angle - cubeAngle - deltaAngle, localUp);
+ localOffset = swing * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::OUTSIDE);
+
+ // central sphere right
+ localOffset = (holeRadius - cubeBoundingRadius - delta) * localRight;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE);
+
+ localOffset = holeRadius * localRight;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INTERSECT);
+
+ localOffset = (holeRadius + cubeBoundingRadius + delta) * localRight;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::OUTSIDE);
+
+ // central sphere up
+ localOffset = (holeRadius - cubeBoundingRadius - delta) * localUp;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE);
+
+ localOffset = holeRadius * localUp;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INTERSECT);
+
+ localOffset = (holeRadius + cubeBoundingRadius + delta) * localUp;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::OUTSIDE);
+
+ // central sphere back
+ localOffset = (-holeRadius + cubeBoundingRadius + delta) * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE);
+
+ localOffset = - holeRadius * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INTERSECT);
+
+ localOffset = (-holeRadius - cubeBoundingRadius - delta) * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::OUTSIDE);
+
+ // central sphere center
+ float bigCubeScale = 2.0f * holeRadius / sqrtf(3.0f) - delta;
+ cube.setBox(center - glm::vec3(0.5f * bigCubeScale), bigCubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE); // smaller than sphere
+
+ bigCubeScale = 2.0f * holeRadius / sqrtf(3.0f) + delta;
+ cube.setBox(center - glm::vec3(0.5f * bigCubeScale), bigCubeScale);
+ QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INTERSECT); // larger than sphere
+}
+
+void ViewFrustumTests::testCubeFrustumIntersection() {
+ float aspect = 1.0f;
+ float fovX = PI / 2.0f;
+ float fovY = 2.0f * asinf(sinf(0.5f * fovX) / aspect);
+ float nearClip = 1.0f;
+ float farClip = 100.0f;
+ float holeRadius = 10.0f;
+
+ glm::vec3 center = glm::vec3(12.3f, 4.56f, 89.7f);
+
+ float angle = PI / 7.0f;
+ glm::vec3 axis = Vectors::UNIT_Y;
+ glm::quat rotation = glm::angleAxis(angle, axis);
+
+ ViewFrustum view;
+ view.setProjection(glm::perspective(fovX, aspect, nearClip, farClip));
+ view.setPosition(center);
+ view.setOrientation(rotation);
+ view.setCenterRadius(holeRadius);
+ view.calculate();
+
+ float delta = 0.1f;
+ float deltaAngle = 0.01f;
+ glm::quat elevation, swing;
+ glm::vec3 cubeCenter, localOffset;
+
+ float cubeScale = 2.68f; // must be much smaller than cubeDistance for small angle approx below
+ glm::vec3 halfScaleOffset = 0.5f * glm::vec3(cubeScale);
+ float cubeDistance = farClip;
+ float cubeBoundingRadius = 0.5f * sqrtf(3.0f) * cubeScale;
+ float cubeAngle = cubeBoundingRadius / cubeDistance; // sine of small angles approximation
+ AACube cube(center, cubeScale);
+
+ // farPlane
+ localOffset = (cubeDistance - cubeBoundingRadius - delta) * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INSIDE);
+
+ localOffset = cubeDistance * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INTERSECT);
+
+ localOffset = (cubeDistance + cubeBoundingRadius + delta) * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::OUTSIDE);
+
+ // nearPlane
+ localOffset = (nearClip + 2.0f * cubeBoundingRadius + delta) * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INSIDE);
+
+ localOffset = (nearClip + delta) * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INTERSECT);
+
+ localOffset = (nearClip - cubeBoundingRadius - delta) * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::OUTSIDE);
+
+ // topPlane
+ angle = 0.5f * fovY;
+ elevation = glm::angleAxis(angle - cubeAngle - deltaAngle, localRight);
+ localOffset = elevation * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INSIDE);
+
+ elevation = glm::angleAxis(angle, localRight);
+ localOffset = elevation * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INTERSECT);
+
+ elevation = glm::angleAxis(angle + cubeAngle + deltaAngle, localRight);
+ localOffset = elevation * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::OUTSIDE);
+
+ // bottom plane
+ angle = -0.5f * fovY;
+ elevation = glm::angleAxis(angle + cubeAngle + deltaAngle, localRight);
+ localOffset = elevation * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INSIDE);
+
+ elevation = glm::angleAxis(angle, localRight);
+ localOffset = elevation * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INTERSECT);
+
+ elevation = glm::angleAxis(angle - cubeAngle - deltaAngle, localRight);
+ localOffset = elevation * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::OUTSIDE);
+
+ // right plane
+ angle = 0.5f * fovX;
+ swing = glm::angleAxis(angle - cubeAngle - deltaAngle, localUp);
+ localOffset = swing * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INSIDE);
+
+ swing = glm::angleAxis(angle, localUp);
+ localOffset = swing * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INTERSECT);
+
+ swing = glm::angleAxis(angle + cubeAngle + deltaAngle, localUp);
+ localOffset = swing * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::OUTSIDE);
+
+ // left plane
+ angle = -0.5f * fovX;
+ swing = glm::angleAxis(angle + cubeAngle + deltaAngle, localUp);
+ localOffset = swing * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INSIDE);
+
+ swing = glm::angleAxis(angle, localUp);
+ localOffset = swing * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INTERSECT);
+
+ swing = glm::angleAxis(angle - cubeAngle - deltaAngle, localUp);
+ localOffset = swing * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::OUTSIDE);
+}
+
+void ViewFrustumTests::testPointIntersectsFrustum() {
+ float aspect = 1.0f;
+ float fovX = PI / 2.0f;
+ float fovY = 2.0f * asinf(sinf(0.5f * fovX) / aspect);
+ float nearClip = 1.0f;
+ float farClip = 100.0f;
+ float holeRadius = 10.0f;
+
+ glm::vec3 center = glm::vec3(12.3f, 4.56f, 89.7f);
+
+ float angle = PI / 7.0f;
+ glm::vec3 axis = Vectors::UNIT_Y;
+ glm::quat rotation = glm::angleAxis(angle, axis);
+
+ ViewFrustum view;
+ view.setProjection(glm::perspective(fovX, aspect, nearClip, farClip));
+ view.setPosition(center);
+ view.setOrientation(rotation);
+ view.setCenterRadius(holeRadius);
+ view.calculate();
+
+ float delta = 0.1f;
+ float deltaAngle = 0.01f;
+ glm::quat elevation, swing;
+ glm::vec3 point, localOffset;
+ float pointDistance = farClip;
+
+ // farPlane
+ localOffset = (pointDistance - delta) * localForward;
+ point = center + rotation * localOffset;
+ QCOMPARE(view.pointIntersectsFrustum(point), true); // inside
+
+ localOffset = (pointDistance + delta) * localForward;
+ point = center + rotation * localOffset;
+ QCOMPARE(view.pointIntersectsFrustum(point), false); // outside
+
+ // nearPlane
+ localOffset = (nearClip + delta) * localForward;
+ point = center + rotation * localOffset;
+ QCOMPARE(view.pointIntersectsFrustum(point), true); // inside
+
+ localOffset = (nearClip - delta) * localForward;
+ point = center + rotation * localOffset;
+ QCOMPARE(view.pointIntersectsFrustum(point), false); // outside
+
+ // topPlane
+ angle = 0.5f * fovY;
+ elevation = glm::angleAxis(angle - deltaAngle, localRight);
+ localOffset = elevation * (pointDistance * localForward);
+ point = center + rotation * localOffset;
+ QCOMPARE(view.pointIntersectsFrustum(point), true); // inside
+
+ elevation = glm::angleAxis(angle + deltaAngle, localRight);
+ localOffset = elevation * (pointDistance * localForward);
+ point = center + rotation * localOffset;
+ QCOMPARE(view.pointIntersectsFrustum(point), false); // outside
+
+ // bottom plane
+ angle = -0.5f * fovY;
+ elevation = glm::angleAxis(angle + deltaAngle, localRight);
+ localOffset = elevation * (pointDistance * localForward);
+ point = center + rotation * localOffset;
+ QCOMPARE(view.pointIntersectsFrustum(point), true); // inside
+
+ elevation = glm::angleAxis(angle - deltaAngle, localRight);
+ localOffset = elevation * (pointDistance * localForward);
+ point = center + rotation * localOffset;
+ QCOMPARE(view.pointIntersectsFrustum(point), false); // outside
+
+ // right plane
+ angle = 0.5f * fovX;
+ swing = glm::angleAxis(angle - deltaAngle, localUp);
+ localOffset = swing * (pointDistance * localForward);
+ point = center + rotation * localOffset;
+ QCOMPARE(view.pointIntersectsFrustum(point), true); // inside
+
+ swing = glm::angleAxis(angle + deltaAngle, localUp);
+ localOffset = swing * (pointDistance * localForward);
+ point = center + rotation * localOffset;
+ QCOMPARE(view.pointIntersectsFrustum(point), false); // outside
+
+ // left plane
+ angle = -0.5f * fovX;
+ swing = glm::angleAxis(angle + deltaAngle, localUp);
+ localOffset = swing * (pointDistance * localForward);
+ point = center + rotation * localOffset;
+ QCOMPARE(view.pointIntersectsFrustum(point), true); // inside
+
+ swing = glm::angleAxis(angle - deltaAngle, localUp);
+ localOffset = swing * (pointDistance * localForward);
+ point = center + rotation * localOffset;
+ QCOMPARE(view.pointIntersectsFrustum(point), false); // outside
+}
+
+void ViewFrustumTests::testSphereIntersectsFrustum() {
+ float aspect = 1.0f;
+ float fovX = PI / 2.0f;
+ float fovY = 2.0f * asinf(sinf(0.5f * fovX) / aspect);
+ float nearClip = 1.0f;
+ float farClip = 100.0f;
+ float holeRadius = 10.0f;
+
+ glm::vec3 center = glm::vec3(12.3f, 4.56f, 89.7f);
+
+ float angle = PI / 7.0f;
+ glm::vec3 axis = Vectors::UNIT_Y;
+ glm::quat rotation = glm::angleAxis(angle, axis);
+
+ ViewFrustum view;
+ view.setProjection(glm::perspective(fovX, aspect, nearClip, farClip));
+ view.setPosition(center);
+ view.setOrientation(rotation);
+ view.setCenterRadius(holeRadius);
+ view.calculate();
+
+ float delta = 0.1f;
+ float deltaAngle = 0.01f;
+ glm::quat elevation, swing;
+ glm::vec3 sphereCenter, localOffset;
+
+ float sphereRadius = 2.68f; // must be much smaller than sphereDistance for small angle approx below
+ float sphereDistance = farClip;
+ float sphereAngle = sphereRadius / sphereDistance; // sine of small angles approximation
+
+ // farPlane
+ localOffset = (sphereDistance - sphereRadius - delta) * localForward;
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // inside
+
+ localOffset = (sphereDistance + sphereRadius - delta) * localForward;
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // straddle
+
+ localOffset = (sphereDistance + sphereRadius + delta) * localForward;
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), false); // outside
+
+ // nearPlane
+ localOffset = (nearClip + 2.0f * sphereRadius + delta) * localForward;
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // inside
+
+ localOffset = (nearClip - sphereRadius + delta) * localForward;
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // straddle
+
+ localOffset = (nearClip - sphereRadius - delta) * localForward;
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), false); // outside
+
+ // topPlane
+ angle = 0.5f * fovY - sphereAngle;
+ elevation = glm::angleAxis(angle - deltaAngle, localRight);
+ localOffset = elevation * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // inside
+
+ angle = 0.5f * fovY + sphereAngle;
+ elevation = glm::angleAxis(angle - deltaAngle, localRight);
+ localOffset = elevation * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // straddle
+
+ elevation = glm::angleAxis(angle + deltaAngle, localRight);
+ localOffset = elevation * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), false); // outside
+
+ // bottom plane
+ angle = -0.5f * fovY + sphereAngle;
+ elevation = glm::angleAxis(angle + deltaAngle, localRight);
+ localOffset = elevation * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // inside
+
+ angle = -0.5f * fovY - sphereAngle;
+ elevation = glm::angleAxis(angle + deltaAngle, localRight);
+ localOffset = elevation * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // straddle
+
+ elevation = glm::angleAxis(angle - deltaAngle, localRight);
+ localOffset = elevation * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), false); // outside
+
+ // right plane
+ angle = 0.5f * fovX - sphereAngle;
+ swing = glm::angleAxis(angle - deltaAngle, localUp);
+ localOffset = swing * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // inside
+
+ angle = 0.5f * fovX + sphereAngle;
+ swing = glm::angleAxis(angle - deltaAngle, localUp);
+ localOffset = swing * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // straddle
+
+ swing = glm::angleAxis(angle + deltaAngle, localUp);
+ localOffset = swing * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), false); // outside
+
+ // left plane
+ angle = -0.5f * fovX + sphereAngle;
+ swing = glm::angleAxis(angle + deltaAngle, localUp);
+ localOffset = swing * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // inside
+
+ angle = -0.5f * fovX - sphereAngle;
+ swing = glm::angleAxis(angle + deltaAngle, localUp);
+ localOffset = swing * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // straddle
+
+ swing = glm::angleAxis(angle - sphereAngle - deltaAngle, localUp);
+ localOffset = swing * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), false); // outside
+}
+
+void ViewFrustumTests::testBoxIntersectsFrustum() {
+ float aspect = 1.0f;
+ float fovX = PI / 2.0f;
+ float fovY = 2.0f * asinf(sinf(0.5f * fovX) / aspect);
+ float nearClip = 1.0f;
+ float farClip = 100.0f;
+ float holeRadius = 10.0f;
+
+ glm::vec3 center = glm::vec3(12.3f, 4.56f, 89.7f);
+
+ float angle = PI / 7.0f;
+ glm::vec3 axis = Vectors::UNIT_Y;
+ glm::quat rotation = glm::angleAxis(angle, axis);
+
+ ViewFrustum view;
+ view.setProjection(glm::perspective(fovX, aspect, nearClip, farClip));
+ view.setPosition(center);
+ view.setOrientation(rotation);
+ view.setCenterRadius(holeRadius);
+ view.calculate();
+
+ float delta = 0.1f;
+ float deltaAngle = 0.01f;
+ glm::quat elevation, swing;
+ glm::vec3 boxCenter, localOffset;
+
+ glm::vec3 boxScale = glm::vec3(2.68f, 1.78f, 0.431f);
+ float boxDistance = farClip;
+ float boxBoundingRadius = 0.5f * glm::length(boxScale);
+ float boxAngle = boxBoundingRadius / boxDistance; // sine of small angles approximation
+ AABox box(center, boxScale);
+
+ // farPlane
+ localOffset = (boxDistance - boxBoundingRadius - delta) * localForward;
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - 0.5f * boxScale, boxScale);
+ QCOMPARE(view.boxIntersectsFrustum(box), true); // inside
+
+ localOffset = boxDistance * localForward;
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - 0.5f * boxScale, boxScale);
+ QCOMPARE(view.boxIntersectsFrustum(box), true); // straddle
+
+ localOffset = (boxDistance + boxBoundingRadius + delta) * localForward;
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - 0.5f * boxScale, boxScale);
+ QCOMPARE(view.boxIntersectsFrustum(box), false); // outside
+
+ // nearPlane
+ localOffset = (nearClip + 2.0f * boxBoundingRadius + delta) * localForward;
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - 0.5f * boxScale, boxScale);
+ QCOMPARE(view.boxIntersectsFrustum(box), true); // inside
+
+ localOffset = nearClip * localForward;
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - 0.5f * boxScale, boxScale);
+ QCOMPARE(view.boxIntersectsFrustum(box), true); // straddle
+
+ localOffset = (nearClip - boxBoundingRadius - delta) * localForward;
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - 0.5f * boxScale, boxScale);
+ QCOMPARE(view.boxIntersectsFrustum(box), false); // outside
+
+ // topPlane
+ angle = 0.5f * fovY;
+ elevation = glm::angleAxis(angle - boxAngle - deltaAngle, localRight);
+ localOffset = elevation * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - 0.5f * boxScale, boxScale);
+ QCOMPARE(view.boxIntersectsFrustum(box), true); // inside
+
+ elevation = glm::angleAxis(angle, localRight);
+ localOffset = elevation * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - 0.5f * boxScale, boxScale);
+ QCOMPARE(view.boxIntersectsFrustum(box), true); // straddle
+
+ elevation = glm::angleAxis(angle + boxAngle + deltaAngle, localRight);
+ localOffset = elevation * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - 0.5f * boxScale, boxScale);
+ QCOMPARE(view.boxIntersectsFrustum(box), false); // outside
+
+ // bottom plane
+ angle = -0.5f * fovY;
+ elevation = glm::angleAxis(angle + boxAngle + deltaAngle, localRight);
+ localOffset = elevation * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - 0.5f * boxScale, boxScale);
+ QCOMPARE(view.boxIntersectsFrustum(box), true); // inside
+
+ elevation = glm::angleAxis(angle, localRight);
+ localOffset = elevation * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - 0.5f * boxScale, boxScale);
+ QCOMPARE(view.boxIntersectsFrustum(box), true); // straddle
+
+ elevation = glm::angleAxis(angle - boxAngle - deltaAngle, localRight);
+ localOffset = elevation * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - 0.5f * boxScale, boxScale);
+ QCOMPARE(view.boxIntersectsFrustum(box), false); // outside
+
+ // right plane
+ angle = 0.5f * fovX;
+ swing = glm::angleAxis(angle - boxAngle - deltaAngle, localUp);
+ localOffset = swing * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - 0.5f * boxScale, boxScale);
+ QCOMPARE(view.boxIntersectsFrustum(box), true); // inside
+
+ swing = glm::angleAxis(angle, localUp);
+ localOffset = swing * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - 0.5f * boxScale, boxScale);
+ QCOMPARE(view.boxIntersectsFrustum(box), true); // straddle
+
+ swing = glm::angleAxis(angle + boxAngle + deltaAngle, localUp);
+ localOffset = swing * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - 0.5f * boxScale, boxScale);
+ QCOMPARE(view.boxIntersectsFrustum(box), false); // outside
+
+ // left plane
+ angle = -0.5f * fovX;
+ swing = glm::angleAxis(angle + boxAngle + deltaAngle, localUp);
+ localOffset = swing * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - 0.5f * boxScale, boxScale);
+ QCOMPARE(view.boxIntersectsFrustum(box), true); // inside
+
+ swing = glm::angleAxis(angle, localUp);
+ localOffset = swing * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - 0.5f * boxScale, boxScale);
+ QCOMPARE(view.boxIntersectsFrustum(box), true); // straddle
+
+ swing = glm::angleAxis(angle - boxAngle - deltaAngle, localUp);
+ localOffset = swing * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - 0.5f * boxScale, boxScale);
+ QCOMPARE(view.boxIntersectsFrustum(box), false); // outside
+}
+
+void ViewFrustumTests::testSphereIntersectsKeyhole() {
+ float aspect = 1.0f;
+ float fovX = PI / 2.0f;
+ float fovY = 2.0f * asinf(sinf(0.5f * fovX) / aspect);
+ float nearClip = 1.0f;
+ float farClip = 100.0f;
+ float holeRadius = 10.0f;
+
+ glm::vec3 center = glm::vec3(12.3f, 4.56f, 89.7f);
+
+ float angle = PI / 7.0f;
+ glm::vec3 axis = Vectors::UNIT_Y;
+ glm::quat rotation = glm::angleAxis(angle, axis);
+
+ ViewFrustum view;
+ view.setProjection(glm::perspective(fovX, aspect, nearClip, farClip));
+ view.setPosition(center);
+ view.setOrientation(rotation);
+ view.setCenterRadius(holeRadius);
+ view.calculate();
+
+ float delta = 0.1f;
+ float deltaAngle = 0.01f;
+ glm::quat elevation, swing;
+ glm::vec3 sphereCenter, localOffset;
+
+ float sphereRadius = 2.68f; // must be much smaller than sphereDistance for small angle approx below
+ float sphereDistance = farClip;
+ float sphereAngle = sphereRadius / sphereDistance; // sine of small angles approximation
+
+ // farPlane
+ localOffset = (sphereDistance - sphereRadius - delta) * localForward;
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // inside
+
+ localOffset = sphereDistance * localForward;
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // straddle
+
+ localOffset = (sphereDistance + sphereRadius + delta) * localForward;
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), false); // outside
+
+ // nearPlane
+ localOffset = (nearClip + 2.0f * sphereRadius + delta) * localForward;
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // inside
+
+ localOffset = (nearClip - sphereRadius + delta) * localForward;
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // straddle
+
+ localOffset = (nearClip - sphereRadius - delta) * localForward;
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // touches central sphere
+
+ // topPlane
+ angle = 0.5f * fovY - sphereAngle;
+ elevation = glm::angleAxis(angle - deltaAngle, localRight);
+ localOffset = elevation * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // inside
+
+ angle = 0.5f * fovY + sphereAngle;
+ elevation = glm::angleAxis(angle - deltaAngle, localRight);
+ localOffset = elevation * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // straddle
+
+ elevation = glm::angleAxis(angle + deltaAngle, localRight);
+ localOffset = elevation * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), false); // outside
+
+ // bottom plane
+ angle = -0.5f * fovY + sphereAngle;
+ elevation = glm::angleAxis(angle + deltaAngle, localRight);
+ localOffset = elevation * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // inside
+
+ angle = -0.5f * fovY - sphereAngle;
+ elevation = glm::angleAxis(angle + deltaAngle, localRight);
+ localOffset = elevation * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // straddle
+
+ elevation = glm::angleAxis(angle - deltaAngle, localRight);
+ localOffset = elevation * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), false); // outside
+
+ // right plane
+ angle = 0.5f * fovX - sphereAngle;
+ swing = glm::angleAxis(angle - deltaAngle, localUp);
+ localOffset = swing * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // inside
+
+ angle = 0.5f * fovX + sphereAngle;
+ swing = glm::angleAxis(angle - deltaAngle, localUp);
+ localOffset = swing * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // straddle
+
+ swing = glm::angleAxis(angle + deltaAngle, localUp);
+ localOffset = swing * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), false); // outside
+
+ // left plane
+ angle = -0.5f * fovX + sphereAngle;
+ swing = glm::angleAxis(angle + deltaAngle, localUp);
+ localOffset = swing * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // inside
+
+ angle = -0.5f * fovX - sphereAngle;
+ swing = glm::angleAxis(angle + deltaAngle, localUp);
+ localOffset = swing * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // straddle
+
+ swing = glm::angleAxis(angle - sphereAngle - deltaAngle, localUp);
+ localOffset = swing * (sphereDistance * localForward);
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), false); // outside
+
+ // central sphere right
+ localOffset = (holeRadius - sphereRadius - delta) * localRight;
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // inside right
+
+ localOffset = holeRadius * localRight;
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // straddle right
+
+ localOffset = (holeRadius + sphereRadius + delta) * localRight;
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), false); // outside right
+
+ // central sphere up
+ localOffset = (holeRadius - sphereRadius - delta) * localUp;
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // inside up
+
+ localOffset = holeRadius * localUp;
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // straddle up
+
+ localOffset = (holeRadius + sphereRadius + delta) * localUp;
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), false); // outside up
+
+ // central sphere back
+ localOffset = (-holeRadius + sphereRadius + delta) * localForward;
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // inside back
+
+ localOffset = - holeRadius * localForward;
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // straddle back
+
+ localOffset = (-holeRadius - sphereRadius - delta) * localForward;
+ sphereCenter = center + rotation * localOffset;
+ QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), false); // outside back
+}
+
+void ViewFrustumTests::testCubeIntersectsKeyhole() {
+ float aspect = 1.0f;
+ float fovX = PI / 2.0f;
+ float fovY = 2.0f * asinf(sinf(0.5f * fovX) / aspect);
+ float nearClip = 1.0f;
+ float farClip = 100.0f;
+ float holeRadius = 10.0f;
+
+ glm::vec3 center = glm::vec3(12.3f, 4.56f, 89.7f);
+
+ float angle = PI / 7.0f;
+ glm::vec3 axis = Vectors::UNIT_Y;
+ glm::quat rotation = glm::angleAxis(angle, axis);
+
+ ViewFrustum view;
+ view.setProjection(glm::perspective(fovX, aspect, nearClip, farClip));
+ view.setPosition(center);
+ view.setOrientation(rotation);
+ view.setCenterRadius(holeRadius);
+ view.calculate();
+
+ float delta = 0.1f;
+ float deltaAngle = 0.01f;
+ glm::quat elevation, swing;
+ glm::vec3 cubeCenter, localOffset;
+
+ float cubeScale = 2.68f; // must be much smaller than cubeDistance for small angle approx below
+ glm::vec3 halfScaleOffset = 0.5f * glm::vec3(cubeScale);
+ float cubeDistance = farClip;
+ float cubeBoundingRadius = 0.5f * sqrtf(3.0f) * cubeScale;
+ float cubeAngle = cubeBoundingRadius / cubeDistance; // sine of small angles approximation
+ AACube cube(center, cubeScale);
+
+ // farPlane
+ localOffset = (cubeDistance - cubeBoundingRadius - delta) * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), true);
+
+ localOffset = cubeDistance * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), true);
+
+ localOffset = (cubeDistance + cubeBoundingRadius + delta) * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), false);
+
+ // nearPlane
+ localOffset = (nearClip + 2.0f * cubeBoundingRadius + delta) * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // inside
+
+ localOffset = (nearClip + delta) * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // straddle
+
+ localOffset = (nearClip - cubeBoundingRadius - delta) * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // touches centeral sphere
+
+ // topPlane
+ angle = 0.5f * fovY;
+ elevation = glm::angleAxis(angle - cubeAngle - deltaAngle, localRight);
+ localOffset = elevation * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // inside
+
+ elevation = glm::angleAxis(angle, localRight);
+ localOffset = elevation * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // straddle
+
+ elevation = glm::angleAxis(angle + cubeAngle + deltaAngle, localRight);
+ localOffset = elevation * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), false); // outside
+
+ // bottom plane
+ angle = -0.5f * fovY;
+ elevation = glm::angleAxis(angle + cubeAngle + deltaAngle, localRight);
+ localOffset = elevation * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // inside
+
+ elevation = glm::angleAxis(angle, localRight);
+ localOffset = elevation * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // straddle
+
+ elevation = glm::angleAxis(angle - cubeAngle - deltaAngle, localRight);
+ localOffset = elevation * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), false); // outside
+
+ // right plane
+ angle = 0.5f * fovX;
+ swing = glm::angleAxis(angle - cubeAngle - deltaAngle, localUp);
+ localOffset = swing * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // inside
+
+ swing = glm::angleAxis(angle, localUp);
+ localOffset = swing * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // straddle
+
+ swing = glm::angleAxis(angle + cubeAngle + deltaAngle, localUp);
+ localOffset = swing * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), false); // outside
+
+ // left plane
+ angle = -0.5f * fovX;
+ swing = glm::angleAxis(angle + cubeAngle + deltaAngle, localUp);
+ localOffset = swing * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // inside
+
+ swing = glm::angleAxis(angle, localUp);
+ localOffset = swing * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // straddle
+
+ swing = glm::angleAxis(angle - cubeAngle - deltaAngle, localUp);
+ localOffset = swing * (cubeDistance * localForward);
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), false); // outside
+
+ // central sphere right
+ localOffset = (holeRadius - cubeBoundingRadius - delta) * localRight;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // inside right
+
+ localOffset = holeRadius * localRight;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // straddle right
+
+ localOffset = (holeRadius + cubeBoundingRadius + delta) * localRight;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), false); // outside right
+
+ // central sphere up
+ localOffset = (holeRadius - cubeBoundingRadius - delta) * localUp;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // inside up
+
+ localOffset = holeRadius * localUp;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // straddle up
+
+ localOffset = (holeRadius + cubeBoundingRadius + delta) * localUp;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), false); // outside up
+
+ // central sphere back
+ localOffset = (-holeRadius + cubeBoundingRadius + delta) * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // inside back
+
+ localOffset = - holeRadius * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // straddle back
+
+ localOffset = (-holeRadius - cubeBoundingRadius - delta) * localForward;
+ cubeCenter = center + rotation * localOffset;
+ cube.setBox(cubeCenter - halfScaleOffset, cubeScale);
+ QCOMPARE(view.cubeIntersectsKeyhole(cube), false); // outside back
+}
+
+void ViewFrustumTests::testBoxIntersectsKeyhole() {
+ float aspect = 1.0f;
+ float fovX = PI / 2.0f;
+ float fovY = 2.0f * asinf(sinf(0.5f * fovX) / aspect);
+ float nearClip = 1.0f;
+ float farClip = 100.0f;
+ float holeRadius = 10.0f;
+
+ glm::vec3 center = glm::vec3(12.3f, 4.56f, 89.7f);
+
+ float angle = PI / 7.0f;
+ glm::vec3 axis = Vectors::UNIT_Y;
+ glm::quat rotation = glm::angleAxis(angle, axis);
+
+ ViewFrustum view;
+ view.setProjection(glm::perspective(fovX, aspect, nearClip, farClip));
+ view.setPosition(center);
+ view.setOrientation(rotation);
+ view.setCenterRadius(holeRadius);
+ view.calculate();
+
+ float delta = 0.1f;
+ float deltaAngle = 0.01f;
+ glm::quat elevation, swing;
+ glm::vec3 boxCenter, localOffset;
+
+ glm::vec3 boxScale = glm::vec3(2.68f, 1.78f, 0.431f); // sides must be much smaller than boxDistance
+ glm::vec3 halfScaleOffset = 0.5f * boxScale;
+ float boxDistance = farClip;
+ float boxBoundingRadius = 0.5f * glm::length(boxScale);
+ float boxAngle = boxBoundingRadius / boxDistance; // sine of small angles approximation
+ AABox box(center, boxScale);
+
+ // farPlane
+ localOffset = (boxDistance - boxBoundingRadius - delta) * localForward;
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), true);
+
+ localOffset = boxDistance * localForward;
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), true);
+
+ localOffset = (boxDistance + boxBoundingRadius + delta) * localForward;
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), false);
+
+ // nearPlane
+ localOffset = (nearClip + 2.0f * boxBoundingRadius + delta) * localForward;
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), true); // inside
+
+ localOffset = (nearClip + delta) * localForward;
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), true); // straddle
+
+ localOffset = (nearClip - boxBoundingRadius - delta) * localForward;
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), true); // touches centeral sphere
+
+ // topPlane
+ angle = 0.5f * fovY;
+ elevation = glm::angleAxis(angle - boxAngle - deltaAngle, localRight);
+ localOffset = elevation * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), true); // inside
+
+ elevation = glm::angleAxis(angle, localRight);
+ localOffset = elevation * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), true); // straddle
+
+ elevation = glm::angleAxis(angle + boxAngle + deltaAngle, localRight);
+ localOffset = elevation * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), false); // outside
+
+ // bottom plane
+ angle = -0.5f * fovY;
+ elevation = glm::angleAxis(angle + boxAngle + deltaAngle, localRight);
+ localOffset = elevation * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), true); // inside
+
+ elevation = glm::angleAxis(angle, localRight);
+ localOffset = elevation * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), true); // straddle
+
+ elevation = glm::angleAxis(angle - boxAngle - deltaAngle, localRight);
+ localOffset = elevation * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), false); // outside
+
+ // right plane
+ angle = 0.5f * fovX;
+ swing = glm::angleAxis(angle - boxAngle - deltaAngle, localUp);
+ localOffset = swing * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), true); // inside
+
+ swing = glm::angleAxis(angle, localUp);
+ localOffset = swing * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), true); // straddle
+
+ swing = glm::angleAxis(angle + boxAngle + deltaAngle, localUp);
+ localOffset = swing * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), false); // outside
+
+ // left plane
+ angle = -0.5f * fovX;
+ swing = glm::angleAxis(angle + boxAngle + deltaAngle, localUp);
+ localOffset = swing * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), true); // inside
+
+ swing = glm::angleAxis(angle, localUp);
+ localOffset = swing * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), true); // straddle
+
+ swing = glm::angleAxis(angle - boxAngle - deltaAngle, localUp);
+ localOffset = swing * (boxDistance * localForward);
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), false); // outside
+
+ // central sphere right
+ localOffset = (holeRadius - boxBoundingRadius - delta) * localRight;
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), true); // inside right
+
+ localOffset = holeRadius * localRight;
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), true); // straddle right
+
+ localOffset = (holeRadius + boxBoundingRadius + delta) * localRight;
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), false); // outside right
+
+ // central sphere up
+ localOffset = (holeRadius - boxBoundingRadius - delta) * localUp;
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), true); // inside up
+
+ localOffset = holeRadius * localUp;
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), true); // straddle up
+
+ localOffset = (holeRadius + boxBoundingRadius + delta) * localUp;
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), false); // outside up
+
+ // central sphere back
+ localOffset = (-holeRadius + boxBoundingRadius + delta) * localForward;
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), true); // inside back
+
+ localOffset = - holeRadius * localForward;
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), true); // straddle back
+
+ localOffset = (-holeRadius - boxBoundingRadius - delta) * localForward;
+ boxCenter = center + rotation * localOffset;
+ box.setBox(boxCenter - halfScaleOffset, boxScale);
+ QCOMPARE(view.boxIntersectsKeyhole(box), false); // outside back
+}
diff --git a/tests/octree/src/ViewFrustumTests.h b/tests/octree/src/ViewFrustumTests.h
new file mode 100644
index 0000000000..a64a72e669
--- /dev/null
+++ b/tests/octree/src/ViewFrustumTests.h
@@ -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
+
+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
diff --git a/tests/shared/src/AABoxTests.cpp b/tests/shared/src/AABoxTests.cpp
new file mode 100644
index 0000000000..fd709a488c
--- /dev/null
+++ b/tests/shared/src/AABoxTests.cpp
@@ -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
+
+#include "AABoxTests.h"
+
+#include
+#include
+#include
+
+#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);
+ }
+ }
+}
+
diff --git a/tests/shared/src/AABoxTests.h b/tests/shared/src/AABoxTests.h
new file mode 100644
index 0000000000..fe7afede57
--- /dev/null
+++ b/tests/shared/src/AABoxTests.h
@@ -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
+#include
+
+#include
+
+class AABoxTests : public QObject {
+ Q_OBJECT
+private slots:
+ void testCtorsAndSetters();
+ void testContainsPoint();
+ void testTouchesSphere();
+};
+
+#endif // hifi_AABoxTests_h
diff --git a/tests/shared/src/AACubeTests.cpp b/tests/shared/src/AACubeTests.cpp
new file mode 100644
index 0000000000..177daf89f1
--- /dev/null
+++ b/tests/shared/src/AACubeTests.cpp
@@ -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
+
+#include "AACubeTests.h"
+
+#include
+#include
+#include
+
+#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);
+ }
+ }
+}
+
diff --git a/tests/shared/src/AACubeTests.h b/tests/shared/src/AACubeTests.h
new file mode 100644
index 0000000000..a2b2e08cc5
--- /dev/null
+++ b/tests/shared/src/AACubeTests.h
@@ -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
+#include
+
+#include
+
+class AACubeTests : public QObject {
+ Q_OBJECT
+private slots:
+ void ctorsAndSetters();
+ void containsPoint();
+ void touchesSphere();
+};
+
+#endif // hifi_AACubeTests_h
diff --git a/tests/shared/src/AngularConstraintTests.cpp b/tests/shared/src/AngularConstraintTests.cpp
deleted file mode 100644
index a711bac709..0000000000
--- a/tests/shared/src/AngularConstraintTests.cpp
+++ /dev/null
@@ -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
-
-#include
-#include
-#include
-
-#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;
-}
-
diff --git a/tests/shared/src/AngularConstraintTests.h b/tests/shared/src/AngularConstraintTests.h
deleted file mode 100644
index 705639b571..0000000000
--- a/tests/shared/src/AngularConstraintTests.h
+++ /dev/null
@@ -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
-#include
-
-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
diff --git a/unpublishedScripts/DomainContent/Home/tiltMaze/createTiltMaze.js b/unpublishedScripts/DomainContent/Home/tiltMaze/createTiltMaze.js
new file mode 100644
index 0000000000..7779bb7a07
--- /dev/null
+++ b/unpublishedScripts/DomainContent/Home/tiltMaze/createTiltMaze.js
@@ -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);
+ });
+};
\ No newline at end of file
diff --git a/unpublishedScripts/DomainContent/Home/tiltMaze/maze.js b/unpublishedScripts/DomainContent/Home/tiltMaze/maze.js
new file mode 100644
index 0000000000..557861ba30
--- /dev/null
+++ b/unpublishedScripts/DomainContent/Home/tiltMaze/maze.js
@@ -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();
+});
\ No newline at end of file