mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
Merge branch 'master' of github.com:highfidelity/hifi into fix-grab-more
This commit is contained in:
commit
95f9793999
110 changed files with 4194 additions and 2671 deletions
|
@ -277,14 +277,17 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio
|
|||
// set of stats to have, but we'd probably want a different data structure if we keep it very long.
|
||||
// Since this version uses a single shared QMap for all senders, there could be some lock contention
|
||||
// on this QWriteLocker
|
||||
void EntityServer::trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& viewerNode) {
|
||||
void EntityServer::trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& sessionID) {
|
||||
QWriteLocker locker(&_viewerSendingStatsLock);
|
||||
_viewerSendingStats[viewerNode][dataID] = { usecTimestampNow(), dataLastEdited };
|
||||
_viewerSendingStats[sessionID][dataID] = { usecTimestampNow(), dataLastEdited };
|
||||
}
|
||||
|
||||
void EntityServer::trackViewerGone(const QUuid& viewerNode) {
|
||||
void EntityServer::trackViewerGone(const QUuid& sessionID) {
|
||||
QWriteLocker locker(&_viewerSendingStatsLock);
|
||||
_viewerSendingStats.remove(viewerNode);
|
||||
_viewerSendingStats.remove(sessionID);
|
||||
if (_entitySimulation) {
|
||||
_entitySimulation->clearOwnership(sessionID);
|
||||
}
|
||||
}
|
||||
|
||||
QString EntityServer::serverSubclassStats() {
|
||||
|
|
|
@ -27,6 +27,8 @@ struct ViewerSendingStats {
|
|||
quint64 lastEdited;
|
||||
};
|
||||
|
||||
class SimpleEntitySimulation;
|
||||
|
||||
class EntityServer : public OctreeServer, public NewlyCreatedEntityHook {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
@ -52,8 +54,8 @@ public:
|
|||
virtual void readAdditionalConfiguration(const QJsonObject& settingsSectionObject) override;
|
||||
virtual QString serverSubclassStats() override;
|
||||
|
||||
virtual void trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& viewerNode) override;
|
||||
virtual void trackViewerGone(const QUuid& viewerNode) override;
|
||||
virtual void trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& sessionID) override;
|
||||
virtual void trackViewerGone(const QUuid& sessionID) override;
|
||||
|
||||
public slots:
|
||||
void pruneDeletedEntities();
|
||||
|
@ -65,7 +67,7 @@ private slots:
|
|||
void handleEntityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
|
||||
private:
|
||||
EntitySimulation* _entitySimulation;
|
||||
SimpleEntitySimulation* _entitySimulation;
|
||||
QTimer* _pruneDeletedEntitiesTimer = nullptr;
|
||||
|
||||
QReadWriteLock _viewerSendingStatsLock;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -236,6 +236,10 @@ OctreeServer::OctreeServer(ReceivedMessage& message) :
|
|||
{
|
||||
_averageLoopTime.updateAverage(0);
|
||||
qDebug() << "Octree server starting... [" << this << "]";
|
||||
|
||||
// make sure the AccountManager has an Auth URL for payment redemptions
|
||||
|
||||
AccountManager::getInstance().setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL);
|
||||
}
|
||||
|
||||
OctreeServer::~OctreeServer() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -778,7 +778,7 @@ function chooseFromHighFidelityDomains(clickedButton) {
|
|||
function createTemporaryDomain() {
|
||||
swal({
|
||||
title: 'Create temporary place name',
|
||||
text: "This will create a temporary place name and domain ID"
|
||||
text: "This will create a temporary place name and domain ID (valid for 30 days)"
|
||||
+ " so other users can easily connect to your domain.</br></br>"
|
||||
+ "In order to make your domain reachable, this will also enable full automatic networking.",
|
||||
showCancelButton: true,
|
||||
|
|
|
@ -331,6 +331,7 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
|||
QCryptographicHash::Sha256);
|
||||
|
||||
if (rsaPublicKey) {
|
||||
QByteArray decryptedArray(RSA_size(rsaPublicKey), 0);
|
||||
int decryptResult = RSA_verify(NID_sha256,
|
||||
reinterpret_cast<const unsigned char*>(usernameWithToken.constData()),
|
||||
usernameWithToken.size(),
|
||||
|
|
|
@ -96,7 +96,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
// make sure we hear about newly connected nodes from our gatekeeper
|
||||
connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode);
|
||||
|
||||
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) {
|
||||
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) {
|
||||
// we either read a certificate and private key or were not passed one
|
||||
// and completed login or did not need to
|
||||
|
||||
|
@ -198,6 +198,7 @@ bool DomainServer::optionallySetupOAuth() {
|
|||
}
|
||||
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
accountManager.disableSettingsFilePersistence();
|
||||
accountManager.setAuthURL(_oauthProviderURL);
|
||||
|
||||
_oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString();
|
||||
|
@ -371,12 +372,20 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
|
|||
packetReceiver.registerListener(PacketType::ICEPing, &_gatekeeper, "processICEPingPacket");
|
||||
packetReceiver.registerListener(PacketType::ICEPingReply, &_gatekeeper, "processICEPingReplyPacket");
|
||||
packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_gatekeeper, "processICEPeerInformationPacket");
|
||||
packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied, this, "processICEServerHeartbeatDenialPacket");
|
||||
|
||||
// add whatever static assignments that have been parsed to the queue
|
||||
addStaticAssignmentsToQueue();
|
||||
}
|
||||
|
||||
bool DomainServer::didSetupAccountManagerWithAccessToken() {
|
||||
if (AccountManager::getInstance().hasValidAccessToken()) {
|
||||
// we already gave the account manager a valid access token
|
||||
return true;
|
||||
}
|
||||
|
||||
return resetAccountManagerAccessToken();
|
||||
}
|
||||
|
||||
const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token";
|
||||
|
||||
bool DomainServer::resetAccountManagerAccessToken() {
|
||||
|
@ -392,13 +401,9 @@ bool DomainServer::resetAccountManagerAccessToken() {
|
|||
if (accessTokenVariant && accessTokenVariant->canConvert(QMetaType::QString)) {
|
||||
accessToken = accessTokenVariant->toString();
|
||||
} else {
|
||||
qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present.";
|
||||
qDebug() << "Set an access token via the web interface, in your user or master config"
|
||||
qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present."
|
||||
<< "Set an access token via the web interface, in your user or master config"
|
||||
<< "at keypath metaverse.access_token or in your ENV at key DOMAIN_SERVER_ACCESS_TOKEN";
|
||||
|
||||
// clear any existing access token from AccountManager
|
||||
AccountManager::getInstance().setAccessTokenForCurrentAuthURL(QString());
|
||||
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
|
@ -424,6 +429,34 @@ bool DomainServer::resetAccountManagerAccessToken() {
|
|||
}
|
||||
}
|
||||
|
||||
bool DomainServer::optionallySetupAssignmentPayment() {
|
||||
const QString PAY_FOR_ASSIGNMENTS_OPTION = "pay-for-assignments";
|
||||
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
|
||||
|
||||
if (settingsMap.contains(PAY_FOR_ASSIGNMENTS_OPTION) &&
|
||||
settingsMap.value(PAY_FOR_ASSIGNMENTS_OPTION).toBool() &&
|
||||
didSetupAccountManagerWithAccessToken()) {
|
||||
|
||||
qDebug() << "Assignments will be paid for via" << qPrintable(_oauthProviderURL.toString());
|
||||
|
||||
// assume that the fact we are authing against HF data server means we will pay for assignments
|
||||
// setup a timer to send transactions to pay assigned nodes every 30 seconds
|
||||
QTimer* creditSetupTimer = new QTimer(this);
|
||||
connect(creditSetupTimer, &QTimer::timeout, this, &DomainServer::setupPendingAssignmentCredits);
|
||||
|
||||
const qint64 CREDIT_CHECK_INTERVAL_MSECS = 5 * 1000;
|
||||
creditSetupTimer->start(CREDIT_CHECK_INTERVAL_MSECS);
|
||||
|
||||
QTimer* nodePaymentTimer = new QTimer(this);
|
||||
connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::sendPendingTransactionsToServer);
|
||||
|
||||
const qint64 TRANSACTION_SEND_INTERVAL_MSECS = 30 * 1000;
|
||||
nodePaymentTimer->start(TRANSACTION_SEND_INTERVAL_MSECS);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DomainServer::setupAutomaticNetworking() {
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
|
@ -434,9 +467,9 @@ void DomainServer::setupAutomaticNetworking() {
|
|||
setupICEHeartbeatForFullNetworking();
|
||||
}
|
||||
|
||||
if (!resetAccountManagerAccessToken()) {
|
||||
qDebug() << "Will not send heartbeat to Metaverse API without an access token.";
|
||||
qDebug() << "If this is not a temporary domain add an access token to your config file or via the web interface.";
|
||||
if (!didSetupAccountManagerWithAccessToken()) {
|
||||
qDebug() << "Cannot send heartbeat to data server without an access token.";
|
||||
qDebug() << "Add an access token to your config file or via the web interface.";
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -493,19 +526,6 @@ void DomainServer::setupICEHeartbeatForFullNetworking() {
|
|||
// we need this DS to know what our public IP is - start trying to figure that out now
|
||||
limitedNodeList->startSTUNPublicSocketUpdate();
|
||||
|
||||
// to send ICE heartbeats we'd better have a private key locally with an uploaded public key
|
||||
auto& accountManager = AccountManager::getInstance();
|
||||
auto domainID = accountManager.getAccountInfo().getDomainID();
|
||||
|
||||
// if we have an access token and we don't have a private key or the current domain ID has changed
|
||||
// we should generate a new keypair
|
||||
if (!accountManager.getAccountInfo().hasPrivateKey() || domainID != limitedNodeList->getSessionUUID()) {
|
||||
accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID());
|
||||
}
|
||||
|
||||
// hookup to the signal from account manager that tells us when keypair is available
|
||||
connect(&accountManager, &AccountManager::newKeypair, this, &DomainServer::handleKeypairChange);
|
||||
|
||||
if (!_iceHeartbeatTimer) {
|
||||
// setup a timer to heartbeat with the ice-server every so often
|
||||
_iceHeartbeatTimer = new QTimer { this };
|
||||
|
@ -1062,76 +1082,11 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) {
|
|||
domainUpdateJSON.toUtf8());
|
||||
}
|
||||
|
||||
// TODO: have data-web respond with ice-server hostname to use
|
||||
|
||||
void DomainServer::sendHeartbeatToIceServer() {
|
||||
if (!_iceServerSocket.getAddress().isNull()) {
|
||||
|
||||
auto& accountManager = AccountManager::getInstance();
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
if (!accountManager.getAccountInfo().hasPrivateKey()) {
|
||||
qWarning() << "Cannot send an ice-server heartbeat without a private key for signature.";
|
||||
qWarning() << "Waiting for keypair generation to complete before sending ICE heartbeat.";
|
||||
|
||||
if (!limitedNodeList->getSessionUUID().isNull()) {
|
||||
accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID());
|
||||
} else {
|
||||
qWarning() << "Attempting to send ICE server heartbeat with no domain ID. This is not supported";
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: I'd love to specify the correct size for the packet here, but it's a little trickey with
|
||||
// QDataStream and the possibility of IPv6 address for the sockets.
|
||||
if (!_iceServerHeartbeatPacket) {
|
||||
_iceServerHeartbeatPacket = NLPacket::create(PacketType::ICEServerHeartbeat);
|
||||
}
|
||||
|
||||
bool shouldRecreatePacket = false;
|
||||
|
||||
if (_iceServerHeartbeatPacket->getPayloadSize() > 0) {
|
||||
// if either of our sockets have changed we need to re-sign the heartbeat
|
||||
// first read the sockets out from the current packet
|
||||
_iceServerHeartbeatPacket->seek(0);
|
||||
QDataStream heartbeatStream(_iceServerHeartbeatPacket.get());
|
||||
|
||||
QUuid senderUUID;
|
||||
HifiSockAddr publicSocket, localSocket;
|
||||
heartbeatStream >> senderUUID >> publicSocket >> localSocket;
|
||||
|
||||
if (senderUUID != limitedNodeList->getSessionUUID()
|
||||
|| publicSocket != limitedNodeList->getPublicSockAddr()
|
||||
|| localSocket != limitedNodeList->getLocalSockAddr()) {
|
||||
shouldRecreatePacket = true;
|
||||
}
|
||||
} else {
|
||||
shouldRecreatePacket = true;
|
||||
}
|
||||
|
||||
if (shouldRecreatePacket) {
|
||||
// either we don't have a heartbeat packet yet or some combination of sockets, ID and keypair have changed
|
||||
// and we need to make a new one
|
||||
|
||||
// reset the position in the packet before writing
|
||||
_iceServerHeartbeatPacket->reset();
|
||||
|
||||
// write our plaintext data to the packet
|
||||
QDataStream heartbeatDataStream(_iceServerHeartbeatPacket.get());
|
||||
heartbeatDataStream << limitedNodeList->getSessionUUID()
|
||||
<< limitedNodeList->getPublicSockAddr() << limitedNodeList->getLocalSockAddr();
|
||||
|
||||
// setup a QByteArray that points to the plaintext data
|
||||
auto plaintext = QByteArray::fromRawData(_iceServerHeartbeatPacket->getPayload(), _iceServerHeartbeatPacket->getPayloadSize());
|
||||
|
||||
// generate a signature for the plaintext data in the packet
|
||||
auto signature = accountManager.getAccountInfo().signPlaintext(plaintext);
|
||||
|
||||
// pack the signature with the data
|
||||
heartbeatDataStream << signature;
|
||||
}
|
||||
|
||||
// send the heartbeat packet to the ice server now
|
||||
limitedNodeList->sendUnreliablePacket(*_iceServerHeartbeatPacket, _iceServerSocket);
|
||||
DependencyManager::get<LimitedNodeList>()->sendHeartbeatToIceServer(_iceServerSocket);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2015,31 +1970,3 @@ void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMes
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
static const int NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN = 3;
|
||||
|
||||
static int numHeartbeatDenials = 0;
|
||||
if (++numHeartbeatDenials > NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN) {
|
||||
qDebug() << "Received" << NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN << "heartbeat denials from ice-server"
|
||||
<< "- re-generating keypair now";
|
||||
|
||||
// we've hit our threshold of heartbeat denials, trigger a keypair re-generation
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
AccountManager::getInstance().generateNewDomainKeypair(limitedNodeList->getSessionUUID());
|
||||
|
||||
// reset our number of heartbeat denials
|
||||
numHeartbeatDenials = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::handleKeypairChange() {
|
||||
if (_iceServerHeartbeatPacket) {
|
||||
// reset the payload size of the ice-server heartbeat packet - this causes the packet to be re-generated
|
||||
// the next time we go to send an ice-server heartbeat
|
||||
_iceServerHeartbeatPacket->setPayloadSize(0);
|
||||
|
||||
// send a heartbeat to the ice server immediately
|
||||
sendHeartbeatToIceServer();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,6 @@ public slots:
|
|||
void processNodeJSONStatsPacket(QSharedPointer<ReceivedMessage> packetList, SharedNodePointer sendingNode);
|
||||
void processPathQueryPacket(QSharedPointer<ReceivedMessage> packet);
|
||||
void processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
private slots:
|
||||
void aboutToQuit();
|
||||
|
@ -79,16 +78,16 @@ private slots:
|
|||
void handleTempDomainError(QNetworkReply& requestReply);
|
||||
|
||||
void queuedQuit(QString quitMessage, int exitCode);
|
||||
|
||||
void handleKeypairChange();
|
||||
|
||||
private:
|
||||
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
|
||||
bool optionallySetupOAuth();
|
||||
bool optionallyReadX509KeyAndCertificate();
|
||||
bool optionallySetupAssignmentPayment();
|
||||
|
||||
void optionallyGetTemporaryName(const QStringList& arguments);
|
||||
|
||||
bool didSetupAccountManagerWithAccessToken();
|
||||
bool resetAccountManagerAccessToken();
|
||||
|
||||
void setupAutomaticNetworking();
|
||||
|
@ -154,7 +153,6 @@ private:
|
|||
DomainServerSettingsManager _settingsManager;
|
||||
|
||||
HifiSockAddr _iceServerSocket;
|
||||
std::unique_ptr<NLPacket> _iceServerHeartbeatPacket;
|
||||
|
||||
QTimer* _iceHeartbeatTimer { nullptr }; // this looks like it dangles when created but it's parented to the DomainServer
|
||||
|
||||
|
|
|
@ -95,9 +95,9 @@ EntityViewer.setPosition({
|
|||
y: 0,
|
||||
z: 0
|
||||
});
|
||||
EntityViewer.setKeyholeRadius(60000);
|
||||
EntityViewer.setCenterRadius(60000);
|
||||
var octreeQueryInterval = Script.setInterval(function() {
|
||||
EntityViewer.queryOctree();
|
||||
}, 1000);
|
||||
|
||||
Script.update.connect(update);
|
||||
Script.update.connect(update);
|
||||
|
|
|
@ -45,7 +45,7 @@ function debug() { // Display the arguments not just [Object object].
|
|||
//print.apply(null, [].map.call(arguments, JSON.stringify));
|
||||
}
|
||||
|
||||
EntityViewer.setKeyholeRadius(QUERY_RADIUS);
|
||||
EntityViewer.setCenterRadius(QUERY_RADIUS);
|
||||
|
||||
// ENTITY DATA CACHE
|
||||
//
|
||||
|
|
|
@ -171,9 +171,8 @@ var STATE_WAITING_FOR_BUMPER_RELEASE = 15;
|
|||
var COLLIDES_WITH_WHILE_GRABBED = "dynamic,otherAvatar";
|
||||
var COLLIDES_WITH_WHILE_MULTI_GRABBED = "dynamic";
|
||||
|
||||
var HEART_BEAT_INTERVAL = 5; // seconds
|
||||
var HEART_BEAT_TIMEOUT = 15;
|
||||
|
||||
var HEART_BEAT_INTERVAL = 5 * MSECS_PER_SEC;
|
||||
var HEART_BEAT_TIMEOUT = 15 * MSECS_PER_SEC;
|
||||
|
||||
function stateToName(state) {
|
||||
switch (state) {
|
||||
|
|
|
@ -11,6 +11,21 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var leftTriggerValue = 0;
|
||||
var rightTriggerValue = 0;
|
||||
|
||||
var LEAP_TRIGGER_START_ANGLE = 15.0;
|
||||
var LEAP_TRIGGER_END_ANGLE = 40.0;
|
||||
|
||||
function getLeapMotionLeftTrigger() {
|
||||
//print("left trigger = " + leftTriggerValue);
|
||||
return leftTriggerValue;
|
||||
}
|
||||
function getLeapMotionRightTrigger() {
|
||||
//print("right trigger = " + rightTriggerValue);
|
||||
return rightTriggerValue;
|
||||
}
|
||||
|
||||
var leapHands = (function () {
|
||||
|
||||
var isOnHMD,
|
||||
|
@ -61,78 +76,6 @@ var leapHands = (function () {
|
|||
print(i + ": " + jointNames[i]);
|
||||
}
|
||||
print("... skeleton joint names");
|
||||
|
||||
/*
|
||||
http://public.highfidelity.io/models/skeletons/ron_standing.fst
|
||||
Skeleton joint names ...
|
||||
0: Hips
|
||||
1: RightUpLeg
|
||||
2: RightLeg
|
||||
3: RightFoot
|
||||
4: RightToeBase
|
||||
5: RightToe_End
|
||||
6: LeftUpLeg
|
||||
7: LeftLeg
|
||||
8: LeftFoot
|
||||
9: LeftToeBase
|
||||
10: LeftToe_End
|
||||
11: Spine
|
||||
12: Spine1
|
||||
13: Spine2
|
||||
14: RightShoulder
|
||||
15: RightArm
|
||||
16: RightForeArm
|
||||
17: RightHand
|
||||
18: RightHandPinky1
|
||||
19: RightHandPinky2
|
||||
20: RightHandPinky3
|
||||
21: RightHandPinky4
|
||||
22: RightHandRing1
|
||||
23: RightHandRing2
|
||||
24: RightHandRing3
|
||||
25: RightHandRing4
|
||||
26: RightHandMiddle1
|
||||
27: RightHandMiddle2
|
||||
28: RightHandMiddle3
|
||||
29: RightHandMiddle4
|
||||
30: RightHandIndex1
|
||||
31: RightHandIndex2
|
||||
32: RightHandIndex3
|
||||
33: RightHandIndex4
|
||||
34: RightHandThumb1
|
||||
35: RightHandThumb2
|
||||
36: RightHandThumb3
|
||||
37: RightHandThumb4
|
||||
38: LeftShoulder
|
||||
39: LeftArm
|
||||
40: LeftForeArm
|
||||
41: LeftHand
|
||||
42: LeftHandPinky1
|
||||
43: LeftHandPinky2
|
||||
44: LeftHandPinky3
|
||||
45: LeftHandPinky4
|
||||
46: LeftHandRing1
|
||||
47: LeftHandRing2
|
||||
48: LeftHandRing3
|
||||
49: LeftHandRing4
|
||||
50: LeftHandMiddle1
|
||||
51: LeftHandMiddle2
|
||||
52: LeftHandMiddle3
|
||||
53: LeftHandMiddle4
|
||||
54: LeftHandIndex1
|
||||
55: LeftHandIndex2
|
||||
56: LeftHandIndex3
|
||||
57: LeftHandIndex4
|
||||
58: LeftHandThumb1
|
||||
59: LeftHandThumb2
|
||||
60: LeftHandThumb3
|
||||
61: LeftHandThumb4
|
||||
62: Neck
|
||||
63: Head
|
||||
64: HeadTop_End
|
||||
65: body
|
||||
... skeleton joint names
|
||||
*/
|
||||
}
|
||||
|
||||
function animateLeftHand() {
|
||||
|
@ -357,6 +300,13 @@ var leapHands = (function () {
|
|||
settingsTimer = Script.setInterval(checkSettings, 2000);
|
||||
|
||||
calibrationStatus = UNCALIBRATED;
|
||||
|
||||
{
|
||||
var mapping = Controller.newMapping("LeapmotionTrigger");
|
||||
mapping.from(getLeapMotionLeftTrigger).to(Controller.Standard.LT);
|
||||
mapping.from(getLeapMotionRightTrigger).to(Controller.Standard.RT);
|
||||
mapping.enable();
|
||||
}
|
||||
}
|
||||
|
||||
function moveHands() {
|
||||
|
@ -469,10 +419,17 @@ var leapHands = (function () {
|
|||
hands[h].rotation = handRotation;
|
||||
|
||||
// Set finger joints ...
|
||||
var summed = 0;
|
||||
var closeAngle = 0;
|
||||
for (i = 0; i < NUM_FINGERS; i += 1) {
|
||||
for (j = 0; j < NUM_FINGER_JOINTS; j += 1) {
|
||||
if (fingers[h][i][j].controller !== null) {
|
||||
locRotation = fingers[h][i][j].controller.getLocRotation();
|
||||
var eulers = Quat.safeEulerAngles(locRotation);
|
||||
closeAngle += eulers.x;
|
||||
|
||||
summed++;
|
||||
|
||||
if (i === THUMB) {
|
||||
locRotation = {
|
||||
x: side * locRotation.y,
|
||||
|
@ -496,8 +453,21 @@ var leapHands = (function () {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
hands[h].inactiveCount = 0;
|
||||
if (summed > 0) {
|
||||
closeAngle /= summed;
|
||||
}
|
||||
|
||||
var triggerValue = (-closeAngle - LEAP_TRIGGER_START_ANGLE) / (LEAP_TRIGGER_END_ANGLE - LEAP_TRIGGER_START_ANGLE);
|
||||
triggerValue = Math.max(0.0, Math.min(triggerValue, 1.0));
|
||||
|
||||
if (h == 0) {
|
||||
leftTriggerValue = triggerValue;
|
||||
} else {
|
||||
rightTriggerValue = triggerValue;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
|
@ -509,6 +479,8 @@ var leapHands = (function () {
|
|||
if (handAnimationStateHandlers[h] !== null) {
|
||||
MyAvatar.removeAnimationStateHandler(handAnimationStateHandlers[h]);
|
||||
handAnimationStateHandlers[h] = null;
|
||||
leftTriggerValue = 0.0;
|
||||
rightTriggerValue = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,17 +12,23 @@
|
|||
|
||||
var APPARENT_2D_OVERLAY_DEPTH = 1.0;
|
||||
var APPARENT_MAXIMUM_DEPTH = 100.0; // this is a depth at which things all seem sufficiently distant
|
||||
var lastDepthCheckTime = 0;
|
||||
var lastDepthCheckTime = Date.now();
|
||||
var desiredDepth = APPARENT_2D_OVERLAY_DEPTH;
|
||||
var TIME_BETWEEN_DEPTH_CHECKS = 100;
|
||||
var MINIMUM_DEPTH_ADJUST = 0.01;
|
||||
var NON_LINEAR_DIVISOR = 2;
|
||||
|
||||
Script.update.connect(function(deltaTime) {
|
||||
var TIME_BETWEEN_DEPTH_CHECKS = 100;
|
||||
var timeSinceLastDepthCheck = Date.now() - lastDepthCheckTime;
|
||||
var now = Date.now();
|
||||
var timeSinceLastDepthCheck = now - lastDepthCheckTime;
|
||||
if (timeSinceLastDepthCheck > TIME_BETWEEN_DEPTH_CHECKS) {
|
||||
var newDesiredDepth = desiredDepth;
|
||||
lastDepthCheckTime = now;
|
||||
var reticlePosition = Reticle.position;
|
||||
|
||||
// first check the 2D Overlays
|
||||
if (Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(reticlePosition)) {
|
||||
Reticle.setDepth(APPARENT_2D_OVERLAY_DEPTH);
|
||||
newDesiredDepth = APPARENT_2D_OVERLAY_DEPTH;
|
||||
} else {
|
||||
var pickRay = Camera.computePickRay(reticlePosition.x, reticlePosition.y);
|
||||
|
||||
|
@ -37,11 +43,30 @@ Script.update.connect(function(deltaTime) {
|
|||
// If either the overlays or entities intersect, then set the reticle depth to
|
||||
// the distance of intersection
|
||||
if (result.intersects) {
|
||||
Reticle.setDepth(result.distance);
|
||||
newDesiredDepth = result.distance;
|
||||
} else {
|
||||
// if nothing intersects... set the depth to some sufficiently large depth
|
||||
Reticle.setDepth(APPARENT_MAXIMUM_DEPTH);
|
||||
newDesiredDepth = APPARENT_MAXIMUM_DEPTH;
|
||||
}
|
||||
}
|
||||
|
||||
// If the desired depth has changed, reset our fade start time
|
||||
if (desiredDepth != newDesiredDepth) {
|
||||
desiredDepth = newDesiredDepth;
|
||||
}
|
||||
}
|
||||
|
||||
// move the reticle toward the desired depth
|
||||
if (desiredDepth != Reticle.depth) {
|
||||
|
||||
// cut distance between desiredDepth and current depth in half until we're close enough
|
||||
var distanceToAdjustThisCycle = (desiredDepth - Reticle.depth) / NON_LINEAR_DIVISOR;
|
||||
if (Math.abs(distanceToAdjustThisCycle) < MINIMUM_DEPTH_ADJUST) {
|
||||
newDepth = desiredDepth;
|
||||
} else {
|
||||
newDepth = Reticle.depth + distanceToAdjustThisCycle;
|
||||
}
|
||||
|
||||
Reticle.setDepth(newDepth);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -17,8 +17,8 @@ var SIZE = 10.0;
|
|||
var SEPARATION = 20.0;
|
||||
var ROWS_X = 30;
|
||||
var ROWS_Z = 30;
|
||||
var TYPE = "Sphere"; // Right now this can be "Box" or "Model" or "Sphere"
|
||||
var MODEL_URL = "https://hifi-public.s3.amazonaws.com/models/props/LowPolyIsland/CypressTreeGroup.fbx";
|
||||
var TYPE = "Model"; // Right now this can be "Box" or "Model" or "Sphere"
|
||||
var MODEL_URL = "http://hifi-content.s3.amazonaws.com/DomainContent/CellScience/Instances/vesicle.fbx";
|
||||
var MODEL_DIMENSION = { x: 33, y: 16, z: 49 };
|
||||
var RATE_PER_SECOND = 1000; // The entity server will drop data if we create things too fast.
|
||||
var SCRIPT_INTERVAL = 100;
|
||||
|
@ -38,6 +38,7 @@ Script.setInterval(function () {
|
|||
var numToCreate = RATE_PER_SECOND * (SCRIPT_INTERVAL / 1000.0);
|
||||
for (var i = 0; i < numToCreate; i++) {
|
||||
var position = { x: SIZE + (x * SEPARATION), y: SIZE, z: SIZE + (z * SEPARATION) };
|
||||
print('position:'+JSON.stringify(position))
|
||||
if (TYPE == "Model") {
|
||||
Entities.addEntity({
|
||||
type: TYPE,
|
||||
|
|
|
@ -72,10 +72,13 @@
|
|||
};
|
||||
}
|
||||
|
||||
function createEmitNumberPropertyUpdateFunction(propertyName) {
|
||||
function createEmitNumberPropertyUpdateFunction(propertyName, decimals) {
|
||||
decimals = decimals == undefined ? 4 : decimals;
|
||||
return function() {
|
||||
var value = parseFloat(this.value).toFixed(decimals);
|
||||
|
||||
EventBridge.emitWebEvent(
|
||||
'{ "type":"update", "properties":{"' + propertyName + '":' + parseFloat(this.value).toFixed(4) + '}}'
|
||||
'{ "type":"update", "properties":{"' + propertyName + '":' + value + '}}'
|
||||
);
|
||||
};
|
||||
}
|
||||
|
@ -323,6 +326,7 @@
|
|||
var elLightColorBlue = document.getElementById("property-light-color-blue");
|
||||
|
||||
var elLightIntensity = document.getElementById("property-light-intensity");
|
||||
var elLightFalloffRadius = document.getElementById("property-light-falloff-radius");
|
||||
var elLightExponent = document.getElementById("property-light-exponent");
|
||||
var elLightCutoff = document.getElementById("property-light-cutoff");
|
||||
|
||||
|
@ -604,9 +608,10 @@
|
|||
elLightColorGreen.value = properties.color.green;
|
||||
elLightColorBlue.value = properties.color.blue;
|
||||
|
||||
elLightIntensity.value = properties.intensity;
|
||||
elLightExponent.value = properties.exponent;
|
||||
elLightCutoff.value = properties.cutoff;
|
||||
elLightIntensity.value = properties.intensity.toFixed(1);
|
||||
elLightFalloffRadius.value = properties.falloffRadius.toFixed(1);
|
||||
elLightExponent.value = properties.exponent.toFixed(2);
|
||||
elLightCutoff.value = properties.cutoff.toFixed(2);
|
||||
} else if (properties.type == "Zone") {
|
||||
for (var i = 0; i < elZoneSections.length; i++) {
|
||||
elZoneSections[i].style.display = 'block';
|
||||
|
@ -795,9 +800,10 @@
|
|||
}
|
||||
})
|
||||
|
||||
elLightIntensity.addEventListener('change', createEmitNumberPropertyUpdateFunction('intensity'));
|
||||
elLightExponent.addEventListener('change', createEmitNumberPropertyUpdateFunction('exponent'));
|
||||
elLightCutoff.addEventListener('change', createEmitNumberPropertyUpdateFunction('cutoff'));
|
||||
elLightIntensity.addEventListener('change', createEmitNumberPropertyUpdateFunction('intensity', 1));
|
||||
elLightFalloffRadius.addEventListener('change', createEmitNumberPropertyUpdateFunction('falloffRadius', 1));
|
||||
elLightExponent.addEventListener('change', createEmitNumberPropertyUpdateFunction('exponent', 2));
|
||||
elLightCutoff.addEventListener('change', createEmitNumberPropertyUpdateFunction('cutoff', 2));
|
||||
|
||||
elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl'));
|
||||
|
||||
|
@ -1043,25 +1049,25 @@
|
|||
<div class="text-section property">
|
||||
<div class="label">Line Height</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-text-line-height" min="0" step="0.005">
|
||||
<input class="coord" type="number" id="property-text-line-height" min="0" step="0.005">
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-section property">
|
||||
<div class="label">Text Color</div>
|
||||
<div class="value">
|
||||
<div class='color-picker' id="property-text-text-color"></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-text-text-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-text-text-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-text-text-color-blue"></div>
|
||||
<div class="input-area">R <input class="coord" type="number" id="property-text-text-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type="number" id="property-text-text-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type="number" id="property-text-text-color-blue"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-section property">
|
||||
<div class="label">Background Color</div>
|
||||
<div class="value">
|
||||
<div class='color-picker' id="property-text-background-color"></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-text-background-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-text-background-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-text-background-color-blue"></div>
|
||||
<div class="input-area">R <input class="coord" type="number" id="property-text-background-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type="number" id="property-text-background-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type="number" id="property-text-background-color-blue"></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1085,32 +1091,32 @@
|
|||
<div class="label">Light Color</div>
|
||||
<div class="value">
|
||||
<div class='color-picker' id="property-zone-key-light-color"></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-zone-key-light-color-red" min="0" max="255" step="1"></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-zone-key-light-color-green" min="0" max="255" step="1"></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-zone-key-light-color-blue" min="0" max="255" step="1"></div>
|
||||
<div class="input-area">R <input class="coord" type="number" id="property-zone-key-light-color-red" min="0" max="255" step="1"></div>
|
||||
<div class="input-area">G <input class="coord" type="number" id="property-zone-key-light-color-green" min="0" max="255" step="1"></div>
|
||||
<div class="input-area">B <input class="coord" type="number" id="property-zone-key-light-color-blue" min="0" max="255" step="1"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="zone-section keyLight-section property">
|
||||
<div class="label">Light Intensity</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-key-intensity" min="0" max="10" step="0.1">
|
||||
<input class="coord" type="number" id="property-zone-key-intensity" min="0" max="10" step="0.1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="zone-section keyLight-section property">
|
||||
<div class="label">Light Direction</div>
|
||||
<div class="value">
|
||||
<div class="input-area">Pitch <input class="coord" type='number' id="property-zone-key-light-direction-x"></div>
|
||||
<div class="input-area">Yaw <input class="coord" type='number' id="property-zone-key-light-direction-y"></div>
|
||||
<div class="input-area">Roll <input class="coord" type='number' id="property-zone-key-light-direction-z"></div>
|
||||
<div class="input-area">Pitch <input class="coord" type="number" id="property-zone-key-light-direction-x"></div>
|
||||
<div class="input-area">Yaw <input class="coord" type="number" id="property-zone-key-light-direction-y"></div>
|
||||
<div class="input-area">Roll <input class="coord" type="number" id="property-zone-key-light-direction-z"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="zone-section keyLight-section property">
|
||||
<div class="label">Ambient Intensity</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-key-ambient-intensity" min="0" max="10" step="0.1">
|
||||
<input class="coord" type="number" id="property-zone-key-ambient-intensity" min="0" max="10" step="0.1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1129,19 +1135,19 @@
|
|||
<div class="zone-section stage-section property">
|
||||
<div class="label">Stage Latitude</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-stage-latitude" min="-90" max="90" step="1">
|
||||
<input class="coord" type="number" id="property-zone-stage-latitude" min="-90" max="90" step="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section stage-section property">
|
||||
<div class="label">Stage Longitude</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-stage-longitude" min="-180" max="180" step="1">
|
||||
<input class="coord" type="number" id="property-zone-stage-longitude" min="-180" max="180" step="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section stage-section property">
|
||||
<div class="label">Stage Altitude</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-stage-altitude" step="1">
|
||||
<input class="coord" type="number" id="property-zone-stage-altitude" step="1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1155,13 +1161,13 @@
|
|||
<div class="zone-section stage-section property">
|
||||
<div class="label">Stage Day</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-stage-day" min="0" max="365" step="1">
|
||||
<input class="coord" type="number" id="property-zone-stage-day" min="0" max="365" step="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section stage-section property">
|
||||
<div class="label">Stage Hour</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-zone-stage-hour" min="0" max="24" step="0.5">
|
||||
<input class="coord" type="number" id="property-zone-stage-hour" min="0" max="24" step="0.5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1186,9 +1192,9 @@
|
|||
<div class="label">Skybox Color</div>
|
||||
<div class="value">
|
||||
<div class='color-picker' id="property-zone-skybox-color"></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-zone-skybox-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-zone-skybox-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-zone-skybox-color-blue"></div>
|
||||
<div class="input-area">R <input class="coord" type="number" id="property-zone-skybox-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type="number" id="property-zone-skybox-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type="number" id="property-zone-skybox-color-blue"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1264,23 +1270,23 @@
|
|||
<div class="property">
|
||||
<div class="label">Registration</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-reg-x"><div class="prop-x"></div></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-reg-y"><div class="prop-y"></div></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-reg-z"><div class="prop-z"></div></div>
|
||||
<div class="input-area">X <input class="coord" type="number" id="property-reg-x"><div class="prop-x"></div></div>
|
||||
<div class="input-area">Y <input class="coord" type="number" id="property-reg-y"><div class="prop-y"></div></div>
|
||||
<div class="input-area">Z <input class="coord" type="number" id="property-reg-z"><div class="prop-z"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<div class="label">Dimensions</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-dim-x" step="0.1"><div class="prop-x"></div></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-dim-y" step="0.1"><div class="prop-y"></div></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-dim-z" step="0.1"><div class="prop-z"></div></div>
|
||||
<div class="input-area">X <input class="coord" type="number" id="property-dim-x" step="0.1"><div class="prop-x"></div></div>
|
||||
<div class="input-area">Y <input class="coord" type="number" id="property-dim-y" step="0.1"><div class="prop-y"></div></div>
|
||||
<div class="input-area">Z <input class="coord" type="number" id="property-dim-z" step="0.1"><div class="prop-z"></div></div>
|
||||
<div>
|
||||
<input type="button" id="reset-to-natural-dimensions" value="Reset to Natural Dimensions">
|
||||
</div>
|
||||
<div class="input-area">
|
||||
<input class="" type='number' id="dimension-rescale-pct" value=100>%
|
||||
<input class="" type="number" id="dimension-rescale-pct" value=100>%
|
||||
</div>
|
||||
<span>
|
||||
<input type="button" id="dimension-rescale-button" value="Rescale">
|
||||
|
@ -1291,9 +1297,9 @@
|
|||
<div class="poly-vox-section property">
|
||||
<div class="label">Voxel Volume Size</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <br> <input class="coord" type='number' id="property-voxel-volume-size-x"></div>
|
||||
<div class="input-area">Y <br><input class="coord" type='number' id="property-voxel-volume-size-y"></div>
|
||||
<div class="input-area">Z <br><input class="coord" type='number' id="property-voxel-volume-size-z"></div>
|
||||
<div class="input-area">X <br> <input class="coord" type="number" id="property-voxel-volume-size-x"></div>
|
||||
<div class="input-area">Y <br><input class="coord" type="number" id="property-voxel-volume-size-y"></div>
|
||||
<div class="input-area">Z <br><input class="coord" type="number" id="property-voxel-volume-size-z"></div>
|
||||
</div>
|
||||
|
||||
<div class="label">Surface Extractor</div>
|
||||
|
@ -1325,9 +1331,9 @@
|
|||
<div class="property">
|
||||
<div class="label">Rotation</div>
|
||||
<div class="value">
|
||||
<div class="input-area">Pitch <input class="coord" type='number' id="property-rot-x" step="0.1"></div>
|
||||
<div class="input-area">Yaw <input class="coord" type='number' id="property-rot-y" step="0.1"></div>
|
||||
<div class="input-area">Roll <input class="coord" type='number' id="property-rot-z"step="0.1"></div>
|
||||
<div class="input-area">Pitch <input class="coord" type="number" id="property-rot-x" step="0.1"></div>
|
||||
<div class="input-area">Yaw <input class="coord" type="number" id="property-rot-y" step="0.1"></div>
|
||||
<div class="input-area">Roll <input class="coord" type="number" id="property-rot-z"step="0.1"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1339,66 +1345,66 @@
|
|||
<div class="property">
|
||||
<div class="label">Linear Velocity</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-lvel-x"><div class="prop-x"></div></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-lvel-y"><div class="prop-y"></div></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-lvel-z"><div class="prop-z"></div></div>
|
||||
<div class="input-area">X <input class="coord" type="number" id="property-lvel-x"><div class="prop-x"></div></div>
|
||||
<div class="input-area">Y <input class="coord" type="number" id="property-lvel-y"><div class="prop-y"></div></div>
|
||||
<div class="input-area">Z <input class="coord" type="number" id="property-lvel-z"><div class="prop-z"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="label">Linear Damping</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-ldamping">
|
||||
<input class="coord" type="number" id="property-ldamping">
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="label">Angular Velocity</div>
|
||||
<div class="value">
|
||||
<div class="input-area">Pitch <input class="coord" type='number' id="property-avel-x"></div>
|
||||
<div class="input-area">Yaw <input class="coord" type='number' id="property-avel-y"></div>
|
||||
<div class="input-area">Roll <input class="coord" type='number' id="property-avel-z"></div>
|
||||
<div class="input-area">Pitch <input class="coord" type="number" id="property-avel-x"></div>
|
||||
<div class="input-area">Yaw <input class="coord" type="number" id="property-avel-y"></div>
|
||||
<div class="input-area">Roll <input class="coord" type="number" id="property-avel-z"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="label">Angular Damping</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-adamping">
|
||||
<input class="coord" type="number" id="property-adamping">
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="label">Restitution</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-restitution">
|
||||
<input class="coord" type="number" id="property-restitution">
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="label">Friction</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-friction">
|
||||
<input class="coord" type="number" id="property-friction">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<div class="label">Gravity</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-grav-x"><div class="prop-x"></div></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-grav-y"><div class="prop-y"></div></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-grav-z"><div class="prop-z"></div></div>
|
||||
<div class="input-area">X <input class="coord" type="number" id="property-grav-x"><div class="prop-x"></div></div>
|
||||
<div class="input-area">Y <input class="coord" type="number" id="property-grav-y"><div class="prop-y"></div></div>
|
||||
<div class="input-area">Z <input class="coord" type="number" id="property-grav-z"><div class="prop-z"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<div class="label">Acceleration</div>
|
||||
<div class="value">
|
||||
<div class="input-area">X <input class="coord" type='number' id="property-lacc-x"><div class="prop-x"></div></div>
|
||||
<div class="input-area">Y <input class="coord" type='number' id="property-lacc-y"><div class="prop-y"></div></div>
|
||||
<div class="input-area">Z <input class="coord" type='number' id="property-lacc-z"><div class="prop-z"></div></div>
|
||||
<div class="input-area">X <input class="coord" type="number" id="property-lacc-x"><div class="prop-x"></div></div>
|
||||
<div class="input-area">Y <input class="coord" type="number" id="property-lacc-y"><div class="prop-y"></div></div>
|
||||
<div class="input-area">Z <input class="coord" type="number" id="property-lacc-z"><div class="prop-z"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<div class="label">Density</div>
|
||||
<div>
|
||||
<input type='number' id="property-density">
|
||||
<input type="number" id="property-density">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1406,9 +1412,9 @@
|
|||
<div class="label">Color</div>
|
||||
<div class="value">
|
||||
<div id="property-color" class='color-picker'></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-color-blue"></div>
|
||||
<div class="input-area">R <input class="coord" type="number" id="property-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type="number" id="property-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type="number" id="property-color-blue"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1483,7 +1489,7 @@
|
|||
<div class="property">
|
||||
<div class="label">Lifetime</div>
|
||||
<div class="value">
|
||||
<input type='number' id="property-lifetime">
|
||||
<input type="number" id="property-lifetime">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1541,25 +1547,25 @@
|
|||
<div class="model-section property">
|
||||
<div class="label">Animation FPS</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-model-animation-fps">
|
||||
<input class="coord" type="number" id="property-model-animation-fps">
|
||||
</div>
|
||||
</div>
|
||||
<div class="model-section property">
|
||||
<div class="label">Animation Frame</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-model-animation-frame">
|
||||
<input class="coord" type="number" id="property-model-animation-frame">
|
||||
</div>
|
||||
</div>
|
||||
<div class="model-section property">
|
||||
<div class="label">Animation First Frame</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-model-animation-first-frame">
|
||||
<input class="coord" type="number" id="property-model-animation-first-frame">
|
||||
</div>
|
||||
</div>
|
||||
<div class="model-section property">
|
||||
<div class="label">Animation Last Frame</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-model-animation-last-frame">
|
||||
<input class="coord" type="number" id="property-model-animation-last-frame">
|
||||
</div>
|
||||
</div>
|
||||
<div class="model-section property">
|
||||
|
@ -1591,37 +1597,43 @@
|
|||
<label>Light</label>
|
||||
</div>
|
||||
|
||||
<div class="light-section property">
|
||||
<div class="label">Color</div>
|
||||
<div class="value">
|
||||
<div class='color-picker' id="property-light-color"></div>
|
||||
<div class="input-area">R <input class="coord" type="number" id="property-light-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type="number" id="property-light-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type="number" id="property-light-color-blue"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="light-section property">
|
||||
<div class="label">Intensity</div>
|
||||
<div class="value">
|
||||
<input class="coord" type="number" id="property-light-intensity" min="0" step="0.1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="light-section property">
|
||||
<div class="label">Falloff Radius</div>
|
||||
<div class="value">
|
||||
<input class="coord" type="number" id="property-light-falloff-radius" min="0" step="0.1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="light-section property">
|
||||
<span class="label">Spot Light</span>
|
||||
<span class="value">
|
||||
<input type='checkbox' id="property-light-spot-light">
|
||||
</span>
|
||||
</div>
|
||||
<div class="light-section property">
|
||||
<div class="label">Color</div>
|
||||
<div class="value">
|
||||
<div class='color-picker' id="property-light-color"></div>
|
||||
<div class="input-area">R <input class="coord" type='number' id="property-light-color-red"></div>
|
||||
<div class="input-area">G <input class="coord" type='number' id="property-light-color-green"></div>
|
||||
<div class="input-area">B <input class="coord" type='number' id="property-light-color-blue"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="light-section property">
|
||||
<div class="label">Intensity</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-light-intensity">
|
||||
</div>
|
||||
</div>
|
||||
<div class="light-section property">
|
||||
<div class="label">Spot Light Exponent</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-light-exponent">
|
||||
<input class="coord" type="number" id="property-light-exponent" step="0.01">
|
||||
</div>
|
||||
</div>
|
||||
<div class="light-section property">
|
||||
<div class="label">Spot Light Cutoff (degrees)</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-light-cutoff">
|
||||
<input class="coord" type="number" id="property-light-cutoff" step="0.01">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -6,17 +6,3 @@ setup_hifi_project(Network)
|
|||
# link the shared hifi libraries
|
||||
link_hifi_libraries(embedded-webserver networking shared)
|
||||
package_libraries_for_deployment()
|
||||
|
||||
# find OpenSSL
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
if (APPLE AND ${OPENSSL_INCLUDE_DIR} STREQUAL "/usr/include")
|
||||
# this is a user on OS X using system OpenSSL, which is going to throw warnings since they're deprecating for their common crypto
|
||||
message(WARNING "The found version of OpenSSL is the OS X system version. This will produce deprecation warnings."
|
||||
"\nWe recommend you install a newer version (at least 1.0.1h) in a different directory and set OPENSSL_ROOT_DIR in your env so Cmake can find it.")
|
||||
endif ()
|
||||
|
||||
include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}")
|
||||
|
||||
# append OpenSSL to our list of libraries to link
|
||||
target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES})
|
||||
|
|
|
@ -9,21 +9,14 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "IceServer.h"
|
||||
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QTimer>
|
||||
|
||||
#include <LimitedNodeList.h>
|
||||
#include <NetworkingConstants.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "IceServer.h"
|
||||
|
||||
const int CLEAR_INACTIVE_PEERS_INTERVAL_MSECS = 1 * 1000;
|
||||
const int PEER_SILENCE_THRESHOLD_MSECS = 5 * 1000;
|
||||
|
||||
|
@ -52,6 +45,7 @@ IceServer::IceServer(int argc, char* argv[]) :
|
|||
QTimer* inactivePeerTimer = new QTimer(this);
|
||||
connect(inactivePeerTimer, &QTimer::timeout, this, &IceServer::clearInactivePeers);
|
||||
inactivePeerTimer->start(CLEAR_INACTIVE_PEERS_INTERVAL_MSECS);
|
||||
|
||||
}
|
||||
|
||||
bool IceServer::packetVersionMatch(const udt::Packet& packet) {
|
||||
|
@ -76,14 +70,9 @@ void IceServer::processPacket(std::unique_ptr<udt::Packet> packet) {
|
|||
|
||||
if (nlPacket->getType() == PacketType::ICEServerHeartbeat) {
|
||||
SharedNetworkPeer peer = addOrUpdateHeartbeatingPeer(*nlPacket);
|
||||
if (peer) {
|
||||
// so that we can send packets to the heartbeating peer when we need, we need to activate a socket now
|
||||
peer->activateMatchingOrNewSymmetricSocket(nlPacket->getSenderSockAddr());
|
||||
} else {
|
||||
// we couldn't verify this peer - respond back to them so they know they may need to perform keypair re-generation
|
||||
static auto deniedPacket = NLPacket::create(PacketType::ICEServerHeartbeatDenied);
|
||||
_serverSocket.writePacket(*deniedPacket, nlPacket->getSenderSockAddr());
|
||||
}
|
||||
|
||||
// so that we can send packets to the heartbeating peer when we need, we need to activate a socket now
|
||||
peer->activateMatchingOrNewSymmetricSocket(nlPacket->getSenderSockAddr());
|
||||
} else if (nlPacket->getType() == PacketType::ICEServerQuery) {
|
||||
QDataStream heartbeatStream(nlPacket.get());
|
||||
|
||||
|
@ -125,135 +114,31 @@ SharedNetworkPeer IceServer::addOrUpdateHeartbeatingPeer(NLPacket& packet) {
|
|||
// pull the UUID, public and private sock addrs for this peer
|
||||
QUuid senderUUID;
|
||||
HifiSockAddr publicSocket, localSocket;
|
||||
QByteArray signature;
|
||||
|
||||
QDataStream heartbeatStream(&packet);
|
||||
heartbeatStream >> senderUUID >> publicSocket >> localSocket;
|
||||
|
||||
heartbeatStream >> senderUUID;
|
||||
heartbeatStream >> publicSocket >> localSocket;
|
||||
|
||||
auto signedPlaintext = QByteArray::fromRawData(packet.getPayload(), heartbeatStream.device()->pos());
|
||||
heartbeatStream >> signature;
|
||||
// make sure we have this sender in our peer hash
|
||||
SharedNetworkPeer matchingPeer = _activePeers.value(senderUUID);
|
||||
|
||||
// make sure this is a verified heartbeat before performing any more processing
|
||||
if (isVerifiedHeartbeat(senderUUID, signedPlaintext, signature)) {
|
||||
// make sure we have this sender in our peer hash
|
||||
SharedNetworkPeer matchingPeer = _activePeers.value(senderUUID);
|
||||
if (!matchingPeer) {
|
||||
// if we don't have this sender we need to create them now
|
||||
matchingPeer = QSharedPointer<NetworkPeer>::create(senderUUID, publicSocket, localSocket);
|
||||
_activePeers.insert(senderUUID, matchingPeer);
|
||||
|
||||
if (!matchingPeer) {
|
||||
// if we don't have this sender we need to create them now
|
||||
matchingPeer = QSharedPointer<NetworkPeer>::create(senderUUID, publicSocket, localSocket);
|
||||
_activePeers.insert(senderUUID, matchingPeer);
|
||||
|
||||
qDebug() << "Added a new network peer" << *matchingPeer;
|
||||
} else {
|
||||
// we already had the peer so just potentially update their sockets
|
||||
matchingPeer->setPublicSocket(publicSocket);
|
||||
matchingPeer->setLocalSocket(localSocket);
|
||||
}
|
||||
|
||||
// update our last heard microstamp for this network peer to now
|
||||
matchingPeer->setLastHeardMicrostamp(usecTimestampNow());
|
||||
|
||||
return matchingPeer;
|
||||
qDebug() << "Added a new network peer" << *matchingPeer;
|
||||
} else {
|
||||
// not verified, return the empty peer object
|
||||
return SharedNetworkPeer();
|
||||
}
|
||||
}
|
||||
|
||||
bool IceServer::isVerifiedHeartbeat(const QUuid& domainID, const QByteArray& plaintext, const QByteArray& signature) {
|
||||
// check if we have a private key for this domain ID - if we do not then fire off the request for it
|
||||
auto it = _domainPublicKeys.find(domainID);
|
||||
if (it != _domainPublicKeys.end()) {
|
||||
|
||||
// attempt to verify the signature for this heartbeat
|
||||
const unsigned char* publicKeyData = reinterpret_cast<const unsigned char*>(it->second.constData());
|
||||
|
||||
// first load up the public key into an RSA struct
|
||||
RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, it->second.size());
|
||||
|
||||
if (rsaPublicKey) {
|
||||
auto hashedPlaintext = QCryptographicHash::hash(plaintext, QCryptographicHash::Sha256);
|
||||
int verificationResult = RSA_verify(NID_sha256,
|
||||
reinterpret_cast<const unsigned char*>(hashedPlaintext.constData()),
|
||||
hashedPlaintext.size(),
|
||||
reinterpret_cast<const unsigned char*>(signature.constData()),
|
||||
signature.size(),
|
||||
rsaPublicKey);
|
||||
|
||||
// free up the public key and remove connection token before we return
|
||||
RSA_free(rsaPublicKey);
|
||||
|
||||
if (verificationResult == 1) {
|
||||
// this is the only success case - we return true here to indicate that the heartbeat is verified
|
||||
return true;
|
||||
} else {
|
||||
qDebug() << "Failed to verify heartbeat for" << domainID << "- re-requesting public key from API.";
|
||||
}
|
||||
|
||||
} else {
|
||||
// we can't let this user in since we couldn't convert their public key to an RSA key we could use
|
||||
qWarning() << "Could not convert in-memory public key for" << domainID << "to usable RSA public key.";
|
||||
qWarning() << "Re-requesting public key from API";
|
||||
}
|
||||
// we already had the peer so just potentially update their sockets
|
||||
matchingPeer->setPublicSocket(publicSocket);
|
||||
matchingPeer->setLocalSocket(localSocket);
|
||||
}
|
||||
|
||||
// we could not verify this heartbeat (missing public key, could not load public key, bad actor)
|
||||
// ask the metaverse API for the right public key and return false to indicate that this is not verified
|
||||
requestDomainPublicKey(domainID);
|
||||
// update our last heard microstamp for this network peer to now
|
||||
matchingPeer->setLastHeardMicrostamp(usecTimestampNow());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void IceServer::requestDomainPublicKey(const QUuid& domainID) {
|
||||
// send a request to the metaverse API for the public key for this domain
|
||||
QNetworkAccessManager* manager = new QNetworkAccessManager { this };
|
||||
connect(manager, &QNetworkAccessManager::finished, this, &IceServer::publicKeyReplyFinished);
|
||||
|
||||
QUrl publicKeyURL { NetworkingConstants::METAVERSE_SERVER_URL };
|
||||
QString publicKeyPath = QString("/api/v1/domains/%1/public_key").arg(uuidStringWithoutCurlyBraces(domainID));
|
||||
publicKeyURL.setPath(publicKeyPath);
|
||||
|
||||
QNetworkRequest publicKeyRequest { publicKeyURL };
|
||||
publicKeyRequest.setAttribute(QNetworkRequest::User, domainID);
|
||||
|
||||
qDebug() << "Requesting public key for domain with ID" << domainID;
|
||||
|
||||
manager->get(publicKeyRequest);
|
||||
}
|
||||
|
||||
void IceServer::publicKeyReplyFinished(QNetworkReply* reply) {
|
||||
// get the domain ID from the QNetworkReply attribute
|
||||
QUuid domainID = reply->request().attribute(QNetworkRequest::User).toUuid();
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
// pull out the public key and store it for this domain
|
||||
|
||||
// the response should be JSON
|
||||
QJsonDocument responseDocument = QJsonDocument::fromJson(reply->readAll());
|
||||
|
||||
static const QString DATA_KEY = "data";
|
||||
static const QString PUBLIC_KEY_KEY = "public_key";
|
||||
static const QString STATUS_KEY = "status";
|
||||
static const QString SUCCESS_VALUE = "success";
|
||||
|
||||
auto responseObject = responseDocument.object();
|
||||
if (responseObject[STATUS_KEY].toString() == SUCCESS_VALUE) {
|
||||
auto dataObject = responseObject[DATA_KEY].toObject();
|
||||
if (dataObject.contains(PUBLIC_KEY_KEY)) {
|
||||
_domainPublicKeys[domainID] = QByteArray::fromBase64(dataObject[PUBLIC_KEY_KEY].toString().toUtf8());
|
||||
} else {
|
||||
qWarning() << "There was no public key present in response for domain with ID" << domainID;
|
||||
}
|
||||
} else {
|
||||
qWarning() << "The metaverse API did not return success for public key request for domain with ID" << domainID;
|
||||
}
|
||||
|
||||
} else {
|
||||
// there was a problem getting the public key for the domain
|
||||
// log it since it will be re-requested on the next heartbeat
|
||||
|
||||
qWarning() << "Error retreiving public key for domain with ID" << domainID << "-" << reply->errorString();
|
||||
}
|
||||
return matchingPeer;
|
||||
}
|
||||
|
||||
void IceServer::sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr) {
|
||||
|
|
|
@ -16,15 +16,13 @@
|
|||
#include <QtCore/QSharedPointer>
|
||||
#include <QUdpSocket>
|
||||
|
||||
#include <UUIDHasher.h>
|
||||
|
||||
#include <NetworkPeer.h>
|
||||
#include <HTTPConnection.h>
|
||||
#include <HTTPManager.h>
|
||||
#include <NLPacket.h>
|
||||
#include <udt/Socket.h>
|
||||
|
||||
class QNetworkReply;
|
||||
typedef QHash<QUuid, SharedNetworkPeer> NetworkPeerHash;
|
||||
|
||||
class IceServer : public QCoreApplication, public HTTPRequestHandler {
|
||||
Q_OBJECT
|
||||
|
@ -33,7 +31,6 @@ public:
|
|||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
|
||||
private slots:
|
||||
void clearInactivePeers();
|
||||
void publicKeyReplyFinished(QNetworkReply* reply);
|
||||
private:
|
||||
bool packetVersionMatch(const udt::Packet& packet);
|
||||
void processPacket(std::unique_ptr<udt::Packet> packet);
|
||||
|
@ -41,19 +38,10 @@ private:
|
|||
SharedNetworkPeer addOrUpdateHeartbeatingPeer(NLPacket& incomingPacket);
|
||||
void sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr);
|
||||
|
||||
bool isVerifiedHeartbeat(const QUuid& domainID, const QByteArray& plaintext, const QByteArray& signature);
|
||||
void requestDomainPublicKey(const QUuid& domainID);
|
||||
|
||||
QUuid _id;
|
||||
udt::Socket _serverSocket;
|
||||
|
||||
using NetworkPeerHash = QHash<QUuid, SharedNetworkPeer>;
|
||||
NetworkPeerHash _activePeers;
|
||||
|
||||
HTTPManager _httpManager;
|
||||
|
||||
using DomainPublicKeyHash = std::unordered_map<QUuid, QByteArray>;
|
||||
DomainPublicKeyHash _domainPublicKeys;
|
||||
};
|
||||
|
||||
#endif // hifi_IceServer_h
|
||||
|
|
|
@ -210,8 +210,6 @@ static const QString INFO_EDIT_ENTITIES_PATH = "html/edit-commands.html";
|
|||
|
||||
static const unsigned int THROTTLED_SIM_FRAMERATE = 15;
|
||||
static const int THROTTLED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / THROTTLED_SIM_FRAMERATE;
|
||||
static const unsigned int CAPPED_SIM_FRAMERATE = 120;
|
||||
static const int CAPPED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / CAPPED_SIM_FRAMERATE;
|
||||
|
||||
static const uint32_t INVALID_FRAME = UINT32_MAX;
|
||||
|
||||
|
@ -425,11 +423,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
_maxOctreePPS(maxOctreePacketsPerSecond.get()),
|
||||
_lastFaceTrackerUpdate(0)
|
||||
{
|
||||
// FIXME this may be excessivly conservative. On the other hand
|
||||
// FIXME this may be excessivly conservative. On the other hand
|
||||
// maybe I'm used to having an 8-core machine
|
||||
// Perhaps find the ideal thread count and subtract 2 or 3
|
||||
// Perhaps find the ideal thread count and subtract 2 or 3
|
||||
// (main thread, present thread, random OS load)
|
||||
// More threads == faster concurrent loads, but also more concurrent
|
||||
// More threads == faster concurrent loads, but also more concurrent
|
||||
// load on the GPU until we can serialize GPU transfers (off the main thread)
|
||||
QThreadPool::globalInstance()->setMaxThreadCount(2);
|
||||
thread()->setPriority(QThread::HighPriority);
|
||||
|
@ -553,13 +551,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle()));
|
||||
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle()));
|
||||
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails()));
|
||||
connect(&domainHandler, &DomainHandler::settingsReceived, this, &Application::domainSettingsReceived);
|
||||
|
||||
// update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one
|
||||
const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000;
|
||||
|
||||
auto discoverabilityManager = DependencyManager::get<DiscoverabilityManager>();
|
||||
connect(&locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation);
|
||||
connect(&locationUpdateTimer, &QTimer::timeout,
|
||||
connect(&locationUpdateTimer, &QTimer::timeout,
|
||||
DependencyManager::get<AddressManager>().data(), &AddressManager::storeCurrentAddress);
|
||||
locationUpdateTimer.start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS);
|
||||
|
||||
|
@ -589,7 +588,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
connect(&accountManager, &AccountManager::usernameChanged, this, &Application::updateWindowTitle);
|
||||
|
||||
// set the account manager's root URL and trigger a login request if we don't have the access token
|
||||
accountManager.setIsAgent(true);
|
||||
accountManager.setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL);
|
||||
UserActivityLogger::getInstance().launch(applicationVersion());
|
||||
|
||||
|
@ -604,7 +602,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
|
||||
connect(addressManager.data(), &AddressManager::hostChanged, this, &Application::updateWindowTitle);
|
||||
connect(this, &QCoreApplication::aboutToQuit, addressManager.data(), &AddressManager::storeCurrentAddress);
|
||||
|
||||
|
||||
// Save avatar location immediately after a teleport.
|
||||
connect(getMyAvatar(), &MyAvatar::positionGoneTo,
|
||||
DependencyManager::get<AddressManager>().data(), &AddressManager::storeCurrentAddress);
|
||||
|
@ -625,7 +623,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
getEntities()->reloadEntityScripts();
|
||||
}, Qt::QueuedConnection);
|
||||
|
||||
connect(scriptEngines, &ScriptEngines::scriptLoadError,
|
||||
connect(scriptEngines, &ScriptEngines::scriptLoadError,
|
||||
scriptEngines, [](const QString& filename, const QString& error){
|
||||
OffscreenUi::warning(nullptr, "Error Loading Script", filename + " failed to load.");
|
||||
}, Qt::QueuedConnection);
|
||||
|
@ -817,6 +815,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
} else if (action == controller::toInt(controller::Action::RETICLE_Y)) {
|
||||
auto oldPos = _compositor.getReticlePosition();
|
||||
_compositor.setReticlePosition({ oldPos.x, oldPos.y + state });
|
||||
} else if (action == controller::toInt(controller::Action::TOGGLE_OVERLAY)) {
|
||||
toggleOverlays();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -903,6 +903,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
SpacemouseManager::getInstance().init();
|
||||
#endif
|
||||
|
||||
auto& packetReceiver = nodeList->getPacketReceiver();
|
||||
packetReceiver.registerListener(PacketType::DomainConnectionDenied, this, "handleDomainConnectionDeniedPacket");
|
||||
|
||||
// If the user clicks an an entity, we will check that it's an unlocked web entity, and if so, set the focus to it
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity,
|
||||
|
@ -964,6 +967,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
|
||||
connect(this, &Application::applicationStateChanged, this, &Application::activeChanged);
|
||||
qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0);
|
||||
|
||||
_idleTimer = new QTimer(this);
|
||||
connect(_idleTimer, &QTimer::timeout, [=] {
|
||||
idle(usecTimestampNow());
|
||||
|
@ -972,7 +976,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
disconnect(_idleTimer);
|
||||
});
|
||||
// Setting the interval to zero forces this to get called whenever there are no messages
|
||||
// in the queue, which can be pretty damn frequent. Hence the idle function has a bunch
|
||||
// in the queue, which can be pretty damn frequent. Hence the idle function has a bunch
|
||||
// of logic to abort early if it's being called too often.
|
||||
_idleTimer->start(0);
|
||||
}
|
||||
|
@ -1020,7 +1024,7 @@ void Application::cleanupBeforeQuit() {
|
|||
getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts
|
||||
DependencyManager::get<ScriptEngines>()->saveScripts();
|
||||
DependencyManager::get<ScriptEngines>()->shutdownScripting(); // stop all currently running global scripts
|
||||
DependencyManager::destroy<ScriptEngines>();
|
||||
DependencyManager::destroy<ScriptEngines>();
|
||||
|
||||
// first stop all timers directly or by invokeMethod
|
||||
// depending on what thread they run in
|
||||
|
@ -1195,7 +1199,6 @@ void Application::initializeUi() {
|
|||
// OffscreenUi is a subclass of OffscreenQmlSurface specifically designed to
|
||||
// support the window management and scripting proxies for VR use
|
||||
offscreenUi->createDesktop(QString("hifi/Desktop.qml"));
|
||||
connect(offscreenUi.data(), &OffscreenUi::showDesktop, this, &Application::showDesktop);
|
||||
|
||||
// FIXME either expose so that dialogs can set this themselves or
|
||||
// do better detection in the offscreen UI of what has focus
|
||||
|
@ -1209,10 +1212,10 @@ void Application::initializeUi() {
|
|||
|
||||
setupPreferences();
|
||||
|
||||
// For some reason there is already an "Application" object in the QML context,
|
||||
// For some reason there is already an "Application" object in the QML context,
|
||||
// though I can't find it. Hence, "ApplicationInterface"
|
||||
rootContext->setContextProperty("SnapshotUploader", new SnapshotUploader());
|
||||
rootContext->setContextProperty("ApplicationInterface", this);
|
||||
rootContext->setContextProperty("ApplicationInterface", this);
|
||||
rootContext->setContextProperty("AnimationCache", DependencyManager::get<AnimationCache>().data());
|
||||
rootContext->setContextProperty("Audio", &AudioScriptingInterface::getInstance());
|
||||
rootContext->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
|
||||
|
@ -1293,6 +1296,7 @@ void Application::initializeUi() {
|
|||
}
|
||||
|
||||
void Application::paintGL() {
|
||||
|
||||
// paintGL uses a queued connection, so we can get messages from the queue even after we've quit
|
||||
// and the plugins have shutdown
|
||||
if (_aboutToQuit) {
|
||||
|
@ -1318,10 +1322,6 @@ void Application::paintGL() {
|
|||
_lastFramesPerSecondUpdate = now;
|
||||
}
|
||||
|
||||
if (_isGLInitialized) {
|
||||
idle(now);
|
||||
}
|
||||
|
||||
PROFILE_RANGE(__FUNCTION__);
|
||||
PerformanceTimer perfTimer("paintGL");
|
||||
|
||||
|
@ -1616,7 +1616,6 @@ void Application::paintGL() {
|
|||
}
|
||||
|
||||
_lastInstantaneousFps = instantaneousFps;
|
||||
_pendingPaint = false;
|
||||
}
|
||||
|
||||
void Application::runTests() {
|
||||
|
@ -2027,9 +2026,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, !Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson));
|
||||
cameraMenuChanged();
|
||||
break;
|
||||
case Qt::Key_O:
|
||||
_overlayConductor.setEnabled(!_overlayConductor.getEnabled());
|
||||
break;
|
||||
|
||||
case Qt::Key_Slash:
|
||||
Menu::getInstance()->triggerOption(MenuOption::Stats);
|
||||
break;
|
||||
|
@ -2429,10 +2426,24 @@ bool Application::acceptSnapshot(const QString& urlString) {
|
|||
static uint32_t _renderedFrameIndex { INVALID_FRAME };
|
||||
|
||||
void Application::idle(uint64_t now) {
|
||||
|
||||
if (_aboutToQuit) {
|
||||
return; // bail early, nothing to do here.
|
||||
}
|
||||
|
||||
|
||||
Stats::getInstance()->updateStats();
|
||||
AvatarInputs::getInstance()->update();
|
||||
|
||||
// These tasks need to be done on our first idle, because we don't want the showing of
|
||||
// overlay subwindows to do a showDesktop() until after the first time through
|
||||
static bool firstIdle = true;
|
||||
if (firstIdle) {
|
||||
firstIdle = false;
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
connect(offscreenUi.data(), &OffscreenUi::showDesktop, this, &Application::showDesktop);
|
||||
_overlayConductor.setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Overlays));
|
||||
}
|
||||
|
||||
auto displayPlugin = getActiveDisplayPlugin();
|
||||
// depending on whether we're throttling or not.
|
||||
// Once rendering is off on another thread we should be able to have Application::idle run at start(0) in
|
||||
|
@ -2453,30 +2464,16 @@ void Application::idle(uint64_t now) {
|
|||
_renderedFrameIndex = INVALID_FRAME;
|
||||
}
|
||||
|
||||
// Nested ifs are for clarity in the logic. Don't collapse them into a giant single if.
|
||||
// Don't saturate the main thread with rendering, no paint calls until the last one is complete
|
||||
if (!_pendingPaint) {
|
||||
// Also no paint calls until the display plugin has increased by at least one frame
|
||||
// (don't render at 90fps if the display plugin only goes at 60)
|
||||
if (_renderedFrameIndex == INVALID_FRAME || presentCount > _renderedFrameIndex) {
|
||||
// Record what present frame we're on
|
||||
_renderedFrameIndex = presentCount;
|
||||
// Don't allow paint requests to stack up in the event queue
|
||||
_pendingPaint = true;
|
||||
// But when we DO request a paint, get to it as soon as possible: high priority
|
||||
postEvent(this, new QEvent(static_cast<QEvent::Type>(Paint)), Qt::HighEventPriority);
|
||||
}
|
||||
}
|
||||
|
||||
// For the rest of idle, we want to cap at the max sim rate, so we might not call
|
||||
// the remaining idle work every paint frame, or vice versa
|
||||
// In theory this means we could call idle processing more often than painting,
|
||||
// but in practice, when the paintGL calls aren't keeping up, there's no room left
|
||||
// in the main thread to call idle more often than paint.
|
||||
// This check is mostly to keep idle from burning up CPU cycles by running at
|
||||
// hundreds of idles per second when the rendering is that fast
|
||||
if ((timeSinceLastUpdateUs / USECS_PER_MSEC) < CAPPED_SIM_FRAME_PERIOD_MS) {
|
||||
// No paint this round, but might be time for a new idle, otherwise return
|
||||
// Don't saturate the main thread with rendering and simulation,
|
||||
// unless display plugin has increased by at least one frame
|
||||
if (_renderedFrameIndex == INVALID_FRAME || presentCount > _renderedFrameIndex) {
|
||||
// Record what present frame we're on
|
||||
_renderedFrameIndex = presentCount;
|
||||
|
||||
// request a paint, get to it as soon as possible: high priority
|
||||
postEvent(this, new QEvent(static_cast<QEvent::Type>(Paint)), Qt::HighEventPriority);
|
||||
} else {
|
||||
// there's no use in simulating or rendering faster then the present rate.
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2990,6 +2987,16 @@ void Application::updateThreads(float deltaTime) {
|
|||
}
|
||||
}
|
||||
|
||||
void Application::toggleOverlays() {
|
||||
auto newOverlaysVisible = !_overlayConductor.getEnabled();
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, newOverlaysVisible);
|
||||
_overlayConductor.setEnabled(newOverlaysVisible);
|
||||
}
|
||||
|
||||
void Application::setOverlaysVisible(bool visible) {
|
||||
_overlayConductor.setEnabled(visible);
|
||||
}
|
||||
|
||||
void Application::cycleCamera() {
|
||||
auto menu = Menu::getInstance();
|
||||
if (menu->isOptionChecked(MenuOption::FullscreenMirror)) {
|
||||
|
@ -3384,7 +3391,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
|
|||
_octreeQuery.setCameraNearClip(_viewFrustum.getNearClip());
|
||||
_octreeQuery.setCameraFarClip(_viewFrustum.getFarClip());
|
||||
_octreeQuery.setCameraEyeOffsetPosition(glm::vec3());
|
||||
_octreeQuery.setKeyholeRadius(_viewFrustum.getKeyholeRadius());
|
||||
_octreeQuery.setCameraCenterRadius(_viewFrustum.getCenterRadius());
|
||||
auto lodManager = DependencyManager::get<LODManager>();
|
||||
_octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale());
|
||||
_octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust());
|
||||
|
@ -3420,9 +3427,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
|
|||
rootDetails.y * TREE_SCALE,
|
||||
rootDetails.z * TREE_SCALE) - glm::vec3(HALF_TREE_SCALE),
|
||||
rootDetails.s * TREE_SCALE);
|
||||
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
|
||||
|
||||
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
|
||||
if (_viewFrustum.cubeIntersectsKeyhole(serverBounds)) {
|
||||
inViewServers++;
|
||||
}
|
||||
}
|
||||
|
@ -3488,12 +3493,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
|
|||
rootDetails.s * TREE_SCALE);
|
||||
|
||||
|
||||
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
|
||||
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
|
||||
inView = true;
|
||||
} else {
|
||||
inView = false;
|
||||
}
|
||||
inView = _viewFrustum.cubeIntersectsKeyhole(serverBounds);
|
||||
} else {
|
||||
if (wantExtraDebugging) {
|
||||
qCDebug(interfaceapp) << "Jurisdiction without RootCode for node " << *node << ". That's unusual!";
|
||||
|
@ -3954,10 +3954,30 @@ void Application::clearDomainOctreeDetails() {
|
|||
void Application::domainChanged(const QString& domainHostname) {
|
||||
updateWindowTitle();
|
||||
clearDomainOctreeDetails();
|
||||
_domainConnectionRefusals.clear();
|
||||
// disable physics until we have enough information about our new location to not cause craziness.
|
||||
_physicsEnabled = false;
|
||||
}
|
||||
|
||||
void Application::handleDomainConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
// Read deny reason from packet
|
||||
quint16 reasonSize;
|
||||
message->readPrimitive(&reasonSize);
|
||||
QString reason = QString::fromUtf8(message->readWithoutCopy(reasonSize));
|
||||
|
||||
// output to the log so the user knows they got a denied connection request
|
||||
// and check and signal for an access token so that we can make sure they are logged in
|
||||
qCDebug(interfaceapp) << "The domain-server denied a connection request: " << reason;
|
||||
qCDebug(interfaceapp) << "You may need to re-log to generate a keypair so you can provide a username signature.";
|
||||
|
||||
if (!_domainConnectionRefusals.contains(reason)) {
|
||||
_domainConnectionRefusals.append(reason);
|
||||
emit domainConnectionRefused(reason);
|
||||
}
|
||||
|
||||
AccountManager::getInstance().checkAndSignalForAccessToken();
|
||||
}
|
||||
|
||||
void Application::connectedToDomain(const QString& hostname) {
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
const QUuid& domainID = DependencyManager::get<NodeList>()->getDomainHandler().getUUID();
|
||||
|
@ -4501,6 +4521,33 @@ void Application::openUrl(const QUrl& url) {
|
|||
}
|
||||
}
|
||||
|
||||
void Application::domainSettingsReceived(const QJsonObject& domainSettingsObject) {
|
||||
// from the domain-handler, figure out the satoshi cost per voxel and per meter cubed
|
||||
const QString VOXEL_SETTINGS_KEY = "voxels";
|
||||
const QString PER_VOXEL_COST_KEY = "per-voxel-credits";
|
||||
const QString PER_METER_CUBED_COST_KEY = "per-meter-cubed-credits";
|
||||
const QString VOXEL_WALLET_UUID = "voxel-wallet";
|
||||
|
||||
const QJsonObject& voxelObject = domainSettingsObject[VOXEL_SETTINGS_KEY].toObject();
|
||||
|
||||
qint64 satoshisPerVoxel = 0;
|
||||
qint64 satoshisPerMeterCubed = 0;
|
||||
QUuid voxelWalletUUID;
|
||||
|
||||
if (!domainSettingsObject.isEmpty()) {
|
||||
float perVoxelCredits = (float) voxelObject[PER_VOXEL_COST_KEY].toDouble();
|
||||
float perMeterCubedCredits = (float) voxelObject[PER_METER_CUBED_COST_KEY].toDouble();
|
||||
|
||||
satoshisPerVoxel = (qint64) floorf(perVoxelCredits * SATOSHIS_PER_CREDIT);
|
||||
satoshisPerMeterCubed = (qint64) floorf(perMeterCubedCredits * SATOSHIS_PER_CREDIT);
|
||||
|
||||
voxelWalletUUID = QUuid(voxelObject[VOXEL_WALLET_UUID].toString());
|
||||
}
|
||||
|
||||
qCDebug(interfaceapp) << "Octree edits costs are" << satoshisPerVoxel << "per octree cell and" << satoshisPerMeterCubed << "per meter cubed";
|
||||
qCDebug(interfaceapp) << "Destination wallet UUID for edit payments is" << voxelWalletUUID;
|
||||
}
|
||||
|
||||
void Application::loadDialog() {
|
||||
auto scriptEngines = DependencyManager::get<ScriptEngines>();
|
||||
QString fileNameString = OffscreenUi::getOpenFileName(
|
||||
|
@ -4702,7 +4749,7 @@ static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool acti
|
|||
groupingMenu = "Developer";
|
||||
break;
|
||||
default:
|
||||
groupingMenu = "Standard";
|
||||
groupingMenu = "Standard";
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -149,6 +149,7 @@ public:
|
|||
const ApplicationOverlay& getApplicationOverlay() const { return _applicationOverlay; }
|
||||
ApplicationCompositor& getApplicationCompositor() { return _compositor; }
|
||||
const ApplicationCompositor& getApplicationCompositor() const { return _compositor; }
|
||||
|
||||
Overlays& getOverlays() { return _overlays; }
|
||||
|
||||
bool isForeground() const { return _isForeground; }
|
||||
|
@ -224,6 +225,7 @@ signals:
|
|||
void svoImportRequested(const QString& url);
|
||||
|
||||
void checkBackgroundDownloads();
|
||||
void domainConnectionRefused(const QString& reason);
|
||||
|
||||
void fullAvatarURLChanged(const QString& newValue, const QString& modelName);
|
||||
|
||||
|
@ -269,6 +271,8 @@ public slots:
|
|||
|
||||
void cycleCamera();
|
||||
void cameraMenuChanged();
|
||||
void toggleOverlays();
|
||||
void setOverlaysVisible(bool visible);
|
||||
|
||||
void reloadResourceCaches();
|
||||
|
||||
|
@ -291,6 +295,9 @@ private slots:
|
|||
|
||||
void activeChanged(Qt::ApplicationState state);
|
||||
|
||||
void domainSettingsReceived(const QJsonObject& domainSettingsObject);
|
||||
void handleDomainConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
void notifyPacketVersionMismatch();
|
||||
|
||||
void loadSettings();
|
||||
|
@ -469,6 +476,7 @@ private:
|
|||
typedef bool (Application::* AcceptURLMethod)(const QString &);
|
||||
static const QHash<QString, AcceptURLMethod> _acceptedExtensions;
|
||||
|
||||
QList<QString> _domainConnectionRefusals;
|
||||
glm::uvec2 _renderResolution;
|
||||
|
||||
int _maxOctreePPS = DEFAULT_MAX_OCTREE_PPS;
|
||||
|
@ -504,7 +512,6 @@ private:
|
|||
int _avatarAttachmentRequest = 0;
|
||||
|
||||
bool _settingsLoaded { false };
|
||||
bool _pendingPaint { false };
|
||||
QTimer* _idleTimer { nullptr };
|
||||
|
||||
bool _fakedMouseEvent { false };
|
||||
|
|
|
@ -22,8 +22,11 @@
|
|||
#include <QStandardPaths>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "DataServerAccountInfo.h"
|
||||
#include "Menu.h"
|
||||
|
||||
Q_DECLARE_METATYPE(DataServerAccountInfo)
|
||||
|
||||
static const QString RUNNING_MARKER_FILENAME = "Interface.running";
|
||||
|
||||
void CrashHandler::checkForAndHandleCrash() {
|
||||
|
@ -54,7 +57,7 @@ CrashHandler::Action CrashHandler::promptUserForAction() {
|
|||
layout->addWidget(label);
|
||||
|
||||
QRadioButton* option1 = new QRadioButton("Reset all my settings");
|
||||
QRadioButton* option2 = new QRadioButton("Reset my settings but retain avatar info.");
|
||||
QRadioButton* option2 = new QRadioButton("Reset my settings but retain login and avatar info.");
|
||||
QRadioButton* option3 = new QRadioButton("Continue with my current settings");
|
||||
option3->setChecked(true);
|
||||
layout->addWidget(option1);
|
||||
|
@ -76,7 +79,7 @@ CrashHandler::Action CrashHandler::promptUserForAction() {
|
|||
return CrashHandler::DELETE_INTERFACE_INI;
|
||||
}
|
||||
if (option2->isChecked()) {
|
||||
return CrashHandler::RETAIN_AVATAR_INFO;
|
||||
return CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,7 +88,7 @@ CrashHandler::Action CrashHandler::promptUserForAction() {
|
|||
}
|
||||
|
||||
void CrashHandler::handleCrash(CrashHandler::Action action) {
|
||||
if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_AVATAR_INFO) {
|
||||
if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) {
|
||||
// CrashHandler::DO_NOTHING or unexpected value
|
||||
return;
|
||||
}
|
||||
|
@ -98,13 +101,18 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
|
|||
const QString DISPLAY_NAME_KEY = "displayName";
|
||||
const QString FULL_AVATAR_URL_KEY = "fullAvatarURL";
|
||||
const QString FULL_AVATAR_MODEL_NAME_KEY = "fullAvatarModelName";
|
||||
const QString ACCOUNTS_GROUP = "accounts";
|
||||
QString displayName;
|
||||
QUrl fullAvatarURL;
|
||||
QString fullAvatarModelName;
|
||||
QUrl address;
|
||||
QMap<QString, DataServerAccountInfo> accounts;
|
||||
|
||||
if (action == CrashHandler::RETAIN_AVATAR_INFO) {
|
||||
// Read avatar info
|
||||
if (action == CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) {
|
||||
// Read login and avatar info
|
||||
|
||||
qRegisterMetaType<DataServerAccountInfo>("DataServerAccountInfo");
|
||||
qRegisterMetaTypeStreamOperators<DataServerAccountInfo>("DataServerAccountInfo");
|
||||
|
||||
// Location and orientation
|
||||
settings.beginGroup(ADDRESS_MANAGER_GROUP);
|
||||
|
@ -117,6 +125,13 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
|
|||
fullAvatarURL = settings.value(FULL_AVATAR_URL_KEY).toUrl();
|
||||
fullAvatarModelName = settings.value(FULL_AVATAR_MODEL_NAME_KEY).toString();
|
||||
settings.endGroup();
|
||||
|
||||
// Accounts
|
||||
settings.beginGroup(ACCOUNTS_GROUP);
|
||||
foreach(const QString& key, settings.allKeys()) {
|
||||
accounts.insert(key, settings.value(key).value<DataServerAccountInfo>());
|
||||
}
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
// Delete Interface.ini
|
||||
|
@ -125,8 +140,8 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
|
|||
settingsFile.remove();
|
||||
}
|
||||
|
||||
if (action == CrashHandler::RETAIN_AVATAR_INFO) {
|
||||
// Write avatar info
|
||||
if (action == CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) {
|
||||
// Write login and avatar info
|
||||
|
||||
// Location and orientation
|
||||
settings.beginGroup(ADDRESS_MANAGER_GROUP);
|
||||
|
@ -139,6 +154,13 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
|
|||
settings.setValue(FULL_AVATAR_URL_KEY, fullAvatarURL);
|
||||
settings.setValue(FULL_AVATAR_MODEL_NAME_KEY, fullAvatarModelName);
|
||||
settings.endGroup();
|
||||
|
||||
// Accounts
|
||||
settings.beginGroup(ACCOUNTS_GROUP);
|
||||
foreach(const QString& key, accounts.keys()) {
|
||||
settings.setValue(key, QVariant::fromValue(accounts.value(key)));
|
||||
}
|
||||
settings.endGroup();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ public:
|
|||
private:
|
||||
enum Action {
|
||||
DELETE_INTERFACE_INI,
|
||||
RETAIN_AVATAR_INFO,
|
||||
RETAIN_LOGIN_AND_AVATAR_INFO,
|
||||
DO_NOTHING
|
||||
};
|
||||
|
||||
|
|
|
@ -247,6 +247,9 @@ Menu::Menu() {
|
|||
0, true, qApp, SLOT(rotationModeChanged()),
|
||||
UNSPECIFIED_POSITION, "Advanced");
|
||||
|
||||
// View > Overlays
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true,
|
||||
qApp, SLOT(setOverlaysVisible(bool)));
|
||||
|
||||
// Navigate menu ----------------------------------
|
||||
MenuWrapper* navigateMenu = addMenu("Navigate");
|
||||
|
|
|
@ -247,6 +247,7 @@ namespace MenuOption {
|
|||
const QString OnePointCalibration = "1 Point Calibration";
|
||||
const QString OnlyDisplayTopTen = "Only Display Top Ten";
|
||||
const QString OutputMenu = "Display";
|
||||
const QString Overlays = "Overlays";
|
||||
const QString PackageModel = "Package Model...";
|
||||
const QString Pair = "Pair";
|
||||
const QString PhysicsShowHulls = "Draw Collision Hulls";
|
||||
|
|
|
@ -136,7 +136,7 @@ glm::quat Avatar::getWorldAlignedOrientation () const {
|
|||
|
||||
AABox Avatar::getBounds() const {
|
||||
// Our skeleton models are rigged, and this method call safely produces the static bounds of the model.
|
||||
// Except, that getPartBounds produces an infinite, uncentered bounding box when the model is not yet parsed,
|
||||
// Except, that getPartBounds produces an infinite, uncentered bounding box when the model is not yet parsed,
|
||||
// and we want a centered one. NOTE: There is code that may never try to render, and thus never load and get the
|
||||
// real model bounds, if this is unrealistically small.
|
||||
if (!_skeletonModel.isRenderable()) {
|
||||
|
@ -188,15 +188,14 @@ void Avatar::simulate(float deltaTime) {
|
|||
|
||||
// simple frustum check
|
||||
float boundingRadius = getBoundingRadius();
|
||||
bool inViewFrustum = qApp->getViewFrustum()->sphereInFrustum(getPosition(), boundingRadius) !=
|
||||
ViewFrustum::OUTSIDE;
|
||||
bool inView = qApp->getViewFrustum()->sphereIntersectsFrustum(getPosition(), boundingRadius);
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("hand");
|
||||
getHand()->simulate(deltaTime, false);
|
||||
}
|
||||
|
||||
if (_shouldAnimate && !_shouldSkipRender && inViewFrustum) {
|
||||
if (_shouldAnimate && !_shouldSkipRender && inView) {
|
||||
{
|
||||
PerformanceTimer perfTimer("skeleton");
|
||||
_skeletonModel.getRig()->copyJointsFromJointData(_jointData);
|
||||
|
@ -401,7 +400,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
frustum = qApp->getDisplayViewFrustum();
|
||||
}
|
||||
|
||||
if (frustum->sphereInFrustum(getPosition(), boundingRadius) == ViewFrustum::OUTSIDE) {
|
||||
if (!frustum->sphereIntersectsFrustum(getPosition(), boundingRadius)) {
|
||||
endRender();
|
||||
return;
|
||||
}
|
||||
|
@ -430,6 +429,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE) {
|
||||
// add local lights
|
||||
const float BASE_LIGHT_DISTANCE = 2.0f;
|
||||
const float LIGHT_FALLOFF_RADIUS = 0.01f;
|
||||
const float LIGHT_EXPONENT = 1.0f;
|
||||
const float LIGHT_CUTOFF = glm::radians(80.0f);
|
||||
float distance = BASE_LIGHT_DISTANCE * getUniformScale();
|
||||
|
@ -438,7 +438,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
foreach (const AvatarManager::LocalLight& light, DependencyManager::get<AvatarManager>()->getLocalLights()) {
|
||||
glm::vec3 direction = orientation * light.direction;
|
||||
DependencyManager::get<DeferredLightingEffect>()->addSpotLight(position - direction * distance,
|
||||
distance * 2.0f, light.color, 0.5f, orientation, LIGHT_EXPONENT, LIGHT_CUTOFF);
|
||||
distance * 2.0f, light.color, 0.5f, LIGHT_FALLOFF_RADIUS, orientation, LIGHT_EXPONENT, LIGHT_CUTOFF);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -516,7 +516,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
auto& frustum = *renderArgs->_viewFrustum;
|
||||
auto textPosition = getDisplayNamePosition();
|
||||
|
||||
if (frustum.pointInFrustum(textPosition, true) == ViewFrustum::INSIDE) {
|
||||
if (frustum.pointIntersectsFrustum(textPosition)) {
|
||||
renderDisplayName(batch, frustum, textPosition);
|
||||
}
|
||||
}
|
||||
|
@ -669,10 +669,10 @@ glm::vec3 Avatar::getDisplayNamePosition() const {
|
|||
return namePosition;
|
||||
}
|
||||
|
||||
Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, const glm::vec3& textPosition) const {
|
||||
Q_ASSERT_X(frustum.pointInFrustum(textPosition, true) == ViewFrustum::INSIDE,
|
||||
Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& view, const glm::vec3& textPosition) const {
|
||||
Q_ASSERT_X(view.pointIntersectsFrustum(textPosition),
|
||||
"Avatar::calculateDisplayNameTransform", "Text not in viewfrustum.");
|
||||
glm::vec3 toFrustum = frustum.getPosition() - textPosition;
|
||||
glm::vec3 toFrustum = view.getPosition() - textPosition;
|
||||
|
||||
// Compute orientation
|
||||
// If x and z are 0, atan(x, z) adais undefined, so default to 0 degrees
|
||||
|
@ -694,7 +694,7 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, cons
|
|||
return result;
|
||||
}
|
||||
|
||||
void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, const glm::vec3& textPosition) const {
|
||||
void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition) const {
|
||||
PROFILE_RANGE_BATCH(batch, __FUNCTION__);
|
||||
|
||||
bool shouldShowReceiveStats = DependencyManager::get<AvatarManager>()->shouldShowReceiveStats() && !isMyAvatar();
|
||||
|
@ -702,7 +702,7 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, co
|
|||
// If we have nothing to draw, or it's totally transparent, or it's too close or behind the camera, return
|
||||
static const float CLIP_DISTANCE = 0.2f;
|
||||
if ((_displayName.isEmpty() && !shouldShowReceiveStats) || _displayNameAlpha == 0.0f
|
||||
|| (glm::dot(frustum.getDirection(), getDisplayNamePosition() - frustum.getPosition()) <= CLIP_DISTANCE)) {
|
||||
|| (glm::dot(view.getDirection(), getDisplayNamePosition() - view.getPosition()) <= CLIP_DISTANCE)) {
|
||||
return;
|
||||
}
|
||||
auto renderer = textRenderer(DISPLAYNAME);
|
||||
|
@ -743,7 +743,7 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, co
|
|||
(_displayNameAlpha / DISPLAYNAME_ALPHA) * DISPLAYNAME_BACKGROUND_ALPHA);
|
||||
|
||||
// Compute display name transform
|
||||
auto textTransform = calculateDisplayNameTransform(frustum, textPosition);
|
||||
auto textTransform = calculateDisplayNameTransform(view, textPosition);
|
||||
// Test on extent above insures abs(height) > 0.0f
|
||||
textTransform.postScale(1.0f / height);
|
||||
batch.setModelTransform(textTransform);
|
||||
|
|
|
@ -231,8 +231,8 @@ protected:
|
|||
float getPelvisFloatingHeight() const;
|
||||
glm::vec3 getDisplayNamePosition() const;
|
||||
|
||||
Transform calculateDisplayNameTransform(const ViewFrustum& frustum, const glm::vec3& textPosition) const;
|
||||
void renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, const glm::vec3& textPosition) const;
|
||||
Transform calculateDisplayNameTransform(const ViewFrustum& view, const glm::vec3& textPosition) const;
|
||||
void renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition) const;
|
||||
virtual void renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, float glowLevel = 0.0f);
|
||||
virtual bool shouldRenderHead(const RenderArgs* renderArgs) const;
|
||||
virtual void fixupModelsInScene();
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
WindowScriptingInterface::WindowScriptingInterface() {
|
||||
const DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
|
||||
connect(&domainHandler, &DomainHandler::connectedToDomain, this, &WindowScriptingInterface::domainChanged);
|
||||
connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused);
|
||||
connect(qApp, &Application::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused);
|
||||
|
||||
connect(qApp, &Application::svoImportRequested, [this](const QString& urlString) {
|
||||
static const QMetaMethod svoImportRequestedSignal =
|
||||
|
|
|
@ -301,7 +301,7 @@ void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int
|
|||
// look at borrowed from overlays
|
||||
float elevation = -asinf(relativePosition.y / glm::length(relativePosition));
|
||||
float azimuth = atan2f(relativePosition.x, relativePosition.z);
|
||||
glm::quat faceCamera = glm::quat(glm::vec3(elevation, azimuth, 0)) * quat(vec3(0, 0, -1)); // this extra *quat(vec3(0,0,-1)) was required to get the quad to flip this seems like we could optimize
|
||||
glm::quat faceCamera = glm::quat(glm::vec3(elevation, azimuth, 0)) * quat(vec3(0, -PI, 0)); // this extra *quat(vec3(0,-PI,0)) was required to get the quad to flip this seems like we could optimize
|
||||
|
||||
Transform transform;
|
||||
transform.setTranslation(relativePosition);
|
||||
|
@ -403,6 +403,22 @@ glm::vec2 ApplicationCompositor::getReticlePosition() const {
|
|||
return toGlm(QCursor::pos());
|
||||
}
|
||||
|
||||
bool ApplicationCompositor::getReticleOverDesktop() const {
|
||||
// if the QML/Offscreen UI thinks we're over the desktop, then we are...
|
||||
// but... if we're outside of the overlay area, we also want to call ourselves
|
||||
// as being over the desktop.
|
||||
if (qApp->isHMDMode()) {
|
||||
QMutexLocker locker(&_reticleLock);
|
||||
glm::vec2 maxOverlayPosition = qApp->getUiSize();
|
||||
if (_reticlePositionInHMD.x < 0 || _reticlePositionInHMD.y < 0 ||
|
||||
_reticlePositionInHMD.x > maxOverlayPosition.x || _reticlePositionInHMD.y > maxOverlayPosition.y) {
|
||||
return true; // we are outside the overlay area, consider ourselves over the desktop
|
||||
}
|
||||
}
|
||||
return _isOverDesktop;
|
||||
}
|
||||
|
||||
|
||||
void ApplicationCompositor::setReticlePosition(glm::vec2 position, bool sendFakeEvent) {
|
||||
if (qApp->isHMDMode()) {
|
||||
QMutexLocker locker(&_reticleLock);
|
||||
|
|
|
@ -107,7 +107,7 @@ public:
|
|||
bool shouldCaptureMouse() const;
|
||||
|
||||
/// if the reticle is pointing to a system overlay (a dialog box for example) then the function returns true otherwise false
|
||||
bool getReticleOverDesktop() const { return _isOverDesktop; }
|
||||
bool getReticleOverDesktop() const;
|
||||
void setReticleOverDesktop(bool value) { _isOverDesktop = value; }
|
||||
|
||||
private:
|
||||
|
@ -131,7 +131,7 @@ private:
|
|||
float _textureAspectRatio { 1.0f };
|
||||
int _hemiVerticesID { GeometryCache::UNKNOWN_ID };
|
||||
|
||||
float _alpha { 1.0f };
|
||||
float _alpha { 0.0f }; // hidden by default
|
||||
float _prevAlpha { 1.0f };
|
||||
float _fadeInAlpha { true };
|
||||
float _oculusUIRadius { 1.0f };
|
||||
|
|
|
@ -58,10 +58,6 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) {
|
|||
CHECK_GL_ERROR();
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "ApplicationOverlay::displayOverlay()");
|
||||
|
||||
// TODO move to Application::idle()?
|
||||
Stats::getInstance()->updateStats();
|
||||
AvatarInputs::getInstance()->update();
|
||||
|
||||
buildFramebufferObject();
|
||||
|
||||
if (!_overlayFramebuffer) {
|
||||
|
|
|
@ -110,19 +110,12 @@ void OverlayConductor::setEnabled(bool enabled) {
|
|||
return;
|
||||
}
|
||||
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, enabled);
|
||||
|
||||
_enabled = enabled; // set the new value
|
||||
|
||||
// if the new state is visible/enabled...
|
||||
if (_enabled) {
|
||||
// alpha fadeOut the overlay mesh.
|
||||
qApp->getApplicationCompositor().fadeOut();
|
||||
|
||||
// disable mouse clicks from script
|
||||
qApp->getOverlays().disable();
|
||||
|
||||
// disable QML events
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
offscreenUi->getRootItem()->setEnabled(false);
|
||||
|
||||
_enabled = false;
|
||||
} else {
|
||||
// alpha fadeIn the overlay mesh.
|
||||
qApp->getApplicationCompositor().fadeIn();
|
||||
|
||||
|
@ -142,8 +135,16 @@ void OverlayConductor::setEnabled(bool enabled) {
|
|||
t.setRotation(glm::quat_cast(camMat));
|
||||
qApp->getApplicationCompositor().setModelTransform(t);
|
||||
}
|
||||
} else { // other wise, if the new state is hidden/not enabled
|
||||
// alpha fadeOut the overlay mesh.
|
||||
qApp->getApplicationCompositor().fadeOut();
|
||||
|
||||
_enabled = true;
|
||||
// disable mouse clicks from script
|
||||
qApp->getOverlays().disable();
|
||||
|
||||
// disable QML events
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
offscreenUi->getRootItem()->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,8 +29,8 @@ private:
|
|||
STANDING
|
||||
};
|
||||
|
||||
Mode _mode = FLAT;
|
||||
bool _enabled = true;
|
||||
Mode _mode { FLAT };
|
||||
bool _enabled { false };
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -61,6 +61,7 @@ namespace controller {
|
|||
makeButtonPair(Action::CONTEXT_MENU, "ContextMenu"),
|
||||
makeButtonPair(Action::TOGGLE_MUTE, "ToggleMute"),
|
||||
makeButtonPair(Action::CYCLE_CAMERA, "CycleCamera"),
|
||||
makeButtonPair(Action::TOGGLE_OVERLAY, "ToggleOverlay"),
|
||||
|
||||
makeAxisPair(Action::RETICLE_CLICK, "ReticleClick"),
|
||||
makeAxisPair(Action::RETICLE_X, "ReticleX"),
|
||||
|
|
|
@ -52,6 +52,7 @@ enum class Action {
|
|||
CONTEXT_MENU,
|
||||
TOGGLE_MUTE,
|
||||
CYCLE_CAMERA,
|
||||
TOGGLE_OVERLAY,
|
||||
|
||||
SHIFT,
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ void RenderableBoxEntityItem::render(RenderArgs* args) {
|
|||
}
|
||||
|
||||
batch.setModelTransform(transToCenter); // we want to include the scale as well
|
||||
if (_procedural->ready()) {
|
||||
if (_procedural && _procedural->ready()) {
|
||||
_procedural->prepare(batch, getPosition(), getDimensions());
|
||||
auto color = _procedural->getColor(cubeColor);
|
||||
batch._glColor4f(color.r, color.g, color.b, color.a);
|
||||
|
|
|
@ -36,15 +36,16 @@ void RenderableLightEntityItem::render(RenderArgs* args) {
|
|||
glm::vec3 color = toGlm(getXColor());
|
||||
|
||||
float intensity = getIntensity();
|
||||
float falloffRadius = getFalloffRadius();
|
||||
float exponent = getExponent();
|
||||
float cutoff = glm::radians(getCutoff());
|
||||
|
||||
if (_isSpotlight) {
|
||||
DependencyManager::get<DeferredLightingEffect>()->addSpotLight(position, largestDiameter / 2.0f,
|
||||
color, intensity, rotation, exponent, cutoff);
|
||||
color, intensity, falloffRadius, rotation, exponent, cutoff);
|
||||
} else {
|
||||
DependencyManager::get<DeferredLightingEffect>()->addPointLight(position, largestDiameter / 2.0f,
|
||||
color, intensity);
|
||||
color, intensity, falloffRadius);
|
||||
}
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
|
|
|
@ -245,6 +245,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
CHECK_PROPERTY_CHANGE(PROP_DYNAMIC, dynamic);
|
||||
CHECK_PROPERTY_CHANGE(PROP_IS_SPOTLIGHT, isSpotlight);
|
||||
CHECK_PROPERTY_CHANGE(PROP_INTENSITY, intensity);
|
||||
CHECK_PROPERTY_CHANGE(PROP_FALLOFF_RADIUS, falloffRadius);
|
||||
CHECK_PROPERTY_CHANGE(PROP_EXPONENT, exponent);
|
||||
CHECK_PROPERTY_CHANGE(PROP_CUTOFF, cutoff);
|
||||
CHECK_PROPERTY_CHANGE(PROP_LOCKED, locked);
|
||||
|
@ -445,6 +446,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
if (_type == EntityTypes::Light) {
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IS_SPOTLIGHT, isSpotlight);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_INTENSITY, intensity);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FALLOFF_RADIUS, falloffRadius);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EXPONENT, exponent);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CUTOFF, cutoff);
|
||||
}
|
||||
|
@ -597,6 +599,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
|
|||
COPY_PROPERTY_FROM_QSCRIPTVALUE(dynamic, bool, setDynamic);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(isSpotlight, bool, setIsSpotlight);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(intensity, float, setIntensity);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(falloffRadius, float, setFalloffRadius);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(exponent, float, setExponent);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(cutoff, float, setCutoff);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(locked, bool, setLocked);
|
||||
|
@ -762,6 +765,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
|
|||
ADD_PROPERTY_TO_MAP(PROP_DYNAMIC, unused, dynamic, unused);
|
||||
ADD_PROPERTY_TO_MAP(PROP_IS_SPOTLIGHT, IsSpotlight, isSpotlight, bool);
|
||||
ADD_PROPERTY_TO_MAP(PROP_INTENSITY, Intensity, intensity, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_FALLOFF_RADIUS, FalloffRadius, falloffRadius, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_EXPONENT, Exponent, exponent, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_CUTOFF, Cutoff, cutoff, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_LOCKED, Locked, locked, bool);
|
||||
|
@ -1043,6 +1047,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
|||
APPEND_ENTITY_PROPERTY(PROP_IS_SPOTLIGHT, properties.getIsSpotlight());
|
||||
APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor());
|
||||
APPEND_ENTITY_PROPERTY(PROP_INTENSITY, properties.getIntensity());
|
||||
APPEND_ENTITY_PROPERTY(PROP_FALLOFF_RADIUS, properties.getFalloffRadius());
|
||||
APPEND_ENTITY_PROPERTY(PROP_EXPONENT, properties.getExponent());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CUTOFF, properties.getCutoff());
|
||||
}
|
||||
|
@ -1332,6 +1337,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IS_SPOTLIGHT, bool, setIsSpotlight);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, xColor, setColor);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_INTENSITY, float, setIntensity);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FALLOFF_RADIUS, float, setFalloffRadius);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EXPONENT, float, setExponent);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CUTOFF, float, setCutoff);
|
||||
}
|
||||
|
@ -1477,6 +1483,7 @@ void EntityItemProperties::markAllChanged() {
|
|||
_dynamicChanged = true;
|
||||
|
||||
_intensityChanged = true;
|
||||
_falloffRadiusChanged = true;
|
||||
_exponentChanged = true;
|
||||
_cutoffChanged = true;
|
||||
_lockedChanged = true;
|
||||
|
@ -1719,6 +1726,9 @@ QList<QString> EntityItemProperties::listChangedProperties() {
|
|||
if (intensityChanged()) {
|
||||
out += "intensity";
|
||||
}
|
||||
if (falloffRadiusChanged()) {
|
||||
out += "falloffRadius";
|
||||
}
|
||||
if (exponentChanged()) {
|
||||
out += "exponent";
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include "EntityItemPropertiesMacros.h"
|
||||
#include "EntityTypes.h"
|
||||
#include "EntityPropertyFlags.h"
|
||||
#include "LightEntityItem.h"
|
||||
#include "LineEntityItem.h"
|
||||
#include "ParticleEffectEntityItem.h"
|
||||
#include "PolyVoxEntityItem.h"
|
||||
|
@ -129,10 +130,11 @@ public:
|
|||
DEFINE_PROPERTY(PROP_COLLISIONLESS, Collisionless, collisionless, bool, ENTITY_ITEM_DEFAULT_COLLISIONLESS);
|
||||
DEFINE_PROPERTY(PROP_COLLISION_MASK, CollisionMask, collisionMask, uint8_t, ENTITY_COLLISION_MASK_DEFAULT);
|
||||
DEFINE_PROPERTY(PROP_DYNAMIC, Dynamic, dynamic, bool, ENTITY_ITEM_DEFAULT_DYNAMIC);
|
||||
DEFINE_PROPERTY(PROP_IS_SPOTLIGHT, IsSpotlight, isSpotlight, bool, false);
|
||||
DEFINE_PROPERTY(PROP_INTENSITY, Intensity, intensity, float, 1.0f);
|
||||
DEFINE_PROPERTY(PROP_EXPONENT, Exponent, exponent, float, 0.0f);
|
||||
DEFINE_PROPERTY(PROP_CUTOFF, Cutoff, cutoff, float, ENTITY_ITEM_DEFAULT_CUTOFF);
|
||||
DEFINE_PROPERTY(PROP_IS_SPOTLIGHT, IsSpotlight, isSpotlight, bool, LightEntityItem::DEFAULT_IS_SPOTLIGHT);
|
||||
DEFINE_PROPERTY(PROP_INTENSITY, Intensity, intensity, float, LightEntityItem::DEFAULT_INTENSITY);
|
||||
DEFINE_PROPERTY(PROP_FALLOFF_RADIUS, FalloffRadius, falloffRadius, float, LightEntityItem::DEFAULT_FALLOFF_RADIUS);
|
||||
DEFINE_PROPERTY(PROP_EXPONENT, Exponent, exponent, float, LightEntityItem::DEFAULT_EXPONENT);
|
||||
DEFINE_PROPERTY(PROP_CUTOFF, Cutoff, cutoff, float, LightEntityItem::DEFAULT_CUTOFF);
|
||||
DEFINE_PROPERTY(PROP_LOCKED, Locked, locked, bool, ENTITY_ITEM_DEFAULT_LOCKED);
|
||||
DEFINE_PROPERTY_REF(PROP_TEXTURES, Textures, textures, QString, "");
|
||||
DEFINE_PROPERTY_REF(PROP_USER_DATA, UserData, userData, QString, ENTITY_ITEM_DEFAULT_USER_DATA);
|
||||
|
@ -359,6 +361,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
|
|||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Dynamic, dynamic, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, IsSpotlight, isSpotlight, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Intensity, intensity, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, FalloffRadius, falloffRadius, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Exponent, exponent, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Cutoff, cutoff, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Locked, locked, "");
|
||||
|
|
|
@ -72,8 +72,6 @@ const bool ENTITY_ITEM_DEFAULT_COLLISIONLESS = false;
|
|||
const bool ENTITY_ITEM_DEFAULT_DYNAMIC = false;
|
||||
const bool ENTITY_ITEM_DEFAULT_BILLBOARDED = false;
|
||||
|
||||
const float ENTITY_ITEM_DEFAULT_CUTOFF = PI / 2;
|
||||
|
||||
const QString ENTITY_ITEM_DEFAULT_NAME = QString("");
|
||||
|
||||
#endif // hifi_EntityItemPropertiesDefaults_h
|
||||
|
|
|
@ -167,6 +167,8 @@ enum EntityPropertyList {
|
|||
|
||||
PROP_COLLISION_MASK, // one byte of collision group flags
|
||||
|
||||
PROP_FALLOFF_RADIUS, // for Light entity
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ATTENTION: add new properties to end of list just ABOVE this line
|
||||
PROP_AFTER_LAST_ITEM,
|
||||
|
|
|
@ -85,7 +85,7 @@ void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params)
|
|||
forEachEntity([&](EntityItemPointer entity) {
|
||||
entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params));
|
||||
});
|
||||
|
||||
|
||||
// TODO: some of these inserts might be redundant!!!
|
||||
extraEncodeData->insert(this, entityTreeElementExtraEncodeData);
|
||||
}
|
||||
|
@ -96,39 +96,39 @@ bool EntityTreeElement::shouldIncludeChildData(int childIndex, EncodeBitstreamPa
|
|||
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
|
||||
|
||||
if (extraEncodeData->contains(this)) {
|
||||
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
|
||||
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
|
||||
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this));
|
||||
|
||||
|
||||
bool childCompleted = entityTreeElementExtraEncodeData->childCompleted[childIndex];
|
||||
|
||||
|
||||
// If we haven't completely sent the child yet, then we should include it
|
||||
return !childCompleted;
|
||||
}
|
||||
|
||||
|
||||
// I'm not sure this should ever happen, since we should have the extra encode data if we're considering
|
||||
// the child data for this element
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EntityTreeElement::shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const {
|
||||
bool EntityTreeElement::shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const {
|
||||
EntityTreeElementPointer childElement = getChildAtIndex(childIndex);
|
||||
if (childElement->alreadyFullyEncoded(params)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true; // if we don't know otherwise than recurse!
|
||||
}
|
||||
|
||||
bool EntityTreeElement::alreadyFullyEncoded(EncodeBitstreamParams& params) const {
|
||||
bool EntityTreeElement::alreadyFullyEncoded(EncodeBitstreamParams& params) const {
|
||||
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
|
||||
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
|
||||
|
||||
if (extraEncodeData->contains(this)) {
|
||||
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
|
||||
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
|
||||
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this));
|
||||
|
||||
// If we know that ALL subtrees below us have already been recursed, then we don't
|
||||
// If we know that ALL subtrees below us have already been recursed, then we don't
|
||||
// need to recurse this child.
|
||||
return entityTreeElementExtraEncodeData->subtreeCompleted;
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ void EntityTreeElement::updateEncodedData(int childIndex, AppendState childAppen
|
|||
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
|
||||
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
|
||||
if (extraEncodeData->contains(this)) {
|
||||
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
|
||||
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
|
||||
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this));
|
||||
|
||||
if (childAppendState == OctreeElement::COMPLETED) {
|
||||
|
@ -155,7 +155,7 @@ void EntityTreeElement::updateEncodedData(int childIndex, AppendState childAppen
|
|||
|
||||
void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) const {
|
||||
const bool wantDebug = false;
|
||||
|
||||
|
||||
if (wantDebug) {
|
||||
qCDebug(entities) << "EntityTreeElement::elementEncodeComplete() element:" << _cube;
|
||||
}
|
||||
|
@ -188,7 +188,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con
|
|||
// If we've encoding this element before... but we're coming back a second time in an attempt to
|
||||
// encoud our parent... this might happen.
|
||||
if (extraEncodeData->contains(childElement.get())) {
|
||||
EntityTreeElementExtraEncodeData* childExtraEncodeData
|
||||
EntityTreeElementExtraEncodeData* childExtraEncodeData
|
||||
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(childElement.get()));
|
||||
|
||||
if (wantDebug) {
|
||||
|
@ -197,7 +197,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con
|
|||
qCDebug(entities) << " childExtraEncodeData->elementCompleted:" << childExtraEncodeData->elementCompleted;
|
||||
qCDebug(entities) << " childExtraEncodeData->subtreeCompleted:" << childExtraEncodeData->subtreeCompleted;
|
||||
}
|
||||
|
||||
|
||||
if (childElement->isLeaf() && childExtraEncodeData->elementCompleted) {
|
||||
if (wantDebug) {
|
||||
qCDebug(entities) << " CHILD IS LEAF -- AND CHILD ELEMENT DATA COMPLETED!!!";
|
||||
|
@ -217,24 +217,24 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con
|
|||
qCDebug(entities) << " WAS elementCompleted:" << thisExtraEncodeData->elementCompleted;
|
||||
qCDebug(entities) << " WAS subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted;
|
||||
}
|
||||
|
||||
|
||||
thisExtraEncodeData->subtreeCompleted = !someChildTreeNotComplete;
|
||||
|
||||
if (wantDebug) {
|
||||
qCDebug(entities) << " NOW elementCompleted:" << thisExtraEncodeData->elementCompleted;
|
||||
qCDebug(entities) << " NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted;
|
||||
|
||||
|
||||
if (thisExtraEncodeData->subtreeCompleted) {
|
||||
qCDebug(entities) << " YEAH!!!!! >>>>>>>>>>>>>> NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData* packetData,
|
||||
OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData* packetData,
|
||||
EncodeBitstreamParams& params) const {
|
||||
|
||||
OctreeElement::AppendState appendElementState = OctreeElement::COMPLETED; // assume the best...
|
||||
|
||||
|
||||
// first, check the params.extraEncodeData to see if there's any partial re-encode data for this element
|
||||
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
|
||||
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData = NULL;
|
||||
|
@ -280,7 +280,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
|
|||
QVector<uint16_t> indexesOfEntitiesToInclude;
|
||||
|
||||
// It's possible that our element has been previous completed. In this case we'll simply not include any of our
|
||||
// entities for encoding. This is needed because we encode the element data at the "parent" level, and so we
|
||||
// entities for encoding. This is needed because we encode the element data at the "parent" level, and so we
|
||||
// need to handle the case where our sibling elements need encoding but we don't.
|
||||
if (!entityTreeElementExtraEncodeData->elementCompleted) {
|
||||
for (uint16_t i = 0; i < _entityItems.size(); i++) {
|
||||
|
@ -304,15 +304,13 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
|
|||
// frustum culling on rendering.
|
||||
bool success;
|
||||
AACube entityCube = entity->getQueryAACube(success);
|
||||
if (!success || params.viewFrustum->cubeInFrustum(entityCube) == ViewFrustum::OUTSIDE) {
|
||||
if (!success || !params.viewFrustum->cubeIntersectsKeyhole(entityCube)) {
|
||||
includeThisEntity = false; // out of view, don't include it
|
||||
}
|
||||
|
||||
// Now check the size of the entity, it's possible that a "too small to see" entity is included in a
|
||||
// larger octree cell because of its position (for example if it crosses the boundary of a cell it
|
||||
// pops to the next higher cell. So we want to check to see that the entity is large enough to be seen
|
||||
// before we consider including it.
|
||||
if (includeThisEntity) {
|
||||
} else {
|
||||
// Check the size of the entity, it's possible that a "too small to see" entity is included in a
|
||||
// larger octree cell because of its position (for example if it crosses the boundary of a cell it
|
||||
// pops to the next higher cell. So we want to check to see that the entity is large enough to be seen
|
||||
// before we consider including it.
|
||||
success = true;
|
||||
// we can't cull a parent-entity by its dimensions because the child may be larger. we need to
|
||||
// avoid sending details about a child but not the parent. the parent's queryAACube should have
|
||||
|
@ -397,7 +395,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
|
|||
// this octree element.
|
||||
if (extraEncodeData && entityTreeElementExtraEncodeData) {
|
||||
|
||||
// After processing, if we are PARTIAL or COMPLETED then we need to re-include our extra data.
|
||||
// After processing, if we are PARTIAL or COMPLETED then we need to re-include our extra data.
|
||||
// Only our parent can remove our extra data in these cases and only after it knows that all of its
|
||||
// children have been encoded.
|
||||
// If we weren't able to encode ANY data about ourselves, then we go ahead and remove our element data
|
||||
|
@ -412,7 +410,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
|
|||
extraEncodeData->insert(this, entityTreeElementExtraEncodeData);
|
||||
}
|
||||
} else {
|
||||
|
||||
|
||||
// If we weren't previously completed, check to see if we are
|
||||
if (!entityTreeElementExtraEncodeData->elementCompleted) {
|
||||
// If all of our items have been encoded, then we are complete as an element.
|
||||
|
@ -426,9 +424,9 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
|
|||
}
|
||||
}
|
||||
|
||||
// Determine if no entities at all were able to fit
|
||||
// Determine if no entities at all were able to fit
|
||||
bool noEntitiesFit = (numberOfEntities > 0 && actualNumberOfEntities == 0);
|
||||
|
||||
|
||||
// If we wrote fewer entities than we expected, update the number of entities in our packet
|
||||
bool successUpdateEntityCount = true;
|
||||
if (numberOfEntities != actualNumberOfEntities) {
|
||||
|
@ -504,7 +502,7 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3
|
|||
glm::vec3 clampedMax = glm::clamp(maxPoint, (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE);
|
||||
|
||||
if (_cube.contains(clampedMin) && _cube.contains(clampedMax)) {
|
||||
|
||||
|
||||
// If our child would be smaller than our smallest reasonable element, then we are the best fit.
|
||||
float childScale = _cube.getScale() / 2.0f;
|
||||
if (childScale <= SMALLEST_REASONABLE_OCTREE_ELEMENT_SCALE) {
|
||||
|
@ -524,7 +522,7 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3
|
|||
|
||||
bool EntityTreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
||||
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
|
||||
const QVector<EntityItemID>& entityIdsToDiscard, void** intersectedObject, bool precisionPicking) {
|
||||
|
||||
keepSearching = true; // assume that we will continue searching after this.
|
||||
|
@ -607,7 +605,7 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con
|
|||
|
||||
// we can use the AABox's ray intersection by mapping our origin and direction into the entity frame
|
||||
// and testing intersection there.
|
||||
if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance,
|
||||
if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance,
|
||||
localFace, localSurfaceNormal)) {
|
||||
if (localDistance < distance) {
|
||||
// now ask the entity if we actually intersect
|
||||
|
@ -862,12 +860,12 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
|
|||
if (this == _myTree->getRoot().get() && args.bitstreamVersion < VERSION_ROOT_ELEMENT_HAS_DATA) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
const unsigned char* dataAt = data;
|
||||
int bytesRead = 0;
|
||||
uint16_t numberOfEntities = 0;
|
||||
int expectedBytesPerEntity = EntityItem::expectedBytes();
|
||||
|
||||
|
||||
args.elementsPerPacket++;
|
||||
|
||||
if (bytesLeftToRead >= (int)sizeof(numberOfEntities)) {
|
||||
|
@ -947,7 +945,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
|
|||
entityItem->recordCreationTime();
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Recieved packet for previously deleted entity [" <<
|
||||
qDebug() << "Recieved packet for previously deleted entity [" <<
|
||||
entityItem->getID() << "] ignoring. (inside " << __FUNCTION__ << ")";
|
||||
}
|
||||
}
|
||||
|
@ -959,7 +957,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
|
@ -990,7 +988,7 @@ bool EntityTreeElement::pruneChildren() {
|
|||
bool somethingPruned = false;
|
||||
for (int childIndex = 0; childIndex < NUMBER_OF_CHILDREN; childIndex++) {
|
||||
EntityTreeElementPointer child = getChildAtIndex(childIndex);
|
||||
|
||||
|
||||
// if my child is a leaf, but has no entities, then it's safe to delete my child
|
||||
if (child && child->isLeaf() && !child->hasEntities()) {
|
||||
deleteChildAtIndex(childIndex);
|
||||
|
@ -1040,4 +1038,4 @@ void EntityTreeElement::debugDump() {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -21,6 +21,12 @@
|
|||
#include "EntityTreeElement.h"
|
||||
#include "LightEntityItem.h"
|
||||
|
||||
const bool LightEntityItem::DEFAULT_IS_SPOTLIGHT = false;
|
||||
const float LightEntityItem::DEFAULT_INTENSITY = 1.0f;
|
||||
const float LightEntityItem::DEFAULT_FALLOFF_RADIUS = 0.1f;
|
||||
const float LightEntityItem::DEFAULT_EXPONENT = 0.0f;
|
||||
const float LightEntityItem::DEFAULT_CUTOFF = PI / 2.0f;
|
||||
|
||||
bool LightEntityItem::_lightsArePickable = false;
|
||||
|
||||
EntityItemPointer LightEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
|
@ -32,12 +38,7 @@ EntityItemPointer LightEntityItem::factory(const EntityItemID& entityID, const E
|
|||
// our non-pure virtual subclass for now...
|
||||
LightEntityItem::LightEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) {
|
||||
_type = EntityTypes::Light;
|
||||
|
||||
// default property values
|
||||
_color[RED_INDEX] = _color[GREEN_INDEX] = _color[BLUE_INDEX] = 0;
|
||||
_intensity = 1.0f;
|
||||
_exponent = 0.0f;
|
||||
_cutoff = PI;
|
||||
}
|
||||
|
||||
void LightEntityItem::setDimensions(const glm::vec3& value) {
|
||||
|
@ -62,10 +63,15 @@ EntityItemProperties LightEntityItem::getProperties(EntityPropertyFlags desiredP
|
|||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(intensity, getIntensity);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(exponent, getExponent);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cutoff, getCutoff);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(falloffRadius, getFalloffRadius);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
void LightEntityItem::setFalloffRadius(float value) {
|
||||
_falloffRadius = glm::max(value, 0.0f);
|
||||
}
|
||||
|
||||
void LightEntityItem::setIsSpotlight(bool value) {
|
||||
if (value != _isSpotlight) {
|
||||
_isSpotlight = value;
|
||||
|
@ -101,6 +107,7 @@ bool LightEntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(intensity, setIntensity);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(exponent, setExponent);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cutoff, setCutoff);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(falloffRadius, setFalloffRadius);
|
||||
|
||||
if (somethingChanged) {
|
||||
bool wantDebug = false;
|
||||
|
@ -150,6 +157,7 @@ int LightEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
|||
READ_ENTITY_PROPERTY(PROP_INTENSITY, float, setIntensity);
|
||||
READ_ENTITY_PROPERTY(PROP_EXPONENT, float, setExponent);
|
||||
READ_ENTITY_PROPERTY(PROP_CUTOFF, float, setCutoff);
|
||||
READ_ENTITY_PROPERTY(PROP_FALLOFF_RADIUS, float, setFalloffRadius);
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
|
@ -164,6 +172,7 @@ EntityPropertyFlags LightEntityItem::getEntityProperties(EncodeBitstreamParams&
|
|||
requestedProperties += PROP_INTENSITY;
|
||||
requestedProperties += PROP_EXPONENT;
|
||||
requestedProperties += PROP_CUTOFF;
|
||||
requestedProperties += PROP_FALLOFF_RADIUS;
|
||||
return requestedProperties;
|
||||
}
|
||||
|
||||
|
@ -181,4 +190,5 @@ void LightEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit
|
|||
APPEND_ENTITY_PROPERTY(PROP_INTENSITY, getIntensity());
|
||||
APPEND_ENTITY_PROPERTY(PROP_EXPONENT, getExponent());
|
||||
APPEND_ENTITY_PROPERTY(PROP_CUTOFF, getCutoff());
|
||||
APPEND_ENTITY_PROPERTY(PROP_FALLOFF_RADIUS, getFalloffRadius());
|
||||
}
|
||||
|
|
|
@ -16,6 +16,12 @@
|
|||
|
||||
class LightEntityItem : public EntityItem {
|
||||
public:
|
||||
static const bool DEFAULT_IS_SPOTLIGHT;
|
||||
static const float DEFAULT_INTENSITY;
|
||||
static const float DEFAULT_FALLOFF_RADIUS;
|
||||
static const float DEFAULT_EXPONENT;
|
||||
static const float DEFAULT_CUTOFF;
|
||||
|
||||
static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
|
||||
LightEntityItem(const EntityItemID& entityItemID);
|
||||
|
@ -65,6 +71,9 @@ public:
|
|||
float getIntensity() const { return _intensity; }
|
||||
void setIntensity(float value) { _intensity = value; }
|
||||
|
||||
float getFalloffRadius() const { return _falloffRadius; }
|
||||
void setFalloffRadius(float value);
|
||||
|
||||
float getExponent() const { return _exponent; }
|
||||
void setExponent(float value) { _exponent = value; }
|
||||
|
||||
|
@ -78,10 +87,11 @@ protected:
|
|||
|
||||
// properties of a light
|
||||
rgbColor _color;
|
||||
bool _isSpotlight;
|
||||
float _intensity;
|
||||
float _exponent;
|
||||
float _cutoff;
|
||||
bool _isSpotlight { DEFAULT_IS_SPOTLIGHT };
|
||||
float _intensity { DEFAULT_INTENSITY };
|
||||
float _falloffRadius { DEFAULT_FALLOFF_RADIUS };
|
||||
float _exponent { DEFAULT_EXPONENT };
|
||||
float _cutoff { DEFAULT_CUTOFF };
|
||||
|
||||
static bool _lightsArePickable;
|
||||
};
|
||||
|
|
|
@ -18,51 +18,69 @@
|
|||
#include "EntityItem.h"
|
||||
#include "EntitiesLogging.h"
|
||||
|
||||
const quint64 MIN_SIMULATION_OWNERSHIP_UPDATE_PERIOD = 2 * USECS_PER_SECOND;
|
||||
|
||||
void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) {
|
||||
if (_entitiesWithSimulator.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (now < _nextSimulationExpiry) {
|
||||
// nothing has expired yet
|
||||
return;
|
||||
}
|
||||
|
||||
// If an Entity has a simulation owner but there has been no update for a while: clear the owner.
|
||||
// If an Entity goes ownerless for too long: zero velocity and remove from _entitiesWithSimulator.
|
||||
_nextSimulationExpiry = now + MIN_SIMULATION_OWNERSHIP_UPDATE_PERIOD;
|
||||
const quint64 MAX_OWNERLESS_PERIOD = 2 * USECS_PER_SECOND;
|
||||
|
||||
void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
SetOfEntities::iterator itemItr = _entitiesWithSimulator.begin();
|
||||
while (itemItr != _entitiesWithSimulator.end()) {
|
||||
SetOfEntities::iterator itemItr = _entitiesWithSimulationOwner.begin();
|
||||
while (itemItr != _entitiesWithSimulationOwner.end()) {
|
||||
EntityItemPointer entity = *itemItr;
|
||||
quint64 expiry = entity->getLastChangedOnServer() + MIN_SIMULATION_OWNERSHIP_UPDATE_PERIOD;
|
||||
if (expiry < now) {
|
||||
if (entity->getSimulatorID().isNull()) {
|
||||
// no simulators are volunteering
|
||||
// zero the velocity on this entity so that it doesn't drift far away
|
||||
entity->setVelocity(Vectors::ZERO);
|
||||
entity->setAngularVelocity(Vectors::ZERO);
|
||||
entity->setAcceleration(Vectors::ZERO);
|
||||
// remove from list
|
||||
itemItr = _entitiesWithSimulator.erase(itemItr);
|
||||
continue;
|
||||
} else {
|
||||
// the simulator has stopped updating this object
|
||||
// clear ownership and restart timer, giving nearby simulators time to volunteer
|
||||
qCDebug(entities) << "auto-removing simulation owner " << entity->getSimulatorID();
|
||||
entity->clearSimulationOwnership();
|
||||
if (entity->getSimulatorID() == ownerID) {
|
||||
// the simulator has abandonded this object --> remove from owned list
|
||||
qCDebug(entities) << "auto-removing simulation owner " << entity->getSimulatorID();
|
||||
itemItr = _entitiesWithSimulationOwner.erase(itemItr);
|
||||
|
||||
if (entity->getDynamic() && entity->hasLocalVelocity()) {
|
||||
// it is still moving dynamically --> add to orphaned list
|
||||
_entitiesThatNeedSimulationOwner.insert(entity);
|
||||
quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
|
||||
if (expiry < _nextOwnerlessExpiry) {
|
||||
_nextOwnerlessExpiry = expiry;
|
||||
}
|
||||
}
|
||||
|
||||
// remove ownership and dirty all the tree elements that contain the it
|
||||
entity->clearSimulationOwnership();
|
||||
entity->markAsChangedOnServer();
|
||||
// dirty all the tree elements that contain the entity
|
||||
DirtyOctreeElementOperator op(entity->getElement());
|
||||
getEntityTree()->recurseTreeWithOperator(&op);
|
||||
} else if (expiry < _nextSimulationExpiry) {
|
||||
_nextSimulationExpiry = expiry;
|
||||
} else {
|
||||
++itemItr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) {
|
||||
if (now > _nextOwnerlessExpiry) {
|
||||
// search for ownerless objects that have expired
|
||||
QMutexLocker lock(&_mutex);
|
||||
_nextOwnerlessExpiry = -1;
|
||||
SetOfEntities::iterator itemItr = _entitiesThatNeedSimulationOwner.begin();
|
||||
while (itemItr != _entitiesThatNeedSimulationOwner.end()) {
|
||||
EntityItemPointer entity = *itemItr;
|
||||
quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
|
||||
if (expiry < now) {
|
||||
// no simulators have volunteered ownership --> remove from list
|
||||
itemItr = _entitiesThatNeedSimulationOwner.erase(itemItr);
|
||||
|
||||
if (entity->getSimulatorID().isNull() && entity->getDynamic() && entity->hasLocalVelocity()) {
|
||||
// zero the derivatives
|
||||
entity->setVelocity(Vectors::ZERO);
|
||||
entity->setAngularVelocity(Vectors::ZERO);
|
||||
entity->setAcceleration(Vectors::ZERO);
|
||||
|
||||
// dirty all the tree elements that contain it
|
||||
entity->markAsChangedOnServer();
|
||||
DirtyOctreeElementOperator op(entity->getElement());
|
||||
getEntityTree()->recurseTreeWithOperator(&op);
|
||||
}
|
||||
} else {
|
||||
++itemItr;
|
||||
if (expiry < _nextOwnerlessExpiry) {
|
||||
_nextOwnerlessExpiry = expiry;
|
||||
}
|
||||
}
|
||||
}
|
||||
++itemItr;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,26 +88,47 @@ void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) {
|
|||
EntitySimulation::addEntityInternal(entity);
|
||||
if (!entity->getSimulatorID().isNull()) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
_entitiesWithSimulator.insert(entity);
|
||||
_entitiesWithSimulationOwner.insert(entity);
|
||||
} else if (entity->getDynamic() && entity->hasLocalVelocity()) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
_entitiesThatNeedSimulationOwner.insert(entity);
|
||||
quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
|
||||
if (expiry < _nextOwnerlessExpiry) {
|
||||
_nextOwnerlessExpiry = expiry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleEntitySimulation::removeEntityInternal(EntityItemPointer entity) {
|
||||
EntitySimulation::removeEntityInternal(entity);
|
||||
QMutexLocker lock(&_mutex);
|
||||
_entitiesWithSimulator.remove(entity);
|
||||
_entitiesWithSimulationOwner.remove(entity);
|
||||
_entitiesThatNeedSimulationOwner.remove(entity);
|
||||
}
|
||||
|
||||
void SimpleEntitySimulation::changeEntityInternal(EntityItemPointer entity) {
|
||||
EntitySimulation::changeEntityInternal(entity);
|
||||
if (!entity->getSimulatorID().isNull()) {
|
||||
if (entity->getSimulatorID().isNull()) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
_entitiesWithSimulator.insert(entity);
|
||||
_entitiesWithSimulationOwner.remove(entity);
|
||||
if (entity->getDynamic() && entity->hasLocalVelocity()) {
|
||||
_entitiesThatNeedSimulationOwner.insert(entity);
|
||||
quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
|
||||
if (expiry < _nextOwnerlessExpiry) {
|
||||
_nextOwnerlessExpiry = expiry;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QMutexLocker lock(&_mutex);
|
||||
_entitiesWithSimulationOwner.insert(entity);
|
||||
_entitiesThatNeedSimulationOwner.remove(entity);
|
||||
}
|
||||
entity->clearDirtyFlags();
|
||||
}
|
||||
|
||||
void SimpleEntitySimulation::clearEntitiesInternal() {
|
||||
_entitiesWithSimulator.clear();
|
||||
QMutexLocker lock(&_mutex);
|
||||
_entitiesWithSimulationOwner.clear();
|
||||
_entitiesThatNeedSimulationOwner.clear();
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@ public:
|
|||
SimpleEntitySimulation() : EntitySimulation() { }
|
||||
virtual ~SimpleEntitySimulation() { clearEntitiesInternal(); }
|
||||
|
||||
void clearOwnership(const QUuid& ownerID);
|
||||
|
||||
protected:
|
||||
virtual void updateEntitiesInternal(const quint64& now) override;
|
||||
virtual void addEntityInternal(EntityItemPointer entity) override;
|
||||
|
@ -28,8 +30,9 @@ protected:
|
|||
virtual void changeEntityInternal(EntityItemPointer entity) override;
|
||||
virtual void clearEntitiesInternal() override;
|
||||
|
||||
SetOfEntities _entitiesWithSimulator;
|
||||
quint64 _nextSimulationExpiry { 0 };
|
||||
SetOfEntities _entitiesWithSimulationOwner;
|
||||
SetOfEntities _entitiesThatNeedSimulationOwner;
|
||||
quint64 _nextOwnerlessExpiry { 0 };
|
||||
};
|
||||
|
||||
#endif // hifi_SimpleEntitySimulation_h
|
||||
|
|
|
@ -21,6 +21,7 @@ Framebuffer::~Framebuffer() {
|
|||
Framebuffer* Framebuffer::create() {
|
||||
auto framebuffer = new Framebuffer();
|
||||
framebuffer->_renderBuffers.resize(MAX_NUM_RENDER_BUFFERS);
|
||||
framebuffer->_colorStamps.resize(MAX_NUM_RENDER_BUFFERS, 0);
|
||||
return framebuffer;
|
||||
}
|
||||
|
||||
|
@ -174,6 +175,8 @@ int Framebuffer::setRenderBuffer(uint32 slot, const TexturePointer& texture, uin
|
|||
}
|
||||
}
|
||||
|
||||
++_colorStamps[slot];
|
||||
|
||||
updateSize(texture);
|
||||
|
||||
// assign the new one
|
||||
|
@ -190,6 +193,7 @@ int Framebuffer::setRenderBuffer(uint32 slot, const TexturePointer& texture, uin
|
|||
}
|
||||
|
||||
void Framebuffer::removeRenderBuffers() {
|
||||
|
||||
if (isSwapchain()) {
|
||||
return;
|
||||
}
|
||||
|
@ -230,6 +234,7 @@ uint32 Framebuffer::getRenderBufferSubresource(uint32 slot) const {
|
|||
}
|
||||
|
||||
bool Framebuffer::setDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource) {
|
||||
++_depthStamp;
|
||||
if (isSwapchain()) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -135,10 +135,15 @@ public:
|
|||
static uint32 getMaxNumRenderBuffers() { return MAX_NUM_RENDER_BUFFERS; }
|
||||
|
||||
const GPUObjectPointer gpuObject {};
|
||||
|
||||
|
||||
Stamp getDepthStamp() const { return _depthStamp; }
|
||||
const std::vector<Stamp>& getColorStamps() const { return _colorStamps; }
|
||||
|
||||
protected:
|
||||
SwapchainPointer _swapchain;
|
||||
|
||||
Stamp _depthStamp { 0 };
|
||||
std::vector<Stamp> _colorStamps;
|
||||
TextureViews _renderBuffers;
|
||||
TextureView _depthStencilBuffer;
|
||||
|
||||
|
|
|
@ -173,6 +173,9 @@ public:
|
|||
public:
|
||||
GLuint _fbo = 0;
|
||||
std::vector<GLenum> _colorBuffers;
|
||||
Stamp _depthStamp { 0 };
|
||||
std::vector<Stamp> _colorStamps;
|
||||
|
||||
|
||||
GLFramebuffer();
|
||||
~GLFramebuffer();
|
||||
|
|
|
@ -27,8 +27,15 @@ GLBackend::GLFramebuffer::~GLFramebuffer() {
|
|||
GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffer) {
|
||||
GLFramebuffer* object = Backend::getGPUObject<GLBackend::GLFramebuffer>(framebuffer);
|
||||
|
||||
bool needsUpate { false };
|
||||
if (!object ||
|
||||
framebuffer.getDepthStamp() != object->_depthStamp ||
|
||||
framebuffer.getColorStamps() != object->_colorStamps) {
|
||||
needsUpate = true;
|
||||
}
|
||||
|
||||
// If GPU object already created and in sync
|
||||
if (object) {
|
||||
if (!needsUpate) {
|
||||
return object;
|
||||
} else if (framebuffer.isEmpty()) {
|
||||
// NO framebuffer definition yet so let's avoid thinking
|
||||
|
@ -37,94 +44,112 @@ GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffe
|
|||
|
||||
// need to have a gpu object?
|
||||
if (!object) {
|
||||
GLint currentFBO;
|
||||
// All is green, assign the gpuobject to the Framebuffer
|
||||
object = new GLFramebuffer();
|
||||
Backend::setGPUObject(framebuffer, object);
|
||||
glGenFramebuffers(1, &object->_fbo);
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
if (needsUpate) {
|
||||
GLint currentFBO = -1;
|
||||
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤tFBO);
|
||||
|
||||
GLuint fbo;
|
||||
glGenFramebuffers(1, &fbo);
|
||||
(void) CHECK_GL_ERROR();
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, object->_fbo);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
GLTexture* gltexture = nullptr;
|
||||
TexturePointer surface;
|
||||
if (framebuffer.getColorStamps() != object->_colorStamps) {
|
||||
if (framebuffer.hasColor()) {
|
||||
object->_colorBuffers.clear();
|
||||
static const GLenum colorAttachments[] = {
|
||||
GL_COLOR_ATTACHMENT0,
|
||||
GL_COLOR_ATTACHMENT1,
|
||||
GL_COLOR_ATTACHMENT2,
|
||||
GL_COLOR_ATTACHMENT3,
|
||||
GL_COLOR_ATTACHMENT4,
|
||||
GL_COLOR_ATTACHMENT5,
|
||||
GL_COLOR_ATTACHMENT6,
|
||||
GL_COLOR_ATTACHMENT7,
|
||||
GL_COLOR_ATTACHMENT8,
|
||||
GL_COLOR_ATTACHMENT9,
|
||||
GL_COLOR_ATTACHMENT10,
|
||||
GL_COLOR_ATTACHMENT11,
|
||||
GL_COLOR_ATTACHMENT12,
|
||||
GL_COLOR_ATTACHMENT13,
|
||||
GL_COLOR_ATTACHMENT14,
|
||||
GL_COLOR_ATTACHMENT15 };
|
||||
|
||||
std::vector<GLenum> colorBuffers;
|
||||
if (framebuffer.hasColor()) {
|
||||
static const GLenum colorAttachments[] = {
|
||||
GL_COLOR_ATTACHMENT0,
|
||||
GL_COLOR_ATTACHMENT1,
|
||||
GL_COLOR_ATTACHMENT2,
|
||||
GL_COLOR_ATTACHMENT3,
|
||||
GL_COLOR_ATTACHMENT4,
|
||||
GL_COLOR_ATTACHMENT5,
|
||||
GL_COLOR_ATTACHMENT6,
|
||||
GL_COLOR_ATTACHMENT7,
|
||||
GL_COLOR_ATTACHMENT8,
|
||||
GL_COLOR_ATTACHMENT9,
|
||||
GL_COLOR_ATTACHMENT10,
|
||||
GL_COLOR_ATTACHMENT11,
|
||||
GL_COLOR_ATTACHMENT12,
|
||||
GL_COLOR_ATTACHMENT13,
|
||||
GL_COLOR_ATTACHMENT14,
|
||||
GL_COLOR_ATTACHMENT15 };
|
||||
int unit = 0;
|
||||
for (auto& b : framebuffer.getRenderBuffers()) {
|
||||
surface = b._texture;
|
||||
if (surface) {
|
||||
gltexture = GLBackend::syncGPUObject(*surface);
|
||||
} else {
|
||||
gltexture = nullptr;
|
||||
}
|
||||
|
||||
int unit = 0;
|
||||
for (auto& b : framebuffer.getRenderBuffers()) {
|
||||
auto surface = b._texture;
|
||||
if (surface) {
|
||||
auto gltexture = GLBackend::syncGPUObject(*surface);
|
||||
if (gltexture) {
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, gltexture->_texture, 0);
|
||||
object->_colorBuffers.push_back(colorAttachments[unit]);
|
||||
} else {
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, 0, 0);
|
||||
}
|
||||
colorBuffers.push_back(colorAttachments[unit]);
|
||||
unit++;
|
||||
}
|
||||
}
|
||||
}
|
||||
#if (GPU_FEATURE_PROFILE == GPU_LEGACY)
|
||||
// for reasons that i don't understand yet, it seems that on mac gl, a fbo must have a color buffer...
|
||||
else {
|
||||
GLuint renderBuffer = 0;
|
||||
glGenRenderbuffers(1, &renderBuffer);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, framebuffer.getWidth(), framebuffer.getHeight());
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer);
|
||||
(void) CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
#endif
|
||||
|
||||
|
||||
if (framebuffer.hasDepthStencil()) {
|
||||
auto surface = framebuffer.getDepthStencilBuffer();
|
||||
if (surface) {
|
||||
auto gltexture = GLBackend::syncGPUObject(*surface);
|
||||
if (gltexture) {
|
||||
GLenum attachement = GL_DEPTH_STENCIL_ATTACHMENT;
|
||||
if (!framebuffer.hasStencil()) {
|
||||
attachement = GL_DEPTH_ATTACHMENT;
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0);
|
||||
} else if (!framebuffer.hasDepth()) {
|
||||
attachement = GL_STENCIL_ATTACHMENT;
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0);
|
||||
} else {
|
||||
attachement = GL_DEPTH_STENCIL_ATTACHMENT;
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0);
|
||||
}
|
||||
(void) CHECK_GL_ERROR();
|
||||
}
|
||||
#if (GPU_FEATURE_PROFILE == GPU_LEGACY)
|
||||
// for reasons that i don't understand yet, it seems that on mac gl, a fbo must have a color buffer...
|
||||
else {
|
||||
GLuint renderBuffer = 0;
|
||||
glGenRenderbuffers(1, &renderBuffer);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, framebuffer.getWidth(), framebuffer.getHeight());
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer);
|
||||
(void) CHECK_GL_ERROR();
|
||||
}
|
||||
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
#endif
|
||||
object->_colorStamps = framebuffer.getColorStamps();
|
||||
}
|
||||
|
||||
GLenum attachement = GL_DEPTH_STENCIL_ATTACHMENT;
|
||||
if (!framebuffer.hasStencil()) {
|
||||
attachement = GL_DEPTH_ATTACHMENT;
|
||||
} else if (!framebuffer.hasDepth()) {
|
||||
attachement = GL_STENCIL_ATTACHMENT;
|
||||
}
|
||||
|
||||
if (framebuffer.getDepthStamp() != object->_depthStamp) {
|
||||
auto surface = framebuffer.getDepthStencilBuffer();
|
||||
if (framebuffer.hasDepthStencil() && surface) {
|
||||
gltexture = GLBackend::syncGPUObject(*surface);
|
||||
}
|
||||
|
||||
if (gltexture) {
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0);
|
||||
} else {
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, 0, 0);
|
||||
}
|
||||
object->_depthStamp = framebuffer.getDepthStamp();
|
||||
}
|
||||
|
||||
|
||||
// Last but not least, define where we draw
|
||||
if (!colorBuffers.empty()) {
|
||||
glDrawBuffers((GLsizei)colorBuffers.size(), colorBuffers.data());
|
||||
if (!object->_colorBuffers.empty()) {
|
||||
glDrawBuffers((GLsizei)object->_colorBuffers.size(), object->_colorBuffers.data());
|
||||
} else {
|
||||
glDrawBuffer( GL_NONE );
|
||||
}
|
||||
|
||||
// Now check for completness
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
|
||||
// restore the current framebuffer
|
||||
if (currentFBO != -1) {
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, currentFBO);
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
switch (status) {
|
||||
case GL_FRAMEBUFFER_COMPLETE :
|
||||
|
@ -147,20 +172,10 @@ GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffe
|
|||
qCDebug(gpulogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED.";
|
||||
break;
|
||||
}
|
||||
if (!result && fbo) {
|
||||
glDeleteFramebuffers( 1, &fbo );
|
||||
if (!result && object->_fbo) {
|
||||
glDeleteFramebuffers(1, &object->_fbo);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
// All is green, assign the gpuobject to the Framebuffer
|
||||
object = new GLFramebuffer();
|
||||
object->_fbo = fbo;
|
||||
object->_colorBuffers = colorBuffers;
|
||||
Backend::setGPUObject(framebuffer, object);
|
||||
|
||||
// restore the current framebuffer
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, currentFBO);
|
||||
}
|
||||
|
||||
return object;
|
||||
|
|
|
@ -12,19 +12,18 @@
|
|||
|
||||
using namespace model;
|
||||
|
||||
Light::Light() :
|
||||
_flags(0),
|
||||
_schemaBuffer(),
|
||||
_transform() {
|
||||
Light::Light() {
|
||||
// only if created from nothing shall we create the Buffer to store the properties
|
||||
Schema schema;
|
||||
_schemaBuffer = std::make_shared<gpu::Buffer>(sizeof(Schema), (const gpu::Byte*) &schema);
|
||||
updateLightRadius();
|
||||
}
|
||||
|
||||
Light::Light(const Light& light) :
|
||||
_flags(light._flags),
|
||||
_schemaBuffer(light._schemaBuffer),
|
||||
_transform(light._transform) {
|
||||
_transform(light._transform)
|
||||
{
|
||||
}
|
||||
|
||||
Light& Light::operator= (const Light& light) {
|
||||
|
@ -70,18 +69,36 @@ void Light::setAmbientIntensity(float intensity) {
|
|||
editSchema()._ambientIntensity = intensity;
|
||||
}
|
||||
|
||||
void Light::setFalloffRadius(float radius) {
|
||||
if (radius <= 0.0f) {
|
||||
radius = 0.1f;
|
||||
}
|
||||
editSchema()._attenuation.x = radius;
|
||||
updateLightRadius();
|
||||
}
|
||||
void Light::setMaximumRadius(float radius) {
|
||||
if (radius <= 0.f) {
|
||||
radius = 1.0f;
|
||||
}
|
||||
editSchema()._attenuation.w = radius;
|
||||
editSchema()._attenuation.y = radius;
|
||||
updateLightRadius();
|
||||
}
|
||||
|
||||
void Light::updateLightRadius() {
|
||||
float CutOffIntensityRatio = 0.05f;
|
||||
float surfaceRadius = getMaximumRadius() / (sqrtf((getIntensity() * std::max(std::max(getColor().x, getColor().y), getColor().z)) / CutOffIntensityRatio) - 1.0f);
|
||||
editSchema()._attenuation = Vec4(surfaceRadius, 1.0f/surfaceRadius, CutOffIntensityRatio, getMaximumRadius());
|
||||
// This function relies on the attenuation equation:
|
||||
// I = Li / (1 + (d + Lr)/Lr)^2
|
||||
// where I = calculated intensity, Li = light intensity, Lr = light falloff radius, d = distance from surface
|
||||
// see: https://imdoingitwrong.wordpress.com/2011/01/31/light-attenuation/
|
||||
// note that falloff radius replaces surface radius in linked example
|
||||
// This equation is biased back by Lr so that all lights act as true points, regardless of surface radii
|
||||
|
||||
const float MIN_CUTOFF_INTENSITY = 0.001f;
|
||||
// Get cutoff radius at minimum intensity
|
||||
float intensity = getIntensity() * std::max(std::max(getColor().x, getColor().y), getColor().z);
|
||||
float cutoffRadius = getFalloffRadius() * ((glm::sqrt(intensity / MIN_CUTOFF_INTENSITY) - 1) - 1);
|
||||
|
||||
// If it is less than max radius, store it to buffer to avoid extra shading
|
||||
editSchema()._attenuation.z = std::min(getMaximumRadius(), cutoffRadius);
|
||||
}
|
||||
|
||||
#include <math.h>
|
||||
|
|
|
@ -74,8 +74,17 @@ public:
|
|||
|
||||
bool isRanged() const { return (getType() == POINT) || (getType() == SPOT ); }
|
||||
|
||||
// FalloffRradius is the physical radius of the light sphere through which energy shines,
|
||||
// expressed in meters. It is used only to calculate the falloff curve of the light.
|
||||
// Actual rendered lights will all have surface radii approaching 0.
|
||||
void setFalloffRadius(float radius);
|
||||
float getFalloffRadius() const { return getSchema()._attenuation.x; }
|
||||
|
||||
// Maximum radius is the cutoff radius of the light energy, expressed in meters.
|
||||
// It is used to bound light entities, and *will not* affect the falloff curve of the light.
|
||||
// Setting it low will result in a noticeable cutoff.
|
||||
void setMaximumRadius(float radius);
|
||||
float getMaximumRadius() const { return getSchema()._attenuation.w; }
|
||||
float getMaximumRadius() const { return getSchema()._attenuation.y; }
|
||||
|
||||
// Spot properties
|
||||
bool isSpot() const { return getType() == SPOT; }
|
||||
|
@ -107,7 +116,7 @@ public:
|
|||
float _ambientIntensity{0.0f};
|
||||
Color _color{1.0f};
|
||||
float _intensity{1.0f};
|
||||
Vec4 _attenuation{1.0f};
|
||||
Vec4 _attenuation{0.1f, 1.0f, 0.0f, 0.0f};
|
||||
Vec4 _spot{0.0f, 0.0f, 0.0f, 0.0f};
|
||||
Vec4 _shadow{0.0f};
|
||||
|
||||
|
@ -120,7 +129,7 @@ public:
|
|||
|
||||
protected:
|
||||
|
||||
Flags _flags;
|
||||
Flags _flags{ 0 };
|
||||
UniformBufferView _schemaBuffer;
|
||||
Transform _transform;
|
||||
|
||||
|
|
|
@ -66,14 +66,6 @@ vec3 getLightColor(Light l) { return l._color.rgb; }
|
|||
float getLightIntensity(Light l) { return l._color.w; }
|
||||
float getLightAmbientIntensity(Light l) { return l._direction.w; }
|
||||
|
||||
float evalLightAttenuation(Light l, float r) {
|
||||
float d = max(r - l._attenuation.x, 0.0);
|
||||
float denom = d * l._attenuation.y + 1.0;
|
||||
float attenuation = 1.0 / (denom * denom);
|
||||
return max((attenuation - l._attenuation.z)/(1.0 - l._attenuation.z), 0.0);
|
||||
// return clamp(1.0/(l._attenuation.x + l._attenuation.y * r + l._attenuation.z * r * r), 0.0, 1.0);
|
||||
}
|
||||
|
||||
float getLightSpotAngleCos(Light l) {
|
||||
return l._spot.x;
|
||||
}
|
||||
|
@ -86,22 +78,33 @@ float evalLightSpotAttenuation(Light l, float cosA) {
|
|||
return pow(cosA, l._spot.w);
|
||||
}
|
||||
|
||||
float getLightSquareRadius(Light l) {
|
||||
return l._attenuation.w * l._attenuation.w;
|
||||
}
|
||||
|
||||
float getLightRadius(Light l) {
|
||||
return l._attenuation.w;
|
||||
return l._attenuation.x;
|
||||
}
|
||||
|
||||
float getLightAttenuationCutoff(Light l) {
|
||||
float getLightSquareRadius(Light l) {
|
||||
return getLightRadius(l) * getLightRadius(l);
|
||||
}
|
||||
|
||||
float getLightCutoffRadius(Light l) {
|
||||
return l._attenuation.z;
|
||||
}
|
||||
|
||||
float getLightCutoffSquareRadius(Light l) {
|
||||
return getLightCutoffRadius(l) * getLightCutoffRadius(l);
|
||||
}
|
||||
|
||||
float getLightShowContour(Light l) {
|
||||
return l._control.w;
|
||||
}
|
||||
|
||||
float evalLightAttenuation(Light l, float d) {
|
||||
float radius = getLightRadius(l);
|
||||
float denom = d / radius + 1.0;
|
||||
float attenuation = min(1.0, 1.0 / (denom * denom));
|
||||
return attenuation;
|
||||
}
|
||||
|
||||
SphericalHarmonics getLightAmbientSphere(Light l) {
|
||||
return l._ambientSphere;
|
||||
}
|
||||
|
|
|
@ -142,6 +142,8 @@ SunSkyStage::SunSkyStage() :
|
|||
_skybox(std::make_shared<Skybox>())
|
||||
{
|
||||
_sunLight->setType(Light::SUN);
|
||||
// Default ambient sphere (for lack of skybox)
|
||||
_sunLight->setAmbientSpherePreset(gpu::SphericalHarmonics::Preset::OLD_TOWN_SQUARE);
|
||||
|
||||
setSunIntensity(1.0f);
|
||||
setSunAmbientIntensity(0.5f);
|
||||
|
|
|
@ -12,12 +12,10 @@
|
|||
#include <memory>
|
||||
|
||||
#include <QtCore/QDataStream>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QMap>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QStandardPaths>
|
||||
#include <QtCore/QUrlQuery>
|
||||
#include <QtNetwork/QHttpMultiPart>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
|
@ -62,12 +60,13 @@ JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, co
|
|||
updateReciever(updateReceiver),
|
||||
updateSlot(updateSlot)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
AccountManager::AccountManager() :
|
||||
_authURL(),
|
||||
_pendingCallbackMap()
|
||||
_pendingCallbackMap(),
|
||||
_accountInfo(),
|
||||
_shouldPersistToSettingsFile(true)
|
||||
{
|
||||
qRegisterMetaType<OAuthAccessToken>("OAuthAccessToken");
|
||||
qRegisterMetaTypeStreamOperators<OAuthAccessToken>("OAuthAccessToken");
|
||||
|
@ -81,6 +80,9 @@ AccountManager::AccountManager() :
|
|||
qRegisterMetaType<QHttpMultiPart*>("QHttpMultiPart*");
|
||||
|
||||
connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged);
|
||||
|
||||
// once we have a profile in account manager make sure we generate a new keypair
|
||||
connect(this, &AccountManager::profileChanged, this, &AccountManager::generateNewKeypair);
|
||||
}
|
||||
|
||||
const QString DOUBLE_SLASH_SUBSTITUTE = "slashslash";
|
||||
|
@ -91,9 +93,16 @@ void AccountManager::logout() {
|
|||
|
||||
emit balanceChanged(0);
|
||||
connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged);
|
||||
|
||||
// remove this account from the account settings file
|
||||
removeAccountFromFile();
|
||||
|
||||
if (_shouldPersistToSettingsFile) {
|
||||
QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE));
|
||||
QStringList path = QStringList() << ACCOUNTS_GROUP << keyURLString;
|
||||
Setting::Handle<DataServerAccountInfo>(path).remove();
|
||||
|
||||
qCDebug(networking) << "Removed account info for" << _authURL << "from in-memory accounts and .ini file";
|
||||
} else {
|
||||
qCDebug(networking) << "Cleared data server account info in account manager.";
|
||||
}
|
||||
|
||||
emit logoutComplete();
|
||||
// the username has changed to blank
|
||||
|
@ -115,83 +124,35 @@ void AccountManager::accountInfoBalanceChanged(qint64 newBalance) {
|
|||
emit balanceChanged(newBalance);
|
||||
}
|
||||
|
||||
QString accountFilePath() {
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/AccountInfo.bin";
|
||||
}
|
||||
|
||||
QVariantMap accountMapFromFile(bool& success) {
|
||||
QFile accountFile { accountFilePath() };
|
||||
|
||||
if (accountFile.open(QIODevice::ReadOnly)) {
|
||||
// grab the current QVariantMap from the settings file
|
||||
QDataStream readStream(&accountFile);
|
||||
QVariantMap accountMap;
|
||||
|
||||
readStream >> accountMap;
|
||||
|
||||
// close the file now that we have read the data
|
||||
accountFile.close();
|
||||
|
||||
success = true;
|
||||
|
||||
return accountMap;
|
||||
} else {
|
||||
// failed to open file, return empty QVariantMap
|
||||
// there was only an error if the account file existed when we tried to load it
|
||||
success = !accountFile.exists();
|
||||
|
||||
return QVariantMap();
|
||||
}
|
||||
}
|
||||
|
||||
void AccountManager::setAuthURL(const QUrl& authURL) {
|
||||
if (_authURL != authURL) {
|
||||
_authURL = authURL;
|
||||
|
||||
qCDebug(networking) << "AccountManager URL for authenticated requests has been changed to" << qPrintable(_authURL.toString());
|
||||
|
||||
// check if there are existing access tokens to load from settings
|
||||
QFile accountsFile { accountFilePath() };
|
||||
bool loadedMap = false;
|
||||
auto accountsMap = accountMapFromFile(loadedMap);
|
||||
|
||||
if (accountsFile.exists() && loadedMap) {
|
||||
// pull out the stored account info and store it in memory
|
||||
_accountInfo = accountsMap[_authURL.toString()].value<DataServerAccountInfo>();
|
||||
|
||||
qCDebug(networking) << "Found metaverse API account information for" << qPrintable(_authURL.toString());
|
||||
} else {
|
||||
// we didn't have a file - see if we can migrate old settings and store them in the new file
|
||||
|
||||
if (_shouldPersistToSettingsFile) {
|
||||
// check if there are existing access tokens to load from settings
|
||||
Settings settings;
|
||||
settings.beginGroup(ACCOUNTS_GROUP);
|
||||
|
||||
|
||||
foreach(const QString& key, settings.allKeys()) {
|
||||
// take a key copy to perform the double slash replacement
|
||||
QString keyCopy(key);
|
||||
QUrl keyURL(keyCopy.replace(DOUBLE_SLASH_SUBSTITUTE, "//"));
|
||||
|
||||
|
||||
if (keyURL == _authURL) {
|
||||
// pull out the stored access token and store it in memory
|
||||
_accountInfo = settings.value(key).value<DataServerAccountInfo>();
|
||||
|
||||
qCDebug(networking) << "Migrated an access token for" << qPrintable(keyURL.toString())
|
||||
<< "from previous settings file";
|
||||
qCDebug(networking) << "Found a data-server access token for" << qPrintable(keyURL.toString());
|
||||
|
||||
// profile info isn't guaranteed to be saved too
|
||||
if (_accountInfo.hasProfile()) {
|
||||
emit profileChanged();
|
||||
} else {
|
||||
requestProfile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_accountInfo.getAccessToken().token.isEmpty()) {
|
||||
qCWarning(networking) << "Unable to load account file. No existing account settings will be loaded.";
|
||||
} else {
|
||||
// persist the migrated settings to file
|
||||
persistAccountToFile();
|
||||
}
|
||||
}
|
||||
|
||||
if (_isAgent && !_accountInfo.getAccessToken().token.isEmpty() && !_accountInfo.hasProfile()) {
|
||||
// we are missing profile information, request it now
|
||||
requestProfile();
|
||||
}
|
||||
|
||||
// tell listeners that the auth endpoint has changed
|
||||
|
@ -338,11 +299,9 @@ void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) {
|
|||
|
||||
} else {
|
||||
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
|
||||
qCDebug(networking) << "Received JSON response from metaverse API that has no matching callback.";
|
||||
qCDebug(networking) << "Received JSON response from data-server that has no matching callback.";
|
||||
qCDebug(networking) << QJsonDocument::fromJson(requestReply->readAll());
|
||||
}
|
||||
|
||||
requestReply->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -358,69 +317,22 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) {
|
|||
_pendingCallbackMap.remove(requestReply);
|
||||
} else {
|
||||
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
|
||||
qCDebug(networking) << "Received error response from metaverse API that has no matching callback.";
|
||||
qCDebug(networking) << "Received error response from data-server that has no matching callback.";
|
||||
qCDebug(networking) << "Error" << requestReply->error() << "-" << requestReply->errorString();
|
||||
qCDebug(networking) << requestReply->readAll();
|
||||
}
|
||||
|
||||
requestReply->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
bool writeAccountMapToFile(const QVariantMap& accountMap) {
|
||||
// re-open the file and truncate it
|
||||
QFile accountFile { accountFilePath() };
|
||||
if (accountFile.open(QIODevice::WriteOnly)) {
|
||||
QDataStream writeStream(&accountFile);
|
||||
|
||||
// persist the updated account QVariantMap to file
|
||||
writeStream << accountMap;
|
||||
|
||||
// close the file with the newly persisted settings
|
||||
accountFile.close();
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
void AccountManager::persistAccountToSettings() {
|
||||
if (_shouldPersistToSettingsFile) {
|
||||
// store this access token into the local settings
|
||||
QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE));
|
||||
QStringList path = QStringList() << ACCOUNTS_GROUP << keyURLString;
|
||||
Setting::Handle<QVariant>(path).set(QVariant::fromValue(_accountInfo));
|
||||
}
|
||||
}
|
||||
|
||||
void AccountManager::persistAccountToFile() {
|
||||
|
||||
qCDebug(networking) << "Persisting AccountManager accounts to" << accountFilePath();
|
||||
|
||||
bool wasLoaded = false;
|
||||
auto accountMap = accountMapFromFile(wasLoaded);
|
||||
|
||||
if (wasLoaded) {
|
||||
// replace the current account information for this auth URL in the account map
|
||||
accountMap[_authURL.toString()] = QVariant::fromValue(_accountInfo);
|
||||
|
||||
// re-open the file and truncate it
|
||||
if (writeAccountMapToFile(accountMap)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
qCWarning(networking) << "Could not load accounts file - unable to persist account information to file.";
|
||||
}
|
||||
|
||||
void AccountManager::removeAccountFromFile() {
|
||||
bool wasLoaded = false;
|
||||
auto accountMap = accountMapFromFile(wasLoaded);
|
||||
|
||||
if (wasLoaded) {
|
||||
accountMap.remove(_authURL.toString());
|
||||
if (writeAccountMapToFile(accountMap)) {
|
||||
qCDebug(networking) << "Removed account info for" << _authURL << "from settings file.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
qCWarning(networking) << "Count not load accounts file - unable to remove account information for" << _authURL
|
||||
<< "from settings file.";
|
||||
}
|
||||
|
||||
bool AccountManager::hasValidAccessToken() {
|
||||
|
||||
if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) {
|
||||
|
@ -447,19 +359,16 @@ bool AccountManager::checkAndSignalForAccessToken() {
|
|||
}
|
||||
|
||||
void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) {
|
||||
// replace the account info access token with a new OAuthAccessToken
|
||||
// clear our current DataServerAccountInfo
|
||||
_accountInfo = DataServerAccountInfo();
|
||||
|
||||
// start the new account info with a new OAuthAccessToken
|
||||
OAuthAccessToken newOAuthToken;
|
||||
newOAuthToken.token = accessToken;
|
||||
|
||||
if (!accessToken.isEmpty()) {
|
||||
qCDebug(networking) << "Setting new AccountManager OAuth token. F2C:" << accessToken.left(2) << "L2C:" << accessToken.right(2);
|
||||
} else if (!_accountInfo.getAccessToken().token.isEmpty()) {
|
||||
qCDebug(networking) << "Clearing AccountManager OAuth token.";
|
||||
}
|
||||
|
||||
qCDebug(networking) << "Setting new account manager access token. F2C:" << accessToken.left(2) << "L2C:" << accessToken.right(2);
|
||||
|
||||
_accountInfo.setAccessToken(newOAuthToken);
|
||||
|
||||
persistAccountToFile();
|
||||
}
|
||||
|
||||
void AccountManager::requestAccessToken(const QString& login, const QString& password) {
|
||||
|
@ -514,7 +423,7 @@ void AccountManager::requestAccessTokenFinished() {
|
|||
|
||||
emit loginComplete(rootURL);
|
||||
|
||||
persistAccountToFile();
|
||||
persistAccountToSettings();
|
||||
|
||||
requestProfile();
|
||||
}
|
||||
|
@ -560,7 +469,7 @@ void AccountManager::requestProfileFinished() {
|
|||
emit usernameChanged(_accountInfo.getUsername());
|
||||
|
||||
// store the whole profile into the local settings
|
||||
persistAccountToFile();
|
||||
persistAccountToSettings();
|
||||
|
||||
} else {
|
||||
// TODO: error handling
|
||||
|
@ -573,141 +482,57 @@ void AccountManager::requestProfileError(QNetworkReply::NetworkError error) {
|
|||
qCDebug(networking) << "AccountManager requestProfileError - " << error;
|
||||
}
|
||||
|
||||
void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainID) {
|
||||
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "generateNewKeypair", Q_ARG(bool, isUserKeypair), Q_ARG(QUuid, domainID));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isUserKeypair && domainID.isNull()) {
|
||||
qCWarning(networking) << "AccountManager::generateNewKeypair called for domain keypair with no domain ID. Will not generate keypair.";
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure we don't already have an outbound keypair generation request
|
||||
if (!_isWaitingForKeypairResponse) {
|
||||
_isWaitingForKeypairResponse = true;
|
||||
|
||||
// clear the current private key
|
||||
qDebug() << "Clearing current private key in DataServerAccountInfo";
|
||||
_accountInfo.setPrivateKey(QByteArray());
|
||||
|
||||
// setup a new QThread to generate the keypair on, in case it takes a while
|
||||
QThread* generateThread = new QThread(this);
|
||||
generateThread->setObjectName("Account Manager Generator Thread");
|
||||
|
||||
// setup a keypair generator
|
||||
RSAKeypairGenerator* keypairGenerator = new RSAKeypairGenerator;
|
||||
|
||||
if (!isUserKeypair) {
|
||||
keypairGenerator->setDomainID(domainID);
|
||||
_accountInfo.setDomainID(domainID);
|
||||
}
|
||||
|
||||
// start keypair generation when the thread starts
|
||||
connect(generateThread, &QThread::started, keypairGenerator, &RSAKeypairGenerator::generateKeypair);
|
||||
|
||||
// handle success or failure of keypair generation
|
||||
connect(keypairGenerator, &RSAKeypairGenerator::generatedKeypair, this, &AccountManager::processGeneratedKeypair);
|
||||
connect(keypairGenerator, &RSAKeypairGenerator::errorGeneratingKeypair,
|
||||
this, &AccountManager::handleKeypairGenerationError);
|
||||
|
||||
connect(keypairGenerator, &QObject::destroyed, generateThread, &QThread::quit);
|
||||
connect(generateThread, &QThread::finished, generateThread, &QThread::deleteLater);
|
||||
|
||||
keypairGenerator->moveToThread(generateThread);
|
||||
|
||||
qCDebug(networking) << "Starting worker thread to generate 2048-bit RSA keypair.";
|
||||
generateThread->start();
|
||||
}
|
||||
}
|
||||
|
||||
void AccountManager::processGeneratedKeypair() {
|
||||
void AccountManager::generateNewKeypair() {
|
||||
// setup a new QThread to generate the keypair on, in case it takes a while
|
||||
QThread* generateThread = new QThread(this);
|
||||
generateThread->setObjectName("Account Manager Generator Thread");
|
||||
|
||||
qCDebug(networking) << "Generated 2048-bit RSA keypair. Uploading public key now.";
|
||||
|
||||
RSAKeypairGenerator* keypairGenerator = qobject_cast<RSAKeypairGenerator*>(sender());
|
||||
|
||||
if (keypairGenerator) {
|
||||
// hold the private key to later set our metaverse API account info if upload succeeds
|
||||
_pendingPrivateKey = keypairGenerator->getPrivateKey();
|
||||
|
||||
// upload the public key so data-web has an up-to-date key
|
||||
const QString USER_PUBLIC_KEY_UPDATE_PATH = "api/v1/user/public_key";
|
||||
const QString DOMAIN_PUBLIC_KEY_UPDATE_PATH = "api/v1/domains/%1/public_key";
|
||||
|
||||
QString uploadPath;
|
||||
if (keypairGenerator->getDomainID().isNull()) {
|
||||
uploadPath = USER_PUBLIC_KEY_UPDATE_PATH;
|
||||
} else {
|
||||
uploadPath = DOMAIN_PUBLIC_KEY_UPDATE_PATH.arg(uuidStringWithoutCurlyBraces(keypairGenerator->getDomainID()));
|
||||
}
|
||||
|
||||
// setup a multipart upload to send up the public key
|
||||
QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
||||
|
||||
QHttpPart keyPart;
|
||||
keyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
|
||||
keyPart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||
QVariant("form-data; name=\"public_key\"; filename=\"public_key\""));
|
||||
keyPart.setBody(keypairGenerator->getPublicKey());
|
||||
|
||||
requestMultiPart->append(keyPart);
|
||||
|
||||
// setup callback parameters so we know once the keypair upload has succeeded or failed
|
||||
JSONCallbackParameters callbackParameters;
|
||||
callbackParameters.jsonCallbackReceiver = this;
|
||||
callbackParameters.jsonCallbackMethod = "publicKeyUploadSucceeded";
|
||||
callbackParameters.errorCallbackReceiver = this;
|
||||
callbackParameters.errorCallbackMethod = "publicKeyUploadFailed";
|
||||
|
||||
sendRequest(uploadPath, AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation,
|
||||
callbackParameters, QByteArray(), requestMultiPart);
|
||||
|
||||
keypairGenerator->deleteLater();
|
||||
} else {
|
||||
qCWarning(networking) << "Expected processGeneratedKeypair to be called by a live RSAKeypairGenerator"
|
||||
<< "but the casted sender is NULL. Will not process generated keypair.";
|
||||
}
|
||||
// setup a keypair generator
|
||||
RSAKeypairGenerator* keypairGenerator = new RSAKeypairGenerator();
|
||||
|
||||
connect(generateThread, &QThread::started, keypairGenerator, &RSAKeypairGenerator::generateKeypair);
|
||||
connect(keypairGenerator, &RSAKeypairGenerator::generatedKeypair, this, &AccountManager::processGeneratedKeypair);
|
||||
connect(keypairGenerator, &RSAKeypairGenerator::errorGeneratingKeypair,
|
||||
this, &AccountManager::handleKeypairGenerationError);
|
||||
connect(keypairGenerator, &QObject::destroyed, generateThread, &QThread::quit);
|
||||
connect(generateThread, &QThread::finished, generateThread, &QThread::deleteLater);
|
||||
|
||||
keypairGenerator->moveToThread(generateThread);
|
||||
|
||||
qCDebug(networking) << "Starting worker thread to generate 2048-bit RSA key-pair.";
|
||||
generateThread->start();
|
||||
}
|
||||
|
||||
void AccountManager::publicKeyUploadSucceeded(QNetworkReply& reply) {
|
||||
qDebug() << "Uploaded public key to Metaverse API. RSA keypair generation is completed.";
|
||||
|
||||
// public key upload complete - store the matching private key and persist the account to settings
|
||||
_accountInfo.setPrivateKey(_pendingPrivateKey);
|
||||
_pendingPrivateKey.clear();
|
||||
persistAccountToFile();
|
||||
|
||||
// clear our waiting state
|
||||
_isWaitingForKeypairResponse = false;
|
||||
|
||||
emit newKeypair();
|
||||
|
||||
// delete the reply object now that we are done with it
|
||||
reply.deleteLater();
|
||||
}
|
||||
|
||||
void AccountManager::publicKeyUploadFailed(QNetworkReply& reply) {
|
||||
// the public key upload has failed
|
||||
qWarning() << "Public key upload failed from AccountManager" << reply.errorString();
|
||||
|
||||
// we aren't waiting for a response any longer
|
||||
_isWaitingForKeypairResponse = false;
|
||||
|
||||
// clear our pending private key
|
||||
_pendingPrivateKey.clear();
|
||||
|
||||
// delete the reply object now that we are done with it
|
||||
reply.deleteLater();
|
||||
void AccountManager::processGeneratedKeypair(const QByteArray& publicKey, const QByteArray& privateKey) {
|
||||
|
||||
qCDebug(networking) << "Generated 2048-bit RSA key-pair. Storing private key and uploading public key.";
|
||||
|
||||
// set the private key on our data-server account info
|
||||
_accountInfo.setPrivateKey(privateKey);
|
||||
persistAccountToSettings();
|
||||
|
||||
// upload the public key so data-web has an up-to-date key
|
||||
const QString PUBLIC_KEY_UPDATE_PATH = "api/v1/user/public_key";
|
||||
|
||||
// setup a multipart upload to send up the public key
|
||||
QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
||||
|
||||
QHttpPart keyPart;
|
||||
keyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
|
||||
keyPart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||
QVariant("form-data; name=\"public_key\"; filename=\"public_key\""));
|
||||
keyPart.setBody(publicKey);
|
||||
|
||||
requestMultiPart->append(keyPart);
|
||||
|
||||
sendRequest(PUBLIC_KEY_UPDATE_PATH, AccountManagerAuth::Required, QNetworkAccessManager::PutOperation,
|
||||
JSONCallbackParameters(), QByteArray(), requestMultiPart);
|
||||
|
||||
// get rid of the keypair generator now that we don't need it anymore
|
||||
sender()->deleteLater();
|
||||
}
|
||||
|
||||
void AccountManager::handleKeypairGenerationError() {
|
||||
qCritical() << "Error generating keypair - this is likely to cause authentication issues.";
|
||||
|
||||
// reset our waiting state for keypair response
|
||||
_isWaitingForKeypairResponse = false;
|
||||
|
||||
// for now there isn't anything we do with this except get the worker thread to clean up
|
||||
sender()->deleteLater();
|
||||
}
|
||||
|
|
|
@ -62,12 +62,12 @@ public:
|
|||
QHttpMultiPart* dataMultiPart = NULL,
|
||||
const QVariantMap& propertyMap = QVariantMap());
|
||||
|
||||
void setIsAgent(bool isAgent) { _isAgent = isAgent; }
|
||||
|
||||
const QUrl& getAuthURL() const { return _authURL; }
|
||||
void setAuthURL(const QUrl& authURL);
|
||||
bool hasAuthEndpoint() { return !_authURL.isEmpty(); }
|
||||
|
||||
void disableSettingsFilePersistence() { _shouldPersistToSettingsFile = false; }
|
||||
|
||||
bool isLoggedIn() { return !_authURL.isEmpty() && hasValidAccessToken(); }
|
||||
bool hasValidAccessToken();
|
||||
Q_INVOKABLE bool checkAndSignalForAccessToken();
|
||||
|
@ -87,9 +87,7 @@ public slots:
|
|||
void logout();
|
||||
void updateBalance();
|
||||
void accountInfoBalanceChanged(qint64 newBalance);
|
||||
void generateNewUserKeypair() { generateNewKeypair(); }
|
||||
void generateNewDomainKeypair(const QUuid& domainID) { generateNewKeypair(false, domainID); }
|
||||
|
||||
void generateNewKeypair();
|
||||
signals:
|
||||
void authRequired();
|
||||
void authEndpointChanged();
|
||||
|
@ -99,36 +97,25 @@ signals:
|
|||
void loginFailed();
|
||||
void logoutComplete();
|
||||
void balanceChanged(qint64 newBalance);
|
||||
void newKeypair();
|
||||
|
||||
private slots:
|
||||
void processReply();
|
||||
void handleKeypairGenerationError();
|
||||
void processGeneratedKeypair();
|
||||
void publicKeyUploadSucceeded(QNetworkReply& reply);
|
||||
void publicKeyUploadFailed(QNetworkReply& reply);
|
||||
void generateNewKeypair(bool isUserKeypair = true, const QUuid& domainID = QUuid());
|
||||
|
||||
void processGeneratedKeypair(const QByteArray& publicKey, const QByteArray& privateKey);
|
||||
private:
|
||||
AccountManager();
|
||||
AccountManager(AccountManager const& other) = delete;
|
||||
void operator=(AccountManager const& other) = delete;
|
||||
AccountManager(AccountManager const& other); // not implemented
|
||||
void operator=(AccountManager const& other); // not implemented
|
||||
|
||||
void persistAccountToFile();
|
||||
void removeAccountFromFile();
|
||||
void persistAccountToSettings();
|
||||
|
||||
void passSuccessToCallback(QNetworkReply* reply);
|
||||
void passErrorToCallback(QNetworkReply* reply);
|
||||
|
||||
QUrl _authURL;
|
||||
|
||||
QMap<QNetworkReply*, JSONCallbackParameters> _pendingCallbackMap;
|
||||
|
||||
DataServerAccountInfo _accountInfo;
|
||||
bool _isAgent { false };
|
||||
|
||||
bool _isWaitingForKeypairResponse { false };
|
||||
QByteArray _pendingPrivateKey;
|
||||
bool _shouldPersistToSettingsFile;
|
||||
};
|
||||
|
||||
#endif // hifi_AccountManager_h
|
||||
|
|
|
@ -25,6 +25,19 @@
|
|||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif
|
||||
|
||||
DataServerAccountInfo::DataServerAccountInfo() :
|
||||
_accessToken(),
|
||||
_username(),
|
||||
_xmppPassword(),
|
||||
_discourseApiKey(),
|
||||
_walletID(),
|
||||
_balance(0),
|
||||
_hasBalance(false),
|
||||
_privateKey()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) : QObject() {
|
||||
_accessToken = otherInfo._accessToken;
|
||||
_username = otherInfo._username;
|
||||
|
@ -34,7 +47,6 @@ DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherI
|
|||
_balance = otherInfo._balance;
|
||||
_hasBalance = otherInfo._hasBalance;
|
||||
_privateKey = otherInfo._privateKey;
|
||||
_domainID = otherInfo._domainID;
|
||||
}
|
||||
|
||||
DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) {
|
||||
|
@ -54,7 +66,6 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) {
|
|||
swap(_balance, otherInfo._balance);
|
||||
swap(_hasBalance, otherInfo._hasBalance);
|
||||
swap(_privateKey, otherInfo._privateKey);
|
||||
swap(_domainID, otherInfo._domainID);
|
||||
}
|
||||
|
||||
void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) {
|
||||
|
@ -117,62 +128,59 @@ void DataServerAccountInfo::setProfileInfoFromJSON(const QJsonObject& jsonObject
|
|||
}
|
||||
|
||||
QByteArray DataServerAccountInfo::getUsernameSignature(const QUuid& connectionToken) {
|
||||
auto lowercaseUsername = _username.toLower().toUtf8();
|
||||
auto plaintext = lowercaseUsername.append(connectionToken.toRfc4122());
|
||||
|
||||
if (!_privateKey.isEmpty()) {
|
||||
const char* privateKeyData = _privateKey.constData();
|
||||
RSA* rsaPrivateKey = d2i_RSAPrivateKey(NULL,
|
||||
reinterpret_cast<const unsigned char**>(&privateKeyData),
|
||||
_privateKey.size());
|
||||
if (rsaPrivateKey) {
|
||||
QByteArray lowercaseUsername = _username.toLower().toUtf8();
|
||||
QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()),
|
||||
QCryptographicHash::Sha256);
|
||||
|
||||
QByteArray usernameSignature(RSA_size(rsaPrivateKey), 0);
|
||||
unsigned int usernameSignatureSize = 0;
|
||||
|
||||
int encryptReturn = RSA_sign(NID_sha256,
|
||||
reinterpret_cast<const unsigned char*>(usernameWithToken.constData()),
|
||||
usernameWithToken.size(),
|
||||
reinterpret_cast<unsigned char*>(usernameSignature.data()),
|
||||
&usernameSignatureSize,
|
||||
rsaPrivateKey);
|
||||
|
||||
// free the private key RSA struct now that we are done with it
|
||||
RSA_free(rsaPrivateKey);
|
||||
|
||||
auto signature = signPlaintext(plaintext);
|
||||
if (!signature.isEmpty()) {
|
||||
qDebug(networking) << "Returning username" << _username
|
||||
<< "signed with connection UUID" << uuidStringWithoutCurlyBraces(connectionToken);
|
||||
} else {
|
||||
qCDebug(networking) << "Error signing username with connection token";
|
||||
qCDebug(networking) << "Will re-attempt on next domain-server check in.";
|
||||
}
|
||||
|
||||
return signature;
|
||||
if (encryptReturn == -1) {
|
||||
qCDebug(networking) << "Error encrypting username signature.";
|
||||
qCDebug(networking) << "Will re-attempt on next domain-server check in.";
|
||||
} else {
|
||||
qDebug(networking) << "Returning username" << _username << "signed with connection UUID" << uuidStringWithoutCurlyBraces(connectionToken);
|
||||
return usernameSignature;
|
||||
}
|
||||
|
||||
} else {
|
||||
qCDebug(networking) << "Could not create RSA struct from QByteArray private key.";
|
||||
qCDebug(networking) << "Will re-attempt on next domain-server check in.";
|
||||
}
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
QByteArray DataServerAccountInfo::signPlaintext(const QByteArray& plaintext) {
|
||||
if (!_privateKey.isEmpty()) {
|
||||
const char* privateKeyData = _privateKey.constData();
|
||||
RSA* rsaPrivateKey = d2i_RSAPrivateKey(NULL,
|
||||
reinterpret_cast<const unsigned char**>(&privateKeyData),
|
||||
_privateKey.size());
|
||||
if (rsaPrivateKey) {
|
||||
QByteArray signature(RSA_size(rsaPrivateKey), 0);
|
||||
unsigned int signatureBytes = 0;
|
||||
|
||||
QByteArray hashedPlaintext = QCryptographicHash::hash(plaintext, QCryptographicHash::Sha256);
|
||||
|
||||
int encryptReturn = RSA_sign(NID_sha256,
|
||||
reinterpret_cast<const unsigned char*>(hashedPlaintext.constData()),
|
||||
hashedPlaintext.size(),
|
||||
reinterpret_cast<unsigned char*>(signature.data()),
|
||||
&signatureBytes,
|
||||
rsaPrivateKey);
|
||||
|
||||
// free the private key RSA struct now that we are done with it
|
||||
RSA_free(rsaPrivateKey);
|
||||
|
||||
if (encryptReturn != -1) {
|
||||
return signature;
|
||||
}
|
||||
} else {
|
||||
qCDebug(networking) << "Could not create RSA struct from QByteArray private key.";
|
||||
}
|
||||
}
|
||||
return QByteArray();
|
||||
void DataServerAccountInfo::setPrivateKey(const QByteArray& privateKey) {
|
||||
_privateKey = privateKey;
|
||||
|
||||
}
|
||||
|
||||
QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) {
|
||||
out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey
|
||||
<< info._walletID << info._privateKey << info._domainID;
|
||||
|
||||
<< info._walletID << info._privateKey;
|
||||
return out;
|
||||
}
|
||||
|
||||
QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) {
|
||||
in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey
|
||||
>> info._walletID >> info._privateKey >> info._domainID;
|
||||
>> info._walletID >> info._privateKey;
|
||||
return in;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ const float SATOSHIS_PER_CREDIT = 100000000.0f;
|
|||
class DataServerAccountInfo : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DataServerAccountInfo() {};
|
||||
DataServerAccountInfo();
|
||||
DataServerAccountInfo(const DataServerAccountInfo& otherInfo);
|
||||
DataServerAccountInfo& operator=(const DataServerAccountInfo& otherInfo);
|
||||
|
||||
|
@ -42,6 +42,10 @@ public:
|
|||
|
||||
const QUuid& getWalletID() const { return _walletID; }
|
||||
void setWalletID(const QUuid& walletID);
|
||||
|
||||
QByteArray getUsernameSignature(const QUuid& connectionToken);
|
||||
bool hasPrivateKey() const { return !_privateKey.isEmpty(); }
|
||||
void setPrivateKey(const QByteArray& privateKey);
|
||||
|
||||
qint64 getBalance() const { return _balance; }
|
||||
float getBalanceInSatoshis() const { return _balance / SATOSHIS_PER_CREDIT; }
|
||||
|
@ -50,15 +54,6 @@ public:
|
|||
void setHasBalance(bool hasBalance) { _hasBalance = hasBalance; }
|
||||
Q_INVOKABLE void setBalanceFromJSON(QNetworkReply& requestReply);
|
||||
|
||||
QByteArray getUsernameSignature(const QUuid& connectionToken);
|
||||
bool hasPrivateKey() const { return !_privateKey.isEmpty(); }
|
||||
void setPrivateKey(const QByteArray& privateKey) { _privateKey = privateKey; }
|
||||
|
||||
QByteArray signPlaintext(const QByteArray& plaintext);
|
||||
|
||||
void setDomainID(const QUuid& domainID) { _domainID = domainID; }
|
||||
const QUuid& getDomainID() const { return _domainID; }
|
||||
|
||||
bool hasProfile() const;
|
||||
|
||||
void setProfileInfoFromJSON(const QJsonObject& jsonObject);
|
||||
|
@ -75,9 +70,8 @@ private:
|
|||
QString _xmppPassword;
|
||||
QString _discourseApiKey;
|
||||
QUuid _walletID;
|
||||
qint64 _balance { 0 };
|
||||
bool _hasBalance { false };
|
||||
QUuid _domainID; // if this holds account info for a domain, this holds the ID of that domain
|
||||
qint64 _balance;
|
||||
bool _hasBalance;
|
||||
QByteArray _privateKey;
|
||||
|
||||
};
|
||||
|
|
|
@ -92,9 +92,7 @@ void DomainHandler::softReset() {
|
|||
disconnect();
|
||||
|
||||
clearSettings();
|
||||
|
||||
_connectionDenialsSinceKeypairRegen = 0;
|
||||
|
||||
|
||||
// cancel the failure timeout for any pending requests for settings
|
||||
QMetaObject::invokeMethod(&_settingsTimer, "stop");
|
||||
}
|
||||
|
@ -108,9 +106,6 @@ void DomainHandler::hardReset() {
|
|||
_hostname = QString();
|
||||
_sockAddr.clear();
|
||||
|
||||
_hasCheckedForAccessToken = false;
|
||||
_domainConnectionRefusals.clear();
|
||||
|
||||
// clear any pending path we may have wanted to ask the previous DS about
|
||||
_pendingPath.clear();
|
||||
}
|
||||
|
@ -352,35 +347,3 @@ void DomainHandler::processICEResponsePacket(QSharedPointer<ReceivedMessage> mes
|
|||
emit icePeerSocketsReceived();
|
||||
}
|
||||
}
|
||||
|
||||
void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
// Read deny reason from packet
|
||||
quint16 reasonSize;
|
||||
message->readPrimitive(&reasonSize);
|
||||
QString reason = QString::fromUtf8(message->readWithoutCopy(reasonSize));
|
||||
|
||||
// output to the log so the user knows they got a denied connection request
|
||||
// and check and signal for an access token so that we can make sure they are logged in
|
||||
qCWarning(networking) << "The domain-server denied a connection request: " << reason;
|
||||
qCWarning(networking) << "Make sure you are logged in.";
|
||||
|
||||
if (!_domainConnectionRefusals.contains(reason)) {
|
||||
_domainConnectionRefusals.append(reason);
|
||||
emit domainConnectionRefused(reason);
|
||||
}
|
||||
|
||||
auto& accountManager = AccountManager::getInstance();
|
||||
|
||||
if (!_hasCheckedForAccessToken) {
|
||||
accountManager.checkAndSignalForAccessToken();
|
||||
_hasCheckedForAccessToken = true;
|
||||
}
|
||||
|
||||
static const int CONNECTION_DENIALS_FOR_KEYPAIR_REGEN = 3;
|
||||
|
||||
// force a re-generation of key-pair after CONNECTION_DENIALS_FOR_KEYPAIR_REGEN failed connection attempts
|
||||
if (++_connectionDenialsSinceKeypairRegen >= CONNECTION_DENIALS_FOR_KEYPAIR_REGEN) {
|
||||
accountManager.generateNewUserKeypair();
|
||||
_connectionDenialsSinceKeypairRegen = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,7 +92,6 @@ public slots:
|
|||
void processICEPingReplyPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void processDTLSRequirementPacket(QSharedPointer<ReceivedMessage> dtlsRequirementPacket);
|
||||
void processICEResponsePacket(QSharedPointer<ReceivedMessage> icePacket);
|
||||
void processDomainServerConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
private slots:
|
||||
void completedHostnameLookup(const QHostInfo& hostInfo);
|
||||
|
@ -114,8 +113,6 @@ signals:
|
|||
void settingsReceived(const QJsonObject& domainSettingsObject);
|
||||
void settingsReceiveFail();
|
||||
|
||||
void domainConnectionRefused(QString reason);
|
||||
|
||||
private:
|
||||
void sendDisconnectPacket();
|
||||
void hardReset();
|
||||
|
@ -133,10 +130,6 @@ private:
|
|||
QJsonObject _settingsObject;
|
||||
QString _pendingPath;
|
||||
QTimer _settingsTimer;
|
||||
|
||||
QStringList _domainConnectionRefusals;
|
||||
bool _hasCheckedForAccessToken { false };
|
||||
int _connectionDenialsSinceKeypairRegen { 0 };
|
||||
};
|
||||
|
||||
#endif // hifi_DomainHandler_h
|
||||
|
|
|
@ -902,6 +902,10 @@ void LimitedNodeList::updateLocalSockAddr() {
|
|||
}
|
||||
}
|
||||
|
||||
void LimitedNodeList::sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr) {
|
||||
sendPacketToIceServer(PacketType::ICEServerHeartbeat, iceServerSockAddr, _sessionUUID);
|
||||
}
|
||||
|
||||
void LimitedNodeList::sendPeerQueryToIceServer(const HifiSockAddr& iceServerSockAddr, const QUuid& clientID,
|
||||
const QUuid& peerID) {
|
||||
sendPacketToIceServer(PacketType::ICEServerQuery, iceServerSockAddr, clientID, peerID);
|
||||
|
|
|
@ -143,7 +143,6 @@ public:
|
|||
bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; }
|
||||
|
||||
const HifiSockAddr& getLocalSockAddr() const { return _localSockAddr; }
|
||||
const HifiSockAddr& getPublicSockAddr() const { return _publicSockAddr; }
|
||||
const HifiSockAddr& getSTUNSockAddr() const { return _stunSockAddr; }
|
||||
|
||||
void processKillNode(ReceivedMessage& message);
|
||||
|
@ -162,6 +161,7 @@ public:
|
|||
std::unique_ptr<NLPacket> constructICEPingPacket(PingType_t pingType, const QUuid& iceID);
|
||||
std::unique_ptr<NLPacket> constructICEPingReplyPacket(ReceivedMessage& message, const QUuid& iceID);
|
||||
|
||||
void sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr);
|
||||
void sendPeerQueryToIceServer(const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerID);
|
||||
|
||||
SharedNodePointer findNodeWithAddr(const HifiSockAddr& addr);
|
||||
|
|
|
@ -80,16 +80,11 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned
|
|||
// send a ping punch immediately
|
||||
connect(&_domainHandler, &DomainHandler::icePeerSocketsReceived, this, &NodeList::pingPunchForDomainServer);
|
||||
|
||||
auto &accountManager = AccountManager::getInstance();
|
||||
|
||||
// assume that we may need to send a new DS check in anytime a new keypair is generated
|
||||
connect(&accountManager, &AccountManager::newKeypair, this, &NodeList::sendDomainServerCheckIn);
|
||||
|
||||
// clear out NodeList when login is finished
|
||||
connect(&accountManager, &AccountManager::loginComplete , this, &NodeList::reset);
|
||||
connect(&AccountManager::getInstance(), &AccountManager::loginComplete , this, &NodeList::reset);
|
||||
|
||||
// clear our NodeList when logout is requested
|
||||
connect(&accountManager, &AccountManager::logoutComplete , this, &NodeList::reset);
|
||||
connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset);
|
||||
|
||||
// anytime we get a new node we will want to attempt to punch to it
|
||||
connect(this, &LimitedNodeList::nodeAdded, this, &NodeList::startNodeHolePunch);
|
||||
|
@ -110,7 +105,6 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned
|
|||
packetReceiver.registerListener(PacketType::ICEPing, this, "processICEPingPacket");
|
||||
packetReceiver.registerListener(PacketType::DomainServerAddedNode, this, "processDomainServerAddedNode");
|
||||
packetReceiver.registerListener(PacketType::DomainServerConnectionToken, this, "processDomainServerConnectionTokenPacket");
|
||||
packetReceiver.registerListener(PacketType::DomainConnectionDenied, &_domainHandler, "processDomainServerConnectionDeniedPacket");
|
||||
packetReceiver.registerListener(PacketType::DomainSettings, &_domainHandler, "processSettingsPacketList");
|
||||
packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_domainHandler, "processICEResponsePacket");
|
||||
packetReceiver.registerListener(PacketType::DomainServerRequireDTLS, &_domainHandler, "processDTLSRequirementPacket");
|
||||
|
@ -271,26 +265,6 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
|
||||
}
|
||||
|
||||
// check if we're missing a keypair we need to verify ourselves with the domain-server
|
||||
auto& accountManager = AccountManager::getInstance();
|
||||
const QUuid& connectionToken = _domainHandler.getConnectionToken();
|
||||
|
||||
// we assume that we're on the same box as the DS if it has the same local address and
|
||||
// it didn't present us with a connection token to use for username signature
|
||||
bool localhostDomain = _domainHandler.getSockAddr().getAddress() == QHostAddress::LocalHost
|
||||
|| (_domainHandler.getSockAddr().getAddress() == _localSockAddr.getAddress() && connectionToken.isNull());
|
||||
|
||||
bool requiresUsernameSignature = !_domainHandler.isConnected() && !connectionToken.isNull() && !localhostDomain;
|
||||
|
||||
if (requiresUsernameSignature && !accountManager.getAccountInfo().hasPrivateKey()) {
|
||||
qWarning() << "A keypair is required to present a username signature to the domain-server"
|
||||
<< "but no keypair is present. Waiting for keypair generation to complete.";
|
||||
accountManager.generateNewUserKeypair();
|
||||
|
||||
// don't send the check in packet - wait for the keypair first
|
||||
return;
|
||||
}
|
||||
|
||||
auto domainPacket = NLPacket::create(domainPacketType);
|
||||
|
||||
QDataStream packetStream(domainPacket.get());
|
||||
|
@ -315,15 +289,23 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
|
||||
// pack our data to send to the domain-server
|
||||
packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList();
|
||||
|
||||
if (!_domainHandler.isConnected()) {
|
||||
DataServerAccountInfo& accountInfo = accountManager.getAccountInfo();
|
||||
|
||||
// if this is a connect request, and we can present a username signature, send it along
|
||||
if (!_domainHandler.isConnected() ) {
|
||||
|
||||
DataServerAccountInfo& accountInfo = AccountManager::getInstance().getAccountInfo();
|
||||
packetStream << accountInfo.getUsername();
|
||||
|
||||
// if this is a connect request, and we can present a username signature, send it along
|
||||
if (requiresUsernameSignature && accountManager.getAccountInfo().hasPrivateKey()) {
|
||||
const QByteArray& usernameSignature = accountManager.getAccountInfo().getUsernameSignature(connectionToken);
|
||||
packetStream << usernameSignature;
|
||||
|
||||
// get connection token from the domain-server
|
||||
const QUuid& connectionToken = _domainHandler.getConnectionToken();
|
||||
|
||||
if (!connectionToken.isNull()) {
|
||||
|
||||
const QByteArray& usernameSignature = AccountManager::getInstance().getAccountInfo().getUsernameSignature(connectionToken);
|
||||
|
||||
if (!usernameSignature.isEmpty()) {
|
||||
packetStream << usernameSignature;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -85,12 +85,12 @@ void RSAKeypairGenerator::generateKeypair() {
|
|||
// we can cleanup the RSA struct before we continue on
|
||||
RSA_free(keyPair);
|
||||
|
||||
_publicKey = QByteArray { reinterpret_cast<char*>(publicKeyDER), publicKeyLength };
|
||||
_privateKey = QByteArray { reinterpret_cast<char*>(privateKeyDER), privateKeyLength };
|
||||
QByteArray publicKeyArray(reinterpret_cast<char*>(publicKeyDER), publicKeyLength);
|
||||
QByteArray privateKeyArray(reinterpret_cast<char*>(privateKeyDER), privateKeyLength);
|
||||
|
||||
// cleanup the publicKeyDER and publicKeyDER data
|
||||
OPENSSL_free(publicKeyDER);
|
||||
OPENSSL_free(privateKeyDER);
|
||||
|
||||
emit generatedKeypair();
|
||||
emit generatedKeypair(publicKeyArray, privateKeyArray);
|
||||
}
|
||||
|
|
|
@ -12,31 +12,17 @@
|
|||
#ifndef hifi_RSAKeypairGenerator_h
|
||||
#define hifi_RSAKeypairGenerator_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QUuid>
|
||||
#include <qobject.h>
|
||||
|
||||
class RSAKeypairGenerator : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
RSAKeypairGenerator(QObject* parent = 0);
|
||||
|
||||
void setDomainID(const QUuid& domainID) { _domainID = domainID; }
|
||||
const QUuid& getDomainID() const { return _domainID; }
|
||||
|
||||
const QByteArray& getPublicKey() const { return _publicKey; }
|
||||
const QByteArray& getPrivateKey() const { return _privateKey; }
|
||||
|
||||
public slots:
|
||||
void generateKeypair();
|
||||
|
||||
signals:
|
||||
void errorGeneratingKeypair();
|
||||
void generatedKeypair();
|
||||
|
||||
private:
|
||||
QUuid _domainID;
|
||||
QByteArray _publicKey;
|
||||
QByteArray _privateKey;
|
||||
void generatedKeypair(const QByteArray& publicKey, const QByteArray& privateKey);
|
||||
};
|
||||
|
||||
#endif // hifi_RSAKeypairGenerator_h
|
||||
#endif // hifi_RSAKeypairGenerator_h
|
|
@ -30,7 +30,7 @@ const QSet<PacketType> NON_SOURCED_PACKETS = QSet<PacketType>()
|
|||
<< PacketType::DomainServerAddedNode << PacketType::DomainServerConnectionToken
|
||||
<< PacketType::DomainSettingsRequest << PacketType::DomainSettings
|
||||
<< PacketType::ICEServerPeerInformation << PacketType::ICEServerQuery << PacketType::ICEServerHeartbeat
|
||||
<< PacketType::ICEPing << PacketType::ICEPingReply << PacketType::ICEServerHeartbeatDenied
|
||||
<< PacketType::ICEPing << PacketType::ICEPingReply
|
||||
<< PacketType::AssignmentClientStatus << PacketType::StopNode
|
||||
<< PacketType::DomainServerRemovedNode;
|
||||
|
||||
|
@ -41,12 +41,10 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::EntityAdd:
|
||||
case PacketType::EntityEdit:
|
||||
case PacketType::EntityData:
|
||||
return VERSION_ATMOSPHERE_REMOVED;
|
||||
return VERSION_LIGHT_HAS_FALLOFF_RADIUS;
|
||||
case PacketType::AvatarData:
|
||||
case PacketType::BulkAvatarData:
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::SoftAttachmentSupport);
|
||||
case PacketType::ICEServerHeartbeat:
|
||||
return 18; // ICE Server Heartbeat signing
|
||||
default:
|
||||
return 17;
|
||||
}
|
||||
|
|
|
@ -90,8 +90,7 @@ public:
|
|||
DomainServerRemovedNode,
|
||||
MessagesData,
|
||||
MessagesSubscribe,
|
||||
MessagesUnsubscribe,
|
||||
ICEServerHeartbeatDenied
|
||||
MessagesUnsubscribe
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -167,6 +166,7 @@ const PacketVersion VERSION_MODEL_ENTITIES_JOINTS_ON_WIRE = 53;
|
|||
const PacketVersion VERSION_ENTITITES_HAVE_QUERY_BOX = 54;
|
||||
const PacketVersion VERSION_ENTITITES_HAVE_COLLISION_MASK = 55;
|
||||
const PacketVersion VERSION_ATMOSPHERE_REMOVED = 56;
|
||||
const PacketVersion VERSION_LIGHT_HAS_FALLOFF_RADIUS = 57;
|
||||
|
||||
enum class AvatarMixerPacketVersion : PacketVersion {
|
||||
TranslationSupport = 17,
|
||||
|
|
|
@ -937,7 +937,7 @@ int Octree::encodeTreeBitstream(OctreeElementPointer element,
|
|||
params.stats->traversed(element);
|
||||
}
|
||||
|
||||
ViewFrustum::location parentLocationThisView = ViewFrustum::INTERSECT; // assume parent is in view, but not fully
|
||||
ViewFrustum::intersection parentLocationThisView = ViewFrustum::INTERSECT; // assume parent is in view, but not fully
|
||||
|
||||
int childBytesWritten = encodeTreeBitstreamRecursion(element, packetData, bag, params,
|
||||
currentEncodeLevel, parentLocationThisView);
|
||||
|
@ -974,7 +974,7 @@ int Octree::encodeTreeBitstream(OctreeElementPointer element,
|
|||
int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
||||
OctreePacketData* packetData, OctreeElementBag& bag,
|
||||
EncodeBitstreamParams& params, int& currentEncodeLevel,
|
||||
const ViewFrustum::location& parentLocationThisView) const {
|
||||
const ViewFrustum::intersection& parentLocationThisView) const {
|
||||
|
||||
|
||||
const bool wantDebug = false;
|
||||
|
@ -1013,7 +1013,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
}
|
||||
}
|
||||
|
||||
ViewFrustum::location nodeLocationThisView = ViewFrustum::INSIDE; // assume we're inside
|
||||
ViewFrustum::intersection nodeLocationThisView = ViewFrustum::INSIDE; // assume we're inside
|
||||
|
||||
// caller can pass NULL as viewFrustum if they want everything
|
||||
if (params.viewFrustum) {
|
||||
|
@ -1034,7 +1034,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
// if we are INSIDE, INTERSECT, or OUTSIDE
|
||||
if (parentLocationThisView != ViewFrustum::INSIDE) {
|
||||
assert(parentLocationThisView != ViewFrustum::OUTSIDE); // we shouldn't be here if our parent was OUTSIDE!
|
||||
nodeLocationThisView = element->inFrustum(*params.viewFrustum);
|
||||
nodeLocationThisView = element->computeViewIntersection(*params.viewFrustum);
|
||||
}
|
||||
|
||||
// If we're at a element that is out of view, then we can return, because no nodes below us will be in view!
|
||||
|
@ -1053,7 +1053,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
bool wasInView = false;
|
||||
|
||||
if (params.deltaViewFrustum && params.lastViewFrustum) {
|
||||
ViewFrustum::location location = element->inFrustum(*params.lastViewFrustum);
|
||||
ViewFrustum::intersection location = element->computeViewIntersection(*params.lastViewFrustum);
|
||||
|
||||
// If we're a leaf, then either intersect or inside is considered "formerly in view"
|
||||
if (element->isLeaf()) {
|
||||
|
@ -1237,7 +1237,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
bool childWasInView = false;
|
||||
|
||||
if (childElement && params.deltaViewFrustum && params.lastViewFrustum) {
|
||||
ViewFrustum::location location = childElement->inFrustum(*params.lastViewFrustum);
|
||||
ViewFrustum::intersection location = childElement->computeViewIntersection(*params.lastViewFrustum);
|
||||
|
||||
// If we're a leaf, then either intersect or inside is considered "formerly in view"
|
||||
if (childElement->isLeaf()) {
|
||||
|
|
|
@ -367,7 +367,7 @@ protected:
|
|||
int encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
||||
OctreePacketData* packetData, OctreeElementBag& bag,
|
||||
EncodeBitstreamParams& params, int& currentEncodeLevel,
|
||||
const ViewFrustum::location& parentLocationThisView) const;
|
||||
const ViewFrustum::intersection& parentLocationThisView) const;
|
||||
|
||||
static bool countOctreeElementsOperation(OctreeElementPointer element, void* extraData);
|
||||
|
||||
|
|
|
@ -458,8 +458,8 @@ float OctreeElement::getEnclosingRadius() const {
|
|||
return getScale() * sqrtf(3.0f) / 2.0f;
|
||||
}
|
||||
|
||||
ViewFrustum::location OctreeElement::inFrustum(const ViewFrustum& viewFrustum) const {
|
||||
return viewFrustum.cubeInFrustum(_cube);
|
||||
ViewFrustum::intersection OctreeElement::computeViewIntersection(const ViewFrustum& viewFrustum) const {
|
||||
return viewFrustum.calculateCubeKeyholeIntersection(_cube);
|
||||
}
|
||||
|
||||
// There are two types of nodes for which we want to "render"
|
||||
|
|
|
@ -49,20 +49,20 @@ protected:
|
|||
OctreeElement();
|
||||
|
||||
virtual OctreeElementPointer createNewElement(unsigned char * octalCode = NULL) = 0;
|
||||
|
||||
|
||||
public:
|
||||
virtual void init(unsigned char * octalCode); /// Your subclass must call init on construction.
|
||||
virtual ~OctreeElement();
|
||||
|
||||
// methods you can and should override to implement your tree functionality
|
||||
|
||||
|
||||
/// Adds a child to the current element. Override this if there is additional child initialization your class needs.
|
||||
virtual OctreeElementPointer addChildAtIndex(int childIndex);
|
||||
|
||||
/// Override this to implement LOD averaging on changes to the tree.
|
||||
/// Override this to implement LOD averaging on changes to the tree.
|
||||
virtual void calculateAverageFromChildren() { }
|
||||
|
||||
/// Override this to implement LOD collapsing and identical child pruning on changes to the tree.
|
||||
/// Override this to implement LOD collapsing and identical child pruning on changes to the tree.
|
||||
virtual bool collapseChildren() { return false; }
|
||||
|
||||
/// Should this element be considered to have content in it. This will be used in collision and ray casting methods.
|
||||
|
@ -72,12 +72,12 @@ public:
|
|||
/// Should this element be considered to have detailed content in it. Specifically should it be rendered.
|
||||
/// By default we assume that only leaves have detailed content, but some octrees may have different semantics.
|
||||
virtual bool hasDetailedContent() const { return isLeaf(); }
|
||||
|
||||
|
||||
/// Override this to break up large octree elements when an edit operation is performed on a smaller octree element.
|
||||
/// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the
|
||||
/// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the
|
||||
/// meaningful split would be to break the larger cube into smaller cubes of the same color/texture.
|
||||
virtual void splitChildren() { }
|
||||
|
||||
|
||||
/// Override to indicate that this element requires a split before editing lower elements in the octree
|
||||
virtual bool requiresSplit() const { return false; }
|
||||
|
||||
|
@ -88,17 +88,17 @@ public:
|
|||
virtual void initializeExtraEncodeData(EncodeBitstreamParams& params) { }
|
||||
virtual bool shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const { return true; }
|
||||
virtual bool shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const { return true; }
|
||||
|
||||
|
||||
virtual void updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const { }
|
||||
virtual void elementEncodeComplete(EncodeBitstreamParams& params) const { }
|
||||
|
||||
/// Override to serialize the state of this element. This is used for persistance and for transmission across the network.
|
||||
virtual AppendState appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const
|
||||
virtual AppendState appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const
|
||||
{ return COMPLETED; }
|
||||
|
||||
|
||||
/// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading
|
||||
/// from the network.
|
||||
virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args)
|
||||
virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args)
|
||||
{ return 0; }
|
||||
|
||||
/// Override to indicate that the item is currently rendered in the rendering engine. By default we assume that if
|
||||
|
@ -106,7 +106,7 @@ public:
|
|||
/// where an element is not actually rendering all should render elements. If the isRendered() state doesn't match the
|
||||
/// shouldRender() state, the tree will remark elements as changed even in cases there the elements have not changed.
|
||||
virtual bool isRendered() const { return getShouldRender(); }
|
||||
|
||||
|
||||
virtual bool deleteApproved() const { return true; }
|
||||
|
||||
virtual bool canRayIntersect() const { return isLeaf(); }
|
||||
|
@ -114,7 +114,7 @@ public:
|
|||
/// \param radius radius of sphere in meters
|
||||
/// \param[out] penetration pointing into cube from sphere
|
||||
/// \param penetratedObject unused
|
||||
virtual bool findSpherePenetration(const glm::vec3& center, float radius,
|
||||
virtual bool findSpherePenetration(const glm::vec3& center, float radius,
|
||||
glm::vec3& penetration, void** penetratedObject) const;
|
||||
|
||||
// Base class methods you don't need to implement
|
||||
|
@ -125,7 +125,7 @@ public:
|
|||
bool isParentOf(OctreeElementPointer possibleChild) const;
|
||||
|
||||
/// handles deletion of all descendants, returns false if delete not approved
|
||||
bool safeDeepDeleteChildAtIndex(int childIndex, int recursionCount = 0);
|
||||
bool safeDeepDeleteChildAtIndex(int childIndex, int recursionCount = 0);
|
||||
|
||||
|
||||
const AACube& getAACube() const { return _cube; }
|
||||
|
@ -134,8 +134,8 @@ public:
|
|||
int getLevel() const { return numberOfThreeBitSectionsInCode(getOctalCode()) + 1; }
|
||||
|
||||
float getEnclosingRadius() const;
|
||||
bool isInView(const ViewFrustum& viewFrustum) const { return inFrustum(viewFrustum) != ViewFrustum::OUTSIDE; }
|
||||
ViewFrustum::location inFrustum(const ViewFrustum& viewFrustum) const;
|
||||
bool isInView(const ViewFrustum& viewFrustum) const { return computeViewIntersection(viewFrustum) != ViewFrustum::OUTSIDE; }
|
||||
ViewFrustum::intersection computeViewIntersection(const ViewFrustum& viewFrustum) const;
|
||||
float distanceToCamera(const ViewFrustum& viewFrustum) const;
|
||||
float furthestDistanceToCamera(const ViewFrustum& viewFrustum) const;
|
||||
|
||||
|
@ -257,7 +257,7 @@ protected:
|
|||
static std::map<QString, uint16_t> _mapSourceUUIDsToKeys;
|
||||
static std::map<uint16_t, QString> _mapKeysToSourceUUIDs;
|
||||
|
||||
unsigned char _childBitmask; // 1 byte
|
||||
unsigned char _childBitmask; // 1 byte
|
||||
|
||||
bool _falseColored : 1, /// Client only, is this voxel false colored, 1 bit
|
||||
_isDirty : 1, /// Client only, has this voxel changed since being rendered, 1 bit
|
||||
|
|
|
@ -27,9 +27,9 @@ void OctreeHeadlessViewer::init() {
|
|||
void OctreeHeadlessViewer::queryOctree() {
|
||||
char serverType = getMyNodeType();
|
||||
PacketType packetType = getMyQueryMessageType();
|
||||
|
||||
|
||||
NodeToJurisdictionMap& jurisdictions = *_jurisdictionListener->getJurisdictions();
|
||||
|
||||
|
||||
bool wantExtraDebugging = false;
|
||||
|
||||
if (wantExtraDebugging) {
|
||||
|
@ -52,7 +52,7 @@ void OctreeHeadlessViewer::queryOctree() {
|
|||
_octreeQuery.setCameraNearClip(_viewFrustum.getNearClip());
|
||||
_octreeQuery.setCameraFarClip(_viewFrustum.getFarClip());
|
||||
_octreeQuery.setCameraEyeOffsetPosition(glm::vec3());
|
||||
_octreeQuery.setKeyholeRadius(_viewFrustum.getKeyholeRadius());
|
||||
_octreeQuery.setCameraCenterRadius(_viewFrustum.getCenterRadius());
|
||||
_octreeQuery.setOctreeSizeScale(_voxelSizeScale);
|
||||
_octreeQuery.setBoundaryLevelAdjust(_boundaryLevelAdjust);
|
||||
|
||||
|
@ -77,7 +77,7 @@ void OctreeHeadlessViewer::queryOctree() {
|
|||
if (jurisdictions.find(nodeUUID) == jurisdictions.end()) {
|
||||
unknownJurisdictionServers++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
const JurisdictionMap& map = (jurisdictions)[nodeUUID];
|
||||
|
||||
unsigned char* rootCode = map.getRootOctalCode();
|
||||
|
@ -91,9 +91,7 @@ void OctreeHeadlessViewer::queryOctree() {
|
|||
|
||||
if (foundRootDetails) {
|
||||
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
|
||||
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
|
||||
|
||||
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
|
||||
if ((bool)(_viewFrustum.calculateCubeKeyholeIntersection(serverBounds))) {
|
||||
inViewServers++;
|
||||
}
|
||||
}
|
||||
|
@ -164,13 +162,7 @@ void OctreeHeadlessViewer::queryOctree() {
|
|||
|
||||
if (foundRootDetails) {
|
||||
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
|
||||
|
||||
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
|
||||
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
|
||||
inView = true;
|
||||
} else {
|
||||
inView = false;
|
||||
}
|
||||
inView = (bool)(_viewFrustum.calculateCubeKeyholeIntersection(serverBounds));
|
||||
}
|
||||
|
||||
if (inView) {
|
||||
|
@ -208,7 +200,7 @@ void OctreeHeadlessViewer::queryOctree() {
|
|||
|
||||
// setup the query packet
|
||||
auto queryPacket = NLPacket::create(packetType);
|
||||
|
||||
|
||||
// read the data to our packet and set the payload size to fit the query
|
||||
int querySize = _octreeQuery.getBroadcastData(reinterpret_cast<unsigned char*>(queryPacket->getPayload()));
|
||||
queryPacket->setPayloadSize(querySize);
|
||||
|
|
|
@ -46,7 +46,8 @@ public slots:
|
|||
// setters for camera attributes
|
||||
void setPosition(const glm::vec3& position) { _viewFrustum.setPosition(position); }
|
||||
void setOrientation(const glm::quat& orientation) { _viewFrustum.setOrientation(orientation); }
|
||||
void setKeyholeRadius(float keyholdRadius) { _viewFrustum.setKeyholeRadius(keyholdRadius); }
|
||||
void setCenterRadius(float radius) { _viewFrustum.setCenterRadius(radius); }
|
||||
void setKeyholeRadius(float radius) { _viewFrustum.setCenterRadius(radius); } // TODO: remove this legacy support
|
||||
|
||||
// setters for LOD and PPS
|
||||
void setVoxelSizeScale(float sizeScale) { _voxelSizeScale = sizeScale; }
|
||||
|
|
|
@ -64,8 +64,8 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) {
|
|||
memcpy(destinationBuffer, &_boundaryLevelAdjust, sizeof(_boundaryLevelAdjust));
|
||||
destinationBuffer += sizeof(_boundaryLevelAdjust);
|
||||
|
||||
memcpy(destinationBuffer, &_keyholeRadius, sizeof(_keyholeRadius));
|
||||
destinationBuffer += sizeof(_keyholeRadius);
|
||||
memcpy(destinationBuffer, &_cameraCenterRadius, sizeof(_cameraCenterRadius));
|
||||
destinationBuffer += sizeof(_cameraCenterRadius);
|
||||
|
||||
return destinationBuffer - bufferStart;
|
||||
}
|
||||
|
@ -109,9 +109,9 @@ int OctreeQuery::parseData(ReceivedMessage& message) {
|
|||
|
||||
auto bytesRead = sourceBuffer - startPosition;
|
||||
auto bytesLeft = message.getSize() - bytesRead;
|
||||
if (bytesLeft >= (int)sizeof(_keyholeRadius)) {
|
||||
memcpy(&_keyholeRadius, sourceBuffer, sizeof(_keyholeRadius));
|
||||
sourceBuffer += sizeof(_keyholeRadius);
|
||||
if (bytesLeft >= (int)sizeof(_cameraCenterRadius)) {
|
||||
memcpy(&_cameraCenterRadius, sourceBuffer, sizeof(_cameraCenterRadius));
|
||||
sourceBuffer += sizeof(_cameraCenterRadius);
|
||||
}
|
||||
return sourceBuffer - startPosition;
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ public:
|
|||
float getCameraNearClip() const { return _cameraNearClip; }
|
||||
float getCameraFarClip() const { return _cameraFarClip; }
|
||||
const glm::vec3& getCameraEyeOffsetPosition() const { return _cameraEyeOffsetPosition; }
|
||||
float getKeyholeRadius() const { return _keyholeRadius; }
|
||||
float getCameraCenterRadius() const { return _cameraCenterRadius; }
|
||||
|
||||
glm::vec3 calculateCameraDirection() const;
|
||||
|
||||
|
@ -70,7 +70,7 @@ public:
|
|||
void setCameraNearClip(float nearClip) { _cameraNearClip = nearClip; }
|
||||
void setCameraFarClip(float farClip) { _cameraFarClip = farClip; }
|
||||
void setCameraEyeOffsetPosition(const glm::vec3& eyeOffsetPosition) { _cameraEyeOffsetPosition = eyeOffsetPosition; }
|
||||
void setKeyholeRadius(float keyholeRadius) { _keyholeRadius = keyholeRadius; }
|
||||
void setCameraCenterRadius(float radius) { _cameraCenterRadius = radius; }
|
||||
|
||||
// related to Octree Sending strategies
|
||||
int getMaxQueryPacketsPerSecond() const { return _maxQueryPPS; }
|
||||
|
@ -90,7 +90,7 @@ protected:
|
|||
float _cameraAspectRatio = 1.0f;
|
||||
float _cameraNearClip = 0.0f;
|
||||
float _cameraFarClip = 0.0f;
|
||||
float _keyholeRadius { 0.0f };
|
||||
float _cameraCenterRadius { 0.0f };
|
||||
glm::vec3 _cameraEyeOffsetPosition = glm::vec3(0.0f);
|
||||
|
||||
// octree server sending items
|
||||
|
|
|
@ -115,10 +115,6 @@ void ViewFrustum::calculate() {
|
|||
|
||||
// Our ModelViewProjection : multiplication of our 3 matrices (note: model is identity, so we can drop it)
|
||||
_ourModelViewProjectionMatrix = _projection * view; // Remember, matrix multiplication is the other way around
|
||||
|
||||
// Set up our keyhole bounding box...
|
||||
glm::vec3 corner = _position - _keyholeRadius;
|
||||
_keyholeBoundingCube = AACube(corner,(_keyholeRadius * 2.0f));
|
||||
}
|
||||
|
||||
//enum { TOP_PLANE = 0, BOTTOM_PLANE, LEFT_PLANE, RIGHT_PLANE, NEAR_PLANE, FAR_PLANE };
|
||||
|
@ -134,231 +130,129 @@ const char* ViewFrustum::debugPlaneName (int plane) const {
|
|||
return "Unknown";
|
||||
}
|
||||
|
||||
ViewFrustum::location ViewFrustum::pointInKeyhole(const glm::vec3& point) const {
|
||||
|
||||
ViewFrustum::location result = INTERSECT;
|
||||
|
||||
float distance = glm::distance(point, _position);
|
||||
if (distance > _keyholeRadius) {
|
||||
result = OUTSIDE;
|
||||
} else if (distance < _keyholeRadius) {
|
||||
result = INSIDE;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// To determine if two spheres intersect, simply calculate the distance between the centers of the two spheres.
|
||||
// If the distance is greater than the sum of the two sphere radii, they don’t intersect. Otherwise they intersect.
|
||||
// If the distance plus the radius of sphere A is less than the radius of sphere B then, sphere A is inside of sphere B
|
||||
ViewFrustum::location ViewFrustum::sphereInKeyhole(const glm::vec3& center, float radius) const {
|
||||
ViewFrustum::location result = INTERSECT;
|
||||
|
||||
float distance = glm::distance(center, _position);
|
||||
if (distance > (radius + _keyholeRadius)) {
|
||||
result = OUTSIDE;
|
||||
} else if ((distance + radius) < _keyholeRadius) {
|
||||
result = INSIDE;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// A box is inside a sphere if all of its corners are inside the sphere
|
||||
// A box intersects a sphere if any of its edges (as rays) interesect the sphere
|
||||
// A box is outside a sphere if none of its edges (as rays) interesect the sphere
|
||||
ViewFrustum::location ViewFrustum::cubeInKeyhole(const AACube& cube) const {
|
||||
|
||||
// First check to see if the cube is in the bounding cube for the sphere, if it's not, then we can short circuit
|
||||
// this and not check with sphere penetration which is more expensive
|
||||
if (!_keyholeBoundingCube.contains(cube)) {
|
||||
return OUTSIDE;
|
||||
}
|
||||
|
||||
glm::vec3 penetration;
|
||||
bool intersects = cube.findSpherePenetration(_position, _keyholeRadius, penetration);
|
||||
|
||||
ViewFrustum::location result = OUTSIDE;
|
||||
|
||||
// if the cube intersects the sphere, then it may also be inside... calculate further
|
||||
if (intersects) {
|
||||
result = INTERSECT;
|
||||
|
||||
// test all the corners, if they are all inside the sphere, the entire cube is in the sphere
|
||||
bool allPointsInside = true; // assume the best
|
||||
for (int v = BOTTOM_LEFT_NEAR; v < TOP_LEFT_FAR; v++) {
|
||||
glm::vec3 vertex = cube.getVertex((BoxVertex)v);
|
||||
if (!pointInKeyhole(vertex)) {
|
||||
allPointsInside = false;
|
||||
break;
|
||||
ViewFrustum::intersection ViewFrustum::calculateCubeFrustumIntersection(const AACube& cube) const {
|
||||
// only check against frustum
|
||||
ViewFrustum::intersection result = INSIDE;
|
||||
for(int i=0; i < 6; i++) {
|
||||
const glm::vec3& normal = _planes[i].getNormal();
|
||||
// check distance to farthest cube point
|
||||
if ( _planes[i].distance(cube.getFarthestVertex(normal)) < 0.0f) {
|
||||
return OUTSIDE;
|
||||
} else {
|
||||
// check distance to nearest cube point
|
||||
if (_planes[i].distance(cube.getNearestVertex(normal)) < 0.0f) {
|
||||
// cube straddles the plane
|
||||
result = INTERSECT;
|
||||
}
|
||||
}
|
||||
|
||||
if (allPointsInside) {
|
||||
result = INSIDE;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// A box is inside a sphere if all of its corners are inside the sphere
|
||||
// A box intersects a sphere if any of its edges (as rays) interesect the sphere
|
||||
// A box is outside a sphere if none of its edges (as rays) interesect the sphere
|
||||
ViewFrustum::location ViewFrustum::boxInKeyhole(const AABox& box) const {
|
||||
const float HALF_SQRT_THREE = 0.8660254f;
|
||||
|
||||
// First check to see if the box is in the bounding box for the sphere, if it's not, then we can short circuit
|
||||
// this and not check with sphere penetration which is more expensive
|
||||
if (!_keyholeBoundingCube.contains(box)) {
|
||||
return OUTSIDE;
|
||||
ViewFrustum::intersection ViewFrustum::calculateCubeKeyholeIntersection(const AACube& cube) const {
|
||||
// check against centeral sphere
|
||||
ViewFrustum::intersection sphereResult = INTERSECT;
|
||||
glm::vec3 cubeOffset = cube.calcCenter() - _position;
|
||||
float distance = glm::length(cubeOffset);
|
||||
if (distance > EPSILON) {
|
||||
glm::vec3 vertex = cube.getFarthestVertex(cubeOffset) - _position;
|
||||
if (glm::dot(vertex, cubeOffset) < _centerSphereRadius * distance) {
|
||||
// the most outward cube vertex is inside central sphere
|
||||
return INSIDE;
|
||||
}
|
||||
if (!cube.touchesSphere(_position, _centerSphereRadius)) {
|
||||
sphereResult = OUTSIDE;
|
||||
}
|
||||
} else if (_centerSphereRadius > HALF_SQRT_THREE * cube.getScale()) {
|
||||
// the cube is in center of sphere and its bounding radius is inside
|
||||
return INSIDE;
|
||||
}
|
||||
|
||||
glm::vec3 penetration;
|
||||
bool intersects = box.findSpherePenetration(_position, _keyholeRadius, penetration);
|
||||
// check against frustum
|
||||
ViewFrustum::intersection frustumResult = calculateCubeFrustumIntersection(cube);
|
||||
|
||||
ViewFrustum::location result = OUTSIDE;
|
||||
|
||||
// if the box intersects the sphere, then it may also be inside... calculate further
|
||||
if (intersects) {
|
||||
result = INTERSECT;
|
||||
|
||||
// test all the corners, if they are all inside the sphere, the entire box is in the sphere
|
||||
bool allPointsInside = true; // assume the best
|
||||
for (int v = BOTTOM_LEFT_NEAR; v < TOP_LEFT_FAR; v++) {
|
||||
glm::vec3 vertex = box.getVertex((BoxVertex)v);
|
||||
if (!pointInKeyhole(vertex)) {
|
||||
allPointsInside = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allPointsInside) {
|
||||
result = INSIDE;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return (frustumResult == OUTSIDE) ? sphereResult : frustumResult;
|
||||
}
|
||||
|
||||
ViewFrustum::location ViewFrustum::pointInFrustum(const glm::vec3& point, bool ignoreKeyhole) const {
|
||||
ViewFrustum::location regularResult = INSIDE;
|
||||
ViewFrustum::location keyholeResult = OUTSIDE;
|
||||
|
||||
// If we have a keyholeRadius, check that first, since it's cheaper
|
||||
if (!ignoreKeyhole && _keyholeRadius >= 0.0f) {
|
||||
keyholeResult = pointInKeyhole(point);
|
||||
|
||||
if (keyholeResult == INSIDE) {
|
||||
return keyholeResult;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're not known to be INSIDE the keyhole, then check the regular frustum
|
||||
bool ViewFrustum::pointIntersectsFrustum(const glm::vec3& point) const {
|
||||
// only check against frustum
|
||||
for(int i = 0; i < 6; ++i) {
|
||||
float distance = _planes[i].distance(point);
|
||||
if (distance < 0) {
|
||||
return keyholeResult; // escape early will be the value from checking the keyhole
|
||||
if (distance < 0.0f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return regularResult;
|
||||
return true;
|
||||
}
|
||||
|
||||
ViewFrustum::location ViewFrustum::sphereInFrustum(const glm::vec3& center, float radius) const {
|
||||
ViewFrustum::location regularResult = INSIDE;
|
||||
ViewFrustum::location keyholeResult = OUTSIDE;
|
||||
|
||||
// If we have a keyholeRadius, check that first, since it's cheaper
|
||||
if (_keyholeRadius >= 0.0f) {
|
||||
keyholeResult = sphereInKeyhole(center, radius);
|
||||
}
|
||||
if (keyholeResult == INSIDE) {
|
||||
return keyholeResult;
|
||||
}
|
||||
|
||||
float distance;
|
||||
bool ViewFrustum::sphereIntersectsFrustum(const glm::vec3& center, float radius) const {
|
||||
// only check against frustum
|
||||
for(int i=0; i < 6; i++) {
|
||||
distance = _planes[i].distance(center);
|
||||
float distance = _planes[i].distance(center);
|
||||
if (distance < -radius) {
|
||||
// This is outside the regular frustum, so just return the value from checking the keyhole
|
||||
return keyholeResult;
|
||||
} else if (distance < radius) {
|
||||
regularResult = INTERSECT;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return regularResult;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
ViewFrustum::location ViewFrustum::cubeInFrustum(const AACube& cube) const {
|
||||
|
||||
ViewFrustum::location regularResult = INSIDE;
|
||||
ViewFrustum::location keyholeResult = OUTSIDE;
|
||||
|
||||
// If we have a keyholeRadius, check that first, since it's cheaper
|
||||
if (_keyholeRadius >= 0.0f) {
|
||||
keyholeResult = cubeInKeyhole(cube);
|
||||
}
|
||||
if (keyholeResult == INSIDE) {
|
||||
return keyholeResult;
|
||||
}
|
||||
|
||||
// TODO: These calculations are expensive, taking up 80% of our time in this function.
|
||||
// This appears to be expensive because we have to test the distance to each plane.
|
||||
// One suggested optimization is to first check against the approximated cone. We might
|
||||
// also be able to test against the cone to the bounding sphere of the box.
|
||||
bool ViewFrustum::boxIntersectsFrustum(const AABox& box) const {
|
||||
// only check against frustum
|
||||
for(int i=0; i < 6; i++) {
|
||||
const glm::vec3& normal = _planes[i].getNormal();
|
||||
const glm::vec3& boxVertexP = cube.getVertexP(normal);
|
||||
float planeToBoxVertexPDistance = _planes[i].distance(boxVertexP);
|
||||
|
||||
const glm::vec3& boxVertexN = cube.getVertexN(normal);
|
||||
float planeToBoxVertexNDistance = _planes[i].distance(boxVertexN);
|
||||
|
||||
if (planeToBoxVertexPDistance < 0) {
|
||||
// This is outside the regular frustum, so just return the value from checking the keyhole
|
||||
return keyholeResult;
|
||||
} else if (planeToBoxVertexNDistance < 0) {
|
||||
regularResult = INTERSECT;
|
||||
// check distance to farthest box point
|
||||
if ( _planes[i].distance(box.getFarthestVertex(normal)) < 0.0f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return regularResult;
|
||||
return true;
|
||||
}
|
||||
|
||||
ViewFrustum::location ViewFrustum::boxInFrustum(const AABox& box) const {
|
||||
|
||||
ViewFrustum::location regularResult = INSIDE;
|
||||
ViewFrustum::location keyholeResult = OUTSIDE;
|
||||
|
||||
// If we have a keyholeRadius, check that first, since it's cheaper
|
||||
if (_keyholeRadius >= 0.0f) {
|
||||
keyholeResult = boxInKeyhole(box);
|
||||
bool ViewFrustum::sphereIntersectsKeyhole(const glm::vec3& center, float radius) const {
|
||||
// check positive touch against central sphere
|
||||
if (glm::length(center - _position) <= (radius + _centerSphereRadius)) {
|
||||
return true;
|
||||
}
|
||||
if (keyholeResult == INSIDE) {
|
||||
return keyholeResult;
|
||||
}
|
||||
|
||||
// TODO: These calculations are expensive, taking up 80% of our time in this function.
|
||||
// This appears to be expensive because we have to test the distance to each plane.
|
||||
// One suggested optimization is to first check against the approximated cone. We might
|
||||
// also be able to test against the cone to the bounding sphere of the box.
|
||||
// check negative touches against frustum planes
|
||||
for(int i=0; i < 6; i++) {
|
||||
const glm::vec3& normal = _planes[i].getNormal();
|
||||
const glm::vec3& boxVertexP = box.getVertexP(normal);
|
||||
float planeToBoxVertexPDistance = _planes[i].distance(boxVertexP);
|
||||
|
||||
const glm::vec3& boxVertexN = box.getVertexN(normal);
|
||||
float planeToBoxVertexNDistance = _planes[i].distance(boxVertexN);
|
||||
|
||||
if (planeToBoxVertexPDistance < 0) {
|
||||
// This is outside the regular frustum, so just return the value from checking the keyhole
|
||||
return keyholeResult;
|
||||
} else if (planeToBoxVertexNDistance < 0) {
|
||||
regularResult = INTERSECT;
|
||||
if ( _planes[i].distance(center) < -radius) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return regularResult;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ViewFrustum::cubeIntersectsKeyhole(const AACube& cube) const {
|
||||
// check positive touch against central sphere
|
||||
if (cube.touchesSphere(_position, _centerSphereRadius)) {
|
||||
return true;
|
||||
}
|
||||
// check negative touches against frustum planes
|
||||
for(int i=0; i < 6; i++) {
|
||||
const glm::vec3& normal = _planes[i].getNormal();
|
||||
if ( _planes[i].distance(cube.getFarthestVertex(normal)) < 0.0f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ViewFrustum::boxIntersectsKeyhole(const AABox& box) const {
|
||||
// check positive touch against central sphere
|
||||
if (box.touchesSphere(_position, _centerSphereRadius)) {
|
||||
return true;
|
||||
}
|
||||
// check negative touches against frustum planes
|
||||
for(int i=0; i < 6; i++) {
|
||||
const glm::vec3& normal = _planes[i].getNormal();
|
||||
if ( _planes[i].distance(box.getFarthestVertex(normal)) < 0.0f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool testMatches(glm::quat lhs, glm::quat rhs, float epsilon = EPSILON) {
|
||||
|
@ -490,7 +384,7 @@ PickRay ViewFrustum::computePickRay(float x, float y) {
|
|||
}
|
||||
|
||||
void ViewFrustum::computePickRay(float x, float y, glm::vec3& origin, glm::vec3& direction) const {
|
||||
origin = _cornersWorld[TOP_LEFT_NEAR] + x * (_cornersWorld[TOP_RIGHT_NEAR] - _cornersWorld[TOP_LEFT_NEAR]) +
|
||||
origin = _cornersWorld[TOP_LEFT_NEAR] + x * (_cornersWorld[TOP_RIGHT_NEAR] - _cornersWorld[TOP_LEFT_NEAR]) +
|
||||
y * (_cornersWorld[BOTTOM_LEFT_NEAR] - _cornersWorld[TOP_LEFT_NEAR]);
|
||||
direction = glm::normalize(origin - _position);
|
||||
}
|
||||
|
@ -540,7 +434,7 @@ void ViewFrustum::printDebugDetails() const {
|
|||
qCDebug(octree, "_right=%f,%f,%f", (double)_right.x, (double)_right.y, (double)_right.z );
|
||||
qCDebug(octree, "_fieldOfView=%f", (double)_fieldOfView);
|
||||
qCDebug(octree, "_aspectRatio=%f", (double)_aspectRatio);
|
||||
qCDebug(octree, "_keyHoleRadius=%f", (double)_keyholeRadius);
|
||||
qCDebug(octree, "_centerSphereRadius=%f", (double)_centerSphereRadius);
|
||||
qCDebug(octree, "_nearClip=%f", (double)_nearClip);
|
||||
qCDebug(octree, "_farClip=%f", (double)_farClip);
|
||||
qCDebug(octree, "_focalLength=%f", (double)_focalLength);
|
||||
|
@ -804,7 +698,7 @@ float ViewFrustum::calculateRenderAccuracy(const AABox& bounds, float octreeSize
|
|||
// FIXME - for now, it's either visible or not visible. We want to adjust this to eventually return
|
||||
// a floating point for objects that have small angular size to indicate that they may be rendered
|
||||
// with lower preciscion
|
||||
return (distanceToCamera <= visibleDistanceAtClosestScale) ? 1.0f : 0.0f;
|
||||
return (distanceToCamera <= visibleDistanceAtClosestScale) ? 1.0f : 0.0f;
|
||||
}
|
||||
|
||||
float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale) {
|
||||
|
|
|
@ -27,12 +27,15 @@
|
|||
#include "OctreeConstants.h"
|
||||
#include "OctreeProjectedPolygon.h"
|
||||
|
||||
const float DEFAULT_KEYHOLE_RADIUS = 3.0f;
|
||||
const float DEFAULT_CENTER_SPHERE_RADIUS = 3.0f;
|
||||
const float DEFAULT_FIELD_OF_VIEW_DEGREES = 45.0f;
|
||||
const float DEFAULT_ASPECT_RATIO = 16.0f/9.0f;
|
||||
const float DEFAULT_NEAR_CLIP = 0.08f;
|
||||
const float DEFAULT_FAR_CLIP = (float)HALF_TREE_SCALE;
|
||||
|
||||
// the "ViewFrustum" has a "keyhole" shape: a regular frustum for stuff that is "visible" with
|
||||
// a central sphere for stuff that is nearby (for physics simulation).
|
||||
|
||||
class ViewFrustum {
|
||||
public:
|
||||
// setters for camera attributes
|
||||
|
@ -83,18 +86,26 @@ public:
|
|||
const glm::vec3& getNearBottomLeft() const { return _cornersWorld[BOTTOM_LEFT_NEAR]; }
|
||||
const glm::vec3& getNearBottomRight() const { return _cornersWorld[BOTTOM_RIGHT_NEAR]; }
|
||||
|
||||
// get/set for keyhole attribute
|
||||
void setKeyholeRadius(float keyholdRadius) { _keyholeRadius = keyholdRadius; }
|
||||
float getKeyholeRadius() const { return _keyholeRadius; }
|
||||
// get/set for central spherek attribute
|
||||
void setCenterRadius(float radius) { _centerSphereRadius = radius; }
|
||||
float getCenterRadius() const { return _centerSphereRadius; }
|
||||
|
||||
void calculate();
|
||||
|
||||
typedef enum {OUTSIDE, INTERSECT, INSIDE} location;
|
||||
typedef enum { OUTSIDE = 0, INTERSECT, INSIDE } intersection;
|
||||
|
||||
ViewFrustum::location pointInFrustum(const glm::vec3& point, bool ignoreKeyhole = false) const;
|
||||
ViewFrustum::location sphereInFrustum(const glm::vec3& center, float radius) const;
|
||||
ViewFrustum::location cubeInFrustum(const AACube& cube) const;
|
||||
ViewFrustum::location boxInFrustum(const AABox& box) const;
|
||||
/// @return INSIDE, INTERSECT, or OUTSIDE depending on how cube intersects the keyhole shape
|
||||
ViewFrustum::intersection calculateCubeFrustumIntersection(const AACube& cube) const;
|
||||
ViewFrustum::intersection calculateCubeKeyholeIntersection(const AACube& cube) const;
|
||||
|
||||
bool pointIntersectsFrustum(const glm::vec3& point) const;
|
||||
bool sphereIntersectsFrustum(const glm::vec3& center, float radius) const;
|
||||
bool cubeIntersectsFrustum(const AACube& box) const;
|
||||
bool boxIntersectsFrustum(const AABox& box) const;
|
||||
|
||||
bool sphereIntersectsKeyhole(const glm::vec3& center, float radius) const;
|
||||
bool cubeIntersectsKeyhole(const AACube& cube) const;
|
||||
bool boxIntersectsKeyhole(const AABox& box) const;
|
||||
|
||||
// some frustum comparisons
|
||||
bool matches(const ViewFrustum& compareTo, bool debug = false) const;
|
||||
|
@ -114,15 +125,15 @@ public:
|
|||
glm::vec2 projectPoint(glm::vec3 point, bool& pointInView) const;
|
||||
OctreeProjectedPolygon getProjectedPolygon(const AACube& box) const;
|
||||
void getFurthestPointFromCamera(const AACube& box, glm::vec3& furthestPoint) const;
|
||||
|
||||
|
||||
float distanceToCamera(const glm::vec3& point) const;
|
||||
|
||||
|
||||
void evalProjectionMatrix(glm::mat4& proj) const;
|
||||
void evalViewTransform(Transform& view) const;
|
||||
|
||||
/// renderAccuracy represents a floating point "visibility" of an object based on it's view from the camera. At a simple
|
||||
/// level it returns 0.0f for things that are so small for the current settings that they could not be visible.
|
||||
float calculateRenderAccuracy(const AABox& bounds, float octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE,
|
||||
float calculateRenderAccuracy(const AABox& bounds, float octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE,
|
||||
int boundaryLevelAdjust = 0) const;
|
||||
|
||||
float getAccuracyAngle(float octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE, int boundaryLevelAdjust = 0) const;
|
||||
|
@ -131,12 +142,6 @@ public:
|
|||
|
||||
const ::Plane* getPlanes() const { return _planes; }
|
||||
private:
|
||||
// Used for keyhole calculations
|
||||
ViewFrustum::location pointInKeyhole(const glm::vec3& point) const;
|
||||
ViewFrustum::location sphereInKeyhole(const glm::vec3& center, float radius) const;
|
||||
ViewFrustum::location cubeInKeyhole(const AACube& cube) const;
|
||||
ViewFrustum::location boxInKeyhole(const AABox& box) const;
|
||||
|
||||
// camera location/orientation attributes
|
||||
glm::vec3 _position; // the position in world-frame
|
||||
glm::quat _orientation;
|
||||
|
@ -150,9 +155,7 @@ private:
|
|||
glm::vec3 _up = IDENTITY_UP;
|
||||
glm::vec3 _right = IDENTITY_RIGHT;
|
||||
|
||||
// keyhole attributes
|
||||
float _keyholeRadius = DEFAULT_KEYHOLE_RADIUS;
|
||||
AACube _keyholeBoundingCube;
|
||||
float _centerSphereRadius = DEFAULT_CENTER_SPHERE_RADIUS;
|
||||
|
||||
// Calculated values
|
||||
glm::mat4 _inverseProjection;
|
||||
|
@ -165,7 +168,7 @@ private:
|
|||
float _fieldOfView = DEFAULT_FIELD_OF_VIEW_DEGREES;
|
||||
glm::vec4 _corners[8];
|
||||
glm::vec3 _cornersWorld[8];
|
||||
::Plane _planes[6]; // How will this be used?
|
||||
::Plane _planes[6]; // plane normals point inside frustum
|
||||
|
||||
const char* debugPlaneName (int plane) const;
|
||||
|
||||
|
|
|
@ -272,7 +272,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
|
|||
float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP;
|
||||
|
||||
if (_numInactiveUpdates > 0) {
|
||||
const uint8_t MAX_NUM_INACTIVE_UPDATES = 3;
|
||||
const uint8_t MAX_NUM_INACTIVE_UPDATES = 20;
|
||||
if (_numInactiveUpdates > MAX_NUM_INACTIVE_UPDATES) {
|
||||
// clear local ownership (stop sending updates) and let the server clear itself
|
||||
_entity->clearSimulationOwnership();
|
||||
|
@ -282,7 +282,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
|
|||
// until it is removed from the outgoing updates
|
||||
// (which happens when we don't own the simulation and it isn't touching our simulation)
|
||||
const float INACTIVE_UPDATE_PERIOD = 0.5f;
|
||||
return (dt > INACTIVE_UPDATE_PERIOD);
|
||||
return (dt > INACTIVE_UPDATE_PERIOD * (float)_numInactiveUpdates);
|
||||
}
|
||||
|
||||
if (!_body->isActive()) {
|
||||
|
@ -404,8 +404,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q
|
|||
assert(_entity);
|
||||
assert(entityTreeIsLocked());
|
||||
|
||||
bool active = _body->isActive();
|
||||
if (!active) {
|
||||
if (!_body->isActive()) {
|
||||
// make sure all derivatives are zero
|
||||
glm::vec3 zero(0.0f);
|
||||
_entity->setVelocity(zero);
|
||||
|
@ -495,16 +494,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q
|
|||
qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now);
|
||||
#endif //def WANT_DEBUG
|
||||
|
||||
if (sessionID == _entity->getSimulatorID()) {
|
||||
// we think we own the simulation
|
||||
if (!active) {
|
||||
// we own the simulation but the entity has stopped, so we tell the server that we're clearing simulatorID
|
||||
// but we remember that we do still own it... and rely on the server to tell us that we don't
|
||||
properties.clearSimulationOwner();
|
||||
_outgoingPriority = ZERO_SIMULATION_PRIORITY;
|
||||
}
|
||||
// else the ownership is not changing so we don't bother to pack it
|
||||
} else {
|
||||
if (_numInactiveUpdates > 0) {
|
||||
// we own the simulation but the entity has stopped, so we tell the server that we're clearing simulatorID
|
||||
// but we remember that we do still own it... and rely on the server to tell us that we don't
|
||||
properties.clearSimulationOwner();
|
||||
_outgoingPriority = ZERO_SIMULATION_PRIORITY;
|
||||
} else if (sessionID != _entity->getSimulatorID()) {
|
||||
// we don't own the simulation for this entity yet, but we're sending a bid for it
|
||||
properties.setSimulationOwner(sessionID, glm::max<uint8_t>(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY));
|
||||
_nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS;
|
||||
|
|
|
@ -87,23 +87,20 @@ void DeferredLightingEffect::init() {
|
|||
_allocatedLights.push_back(std::make_shared<model::Light>());
|
||||
|
||||
model::LightPointer lp = _allocatedLights[0];
|
||||
lp->setType(model::Light::SUN);
|
||||
|
||||
// Add the global light to the light stage (for later shadow rendering)
|
||||
_lightStage.addLight(lp);
|
||||
|
||||
lp->setDirection(-glm::vec3(1.0f, 1.0f, 1.0f));
|
||||
lp->setColor(glm::vec3(1.0f));
|
||||
lp->setIntensity(1.0f);
|
||||
lp->setType(model::Light::SUN);
|
||||
lp->setAmbientSpherePreset(gpu::SphericalHarmonics::Preset(_ambientLightMode % gpu::SphericalHarmonics::NUM_PRESET));
|
||||
}
|
||||
|
||||
void DeferredLightingEffect::addPointLight(const glm::vec3& position, float radius, const glm::vec3& color,
|
||||
float intensity) {
|
||||
addSpotLight(position, radius, color, intensity);
|
||||
float intensity, float falloffRadius) {
|
||||
addSpotLight(position, radius, color, intensity, falloffRadius);
|
||||
}
|
||||
|
||||
void DeferredLightingEffect::addSpotLight(const glm::vec3& position, float radius, const glm::vec3& color,
|
||||
float intensity, const glm::quat& orientation, float exponent, float cutoff) {
|
||||
float intensity, float falloffRadius, const glm::quat& orientation, float exponent, float cutoff) {
|
||||
|
||||
unsigned int lightID = (unsigned int)(_pointLights.size() + _spotLights.size() + _globalLights.size());
|
||||
if (lightID >= _allocatedLights.size()) {
|
||||
|
@ -115,7 +112,7 @@ void DeferredLightingEffect::addSpotLight(const glm::vec3& position, float radiu
|
|||
lp->setMaximumRadius(radius);
|
||||
lp->setColor(color);
|
||||
lp->setIntensity(intensity);
|
||||
//lp->setShowContour(quadraticAttenuation);
|
||||
lp->setFalloffRadius(falloffRadius);
|
||||
|
||||
if (exponent == 0.0f && cutoff == PI) {
|
||||
lp->setType(model::Light::POINT);
|
||||
|
@ -560,7 +557,13 @@ static void loadLightProgram(const char* vertSource, const char* fragSource, boo
|
|||
}
|
||||
|
||||
void DeferredLightingEffect::setGlobalLight(const model::LightPointer& light, const gpu::TexturePointer& skyboxTexture) {
|
||||
_allocatedLights.front() = light;
|
||||
auto globalLight = _allocatedLights.front();
|
||||
globalLight->setDirection(light->getDirection());
|
||||
globalLight->setColor(light->getColor());
|
||||
globalLight->setIntensity(light->getIntensity());
|
||||
globalLight->setAmbientIntensity(light->getAmbientIntensity());
|
||||
globalLight->setAmbientSphere(light->getAmbientSphere());
|
||||
|
||||
_skyboxTexture = skyboxTexture;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -413,9 +413,9 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const {
|
|||
void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) const {
|
||||
if (!_isBlendShaped) {
|
||||
batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
|
||||
|
||||
|
||||
batch.setInputFormat((_drawMesh->getVertexFormat()));
|
||||
|
||||
|
||||
batch.setInputStream(0, _drawMesh->getVertexStream());
|
||||
} else {
|
||||
batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
|
||||
|
@ -426,7 +426,7 @@ void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) const {
|
|||
batch.setInputBuffer(1, _model->_blendedVertexBuffers[_meshIndex], _drawMesh->getNumVertices() * sizeof(glm::vec3), sizeof(glm::vec3));
|
||||
batch.setInputStream(2, _drawMesh->getVertexStream().makeRangedStream(2));
|
||||
}
|
||||
|
||||
|
||||
// TODO: Get rid of that extra call
|
||||
if (!_hasColorAttrib) {
|
||||
batch._glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
|
@ -474,17 +474,14 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
|
|||
#ifdef DEBUG_BOUNDING_PARTS
|
||||
{
|
||||
AABox partBounds = getPartBounds(_meshIndex, partIndex);
|
||||
bool inView = args->_viewFrustum->boxInFrustum(partBounds) != ViewFrustum::OUTSIDE;
|
||||
|
||||
glm::vec4 cubeColor;
|
||||
|
||||
glm::vec4 cubeColor(1.0f, 1.0f, 0.0f, 1.0f);
|
||||
if (isSkinned) {
|
||||
cubeColor = glm::vec4(0.0f, 1.0f, 1.0f, 1.0f);
|
||||
} else if (inView) {
|
||||
} else if (args->_viewFrustum->boxIntersectsFrustum(partBounds)) {
|
||||
cubeColor = glm::vec4(1.0f, 0.0f, 1.0f, 1.0f);
|
||||
} else {
|
||||
cubeColor = glm::vec4(1.0f, 1.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
|
||||
Transform transform;
|
||||
transform.setTranslation(partBounds.calcCenter());
|
||||
transform.setScale(partBounds.getDimensions());
|
||||
|
@ -492,7 +489,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
|
|||
DependencyManager::get<GeometryCache>()->renderWireCube(batch, 1.0f, cubeColor);
|
||||
}
|
||||
#endif //def DEBUG_BOUNDING_PARTS
|
||||
|
||||
|
||||
auto locations = args->_pipeline->locations;
|
||||
assert(locations);
|
||||
|
||||
|
@ -500,23 +497,23 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
|
|||
bool canCauterize = args->_renderMode != RenderArgs::SHADOW_RENDER_MODE;
|
||||
_model->updateClusterMatrices(_transform.getTranslation(), _transform.getRotation());
|
||||
bindTransform(batch, locations, canCauterize);
|
||||
|
||||
|
||||
//Bind the index buffer and vertex buffer and Blend shapes if needed
|
||||
bindMesh(batch);
|
||||
|
||||
|
||||
// apply material properties
|
||||
bindMaterial(batch, locations);
|
||||
|
||||
|
||||
if (args) {
|
||||
args->_details._materialSwitches++;
|
||||
}
|
||||
|
||||
|
||||
// Draw!
|
||||
{
|
||||
PerformanceTimer perfTimer("batch.drawIndexed()");
|
||||
drawCall(batch);
|
||||
}
|
||||
|
||||
|
||||
if (args) {
|
||||
const int INDICES_PER_TRIANGLE = 3;
|
||||
args->_details._trianglesRendered += _drawPart._numIndices / INDICES_PER_TRIANGLE;
|
||||
|
|
|
@ -48,7 +48,7 @@ void main(void) {
|
|||
vec3 fragLightVec = getLightPosition(light) - fragPos.xyz;
|
||||
|
||||
// Kill if too far from the light center
|
||||
if (dot(fragLightVec, fragLightVec) > getLightSquareRadius(light)) {
|
||||
if (dot(fragLightVec, fragLightVec) > getLightCutoffSquareRadius(light)) {
|
||||
discard;
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ void main(void) {
|
|||
vec3 fragLightVec = getLightPosition(light) - fragPos.xyz;
|
||||
|
||||
// Kill if too far from the light center
|
||||
if (dot(fragLightVec, fragLightVec) > getLightSquareRadius(light)) {
|
||||
if (dot(fragLightVec, fragLightVec) > getLightCutoffSquareRadius(light)) {
|
||||
discard;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ void render::cullItems(const RenderContextPointer& renderContext, const CullFunc
|
|||
ViewFrustum* frustum = args->_viewFrustum;
|
||||
|
||||
details._considered += (int)inItems.size();
|
||||
|
||||
|
||||
// Culling / LOD
|
||||
for (auto item : inItems) {
|
||||
if (item.bound.isNull()) {
|
||||
|
@ -39,12 +39,12 @@ void render::cullItems(const RenderContextPointer& renderContext, const CullFunc
|
|||
|
||||
// TODO: some entity types (like lights) might want to be rendered even
|
||||
// when they are outside of the view frustum...
|
||||
bool outOfView;
|
||||
bool inView;
|
||||
{
|
||||
PerformanceTimer perfTimer("boxInFrustum");
|
||||
outOfView = frustum->boxInFrustum(item.bound) == ViewFrustum::OUTSIDE;
|
||||
PerformanceTimer perfTimer("boxIntersectsFrustum");
|
||||
inView = frustum->boxIntersectsFrustum(item.bound);
|
||||
}
|
||||
if (!outOfView) {
|
||||
if (inView) {
|
||||
bool bigEnoughToRender;
|
||||
{
|
||||
PerformanceTimer perfTimer("shouldRender");
|
||||
|
@ -88,10 +88,10 @@ struct BackToFrontSort {
|
|||
void render::depthSortItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, bool frontToBack, const ItemBounds& inItems, ItemBounds& outItems) {
|
||||
assert(renderContext->args);
|
||||
assert(renderContext->args->_viewFrustum);
|
||||
|
||||
|
||||
auto& scene = sceneContext->_scene;
|
||||
RenderArgs* args = renderContext->args;
|
||||
|
||||
|
||||
|
||||
// Allocate and simply copy
|
||||
outItems.clear();
|
||||
|
@ -238,7 +238,7 @@ void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const Re
|
|||
}
|
||||
|
||||
bool frustumTest(const AABox& bound) {
|
||||
if (_args->_viewFrustum->boxInFrustum(bound) == ViewFrustum::OUTSIDE) {
|
||||
if (!_args->_viewFrustum->boxIntersectsFrustum(bound)) {
|
||||
_renderDetails._outOfView++;
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -75,32 +75,32 @@ void AABox::setBox(const glm::vec3& corner, const glm::vec3& scale) {
|
|||
_scale = scale;
|
||||
}
|
||||
|
||||
glm::vec3 AABox::getVertexP(const glm::vec3& normal) const {
|
||||
glm::vec3 AABox::getFarthestVertex(const glm::vec3& normal) const {
|
||||
glm::vec3 result = _corner;
|
||||
if (normal.x > 0) {
|
||||
if (normal.x > 0.0f) {
|
||||
result.x += _scale.x;
|
||||
}
|
||||
if (normal.y > 0) {
|
||||
if (normal.y > 0.0f) {
|
||||
result.y += _scale.y;
|
||||
}
|
||||
if (normal.z > 0) {
|
||||
if (normal.z > 0.0f) {
|
||||
result.z += _scale.z;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::vec3 AABox::getVertexN(const glm::vec3& normal) const {
|
||||
glm::vec3 AABox::getNearestVertex(const glm::vec3& normal) const {
|
||||
glm::vec3 result = _corner;
|
||||
|
||||
if (normal.x < 0) {
|
||||
if (normal.x < 0.0f) {
|
||||
result.x += _scale.x;
|
||||
}
|
||||
|
||||
if (normal.y < 0) {
|
||||
if (normal.y < 0.0f) {
|
||||
result.y += _scale.y;
|
||||
}
|
||||
|
||||
if (normal.z < 0) {
|
||||
if (normal.z < 0.0f) {
|
||||
result.z += _scale.z;
|
||||
}
|
||||
|
||||
|
@ -217,7 +217,7 @@ bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& e
|
|||
isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x));
|
||||
}
|
||||
|
||||
bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal) const {
|
||||
// handle the trivial case where the box contains the origin
|
||||
if (contains(origin)) {
|
||||
|
@ -281,6 +281,12 @@ bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direct
|
|||
return false;
|
||||
}
|
||||
|
||||
bool AABox::touchesSphere(const glm::vec3& center, float radius) const {
|
||||
// Avro's algorithm from this paper: http://www.mrtc.mdh.se/projects/3Dgraphics/paperF.pdf
|
||||
glm::vec3 e = glm::max(_corner - center, Vectors::ZERO) + glm::max(center - _corner - _scale, Vectors::ZERO);
|
||||
return glm::length2(e) <= radius * radius;
|
||||
}
|
||||
|
||||
bool AABox::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const {
|
||||
glm::vec4 center4 = glm::vec4(center, 1.0f);
|
||||
|
||||
|
@ -537,4 +543,4 @@ void AABox::transform(const Transform& transform) {
|
|||
scale(transform.getScale());
|
||||
rotate(transform.getRotation());
|
||||
translate(transform.getTranslation());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,12 +35,12 @@ public:
|
|||
AABox(const glm::vec3& corner, const glm::vec3& dimensions);
|
||||
AABox();
|
||||
~AABox() {};
|
||||
|
||||
|
||||
void setBox(const glm::vec3& corner, const glm::vec3& scale);
|
||||
|
||||
void setBox(const glm::vec3& corner, float scale);
|
||||
glm::vec3 getVertexP(const glm::vec3& normal) const;
|
||||
glm::vec3 getVertexN(const glm::vec3& normal) const;
|
||||
glm::vec3 getFarthestVertex(const glm::vec3& normal) const; // return vertex most parallel to normal
|
||||
glm::vec3 getNearestVertex(const glm::vec3& normal) const; // return vertex most anti-parallel to normal
|
||||
|
||||
const glm::vec3& getCorner() const { return _corner; }
|
||||
const glm::vec3& getScale() const { return _scale; }
|
||||
|
@ -68,11 +68,12 @@ public:
|
|||
|
||||
bool expandedContains(const glm::vec3& point, float expansion) const;
|
||||
bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const;
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal) const;
|
||||
bool touchesSphere(const glm::vec3& center, float radius) const; // fast but may generate false positives
|
||||
bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const;
|
||||
bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const;
|
||||
|
||||
|
||||
bool isNull() const { return _scale == glm::vec3(0.0f, 0.0f, 0.0f); }
|
||||
|
||||
AABox clamp(const glm::vec3& min, const glm::vec3& max) const;
|
||||
|
@ -113,7 +114,7 @@ inline bool operator==(const AABox& a, const AABox& b) {
|
|||
}
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const AABox& box) {
|
||||
debug << "AABox[ ("
|
||||
debug << "AABox[ ("
|
||||
<< box.getCorner().x << "," << box.getCorner().y << "," << box.getCorner().z << " ) to ("
|
||||
<< box.calcTopFarLeft().x << "," << box.calcTopFarLeft().y << "," << box.calcTopFarLeft().z << ") size: ("
|
||||
<< box.getDimensions().x << "," << box.getDimensions().y << "," << box.getDimensions().z << ")"
|
||||
|
|
|
@ -79,32 +79,32 @@ void AACube::setBox(const glm::vec3& corner, float scale) {
|
|||
_scale = scale;
|
||||
}
|
||||
|
||||
glm::vec3 AACube::getVertexP(const glm::vec3& normal) const {
|
||||
glm::vec3 AACube::getFarthestVertex(const glm::vec3& normal) const {
|
||||
glm::vec3 result = _corner;
|
||||
if (normal.x > 0) {
|
||||
if (normal.x > 0.0f) {
|
||||
result.x += _scale;
|
||||
}
|
||||
if (normal.y > 0) {
|
||||
if (normal.y > 0.0f) {
|
||||
result.y += _scale;
|
||||
}
|
||||
if (normal.z > 0) {
|
||||
if (normal.z > 0.0f) {
|
||||
result.z += _scale;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::vec3 AACube::getVertexN(const glm::vec3& normal) const {
|
||||
glm::vec3 AACube::getNearestVertex(const glm::vec3& normal) const {
|
||||
glm::vec3 result = _corner;
|
||||
|
||||
if (normal.x < 0) {
|
||||
if (normal.x < 0.0f) {
|
||||
result.x += _scale;
|
||||
}
|
||||
|
||||
if (normal.y < 0) {
|
||||
if (normal.y < 0.0f) {
|
||||
result.y += _scale;
|
||||
}
|
||||
|
||||
if (normal.z < 0) {
|
||||
if (normal.z < 0.0f) {
|
||||
result.z += _scale;
|
||||
}
|
||||
|
||||
|
@ -284,6 +284,12 @@ bool AACube::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc
|
|||
return false;
|
||||
}
|
||||
|
||||
bool AACube::touchesSphere(const glm::vec3& center, float radius) const {
|
||||
// Avro's algorithm from this paper: http://www.mrtc.mdh.se/projects/3Dgraphics/paperF.pdf
|
||||
glm::vec3 e = glm::max(_corner - center, Vectors::ZERO) + glm::max(center - _corner - glm::vec3(_scale), Vectors::ZERO);
|
||||
return glm::length2(e) <= radius * radius;
|
||||
}
|
||||
|
||||
bool AACube::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const {
|
||||
glm::vec4 center4 = glm::vec4(center, 1.0f);
|
||||
|
||||
|
|
|
@ -34,8 +34,8 @@ public:
|
|||
~AACube() {};
|
||||
|
||||
void setBox(const glm::vec3& corner, float scale);
|
||||
glm::vec3 getVertexP(const glm::vec3& normal) const;
|
||||
glm::vec3 getVertexN(const glm::vec3& normal) const;
|
||||
glm::vec3 getFarthestVertex(const glm::vec3& normal) const; // return vertex most parallel to normal
|
||||
glm::vec3 getNearestVertex(const glm::vec3& normal) const; // return vertex most anti-parallel to normal
|
||||
void scale(float scale);
|
||||
const glm::vec3& getCorner() const { return _corner; }
|
||||
float getScale() const { return _scale; }
|
||||
|
@ -56,8 +56,9 @@ public:
|
|||
bool touches(const AABox& otherBox) const;
|
||||
bool expandedContains(const glm::vec3& point, float expansion) const;
|
||||
bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const;
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal) const;
|
||||
bool touchesSphere(const glm::vec3& center, float radius) const;
|
||||
bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const;
|
||||
bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const;
|
||||
|
||||
|
@ -88,7 +89,7 @@ inline bool operator!=(const AACube& a, const AACube& b) {
|
|||
}
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const AACube& cube) {
|
||||
debug << "AACube[ ("
|
||||
debug << "AACube[ ("
|
||||
<< cube.getCorner().x << "," << cube.getCorner().y << "," << cube.getCorner().z << " ) to ("
|
||||
<< cube.calcTopFarLeft().x << "," << cube.calcTopFarLeft().y << "," << cube.calcTopFarLeft().z << ") size: ("
|
||||
<< cube.getDimensions().x << "," << cube.getDimensions().y << "," << cube.getDimensions().z << ")"
|
||||
|
|
|
@ -460,6 +460,7 @@ void OffscreenUi::unfocusWindows() {
|
|||
}
|
||||
|
||||
void OffscreenUi::toggleMenu(const QPoint& screenPosition) {
|
||||
emit showDesktop(); // we really only want to do this if you're showing the menu, but for now this works
|
||||
auto virtualPos = mapToVirtualScreen(screenPosition, nullptr);
|
||||
QMetaObject::invokeMethod(_desktop, "toggleMenu", Q_ARG(QVariant, virtualPos));
|
||||
}
|
||||
|
|
|
@ -670,9 +670,8 @@ void OctreeTests::byteCountCodingTests() {
|
|||
}
|
||||
|
||||
void OctreeTests::modelItemTests() {
|
||||
bool verbose = true;
|
||||
|
||||
#if 0 // TODO - repair/replace these
|
||||
bool verbose = true;
|
||||
|
||||
//verbose = true;
|
||||
EntityTreeElementExtraEncodeData modelTreeElementExtraEncodeData;
|
||||
|
|
1347
tests/octree/src/ViewFrustumTests.cpp
Normal file
1347
tests/octree/src/ViewFrustumTests.cpp
Normal file
File diff suppressed because it is too large
Load diff
32
tests/octree/src/ViewFrustumTests.h
Normal file
32
tests/octree/src/ViewFrustumTests.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// ViewFrustumTests.h
|
||||
// tests/octree/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2016.02.19
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_ViewFruxtumTests_h
|
||||
#define hifi_ViewFruxtumTests_h
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
|
||||
class ViewFrustumTests : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void testInit();
|
||||
void testCubeFrustumIntersection();
|
||||
void testCubeKeyholeIntersection();
|
||||
void testPointIntersectsFrustum();
|
||||
void testSphereIntersectsFrustum();
|
||||
void testBoxIntersectsFrustum();
|
||||
void testSphereIntersectsKeyhole();
|
||||
void testCubeIntersectsKeyhole();
|
||||
void testBoxIntersectsKeyhole();
|
||||
};
|
||||
|
||||
#endif // hifi_ViewFruxtumTests_h
|
153
tests/shared/src/AABoxTests.cpp
Normal file
153
tests/shared/src/AABoxTests.cpp
Normal file
|
@ -0,0 +1,153 @@
|
|||
//
|
||||
// AABoxTests.cpp
|
||||
// tests/shared/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2016.02.19
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "AABoxTests.h"
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <StreamUtils.h>
|
||||
|
||||
#include <../GLMTestUtils.h>
|
||||
#include <../QTestExtensions.h>
|
||||
|
||||
|
||||
QTEST_MAIN(AABoxTests)
|
||||
|
||||
void AABoxTests::testCtorsAndSetters() {
|
||||
const glm::vec3 corner(1.23f, 4.56f, 7.89f);
|
||||
const glm::vec3 scale(2.34f, 7.53f, 9.14f);
|
||||
|
||||
// test ctor
|
||||
AABox box(corner, scale);
|
||||
QCOMPARE_WITH_ABS_ERROR(box.getCorner(), corner, EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(box.getScale(), scale, EPSILON);
|
||||
|
||||
// test copy ctor
|
||||
AABox copyBox(box);
|
||||
QCOMPARE_WITH_ABS_ERROR(copyBox.getCorner(), corner, EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(copyBox.getScale(), scale, EPSILON);
|
||||
|
||||
// test setBox()
|
||||
const glm::vec3 newCorner(9.87f, 6.54f, 3.21f);
|
||||
const glm::vec3 newScale = glm::vec3(4.32f, 8.95f, 10.31f);
|
||||
box.setBox(newCorner, newScale);
|
||||
QCOMPARE_WITH_ABS_ERROR(box.getCorner(), newCorner, EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(box.getScale(), newScale, EPSILON);
|
||||
|
||||
// test misc
|
||||
QCOMPARE_WITH_ABS_ERROR(newCorner, box.getMinimumPoint(), EPSILON);
|
||||
|
||||
glm::vec3 expectedMaxCorner = newCorner + glm::vec3(newScale);
|
||||
QCOMPARE_WITH_ABS_ERROR(expectedMaxCorner, box.getMaximumPoint(), EPSILON);
|
||||
|
||||
glm::vec3 expectedCenter = newCorner + glm::vec3(0.5f * newScale);
|
||||
QCOMPARE_WITH_ABS_ERROR(expectedCenter, box.calcCenter(), EPSILON);
|
||||
}
|
||||
|
||||
void AABoxTests::testContainsPoint() {
|
||||
const glm::vec3 corner(4.56f, 7.89f, -1.35f);
|
||||
const glm::vec3 scale(2.34f, 7.53f, 9.14f);
|
||||
AABox box(corner, scale);
|
||||
|
||||
float delta = 0.00001f;
|
||||
glm::vec3 center = box.calcCenter();
|
||||
QCOMPARE(box.contains(center), true);
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
glm::vec3 halfScale = Vectors::ZERO;
|
||||
halfScale[i] = 0.5f * scale[i];
|
||||
glm::vec3 deltaOffset = Vectors::ZERO;
|
||||
deltaOffset[i] = delta;
|
||||
|
||||
QCOMPARE(box.contains(center + halfScale + deltaOffset), false); // outside +face
|
||||
QCOMPARE(box.contains(center + halfScale - deltaOffset), true); // inside +face
|
||||
QCOMPARE(box.contains(center - halfScale + deltaOffset), true); // inside -face
|
||||
QCOMPARE(box.contains(center - halfScale - deltaOffset), false); // outside -face
|
||||
}
|
||||
}
|
||||
|
||||
void AABoxTests::testTouchesSphere() {
|
||||
glm::vec3 corner(-4.56f, 7.89f, -1.35f);
|
||||
float scale = 1.23f;
|
||||
AABox box(corner, scale);
|
||||
|
||||
float delta = 0.00001f;
|
||||
glm::vec3 cubeCenter = box.calcCenter();
|
||||
float sphereRadius = 0.468f;
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
int j = (i + 1) % 3;
|
||||
int k = (j + 1) % 3;
|
||||
|
||||
{ // faces
|
||||
glm::vec3 scaleOffset = Vectors::ZERO;
|
||||
scaleOffset[i] = 0.5f * scale + sphereRadius;
|
||||
|
||||
glm::vec3 deltaOffset = Vectors::ZERO;
|
||||
deltaOffset[i] = delta;
|
||||
|
||||
// outside +face
|
||||
glm::vec3 sphereCenter = cubeCenter + scaleOffset + deltaOffset;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
|
||||
// inside +face
|
||||
sphereCenter = cubeCenter + scaleOffset - deltaOffset;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
|
||||
// inside -face
|
||||
sphereCenter = cubeCenter - scaleOffset + deltaOffset;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
|
||||
// outside -face
|
||||
sphereCenter = cubeCenter - scaleOffset - deltaOffset;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
}
|
||||
|
||||
{ // edges
|
||||
glm::vec3 edgeOffset = Vectors::ZERO;
|
||||
edgeOffset[i] = 0.5f * scale;
|
||||
edgeOffset[j] = 0.5f * scale;
|
||||
glm::vec3 edgeDirection = glm::normalize(edgeOffset);
|
||||
glm::vec3 sphereCenter;
|
||||
|
||||
// inside ij
|
||||
sphereCenter = cubeCenter + edgeOffset + (sphereRadius - delta) * edgeDirection;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
sphereCenter = cubeCenter - edgeOffset - (sphereRadius - delta) * edgeDirection;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
|
||||
// outside ij
|
||||
sphereCenter = cubeCenter + edgeOffset + (sphereRadius + delta) * edgeDirection;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
sphereCenter = cubeCenter - edgeOffset - (sphereRadius + delta) * edgeDirection;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
|
||||
edgeOffset[j] = 0.0f;
|
||||
edgeOffset[k] = 0.5f * scale;
|
||||
edgeDirection = glm::normalize(edgeOffset);
|
||||
|
||||
// inside ik
|
||||
sphereCenter = cubeCenter + edgeOffset + (sphereRadius - delta) * edgeDirection;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
sphereCenter = cubeCenter - edgeOffset - (sphereRadius - delta) * edgeDirection;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
|
||||
// outside ik
|
||||
sphereCenter = cubeCenter + edgeOffset + (sphereRadius + delta) * edgeDirection;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
sphereCenter = cubeCenter - edgeOffset - (sphereRadius + delta) * edgeDirection;
|
||||
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
28
tests/shared/src/AABoxTests.h
Normal file
28
tests/shared/src/AABoxTests.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// AABoxTests.h
|
||||
// tests/shared/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2016.02.19
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AABoxTests_h
|
||||
#define hifi_AABoxTests_h
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <AABox.h>
|
||||
|
||||
class AABoxTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void testCtorsAndSetters();
|
||||
void testContainsPoint();
|
||||
void testTouchesSphere();
|
||||
};
|
||||
|
||||
#endif // hifi_AABoxTests_h
|
154
tests/shared/src/AACubeTests.cpp
Normal file
154
tests/shared/src/AACubeTests.cpp
Normal file
|
@ -0,0 +1,154 @@
|
|||
//
|
||||
// AACubeTests.cpp
|
||||
// tests/shared/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2016.02.19
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "AACubeTests.h"
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <StreamUtils.h>
|
||||
|
||||
#include <../GLMTestUtils.h>
|
||||
#include <../QTestExtensions.h>
|
||||
|
||||
|
||||
QTEST_MAIN(AACubeTests)
|
||||
|
||||
void AACubeTests::ctorsAndSetters() {
|
||||
const glm::vec3 corner(1.23f, 4.56f, 7.89f);
|
||||
const float scale = 2.34f;
|
||||
|
||||
// test ctor
|
||||
AACube cube(corner, scale);
|
||||
QCOMPARE_WITH_ABS_ERROR(cube.getCorner(), corner, EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(cube.getScale(), scale, EPSILON);
|
||||
|
||||
// test copy ctor
|
||||
AACube copyCube(cube);
|
||||
QCOMPARE_WITH_ABS_ERROR(copyCube.getCorner(), corner, EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(copyCube.getScale(), scale, EPSILON);
|
||||
|
||||
// test setBox()
|
||||
const glm::vec3 newCorner(9.87f, 6.54f, 3.21f);
|
||||
const float newScale = 4.32f;
|
||||
cube.setBox(newCorner, newScale);
|
||||
QCOMPARE_WITH_ABS_ERROR(cube.getCorner(), newCorner, EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(cube.getScale(), newScale, EPSILON);
|
||||
|
||||
// test misc
|
||||
QCOMPARE_WITH_ABS_ERROR(cube.getMinimumPoint(), newCorner, EPSILON);
|
||||
|
||||
glm::vec3 expectedMaxCorner = newCorner + glm::vec3(newScale);
|
||||
QCOMPARE_WITH_ABS_ERROR(cube.getMaximumPoint(), expectedMaxCorner, EPSILON);
|
||||
|
||||
glm::vec3 expectedCenter = newCorner + glm::vec3(0.5f * newScale);
|
||||
QCOMPARE_WITH_ABS_ERROR(cube.calcCenter(), expectedCenter, EPSILON);
|
||||
}
|
||||
|
||||
void AACubeTests::containsPoint() {
|
||||
const glm::vec3 corner(4.56f, 7.89f, -1.35f);
|
||||
const float scale = 1.23f;
|
||||
AACube cube(corner, scale);
|
||||
|
||||
const float delta = scale / 1000.0f;
|
||||
const glm::vec3 center = cube.calcCenter();
|
||||
QCOMPARE(cube.contains(center), true);
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
glm::vec3 scaleOffset = Vectors::ZERO;
|
||||
scaleOffset[i] = 0.5f * scale;
|
||||
|
||||
glm::vec3 deltaOffset = Vectors::ZERO;
|
||||
deltaOffset[i] = delta;
|
||||
|
||||
QCOMPARE(cube.contains(center + scaleOffset + deltaOffset), false); // outside +face
|
||||
QCOMPARE(cube.contains(center + scaleOffset - deltaOffset), true); // inside +face
|
||||
QCOMPARE(cube.contains(center - scaleOffset + deltaOffset), true); // inside -face
|
||||
QCOMPARE(cube.contains(center - scaleOffset - deltaOffset), false); // outside -face
|
||||
}
|
||||
}
|
||||
|
||||
void AACubeTests::touchesSphere() {
|
||||
const glm::vec3 corner(-4.56f, 7.89f, -1.35f);
|
||||
const float scale = 1.23f;
|
||||
AACube cube(corner, scale);
|
||||
|
||||
const float delta = scale / 1000.0f;
|
||||
const glm::vec3 cubeCenter = cube.calcCenter();
|
||||
const float sphereRadius = 0.468f;
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
int j = (i + 1) % 3;
|
||||
int k = (j + 1) % 3;
|
||||
|
||||
{ // faces
|
||||
glm::vec3 scaleOffset = Vectors::ZERO;
|
||||
scaleOffset[i] = 0.5f * scale + sphereRadius;
|
||||
|
||||
glm::vec3 deltaOffset = Vectors::ZERO;
|
||||
deltaOffset[i] = delta;
|
||||
|
||||
// outside +face
|
||||
glm::vec3 sphereCenter = cubeCenter + scaleOffset + deltaOffset;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
|
||||
// inside +face
|
||||
sphereCenter = cubeCenter + scaleOffset - deltaOffset;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
|
||||
// inside -face
|
||||
sphereCenter = cubeCenter - scaleOffset + deltaOffset;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
|
||||
// outside -face
|
||||
sphereCenter = cubeCenter - scaleOffset - deltaOffset;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
}
|
||||
|
||||
{ // edges
|
||||
glm::vec3 edgeOffset = Vectors::ZERO;
|
||||
edgeOffset[i] = 0.5f * scale;
|
||||
edgeOffset[j] = 0.5f * scale;
|
||||
glm::vec3 edgeDirection = glm::normalize(edgeOffset);
|
||||
glm::vec3 sphereCenter;
|
||||
|
||||
// inside ij
|
||||
sphereCenter = cubeCenter + edgeOffset + (sphereRadius - delta) * edgeDirection;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
sphereCenter = cubeCenter - edgeOffset - (sphereRadius - delta) * edgeDirection;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
|
||||
// outside ij
|
||||
sphereCenter = cubeCenter + edgeOffset + (sphereRadius + delta) * edgeDirection;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
sphereCenter = cubeCenter - edgeOffset - (sphereRadius + delta) * edgeDirection;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
|
||||
edgeOffset[j] = 0.0f;
|
||||
edgeOffset[k] = 0.5f * scale;
|
||||
edgeDirection = glm::normalize(edgeOffset);
|
||||
|
||||
// inside ik
|
||||
sphereCenter = cubeCenter + edgeOffset + (sphereRadius - delta) * edgeDirection;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
sphereCenter = cubeCenter - edgeOffset - (sphereRadius - delta) * edgeDirection;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
|
||||
|
||||
// outside ik
|
||||
sphereCenter = cubeCenter + edgeOffset + (sphereRadius + delta) * edgeDirection;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
sphereCenter = cubeCenter - edgeOffset - (sphereRadius + delta) * edgeDirection;
|
||||
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
28
tests/shared/src/AACubeTests.h
Normal file
28
tests/shared/src/AACubeTests.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// AACubeTests.h
|
||||
// tests/shared/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2016.02.19
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AACubeTests_h
|
||||
#define hifi_AACubeTests_h
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <AACube.h>
|
||||
|
||||
class AACubeTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void ctorsAndSetters();
|
||||
void containsPoint();
|
||||
void touchesSphere();
|
||||
};
|
||||
|
||||
#endif // hifi_AACubeTests_h
|
|
@ -1,316 +0,0 @@
|
|||
//
|
||||
// AngularConstraintTests.cpp
|
||||
// tests/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.05.30
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AngularConstraintTests.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <AngularConstraint.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <StreamUtils.h>
|
||||
|
||||
#include "../QTestExtensions.h"
|
||||
|
||||
|
||||
QTEST_MAIN(AngularConstraintTests)
|
||||
|
||||
void AngularConstraintTests::testHingeConstraint() {
|
||||
float minAngle = -PI;
|
||||
float maxAngle = 0.0f;
|
||||
glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
|
||||
glm::vec3 minAngles(0.0f, -PI, 0.0f);
|
||||
glm::vec3 maxAngles(0.0f, 0.0f, 0.0f);
|
||||
|
||||
AngularConstraint* c = AngularConstraint::newAngularConstraint(minAngles, maxAngles);
|
||||
QVERIFY2(c != nullptr, "newAngularConstraint should make a constraint");
|
||||
{ // test in middle of constraint
|
||||
float angle = 0.5f * (minAngle + maxAngle);
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
QVERIFY2(constrained == false, "HingeConstraint should not clamp()");
|
||||
QVERIFY2(rotation == newRotation, "HingeConstraint should not change rotation");
|
||||
}
|
||||
{ // test just inside min edge of constraint
|
||||
float angle = minAngle + 10.0f * EPSILON;
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
QVERIFY2(!constrained, "HingeConstraint should not clamp()");
|
||||
QVERIFY2(newRotation == rotation, "HingeConstraint should not change rotation");
|
||||
}
|
||||
{ // test just inside max edge of constraint
|
||||
float angle = maxAngle - 10.0f * EPSILON;
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
QVERIFY2(!constrained, "HingeConstraint should not clamp()");
|
||||
QVERIFY2(newRotation == rotation, "HingeConstraint should not change rotation");
|
||||
}
|
||||
{ // test just outside min edge of constraint
|
||||
float angle = minAngle - 0.001f;
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis);
|
||||
|
||||
QVERIFY2(constrained, "HingeConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
{ // test just outside max edge of constraint
|
||||
float angle = maxAngle + 0.001f;
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
QVERIFY2(constrained, "HingeConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, rotation, EPSILON);
|
||||
}
|
||||
{ // test far outside min edge of constraint (wraps around to max)
|
||||
float angle = minAngle - 0.75f * (TWO_PI - (maxAngle - minAngle));
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis);
|
||||
QVERIFY2(constrained, "HingeConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
{ // test far outside max edge of constraint (wraps around to min)
|
||||
float angle = maxAngle + 0.75f * (TWO_PI - (maxAngle - minAngle));
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis);
|
||||
|
||||
QVERIFY2(constrained, "HingeConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
|
||||
float ACCEPTABLE_ERROR = 1.0e-4f;
|
||||
{ // test nearby but off-axis rotation
|
||||
float offAngle = 0.1f;
|
||||
glm::quat offRotation(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
float angle = 0.5f * (maxAngle + minAngle);
|
||||
glm::quat rotation = offRotation * glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
glm::quat expectedRotation = glm::angleAxis(angle, yAxis);
|
||||
|
||||
QVERIFY2(constrained, "HingeConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, ACCEPTABLE_ERROR);
|
||||
}
|
||||
{ // test way off rotation > maxAngle
|
||||
float offAngle = 0.5f;
|
||||
glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
float angle = maxAngle + 0.2f * (TWO_PI - (maxAngle - minAngle));
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
rotation = offRotation * glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis);
|
||||
|
||||
QVERIFY2(constrained, "HingeConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
{ // test way off rotation < minAngle
|
||||
float offAngle = 0.5f;
|
||||
glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
float angle = minAngle - 0.2f * (TWO_PI - (maxAngle - minAngle));
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
rotation = offRotation * glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis);
|
||||
|
||||
QVERIFY2(constrained, "HingeConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
{ // test way off rotation > maxAngle with wrap over to minAngle
|
||||
float offAngle = -0.5f;
|
||||
glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
float angle = maxAngle + 0.6f * (TWO_PI - (maxAngle - minAngle));
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
rotation = offRotation * glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis);
|
||||
|
||||
QVERIFY2(constrained, "HingeConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
{ // test way off rotation < minAngle with wrap over to maxAngle
|
||||
float offAngle = -0.6f;
|
||||
glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
float angle = minAngle - 0.7f * (TWO_PI - (maxAngle - minAngle));
|
||||
glm::quat rotation = glm::angleAxis(angle, yAxis);
|
||||
rotation = offRotation * glm::angleAxis(angle, yAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis);
|
||||
|
||||
QVERIFY2(constrained, "HingeConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
delete c;
|
||||
}
|
||||
|
||||
void AngularConstraintTests::testConeRollerConstraint() {
|
||||
float minAngleX = -PI / 5.0f;
|
||||
float minAngleY = -PI / 5.0f;
|
||||
float minAngleZ = -PI / 8.0f;
|
||||
|
||||
float maxAngleX = PI / 4.0f;
|
||||
float maxAngleY = PI / 3.0f;
|
||||
float maxAngleZ = PI / 4.0f;
|
||||
|
||||
glm::vec3 minAngles(minAngleX, minAngleY, minAngleZ);
|
||||
glm::vec3 maxAngles(maxAngleX, maxAngleY, maxAngleZ);
|
||||
AngularConstraint* c = AngularConstraint::newAngularConstraint(minAngles, maxAngles);
|
||||
|
||||
float expectedConeAngle = 0.25f * (maxAngleX - minAngleX + maxAngleY - minAngleY);
|
||||
glm::vec3 middleAngles = 0.5f * (maxAngles + minAngles);
|
||||
glm::quat yaw = glm::angleAxis(middleAngles[1], glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
glm::quat pitch = glm::angleAxis(middleAngles[0], glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
glm::vec3 expectedConeAxis = pitch * yaw * glm::vec3(0.0f, 0.0f, 1.0f);
|
||||
|
||||
glm::vec3 xAxis(1.0f, 0.0f, 0.0f);
|
||||
glm::vec3 perpAxis = glm::normalize(xAxis - glm::dot(xAxis, expectedConeAxis) * expectedConeAxis);
|
||||
|
||||
QVERIFY2(c != nullptr, "newAngularConstraint() should make a constraint");
|
||||
{ // test in middle of constraint
|
||||
glm::vec3 angles(PI/20.0f, 0.0f, PI/10.0f);
|
||||
glm::quat rotation(angles);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
QVERIFY2(!constrained, "ConeRollerConstraint should not clamp()");
|
||||
QVERIFY2(newRotation == rotation, "ConeRollerConstraint should not change rotation");
|
||||
}
|
||||
float deltaAngle = 0.001f;
|
||||
{ // test just inside edge of cone
|
||||
glm::quat rotation = glm::angleAxis(expectedConeAngle - deltaAngle, perpAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
QVERIFY2(!constrained, "ConeRollerConstraint should not clamp()");
|
||||
QVERIFY2(newRotation == rotation, "ConeRollerConstraint should not change rotation");
|
||||
}
|
||||
{ // test just outside edge of cone
|
||||
glm::quat rotation = glm::angleAxis(expectedConeAngle + deltaAngle, perpAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
QVERIFY2(constrained, "ConeRollerConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation");
|
||||
}
|
||||
{ // test just inside min edge of roll
|
||||
glm::quat rotation = glm::angleAxis(minAngleZ + deltaAngle, expectedConeAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
QVERIFY2(!constrained, "ConeRollerConstraint should not clamp()");
|
||||
QVERIFY2(newRotation == rotation, "ConeRollerConstraint should not change rotation");
|
||||
}
|
||||
{ // test just inside max edge of roll
|
||||
glm::quat rotation = glm::angleAxis(maxAngleZ - deltaAngle, expectedConeAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
QVERIFY2(!constrained, "ConeRollerConstraint should not clamp()");
|
||||
QVERIFY2(newRotation == rotation, "ConeRollerConstraint should not change rotation");
|
||||
}
|
||||
{ // test just outside min edge of roll
|
||||
glm::quat rotation = glm::angleAxis(minAngleZ - deltaAngle, expectedConeAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
glm::quat expectedRotation = glm::angleAxis(minAngleZ, expectedConeAxis);
|
||||
|
||||
QVERIFY2(constrained, "ConeRollerConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
{ // test just outside max edge of roll
|
||||
glm::quat rotation = glm::angleAxis(maxAngleZ + deltaAngle, expectedConeAxis);
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
glm::quat expectedRotation = glm::angleAxis(maxAngleZ, expectedConeAxis);
|
||||
|
||||
QVERIFY2(constrained, "ConeRollerConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
deltaAngle = 0.25f * expectedConeAngle;
|
||||
{ // test far outside cone and min roll
|
||||
glm::quat roll = glm::angleAxis(minAngleZ - deltaAngle, expectedConeAxis);
|
||||
glm::quat pitchYaw = glm::angleAxis(expectedConeAngle + deltaAngle, perpAxis);
|
||||
glm::quat rotation = pitchYaw * roll;
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
glm::quat expectedRoll = glm::angleAxis(minAngleZ, expectedConeAxis);
|
||||
glm::quat expectedPitchYaw = glm::angleAxis(expectedConeAngle, perpAxis);
|
||||
glm::quat expectedRotation = expectedPitchYaw * expectedRoll;
|
||||
|
||||
QVERIFY2(constrained, "ConeRollerConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
{ // test far outside cone and max roll
|
||||
glm::quat roll = glm::angleAxis(maxAngleZ + deltaAngle, expectedConeAxis);
|
||||
glm::quat pitchYaw = glm::angleAxis(- expectedConeAngle - deltaAngle, perpAxis);
|
||||
glm::quat rotation = pitchYaw * roll;
|
||||
|
||||
glm::quat newRotation = rotation;
|
||||
bool constrained = c->clamp(newRotation);
|
||||
|
||||
glm::quat expectedRoll = glm::angleAxis(maxAngleZ, expectedConeAxis);
|
||||
glm::quat expectedPitchYaw = glm::angleAxis(- expectedConeAngle, perpAxis);
|
||||
glm::quat expectedRotation = expectedPitchYaw * expectedRoll;
|
||||
|
||||
QVERIFY2(constrained, "ConeRollerConstraint should clamp()");
|
||||
QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation");
|
||||
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
|
||||
}
|
||||
delete c;
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
//
|
||||
// AngularConstraintTests.h
|
||||
// tests/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows on 2014.05.30
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AngularConstraintTests_h
|
||||
#define hifi_AngularConstraintTests_h
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <QtTest/QtTest>
|
||||
|
||||
class AngularConstraintTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void testHingeConstraint();
|
||||
void testConeRollerConstraint();
|
||||
};
|
||||
|
||||
float getErrorDifference(const glm::quat& a, const glm::quat& b);
|
||||
|
||||
#endif // hifi_AngularConstraintTests_h
|
|
@ -1,182 +0,0 @@
|
|||
// 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
|
||||
//
|
||||
(function() {
|
||||
|
||||
var self = this;
|
||||
|
||||
this.preload = function(entityId) {
|
||||
//print('preload move randomly')
|
||||
this.isConnected = false;
|
||||
this.entityId = entityId;
|
||||
this.updateInterval = 100;
|
||||
this.posFrame = 0;
|
||||
this.rotFrame = 0;
|
||||
this.posInterval = 100;
|
||||
this.rotInterval = 100;
|
||||
this.minVelocity = 1;
|
||||
this.maxVelocity = 5;
|
||||
this.minAngularVelocity = 0.01;
|
||||
this.maxAngularVelocity = 0.03;
|
||||
|
||||
this.initialize(entityId);
|
||||
this.initTimeout = null;
|
||||
|
||||
|
||||
var userData = {
|
||||
ownershipKey: {
|
||||
owner: MyAvatar.sessionUUID
|
||||
},
|
||||
grabbableKey: {
|
||||
grabbable: false
|
||||
}
|
||||
};
|
||||
|
||||
Entities.editEntity(entityId, {
|
||||
userData: JSON.stringify(userData)
|
||||
})
|
||||
}
|
||||
|
||||
this.initialize = function(entityId) {
|
||||
//print('move randomly should initialize' + entityId)
|
||||
var properties = Entities.getEntityProperties(entityId);
|
||||
if (properties.userData.length === 0 || properties.hasOwnProperty('userData') === false) {
|
||||
self.initTimeout = Script.setTimeout(function() {
|
||||
//print('no user data yet, try again in one second')
|
||||
self.initialize(entityId);
|
||||
}, 1000)
|
||||
|
||||
} else {
|
||||
//print('userdata before parse attempt' + properties.userData)
|
||||
self.userData = null;
|
||||
try {
|
||||
self.userData = JSON.parse(properties.userData);
|
||||
} catch (err) {
|
||||
//print('error parsing json');
|
||||
//print('properties are:' + properties.userData);
|
||||
return;
|
||||
}
|
||||
Script.update.connect(self.update);
|
||||
this.isConnected = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.update = function(deltaTime) {
|
||||
// print('jbp in update')
|
||||
var data = Entities.getEntityProperties(self.entityId, 'userData').userData;
|
||||
var userData;
|
||||
try {
|
||||
userData = JSON.parse(data)
|
||||
} catch (e) {
|
||||
//print('error parsing json' + data)
|
||||
return;
|
||||
};
|
||||
|
||||
// print('userdata is' + data)
|
||||
//if the entity doesnt have an owner set yet
|
||||
if (userData.hasOwnProperty('ownershipKey') !== true) {
|
||||
//print('no movement owner yet')
|
||||
return;
|
||||
}
|
||||
|
||||
//print('owner is:::' + userData.ownershipKey.owner)
|
||||
//get all the avatars to see if the owner is around
|
||||
var avatars = AvatarList.getAvatarIdentifiers();
|
||||
var ownerIsAround = false;
|
||||
|
||||
//if the current owner is not me...
|
||||
if (userData.ownershipKey.owner !== MyAvatar.sessionUUID) {
|
||||
|
||||
//look to see if the current owner is around anymore
|
||||
for (var i = 0; i < avatars.length; i++) {
|
||||
if (avatars[i] === userData.ownershipKey.owner) {
|
||||
ownerIsAround = true
|
||||
//the owner is around
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
//if the owner is not around, then take ownership
|
||||
if (ownerIsAround === false) {
|
||||
//print('taking ownership')
|
||||
|
||||
var userData = {
|
||||
ownershipKey: {
|
||||
owner: MyAvatar.sessionUUID
|
||||
},
|
||||
grabbableKey: {
|
||||
grabbable: false
|
||||
}
|
||||
};
|
||||
Entities.editEntity(self.entityId, {
|
||||
userData: JSON.stringify(data)
|
||||
})
|
||||
}
|
||||
}
|
||||
//but if the current owner IS me, then move it
|
||||
else {
|
||||
//print('jbp im the owner so move it')
|
||||
self.posFrame++;
|
||||
self.rotFrame++;
|
||||
|
||||
if (self.posFrame > self.posInterval) {
|
||||
|
||||
self.posInterval = 100 * Math.random() + 300;
|
||||
self.posFrame = 0;
|
||||
|
||||
var magnitudeV = self.maxVelocity;
|
||||
var directionV = {
|
||||
x: Math.random() - 0.5,
|
||||
y: Math.random() - 0.5,
|
||||
z: Math.random() - 0.5
|
||||
};
|
||||
|
||||
//print("POS magnitude is " + magnitudeV + " and direction is " + directionV.x);
|
||||
Entities.editEntity(self.entityId, {
|
||||
velocity: Vec3.multiply(magnitudeV, Vec3.normalize(directionV))
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
if (self.rotFrame > self.rotInterval) {
|
||||
|
||||
self.rotInterval = 100 * Math.random() + 250;
|
||||
self.rotFrame = 0;
|
||||
|
||||
var magnitudeAV = self.maxAngularVelocity;
|
||||
|
||||
var directionAV = {
|
||||
x: Math.random() - 0.5,
|
||||
y: Math.random() - 0.5,
|
||||
z: Math.random() - 0.5
|
||||
};
|
||||
//print("ROT magnitude is " + magnitudeAV + " and direction is " + directionAV.x);
|
||||
Entities.editEntity(self.entityId, {
|
||||
angularVelocity: Vec3.multiply(magnitudeAV, Vec3.normalize(directionAV))
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.unload = function() {
|
||||
if (this.initTimeout !== null) {
|
||||
Script.clearTimeout(this.initTimeout);
|
||||
}
|
||||
|
||||
if (this.isConnected === true) {
|
||||
Script.update.disconnect(this.update);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
})
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
(function() {
|
||||
|
||||
var version = 11;
|
||||
var version = 12;
|
||||
var added = false;
|
||||
this.frame = 0;
|
||||
var utilsScript = Script.resolvePath('utils.js');
|
||||
|
@ -23,22 +23,21 @@
|
|||
}
|
||||
|
||||
this.initialize = function(entityId) {
|
||||
print('JBP nav button should initialize' + entityId)
|
||||
var properties = Entities.getEntityProperties(entityId);
|
||||
if (properties.userData.length === 0 || properties.hasOwnProperty('userData') === false) {
|
||||
self.initTimeout = Script.setTimeout(function() {
|
||||
print('JBP no user data yet, try again in one second')
|
||||
// print(' no user data yet, try again in one second')
|
||||
self.initialize(entityId);
|
||||
}, 1000)
|
||||
|
||||
} else {
|
||||
print('JBP userdata before parse attempt' + properties.userData)
|
||||
// print('userdata before parse attempt' + properties.userData)
|
||||
self.userData = null;
|
||||
try {
|
||||
self.userData = JSON.parse(properties.userData);
|
||||
} catch (err) {
|
||||
print('JBP error parsing json');
|
||||
print('JBP properties are:' + properties.userData);
|
||||
// print(' error parsing json');
|
||||
// print(' properties are:' + properties.userData);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -46,9 +45,9 @@
|
|||
var mySavedSettings = Settings.getValue(entityId);
|
||||
|
||||
if (mySavedSettings.buttons !== undefined) {
|
||||
print('JBP preload buttons' + mySavedSettings.buttons)
|
||||
// print(' preload buttons' + mySavedSettings.buttons)
|
||||
mySavedSettings.buttons.forEach(function(b) {
|
||||
print('JBP deleting button' + b)
|
||||
// print(' deleting button' + b)
|
||||
Overlays.deleteOverlay(b);
|
||||
})
|
||||
Settings.setValue(entityId, '')
|
||||
|
@ -56,16 +55,15 @@
|
|||
|
||||
|
||||
self.buttonImageURL = baseURL + "GUI/GUI_" + self.userData.name + ".png?" + version;
|
||||
print('JBP BUTTON IMAGE URL:' + self.buttonImageURL)
|
||||
// print(' BUTTON IMAGE URL:' + self.buttonImageURL)
|
||||
if (self.button === undefined) {
|
||||
// print('NAV NO BUTTON ADDING ONE!!')
|
||||
// print(' NO BUTTON ADDING ONE!!')
|
||||
self.button = true;
|
||||
self.addButton();
|
||||
|
||||
} else {
|
||||
// print('NAV SELF ALREADY HAS A BUTTON!!')
|
||||
//print(' SELF ALREADY HAS A BUTTON!!')
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue