diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index b6223497e6..b177d2a9a0 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -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() { diff --git a/assignment-client/src/entities/EntityServer.h b/assignment-client/src/entities/EntityServer.h index cd603f44d8..74057bfa5d 100644 --- a/assignment-client/src/entities/EntityServer.h +++ b/assignment-client/src/entities/EntityServer.h @@ -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 message, SharedNodePointer senderNode); private: - EntitySimulation* _entitySimulation; + SimpleEntitySimulation* _entitySimulation; QTimer* _pruneDeletedEntitiesTimer = nullptr; QReadWriteLock _viewerSendingStatsLock; diff --git a/assignment-client/src/octree/OctreeQueryNode.cpp b/assignment-client/src/octree/OctreeQueryNode.cpp index 78a049edd6..55eddf9e13 100644 --- a/assignment-client/src/octree/OctreeQueryNode.cpp +++ b/assignment-client/src/octree/OctreeQueryNode.cpp @@ -153,7 +153,7 @@ bool OctreeQueryNode::updateCurrentViewFrustum() { newestViewFrustum.setPosition(getCameraPosition()); newestViewFrustum.setOrientation(getCameraOrientation()); - newestViewFrustum.setKeyholeRadius(getKeyholeRadius()); + newestViewFrustum.setCenterRadius(getCameraCenterRadius()); // Also make sure it's got the correct lens details from the camera float originalFOV = getCameraFov(); diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index aedf451924..31cab68cdf 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -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() { diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index b5699cb3b3..ee59f4a3ac 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -271,8 +271,9 @@ FunctionEnd @CPACK_NSIS_PAGE_COMPONENTS@ + Page custom PostInstallOptionsPage ReadPostInstallOptions + !insertmacro MUI_PAGE_INSTFILES - Page custom PostInstallOptionsPage HandlePostInstallOptions !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES @@ -341,6 +342,227 @@ FunctionEnd ;Only for solid compression (by default, solid compression is enabled for BZIP2 and LZMA) ReserveFile "@POST_INSTALL_OPTIONS_PATH@" + ; Make sure nsDialogs is included before we use it + !include "nsdialogs.nsh" + +;-------------------------------- +; Post Install Options + +Var PostInstallDialog +Var DesktopClientCheckbox +Var DesktopServerCheckbox +Var ServerStartupCheckbox +Var LaunchNowCheckbox +Var CurrentOffset +Var OffsetUnits +Var CopyFromProductionCheckbox + +!macro SetPostInstallOption Checkbox OptionName Default + ; reads the value for the given post install option to the registry + ReadRegStr $0 HKLM "@REGISTRY_HKLM_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\@POST_INSTALL_OPTIONS_REG_GROUP@" "${OptionName}" + + ${If} $0 == "NO" + ; the value in the registry says it should not be checked + ${NSD_SetState} ${Checkbox} ${BST_UNCHECKED} + ${ElseIf} $0 == "YES" + ; the value in the registry says it should be checked + ${NSD_SetState} ${Checkbox} ${BST_CHECKED} + ${Else} + ; the value in the registry was not in the expected format, use default + ${NSD_SetState} ${Checkbox} ${Default} + ${EndIf} +!macroend + +Function PostInstallOptionsPage + !insertmacro MUI_HEADER_TEXT "Setup Options" "" + + nsDialogs::Create 1018 + Pop $PostInstallDialog + + ${If} $PostInstallDialog == error + Abort + ${EndIf} + + StrCpy $CurrentOffset 0 + StrCpy $OffsetUnits u + + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @INTERFACE_SHORTCUT_NAME@" + Pop $DesktopClientCheckbox + IntOp $CurrentOffset $CurrentOffset + 15 + + ; set the checkbox state depending on what is present in the registry + !insertmacro SetPostInstallOption $DesktopClientCheckbox @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ ${BST_CHECKED} + ${EndIf} + + ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for High Fidelity @CONSOLE_SHORTCUT_NAME@" + Pop $DesktopServerCheckbox + + ; set the checkbox state depending on what is present in the registry + !insertmacro SetPostInstallOption $DesktopServerCheckbox @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ ${BST_UNCHECKED} + + IntOp $CurrentOffset $CurrentOffset + 15 + + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity @CONSOLE_SHORTCUT_NAME@ on startup" + Pop $ServerStartupCheckbox + + ; set the checkbox state depending on what is present in the registry + !insertmacro SetPostInstallOption $ServerStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_CHECKED} + + IntOp $CurrentOffset $CurrentOffset + 15 + ${EndIf} + + ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity Server Console after install" + ${Else} + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity after install" + ${EndIf} + + Pop $LaunchNowCheckbox + + ; set the checkbox state depending on what is present in the registry + !insertmacro SetPostInstallOption $LaunchNowCheckbox @LAUNCH_NOW_REG_KEY@ ${BST_CHECKED} + + ${If} @PR_BUILD@ == 1 + ; a PR build defaults all install options expect LaunchNowCheckbox and the settings copy to unchecked + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ${NSD_SetState} $DesktopClientCheckbox ${BST_UNCHECKED} + ${EndIf} + + ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + ${NSD_SetState} $DesktopServerCheckbox ${BST_UNCHECKED} + ${NSD_SetState} $ServerStartupCheckbox ${BST_UNCHECKED} + ${EndIf} + + ; push the offset + IntOp $CurrentOffset $CurrentOffset + 15 + + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Copy settings and content from production install" + Pop $CopyFromProductionCheckbox + + ${NSD_SetState} $CopyFromProductionCheckbox ${BST_CHECKED} + ${EndIf} + + nsDialogs::Show +FunctionEnd + +!macro WritePostInstallOption OptionName Option + ; writes the value for the given post install option to the registry + WriteRegStr HKLM "@REGISTRY_HKLM_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\@POST_INSTALL_OPTIONS_REG_GROUP@" "${OptionName}" ${Option} +!macroend + +Var DesktopClientState +Var DesktopServerState +Var ServerStartupState +Var LaunchNowState +Var CopyFromProductionState + +Function ReadPostInstallOptions + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ; check if the user asked for a desktop shortcut to High Fidelity + ${NSD_GetState} $DesktopClientCheckbox $DesktopClientState + ${EndIf} + + ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + ; check if the user asked for a desktop shortcut to Server Console + ${NSD_GetState} $DesktopServerCheckbox $DesktopServerState + + ; check if the user asked to have Server Console launched every startup + ${NSD_GetState} $ServerStartupCheckbox $ServerStartupState + ${EndIf} + + ${If} @PR_BUILD@ == 1 + ; check if we need to copy settings/content from production for this PR build + ${NSD_GetState} $CopyFromProductionCheckbox $CopyFromProductionState + ${EndIf} + + ; check if we need to launch an application post-install + ${NSD_GetState} $LaunchNowCheckbox $LaunchNowState +FunctionEnd + +Function HandlePostInstallOptions + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} + ; check if the user asked for a desktop shortcut to High Fidelity + ${If} $DesktopClientState == ${BST_CHECKED} + CreateShortCut "$DESKTOP\@INTERFACE_SHORTCUT_NAME@.lnk" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@" + !insertmacro WritePostInstallOption "@CLIENT_DESKTOP_SHORTCUT_REG_KEY@" YES + ${Else} + !insertmacro WritePostInstallOption @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ NO + ${EndIf} + + ${EndIf} + + ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + ; check if the user asked for a desktop shortcut to Server Console + ${If} $DesktopServerState == ${BST_CHECKED} + CreateShortCut "$DESKTOP\@CONSOLE_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" + !insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ YES + ${Else} + !insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ NO + ${EndIf} + + ; check if the user asked to have Server Console launched every startup + ${If} $ServerStartupState == ${BST_CHECKED} + ; in case we added a shortcut in the global context, pull that now + SetShellVarContext all + Delete "$SMSTARTUP\@CONSOLE_SHORTCUT_NAME@.lnk" + + ; make a startup shortcut in this user's current context + SetShellVarContext current + CreateShortCut "$SMSTARTUP\@CONSOLE_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" + + ; reset the shell var context back + SetShellVarContext all + + !insertmacro WritePostInstallOption @CONSOLE_STARTUP_REG_KEY@ YES + ${Else} + !insertmacro WritePostInstallOption @CONSOLE_STARTUP_REG_KEY@ NO + ${EndIf} + ${EndIf} + + ${If} @PR_BUILD@ == 1 + + ; check if we need to copy settings/content from production for this PR build + ${If} $CopyFromProductionState == ${BST_CHECKED} + SetShellVarContext current + + StrCpy $0 "$APPDATA\@BUILD_ORGANIZATION@" + + ; we need to copy whatever is in the data folder for production build to the data folder for this build + CreateDirectory $0 + + ClearErrors + + ; copy the data from production build to this PR build + CopyFiles "$APPDATA\High Fidelity\*" $0 + + ; handle an error in copying files + IfErrors 0 NoError + + MessageBox mb_IconStop|mb_TopMost|mb_SetForeground \ + "There was a problem copying your production content and settings to $0 for this PR build.$\r$\n$\r$\nPlease copy them manually." + + NoError: + + SetShellVarContext all + ${EndIf} + ${EndIf} + + ${If} $LaunchNowState == ${BST_CHECKED} + !insertmacro WritePostInstallOption @LAUNCH_NOW_REG_KEY@ YES + + ; both launches use the explorer trick in case the user has elevated permissions for the installer + ; it won't be possible to use this approach if either application should be launched with a command line param + ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} + Exec '"$WINDIR\explorer.exe" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"' + ${Else} + Exec '"$WINDIR\explorer.exe" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@"' + ${EndIf} + ${Else} + !insertmacro WritePostInstallOption @LAUNCH_NOW_REG_KEY@ NO + ${EndIf} +FunctionEnd ;-------------------------------- ;Installer Sections @@ -469,233 +691,11 @@ Section "-Core installation" @CPACK_NSIS_EXTRA_INSTALL_COMMANDS@ + ; Handle whichever post install options were set + Call HandlePostInstallOptions + SectionEnd -; Make sure nsDialogs is included before we use it -!include "nsdialogs.nsh" - -Var PostInstallDialog -Var OptionsLabel -Var DesktopClientCheckbox -Var DesktopServerCheckbox -Var ServerStartupCheckbox -Var LaunchNowCheckbox -Var CurrentOffset -Var OffsetUnits -Var CopyFromProductionCheckbox - -!macro SetPostInstallOption Checkbox OptionName Default - ; reads the value for the given post install option to the registry - ReadRegStr $0 HKLM "@REGISTRY_HKLM_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\@POST_INSTALL_OPTIONS_REG_GROUP@" "${OptionName}" - - ${If} $0 == "NO" - ; the value in the registry says it should not be checked - ${NSD_SetState} ${Checkbox} ${BST_UNCHECKED} - ${ElseIf} $0 == "YES" - ; the value in the registry says it should be checked - ${NSD_SetState} ${Checkbox} ${BST_CHECKED} - ${Else} - ; the value in the registry was not in the expected format, use default - ${NSD_SetState} ${Checkbox} ${Default} - ${EndIf} -!macroend - -Function PostInstallOptionsPage - ; Set the text on the dialog button to match finish, hide the back and cancel buttons - GetDlgItem $R1 $hwndparent 1 - SendMessage $R1 ${WM_SETTEXT} 0 "STR:&Finish" - - GetDlgItem $R3 $hwndparent 3 - ShowWindow $R3 0 - - nsDialogs::Create 1018 - Pop $PostInstallDialog - - ${If} $PostInstallDialog == error - Abort - ${EndIf} - - ${NSD_CreateLabel} 0 0 100% 12u "Setup Options" - Pop $OptionsLabel - - ; Set label to bold - CreateFont $R2 "Arial" 10 700 - SendMessage $OptionsLabel ${WM_SETFONT} $R2 0 - - ; Force label redraw - ShowWindow $OptionsLabel ${SW_HIDE} - ShowWindow $OptionsLabel ${SW_SHOW} - - StrCpy $CurrentOffset 15 - StrCpy $OffsetUnits u - - ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @INTERFACE_SHORTCUT_NAME@" - Pop $DesktopClientCheckbox - IntOp $CurrentOffset $CurrentOffset + 15 - - ; set the checkbox state depending on what is present in the registry - !insertmacro SetPostInstallOption $DesktopClientCheckbox @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ ${BST_CHECKED} - ${EndIf} - - ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for High Fidelity @CONSOLE_SHORTCUT_NAME@" - Pop $DesktopServerCheckbox - - ; set the checkbox state depending on what is present in the registry - !insertmacro SetPostInstallOption $DesktopServerCheckbox @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ ${BST_UNCHECKED} - - IntOp $CurrentOffset $CurrentOffset + 15 - - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity @CONSOLE_SHORTCUT_NAME@ on startup" - Pop $ServerStartupCheckbox - - ; set the checkbox state depending on what is present in the registry - !insertmacro SetPostInstallOption $ServerStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_CHECKED} - - IntOp $CurrentOffset $CurrentOffset + 15 - ${EndIf} - - ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity Server Console Now" - ${Else} - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch High Fidelity Now" - ${EndIf} - - Pop $LaunchNowCheckbox - - ; set the checkbox state depending on what is present in the registry - !insertmacro SetPostInstallOption $LaunchNowCheckbox @LAUNCH_NOW_REG_KEY@ ${BST_CHECKED} - - ${If} @PR_BUILD@ == 1 - ; a PR build defaults all install options expect LaunchNowCheckbox and the settings copy to unchecked - ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} - ${NSD_SetState} $DesktopClientCheckbox ${BST_UNCHECKED} - ${EndIf} - - ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} - ${NSD_SetState} $DesktopServerCheckbox ${BST_UNCHECKED} - ${NSD_SetState} $ServerStartupCheckbox ${BST_UNCHECKED} - ${EndIf} - - ; push the offset - IntOp $CurrentOffset $CurrentOffset + 15 - - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Copy settings and content from production install" - Pop $CopyFromProductionCheckbox - - ${NSD_SetState} $CopyFromProductionCheckbox ${BST_CHECKED} - ${EndIf} - - nsDialogs::Show -FunctionEnd - -!macro WritePostInstallOption OptionName Option - ; writes the value for the given post install option to the registry - WriteRegStr HKLM "@REGISTRY_HKLM_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\@POST_INSTALL_OPTIONS_REG_GROUP@" "${OptionName}" ${Option} -!macroend - -Var DesktopClientState -Var DesktopServerState -Var ServerStartupState -Var LaunchNowState -Var CopyFromProductionState - -Function HandlePostInstallOptions - ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} - ; check if the user asked for a desktop shortcut to High Fidelity - ${NSD_GetState} $DesktopClientCheckbox $DesktopClientState - - ${If} $DesktopClientState == ${BST_CHECKED} - CreateShortCut "$DESKTOP\@INTERFACE_SHORTCUT_NAME@.lnk" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@" - !insertmacro WritePostInstallOption "@CLIENT_DESKTOP_SHORTCUT_REG_KEY@" YES - ${Else} - !insertmacro WritePostInstallOption @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ NO - ${EndIf} - - ${EndIf} - - ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} - ; check if the user asked for a desktop shortcut to Server Console - ${NSD_GetState} $DesktopServerCheckbox $DesktopServerState - - ${If} $DesktopServerState == ${BST_CHECKED} - CreateShortCut "$DESKTOP\@CONSOLE_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" - !insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ YES - ${Else} - !insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ NO - ${EndIf} - - ; check if the user asked to have Server Console launched every startup - ${NSD_GetState} $ServerStartupCheckbox $ServerStartupState - - ${If} $ServerStartupState == ${BST_CHECKED} - ; in case we added a shortcut in the global context, pull that now - SetShellVarContext all - Delete "$SMSTARTUP\@CONSOLE_SHORTCUT_NAME@.lnk" - - ; make a startup shortcut in this user's current context - SetShellVarContext current - CreateShortCut "$SMSTARTUP\@CONSOLE_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" - - ; reset the shell var context back - SetShellVarContext all - - !insertmacro WritePostInstallOption @CONSOLE_STARTUP_REG_KEY@ YES - ${Else} - !insertmacro WritePostInstallOption @CONSOLE_STARTUP_REG_KEY@ NO - ${EndIf} - ${EndIf} - - ${If} @PR_BUILD@ == 1 - - ; check if we need to copy settings/content from production for this PR build - ${NSD_GetState} $CopyFromProductionCheckbox $CopyFromProductionState - - ${If} $CopyFromProductionState == ${BST_CHECKED} - SetShellVarContext current - - StrCpy $0 "$APPDATA\@BUILD_ORGANIZATION@" - - ; we need to copy whatever is in the data folder for production build to the data folder for this build - CreateDirectory $0 - - ClearErrors - - ; copy the data from production build to this PR build - CopyFiles "$APPDATA\High Fidelity\*" $0 - - ; handle an error in copying files - IfErrors 0 NoError - - MessageBox mb_IconStop|mb_TopMost|mb_SetForeground \ - "There was a problem copying your production content and settings to $0 for this PR build.$\r$\n$\r$\nPlease copy them manually." - - NoError: - - SetShellVarContext all - ${EndIf} - ${EndIf} - - ; check if we need to launch an application post-install - ${NSD_GetState} $LaunchNowCheckbox $LaunchNowState - - ${If} $LaunchNowState == ${BST_CHECKED} - !insertmacro WritePostInstallOption @LAUNCH_NOW_REG_KEY@ YES - - ; both launches use the explorer trick in case the user has elevated permissions for the installer - ; it won't be possible to use this approach if either application should be launched with a command line param - ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} - Exec '"$WINDIR\explorer.exe" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"' - ${Else} - Exec '"$WINDIR\explorer.exe" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@"' - ${EndIf} - ${Else} - !insertmacro WritePostInstallOption @LAUNCH_NOW_REG_KEY@ NO - ${EndIf} - -FunctionEnd - !include nsProcess.nsh !macro PromptForRunningApplication applicationName displayName action prompter diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index fae07ace45..7dc94421be 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -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.

" + "In order to make your domain reachable, this will also enable full automatic networking.", showCancelButton: true, diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index f385f5c489..3e4ee7b758 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -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(usernameWithToken.constData()), usernameWithToken.size(), diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index f0df67a6f6..9e13c8e6fa 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -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(); @@ -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(); - - 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()->sendHeartbeatToIceServer(_iceServerSocket); } } @@ -2015,31 +1970,3 @@ void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer 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(); - 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(); - } -} diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 3a83e8696b..326ca3e1a8 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -61,7 +61,6 @@ public slots: void processNodeJSONStatsPacket(QSharedPointer packetList, SharedNodePointer sendingNode); void processPathQueryPacket(QSharedPointer packet); void processNodeDisconnectRequestPacket(QSharedPointer message); - void processICEServerHeartbeatDenialPacket(QSharedPointer 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 _iceServerHeartbeatPacket; QTimer* _iceHeartbeatTimer { nullptr }; // this looks like it dangles when created but it's parented to the DomainServer diff --git a/examples/acScripts/entitySpawnerAC.js b/examples/acScripts/entitySpawnerAC.js index 8fb2e259bb..2baf732258 100644 --- a/examples/acScripts/entitySpawnerAC.js +++ b/examples/acScripts/entitySpawnerAC.js @@ -95,9 +95,9 @@ EntityViewer.setPosition({ y: 0, z: 0 }); -EntityViewer.setKeyholeRadius(60000); +EntityViewer.setCenterRadius(60000); var octreeQueryInterval = Script.setInterval(function() { EntityViewer.queryOctree(); }, 1000); -Script.update.connect(update); \ No newline at end of file +Script.update.connect(update); diff --git a/examples/audioExamples/acAudioSearching/ACAudioSearchAndInject.js b/examples/audioExamples/acAudioSearching/ACAudioSearchAndInject.js index 2be59365b8..de7baba267 100644 --- a/examples/audioExamples/acAudioSearching/ACAudioSearchAndInject.js +++ b/examples/audioExamples/acAudioSearching/ACAudioSearchAndInject.js @@ -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 // diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index f2acfb9b47..04e93334cb 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -171,9 +171,8 @@ var STATE_WAITING_FOR_BUMPER_RELEASE = 15; var COLLIDES_WITH_WHILE_GRABBED = "dynamic,otherAvatar"; var COLLIDES_WITH_WHILE_MULTI_GRABBED = "dynamic"; -var HEART_BEAT_INTERVAL = 5; // seconds -var HEART_BEAT_TIMEOUT = 15; - +var HEART_BEAT_INTERVAL = 5 * MSECS_PER_SEC; +var HEART_BEAT_TIMEOUT = 15 * MSECS_PER_SEC; function stateToName(state) { switch (state) { diff --git a/examples/controllers/leap/leapHands.js b/examples/controllers/leap/leapHands.js index a77fa44e1c..1be0b1e5f6 100644 --- a/examples/controllers/leap/leapHands.js +++ b/examples/controllers/leap/leapHands.js @@ -11,6 +11,21 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +var leftTriggerValue = 0; +var rightTriggerValue = 0; + +var LEAP_TRIGGER_START_ANGLE = 15.0; +var LEAP_TRIGGER_END_ANGLE = 40.0; + +function getLeapMotionLeftTrigger() { + //print("left trigger = " + leftTriggerValue); + return leftTriggerValue; +} +function getLeapMotionRightTrigger() { + //print("right trigger = " + rightTriggerValue); + return rightTriggerValue; +} + var leapHands = (function () { var isOnHMD, @@ -61,78 +76,6 @@ var leapHands = (function () { print(i + ": " + jointNames[i]); } print("... skeleton joint names"); - - /* - http://public.highfidelity.io/models/skeletons/ron_standing.fst - Skeleton joint names ... - 0: Hips - 1: RightUpLeg - 2: RightLeg - 3: RightFoot - 4: RightToeBase - 5: RightToe_End - 6: LeftUpLeg - 7: LeftLeg - 8: LeftFoot - 9: LeftToeBase - 10: LeftToe_End - 11: Spine - 12: Spine1 - 13: Spine2 - 14: RightShoulder - 15: RightArm - 16: RightForeArm - 17: RightHand - 18: RightHandPinky1 - 19: RightHandPinky2 - 20: RightHandPinky3 - 21: RightHandPinky4 - 22: RightHandRing1 - 23: RightHandRing2 - 24: RightHandRing3 - 25: RightHandRing4 - 26: RightHandMiddle1 - 27: RightHandMiddle2 - 28: RightHandMiddle3 - 29: RightHandMiddle4 - 30: RightHandIndex1 - 31: RightHandIndex2 - 32: RightHandIndex3 - 33: RightHandIndex4 - 34: RightHandThumb1 - 35: RightHandThumb2 - 36: RightHandThumb3 - 37: RightHandThumb4 - 38: LeftShoulder - 39: LeftArm - 40: LeftForeArm - 41: LeftHand - 42: LeftHandPinky1 - 43: LeftHandPinky2 - 44: LeftHandPinky3 - 45: LeftHandPinky4 - 46: LeftHandRing1 - 47: LeftHandRing2 - 48: LeftHandRing3 - 49: LeftHandRing4 - 50: LeftHandMiddle1 - 51: LeftHandMiddle2 - 52: LeftHandMiddle3 - 53: LeftHandMiddle4 - 54: LeftHandIndex1 - 55: LeftHandIndex2 - 56: LeftHandIndex3 - 57: LeftHandIndex4 - 58: LeftHandThumb1 - 59: LeftHandThumb2 - 60: LeftHandThumb3 - 61: LeftHandThumb4 - 62: Neck - 63: Head - 64: HeadTop_End - 65: body - ... skeleton joint names - */ } function animateLeftHand() { @@ -357,6 +300,13 @@ var leapHands = (function () { settingsTimer = Script.setInterval(checkSettings, 2000); calibrationStatus = UNCALIBRATED; + + { + var mapping = Controller.newMapping("LeapmotionTrigger"); + mapping.from(getLeapMotionLeftTrigger).to(Controller.Standard.LT); + mapping.from(getLeapMotionRightTrigger).to(Controller.Standard.RT); + mapping.enable(); + } } function moveHands() { @@ -469,10 +419,17 @@ var leapHands = (function () { hands[h].rotation = handRotation; // Set finger joints ... + var summed = 0; + var closeAngle = 0; for (i = 0; i < NUM_FINGERS; i += 1) { for (j = 0; j < NUM_FINGER_JOINTS; j += 1) { if (fingers[h][i][j].controller !== null) { locRotation = fingers[h][i][j].controller.getLocRotation(); + var eulers = Quat.safeEulerAngles(locRotation); + closeAngle += eulers.x; + + summed++; + if (i === THUMB) { locRotation = { x: side * locRotation.y, @@ -496,8 +453,21 @@ var leapHands = (function () { } } } - + hands[h].inactiveCount = 0; + if (summed > 0) { + closeAngle /= summed; + } + + var triggerValue = (-closeAngle - LEAP_TRIGGER_START_ANGLE) / (LEAP_TRIGGER_END_ANGLE - LEAP_TRIGGER_START_ANGLE); + triggerValue = Math.max(0.0, Math.min(triggerValue, 1.0)); + + if (h == 0) { + leftTriggerValue = triggerValue; + } else { + rightTriggerValue = triggerValue; + + } } else { @@ -509,6 +479,8 @@ var leapHands = (function () { if (handAnimationStateHandlers[h] !== null) { MyAvatar.removeAnimationStateHandler(handAnimationStateHandlers[h]); handAnimationStateHandlers[h] = null; + leftTriggerValue = 0.0; + rightTriggerValue = 0.0; } } } diff --git a/examples/depthReticle.js b/examples/depthReticle.js index a60e61d07c..4b649f49b6 100644 --- a/examples/depthReticle.js +++ b/examples/depthReticle.js @@ -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); } }); diff --git a/examples/gridTest.js b/examples/gridTest.js index 0d6040470f..5bbd3246ae 100644 --- a/examples/gridTest.js +++ b/examples/gridTest.js @@ -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, diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index e3ad77870d..ccbbc5557a 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -72,10 +72,13 @@ }; } - function createEmitNumberPropertyUpdateFunction(propertyName) { + function createEmitNumberPropertyUpdateFunction(propertyName, decimals) { + decimals = decimals == undefined ? 4 : decimals; return function() { + var value = parseFloat(this.value).toFixed(decimals); + EventBridge.emitWebEvent( - '{ "type":"update", "properties":{"' + propertyName + '":' + parseFloat(this.value).toFixed(4) + '}}' + '{ "type":"update", "properties":{"' + propertyName + '":' + value + '}}' ); }; } @@ -323,6 +326,7 @@ var elLightColorBlue = document.getElementById("property-light-color-blue"); var elLightIntensity = document.getElementById("property-light-intensity"); + var elLightFalloffRadius = document.getElementById("property-light-falloff-radius"); var elLightExponent = document.getElementById("property-light-exponent"); var elLightCutoff = document.getElementById("property-light-cutoff"); @@ -604,9 +608,10 @@ elLightColorGreen.value = properties.color.green; elLightColorBlue.value = properties.color.blue; - elLightIntensity.value = properties.intensity; - elLightExponent.value = properties.exponent; - elLightCutoff.value = properties.cutoff; + elLightIntensity.value = properties.intensity.toFixed(1); + elLightFalloffRadius.value = properties.falloffRadius.toFixed(1); + elLightExponent.value = properties.exponent.toFixed(2); + elLightCutoff.value = properties.cutoff.toFixed(2); } else if (properties.type == "Zone") { for (var i = 0; i < elZoneSections.length; i++) { elZoneSections[i].style.display = 'block'; @@ -795,9 +800,10 @@ } }) - elLightIntensity.addEventListener('change', createEmitNumberPropertyUpdateFunction('intensity')); - elLightExponent.addEventListener('change', createEmitNumberPropertyUpdateFunction('exponent')); - elLightCutoff.addEventListener('change', createEmitNumberPropertyUpdateFunction('cutoff')); + elLightIntensity.addEventListener('change', createEmitNumberPropertyUpdateFunction('intensity', 1)); + elLightFalloffRadius.addEventListener('change', createEmitNumberPropertyUpdateFunction('falloffRadius', 1)); + elLightExponent.addEventListener('change', createEmitNumberPropertyUpdateFunction('exponent', 2)); + elLightCutoff.addEventListener('change', createEmitNumberPropertyUpdateFunction('cutoff', 2)); elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl')); @@ -1043,25 +1049,25 @@
Line Height
- +
Text Color
-
R
-
G
-
B
+
R
+
G
+
B
Background Color
-
R
-
G
-
B
+
R
+
G
+
B
@@ -1085,32 +1091,32 @@
Light Color
-
R
-
G
-
B
+
R
+
G
+
B
Light Intensity
- +
Light Direction
-
Pitch
-
Yaw
-
Roll
+
Pitch
+
Yaw
+
Roll
Ambient Intensity
- +
@@ -1129,19 +1135,19 @@
Stage Latitude
- +
Stage Longitude
- +
Stage Altitude
- +
@@ -1155,13 +1161,13 @@
Stage Day
- +
Stage Hour
- +
@@ -1186,9 +1192,9 @@
Skybox Color
-
R
-
G
-
B
+
R
+
G
+
B
@@ -1264,23 +1270,23 @@
Registration
-
X
-
Y
-
Z
+
X
+
Y
+
Z
Dimensions
-
X
-
Y
-
Z
+
X
+
Y
+
Z
- % + %
@@ -1291,9 +1297,9 @@
Voxel Volume Size
-
X
-
Y
-
Z
+
X
+
Y
+
Z
Surface Extractor
@@ -1325,9 +1331,9 @@
Rotation
-
Pitch
-
Yaw
-
Roll
+
Pitch
+
Yaw
+
Roll
@@ -1339,66 +1345,66 @@
Linear Velocity
-
X
-
Y
-
Z
+
X
+
Y
+
Z
Linear Damping
- +
Angular Velocity
-
Pitch
-
Yaw
-
Roll
+
Pitch
+
Yaw
+
Roll
Angular Damping
- +
Restitution
- +
Friction
- +
Gravity
-
X
-
Y
-
Z
+
X
+
Y
+
Z
Acceleration
-
X
-
Y
-
Z
+
X
+
Y
+
Z
Density
- +
@@ -1406,9 +1412,9 @@
Color
-
R
-
G
-
B
+
R
+
G
+
B
@@ -1483,7 +1489,7 @@
Lifetime
- +
@@ -1541,25 +1547,25 @@
Animation FPS
- +
Animation Frame
- +
Animation First Frame
- +
Animation Last Frame
- +
@@ -1591,37 +1597,43 @@
+
+
Color
+
+
+
R
+
G
+
B
+
+
+
+
Intensity
+
+ +
+
+
+
Falloff Radius
+
+ +
+
Spot Light
-
-
Color
-
-
-
R
-
G
-
B
-
-
-
-
Intensity
-
- -
-
Spot Light Exponent
- +
Spot Light Cutoff (degrees)
- +
diff --git a/ice-server/CMakeLists.txt b/ice-server/CMakeLists.txt index e5bdffe2e2..cfec3c966c 100644 --- a/ice-server/CMakeLists.txt +++ b/ice-server/CMakeLists.txt @@ -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}) diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index f38923b873..2baa7a13a7 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -9,21 +9,14 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "IceServer.h" - -#include -#include - -#include -#include -#include -#include +#include #include -#include #include #include +#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 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::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::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(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(hashedPlaintext.constData()), - hashedPlaintext.size(), - reinterpret_cast(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) { diff --git a/ice-server/src/IceServer.h b/ice-server/src/IceServer.h index 81234b2c3c..f1c2c06b65 100644 --- a/ice-server/src/IceServer.h +++ b/ice-server/src/IceServer.h @@ -16,15 +16,13 @@ #include #include -#include - #include #include #include #include #include -class QNetworkReply; +typedef QHash 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 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; NetworkPeerHash _activePeers; - HTTPManager _httpManager; - - using DomainPublicKeyHash = std::unordered_map; - DomainPublicKeyHash _domainPublicKeys; }; #endif // hifi_IceServer_h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1c4618ec42..21377fa945 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -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(); connect(&locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation); - connect(&locationUpdateTimer, &QTimer::timeout, + connect(&locationUpdateTimer, &QTimer::timeout, DependencyManager::get().data(), &AddressManager::storeCurrentAddress); locationUpdateTimer.start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS); @@ -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().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(); 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()->saveScripts(); DependencyManager::get()->shutdownScripting(); // stop all currently running global scripts - DependencyManager::destroy(); + DependencyManager::destroy(); // first stop all timers directly or by invokeMethod // depending on what thread they run in @@ -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().data()); rootContext->setContextProperty("Audio", &AudioScriptingInterface::getInstance()); rootContext->setContextProperty("Controller", DependencyManager::get().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(); + 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(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(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(); _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 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()->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(); QString fileNameString = OffscreenUi::getOpenFileName( @@ -4702,7 +4749,7 @@ static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool acti groupingMenu = "Developer"; break; default: - groupingMenu = "Standard"; + groupingMenu = "Standard"; break; } diff --git a/interface/src/Application.h b/interface/src/Application.h index cce968e331..d205ce8041 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -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 message); + void notifyPacketVersionMismatch(); void loadSettings(); @@ -469,6 +476,7 @@ private: typedef bool (Application::* AcceptURLMethod)(const QString &); static const QHash _acceptedExtensions; + QList _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 }; diff --git a/interface/src/CrashHandler.cpp b/interface/src/CrashHandler.cpp index f9cd7679ae..ce5facb580 100644 --- a/interface/src/CrashHandler.cpp +++ b/interface/src/CrashHandler.cpp @@ -22,8 +22,11 @@ #include #include +#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 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"); + qRegisterMetaTypeStreamOperators("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()); + } + 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(); } } diff --git a/interface/src/CrashHandler.h b/interface/src/CrashHandler.h index 61361b6107..fc754cf1ac 100644 --- a/interface/src/CrashHandler.h +++ b/interface/src/CrashHandler.h @@ -25,7 +25,7 @@ public: private: enum Action { DELETE_INTERFACE_INI, - RETAIN_AVATAR_INFO, + RETAIN_LOGIN_AND_AVATAR_INFO, DO_NOTHING }; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 407e0fe64f..72e138e0cb 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -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"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 44a24ee895..ca5bf295db 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -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"; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index ee0ef21ae0..0bd134bef5 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -136,7 +136,7 @@ glm::quat Avatar::getWorldAlignedOrientation () const { AABox Avatar::getBounds() const { // Our skeleton models are rigged, and this method call safely produces the static bounds of the model. - // Except, that getPartBounds produces an infinite, uncentered bounding box when the model is not yet parsed, + // Except, that getPartBounds produces an infinite, uncentered bounding box when the model is not yet parsed, // and we want a centered one. NOTE: There is code that may never try to render, and thus never load and get the // real model bounds, if this is unrealistically small. if (!_skeletonModel.isRenderable()) { @@ -188,15 +188,14 @@ void Avatar::simulate(float deltaTime) { // simple frustum check float boundingRadius = getBoundingRadius(); - bool inViewFrustum = qApp->getViewFrustum()->sphereInFrustum(getPosition(), boundingRadius) != - ViewFrustum::OUTSIDE; + bool inView = qApp->getViewFrustum()->sphereIntersectsFrustum(getPosition(), boundingRadius); { PerformanceTimer perfTimer("hand"); getHand()->simulate(deltaTime, false); } - if (_shouldAnimate && !_shouldSkipRender && inViewFrustum) { + if (_shouldAnimate && !_shouldSkipRender && inView) { { PerformanceTimer perfTimer("skeleton"); _skeletonModel.getRig()->copyJointsFromJointData(_jointData); @@ -401,7 +400,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { frustum = qApp->getDisplayViewFrustum(); } - if (frustum->sphereInFrustum(getPosition(), boundingRadius) == ViewFrustum::OUTSIDE) { + if (!frustum->sphereIntersectsFrustum(getPosition(), boundingRadius)) { endRender(); return; } @@ -430,6 +429,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE) { // add local lights const float BASE_LIGHT_DISTANCE = 2.0f; + const float LIGHT_FALLOFF_RADIUS = 0.01f; const float LIGHT_EXPONENT = 1.0f; const float LIGHT_CUTOFF = glm::radians(80.0f); float distance = BASE_LIGHT_DISTANCE * getUniformScale(); @@ -438,7 +438,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { foreach (const AvatarManager::LocalLight& light, DependencyManager::get()->getLocalLights()) { glm::vec3 direction = orientation * light.direction; DependencyManager::get()->addSpotLight(position - direction * distance, - distance * 2.0f, light.color, 0.5f, orientation, LIGHT_EXPONENT, LIGHT_CUTOFF); + distance * 2.0f, light.color, 0.5f, LIGHT_FALLOFF_RADIUS, orientation, LIGHT_EXPONENT, LIGHT_CUTOFF); } } @@ -516,7 +516,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { auto& frustum = *renderArgs->_viewFrustum; auto textPosition = getDisplayNamePosition(); - if (frustum.pointInFrustum(textPosition, true) == ViewFrustum::INSIDE) { + if (frustum.pointIntersectsFrustum(textPosition)) { renderDisplayName(batch, frustum, textPosition); } } @@ -669,10 +669,10 @@ glm::vec3 Avatar::getDisplayNamePosition() const { return namePosition; } -Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, const glm::vec3& textPosition) const { - Q_ASSERT_X(frustum.pointInFrustum(textPosition, true) == ViewFrustum::INSIDE, +Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& view, const glm::vec3& textPosition) const { + Q_ASSERT_X(view.pointIntersectsFrustum(textPosition), "Avatar::calculateDisplayNameTransform", "Text not in viewfrustum."); - glm::vec3 toFrustum = frustum.getPosition() - textPosition; + glm::vec3 toFrustum = view.getPosition() - textPosition; // Compute orientation // If x and z are 0, atan(x, z) adais undefined, so default to 0 degrees @@ -694,7 +694,7 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, cons return result; } -void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, const glm::vec3& textPosition) const { +void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition) const { PROFILE_RANGE_BATCH(batch, __FUNCTION__); bool shouldShowReceiveStats = DependencyManager::get()->shouldShowReceiveStats() && !isMyAvatar(); @@ -702,7 +702,7 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, co // If we have nothing to draw, or it's totally transparent, or it's too close or behind the camera, return static const float CLIP_DISTANCE = 0.2f; if ((_displayName.isEmpty() && !shouldShowReceiveStats) || _displayNameAlpha == 0.0f - || (glm::dot(frustum.getDirection(), getDisplayNamePosition() - frustum.getPosition()) <= CLIP_DISTANCE)) { + || (glm::dot(view.getDirection(), getDisplayNamePosition() - view.getPosition()) <= CLIP_DISTANCE)) { return; } auto renderer = textRenderer(DISPLAYNAME); @@ -743,7 +743,7 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, co (_displayNameAlpha / DISPLAYNAME_ALPHA) * DISPLAYNAME_BACKGROUND_ALPHA); // Compute display name transform - auto textTransform = calculateDisplayNameTransform(frustum, textPosition); + auto textTransform = calculateDisplayNameTransform(view, textPosition); // Test on extent above insures abs(height) > 0.0f textTransform.postScale(1.0f / height); batch.setModelTransform(textTransform); diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 766a4734eb..cd9fc86e1e 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -231,8 +231,8 @@ protected: float getPelvisFloatingHeight() const; glm::vec3 getDisplayNamePosition() const; - Transform calculateDisplayNameTransform(const ViewFrustum& frustum, const glm::vec3& textPosition) const; - void renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, const glm::vec3& textPosition) const; + Transform calculateDisplayNameTransform(const ViewFrustum& view, const glm::vec3& textPosition) const; + void renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition) const; virtual void renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, float glowLevel = 0.0f); virtual bool shouldRenderHead(const RenderArgs* renderArgs) const; virtual void fixupModelsInScene(); diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 4c9c0df1cb..b8c82498a1 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -25,7 +25,7 @@ WindowScriptingInterface::WindowScriptingInterface() { const DomainHandler& domainHandler = DependencyManager::get()->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 = diff --git a/interface/src/ui/ApplicationCompositor.cpp b/interface/src/ui/ApplicationCompositor.cpp index 5c6e4c9819..59d794d7cb 100644 --- a/interface/src/ui/ApplicationCompositor.cpp +++ b/interface/src/ui/ApplicationCompositor.cpp @@ -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); diff --git a/interface/src/ui/ApplicationCompositor.h b/interface/src/ui/ApplicationCompositor.h index 7bdb97727f..32835ace5a 100644 --- a/interface/src/ui/ApplicationCompositor.h +++ b/interface/src/ui/ApplicationCompositor.h @@ -107,7 +107,7 @@ public: bool shouldCaptureMouse() const; /// if the reticle is pointing to a system overlay (a dialog box for example) then the function returns true otherwise false - bool getReticleOverDesktop() const { return _isOverDesktop; } + bool getReticleOverDesktop() const; void setReticleOverDesktop(bool value) { _isOverDesktop = value; } private: @@ -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 }; diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 67b5135e0c..998a64d81a 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -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) { diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index f777e5d4dc..113dad1ab5 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -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->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->getRootItem()->setEnabled(false); } } diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 4b8c0134b5..b94c5be7dd 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -29,8 +29,8 @@ private: STANDING }; - Mode _mode = FLAT; - bool _enabled = true; + Mode _mode { FLAT }; + bool _enabled { false }; }; #endif diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index e6662caa37..dba856cbaa 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -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"), diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index e77e5e5a93..efdc45cb3d 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -52,6 +52,7 @@ enum class Action { CONTEXT_MENU, TOGGLE_MUTE, CYCLE_CAMERA, + TOGGLE_OVERLAY, SHIFT, diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp index 2d054ec31b..671043813e 100644 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp @@ -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); diff --git a/libraries/entities-renderer/src/RenderableLightEntityItem.cpp b/libraries/entities-renderer/src/RenderableLightEntityItem.cpp index 39182f322c..fb6061e94f 100644 --- a/libraries/entities-renderer/src/RenderableLightEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableLightEntityItem.cpp @@ -36,15 +36,16 @@ void RenderableLightEntityItem::render(RenderArgs* args) { glm::vec3 color = toGlm(getXColor()); float intensity = getIntensity(); + float falloffRadius = getFalloffRadius(); float exponent = getExponent(); float cutoff = glm::radians(getCutoff()); if (_isSpotlight) { DependencyManager::get()->addSpotLight(position, largestDiameter / 2.0f, - color, intensity, rotation, exponent, cutoff); + color, intensity, falloffRadius, rotation, exponent, cutoff); } else { DependencyManager::get()->addPointLight(position, largestDiameter / 2.0f, - color, intensity); + color, intensity, falloffRadius); } #ifdef WANT_DEBUG diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 8e0983f62a..550ec205c0 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -245,6 +245,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_DYNAMIC, dynamic); CHECK_PROPERTY_CHANGE(PROP_IS_SPOTLIGHT, isSpotlight); CHECK_PROPERTY_CHANGE(PROP_INTENSITY, intensity); + CHECK_PROPERTY_CHANGE(PROP_FALLOFF_RADIUS, falloffRadius); CHECK_PROPERTY_CHANGE(PROP_EXPONENT, exponent); CHECK_PROPERTY_CHANGE(PROP_CUTOFF, cutoff); CHECK_PROPERTY_CHANGE(PROP_LOCKED, locked); @@ -445,6 +446,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool if (_type == EntityTypes::Light) { COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IS_SPOTLIGHT, isSpotlight); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_INTENSITY, intensity); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FALLOFF_RADIUS, falloffRadius); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EXPONENT, exponent); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CUTOFF, cutoff); } @@ -597,6 +599,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(dynamic, bool, setDynamic); COPY_PROPERTY_FROM_QSCRIPTVALUE(isSpotlight, bool, setIsSpotlight); COPY_PROPERTY_FROM_QSCRIPTVALUE(intensity, float, setIntensity); + COPY_PROPERTY_FROM_QSCRIPTVALUE(falloffRadius, float, setFalloffRadius); COPY_PROPERTY_FROM_QSCRIPTVALUE(exponent, float, setExponent); COPY_PROPERTY_FROM_QSCRIPTVALUE(cutoff, float, setCutoff); COPY_PROPERTY_FROM_QSCRIPTVALUE(locked, bool, setLocked); @@ -762,6 +765,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_DYNAMIC, unused, dynamic, unused); ADD_PROPERTY_TO_MAP(PROP_IS_SPOTLIGHT, IsSpotlight, isSpotlight, bool); ADD_PROPERTY_TO_MAP(PROP_INTENSITY, Intensity, intensity, float); + ADD_PROPERTY_TO_MAP(PROP_FALLOFF_RADIUS, FalloffRadius, falloffRadius, float); ADD_PROPERTY_TO_MAP(PROP_EXPONENT, Exponent, exponent, float); ADD_PROPERTY_TO_MAP(PROP_CUTOFF, Cutoff, cutoff, float); ADD_PROPERTY_TO_MAP(PROP_LOCKED, Locked, locked, bool); @@ -1043,6 +1047,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_IS_SPOTLIGHT, properties.getIsSpotlight()); APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); APPEND_ENTITY_PROPERTY(PROP_INTENSITY, properties.getIntensity()); + APPEND_ENTITY_PROPERTY(PROP_FALLOFF_RADIUS, properties.getFalloffRadius()); APPEND_ENTITY_PROPERTY(PROP_EXPONENT, properties.getExponent()); APPEND_ENTITY_PROPERTY(PROP_CUTOFF, properties.getCutoff()); } @@ -1332,6 +1337,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IS_SPOTLIGHT, bool, setIsSpotlight); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, xColor, setColor); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_INTENSITY, float, setIntensity); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FALLOFF_RADIUS, float, setFalloffRadius); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EXPONENT, float, setExponent); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CUTOFF, float, setCutoff); } @@ -1477,6 +1483,7 @@ void EntityItemProperties::markAllChanged() { _dynamicChanged = true; _intensityChanged = true; + _falloffRadiusChanged = true; _exponentChanged = true; _cutoffChanged = true; _lockedChanged = true; @@ -1719,6 +1726,9 @@ QList EntityItemProperties::listChangedProperties() { if (intensityChanged()) { out += "intensity"; } + if (falloffRadiusChanged()) { + out += "falloffRadius"; + } if (exponentChanged()) { out += "exponent"; } diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 2cfef17c1b..c732d01fa5 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -35,6 +35,7 @@ #include "EntityItemPropertiesMacros.h" #include "EntityTypes.h" #include "EntityPropertyFlags.h" +#include "LightEntityItem.h" #include "LineEntityItem.h" #include "ParticleEffectEntityItem.h" #include "PolyVoxEntityItem.h" @@ -129,10 +130,11 @@ public: DEFINE_PROPERTY(PROP_COLLISIONLESS, Collisionless, collisionless, bool, ENTITY_ITEM_DEFAULT_COLLISIONLESS); DEFINE_PROPERTY(PROP_COLLISION_MASK, CollisionMask, collisionMask, uint8_t, ENTITY_COLLISION_MASK_DEFAULT); DEFINE_PROPERTY(PROP_DYNAMIC, Dynamic, dynamic, bool, ENTITY_ITEM_DEFAULT_DYNAMIC); - DEFINE_PROPERTY(PROP_IS_SPOTLIGHT, IsSpotlight, isSpotlight, bool, false); - DEFINE_PROPERTY(PROP_INTENSITY, Intensity, intensity, float, 1.0f); - DEFINE_PROPERTY(PROP_EXPONENT, Exponent, exponent, float, 0.0f); - DEFINE_PROPERTY(PROP_CUTOFF, Cutoff, cutoff, float, ENTITY_ITEM_DEFAULT_CUTOFF); + DEFINE_PROPERTY(PROP_IS_SPOTLIGHT, IsSpotlight, isSpotlight, bool, LightEntityItem::DEFAULT_IS_SPOTLIGHT); + DEFINE_PROPERTY(PROP_INTENSITY, Intensity, intensity, float, LightEntityItem::DEFAULT_INTENSITY); + DEFINE_PROPERTY(PROP_FALLOFF_RADIUS, FalloffRadius, falloffRadius, float, LightEntityItem::DEFAULT_FALLOFF_RADIUS); + DEFINE_PROPERTY(PROP_EXPONENT, Exponent, exponent, float, LightEntityItem::DEFAULT_EXPONENT); + DEFINE_PROPERTY(PROP_CUTOFF, Cutoff, cutoff, float, LightEntityItem::DEFAULT_CUTOFF); DEFINE_PROPERTY(PROP_LOCKED, Locked, locked, bool, ENTITY_ITEM_DEFAULT_LOCKED); DEFINE_PROPERTY_REF(PROP_TEXTURES, Textures, textures, QString, ""); DEFINE_PROPERTY_REF(PROP_USER_DATA, UserData, userData, QString, ENTITY_ITEM_DEFAULT_USER_DATA); @@ -359,6 +361,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, Dynamic, dynamic, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, IsSpotlight, isSpotlight, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Intensity, intensity, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, FalloffRadius, falloffRadius, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Exponent, exponent, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Cutoff, cutoff, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Locked, locked, ""); diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index 5375b1bc3a..aa4fb5c619 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -72,8 +72,6 @@ const bool ENTITY_ITEM_DEFAULT_COLLISIONLESS = false; const bool ENTITY_ITEM_DEFAULT_DYNAMIC = false; const bool ENTITY_ITEM_DEFAULT_BILLBOARDED = false; -const float ENTITY_ITEM_DEFAULT_CUTOFF = PI / 2; - const QString ENTITY_ITEM_DEFAULT_NAME = QString(""); #endif // hifi_EntityItemPropertiesDefaults_h diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index b60fc6174c..90a7c1e2f7 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -167,6 +167,8 @@ enum EntityPropertyList { PROP_COLLISION_MASK, // one byte of collision group flags + PROP_FALLOFF_RADIUS, // for Light entity + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line PROP_AFTER_LAST_ITEM, diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 4abbe3c3fa..15ff531b06 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -85,7 +85,7 @@ void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params) forEachEntity([&](EntityItemPointer entity) { entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params)); }); - + // TODO: some of these inserts might be redundant!!! extraEncodeData->insert(this, entityTreeElementExtraEncodeData); } @@ -96,39 +96,39 @@ bool EntityTreeElement::shouldIncludeChildData(int childIndex, EncodeBitstreamPa assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes if (extraEncodeData->contains(this)) { - EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData + EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData = static_cast(extraEncodeData->value(this)); - + bool childCompleted = entityTreeElementExtraEncodeData->childCompleted[childIndex]; - + // If we haven't completely sent the child yet, then we should include it return !childCompleted; } - + // I'm not sure this should ever happen, since we should have the extra encode data if we're considering // the child data for this element assert(false); return false; } -bool EntityTreeElement::shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const { +bool EntityTreeElement::shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const { EntityTreeElementPointer childElement = getChildAtIndex(childIndex); if (childElement->alreadyFullyEncoded(params)) { return false; } - + return true; // if we don't know otherwise than recurse! } -bool EntityTreeElement::alreadyFullyEncoded(EncodeBitstreamParams& params) const { +bool EntityTreeElement::alreadyFullyEncoded(EncodeBitstreamParams& params) const { OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData; assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes if (extraEncodeData->contains(this)) { - EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData + EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData = static_cast(extraEncodeData->value(this)); - // If we know that ALL subtrees below us have already been recursed, then we don't + // If we know that ALL subtrees below us have already been recursed, then we don't // need to recurse this child. return entityTreeElementExtraEncodeData->subtreeCompleted; } @@ -139,7 +139,7 @@ void EntityTreeElement::updateEncodedData(int childIndex, AppendState childAppen OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData; assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes if (extraEncodeData->contains(this)) { - EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData + EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData = static_cast(extraEncodeData->value(this)); if (childAppendState == OctreeElement::COMPLETED) { @@ -155,7 +155,7 @@ void EntityTreeElement::updateEncodedData(int childIndex, AppendState childAppen void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) const { const bool wantDebug = false; - + if (wantDebug) { qCDebug(entities) << "EntityTreeElement::elementEncodeComplete() element:" << _cube; } @@ -188,7 +188,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con // If we've encoding this element before... but we're coming back a second time in an attempt to // encoud our parent... this might happen. if (extraEncodeData->contains(childElement.get())) { - EntityTreeElementExtraEncodeData* childExtraEncodeData + EntityTreeElementExtraEncodeData* childExtraEncodeData = static_cast(extraEncodeData->value(childElement.get())); if (wantDebug) { @@ -197,7 +197,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con qCDebug(entities) << " childExtraEncodeData->elementCompleted:" << childExtraEncodeData->elementCompleted; qCDebug(entities) << " childExtraEncodeData->subtreeCompleted:" << childExtraEncodeData->subtreeCompleted; } - + if (childElement->isLeaf() && childExtraEncodeData->elementCompleted) { if (wantDebug) { qCDebug(entities) << " CHILD IS LEAF -- AND CHILD ELEMENT DATA COMPLETED!!!"; @@ -217,24 +217,24 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con qCDebug(entities) << " WAS elementCompleted:" << thisExtraEncodeData->elementCompleted; qCDebug(entities) << " WAS subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted; } - + thisExtraEncodeData->subtreeCompleted = !someChildTreeNotComplete; if (wantDebug) { qCDebug(entities) << " NOW elementCompleted:" << thisExtraEncodeData->elementCompleted; qCDebug(entities) << " NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted; - + if (thisExtraEncodeData->subtreeCompleted) { qCDebug(entities) << " YEAH!!!!! >>>>>>>>>>>>>> NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted; } } } -OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData* packetData, +OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const { OctreeElement::AppendState appendElementState = OctreeElement::COMPLETED; // assume the best... - + // first, check the params.extraEncodeData to see if there's any partial re-encode data for this element OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData; EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData = NULL; @@ -280,7 +280,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData QVector indexesOfEntitiesToInclude; // It's possible that our element has been previous completed. In this case we'll simply not include any of our - // entities for encoding. This is needed because we encode the element data at the "parent" level, and so we + // entities for encoding. This is needed because we encode the element data at the "parent" level, and so we // need to handle the case where our sibling elements need encoding but we don't. if (!entityTreeElementExtraEncodeData->elementCompleted) { for (uint16_t i = 0; i < _entityItems.size(); i++) { @@ -304,15 +304,13 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData // frustum culling on rendering. bool success; AACube entityCube = entity->getQueryAACube(success); - if (!success || params.viewFrustum->cubeInFrustum(entityCube) == ViewFrustum::OUTSIDE) { + if (!success || !params.viewFrustum->cubeIntersectsKeyhole(entityCube)) { includeThisEntity = false; // out of view, don't include it - } - - // Now check the size of the entity, it's possible that a "too small to see" entity is included in a - // larger octree cell because of its position (for example if it crosses the boundary of a cell it - // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen - // before we consider including it. - if (includeThisEntity) { + } else { + // Check the size of the entity, it's possible that a "too small to see" entity is included in a + // larger octree cell because of its position (for example if it crosses the boundary of a cell it + // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen + // before we consider including it. success = true; // we can't cull a parent-entity by its dimensions because the child may be larger. we need to // avoid sending details about a child but not the parent. the parent's queryAACube should have @@ -397,7 +395,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData // this octree element. if (extraEncodeData && entityTreeElementExtraEncodeData) { - // After processing, if we are PARTIAL or COMPLETED then we need to re-include our extra data. + // After processing, if we are PARTIAL or COMPLETED then we need to re-include our extra data. // Only our parent can remove our extra data in these cases and only after it knows that all of its // children have been encoded. // If we weren't able to encode ANY data about ourselves, then we go ahead and remove our element data @@ -412,7 +410,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData extraEncodeData->insert(this, entityTreeElementExtraEncodeData); } } else { - + // If we weren't previously completed, check to see if we are if (!entityTreeElementExtraEncodeData->elementCompleted) { // If all of our items have been encoded, then we are complete as an element. @@ -426,9 +424,9 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData } } - // Determine if no entities at all were able to fit + // Determine if no entities at all were able to fit bool noEntitiesFit = (numberOfEntities > 0 && actualNumberOfEntities == 0); - + // If we wrote fewer entities than we expected, update the number of entities in our packet bool successUpdateEntityCount = true; if (numberOfEntities != actualNumberOfEntities) { @@ -504,7 +502,7 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3 glm::vec3 clampedMax = glm::clamp(maxPoint, (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); if (_cube.contains(clampedMin) && _cube.contains(clampedMax)) { - + // If our child would be smaller than our smallest reasonable element, then we are the best fit. float childScale = _cube.getScale() / 2.0f; if (childScale <= SMALLEST_REASONABLE_OCTREE_ELEMENT_SCALE) { @@ -524,7 +522,7 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3 bool EntityTreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, bool& keepSearching, OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, + BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, void** intersectedObject, bool precisionPicking) { keepSearching = true; // assume that we will continue searching after this. @@ -607,7 +605,7 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con // we can use the AABox's ray intersection by mapping our origin and direction into the entity frame // and testing intersection there. - if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance, + if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance, localFace, localSurfaceNormal)) { if (localDistance < distance) { // now ask the entity if we actually intersect @@ -862,12 +860,12 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int if (this == _myTree->getRoot().get() && args.bitstreamVersion < VERSION_ROOT_ELEMENT_HAS_DATA) { return 0; } - + const unsigned char* dataAt = data; int bytesRead = 0; uint16_t numberOfEntities = 0; int expectedBytesPerEntity = EntityItem::expectedBytes(); - + args.elementsPerPacket++; if (bytesLeftToRead >= (int)sizeof(numberOfEntities)) { @@ -947,7 +945,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int entityItem->recordCreationTime(); } } else { - qDebug() << "Recieved packet for previously deleted entity [" << + qDebug() << "Recieved packet for previously deleted entity [" << entityItem->getID() << "] ignoring. (inside " << __FUNCTION__ << ")"; } } @@ -959,7 +957,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int } } } - + return bytesRead; } @@ -990,7 +988,7 @@ bool EntityTreeElement::pruneChildren() { bool somethingPruned = false; for (int childIndex = 0; childIndex < NUMBER_OF_CHILDREN; childIndex++) { EntityTreeElementPointer child = getChildAtIndex(childIndex); - + // if my child is a leaf, but has no entities, then it's safe to delete my child if (child && child->isLeaf() && !child->hasEntities()) { deleteChildAtIndex(childIndex); @@ -1040,4 +1038,4 @@ void EntityTreeElement::debugDump() { } }); } - + diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp index ac56fc9c1f..852b37a751 100644 --- a/libraries/entities/src/LightEntityItem.cpp +++ b/libraries/entities/src/LightEntityItem.cpp @@ -21,6 +21,12 @@ #include "EntityTreeElement.h" #include "LightEntityItem.h" +const bool LightEntityItem::DEFAULT_IS_SPOTLIGHT = false; +const float LightEntityItem::DEFAULT_INTENSITY = 1.0f; +const float LightEntityItem::DEFAULT_FALLOFF_RADIUS = 0.1f; +const float LightEntityItem::DEFAULT_EXPONENT = 0.0f; +const float LightEntityItem::DEFAULT_CUTOFF = PI / 2.0f; + bool LightEntityItem::_lightsArePickable = false; EntityItemPointer LightEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { @@ -32,12 +38,7 @@ EntityItemPointer LightEntityItem::factory(const EntityItemID& entityID, const E // our non-pure virtual subclass for now... LightEntityItem::LightEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { _type = EntityTypes::Light; - - // default property values _color[RED_INDEX] = _color[GREEN_INDEX] = _color[BLUE_INDEX] = 0; - _intensity = 1.0f; - _exponent = 0.0f; - _cutoff = PI; } void LightEntityItem::setDimensions(const glm::vec3& value) { @@ -62,10 +63,15 @@ EntityItemProperties LightEntityItem::getProperties(EntityPropertyFlags desiredP COPY_ENTITY_PROPERTY_TO_PROPERTIES(intensity, getIntensity); COPY_ENTITY_PROPERTY_TO_PROPERTIES(exponent, getExponent); COPY_ENTITY_PROPERTY_TO_PROPERTIES(cutoff, getCutoff); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(falloffRadius, getFalloffRadius); return properties; } +void LightEntityItem::setFalloffRadius(float value) { + _falloffRadius = glm::max(value, 0.0f); +} + void LightEntityItem::setIsSpotlight(bool value) { if (value != _isSpotlight) { _isSpotlight = value; @@ -101,6 +107,7 @@ bool LightEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(intensity, setIntensity); SET_ENTITY_PROPERTY_FROM_PROPERTIES(exponent, setExponent); SET_ENTITY_PROPERTY_FROM_PROPERTIES(cutoff, setCutoff); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(falloffRadius, setFalloffRadius); if (somethingChanged) { bool wantDebug = false; @@ -150,6 +157,7 @@ int LightEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_INTENSITY, float, setIntensity); READ_ENTITY_PROPERTY(PROP_EXPONENT, float, setExponent); READ_ENTITY_PROPERTY(PROP_CUTOFF, float, setCutoff); + READ_ENTITY_PROPERTY(PROP_FALLOFF_RADIUS, float, setFalloffRadius); } return bytesRead; @@ -164,6 +172,7 @@ EntityPropertyFlags LightEntityItem::getEntityProperties(EncodeBitstreamParams& requestedProperties += PROP_INTENSITY; requestedProperties += PROP_EXPONENT; requestedProperties += PROP_CUTOFF; + requestedProperties += PROP_FALLOFF_RADIUS; return requestedProperties; } @@ -181,4 +190,5 @@ void LightEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit APPEND_ENTITY_PROPERTY(PROP_INTENSITY, getIntensity()); APPEND_ENTITY_PROPERTY(PROP_EXPONENT, getExponent()); APPEND_ENTITY_PROPERTY(PROP_CUTOFF, getCutoff()); + APPEND_ENTITY_PROPERTY(PROP_FALLOFF_RADIUS, getFalloffRadius()); } diff --git a/libraries/entities/src/LightEntityItem.h b/libraries/entities/src/LightEntityItem.h index 103c462809..4c84d3204c 100644 --- a/libraries/entities/src/LightEntityItem.h +++ b/libraries/entities/src/LightEntityItem.h @@ -16,6 +16,12 @@ class LightEntityItem : public EntityItem { public: + static const bool DEFAULT_IS_SPOTLIGHT; + static const float DEFAULT_INTENSITY; + static const float DEFAULT_FALLOFF_RADIUS; + static const float DEFAULT_EXPONENT; + static const float DEFAULT_CUTOFF; + static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); LightEntityItem(const EntityItemID& entityItemID); @@ -65,6 +71,9 @@ public: float getIntensity() const { return _intensity; } void setIntensity(float value) { _intensity = value; } + float getFalloffRadius() const { return _falloffRadius; } + void setFalloffRadius(float value); + float getExponent() const { return _exponent; } void setExponent(float value) { _exponent = value; } @@ -78,10 +87,11 @@ protected: // properties of a light rgbColor _color; - bool _isSpotlight; - float _intensity; - float _exponent; - float _cutoff; + bool _isSpotlight { DEFAULT_IS_SPOTLIGHT }; + float _intensity { DEFAULT_INTENSITY }; + float _falloffRadius { DEFAULT_FALLOFF_RADIUS }; + float _exponent { DEFAULT_EXPONENT }; + float _cutoff { DEFAULT_CUTOFF }; static bool _lightsArePickable; }; diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index bdf27f4440..6bf25f767d 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -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(); } diff --git a/libraries/entities/src/SimpleEntitySimulation.h b/libraries/entities/src/SimpleEntitySimulation.h index 53a7574bf2..d9c04fdcf9 100644 --- a/libraries/entities/src/SimpleEntitySimulation.h +++ b/libraries/entities/src/SimpleEntitySimulation.h @@ -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 diff --git a/libraries/gpu/src/gpu/Framebuffer.cpp b/libraries/gpu/src/gpu/Framebuffer.cpp index 779d70cc88..abaa0f9ef1 100755 --- a/libraries/gpu/src/gpu/Framebuffer.cpp +++ b/libraries/gpu/src/gpu/Framebuffer.cpp @@ -21,6 +21,7 @@ Framebuffer::~Framebuffer() { Framebuffer* Framebuffer::create() { auto framebuffer = new Framebuffer(); framebuffer->_renderBuffers.resize(MAX_NUM_RENDER_BUFFERS); + framebuffer->_colorStamps.resize(MAX_NUM_RENDER_BUFFERS, 0); return framebuffer; } @@ -174,6 +175,8 @@ int Framebuffer::setRenderBuffer(uint32 slot, const TexturePointer& texture, uin } } + ++_colorStamps[slot]; + updateSize(texture); // assign the new one @@ -190,6 +193,7 @@ int Framebuffer::setRenderBuffer(uint32 slot, const TexturePointer& texture, uin } void Framebuffer::removeRenderBuffers() { + if (isSwapchain()) { return; } @@ -230,6 +234,7 @@ uint32 Framebuffer::getRenderBufferSubresource(uint32 slot) const { } bool Framebuffer::setDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource) { + ++_depthStamp; if (isSwapchain()) { return false; } diff --git a/libraries/gpu/src/gpu/Framebuffer.h b/libraries/gpu/src/gpu/Framebuffer.h index e986e4a481..5d016645fe 100755 --- a/libraries/gpu/src/gpu/Framebuffer.h +++ b/libraries/gpu/src/gpu/Framebuffer.h @@ -135,10 +135,15 @@ public: static uint32 getMaxNumRenderBuffers() { return MAX_NUM_RENDER_BUFFERS; } const GPUObjectPointer gpuObject {}; - + + Stamp getDepthStamp() const { return _depthStamp; } + const std::vector& getColorStamps() const { return _colorStamps; } + protected: SwapchainPointer _swapchain; + Stamp _depthStamp { 0 }; + std::vector _colorStamps; TextureViews _renderBuffers; TextureView _depthStencilBuffer; diff --git a/libraries/gpu/src/gpu/GLBackend.h b/libraries/gpu/src/gpu/GLBackend.h index 7f37a73c57..15338d7587 100644 --- a/libraries/gpu/src/gpu/GLBackend.h +++ b/libraries/gpu/src/gpu/GLBackend.h @@ -173,6 +173,9 @@ public: public: GLuint _fbo = 0; std::vector _colorBuffers; + Stamp _depthStamp { 0 }; + std::vector _colorStamps; + GLFramebuffer(); ~GLFramebuffer(); diff --git a/libraries/gpu/src/gpu/GLBackendOutput.cpp b/libraries/gpu/src/gpu/GLBackendOutput.cpp index 5f226141a8..37a10e670b 100755 --- a/libraries/gpu/src/gpu/GLBackendOutput.cpp +++ b/libraries/gpu/src/gpu/GLBackendOutput.cpp @@ -27,8 +27,15 @@ GLBackend::GLFramebuffer::~GLFramebuffer() { GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffer) { GLFramebuffer* object = Backend::getGPUObject(framebuffer); + bool needsUpate { false }; + if (!object || + framebuffer.getDepthStamp() != object->_depthStamp || + framebuffer.getColorStamps() != object->_colorStamps) { + needsUpate = true; + } + // If GPU object already created and in sync - if (object) { + if (!needsUpate) { return object; } else if (framebuffer.isEmpty()) { // NO framebuffer definition yet so let's avoid thinking @@ -37,94 +44,112 @@ GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffe // need to have a gpu object? if (!object) { - GLint currentFBO; + // All is green, assign the gpuobject to the Framebuffer + object = new GLFramebuffer(); + Backend::setGPUObject(framebuffer, object); + glGenFramebuffers(1, &object->_fbo); + (void)CHECK_GL_ERROR(); + } + + if (needsUpate) { + GLint currentFBO = -1; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤tFBO); - - GLuint fbo; - glGenFramebuffers(1, &fbo); - (void) CHECK_GL_ERROR(); + glBindFramebuffer(GL_FRAMEBUFFER, object->_fbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); + GLTexture* gltexture = nullptr; + TexturePointer surface; + if (framebuffer.getColorStamps() != object->_colorStamps) { + if (framebuffer.hasColor()) { + object->_colorBuffers.clear(); + static const GLenum colorAttachments[] = { + GL_COLOR_ATTACHMENT0, + GL_COLOR_ATTACHMENT1, + GL_COLOR_ATTACHMENT2, + GL_COLOR_ATTACHMENT3, + GL_COLOR_ATTACHMENT4, + GL_COLOR_ATTACHMENT5, + GL_COLOR_ATTACHMENT6, + GL_COLOR_ATTACHMENT7, + GL_COLOR_ATTACHMENT8, + GL_COLOR_ATTACHMENT9, + GL_COLOR_ATTACHMENT10, + GL_COLOR_ATTACHMENT11, + GL_COLOR_ATTACHMENT12, + GL_COLOR_ATTACHMENT13, + GL_COLOR_ATTACHMENT14, + GL_COLOR_ATTACHMENT15 }; - std::vector colorBuffers; - if (framebuffer.hasColor()) { - static const GLenum colorAttachments[] = { - GL_COLOR_ATTACHMENT0, - GL_COLOR_ATTACHMENT1, - GL_COLOR_ATTACHMENT2, - GL_COLOR_ATTACHMENT3, - GL_COLOR_ATTACHMENT4, - GL_COLOR_ATTACHMENT5, - GL_COLOR_ATTACHMENT6, - GL_COLOR_ATTACHMENT7, - GL_COLOR_ATTACHMENT8, - GL_COLOR_ATTACHMENT9, - GL_COLOR_ATTACHMENT10, - GL_COLOR_ATTACHMENT11, - GL_COLOR_ATTACHMENT12, - GL_COLOR_ATTACHMENT13, - GL_COLOR_ATTACHMENT14, - GL_COLOR_ATTACHMENT15 }; + int unit = 0; + for (auto& b : framebuffer.getRenderBuffers()) { + surface = b._texture; + if (surface) { + gltexture = GLBackend::syncGPUObject(*surface); + } else { + gltexture = nullptr; + } - int unit = 0; - for (auto& b : framebuffer.getRenderBuffers()) { - auto surface = b._texture; - if (surface) { - auto gltexture = GLBackend::syncGPUObject(*surface); if (gltexture) { glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, gltexture->_texture, 0); + object->_colorBuffers.push_back(colorAttachments[unit]); + } else { + glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, 0, 0); } - colorBuffers.push_back(colorAttachments[unit]); unit++; } } - } -#if (GPU_FEATURE_PROFILE == GPU_LEGACY) - // for reasons that i don't understand yet, it seems that on mac gl, a fbo must have a color buffer... - else { - GLuint renderBuffer = 0; - glGenRenderbuffers(1, &renderBuffer); - glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer); - glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, framebuffer.getWidth(), framebuffer.getHeight()); - glBindRenderbuffer(GL_RENDERBUFFER, 0); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer); - (void) CHECK_GL_ERROR(); - } - - // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); -#endif - - - if (framebuffer.hasDepthStencil()) { - auto surface = framebuffer.getDepthStencilBuffer(); - if (surface) { - auto gltexture = GLBackend::syncGPUObject(*surface); - if (gltexture) { - GLenum attachement = GL_DEPTH_STENCIL_ATTACHMENT; - if (!framebuffer.hasStencil()) { - attachement = GL_DEPTH_ATTACHMENT; - glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0); - } else if (!framebuffer.hasDepth()) { - attachement = GL_STENCIL_ATTACHMENT; - glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0); - } else { - attachement = GL_DEPTH_STENCIL_ATTACHMENT; - glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0); - } - (void) CHECK_GL_ERROR(); - } + #if (GPU_FEATURE_PROFILE == GPU_LEGACY) + // for reasons that i don't understand yet, it seems that on mac gl, a fbo must have a color buffer... + else { + GLuint renderBuffer = 0; + glGenRenderbuffers(1, &renderBuffer); + glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, framebuffer.getWidth(), framebuffer.getHeight()); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer); + (void) CHECK_GL_ERROR(); } + // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + #endif + object->_colorStamps = framebuffer.getColorStamps(); } + GLenum attachement = GL_DEPTH_STENCIL_ATTACHMENT; + if (!framebuffer.hasStencil()) { + attachement = GL_DEPTH_ATTACHMENT; + } else if (!framebuffer.hasDepth()) { + attachement = GL_STENCIL_ATTACHMENT; + } + + if (framebuffer.getDepthStamp() != object->_depthStamp) { + auto surface = framebuffer.getDepthStencilBuffer(); + if (framebuffer.hasDepthStencil() && surface) { + gltexture = GLBackend::syncGPUObject(*surface); + } + + if (gltexture) { + glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0); + } else { + glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, 0, 0); + } + object->_depthStamp = framebuffer.getDepthStamp(); + } + + // Last but not least, define where we draw - if (!colorBuffers.empty()) { - glDrawBuffers((GLsizei)colorBuffers.size(), colorBuffers.data()); + if (!object->_colorBuffers.empty()) { + glDrawBuffers((GLsizei)object->_colorBuffers.size(), object->_colorBuffers.data()); } else { glDrawBuffer( GL_NONE ); } // Now check for completness GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + // restore the current framebuffer + if (currentFBO != -1) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, currentFBO); + } + bool result = false; switch (status) { case GL_FRAMEBUFFER_COMPLETE : @@ -147,20 +172,10 @@ GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffe qCDebug(gpulogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED."; break; } - if (!result && fbo) { - glDeleteFramebuffers( 1, &fbo ); + if (!result && object->_fbo) { + glDeleteFramebuffers(1, &object->_fbo); return nullptr; } - - - // All is green, assign the gpuobject to the Framebuffer - object = new GLFramebuffer(); - object->_fbo = fbo; - object->_colorBuffers = colorBuffers; - Backend::setGPUObject(framebuffer, object); - - // restore the current framebuffer - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, currentFBO); } return object; diff --git a/libraries/model/src/model/Light.cpp b/libraries/model/src/model/Light.cpp index 7ab8b77f5e..8a016c4d77 100755 --- a/libraries/model/src/model/Light.cpp +++ b/libraries/model/src/model/Light.cpp @@ -12,19 +12,18 @@ using namespace model; -Light::Light() : - _flags(0), - _schemaBuffer(), - _transform() { +Light::Light() { // only if created from nothing shall we create the Buffer to store the properties Schema schema; _schemaBuffer = std::make_shared(sizeof(Schema), (const gpu::Byte*) &schema); + updateLightRadius(); } Light::Light(const Light& light) : _flags(light._flags), _schemaBuffer(light._schemaBuffer), - _transform(light._transform) { + _transform(light._transform) +{ } Light& Light::operator= (const Light& light) { @@ -70,18 +69,36 @@ void Light::setAmbientIntensity(float intensity) { editSchema()._ambientIntensity = intensity; } +void Light::setFalloffRadius(float radius) { + if (radius <= 0.0f) { + radius = 0.1f; + } + editSchema()._attenuation.x = radius; + updateLightRadius(); +} void Light::setMaximumRadius(float radius) { if (radius <= 0.f) { radius = 1.0f; } - editSchema()._attenuation.w = radius; + editSchema()._attenuation.y = radius; updateLightRadius(); } void Light::updateLightRadius() { - float CutOffIntensityRatio = 0.05f; - float surfaceRadius = getMaximumRadius() / (sqrtf((getIntensity() * std::max(std::max(getColor().x, getColor().y), getColor().z)) / CutOffIntensityRatio) - 1.0f); - editSchema()._attenuation = Vec4(surfaceRadius, 1.0f/surfaceRadius, CutOffIntensityRatio, getMaximumRadius()); + // This function relies on the attenuation equation: + // I = Li / (1 + (d + Lr)/Lr)^2 + // where I = calculated intensity, Li = light intensity, Lr = light falloff radius, d = distance from surface + // see: https://imdoingitwrong.wordpress.com/2011/01/31/light-attenuation/ + // note that falloff radius replaces surface radius in linked example + // This equation is biased back by Lr so that all lights act as true points, regardless of surface radii + + const float MIN_CUTOFF_INTENSITY = 0.001f; + // Get cutoff radius at minimum intensity + float intensity = getIntensity() * std::max(std::max(getColor().x, getColor().y), getColor().z); + float cutoffRadius = getFalloffRadius() * ((glm::sqrt(intensity / MIN_CUTOFF_INTENSITY) - 1) - 1); + + // If it is less than max radius, store it to buffer to avoid extra shading + editSchema()._attenuation.z = std::min(getMaximumRadius(), cutoffRadius); } #include diff --git a/libraries/model/src/model/Light.h b/libraries/model/src/model/Light.h index 96bf7fc427..dafbf90397 100755 --- a/libraries/model/src/model/Light.h +++ b/libraries/model/src/model/Light.h @@ -74,8 +74,17 @@ public: bool isRanged() const { return (getType() == POINT) || (getType() == SPOT ); } + // FalloffRradius is the physical radius of the light sphere through which energy shines, + // expressed in meters. It is used only to calculate the falloff curve of the light. + // Actual rendered lights will all have surface radii approaching 0. + void setFalloffRadius(float radius); + float getFalloffRadius() const { return getSchema()._attenuation.x; } + + // Maximum radius is the cutoff radius of the light energy, expressed in meters. + // It is used to bound light entities, and *will not* affect the falloff curve of the light. + // Setting it low will result in a noticeable cutoff. void setMaximumRadius(float radius); - float getMaximumRadius() const { return getSchema()._attenuation.w; } + float getMaximumRadius() const { return getSchema()._attenuation.y; } // Spot properties bool isSpot() const { return getType() == SPOT; } @@ -107,7 +116,7 @@ public: float _ambientIntensity{0.0f}; Color _color{1.0f}; float _intensity{1.0f}; - Vec4 _attenuation{1.0f}; + Vec4 _attenuation{0.1f, 1.0f, 0.0f, 0.0f}; Vec4 _spot{0.0f, 0.0f, 0.0f, 0.0f}; Vec4 _shadow{0.0f}; @@ -120,7 +129,7 @@ public: protected: - Flags _flags; + Flags _flags{ 0 }; UniformBufferView _schemaBuffer; Transform _transform; diff --git a/libraries/model/src/model/Light.slh b/libraries/model/src/model/Light.slh index 6529da1b61..af1c251ccb 100644 --- a/libraries/model/src/model/Light.slh +++ b/libraries/model/src/model/Light.slh @@ -66,14 +66,6 @@ vec3 getLightColor(Light l) { return l._color.rgb; } float getLightIntensity(Light l) { return l._color.w; } float getLightAmbientIntensity(Light l) { return l._direction.w; } -float evalLightAttenuation(Light l, float r) { - float d = max(r - l._attenuation.x, 0.0); - float denom = d * l._attenuation.y + 1.0; - float attenuation = 1.0 / (denom * denom); - return max((attenuation - l._attenuation.z)/(1.0 - l._attenuation.z), 0.0); - // return clamp(1.0/(l._attenuation.x + l._attenuation.y * r + l._attenuation.z * r * r), 0.0, 1.0); -} - float getLightSpotAngleCos(Light l) { return l._spot.x; } @@ -86,22 +78,33 @@ float evalLightSpotAttenuation(Light l, float cosA) { return pow(cosA, l._spot.w); } -float getLightSquareRadius(Light l) { - return l._attenuation.w * l._attenuation.w; -} - float getLightRadius(Light l) { - return l._attenuation.w; + return l._attenuation.x; } -float getLightAttenuationCutoff(Light l) { +float getLightSquareRadius(Light l) { + return getLightRadius(l) * getLightRadius(l); +} + +float getLightCutoffRadius(Light l) { return l._attenuation.z; } +float getLightCutoffSquareRadius(Light l) { + return getLightCutoffRadius(l) * getLightCutoffRadius(l); +} + float getLightShowContour(Light l) { return l._control.w; } +float evalLightAttenuation(Light l, float d) { + float radius = getLightRadius(l); + float denom = d / radius + 1.0; + float attenuation = min(1.0, 1.0 / (denom * denom)); + return attenuation; +} + SphericalHarmonics getLightAmbientSphere(Light l) { return l._ambientSphere; } diff --git a/libraries/model/src/model/Stage.cpp b/libraries/model/src/model/Stage.cpp index 6a1e95936b..c976e5c396 100644 --- a/libraries/model/src/model/Stage.cpp +++ b/libraries/model/src/model/Stage.cpp @@ -142,6 +142,8 @@ SunSkyStage::SunSkyStage() : _skybox(std::make_shared()) { _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); diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index d0838c4a8f..4ded2216d0 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -12,12 +12,10 @@ #include #include -#include #include #include #include #include -#include #include #include #include @@ -62,12 +60,13 @@ JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, co updateReciever(updateReceiver), updateSlot(updateSlot) { - } AccountManager::AccountManager() : _authURL(), - _pendingCallbackMap() + _pendingCallbackMap(), + _accountInfo(), + _shouldPersistToSettingsFile(true) { qRegisterMetaType("OAuthAccessToken"); qRegisterMetaTypeStreamOperators("OAuthAccessToken"); @@ -81,6 +80,9 @@ AccountManager::AccountManager() : qRegisterMetaType("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(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(); - - 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(); - - 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(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(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(); } diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 0ebefafbed..719279b0cf 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -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 _pendingCallbackMap; DataServerAccountInfo _accountInfo; - bool _isAgent { false }; - - bool _isWaitingForKeypairResponse { false }; - QByteArray _pendingPrivateKey; + bool _shouldPersistToSettingsFile; }; #endif // hifi_AccountManager_h diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index 9455fb1b88..5d633a8df1 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -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(&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(usernameWithToken.constData()), + usernameWithToken.size(), + reinterpret_cast(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(&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(hashedPlaintext.constData()), - hashedPlaintext.size(), - reinterpret_cast(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; } diff --git a/libraries/networking/src/DataServerAccountInfo.h b/libraries/networking/src/DataServerAccountInfo.h index 6223bc008e..9b80de5422 100644 --- a/libraries/networking/src/DataServerAccountInfo.h +++ b/libraries/networking/src/DataServerAccountInfo.h @@ -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; }; diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 34ca722537..db775983e1 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -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 mes emit icePeerSocketsReceived(); } } - -void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer 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; - } -} diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index b245305a93..f60ac2fbe6 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -92,7 +92,6 @@ public slots: void processICEPingReplyPacket(QSharedPointer message); void processDTLSRequirementPacket(QSharedPointer dtlsRequirementPacket); void processICEResponsePacket(QSharedPointer icePacket); - void processDomainServerConnectionDeniedPacket(QSharedPointer 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 diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index f236f9d596..a3707d19ba 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -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); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index de110c7e7f..fcad23da8f 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -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 constructICEPingPacket(PingType_t pingType, const QUuid& iceID); std::unique_ptr 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); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 02bb17e870..677a1ad1e6 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -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; + } } } diff --git a/libraries/networking/src/RSAKeypairGenerator.cpp b/libraries/networking/src/RSAKeypairGenerator.cpp index a98cf74564..53b9b27cc6 100644 --- a/libraries/networking/src/RSAKeypairGenerator.cpp +++ b/libraries/networking/src/RSAKeypairGenerator.cpp @@ -85,12 +85,12 @@ void RSAKeypairGenerator::generateKeypair() { // we can cleanup the RSA struct before we continue on RSA_free(keyPair); - _publicKey = QByteArray { reinterpret_cast(publicKeyDER), publicKeyLength }; - _privateKey = QByteArray { reinterpret_cast(privateKeyDER), privateKeyLength }; + QByteArray publicKeyArray(reinterpret_cast(publicKeyDER), publicKeyLength); + QByteArray privateKeyArray(reinterpret_cast(privateKeyDER), privateKeyLength); // cleanup the publicKeyDER and publicKeyDER data OPENSSL_free(publicKeyDER); OPENSSL_free(privateKeyDER); - emit generatedKeypair(); + emit generatedKeypair(publicKeyArray, privateKeyArray); } diff --git a/libraries/networking/src/RSAKeypairGenerator.h b/libraries/networking/src/RSAKeypairGenerator.h index 36f4a9550b..dd90313625 100644 --- a/libraries/networking/src/RSAKeypairGenerator.h +++ b/libraries/networking/src/RSAKeypairGenerator.h @@ -12,31 +12,17 @@ #ifndef hifi_RSAKeypairGenerator_h #define hifi_RSAKeypairGenerator_h -#include -#include +#include 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 \ No newline at end of file diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 01bccdfdba..88fabf1a5a 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -30,7 +30,7 @@ const QSet NON_SOURCED_PACKETS = QSet() << 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(AvatarMixerPacketVersion::SoftAttachmentSupport); - case PacketType::ICEServerHeartbeat: - return 18; // ICE Server Heartbeat signing default: return 17; } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index ca04b9cae4..0f586018db 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -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, diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index a685c2580c..8959bd4aa7 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -937,7 +937,7 @@ int Octree::encodeTreeBitstream(OctreeElementPointer element, params.stats->traversed(element); } - ViewFrustum::location parentLocationThisView = ViewFrustum::INTERSECT; // assume parent is in view, but not fully + ViewFrustum::intersection parentLocationThisView = ViewFrustum::INTERSECT; // assume parent is in view, but not fully int childBytesWritten = encodeTreeBitstreamRecursion(element, packetData, bag, params, currentEncodeLevel, parentLocationThisView); @@ -974,7 +974,7 @@ int Octree::encodeTreeBitstream(OctreeElementPointer element, int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, OctreePacketData* packetData, OctreeElementBag& bag, EncodeBitstreamParams& params, int& currentEncodeLevel, - const ViewFrustum::location& parentLocationThisView) const { + const ViewFrustum::intersection& parentLocationThisView) const { const bool wantDebug = false; @@ -1013,7 +1013,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, } } - ViewFrustum::location nodeLocationThisView = ViewFrustum::INSIDE; // assume we're inside + ViewFrustum::intersection nodeLocationThisView = ViewFrustum::INSIDE; // assume we're inside // caller can pass NULL as viewFrustum if they want everything if (params.viewFrustum) { @@ -1034,7 +1034,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, // if we are INSIDE, INTERSECT, or OUTSIDE if (parentLocationThisView != ViewFrustum::INSIDE) { assert(parentLocationThisView != ViewFrustum::OUTSIDE); // we shouldn't be here if our parent was OUTSIDE! - nodeLocationThisView = element->inFrustum(*params.viewFrustum); + nodeLocationThisView = element->computeViewIntersection(*params.viewFrustum); } // If we're at a element that is out of view, then we can return, because no nodes below us will be in view! @@ -1053,7 +1053,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, bool wasInView = false; if (params.deltaViewFrustum && params.lastViewFrustum) { - ViewFrustum::location location = element->inFrustum(*params.lastViewFrustum); + ViewFrustum::intersection location = element->computeViewIntersection(*params.lastViewFrustum); // If we're a leaf, then either intersect or inside is considered "formerly in view" if (element->isLeaf()) { @@ -1237,7 +1237,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element, bool childWasInView = false; if (childElement && params.deltaViewFrustum && params.lastViewFrustum) { - ViewFrustum::location location = childElement->inFrustum(*params.lastViewFrustum); + ViewFrustum::intersection location = childElement->computeViewIntersection(*params.lastViewFrustum); // If we're a leaf, then either intersect or inside is considered "formerly in view" if (childElement->isLeaf()) { diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 0939ae37f6..81a721c54e 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -367,7 +367,7 @@ protected: int encodeTreeBitstreamRecursion(OctreeElementPointer element, OctreePacketData* packetData, OctreeElementBag& bag, EncodeBitstreamParams& params, int& currentEncodeLevel, - const ViewFrustum::location& parentLocationThisView) const; + const ViewFrustum::intersection& parentLocationThisView) const; static bool countOctreeElementsOperation(OctreeElementPointer element, void* extraData); diff --git a/libraries/octree/src/OctreeElement.cpp b/libraries/octree/src/OctreeElement.cpp index f16e1dc88d..db0948b8b6 100644 --- a/libraries/octree/src/OctreeElement.cpp +++ b/libraries/octree/src/OctreeElement.cpp @@ -458,8 +458,8 @@ float OctreeElement::getEnclosingRadius() const { return getScale() * sqrtf(3.0f) / 2.0f; } -ViewFrustum::location OctreeElement::inFrustum(const ViewFrustum& viewFrustum) const { - return viewFrustum.cubeInFrustum(_cube); +ViewFrustum::intersection OctreeElement::computeViewIntersection(const ViewFrustum& viewFrustum) const { + return viewFrustum.calculateCubeKeyholeIntersection(_cube); } // There are two types of nodes for which we want to "render" diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h index 3c25ec0850..398f1827b7 100644 --- a/libraries/octree/src/OctreeElement.h +++ b/libraries/octree/src/OctreeElement.h @@ -49,20 +49,20 @@ protected: OctreeElement(); virtual OctreeElementPointer createNewElement(unsigned char * octalCode = NULL) = 0; - + public: virtual void init(unsigned char * octalCode); /// Your subclass must call init on construction. virtual ~OctreeElement(); // methods you can and should override to implement your tree functionality - + /// Adds a child to the current element. Override this if there is additional child initialization your class needs. virtual OctreeElementPointer addChildAtIndex(int childIndex); - /// Override this to implement LOD averaging on changes to the tree. + /// Override this to implement LOD averaging on changes to the tree. virtual void calculateAverageFromChildren() { } - /// Override this to implement LOD collapsing and identical child pruning on changes to the tree. + /// Override this to implement LOD collapsing and identical child pruning on changes to the tree. virtual bool collapseChildren() { return false; } /// Should this element be considered to have content in it. This will be used in collision and ray casting methods. @@ -72,12 +72,12 @@ public: /// Should this element be considered to have detailed content in it. Specifically should it be rendered. /// By default we assume that only leaves have detailed content, but some octrees may have different semantics. virtual bool hasDetailedContent() const { return isLeaf(); } - + /// Override this to break up large octree elements when an edit operation is performed on a smaller octree element. - /// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the + /// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the /// meaningful split would be to break the larger cube into smaller cubes of the same color/texture. virtual void splitChildren() { } - + /// Override to indicate that this element requires a split before editing lower elements in the octree virtual bool requiresSplit() const { return false; } @@ -88,17 +88,17 @@ public: virtual void initializeExtraEncodeData(EncodeBitstreamParams& params) { } virtual bool shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const { return true; } virtual bool shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const { return true; } - + virtual void updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const { } virtual void elementEncodeComplete(EncodeBitstreamParams& params) const { } /// Override to serialize the state of this element. This is used for persistance and for transmission across the network. - virtual AppendState appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const + virtual AppendState appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const { return COMPLETED; } - + /// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading /// from the network. - virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) + virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { return 0; } /// Override to indicate that the item is currently rendered in the rendering engine. By default we assume that if @@ -106,7 +106,7 @@ public: /// where an element is not actually rendering all should render elements. If the isRendered() state doesn't match the /// shouldRender() state, the tree will remark elements as changed even in cases there the elements have not changed. virtual bool isRendered() const { return getShouldRender(); } - + virtual bool deleteApproved() const { return true; } virtual bool canRayIntersect() const { return isLeaf(); } @@ -114,7 +114,7 @@ public: /// \param radius radius of sphere in meters /// \param[out] penetration pointing into cube from sphere /// \param penetratedObject unused - virtual bool findSpherePenetration(const glm::vec3& center, float radius, + virtual bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const; // Base class methods you don't need to implement @@ -125,7 +125,7 @@ public: bool isParentOf(OctreeElementPointer possibleChild) const; /// handles deletion of all descendants, returns false if delete not approved - bool safeDeepDeleteChildAtIndex(int childIndex, int recursionCount = 0); + bool safeDeepDeleteChildAtIndex(int childIndex, int recursionCount = 0); const AACube& getAACube() const { return _cube; } @@ -134,8 +134,8 @@ public: int getLevel() const { return numberOfThreeBitSectionsInCode(getOctalCode()) + 1; } float getEnclosingRadius() const; - bool isInView(const ViewFrustum& viewFrustum) const { return inFrustum(viewFrustum) != ViewFrustum::OUTSIDE; } - ViewFrustum::location inFrustum(const ViewFrustum& viewFrustum) const; + bool isInView(const ViewFrustum& viewFrustum) const { return computeViewIntersection(viewFrustum) != ViewFrustum::OUTSIDE; } + ViewFrustum::intersection computeViewIntersection(const ViewFrustum& viewFrustum) const; float distanceToCamera(const ViewFrustum& viewFrustum) const; float furthestDistanceToCamera(const ViewFrustum& viewFrustum) const; @@ -257,7 +257,7 @@ protected: static std::map _mapSourceUUIDsToKeys; static std::map _mapKeysToSourceUUIDs; - unsigned char _childBitmask; // 1 byte + unsigned char _childBitmask; // 1 byte bool _falseColored : 1, /// Client only, is this voxel false colored, 1 bit _isDirty : 1, /// Client only, has this voxel changed since being rendered, 1 bit diff --git a/libraries/octree/src/OctreeHeadlessViewer.cpp b/libraries/octree/src/OctreeHeadlessViewer.cpp index 547b3ac32b..33c12b1fe5 100644 --- a/libraries/octree/src/OctreeHeadlessViewer.cpp +++ b/libraries/octree/src/OctreeHeadlessViewer.cpp @@ -27,9 +27,9 @@ void OctreeHeadlessViewer::init() { void OctreeHeadlessViewer::queryOctree() { char serverType = getMyNodeType(); PacketType packetType = getMyQueryMessageType(); - + NodeToJurisdictionMap& jurisdictions = *_jurisdictionListener->getJurisdictions(); - + bool wantExtraDebugging = false; if (wantExtraDebugging) { @@ -52,7 +52,7 @@ void OctreeHeadlessViewer::queryOctree() { _octreeQuery.setCameraNearClip(_viewFrustum.getNearClip()); _octreeQuery.setCameraFarClip(_viewFrustum.getFarClip()); _octreeQuery.setCameraEyeOffsetPosition(glm::vec3()); - _octreeQuery.setKeyholeRadius(_viewFrustum.getKeyholeRadius()); + _octreeQuery.setCameraCenterRadius(_viewFrustum.getCenterRadius()); _octreeQuery.setOctreeSizeScale(_voxelSizeScale); _octreeQuery.setBoundaryLevelAdjust(_boundaryLevelAdjust); @@ -77,7 +77,7 @@ void OctreeHeadlessViewer::queryOctree() { if (jurisdictions.find(nodeUUID) == jurisdictions.end()) { unknownJurisdictionServers++; return; - } + } const JurisdictionMap& map = (jurisdictions)[nodeUUID]; unsigned char* rootCode = map.getRootOctalCode(); @@ -91,9 +91,7 @@ void OctreeHeadlessViewer::queryOctree() { if (foundRootDetails) { AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s); - ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds); - - if (serverFrustumLocation != ViewFrustum::OUTSIDE) { + if ((bool)(_viewFrustum.calculateCubeKeyholeIntersection(serverBounds))) { inViewServers++; } } @@ -164,13 +162,7 @@ void OctreeHeadlessViewer::queryOctree() { if (foundRootDetails) { AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s); - - ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds); - if (serverFrustumLocation != ViewFrustum::OUTSIDE) { - inView = true; - } else { - inView = false; - } + inView = (bool)(_viewFrustum.calculateCubeKeyholeIntersection(serverBounds)); } if (inView) { @@ -208,7 +200,7 @@ void OctreeHeadlessViewer::queryOctree() { // setup the query packet auto queryPacket = NLPacket::create(packetType); - + // read the data to our packet and set the payload size to fit the query int querySize = _octreeQuery.getBroadcastData(reinterpret_cast(queryPacket->getPayload())); queryPacket->setPayloadSize(querySize); diff --git a/libraries/octree/src/OctreeHeadlessViewer.h b/libraries/octree/src/OctreeHeadlessViewer.h index bdfebd82f6..780b68b848 100644 --- a/libraries/octree/src/OctreeHeadlessViewer.h +++ b/libraries/octree/src/OctreeHeadlessViewer.h @@ -46,7 +46,8 @@ public slots: // setters for camera attributes void setPosition(const glm::vec3& position) { _viewFrustum.setPosition(position); } void setOrientation(const glm::quat& orientation) { _viewFrustum.setOrientation(orientation); } - void setKeyholeRadius(float keyholdRadius) { _viewFrustum.setKeyholeRadius(keyholdRadius); } + void setCenterRadius(float radius) { _viewFrustum.setCenterRadius(radius); } + void setKeyholeRadius(float radius) { _viewFrustum.setCenterRadius(radius); } // TODO: remove this legacy support // setters for LOD and PPS void setVoxelSizeScale(float sizeScale) { _voxelSizeScale = sizeScale; } diff --git a/libraries/octree/src/OctreeQuery.cpp b/libraries/octree/src/OctreeQuery.cpp index 0e7f565c00..a69d88c693 100644 --- a/libraries/octree/src/OctreeQuery.cpp +++ b/libraries/octree/src/OctreeQuery.cpp @@ -64,8 +64,8 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) { memcpy(destinationBuffer, &_boundaryLevelAdjust, sizeof(_boundaryLevelAdjust)); destinationBuffer += sizeof(_boundaryLevelAdjust); - memcpy(destinationBuffer, &_keyholeRadius, sizeof(_keyholeRadius)); - destinationBuffer += sizeof(_keyholeRadius); + memcpy(destinationBuffer, &_cameraCenterRadius, sizeof(_cameraCenterRadius)); + destinationBuffer += sizeof(_cameraCenterRadius); return destinationBuffer - bufferStart; } @@ -109,9 +109,9 @@ int OctreeQuery::parseData(ReceivedMessage& message) { auto bytesRead = sourceBuffer - startPosition; auto bytesLeft = message.getSize() - bytesRead; - if (bytesLeft >= (int)sizeof(_keyholeRadius)) { - memcpy(&_keyholeRadius, sourceBuffer, sizeof(_keyholeRadius)); - sourceBuffer += sizeof(_keyholeRadius); + if (bytesLeft >= (int)sizeof(_cameraCenterRadius)) { + memcpy(&_cameraCenterRadius, sourceBuffer, sizeof(_cameraCenterRadius)); + sourceBuffer += sizeof(_cameraCenterRadius); } return sourceBuffer - startPosition; } diff --git a/libraries/octree/src/OctreeQuery.h b/libraries/octree/src/OctreeQuery.h index 7e157c4252..e446e1abc7 100644 --- a/libraries/octree/src/OctreeQuery.h +++ b/libraries/octree/src/OctreeQuery.h @@ -58,7 +58,7 @@ public: float getCameraNearClip() const { return _cameraNearClip; } float getCameraFarClip() const { return _cameraFarClip; } const glm::vec3& getCameraEyeOffsetPosition() const { return _cameraEyeOffsetPosition; } - float getKeyholeRadius() const { return _keyholeRadius; } + float getCameraCenterRadius() const { return _cameraCenterRadius; } glm::vec3 calculateCameraDirection() const; @@ -70,7 +70,7 @@ public: void setCameraNearClip(float nearClip) { _cameraNearClip = nearClip; } void setCameraFarClip(float farClip) { _cameraFarClip = farClip; } void setCameraEyeOffsetPosition(const glm::vec3& eyeOffsetPosition) { _cameraEyeOffsetPosition = eyeOffsetPosition; } - void setKeyholeRadius(float keyholeRadius) { _keyholeRadius = keyholeRadius; } + void setCameraCenterRadius(float radius) { _cameraCenterRadius = radius; } // related to Octree Sending strategies int getMaxQueryPacketsPerSecond() const { return _maxQueryPPS; } @@ -90,7 +90,7 @@ protected: float _cameraAspectRatio = 1.0f; float _cameraNearClip = 0.0f; float _cameraFarClip = 0.0f; - float _keyholeRadius { 0.0f }; + float _cameraCenterRadius { 0.0f }; glm::vec3 _cameraEyeOffsetPosition = glm::vec3(0.0f); // octree server sending items diff --git a/libraries/octree/src/ViewFrustum.cpp b/libraries/octree/src/ViewFrustum.cpp index 4c07a0c784..228e4c7d35 100644 --- a/libraries/octree/src/ViewFrustum.cpp +++ b/libraries/octree/src/ViewFrustum.cpp @@ -115,10 +115,6 @@ void ViewFrustum::calculate() { // Our ModelViewProjection : multiplication of our 3 matrices (note: model is identity, so we can drop it) _ourModelViewProjectionMatrix = _projection * view; // Remember, matrix multiplication is the other way around - - // Set up our keyhole bounding box... - glm::vec3 corner = _position - _keyholeRadius; - _keyholeBoundingCube = AACube(corner,(_keyholeRadius * 2.0f)); } //enum { TOP_PLANE = 0, BOTTOM_PLANE, LEFT_PLANE, RIGHT_PLANE, NEAR_PLANE, FAR_PLANE }; @@ -134,231 +130,129 @@ const char* ViewFrustum::debugPlaneName (int plane) const { return "Unknown"; } -ViewFrustum::location ViewFrustum::pointInKeyhole(const glm::vec3& point) const { - - ViewFrustum::location result = INTERSECT; - - float distance = glm::distance(point, _position); - if (distance > _keyholeRadius) { - result = OUTSIDE; - } else if (distance < _keyholeRadius) { - result = INSIDE; - } - - return result; -} - -// To determine if two spheres intersect, simply calculate the distance between the centers of the two spheres. -// If the distance is greater than the sum of the two sphere radii, they don’t intersect. Otherwise they intersect. -// If the distance plus the radius of sphere A is less than the radius of sphere B then, sphere A is inside of sphere B -ViewFrustum::location ViewFrustum::sphereInKeyhole(const glm::vec3& center, float radius) const { - ViewFrustum::location result = INTERSECT; - - float distance = glm::distance(center, _position); - if (distance > (radius + _keyholeRadius)) { - result = OUTSIDE; - } else if ((distance + radius) < _keyholeRadius) { - result = INSIDE; - } - - return result; -} - - -// A box is inside a sphere if all of its corners are inside the sphere -// A box intersects a sphere if any of its edges (as rays) interesect the sphere -// A box is outside a sphere if none of its edges (as rays) interesect the sphere -ViewFrustum::location ViewFrustum::cubeInKeyhole(const AACube& cube) const { - - // First check to see if the cube is in the bounding cube for the sphere, if it's not, then we can short circuit - // this and not check with sphere penetration which is more expensive - if (!_keyholeBoundingCube.contains(cube)) { - return OUTSIDE; - } - - glm::vec3 penetration; - bool intersects = cube.findSpherePenetration(_position, _keyholeRadius, penetration); - - ViewFrustum::location result = OUTSIDE; - - // if the cube intersects the sphere, then it may also be inside... calculate further - if (intersects) { - result = INTERSECT; - - // test all the corners, if they are all inside the sphere, the entire cube is in the sphere - bool allPointsInside = true; // assume the best - for (int v = BOTTOM_LEFT_NEAR; v < TOP_LEFT_FAR; v++) { - glm::vec3 vertex = cube.getVertex((BoxVertex)v); - if (!pointInKeyhole(vertex)) { - allPointsInside = false; - break; +ViewFrustum::intersection ViewFrustum::calculateCubeFrustumIntersection(const AACube& cube) const { + // only check against frustum + ViewFrustum::intersection result = INSIDE; + for(int i=0; i < 6; i++) { + const glm::vec3& normal = _planes[i].getNormal(); + // check distance to farthest cube point + if ( _planes[i].distance(cube.getFarthestVertex(normal)) < 0.0f) { + return OUTSIDE; + } else { + // check distance to nearest cube point + if (_planes[i].distance(cube.getNearestVertex(normal)) < 0.0f) { + // cube straddles the plane + result = INTERSECT; } } - - if (allPointsInside) { - result = INSIDE; - } } - return result; } -// A box is inside a sphere if all of its corners are inside the sphere -// A box intersects a sphere if any of its edges (as rays) interesect the sphere -// A box is outside a sphere if none of its edges (as rays) interesect the sphere -ViewFrustum::location ViewFrustum::boxInKeyhole(const AABox& box) const { +const float HALF_SQRT_THREE = 0.8660254f; - // First check to see if the box is in the bounding box for the sphere, if it's not, then we can short circuit - // this and not check with sphere penetration which is more expensive - if (!_keyholeBoundingCube.contains(box)) { - return OUTSIDE; +ViewFrustum::intersection ViewFrustum::calculateCubeKeyholeIntersection(const AACube& cube) const { + // check against centeral sphere + ViewFrustum::intersection sphereResult = INTERSECT; + glm::vec3 cubeOffset = cube.calcCenter() - _position; + float distance = glm::length(cubeOffset); + if (distance > EPSILON) { + glm::vec3 vertex = cube.getFarthestVertex(cubeOffset) - _position; + if (glm::dot(vertex, cubeOffset) < _centerSphereRadius * distance) { + // the most outward cube vertex is inside central sphere + return INSIDE; + } + if (!cube.touchesSphere(_position, _centerSphereRadius)) { + sphereResult = OUTSIDE; + } + } else if (_centerSphereRadius > HALF_SQRT_THREE * cube.getScale()) { + // the cube is in center of sphere and its bounding radius is inside + return INSIDE; } - glm::vec3 penetration; - bool intersects = box.findSpherePenetration(_position, _keyholeRadius, penetration); + // check against frustum + ViewFrustum::intersection frustumResult = calculateCubeFrustumIntersection(cube); - ViewFrustum::location result = OUTSIDE; - - // if the box intersects the sphere, then it may also be inside... calculate further - if (intersects) { - result = INTERSECT; - - // test all the corners, if they are all inside the sphere, the entire box is in the sphere - bool allPointsInside = true; // assume the best - for (int v = BOTTOM_LEFT_NEAR; v < TOP_LEFT_FAR; v++) { - glm::vec3 vertex = box.getVertex((BoxVertex)v); - if (!pointInKeyhole(vertex)) { - allPointsInside = false; - break; - } - } - - if (allPointsInside) { - result = INSIDE; - } - } - - return result; + return (frustumResult == OUTSIDE) ? sphereResult : frustumResult; } -ViewFrustum::location ViewFrustum::pointInFrustum(const glm::vec3& point, bool ignoreKeyhole) const { - ViewFrustum::location regularResult = INSIDE; - ViewFrustum::location keyholeResult = OUTSIDE; - - // If we have a keyholeRadius, check that first, since it's cheaper - if (!ignoreKeyhole && _keyholeRadius >= 0.0f) { - keyholeResult = pointInKeyhole(point); - - if (keyholeResult == INSIDE) { - return keyholeResult; - } - } - - // If we're not known to be INSIDE the keyhole, then check the regular frustum +bool ViewFrustum::pointIntersectsFrustum(const glm::vec3& point) const { + // only check against frustum for(int i = 0; i < 6; ++i) { float distance = _planes[i].distance(point); - if (distance < 0) { - return keyholeResult; // escape early will be the value from checking the keyhole + if (distance < 0.0f) { + return false; } } - return regularResult; + return true; } -ViewFrustum::location ViewFrustum::sphereInFrustum(const glm::vec3& center, float radius) const { - ViewFrustum::location regularResult = INSIDE; - ViewFrustum::location keyholeResult = OUTSIDE; - - // If we have a keyholeRadius, check that first, since it's cheaper - if (_keyholeRadius >= 0.0f) { - keyholeResult = sphereInKeyhole(center, radius); - } - if (keyholeResult == INSIDE) { - return keyholeResult; - } - - float distance; +bool ViewFrustum::sphereIntersectsFrustum(const glm::vec3& center, float radius) const { + // only check against frustum for(int i=0; i < 6; i++) { - distance = _planes[i].distance(center); + float distance = _planes[i].distance(center); if (distance < -radius) { // This is outside the regular frustum, so just return the value from checking the keyhole - return keyholeResult; - } else if (distance < radius) { - regularResult = INTERSECT; + return false; } } - - return regularResult; + return true; } - -ViewFrustum::location ViewFrustum::cubeInFrustum(const AACube& cube) const { - - ViewFrustum::location regularResult = INSIDE; - ViewFrustum::location keyholeResult = OUTSIDE; - - // If we have a keyholeRadius, check that first, since it's cheaper - if (_keyholeRadius >= 0.0f) { - keyholeResult = cubeInKeyhole(cube); - } - if (keyholeResult == INSIDE) { - return keyholeResult; - } - - // TODO: These calculations are expensive, taking up 80% of our time in this function. - // This appears to be expensive because we have to test the distance to each plane. - // One suggested optimization is to first check against the approximated cone. We might - // also be able to test against the cone to the bounding sphere of the box. +bool ViewFrustum::boxIntersectsFrustum(const AABox& box) const { + // only check against frustum for(int i=0; i < 6; i++) { const glm::vec3& normal = _planes[i].getNormal(); - const glm::vec3& boxVertexP = cube.getVertexP(normal); - float planeToBoxVertexPDistance = _planes[i].distance(boxVertexP); - - const glm::vec3& boxVertexN = cube.getVertexN(normal); - float planeToBoxVertexNDistance = _planes[i].distance(boxVertexN); - - if (planeToBoxVertexPDistance < 0) { - // This is outside the regular frustum, so just return the value from checking the keyhole - return keyholeResult; - } else if (planeToBoxVertexNDistance < 0) { - regularResult = INTERSECT; + // check distance to farthest box point + if ( _planes[i].distance(box.getFarthestVertex(normal)) < 0.0f) { + return false; } } - return regularResult; + return true; } -ViewFrustum::location ViewFrustum::boxInFrustum(const AABox& box) const { - - ViewFrustum::location regularResult = INSIDE; - ViewFrustum::location keyholeResult = OUTSIDE; - - // If we have a keyholeRadius, check that first, since it's cheaper - if (_keyholeRadius >= 0.0f) { - keyholeResult = boxInKeyhole(box); +bool ViewFrustum::sphereIntersectsKeyhole(const glm::vec3& center, float radius) const { + // check positive touch against central sphere + if (glm::length(center - _position) <= (radius + _centerSphereRadius)) { + return true; } - if (keyholeResult == INSIDE) { - return keyholeResult; - } - - // TODO: These calculations are expensive, taking up 80% of our time in this function. - // This appears to be expensive because we have to test the distance to each plane. - // One suggested optimization is to first check against the approximated cone. We might - // also be able to test against the cone to the bounding sphere of the box. + // check negative touches against frustum planes for(int i=0; i < 6; i++) { - const glm::vec3& normal = _planes[i].getNormal(); - const glm::vec3& boxVertexP = box.getVertexP(normal); - float planeToBoxVertexPDistance = _planes[i].distance(boxVertexP); - - const glm::vec3& boxVertexN = box.getVertexN(normal); - float planeToBoxVertexNDistance = _planes[i].distance(boxVertexN); - - if (planeToBoxVertexPDistance < 0) { - // This is outside the regular frustum, so just return the value from checking the keyhole - return keyholeResult; - } else if (planeToBoxVertexNDistance < 0) { - regularResult = INTERSECT; + if ( _planes[i].distance(center) < -radius) { + return false; } } - return regularResult; + return true; +} + +bool ViewFrustum::cubeIntersectsKeyhole(const AACube& cube) const { + // check positive touch against central sphere + if (cube.touchesSphere(_position, _centerSphereRadius)) { + return true; + } + // check negative touches against frustum planes + for(int i=0; i < 6; i++) { + const glm::vec3& normal = _planes[i].getNormal(); + if ( _planes[i].distance(cube.getFarthestVertex(normal)) < 0.0f) { + return false; + } + } + return true; +} + +bool ViewFrustum::boxIntersectsKeyhole(const AABox& box) const { + // check positive touch against central sphere + if (box.touchesSphere(_position, _centerSphereRadius)) { + return true; + } + // check negative touches against frustum planes + for(int i=0; i < 6; i++) { + const glm::vec3& normal = _planes[i].getNormal(); + if ( _planes[i].distance(box.getFarthestVertex(normal)) < 0.0f) { + return false; + } + } + return true; } bool testMatches(glm::quat lhs, glm::quat rhs, float epsilon = EPSILON) { @@ -490,7 +384,7 @@ PickRay ViewFrustum::computePickRay(float x, float y) { } void ViewFrustum::computePickRay(float x, float y, glm::vec3& origin, glm::vec3& direction) const { - origin = _cornersWorld[TOP_LEFT_NEAR] + x * (_cornersWorld[TOP_RIGHT_NEAR] - _cornersWorld[TOP_LEFT_NEAR]) + + origin = _cornersWorld[TOP_LEFT_NEAR] + x * (_cornersWorld[TOP_RIGHT_NEAR] - _cornersWorld[TOP_LEFT_NEAR]) + y * (_cornersWorld[BOTTOM_LEFT_NEAR] - _cornersWorld[TOP_LEFT_NEAR]); direction = glm::normalize(origin - _position); } @@ -540,7 +434,7 @@ void ViewFrustum::printDebugDetails() const { qCDebug(octree, "_right=%f,%f,%f", (double)_right.x, (double)_right.y, (double)_right.z ); qCDebug(octree, "_fieldOfView=%f", (double)_fieldOfView); qCDebug(octree, "_aspectRatio=%f", (double)_aspectRatio); - qCDebug(octree, "_keyHoleRadius=%f", (double)_keyholeRadius); + qCDebug(octree, "_centerSphereRadius=%f", (double)_centerSphereRadius); qCDebug(octree, "_nearClip=%f", (double)_nearClip); qCDebug(octree, "_farClip=%f", (double)_farClip); qCDebug(octree, "_focalLength=%f", (double)_focalLength); @@ -804,7 +698,7 @@ float ViewFrustum::calculateRenderAccuracy(const AABox& bounds, float octreeSize // FIXME - for now, it's either visible or not visible. We want to adjust this to eventually return // a floating point for objects that have small angular size to indicate that they may be rendered // with lower preciscion - return (distanceToCamera <= visibleDistanceAtClosestScale) ? 1.0f : 0.0f; + return (distanceToCamera <= visibleDistanceAtClosestScale) ? 1.0f : 0.0f; } float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale) { diff --git a/libraries/octree/src/ViewFrustum.h b/libraries/octree/src/ViewFrustum.h index 89c632df8c..412d0f82f2 100644 --- a/libraries/octree/src/ViewFrustum.h +++ b/libraries/octree/src/ViewFrustum.h @@ -27,12 +27,15 @@ #include "OctreeConstants.h" #include "OctreeProjectedPolygon.h" -const float DEFAULT_KEYHOLE_RADIUS = 3.0f; +const float DEFAULT_CENTER_SPHERE_RADIUS = 3.0f; const float DEFAULT_FIELD_OF_VIEW_DEGREES = 45.0f; const float DEFAULT_ASPECT_RATIO = 16.0f/9.0f; const float DEFAULT_NEAR_CLIP = 0.08f; const float DEFAULT_FAR_CLIP = (float)HALF_TREE_SCALE; +// the "ViewFrustum" has a "keyhole" shape: a regular frustum for stuff that is "visible" with +// a central sphere for stuff that is nearby (for physics simulation). + class ViewFrustum { public: // setters for camera attributes @@ -83,18 +86,26 @@ public: const glm::vec3& getNearBottomLeft() const { return _cornersWorld[BOTTOM_LEFT_NEAR]; } const glm::vec3& getNearBottomRight() const { return _cornersWorld[BOTTOM_RIGHT_NEAR]; } - // get/set for keyhole attribute - void setKeyholeRadius(float keyholdRadius) { _keyholeRadius = keyholdRadius; } - float getKeyholeRadius() const { return _keyholeRadius; } + // get/set for central spherek attribute + void setCenterRadius(float radius) { _centerSphereRadius = radius; } + float getCenterRadius() const { return _centerSphereRadius; } void calculate(); - typedef enum {OUTSIDE, INTERSECT, INSIDE} location; + typedef enum { OUTSIDE = 0, INTERSECT, INSIDE } intersection; - ViewFrustum::location pointInFrustum(const glm::vec3& point, bool ignoreKeyhole = false) const; - ViewFrustum::location sphereInFrustum(const glm::vec3& center, float radius) const; - ViewFrustum::location cubeInFrustum(const AACube& cube) const; - ViewFrustum::location boxInFrustum(const AABox& box) const; + /// @return INSIDE, INTERSECT, or OUTSIDE depending on how cube intersects the keyhole shape + ViewFrustum::intersection calculateCubeFrustumIntersection(const AACube& cube) const; + ViewFrustum::intersection calculateCubeKeyholeIntersection(const AACube& cube) const; + + bool pointIntersectsFrustum(const glm::vec3& point) const; + bool sphereIntersectsFrustum(const glm::vec3& center, float radius) const; + bool cubeIntersectsFrustum(const AACube& box) const; + bool boxIntersectsFrustum(const AABox& box) const; + + bool sphereIntersectsKeyhole(const glm::vec3& center, float radius) const; + bool cubeIntersectsKeyhole(const AACube& cube) const; + bool boxIntersectsKeyhole(const AABox& box) const; // some frustum comparisons bool matches(const ViewFrustum& compareTo, bool debug = false) const; @@ -114,15 +125,15 @@ public: glm::vec2 projectPoint(glm::vec3 point, bool& pointInView) const; OctreeProjectedPolygon getProjectedPolygon(const AACube& box) const; void getFurthestPointFromCamera(const AACube& box, glm::vec3& furthestPoint) const; - + float distanceToCamera(const glm::vec3& point) const; - + void evalProjectionMatrix(glm::mat4& proj) const; void evalViewTransform(Transform& view) const; /// renderAccuracy represents a floating point "visibility" of an object based on it's view from the camera. At a simple /// level it returns 0.0f for things that are so small for the current settings that they could not be visible. - float calculateRenderAccuracy(const AABox& bounds, float octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE, + float calculateRenderAccuracy(const AABox& bounds, float octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE, int boundaryLevelAdjust = 0) const; float getAccuracyAngle(float octreeSizeScale = DEFAULT_OCTREE_SIZE_SCALE, int boundaryLevelAdjust = 0) const; @@ -131,12 +142,6 @@ public: const ::Plane* getPlanes() const { return _planes; } private: - // Used for keyhole calculations - ViewFrustum::location pointInKeyhole(const glm::vec3& point) const; - ViewFrustum::location sphereInKeyhole(const glm::vec3& center, float radius) const; - ViewFrustum::location cubeInKeyhole(const AACube& cube) const; - ViewFrustum::location boxInKeyhole(const AABox& box) const; - // camera location/orientation attributes glm::vec3 _position; // the position in world-frame glm::quat _orientation; @@ -150,9 +155,7 @@ private: glm::vec3 _up = IDENTITY_UP; glm::vec3 _right = IDENTITY_RIGHT; - // keyhole attributes - float _keyholeRadius = DEFAULT_KEYHOLE_RADIUS; - AACube _keyholeBoundingCube; + float _centerSphereRadius = DEFAULT_CENTER_SPHERE_RADIUS; // Calculated values glm::mat4 _inverseProjection; @@ -165,7 +168,7 @@ private: float _fieldOfView = DEFAULT_FIELD_OF_VIEW_DEGREES; glm::vec4 _corners[8]; glm::vec3 _cornersWorld[8]; - ::Plane _planes[6]; // How will this be used? + ::Plane _planes[6]; // plane normals point inside frustum const char* debugPlaneName (int plane) const; diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 8c0dab98db..6a818a1972 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -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(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY)); _nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS; diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index fba1c096d4..c1c7240f20 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -87,23 +87,20 @@ void DeferredLightingEffect::init() { _allocatedLights.push_back(std::make_shared()); 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; } diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index 2c33944606..75cefc277d 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -36,11 +36,12 @@ public: /// Adds a point light to render for the current frame. void addPointLight(const glm::vec3& position, float radius, const glm::vec3& color = glm::vec3(0.0f, 0.0f, 0.0f), - float intensity = 0.5f); + float intensity = 0.5f, float falloffRadius = 0.01f); /// Adds a spot light to render for the current frame. void addSpotLight(const glm::vec3& position, float radius, const glm::vec3& color = glm::vec3(1.0f, 1.0f, 1.0f), - float intensity = 0.5f, const glm::quat& orientation = glm::quat(), float exponent = 0.0f, float cutoff = PI); + float intensity = 0.5f, float falloffRadius = 0.01f, + const glm::quat& orientation = glm::quat(), float exponent = 0.0f, float cutoff = PI); void prepare(RenderArgs* args); void render(const render::RenderContextPointer& renderContext); diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index c0003219a0..e5a313e3b1 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -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()->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; diff --git a/libraries/render-utils/src/point_light.slf b/libraries/render-utils/src/point_light.slf index fcfb0b336e..a64d4a81a8 100644 --- a/libraries/render-utils/src/point_light.slf +++ b/libraries/render-utils/src/point_light.slf @@ -48,7 +48,7 @@ void main(void) { vec3 fragLightVec = getLightPosition(light) - fragPos.xyz; // Kill if too far from the light center - if (dot(fragLightVec, fragLightVec) > getLightSquareRadius(light)) { + if (dot(fragLightVec, fragLightVec) > getLightCutoffSquareRadius(light)) { discard; } diff --git a/libraries/render-utils/src/spot_light.slf b/libraries/render-utils/src/spot_light.slf index 8170929636..b72598f810 100644 --- a/libraries/render-utils/src/spot_light.slf +++ b/libraries/render-utils/src/spot_light.slf @@ -47,7 +47,7 @@ void main(void) { vec3 fragLightVec = getLightPosition(light) - fragPos.xyz; // Kill if too far from the light center - if (dot(fragLightVec, fragLightVec) > getLightSquareRadius(light)) { + if (dot(fragLightVec, fragLightVec) > getLightCutoffSquareRadius(light)) { discard; } diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index 3fc6bffbd3..7824514649 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -29,7 +29,7 @@ void render::cullItems(const RenderContextPointer& renderContext, const CullFunc ViewFrustum* frustum = args->_viewFrustum; details._considered += (int)inItems.size(); - + // Culling / LOD for (auto item : inItems) { if (item.bound.isNull()) { @@ -39,12 +39,12 @@ void render::cullItems(const RenderContextPointer& renderContext, const CullFunc // TODO: some entity types (like lights) might want to be rendered even // when they are outside of the view frustum... - bool outOfView; + bool inView; { - PerformanceTimer perfTimer("boxInFrustum"); - outOfView = frustum->boxInFrustum(item.bound) == ViewFrustum::OUTSIDE; + PerformanceTimer perfTimer("boxIntersectsFrustum"); + inView = frustum->boxIntersectsFrustum(item.bound); } - if (!outOfView) { + if (inView) { bool bigEnoughToRender; { PerformanceTimer perfTimer("shouldRender"); @@ -88,10 +88,10 @@ struct BackToFrontSort { void render::depthSortItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, bool frontToBack, const ItemBounds& inItems, ItemBounds& outItems) { assert(renderContext->args); assert(renderContext->args->_viewFrustum); - + auto& scene = sceneContext->_scene; RenderArgs* args = renderContext->args; - + // Allocate and simply copy outItems.clear(); @@ -238,7 +238,7 @@ void CullSpatialSelection::run(const SceneContextPointer& sceneContext, const Re } bool frustumTest(const AABox& bound) { - if (_args->_viewFrustum->boxInFrustum(bound) == ViewFrustum::OUTSIDE) { + if (!_args->_viewFrustum->boxIntersectsFrustum(bound)) { _renderDetails._outOfView++; return false; } diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index d16bdafcec..0b453e6f36 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -75,32 +75,32 @@ void AABox::setBox(const glm::vec3& corner, const glm::vec3& scale) { _scale = scale; } -glm::vec3 AABox::getVertexP(const glm::vec3& normal) const { +glm::vec3 AABox::getFarthestVertex(const glm::vec3& normal) const { glm::vec3 result = _corner; - if (normal.x > 0) { + if (normal.x > 0.0f) { result.x += _scale.x; } - if (normal.y > 0) { + if (normal.y > 0.0f) { result.y += _scale.y; } - if (normal.z > 0) { + if (normal.z > 0.0f) { result.z += _scale.z; } return result; } -glm::vec3 AABox::getVertexN(const glm::vec3& normal) const { +glm::vec3 AABox::getNearestVertex(const glm::vec3& normal) const { glm::vec3 result = _corner; - if (normal.x < 0) { + if (normal.x < 0.0f) { result.x += _scale.x; } - if (normal.y < 0) { + if (normal.y < 0.0f) { result.y += _scale.y; } - if (normal.z < 0) { + if (normal.z < 0.0f) { result.z += _scale.z; } @@ -217,7 +217,7 @@ bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& e isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x)); } -bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, +bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) const { // handle the trivial case where the box contains the origin if (contains(origin)) { @@ -281,6 +281,12 @@ bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direct return false; } +bool AABox::touchesSphere(const glm::vec3& center, float radius) const { + // Avro's algorithm from this paper: http://www.mrtc.mdh.se/projects/3Dgraphics/paperF.pdf + glm::vec3 e = glm::max(_corner - center, Vectors::ZERO) + glm::max(center - _corner - _scale, Vectors::ZERO); + return glm::length2(e) <= radius * radius; +} + bool AABox::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const { glm::vec4 center4 = glm::vec4(center, 1.0f); @@ -537,4 +543,4 @@ void AABox::transform(const Transform& transform) { scale(transform.getScale()); rotate(transform.getRotation()); translate(transform.getTranslation()); -} \ No newline at end of file +} diff --git a/libraries/shared/src/AABox.h b/libraries/shared/src/AABox.h index 24791445b3..ec06c60121 100644 --- a/libraries/shared/src/AABox.h +++ b/libraries/shared/src/AABox.h @@ -35,12 +35,12 @@ public: AABox(const glm::vec3& corner, const glm::vec3& dimensions); AABox(); ~AABox() {}; - + void setBox(const glm::vec3& corner, const glm::vec3& scale); void setBox(const glm::vec3& corner, float scale); - glm::vec3 getVertexP(const glm::vec3& normal) const; - glm::vec3 getVertexN(const glm::vec3& normal) const; + glm::vec3 getFarthestVertex(const glm::vec3& normal) const; // return vertex most parallel to normal + glm::vec3 getNearestVertex(const glm::vec3& normal) const; // return vertex most anti-parallel to normal const glm::vec3& getCorner() const { return _corner; } const glm::vec3& getScale() const { return _scale; } @@ -68,11 +68,12 @@ public: bool expandedContains(const glm::vec3& point, float expansion) const; bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const; - bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) const; + bool touchesSphere(const glm::vec3& center, float radius) const; // fast but may generate false positives bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const; bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const; - + bool isNull() const { return _scale == glm::vec3(0.0f, 0.0f, 0.0f); } AABox clamp(const glm::vec3& min, const glm::vec3& max) const; @@ -113,7 +114,7 @@ inline bool operator==(const AABox& a, const AABox& b) { } inline QDebug operator<<(QDebug debug, const AABox& box) { - debug << "AABox[ (" + debug << "AABox[ (" << box.getCorner().x << "," << box.getCorner().y << "," << box.getCorner().z << " ) to (" << box.calcTopFarLeft().x << "," << box.calcTopFarLeft().y << "," << box.calcTopFarLeft().z << ") size: (" << box.getDimensions().x << "," << box.getDimensions().y << "," << box.getDimensions().z << ")" diff --git a/libraries/shared/src/AACube.cpp b/libraries/shared/src/AACube.cpp index bc97a6ff69..f8122ea4dc 100644 --- a/libraries/shared/src/AACube.cpp +++ b/libraries/shared/src/AACube.cpp @@ -79,32 +79,32 @@ void AACube::setBox(const glm::vec3& corner, float scale) { _scale = scale; } -glm::vec3 AACube::getVertexP(const glm::vec3& normal) const { +glm::vec3 AACube::getFarthestVertex(const glm::vec3& normal) const { glm::vec3 result = _corner; - if (normal.x > 0) { + if (normal.x > 0.0f) { result.x += _scale; } - if (normal.y > 0) { + if (normal.y > 0.0f) { result.y += _scale; } - if (normal.z > 0) { + if (normal.z > 0.0f) { result.z += _scale; } return result; } -glm::vec3 AACube::getVertexN(const glm::vec3& normal) const { +glm::vec3 AACube::getNearestVertex(const glm::vec3& normal) const { glm::vec3 result = _corner; - if (normal.x < 0) { + if (normal.x < 0.0f) { result.x += _scale; } - if (normal.y < 0) { + if (normal.y < 0.0f) { result.y += _scale; } - if (normal.z < 0) { + if (normal.z < 0.0f) { result.z += _scale; } @@ -284,6 +284,12 @@ bool AACube::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc return false; } +bool AACube::touchesSphere(const glm::vec3& center, float radius) const { + // Avro's algorithm from this paper: http://www.mrtc.mdh.se/projects/3Dgraphics/paperF.pdf + glm::vec3 e = glm::max(_corner - center, Vectors::ZERO) + glm::max(center - _corner - glm::vec3(_scale), Vectors::ZERO); + return glm::length2(e) <= radius * radius; +} + bool AACube::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const { glm::vec4 center4 = glm::vec4(center, 1.0f); diff --git a/libraries/shared/src/AACube.h b/libraries/shared/src/AACube.h index dab5dafdaa..87a38cb304 100644 --- a/libraries/shared/src/AACube.h +++ b/libraries/shared/src/AACube.h @@ -34,8 +34,8 @@ public: ~AACube() {}; void setBox(const glm::vec3& corner, float scale); - glm::vec3 getVertexP(const glm::vec3& normal) const; - glm::vec3 getVertexN(const glm::vec3& normal) const; + glm::vec3 getFarthestVertex(const glm::vec3& normal) const; // return vertex most parallel to normal + glm::vec3 getNearestVertex(const glm::vec3& normal) const; // return vertex most anti-parallel to normal void scale(float scale); const glm::vec3& getCorner() const { return _corner; } float getScale() const { return _scale; } @@ -56,8 +56,9 @@ public: bool touches(const AABox& otherBox) const; bool expandedContains(const glm::vec3& point, float expansion) const; bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const; - bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) const; + bool touchesSphere(const glm::vec3& center, float radius) const; bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const; bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const; @@ -88,7 +89,7 @@ inline bool operator!=(const AACube& a, const AACube& b) { } inline QDebug operator<<(QDebug debug, const AACube& cube) { - debug << "AACube[ (" + debug << "AACube[ (" << cube.getCorner().x << "," << cube.getCorner().y << "," << cube.getCorner().z << " ) to (" << cube.calcTopFarLeft().x << "," << cube.calcTopFarLeft().y << "," << cube.calcTopFarLeft().z << ") size: (" << cube.getDimensions().x << "," << cube.getDimensions().y << "," << cube.getDimensions().z << ")" diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 1c41827795..999f9ef2f7 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -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)); } diff --git a/tests/octree/src/OctreeTests.cpp b/tests/octree/src/OctreeTests.cpp index d1f6fba891..ef08855d06 100644 --- a/tests/octree/src/OctreeTests.cpp +++ b/tests/octree/src/OctreeTests.cpp @@ -670,9 +670,8 @@ void OctreeTests::byteCountCodingTests() { } void OctreeTests::modelItemTests() { - bool verbose = true; - #if 0 // TODO - repair/replace these + bool verbose = true; //verbose = true; EntityTreeElementExtraEncodeData modelTreeElementExtraEncodeData; diff --git a/tests/octree/src/ViewFrustumTests.cpp b/tests/octree/src/ViewFrustumTests.cpp new file mode 100644 index 0000000000..2e9df54d83 --- /dev/null +++ b/tests/octree/src/ViewFrustumTests.cpp @@ -0,0 +1,1347 @@ +// +// ViewFrustumTests.cpp +// tests/octree/src +// +// Created by Andrew Meadows on 2016.02.19 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ViewFrustumTests.h" + +#include + +#include +#include +#include + +//#include +#include <../GLMTestUtils.h> +#include <../QTestExtensions.h> + + +const float ACCEPTABLE_FLOAT_ERROR = 1.0e-6f; +const float ACCEPTABLE_DOT_ERROR = 1.0e-5f; +const float ACCEPTABLE_CLIP_ERROR = 3e-4f; + +const glm::vec3 localRight(1.0f, 0.0f, 0.0f); +const glm::vec3 localUp(0.0f, 1.0f, 0.0f); +const glm::vec3 localForward(0.0f, 0.0f, -1.0f); + + +QTEST_MAIN(ViewFrustumTests) + +void ViewFrustumTests::testInit() { + float aspect = 1.0f; + float fovX = PI / 2.0f; + float nearClip = 1.0f; + float farClip = 100.0f; + float holeRadius = 10.0f; + + glm::vec3 center = glm::vec3(12.3f, 4.56f, 89.7f); + + float angle = PI / 7.0f; + glm::vec3 axis = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f)); + glm::quat rotation = glm::angleAxis(angle, axis); + + ViewFrustum view; + view.setProjection(glm::perspective(fovX, aspect, nearClip, farClip)); + view.setPosition(center); + view.setOrientation(rotation); + view.setCenterRadius(holeRadius); + view.calculate(); + + // check frustum dimensions + QCOMPARE_WITH_ABS_ERROR(fovX, glm::radians(view.getFieldOfView()), ACCEPTABLE_FLOAT_ERROR); + QCOMPARE_WITH_ABS_ERROR(aspect, view.getAspectRatio(), ACCEPTABLE_FLOAT_ERROR); + QCOMPARE_WITH_ABS_ERROR(farClip, view.getFarClip(), ACCEPTABLE_CLIP_ERROR); + QCOMPARE_WITH_ABS_ERROR(nearClip, view.getNearClip(), ACCEPTABLE_CLIP_ERROR); + + // check transform + QCOMPARE_WITH_ABS_ERROR(view.getPosition(), center, ACCEPTABLE_FLOAT_ERROR); + float rotationDot = glm::abs(glm::dot(rotation, view.getOrientation())); + QCOMPARE_WITH_ABS_ERROR(rotationDot, 1.0f, ACCEPTABLE_DOT_ERROR); + + // check view directions + glm::vec3 expectedForward = rotation * localForward; + float forwardDot = glm::dot(expectedForward, view.getDirection()); + QCOMPARE_WITH_ABS_ERROR(forwardDot, 1.0f, ACCEPTABLE_DOT_ERROR); + + glm::vec3 expectedRight = rotation * localRight; + float rightDot = glm::dot(expectedRight, view.getRight()); + QCOMPARE_WITH_ABS_ERROR(rightDot, 1.0f, ACCEPTABLE_DOT_ERROR); + + glm::vec3 expectedUp = rotation * localUp; + float upDot = glm::dot(expectedUp, view.getUp()); + QCOMPARE_WITH_ABS_ERROR(upDot, 1.0f, ACCEPTABLE_DOT_ERROR); +} + +void ViewFrustumTests::testCubeKeyholeIntersection() { + float aspect = 1.0f; + float fovX = PI / 2.0f; + float fovY = 2.0f * asinf(sinf(0.5f * fovX) / aspect); + float nearClip = 1.0f; + float farClip = 100.0f; + float holeRadius = 10.0f; + + glm::vec3 center = glm::vec3(12.3f, 4.56f, 89.7f); + + float angle = PI / 7.0f; + glm::vec3 axis = Vectors::UNIT_Y; + glm::quat rotation = glm::angleAxis(angle, axis); + + ViewFrustum view; + view.setProjection(glm::perspective(fovX, aspect, nearClip, farClip)); + view.setPosition(center); + view.setOrientation(rotation); + view.setCenterRadius(holeRadius); + view.calculate(); + + float delta = 0.1f; + float deltaAngle = 0.01f; + glm::quat elevation, swing; + glm::vec3 cubeCenter, localOffset; + + float cubeScale = 2.68f; // must be much smaller than cubeDistance for small angle approx below + glm::vec3 halfScaleOffset = 0.5f * glm::vec3(cubeScale); + float cubeDistance = farClip; + float cubeBoundingRadius = 0.5f * sqrtf(3.0f) * cubeScale; + float cubeAngle = cubeBoundingRadius / cubeDistance; // sine of small angles approximation + AACube cube(center, cubeScale); + + // farPlane + localOffset = (cubeDistance - cubeBoundingRadius - delta) * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE); + + localOffset = cubeDistance * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INTERSECT); + + localOffset = (cubeDistance + cubeBoundingRadius + delta) * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::OUTSIDE); + + // nearPlane + localOffset = (nearClip + 2.0f * cubeBoundingRadius + delta) * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE); + + localOffset = (nearClip + delta) * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE); + + // topPlane + angle = 0.5f * fovY; + elevation = glm::angleAxis(angle - cubeAngle - deltaAngle, localRight); + localOffset = elevation * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE); + + elevation = glm::angleAxis(angle, localRight); + localOffset = elevation * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INTERSECT); + + elevation = glm::angleAxis(angle + cubeAngle + deltaAngle, localRight); + localOffset = elevation * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::OUTSIDE); + + // bottom plane + angle = -0.5f * fovY; + elevation = glm::angleAxis(angle + cubeAngle + deltaAngle, localRight); + localOffset = elevation * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE); + + elevation = glm::angleAxis(angle, localRight); + localOffset = elevation * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INTERSECT); + + elevation = glm::angleAxis(angle - cubeAngle - deltaAngle, localRight); + localOffset = elevation * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::OUTSIDE); + + // right plane + angle = 0.5f * fovX; + swing = glm::angleAxis(angle - cubeAngle - deltaAngle, localUp); + localOffset = swing * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE); + + swing = glm::angleAxis(angle, localUp); + localOffset = swing * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INTERSECT); + + swing = glm::angleAxis(angle + cubeAngle + deltaAngle, localUp); + localOffset = swing * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::OUTSIDE); + + // left plane + angle = -0.5f * fovX; + swing = glm::angleAxis(angle + cubeAngle + deltaAngle, localUp); + localOffset = swing * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE); + + swing = glm::angleAxis(angle, localUp); + localOffset = swing * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INTERSECT); + + swing = glm::angleAxis(angle - cubeAngle - deltaAngle, localUp); + localOffset = swing * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::OUTSIDE); + + // central sphere right + localOffset = (holeRadius - cubeBoundingRadius - delta) * localRight; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE); + + localOffset = holeRadius * localRight; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INTERSECT); + + localOffset = (holeRadius + cubeBoundingRadius + delta) * localRight; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::OUTSIDE); + + // central sphere up + localOffset = (holeRadius - cubeBoundingRadius - delta) * localUp; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE); + + localOffset = holeRadius * localUp; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INTERSECT); + + localOffset = (holeRadius + cubeBoundingRadius + delta) * localUp; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::OUTSIDE); + + // central sphere back + localOffset = (-holeRadius + cubeBoundingRadius + delta) * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE); + + localOffset = - holeRadius * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INTERSECT); + + localOffset = (-holeRadius - cubeBoundingRadius - delta) * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::OUTSIDE); + + // central sphere center + float bigCubeScale = 2.0f * holeRadius / sqrtf(3.0f) - delta; + cube.setBox(center - glm::vec3(0.5f * bigCubeScale), bigCubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INSIDE); // smaller than sphere + + bigCubeScale = 2.0f * holeRadius / sqrtf(3.0f) + delta; + cube.setBox(center - glm::vec3(0.5f * bigCubeScale), bigCubeScale); + QCOMPARE(view.calculateCubeKeyholeIntersection(cube), ViewFrustum::INTERSECT); // larger than sphere +} + +void ViewFrustumTests::testCubeFrustumIntersection() { + float aspect = 1.0f; + float fovX = PI / 2.0f; + float fovY = 2.0f * asinf(sinf(0.5f * fovX) / aspect); + float nearClip = 1.0f; + float farClip = 100.0f; + float holeRadius = 10.0f; + + glm::vec3 center = glm::vec3(12.3f, 4.56f, 89.7f); + + float angle = PI / 7.0f; + glm::vec3 axis = Vectors::UNIT_Y; + glm::quat rotation = glm::angleAxis(angle, axis); + + ViewFrustum view; + view.setProjection(glm::perspective(fovX, aspect, nearClip, farClip)); + view.setPosition(center); + view.setOrientation(rotation); + view.setCenterRadius(holeRadius); + view.calculate(); + + float delta = 0.1f; + float deltaAngle = 0.01f; + glm::quat elevation, swing; + glm::vec3 cubeCenter, localOffset; + + float cubeScale = 2.68f; // must be much smaller than cubeDistance for small angle approx below + glm::vec3 halfScaleOffset = 0.5f * glm::vec3(cubeScale); + float cubeDistance = farClip; + float cubeBoundingRadius = 0.5f * sqrtf(3.0f) * cubeScale; + float cubeAngle = cubeBoundingRadius / cubeDistance; // sine of small angles approximation + AACube cube(center, cubeScale); + + // farPlane + localOffset = (cubeDistance - cubeBoundingRadius - delta) * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INSIDE); + + localOffset = cubeDistance * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INTERSECT); + + localOffset = (cubeDistance + cubeBoundingRadius + delta) * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::OUTSIDE); + + // nearPlane + localOffset = (nearClip + 2.0f * cubeBoundingRadius + delta) * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INSIDE); + + localOffset = (nearClip + delta) * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INTERSECT); + + localOffset = (nearClip - cubeBoundingRadius - delta) * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::OUTSIDE); + + // topPlane + angle = 0.5f * fovY; + elevation = glm::angleAxis(angle - cubeAngle - deltaAngle, localRight); + localOffset = elevation * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INSIDE); + + elevation = glm::angleAxis(angle, localRight); + localOffset = elevation * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INTERSECT); + + elevation = glm::angleAxis(angle + cubeAngle + deltaAngle, localRight); + localOffset = elevation * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::OUTSIDE); + + // bottom plane + angle = -0.5f * fovY; + elevation = glm::angleAxis(angle + cubeAngle + deltaAngle, localRight); + localOffset = elevation * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INSIDE); + + elevation = glm::angleAxis(angle, localRight); + localOffset = elevation * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INTERSECT); + + elevation = glm::angleAxis(angle - cubeAngle - deltaAngle, localRight); + localOffset = elevation * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::OUTSIDE); + + // right plane + angle = 0.5f * fovX; + swing = glm::angleAxis(angle - cubeAngle - deltaAngle, localUp); + localOffset = swing * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INSIDE); + + swing = glm::angleAxis(angle, localUp); + localOffset = swing * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INTERSECT); + + swing = glm::angleAxis(angle + cubeAngle + deltaAngle, localUp); + localOffset = swing * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::OUTSIDE); + + // left plane + angle = -0.5f * fovX; + swing = glm::angleAxis(angle + cubeAngle + deltaAngle, localUp); + localOffset = swing * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INSIDE); + + swing = glm::angleAxis(angle, localUp); + localOffset = swing * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::INTERSECT); + + swing = glm::angleAxis(angle - cubeAngle - deltaAngle, localUp); + localOffset = swing * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.calculateCubeFrustumIntersection(cube), ViewFrustum::OUTSIDE); +} + +void ViewFrustumTests::testPointIntersectsFrustum() { + float aspect = 1.0f; + float fovX = PI / 2.0f; + float fovY = 2.0f * asinf(sinf(0.5f * fovX) / aspect); + float nearClip = 1.0f; + float farClip = 100.0f; + float holeRadius = 10.0f; + + glm::vec3 center = glm::vec3(12.3f, 4.56f, 89.7f); + + float angle = PI / 7.0f; + glm::vec3 axis = Vectors::UNIT_Y; + glm::quat rotation = glm::angleAxis(angle, axis); + + ViewFrustum view; + view.setProjection(glm::perspective(fovX, aspect, nearClip, farClip)); + view.setPosition(center); + view.setOrientation(rotation); + view.setCenterRadius(holeRadius); + view.calculate(); + + float delta = 0.1f; + float deltaAngle = 0.01f; + glm::quat elevation, swing; + glm::vec3 point, localOffset; + float pointDistance = farClip; + + // farPlane + localOffset = (pointDistance - delta) * localForward; + point = center + rotation * localOffset; + QCOMPARE(view.pointIntersectsFrustum(point), true); // inside + + localOffset = (pointDistance + delta) * localForward; + point = center + rotation * localOffset; + QCOMPARE(view.pointIntersectsFrustum(point), false); // outside + + // nearPlane + localOffset = (nearClip + delta) * localForward; + point = center + rotation * localOffset; + QCOMPARE(view.pointIntersectsFrustum(point), true); // inside + + localOffset = (nearClip - delta) * localForward; + point = center + rotation * localOffset; + QCOMPARE(view.pointIntersectsFrustum(point), false); // outside + + // topPlane + angle = 0.5f * fovY; + elevation = glm::angleAxis(angle - deltaAngle, localRight); + localOffset = elevation * (pointDistance * localForward); + point = center + rotation * localOffset; + QCOMPARE(view.pointIntersectsFrustum(point), true); // inside + + elevation = glm::angleAxis(angle + deltaAngle, localRight); + localOffset = elevation * (pointDistance * localForward); + point = center + rotation * localOffset; + QCOMPARE(view.pointIntersectsFrustum(point), false); // outside + + // bottom plane + angle = -0.5f * fovY; + elevation = glm::angleAxis(angle + deltaAngle, localRight); + localOffset = elevation * (pointDistance * localForward); + point = center + rotation * localOffset; + QCOMPARE(view.pointIntersectsFrustum(point), true); // inside + + elevation = glm::angleAxis(angle - deltaAngle, localRight); + localOffset = elevation * (pointDistance * localForward); + point = center + rotation * localOffset; + QCOMPARE(view.pointIntersectsFrustum(point), false); // outside + + // right plane + angle = 0.5f * fovX; + swing = glm::angleAxis(angle - deltaAngle, localUp); + localOffset = swing * (pointDistance * localForward); + point = center + rotation * localOffset; + QCOMPARE(view.pointIntersectsFrustum(point), true); // inside + + swing = glm::angleAxis(angle + deltaAngle, localUp); + localOffset = swing * (pointDistance * localForward); + point = center + rotation * localOffset; + QCOMPARE(view.pointIntersectsFrustum(point), false); // outside + + // left plane + angle = -0.5f * fovX; + swing = glm::angleAxis(angle + deltaAngle, localUp); + localOffset = swing * (pointDistance * localForward); + point = center + rotation * localOffset; + QCOMPARE(view.pointIntersectsFrustum(point), true); // inside + + swing = glm::angleAxis(angle - deltaAngle, localUp); + localOffset = swing * (pointDistance * localForward); + point = center + rotation * localOffset; + QCOMPARE(view.pointIntersectsFrustum(point), false); // outside +} + +void ViewFrustumTests::testSphereIntersectsFrustum() { + float aspect = 1.0f; + float fovX = PI / 2.0f; + float fovY = 2.0f * asinf(sinf(0.5f * fovX) / aspect); + float nearClip = 1.0f; + float farClip = 100.0f; + float holeRadius = 10.0f; + + glm::vec3 center = glm::vec3(12.3f, 4.56f, 89.7f); + + float angle = PI / 7.0f; + glm::vec3 axis = Vectors::UNIT_Y; + glm::quat rotation = glm::angleAxis(angle, axis); + + ViewFrustum view; + view.setProjection(glm::perspective(fovX, aspect, nearClip, farClip)); + view.setPosition(center); + view.setOrientation(rotation); + view.setCenterRadius(holeRadius); + view.calculate(); + + float delta = 0.1f; + float deltaAngle = 0.01f; + glm::quat elevation, swing; + glm::vec3 sphereCenter, localOffset; + + float sphereRadius = 2.68f; // must be much smaller than sphereDistance for small angle approx below + float sphereDistance = farClip; + float sphereAngle = sphereRadius / sphereDistance; // sine of small angles approximation + + // farPlane + localOffset = (sphereDistance - sphereRadius - delta) * localForward; + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // inside + + localOffset = (sphereDistance + sphereRadius - delta) * localForward; + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // straddle + + localOffset = (sphereDistance + sphereRadius + delta) * localForward; + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), false); // outside + + // nearPlane + localOffset = (nearClip + 2.0f * sphereRadius + delta) * localForward; + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // inside + + localOffset = (nearClip - sphereRadius + delta) * localForward; + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // straddle + + localOffset = (nearClip - sphereRadius - delta) * localForward; + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), false); // outside + + // topPlane + angle = 0.5f * fovY - sphereAngle; + elevation = glm::angleAxis(angle - deltaAngle, localRight); + localOffset = elevation * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // inside + + angle = 0.5f * fovY + sphereAngle; + elevation = glm::angleAxis(angle - deltaAngle, localRight); + localOffset = elevation * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // straddle + + elevation = glm::angleAxis(angle + deltaAngle, localRight); + localOffset = elevation * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), false); // outside + + // bottom plane + angle = -0.5f * fovY + sphereAngle; + elevation = glm::angleAxis(angle + deltaAngle, localRight); + localOffset = elevation * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // inside + + angle = -0.5f * fovY - sphereAngle; + elevation = glm::angleAxis(angle + deltaAngle, localRight); + localOffset = elevation * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // straddle + + elevation = glm::angleAxis(angle - deltaAngle, localRight); + localOffset = elevation * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), false); // outside + + // right plane + angle = 0.5f * fovX - sphereAngle; + swing = glm::angleAxis(angle - deltaAngle, localUp); + localOffset = swing * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // inside + + angle = 0.5f * fovX + sphereAngle; + swing = glm::angleAxis(angle - deltaAngle, localUp); + localOffset = swing * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // straddle + + swing = glm::angleAxis(angle + deltaAngle, localUp); + localOffset = swing * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), false); // outside + + // left plane + angle = -0.5f * fovX + sphereAngle; + swing = glm::angleAxis(angle + deltaAngle, localUp); + localOffset = swing * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // inside + + angle = -0.5f * fovX - sphereAngle; + swing = glm::angleAxis(angle + deltaAngle, localUp); + localOffset = swing * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), true); // straddle + + swing = glm::angleAxis(angle - sphereAngle - deltaAngle, localUp); + localOffset = swing * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsFrustum(sphereCenter, sphereRadius), false); // outside +} + +void ViewFrustumTests::testBoxIntersectsFrustum() { + float aspect = 1.0f; + float fovX = PI / 2.0f; + float fovY = 2.0f * asinf(sinf(0.5f * fovX) / aspect); + float nearClip = 1.0f; + float farClip = 100.0f; + float holeRadius = 10.0f; + + glm::vec3 center = glm::vec3(12.3f, 4.56f, 89.7f); + + float angle = PI / 7.0f; + glm::vec3 axis = Vectors::UNIT_Y; + glm::quat rotation = glm::angleAxis(angle, axis); + + ViewFrustum view; + view.setProjection(glm::perspective(fovX, aspect, nearClip, farClip)); + view.setPosition(center); + view.setOrientation(rotation); + view.setCenterRadius(holeRadius); + view.calculate(); + + float delta = 0.1f; + float deltaAngle = 0.01f; + glm::quat elevation, swing; + glm::vec3 boxCenter, localOffset; + + glm::vec3 boxScale = glm::vec3(2.68f, 1.78f, 0.431f); + float boxDistance = farClip; + float boxBoundingRadius = 0.5f * glm::length(boxScale); + float boxAngle = boxBoundingRadius / boxDistance; // sine of small angles approximation + AABox box(center, boxScale); + + // farPlane + localOffset = (boxDistance - boxBoundingRadius - delta) * localForward; + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - 0.5f * boxScale, boxScale); + QCOMPARE(view.boxIntersectsFrustum(box), true); // inside + + localOffset = boxDistance * localForward; + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - 0.5f * boxScale, boxScale); + QCOMPARE(view.boxIntersectsFrustum(box), true); // straddle + + localOffset = (boxDistance + boxBoundingRadius + delta) * localForward; + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - 0.5f * boxScale, boxScale); + QCOMPARE(view.boxIntersectsFrustum(box), false); // outside + + // nearPlane + localOffset = (nearClip + 2.0f * boxBoundingRadius + delta) * localForward; + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - 0.5f * boxScale, boxScale); + QCOMPARE(view.boxIntersectsFrustum(box), true); // inside + + localOffset = nearClip * localForward; + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - 0.5f * boxScale, boxScale); + QCOMPARE(view.boxIntersectsFrustum(box), true); // straddle + + localOffset = (nearClip - boxBoundingRadius - delta) * localForward; + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - 0.5f * boxScale, boxScale); + QCOMPARE(view.boxIntersectsFrustum(box), false); // outside + + // topPlane + angle = 0.5f * fovY; + elevation = glm::angleAxis(angle - boxAngle - deltaAngle, localRight); + localOffset = elevation * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - 0.5f * boxScale, boxScale); + QCOMPARE(view.boxIntersectsFrustum(box), true); // inside + + elevation = glm::angleAxis(angle, localRight); + localOffset = elevation * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - 0.5f * boxScale, boxScale); + QCOMPARE(view.boxIntersectsFrustum(box), true); // straddle + + elevation = glm::angleAxis(angle + boxAngle + deltaAngle, localRight); + localOffset = elevation * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - 0.5f * boxScale, boxScale); + QCOMPARE(view.boxIntersectsFrustum(box), false); // outside + + // bottom plane + angle = -0.5f * fovY; + elevation = glm::angleAxis(angle + boxAngle + deltaAngle, localRight); + localOffset = elevation * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - 0.5f * boxScale, boxScale); + QCOMPARE(view.boxIntersectsFrustum(box), true); // inside + + elevation = glm::angleAxis(angle, localRight); + localOffset = elevation * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - 0.5f * boxScale, boxScale); + QCOMPARE(view.boxIntersectsFrustum(box), true); // straddle + + elevation = glm::angleAxis(angle - boxAngle - deltaAngle, localRight); + localOffset = elevation * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - 0.5f * boxScale, boxScale); + QCOMPARE(view.boxIntersectsFrustum(box), false); // outside + + // right plane + angle = 0.5f * fovX; + swing = glm::angleAxis(angle - boxAngle - deltaAngle, localUp); + localOffset = swing * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - 0.5f * boxScale, boxScale); + QCOMPARE(view.boxIntersectsFrustum(box), true); // inside + + swing = glm::angleAxis(angle, localUp); + localOffset = swing * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - 0.5f * boxScale, boxScale); + QCOMPARE(view.boxIntersectsFrustum(box), true); // straddle + + swing = glm::angleAxis(angle + boxAngle + deltaAngle, localUp); + localOffset = swing * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - 0.5f * boxScale, boxScale); + QCOMPARE(view.boxIntersectsFrustum(box), false); // outside + + // left plane + angle = -0.5f * fovX; + swing = glm::angleAxis(angle + boxAngle + deltaAngle, localUp); + localOffset = swing * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - 0.5f * boxScale, boxScale); + QCOMPARE(view.boxIntersectsFrustum(box), true); // inside + + swing = glm::angleAxis(angle, localUp); + localOffset = swing * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - 0.5f * boxScale, boxScale); + QCOMPARE(view.boxIntersectsFrustum(box), true); // straddle + + swing = glm::angleAxis(angle - boxAngle - deltaAngle, localUp); + localOffset = swing * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - 0.5f * boxScale, boxScale); + QCOMPARE(view.boxIntersectsFrustum(box), false); // outside +} + +void ViewFrustumTests::testSphereIntersectsKeyhole() { + float aspect = 1.0f; + float fovX = PI / 2.0f; + float fovY = 2.0f * asinf(sinf(0.5f * fovX) / aspect); + float nearClip = 1.0f; + float farClip = 100.0f; + float holeRadius = 10.0f; + + glm::vec3 center = glm::vec3(12.3f, 4.56f, 89.7f); + + float angle = PI / 7.0f; + glm::vec3 axis = Vectors::UNIT_Y; + glm::quat rotation = glm::angleAxis(angle, axis); + + ViewFrustum view; + view.setProjection(glm::perspective(fovX, aspect, nearClip, farClip)); + view.setPosition(center); + view.setOrientation(rotation); + view.setCenterRadius(holeRadius); + view.calculate(); + + float delta = 0.1f; + float deltaAngle = 0.01f; + glm::quat elevation, swing; + glm::vec3 sphereCenter, localOffset; + + float sphereRadius = 2.68f; // must be much smaller than sphereDistance for small angle approx below + float sphereDistance = farClip; + float sphereAngle = sphereRadius / sphereDistance; // sine of small angles approximation + + // farPlane + localOffset = (sphereDistance - sphereRadius - delta) * localForward; + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // inside + + localOffset = sphereDistance * localForward; + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // straddle + + localOffset = (sphereDistance + sphereRadius + delta) * localForward; + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), false); // outside + + // nearPlane + localOffset = (nearClip + 2.0f * sphereRadius + delta) * localForward; + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // inside + + localOffset = (nearClip - sphereRadius + delta) * localForward; + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // straddle + + localOffset = (nearClip - sphereRadius - delta) * localForward; + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // touches central sphere + + // topPlane + angle = 0.5f * fovY - sphereAngle; + elevation = glm::angleAxis(angle - deltaAngle, localRight); + localOffset = elevation * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // inside + + angle = 0.5f * fovY + sphereAngle; + elevation = glm::angleAxis(angle - deltaAngle, localRight); + localOffset = elevation * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // straddle + + elevation = glm::angleAxis(angle + deltaAngle, localRight); + localOffset = elevation * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), false); // outside + + // bottom plane + angle = -0.5f * fovY + sphereAngle; + elevation = glm::angleAxis(angle + deltaAngle, localRight); + localOffset = elevation * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // inside + + angle = -0.5f * fovY - sphereAngle; + elevation = glm::angleAxis(angle + deltaAngle, localRight); + localOffset = elevation * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // straddle + + elevation = glm::angleAxis(angle - deltaAngle, localRight); + localOffset = elevation * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), false); // outside + + // right plane + angle = 0.5f * fovX - sphereAngle; + swing = glm::angleAxis(angle - deltaAngle, localUp); + localOffset = swing * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // inside + + angle = 0.5f * fovX + sphereAngle; + swing = glm::angleAxis(angle - deltaAngle, localUp); + localOffset = swing * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // straddle + + swing = glm::angleAxis(angle + deltaAngle, localUp); + localOffset = swing * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), false); // outside + + // left plane + angle = -0.5f * fovX + sphereAngle; + swing = glm::angleAxis(angle + deltaAngle, localUp); + localOffset = swing * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // inside + + angle = -0.5f * fovX - sphereAngle; + swing = glm::angleAxis(angle + deltaAngle, localUp); + localOffset = swing * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // straddle + + swing = glm::angleAxis(angle - sphereAngle - deltaAngle, localUp); + localOffset = swing * (sphereDistance * localForward); + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), false); // outside + + // central sphere right + localOffset = (holeRadius - sphereRadius - delta) * localRight; + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // inside right + + localOffset = holeRadius * localRight; + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // straddle right + + localOffset = (holeRadius + sphereRadius + delta) * localRight; + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), false); // outside right + + // central sphere up + localOffset = (holeRadius - sphereRadius - delta) * localUp; + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // inside up + + localOffset = holeRadius * localUp; + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // straddle up + + localOffset = (holeRadius + sphereRadius + delta) * localUp; + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), false); // outside up + + // central sphere back + localOffset = (-holeRadius + sphereRadius + delta) * localForward; + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // inside back + + localOffset = - holeRadius * localForward; + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), true); // straddle back + + localOffset = (-holeRadius - sphereRadius - delta) * localForward; + sphereCenter = center + rotation * localOffset; + QCOMPARE(view.sphereIntersectsKeyhole(sphereCenter, sphereRadius), false); // outside back +} + +void ViewFrustumTests::testCubeIntersectsKeyhole() { + float aspect = 1.0f; + float fovX = PI / 2.0f; + float fovY = 2.0f * asinf(sinf(0.5f * fovX) / aspect); + float nearClip = 1.0f; + float farClip = 100.0f; + float holeRadius = 10.0f; + + glm::vec3 center = glm::vec3(12.3f, 4.56f, 89.7f); + + float angle = PI / 7.0f; + glm::vec3 axis = Vectors::UNIT_Y; + glm::quat rotation = glm::angleAxis(angle, axis); + + ViewFrustum view; + view.setProjection(glm::perspective(fovX, aspect, nearClip, farClip)); + view.setPosition(center); + view.setOrientation(rotation); + view.setCenterRadius(holeRadius); + view.calculate(); + + float delta = 0.1f; + float deltaAngle = 0.01f; + glm::quat elevation, swing; + glm::vec3 cubeCenter, localOffset; + + float cubeScale = 2.68f; // must be much smaller than cubeDistance for small angle approx below + glm::vec3 halfScaleOffset = 0.5f * glm::vec3(cubeScale); + float cubeDistance = farClip; + float cubeBoundingRadius = 0.5f * sqrtf(3.0f) * cubeScale; + float cubeAngle = cubeBoundingRadius / cubeDistance; // sine of small angles approximation + AACube cube(center, cubeScale); + + // farPlane + localOffset = (cubeDistance - cubeBoundingRadius - delta) * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), true); + + localOffset = cubeDistance * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), true); + + localOffset = (cubeDistance + cubeBoundingRadius + delta) * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), false); + + // nearPlane + localOffset = (nearClip + 2.0f * cubeBoundingRadius + delta) * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // inside + + localOffset = (nearClip + delta) * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // straddle + + localOffset = (nearClip - cubeBoundingRadius - delta) * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // touches centeral sphere + + // topPlane + angle = 0.5f * fovY; + elevation = glm::angleAxis(angle - cubeAngle - deltaAngle, localRight); + localOffset = elevation * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // inside + + elevation = glm::angleAxis(angle, localRight); + localOffset = elevation * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // straddle + + elevation = glm::angleAxis(angle + cubeAngle + deltaAngle, localRight); + localOffset = elevation * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), false); // outside + + // bottom plane + angle = -0.5f * fovY; + elevation = glm::angleAxis(angle + cubeAngle + deltaAngle, localRight); + localOffset = elevation * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // inside + + elevation = glm::angleAxis(angle, localRight); + localOffset = elevation * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // straddle + + elevation = glm::angleAxis(angle - cubeAngle - deltaAngle, localRight); + localOffset = elevation * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), false); // outside + + // right plane + angle = 0.5f * fovX; + swing = glm::angleAxis(angle - cubeAngle - deltaAngle, localUp); + localOffset = swing * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // inside + + swing = glm::angleAxis(angle, localUp); + localOffset = swing * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // straddle + + swing = glm::angleAxis(angle + cubeAngle + deltaAngle, localUp); + localOffset = swing * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), false); // outside + + // left plane + angle = -0.5f * fovX; + swing = glm::angleAxis(angle + cubeAngle + deltaAngle, localUp); + localOffset = swing * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // inside + + swing = glm::angleAxis(angle, localUp); + localOffset = swing * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // straddle + + swing = glm::angleAxis(angle - cubeAngle - deltaAngle, localUp); + localOffset = swing * (cubeDistance * localForward); + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), false); // outside + + // central sphere right + localOffset = (holeRadius - cubeBoundingRadius - delta) * localRight; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // inside right + + localOffset = holeRadius * localRight; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // straddle right + + localOffset = (holeRadius + cubeBoundingRadius + delta) * localRight; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), false); // outside right + + // central sphere up + localOffset = (holeRadius - cubeBoundingRadius - delta) * localUp; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // inside up + + localOffset = holeRadius * localUp; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // straddle up + + localOffset = (holeRadius + cubeBoundingRadius + delta) * localUp; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), false); // outside up + + // central sphere back + localOffset = (-holeRadius + cubeBoundingRadius + delta) * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // inside back + + localOffset = - holeRadius * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), true); // straddle back + + localOffset = (-holeRadius - cubeBoundingRadius - delta) * localForward; + cubeCenter = center + rotation * localOffset; + cube.setBox(cubeCenter - halfScaleOffset, cubeScale); + QCOMPARE(view.cubeIntersectsKeyhole(cube), false); // outside back +} + +void ViewFrustumTests::testBoxIntersectsKeyhole() { + float aspect = 1.0f; + float fovX = PI / 2.0f; + float fovY = 2.0f * asinf(sinf(0.5f * fovX) / aspect); + float nearClip = 1.0f; + float farClip = 100.0f; + float holeRadius = 10.0f; + + glm::vec3 center = glm::vec3(12.3f, 4.56f, 89.7f); + + float angle = PI / 7.0f; + glm::vec3 axis = Vectors::UNIT_Y; + glm::quat rotation = glm::angleAxis(angle, axis); + + ViewFrustum view; + view.setProjection(glm::perspective(fovX, aspect, nearClip, farClip)); + view.setPosition(center); + view.setOrientation(rotation); + view.setCenterRadius(holeRadius); + view.calculate(); + + float delta = 0.1f; + float deltaAngle = 0.01f; + glm::quat elevation, swing; + glm::vec3 boxCenter, localOffset; + + glm::vec3 boxScale = glm::vec3(2.68f, 1.78f, 0.431f); // sides must be much smaller than boxDistance + glm::vec3 halfScaleOffset = 0.5f * boxScale; + float boxDistance = farClip; + float boxBoundingRadius = 0.5f * glm::length(boxScale); + float boxAngle = boxBoundingRadius / boxDistance; // sine of small angles approximation + AABox box(center, boxScale); + + // farPlane + localOffset = (boxDistance - boxBoundingRadius - delta) * localForward; + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), true); + + localOffset = boxDistance * localForward; + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), true); + + localOffset = (boxDistance + boxBoundingRadius + delta) * localForward; + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), false); + + // nearPlane + localOffset = (nearClip + 2.0f * boxBoundingRadius + delta) * localForward; + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), true); // inside + + localOffset = (nearClip + delta) * localForward; + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), true); // straddle + + localOffset = (nearClip - boxBoundingRadius - delta) * localForward; + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), true); // touches centeral sphere + + // topPlane + angle = 0.5f * fovY; + elevation = glm::angleAxis(angle - boxAngle - deltaAngle, localRight); + localOffset = elevation * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), true); // inside + + elevation = glm::angleAxis(angle, localRight); + localOffset = elevation * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), true); // straddle + + elevation = glm::angleAxis(angle + boxAngle + deltaAngle, localRight); + localOffset = elevation * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), false); // outside + + // bottom plane + angle = -0.5f * fovY; + elevation = glm::angleAxis(angle + boxAngle + deltaAngle, localRight); + localOffset = elevation * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), true); // inside + + elevation = glm::angleAxis(angle, localRight); + localOffset = elevation * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), true); // straddle + + elevation = glm::angleAxis(angle - boxAngle - deltaAngle, localRight); + localOffset = elevation * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), false); // outside + + // right plane + angle = 0.5f * fovX; + swing = glm::angleAxis(angle - boxAngle - deltaAngle, localUp); + localOffset = swing * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), true); // inside + + swing = glm::angleAxis(angle, localUp); + localOffset = swing * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), true); // straddle + + swing = glm::angleAxis(angle + boxAngle + deltaAngle, localUp); + localOffset = swing * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), false); // outside + + // left plane + angle = -0.5f * fovX; + swing = glm::angleAxis(angle + boxAngle + deltaAngle, localUp); + localOffset = swing * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), true); // inside + + swing = glm::angleAxis(angle, localUp); + localOffset = swing * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), true); // straddle + + swing = glm::angleAxis(angle - boxAngle - deltaAngle, localUp); + localOffset = swing * (boxDistance * localForward); + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), false); // outside + + // central sphere right + localOffset = (holeRadius - boxBoundingRadius - delta) * localRight; + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), true); // inside right + + localOffset = holeRadius * localRight; + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), true); // straddle right + + localOffset = (holeRadius + boxBoundingRadius + delta) * localRight; + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), false); // outside right + + // central sphere up + localOffset = (holeRadius - boxBoundingRadius - delta) * localUp; + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), true); // inside up + + localOffset = holeRadius * localUp; + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), true); // straddle up + + localOffset = (holeRadius + boxBoundingRadius + delta) * localUp; + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), false); // outside up + + // central sphere back + localOffset = (-holeRadius + boxBoundingRadius + delta) * localForward; + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), true); // inside back + + localOffset = - holeRadius * localForward; + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), true); // straddle back + + localOffset = (-holeRadius - boxBoundingRadius - delta) * localForward; + boxCenter = center + rotation * localOffset; + box.setBox(boxCenter - halfScaleOffset, boxScale); + QCOMPARE(view.boxIntersectsKeyhole(box), false); // outside back +} diff --git a/tests/octree/src/ViewFrustumTests.h b/tests/octree/src/ViewFrustumTests.h new file mode 100644 index 0000000000..a64a72e669 --- /dev/null +++ b/tests/octree/src/ViewFrustumTests.h @@ -0,0 +1,32 @@ +// +// ViewFrustumTests.h +// tests/octree/src +// +// Created by Andrew Meadows on 2016.02.19 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ViewFruxtumTests_h +#define hifi_ViewFruxtumTests_h + +#include + +class ViewFrustumTests : public QObject { + Q_OBJECT + +private slots: + void testInit(); + void testCubeFrustumIntersection(); + void testCubeKeyholeIntersection(); + void testPointIntersectsFrustum(); + void testSphereIntersectsFrustum(); + void testBoxIntersectsFrustum(); + void testSphereIntersectsKeyhole(); + void testCubeIntersectsKeyhole(); + void testBoxIntersectsKeyhole(); +}; + +#endif // hifi_ViewFruxtumTests_h diff --git a/tests/shared/src/AABoxTests.cpp b/tests/shared/src/AABoxTests.cpp new file mode 100644 index 0000000000..fd709a488c --- /dev/null +++ b/tests/shared/src/AABoxTests.cpp @@ -0,0 +1,153 @@ +// +// AABoxTests.cpp +// tests/shared/src +// +// Created by Andrew Meadows on 2016.02.19 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "AABoxTests.h" + +#include +#include +#include + +#include <../GLMTestUtils.h> +#include <../QTestExtensions.h> + + +QTEST_MAIN(AABoxTests) + +void AABoxTests::testCtorsAndSetters() { + const glm::vec3 corner(1.23f, 4.56f, 7.89f); + const glm::vec3 scale(2.34f, 7.53f, 9.14f); + + // test ctor + AABox box(corner, scale); + QCOMPARE_WITH_ABS_ERROR(box.getCorner(), corner, EPSILON); + QCOMPARE_WITH_ABS_ERROR(box.getScale(), scale, EPSILON); + + // test copy ctor + AABox copyBox(box); + QCOMPARE_WITH_ABS_ERROR(copyBox.getCorner(), corner, EPSILON); + QCOMPARE_WITH_ABS_ERROR(copyBox.getScale(), scale, EPSILON); + + // test setBox() + const glm::vec3 newCorner(9.87f, 6.54f, 3.21f); + const glm::vec3 newScale = glm::vec3(4.32f, 8.95f, 10.31f); + box.setBox(newCorner, newScale); + QCOMPARE_WITH_ABS_ERROR(box.getCorner(), newCorner, EPSILON); + QCOMPARE_WITH_ABS_ERROR(box.getScale(), newScale, EPSILON); + + // test misc + QCOMPARE_WITH_ABS_ERROR(newCorner, box.getMinimumPoint(), EPSILON); + + glm::vec3 expectedMaxCorner = newCorner + glm::vec3(newScale); + QCOMPARE_WITH_ABS_ERROR(expectedMaxCorner, box.getMaximumPoint(), EPSILON); + + glm::vec3 expectedCenter = newCorner + glm::vec3(0.5f * newScale); + QCOMPARE_WITH_ABS_ERROR(expectedCenter, box.calcCenter(), EPSILON); +} + +void AABoxTests::testContainsPoint() { + const glm::vec3 corner(4.56f, 7.89f, -1.35f); + const glm::vec3 scale(2.34f, 7.53f, 9.14f); + AABox box(corner, scale); + + float delta = 0.00001f; + glm::vec3 center = box.calcCenter(); + QCOMPARE(box.contains(center), true); + + for (int i = 0; i < 3; ++i) { + glm::vec3 halfScale = Vectors::ZERO; + halfScale[i] = 0.5f * scale[i]; + glm::vec3 deltaOffset = Vectors::ZERO; + deltaOffset[i] = delta; + + QCOMPARE(box.contains(center + halfScale + deltaOffset), false); // outside +face + QCOMPARE(box.contains(center + halfScale - deltaOffset), true); // inside +face + QCOMPARE(box.contains(center - halfScale + deltaOffset), true); // inside -face + QCOMPARE(box.contains(center - halfScale - deltaOffset), false); // outside -face + } +} + +void AABoxTests::testTouchesSphere() { + glm::vec3 corner(-4.56f, 7.89f, -1.35f); + float scale = 1.23f; + AABox box(corner, scale); + + float delta = 0.00001f; + glm::vec3 cubeCenter = box.calcCenter(); + float sphereRadius = 0.468f; + + for (int i = 0; i < 3; ++i) { + int j = (i + 1) % 3; + int k = (j + 1) % 3; + + { // faces + glm::vec3 scaleOffset = Vectors::ZERO; + scaleOffset[i] = 0.5f * scale + sphereRadius; + + glm::vec3 deltaOffset = Vectors::ZERO; + deltaOffset[i] = delta; + + // outside +face + glm::vec3 sphereCenter = cubeCenter + scaleOffset + deltaOffset; + QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false); + + // inside +face + sphereCenter = cubeCenter + scaleOffset - deltaOffset; + QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true); + + // inside -face + sphereCenter = cubeCenter - scaleOffset + deltaOffset; + QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true); + + // outside -face + sphereCenter = cubeCenter - scaleOffset - deltaOffset; + QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false); + } + + { // edges + glm::vec3 edgeOffset = Vectors::ZERO; + edgeOffset[i] = 0.5f * scale; + edgeOffset[j] = 0.5f * scale; + glm::vec3 edgeDirection = glm::normalize(edgeOffset); + glm::vec3 sphereCenter; + + // inside ij + sphereCenter = cubeCenter + edgeOffset + (sphereRadius - delta) * edgeDirection; + QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true); + sphereCenter = cubeCenter - edgeOffset - (sphereRadius - delta) * edgeDirection; + QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true); + + // outside ij + sphereCenter = cubeCenter + edgeOffset + (sphereRadius + delta) * edgeDirection; + QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false); + sphereCenter = cubeCenter - edgeOffset - (sphereRadius + delta) * edgeDirection; + QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false); + + edgeOffset[j] = 0.0f; + edgeOffset[k] = 0.5f * scale; + edgeDirection = glm::normalize(edgeOffset); + + // inside ik + sphereCenter = cubeCenter + edgeOffset + (sphereRadius - delta) * edgeDirection; + QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true); + sphereCenter = cubeCenter - edgeOffset - (sphereRadius - delta) * edgeDirection; + QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true); + + // outside ik + sphereCenter = cubeCenter + edgeOffset + (sphereRadius + delta) * edgeDirection; + QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false); + sphereCenter = cubeCenter - edgeOffset - (sphereRadius + delta) * edgeDirection; + QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false); + } + } +} + diff --git a/tests/shared/src/AABoxTests.h b/tests/shared/src/AABoxTests.h new file mode 100644 index 0000000000..fe7afede57 --- /dev/null +++ b/tests/shared/src/AABoxTests.h @@ -0,0 +1,28 @@ +// +// AABoxTests.h +// tests/shared/src +// +// Created by Andrew Meadows on 2016.02.19 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AABoxTests_h +#define hifi_AABoxTests_h + +#include +#include + +#include + +class AABoxTests : public QObject { + Q_OBJECT +private slots: + void testCtorsAndSetters(); + void testContainsPoint(); + void testTouchesSphere(); +}; + +#endif // hifi_AABoxTests_h diff --git a/tests/shared/src/AACubeTests.cpp b/tests/shared/src/AACubeTests.cpp new file mode 100644 index 0000000000..177daf89f1 --- /dev/null +++ b/tests/shared/src/AACubeTests.cpp @@ -0,0 +1,154 @@ +// +// AACubeTests.cpp +// tests/shared/src +// +// Created by Andrew Meadows on 2016.02.19 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "AACubeTests.h" + +#include +#include +#include + +#include <../GLMTestUtils.h> +#include <../QTestExtensions.h> + + +QTEST_MAIN(AACubeTests) + +void AACubeTests::ctorsAndSetters() { + const glm::vec3 corner(1.23f, 4.56f, 7.89f); + const float scale = 2.34f; + + // test ctor + AACube cube(corner, scale); + QCOMPARE_WITH_ABS_ERROR(cube.getCorner(), corner, EPSILON); + QCOMPARE_WITH_ABS_ERROR(cube.getScale(), scale, EPSILON); + + // test copy ctor + AACube copyCube(cube); + QCOMPARE_WITH_ABS_ERROR(copyCube.getCorner(), corner, EPSILON); + QCOMPARE_WITH_ABS_ERROR(copyCube.getScale(), scale, EPSILON); + + // test setBox() + const glm::vec3 newCorner(9.87f, 6.54f, 3.21f); + const float newScale = 4.32f; + cube.setBox(newCorner, newScale); + QCOMPARE_WITH_ABS_ERROR(cube.getCorner(), newCorner, EPSILON); + QCOMPARE_WITH_ABS_ERROR(cube.getScale(), newScale, EPSILON); + + // test misc + QCOMPARE_WITH_ABS_ERROR(cube.getMinimumPoint(), newCorner, EPSILON); + + glm::vec3 expectedMaxCorner = newCorner + glm::vec3(newScale); + QCOMPARE_WITH_ABS_ERROR(cube.getMaximumPoint(), expectedMaxCorner, EPSILON); + + glm::vec3 expectedCenter = newCorner + glm::vec3(0.5f * newScale); + QCOMPARE_WITH_ABS_ERROR(cube.calcCenter(), expectedCenter, EPSILON); +} + +void AACubeTests::containsPoint() { + const glm::vec3 corner(4.56f, 7.89f, -1.35f); + const float scale = 1.23f; + AACube cube(corner, scale); + + const float delta = scale / 1000.0f; + const glm::vec3 center = cube.calcCenter(); + QCOMPARE(cube.contains(center), true); + + for (int i = 0; i < 3; ++i) { + glm::vec3 scaleOffset = Vectors::ZERO; + scaleOffset[i] = 0.5f * scale; + + glm::vec3 deltaOffset = Vectors::ZERO; + deltaOffset[i] = delta; + + QCOMPARE(cube.contains(center + scaleOffset + deltaOffset), false); // outside +face + QCOMPARE(cube.contains(center + scaleOffset - deltaOffset), true); // inside +face + QCOMPARE(cube.contains(center - scaleOffset + deltaOffset), true); // inside -face + QCOMPARE(cube.contains(center - scaleOffset - deltaOffset), false); // outside -face + } +} + +void AACubeTests::touchesSphere() { + const glm::vec3 corner(-4.56f, 7.89f, -1.35f); + const float scale = 1.23f; + AACube cube(corner, scale); + + const float delta = scale / 1000.0f; + const glm::vec3 cubeCenter = cube.calcCenter(); + const float sphereRadius = 0.468f; + + for (int i = 0; i < 3; ++i) { + int j = (i + 1) % 3; + int k = (j + 1) % 3; + + { // faces + glm::vec3 scaleOffset = Vectors::ZERO; + scaleOffset[i] = 0.5f * scale + sphereRadius; + + glm::vec3 deltaOffset = Vectors::ZERO; + deltaOffset[i] = delta; + + // outside +face + glm::vec3 sphereCenter = cubeCenter + scaleOffset + deltaOffset; + QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false); + + // inside +face + sphereCenter = cubeCenter + scaleOffset - deltaOffset; + QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true); + + // inside -face + sphereCenter = cubeCenter - scaleOffset + deltaOffset; + QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true); + + // outside -face + sphereCenter = cubeCenter - scaleOffset - deltaOffset; + QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false); + } + + { // edges + glm::vec3 edgeOffset = Vectors::ZERO; + edgeOffset[i] = 0.5f * scale; + edgeOffset[j] = 0.5f * scale; + glm::vec3 edgeDirection = glm::normalize(edgeOffset); + glm::vec3 sphereCenter; + + // inside ij + sphereCenter = cubeCenter + edgeOffset + (sphereRadius - delta) * edgeDirection; + QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true); + sphereCenter = cubeCenter - edgeOffset - (sphereRadius - delta) * edgeDirection; + QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true); + + // outside ij + sphereCenter = cubeCenter + edgeOffset + (sphereRadius + delta) * edgeDirection; + QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false); + sphereCenter = cubeCenter - edgeOffset - (sphereRadius + delta) * edgeDirection; + QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false); + + edgeOffset[j] = 0.0f; + edgeOffset[k] = 0.5f * scale; + edgeDirection = glm::normalize(edgeOffset); + + // inside ik + sphereCenter = cubeCenter + edgeOffset + (sphereRadius - delta) * edgeDirection; + QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true); + sphereCenter = cubeCenter - edgeOffset - (sphereRadius - delta) * edgeDirection; + QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true); + + // outside ik + sphereCenter = cubeCenter + edgeOffset + (sphereRadius + delta) * edgeDirection; + QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false); + sphereCenter = cubeCenter - edgeOffset - (sphereRadius + delta) * edgeDirection; + QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false); + } + } +} + diff --git a/tests/shared/src/AACubeTests.h b/tests/shared/src/AACubeTests.h new file mode 100644 index 0000000000..a2b2e08cc5 --- /dev/null +++ b/tests/shared/src/AACubeTests.h @@ -0,0 +1,28 @@ +// +// AACubeTests.h +// tests/shared/src +// +// Created by Andrew Meadows on 2016.02.19 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AACubeTests_h +#define hifi_AACubeTests_h + +#include +#include + +#include + +class AACubeTests : public QObject { + Q_OBJECT +private slots: + void ctorsAndSetters(); + void containsPoint(); + void touchesSphere(); +}; + +#endif // hifi_AACubeTests_h diff --git a/tests/shared/src/AngularConstraintTests.cpp b/tests/shared/src/AngularConstraintTests.cpp deleted file mode 100644 index a711bac709..0000000000 --- a/tests/shared/src/AngularConstraintTests.cpp +++ /dev/null @@ -1,316 +0,0 @@ -// -// AngularConstraintTests.cpp -// tests/physics/src -// -// Created by Andrew Meadows on 2014.05.30 -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "AngularConstraintTests.h" - -#include - -#include -#include -#include - -#include "../QTestExtensions.h" - - -QTEST_MAIN(AngularConstraintTests) - -void AngularConstraintTests::testHingeConstraint() { - float minAngle = -PI; - float maxAngle = 0.0f; - glm::vec3 yAxis(0.0f, 1.0f, 0.0f); - glm::vec3 minAngles(0.0f, -PI, 0.0f); - glm::vec3 maxAngles(0.0f, 0.0f, 0.0f); - - AngularConstraint* c = AngularConstraint::newAngularConstraint(minAngles, maxAngles); - QVERIFY2(c != nullptr, "newAngularConstraint should make a constraint"); - { // test in middle of constraint - float angle = 0.5f * (minAngle + maxAngle); - glm::quat rotation = glm::angleAxis(angle, yAxis); - - glm::quat newRotation = rotation; - bool constrained = c->clamp(newRotation); - - QVERIFY2(constrained == false, "HingeConstraint should not clamp()"); - QVERIFY2(rotation == newRotation, "HingeConstraint should not change rotation"); - } - { // test just inside min edge of constraint - float angle = minAngle + 10.0f * EPSILON; - glm::quat rotation = glm::angleAxis(angle, yAxis); - - glm::quat newRotation = rotation; - bool constrained = c->clamp(newRotation); - - QVERIFY2(!constrained, "HingeConstraint should not clamp()"); - QVERIFY2(newRotation == rotation, "HingeConstraint should not change rotation"); - } - { // test just inside max edge of constraint - float angle = maxAngle - 10.0f * EPSILON; - glm::quat rotation = glm::angleAxis(angle, yAxis); - - glm::quat newRotation = rotation; - bool constrained = c->clamp(newRotation); - - QVERIFY2(!constrained, "HingeConstraint should not clamp()"); - QVERIFY2(newRotation == rotation, "HingeConstraint should not change rotation"); - } - { // test just outside min edge of constraint - float angle = minAngle - 0.001f; - glm::quat rotation = glm::angleAxis(angle, yAxis); - - glm::quat newRotation = rotation; - bool constrained = c->clamp(newRotation); - glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis); - - QVERIFY2(constrained, "HingeConstraint should clamp()"); - QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation"); - QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON); - } - { // test just outside max edge of constraint - float angle = maxAngle + 0.001f; - glm::quat rotation = glm::angleAxis(angle, yAxis); - - glm::quat newRotation = rotation; - bool constrained = c->clamp(newRotation); - - QVERIFY2(constrained, "HingeConstraint should clamp()"); - QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation"); - QCOMPARE_WITH_ABS_ERROR(newRotation, rotation, EPSILON); - } - { // test far outside min edge of constraint (wraps around to max) - float angle = minAngle - 0.75f * (TWO_PI - (maxAngle - minAngle)); - glm::quat rotation = glm::angleAxis(angle, yAxis); - - glm::quat newRotation = rotation; - bool constrained = c->clamp(newRotation); - - glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis); - QVERIFY2(constrained, "HingeConstraint should clamp()"); - QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation"); - QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON); - } - { // test far outside max edge of constraint (wraps around to min) - float angle = maxAngle + 0.75f * (TWO_PI - (maxAngle - minAngle)); - glm::quat rotation = glm::angleAxis(angle, yAxis); - - glm::quat newRotation = rotation; - bool constrained = c->clamp(newRotation); - glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis); - - QVERIFY2(constrained, "HingeConstraint should clamp()"); - QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation"); - QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON); - } - - float ACCEPTABLE_ERROR = 1.0e-4f; - { // test nearby but off-axis rotation - float offAngle = 0.1f; - glm::quat offRotation(offAngle, glm::vec3(1.0f, 0.0f, 0.0f)); - float angle = 0.5f * (maxAngle + minAngle); - glm::quat rotation = offRotation * glm::angleAxis(angle, yAxis); - - glm::quat newRotation = rotation; - bool constrained = c->clamp(newRotation); - glm::quat expectedRotation = glm::angleAxis(angle, yAxis); - - QVERIFY2(constrained, "HingeConstraint should clamp()"); - QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation"); - QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, ACCEPTABLE_ERROR); - } - { // test way off rotation > maxAngle - float offAngle = 0.5f; - glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f)); - float angle = maxAngle + 0.2f * (TWO_PI - (maxAngle - minAngle)); - glm::quat rotation = glm::angleAxis(angle, yAxis); - rotation = offRotation * glm::angleAxis(angle, yAxis); - - glm::quat newRotation = rotation; - bool constrained = c->clamp(newRotation); - glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis); - - QVERIFY2(constrained, "HingeConstraint should clamp()"); - QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation"); - QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON); - } - { // test way off rotation < minAngle - float offAngle = 0.5f; - glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f)); - float angle = minAngle - 0.2f * (TWO_PI - (maxAngle - minAngle)); - glm::quat rotation = glm::angleAxis(angle, yAxis); - rotation = offRotation * glm::angleAxis(angle, yAxis); - - glm::quat newRotation = rotation; - bool constrained = c->clamp(newRotation); - glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis); - - QVERIFY2(constrained, "HingeConstraint should clamp()"); - QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation"); - QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON); - } - { // test way off rotation > maxAngle with wrap over to minAngle - float offAngle = -0.5f; - glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f)); - float angle = maxAngle + 0.6f * (TWO_PI - (maxAngle - minAngle)); - glm::quat rotation = glm::angleAxis(angle, yAxis); - rotation = offRotation * glm::angleAxis(angle, yAxis); - - glm::quat newRotation = rotation; - bool constrained = c->clamp(newRotation); - glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis); - - QVERIFY2(constrained, "HingeConstraint should clamp()"); - QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation"); - QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON); - } - { // test way off rotation < minAngle with wrap over to maxAngle - float offAngle = -0.6f; - glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f)); - float angle = minAngle - 0.7f * (TWO_PI - (maxAngle - minAngle)); - glm::quat rotation = glm::angleAxis(angle, yAxis); - rotation = offRotation * glm::angleAxis(angle, yAxis); - - glm::quat newRotation = rotation; - bool constrained = c->clamp(newRotation); - glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis); - - QVERIFY2(constrained, "HingeConstraint should clamp()"); - QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation"); - QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON); - } - delete c; -} - -void AngularConstraintTests::testConeRollerConstraint() { - float minAngleX = -PI / 5.0f; - float minAngleY = -PI / 5.0f; - float minAngleZ = -PI / 8.0f; - - float maxAngleX = PI / 4.0f; - float maxAngleY = PI / 3.0f; - float maxAngleZ = PI / 4.0f; - - glm::vec3 minAngles(minAngleX, minAngleY, minAngleZ); - glm::vec3 maxAngles(maxAngleX, maxAngleY, maxAngleZ); - AngularConstraint* c = AngularConstraint::newAngularConstraint(minAngles, maxAngles); - - float expectedConeAngle = 0.25f * (maxAngleX - minAngleX + maxAngleY - minAngleY); - glm::vec3 middleAngles = 0.5f * (maxAngles + minAngles); - glm::quat yaw = glm::angleAxis(middleAngles[1], glm::vec3(0.0f, 1.0f, 0.0f)); - glm::quat pitch = glm::angleAxis(middleAngles[0], glm::vec3(1.0f, 0.0f, 0.0f)); - glm::vec3 expectedConeAxis = pitch * yaw * glm::vec3(0.0f, 0.0f, 1.0f); - - glm::vec3 xAxis(1.0f, 0.0f, 0.0f); - glm::vec3 perpAxis = glm::normalize(xAxis - glm::dot(xAxis, expectedConeAxis) * expectedConeAxis); - - QVERIFY2(c != nullptr, "newAngularConstraint() should make a constraint"); - { // test in middle of constraint - glm::vec3 angles(PI/20.0f, 0.0f, PI/10.0f); - glm::quat rotation(angles); - - glm::quat newRotation = rotation; - bool constrained = c->clamp(newRotation); - QVERIFY2(!constrained, "ConeRollerConstraint should not clamp()"); - QVERIFY2(newRotation == rotation, "ConeRollerConstraint should not change rotation"); - } - float deltaAngle = 0.001f; - { // test just inside edge of cone - glm::quat rotation = glm::angleAxis(expectedConeAngle - deltaAngle, perpAxis); - - glm::quat newRotation = rotation; - bool constrained = c->clamp(newRotation); - - QVERIFY2(!constrained, "ConeRollerConstraint should not clamp()"); - QVERIFY2(newRotation == rotation, "ConeRollerConstraint should not change rotation"); - } - { // test just outside edge of cone - glm::quat rotation = glm::angleAxis(expectedConeAngle + deltaAngle, perpAxis); - - glm::quat newRotation = rotation; - bool constrained = c->clamp(newRotation); - - QVERIFY2(constrained, "ConeRollerConstraint should clamp()"); - QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation"); - } - { // test just inside min edge of roll - glm::quat rotation = glm::angleAxis(minAngleZ + deltaAngle, expectedConeAxis); - - glm::quat newRotation = rotation; - bool constrained = c->clamp(newRotation); - - QVERIFY2(!constrained, "ConeRollerConstraint should not clamp()"); - QVERIFY2(newRotation == rotation, "ConeRollerConstraint should not change rotation"); - } - { // test just inside max edge of roll - glm::quat rotation = glm::angleAxis(maxAngleZ - deltaAngle, expectedConeAxis); - - glm::quat newRotation = rotation; - bool constrained = c->clamp(newRotation); - - QVERIFY2(!constrained, "ConeRollerConstraint should not clamp()"); - QVERIFY2(newRotation == rotation, "ConeRollerConstraint should not change rotation"); - } - { // test just outside min edge of roll - glm::quat rotation = glm::angleAxis(minAngleZ - deltaAngle, expectedConeAxis); - - glm::quat newRotation = rotation; - bool constrained = c->clamp(newRotation); - glm::quat expectedRotation = glm::angleAxis(minAngleZ, expectedConeAxis); - - QVERIFY2(constrained, "ConeRollerConstraint should clamp()"); - QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation"); - QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON); - } - { // test just outside max edge of roll - glm::quat rotation = glm::angleAxis(maxAngleZ + deltaAngle, expectedConeAxis); - - glm::quat newRotation = rotation; - bool constrained = c->clamp(newRotation); - glm::quat expectedRotation = glm::angleAxis(maxAngleZ, expectedConeAxis); - - QVERIFY2(constrained, "ConeRollerConstraint should clamp()"); - QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation"); - QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON); - } - deltaAngle = 0.25f * expectedConeAngle; - { // test far outside cone and min roll - glm::quat roll = glm::angleAxis(minAngleZ - deltaAngle, expectedConeAxis); - glm::quat pitchYaw = glm::angleAxis(expectedConeAngle + deltaAngle, perpAxis); - glm::quat rotation = pitchYaw * roll; - - glm::quat newRotation = rotation; - bool constrained = c->clamp(newRotation); - - glm::quat expectedRoll = glm::angleAxis(minAngleZ, expectedConeAxis); - glm::quat expectedPitchYaw = glm::angleAxis(expectedConeAngle, perpAxis); - glm::quat expectedRotation = expectedPitchYaw * expectedRoll; - - QVERIFY2(constrained, "ConeRollerConstraint should clamp()"); - QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation"); - QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON); - } - { // test far outside cone and max roll - glm::quat roll = glm::angleAxis(maxAngleZ + deltaAngle, expectedConeAxis); - glm::quat pitchYaw = glm::angleAxis(- expectedConeAngle - deltaAngle, perpAxis); - glm::quat rotation = pitchYaw * roll; - - glm::quat newRotation = rotation; - bool constrained = c->clamp(newRotation); - - glm::quat expectedRoll = glm::angleAxis(maxAngleZ, expectedConeAxis); - glm::quat expectedPitchYaw = glm::angleAxis(- expectedConeAngle, perpAxis); - glm::quat expectedRotation = expectedPitchYaw * expectedRoll; - - QVERIFY2(constrained, "ConeRollerConstraint should clamp()"); - QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation"); - QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON); - } - delete c; -} - diff --git a/tests/shared/src/AngularConstraintTests.h b/tests/shared/src/AngularConstraintTests.h deleted file mode 100644 index 705639b571..0000000000 --- a/tests/shared/src/AngularConstraintTests.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// AngularConstraintTests.h -// tests/physics/src -// -// Created by Andrew Meadows on 2014.05.30 -// Copyright 2014 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_AngularConstraintTests_h -#define hifi_AngularConstraintTests_h - -#include -#include - -class AngularConstraintTests : public QObject { - Q_OBJECT -private slots: - void testHingeConstraint(); - void testConeRollerConstraint(); -}; - -float getErrorDifference(const glm::quat& a, const glm::quat& b); - -#endif // hifi_AngularConstraintTests_h diff --git a/unpublishedScripts/DomainContent/CellScience/Scripts/moveRandomly.js b/unpublishedScripts/DomainContent/CellScience/Scripts/moveRandomly.js deleted file mode 100644 index a975f74733..0000000000 --- a/unpublishedScripts/DomainContent/CellScience/Scripts/moveRandomly.js +++ /dev/null @@ -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); - } - - } - - - -}) \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/CellScience/Scripts/navigationButton.js b/unpublishedScripts/DomainContent/CellScience/Scripts/navigationButton.js index 4136f1f81b..c1187bc6d5 100644 --- a/unpublishedScripts/DomainContent/CellScience/Scripts/navigationButton.js +++ b/unpublishedScripts/DomainContent/CellScience/Scripts/navigationButton.js @@ -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!!') } - } } diff --git a/unpublishedScripts/DomainContent/CellScience/Scripts/showButtonToPlaySound.js b/unpublishedScripts/DomainContent/CellScience/Scripts/showButtonToPlaySound.js index 6651e435b4..8ee5e0092e 100644 --- a/unpublishedScripts/DomainContent/CellScience/Scripts/showButtonToPlaySound.js +++ b/unpublishedScripts/DomainContent/CellScience/Scripts/showButtonToPlaySound.js @@ -36,8 +36,6 @@ return; } - - self.addButton(); self.buttonShowing = false; self.showDistance = self.userData.showDistance; @@ -51,8 +49,6 @@ }; self.sound = SoundCache.getSound(this.soundURL); - - } this.addButton = function() { diff --git a/unpublishedScripts/DomainContent/CellScience/Scripts/showIdentification.js b/unpublishedScripts/DomainContent/CellScience/Scripts/showIdentification.js index d8b32ab176..3f6067693c 100644 --- a/unpublishedScripts/DomainContent/CellScience/Scripts/showIdentification.js +++ b/unpublishedScripts/DomainContent/CellScience/Scripts/showIdentification.js @@ -10,7 +10,7 @@ var self = this; var baseURL = "https://hifi-content.s3.amazonaws.com/DomainContent/CellScience/"; - var version = 2; + var version = 3; this.preload = function(entityId) { this.soundPlaying = null; this.entityId = entityId; diff --git a/unpublishedScripts/DomainContent/CellScience/Scripts/zoom.js b/unpublishedScripts/DomainContent/CellScience/Scripts/zoom.js index da41ec64ba..3fc5acae2a 100644 --- a/unpublishedScripts/DomainContent/CellScience/Scripts/zoom.js +++ b/unpublishedScripts/DomainContent/CellScience/Scripts/zoom.js @@ -52,13 +52,7 @@ print("Teleporting to (" + data.location.x + ", " + data.location.y + ", " + data.location.z + ")"); MyAvatar.position = data.location; - - // if (data.hasOwnProperty('entryPoint') && data.hasOwnProperty('target')) { - // this.lookAtTarget(data.entryPoint, data.target); - // } - // else{ - // } } } @@ -103,10 +97,4 @@ } } - this.hoverEnterEntity = function(entityID) { - Entities.editEntity(entityID, { - animationURL: animationURL, - animationSettings: '{ "fps": 24, "firstFrame": 1, "lastFrame": 25, "frameIndex": 1, "running": true, "hold": true }' - }); - } }) \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/CellScience/backgroundMusicAC.js b/unpublishedScripts/DomainContent/CellScience/backgroundMusicAC.js index 0ee5b3bf32..1b4c06caaa 100644 --- a/unpublishedScripts/DomainContent/CellScience/backgroundMusicAC.js +++ b/unpublishedScripts/DomainContent/CellScience/backgroundMusicAC.js @@ -7,7 +7,7 @@ var soundMap = [{ y: 15850, z: 15850 }, - volume: 0.1, + volume: 0.03, loop: true } }, { @@ -19,7 +19,7 @@ var soundMap = [{ y: 15950, z: 15950 }, - volume: 0.1, + volume: 0.03, loop: true } }, { @@ -31,7 +31,7 @@ var soundMap = [{ y: 15650, z: 15650 }, - volume: 0.1, + volume: 0.03, loop: true } }, { @@ -43,7 +43,7 @@ var soundMap = [{ y: 15750, z: 15750 }, - volume: 0.1, + volume: 0.03, loop: true } } diff --git a/unpublishedScripts/DomainContent/CellScience/importCellScience.js b/unpublishedScripts/DomainContent/CellScience/importCellScience.js index ca0eb21f21..876df8adb1 100644 --- a/unpublishedScripts/DomainContent/CellScience/importCellScience.js +++ b/unpublishedScripts/DomainContent/CellScience/importCellScience.js @@ -5,7 +5,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var version = 1035; +var version = 1112; var cellLayout; var baseLocation = "https://hifi-content.s3.amazonaws.com/DomainContent/CellScience/"; @@ -103,9 +103,9 @@ var scenes = [{ instances: [{ model: "Cell", dimensions: { - x: 550, - y: 620, - z: 550 + x: 500, + y: 570, + z: 500 }, offset: { x: 0, @@ -151,294 +151,253 @@ var scenes = [{ skybox: "cosmos_skybox_blurred" }, instances: [{ - model: "translation", - dimensions: { - x: 10, - y: 16, - z: 10 - }, - offset: { - x: 0, - y: 0, - z: 0 - }, - radius: 300, - number: 7, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - }, - target: locations.ribosome[1], - location: locations.ribosome[0], - baseURL: baseLocation - }), - script: "zoom.js?" + version, - visible: true - }, { - model: "vesicle", - dimensions: { - x: 60, - y: 60, - z: 60 - }, - randomSize: 10, - offset: { - x: 0, - y: 0, - z: 0 - }, - radius: 1000, - number: 22, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }), - script: "moveRandomly.js?" + version, - visible: true - }, { //golgi vesicles - model: "vesicle", - dimensions: { - x: 10, - y: 10, - z: 10 - }, - randomSize: 10, - offset: { - x: -319, - y: 66, - z: 976 - }, - radius: 140, - number: 10, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }), - script: "", - visible: true - }, { //golgi vesicles - model: "vesicle", - dimensions: { - x: 15, - y: 15, - z: 15 - }, - randomSize: 10, - offset: { - x: -319, - y: 66, - z: 976 - }, - radius: 115, - number: 7, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }), - script: "moveRandomly.js?" + version, - visible: true - }, { - model: "vesicle", - dimensions: { - x: 50, - y: 50, - z: 50 - }, - randomSize: 10, - offset: { - x: 0, - y: 0, - z: 0 - }, - radius: 600, - number: 15, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }), - script: "", - visible: true - }, { //outer vesicles - model: "vesicle", - dimensions: { - x: 60, - y: 60, - z: 60 - }, - randomSize: 10, - offset: { - x: 0, - y: 0, - z: 0 - }, - radius: 1600, - number: 22, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }), - script: "", - visible: true - }, { //outer vesicles - model: "vesicle", - dimensions: { - x: 40, - y: 40, - z: 40 - }, - randomSize: 10, - offset: { - x: 0, - y: 0, - z: 0 - }, - radius: 1400, - number: 22, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }), - script: "moveRandomly.js?" + version, - visible: true - }, { //outer vesicles - model: "vesicle", - dimensions: { - x: 80, - y: 80, - z: 80 - }, - randomSize: 10, - offset: { - x: 0, - y: 0, - z: 0 - }, - radius: 1800, - number: 22, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }), - script: "moveRandomly.js?" + version, - visible: true + model: "translation", + dimensions: { + x: 10, + y: 16, + z: 10 }, - // {//wigglies - // model:"wiggly", - // dimensions:{x:320,y:40,z:160}, - // randomSize: 10, - // offset:{x:0,y:0,z:0}, - // radius:1800, - // number:50, - // userData:"", - // script:"moveRandomly", - // visible:true - // }, - //// {//wigglies - // model:"wiggly", - // dimensions:{x:640,y:80,z:320}, - // randomSize: 10, - // offset:{x:0,y:0,z:0}, - // radius:2100, - // number:50, - // userData:"", - // script:"moveRandomly", - // visible:true - // }, - { - model: "hexokinase", - dimensions: { - x: 3, - y: 4, - z: 3 + offset: { + x: 0, + y: 0, + z: 0 + }, + radius: 300, + number: 7, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false }, - randomSize: 10, - offset: { - x: 236, - y: 8, - z: 771 + target: locations.ribosome[1], + location: locations.ribosome[0], + baseURL: baseLocation + }), + script: "zoom.js?" + version, + visible: true + }, { + model: "vesicle", + dimensions: { + x: 60, + y: 60, + z: 60 + }, + randomSize: 10, + offset: { + x: 0, + y: 0, + z: 0 + }, + radius: 1000, + number: 22, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }), + visible: true + }, { //golgi vesicles + model: "vesicle", + dimensions: { + x: 10, + y: 10, + z: 10 + }, + randomSize: 10, + offset: { + x: -319, + y: 66, + z: 976 + }, + radius: 140, + number: 10, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }), + script: "", + visible: true + }, { //golgi vesicles + model: "vesicle", + dimensions: { + x: 15, + y: 15, + z: 15 + }, + randomSize: 10, + offset: { + x: -319, + y: 66, + z: 976 + }, + radius: 115, + number: 7, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }), + visible: true + }, { + model: "vesicle", + dimensions: { + x: 50, + y: 50, + z: 50 + }, + randomSize: 10, + offset: { + x: 0, + y: 0, + z: 0 + }, + radius: 600, + number: 15, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }), + script: "", + visible: true + }, { //outer vesicles + model: "vesicle", + dimensions: { + x: 60, + y: 60, + z: 60 + }, + randomSize: 10, + offset: { + x: 0, + y: 0, + z: 0 + }, + radius: 1600, + number: 22, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }), + script: "", + visible: true + }, { //outer vesicles + model: "vesicle", + dimensions: { + x: 40, + y: 40, + z: 40 + }, + randomSize: 10, + offset: { + x: 0, + y: 0, + z: 0 + }, + radius: 1400, + number: 22, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }), + visible: true + }, { //outer vesicles + model: "vesicle", + dimensions: { + x: 80, + y: 80, + z: 80 + }, + randomSize: 10, + offset: { + x: 0, + y: 0, + z: 0 + }, + radius: 1800, + number: 22, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }), + visible: true + }, { + model: "hexokinase", + dimensions: { + x: 3, + y: 4, + z: 3 + }, + randomSize: 10, + offset: { + x: 236, + y: 8, + z: 771 + }, + radius: 80, + number: 7, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false }, - radius: 80, - number: 7, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - }, - target: locations.hexokinase[1], - location: locations.hexokinase[0], - baseURL: baseLocation - }), - script: "zoom.js?" + version, - visible: true - }, { - model: "pfructo_kinase", - dimensions: { - x: 3, - y: 4, - z: 3 + target: locations.hexokinase[1], + location: locations.hexokinase[0], + baseURL: baseLocation + }), + script: "zoom.js?" + version, + visible: true + }, { + model: "pfructo_kinase", + dimensions: { + x: 3, + y: 4, + z: 3 + }, + randomSize: 10, + offset: { + x: 236, + y: 8, + z: 771 + }, + radius: 60, + number: 7, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false }, - randomSize: 10, - offset: { - x: 236, - y: 8, - z: 771 + target: locations.hexokinase[1], + location: locations.hexokinase[0], + }), + script: "zoom.js?" + version, + visible: true + }, { + model: "glucose_isomerase", + dimensions: { + x: 3, + y: 4, + z: 3 + }, + randomSize: 10, + offset: { + x: 236, + y: 8, + z: 771 + }, + radius: 70, + number: 7, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false }, - radius: 60, - number: 7, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - }, - target: locations.hexokinase[1], - location: locations.hexokinase[0], - }), - script: "zoom.js?" + version, - visible: true - }, { - model: "glucose_isomerase", - dimensions: { - x: 3, - y: 4, - z: 3 - }, - randomSize: 10, - offset: { - x: 236, - y: 8, - z: 771 - }, - radius: 70, - number: 7, - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - }, - target: locations.hexokinase[1], - location: locations.hexokinase[0], - }), - script: "zoom.js?" + version, - visible: true - } - // { - // model:"NPC", - // dimensions:{x:20,y:20,z:20}, - // randomSize: 10, - // offset:{x:208.593693,y:6.113100222,z:153.3202277}, - // radius:520, - // number:25, - // userData: "", - // script:"", - // visible:true - // } - - - ], + target: locations.hexokinase[1], + location: locations.hexokinase[0], + }), + script: "zoom.js?" + version, + visible: true + }], boundary: { radius: locations.cellLayout[2], center: locations.cellLayout[0], @@ -600,7 +559,7 @@ function ImportScene(scene) { CreateZone(scene); CreateInstances(scene); - CreateBoundary(scene); + // CreateBoundary(scene); // print("done " + scene.name); @@ -609,12 +568,10 @@ function ImportScene(scene) { clearAllNav(); function clearAllNav() { - // print('NAV CLEARING ALL NAV'); var result = Entities.findEntities(MyAvatar.position, 25000); result.forEach(function(r) { var properties = Entities.getEntityProperties(r, "name"); if (properties.name.indexOf('navigation button') > -1) { - // print('NAV DELETING NAV BUTTON AT START:: '+r) Entities.deleteEntity(r); } }) @@ -645,9 +602,6 @@ function createLayoutLights() { } function CreateNavigationButton(scene, number) { - // print('NAV NAVIGATION CREATING NAV!!' +scene.name + " " + number) - - Entities.addEntity({ type: "Box", name: scene.name + " navigation button", @@ -818,7 +772,7 @@ function CreateInstances(scene) { x: 0, y: 0, z: 0 - }, idBounds, 150); + }, idBounds, 150, scene.instances[i]); } //print('SCRIPT AT CREATE ENTITY: ' + script) @@ -831,6 +785,7 @@ function CreateInstances(scene) { function CreateIdentification(name, position, rotation, dimensions, showDistance) { //print ("creating ID for " + name); + Entities.addEntity({ type: "Sphere", name: "ID for " + name, @@ -9045,4 +9000,9 @@ createLayoutLights(); Script.scriptEnding.connect(function() { Entities.addingEntity.disconnect(makeUngrabbable); -}); \ No newline at end of file +}); + +Script.setTimeout(function() { + print('JBP stopping cell science import'); + Script.stop(); +}, 30000) \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/CellScience/motorProteinControllerAC.js b/unpublishedScripts/DomainContent/CellScience/motorProteinControllerAC.js index 672ec1fd92..59577d49ba 100644 --- a/unpublishedScripts/DomainContent/CellScience/motorProteinControllerAC.js +++ b/unpublishedScripts/DomainContent/CellScience/motorProteinControllerAC.js @@ -18,8 +18,6 @@ if (USE_LOCAL_HOST === true) { var USE_LOCAL_HOST = false; -Agent.isAvatar = true; - EntityViewer.setPosition({ x: 3000, y: 13500, diff --git a/unpublishedScripts/DomainContent/CellScience/moveCellsAC.js b/unpublishedScripts/DomainContent/CellScience/moveCellsAC.js new file mode 100644 index 0000000000..cf35e081e0 --- /dev/null +++ b/unpublishedScripts/DomainContent/CellScience/moveCellsAC.js @@ -0,0 +1,103 @@ +// 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 +// + +var basePosition = { + x: 3000, + y: 13500, + z: 3000 +}; + +var initialized = false; + +EntityViewer.setPosition(basePosition); +EntityViewer.setKeyholeRadius(60000); +var octreeQueryInterval = Script.setInterval(function() { + EntityViewer.queryOctree(); +}, 200); + +var THROTTLE = true; +var THROTTLE_RATE = 5000; + +var sinceLastUpdate = 0; + +//print('cells script') + +function findCells() { + var results = Entities.findEntities(basePosition, 60000); + + if (results.length === 0) { + // print('no entities found') + return; + } + + results.forEach(function(v) { + var name = Entities.getEntityProperties(v, 'name').name; + // print('name is:: ' + name) + if (name === 'Cell') { + // print('found a cell!!' + v) + Script.setTimeout(function() { + moveCell(v); + }, Math.random() * THROTTLE_RATE); + } + }); +} + + +var minAngularVelocity = 0.01; +var maxAngularVelocity = 0.03; + +function moveCell(entityId) { + // print('moving a cell! ' + entityId) + + var magnitudeAV = 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(entityId, { + angularVelocity: Vec3.multiply(magnitudeAV, Vec3.normalize(directionAV)) + }); + +} + +function update(deltaTime) { + + // print('deltaTime',deltaTime) + if (!initialized) { + print("checking for servers..."); + if (Entities.serversExist() && Entities.canRez()) { + print("servers exist -- makeAll..."); + Entities.setPacketsPerSecond(6000); + print("PPS:" + Entities.getPacketsPerSecond()); + initialized = true; + } + return; + } + + if (THROTTLE === true) { + sinceLastUpdate = sinceLastUpdate + deltaTime * 1000; + if (sinceLastUpdate > THROTTLE_RATE) { + // print('SHOULD FIND CELLS!!!') + sinceLastUpdate = 0; + findCells(); + } else { + // print('returning in update ' + sinceLastUpdate) + return; + } + } + +} + +function unload() { + Script.update.disconnect(update); +} + +Script.update.connect(update); +Script.scriptEnding.connect(unload); \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/CellScience/moveVesiclesAC.js b/unpublishedScripts/DomainContent/CellScience/moveVesiclesAC.js new file mode 100644 index 0000000000..922f0d94cf --- /dev/null +++ b/unpublishedScripts/DomainContent/CellScience/moveVesiclesAC.js @@ -0,0 +1,108 @@ +// 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 +// + +var basePosition = { + x: 3000, + y: 13500, + z: 3000 +}; + +var initialized = false; + +EntityViewer.setPosition(basePosition); +EntityViewer.setKeyholeRadius(60000); +var octreeQueryInterval = Script.setInterval(function() { + EntityViewer.queryOctree(); +}, 200); + +var THROTTLE = true; +var THROTTLE_RATE = 5000; + +var sinceLastUpdate = 0; + +//print('vesicle script') + +function findVesicles() { + var results = Entities.findEntities(basePosition, 60000); + + if (results.length === 0) { + // print('no entities found'); + return; + } + + results.forEach(function(v) { + var name = Entities.getEntityProperties(v, 'name').name; + if (name === 'vesicle') { + //print('found a vesicle!!' + v) + Script.setTimeout(function() { + moveVesicle(v); + }, Math.random() * THROTTLE_RATE); + } + }); +} + +var minVelocity = 1; +var maxVelocity = 5; +var minAngularVelocity = 0.01; +var maxAngularVelocity = 0.03; + +function moveVesicle(entityId) { + // print('moving a vesicle! ' + entityId) + var magnitudeV = 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); + + var magnitudeAV = 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(entityId, { + velocity: Vec3.multiply(magnitudeV, Vec3.normalize(directionV)), + angularVelocity: Vec3.multiply(magnitudeAV, Vec3.normalize(directionAV)) + }); + +} + +function update(deltaTime) { + if (!initialized) { + print("checking for servers..."); + if (Entities.serversExist() && Entities.canRez()) { + print("servers exist -- makeAll..."); + Entities.setPacketsPerSecond(6000); + print("PPS:" + Entities.getPacketsPerSecond()); + initialized = true; + } + return; + } + + if (THROTTLE === true) { + sinceLastUpdate = sinceLastUpdate + deltaTime * 1000; + if (sinceLastUpdate > THROTTLE_RATE) { + sinceLastUpdate = 0; + findVesicles(); + } else { + return; + } + } + +} + +function unload() { + Script.update.disconnect(update); +} + +Script.update.connect(update); +Script.scriptEnding.connect(unload); \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/Home/tiltMaze/createTiltMaze.js b/unpublishedScripts/DomainContent/Home/tiltMaze/createTiltMaze.js new file mode 100644 index 0000000000..7779bb7a07 --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/tiltMaze/createTiltMaze.js @@ -0,0 +1,272 @@ +// +// createTiltMaze.js +// +// Created by James B. Pollack @imgntn on 2/15/2016 +// Copyright 2016 High Fidelity, Inc. +// +// This script creates a maze with a ball that you can tilt to try to get to the end. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var ball, ballSpawningAnchor, ballDetector, tiltMaze, lightAtTheEnd; + +var MAZE_MODEL_URL = "http://hifi-content.s3.amazonaws.com/DomainContent/Home/tiltMaze/newmaze_tex-4.fbx"; +var MAZE_COLLISION_HULL = "http://hifi-content.s3.amazonaws.com/DomainContent/Home/tiltMaze/newmaze_tex-3.obj"; +var MAZE_SCRIPT = Script.resolvePath('maze.js?' + Math.random()); + +var SCALE = 0.5; + +var MAZE_DIMENSIONS = Vec3.multiply(SCALE, { + x: 1, + y: 0.15, + z: 1 +}); + +var BALL_DIMENSIONS = Vec3.multiply(SCALE, { + x: 0.035, + y: 0.035, + z: 0.035 +}); + +var BALL_SPAWNER_DIMENSIONS = Vec3.multiply(SCALE, { + x: 0.05, + y: 0.05, + z: 0.05 +}); + +var BALL_DETECTOR_DIMENSIONS = Vec3.multiply(SCALE, { + x: 0.1, + y: 0.1, + z: 0.1 +}); + +var BALL_COLOR = { + red: 255, + green: 0, + blue: 0 +}; + +var DEBUG_COLOR = { + red: 0, + green: 255, + blue: 0 +}; + +var center = Vec3.sum(Vec3.sum(MyAvatar.position, { + x: 0, + y: 0.5, + z: 0 +}), Vec3.multiply(1.5, Quat.getFront(Camera.getOrientation()))); + +var CLEANUP = true; + +var BALL_FORWARD_OFFSET = -0.2 * SCALE; +var BALL_RIGHT_OFFSET = -0.4 * SCALE; +var BALL_VERTICAL_OFFSET = 0.02 * SCALE; + +var BALL_FRICTION = 0.7; +var BALL_RESTITUTION = 0.1; +var BALL_DAMPING = 0.6; +var BALL_ANGULAR_DAMPING = 0.2; +var BALL_DENSITY = 1000; +var BALL_GRAVITY = { + x: 0, + y: -9.8, + z: 0 +}; + +var MAZE_DENSITY = 1000; +var MAZE_RESTITUTION = 0.1; +var MAZE_DAMPING = 0.6; +var MAZE_ANGULAR_DAMPING = 0.6; + +var DETECTOR_VERTICAL_OFFSET = 0.0 * SCALE; +var DETECTOR_FORWARD_OFFSET = 0.35 * SCALE; +var DETECTOR_RIGHT_OFFSET = 0.35 * SCALE; + +var END_LIGHT_COLOR = { + red: 255, + green: 0, + blue: 0 +}; + +var END_LIGHT_DIMENSIONS = { + x: 0.2, + y: 0.2, + z: 0.8 +}; + +var END_LIGHT_INTENSITY = 0.035; +var END_LIGHT_CUTOFF = 30; +var END_LIGHT_EXPONENT = 1; + +var getBallStartLocation = function() { + var mazeProps = Entities.getEntityProperties(tiltMaze); + var right = Quat.getRight(mazeProps.rotation); + var front = Quat.getFront(mazeProps.rotation); + var vertical = { + x: 0, + y: BALL_VERTICAL_OFFSET, + z: 0 + }; + + var finalOffset = Vec3.sum(vertical, Vec3.multiply(right, BALL_RIGHT_OFFSET)); + finalOffset = Vec3.sum(finalOffset, Vec3.multiply(front, BALL_FORWARD_OFFSET)); + var location = Vec3.sum(mazeProps.position, finalOffset); + return location; +}; + +var getBallFinishLocation = function() { + var mazeProps = Entities.getEntityProperties(tiltMaze); + var right = Quat.getRight(mazeProps.rotation); + var forward = Quat.getFront(mazeProps.rotation); + var up = Quat.getUp(mazeProps.rotation); + + var position = Vec3.sum(mazeProps.position, Vec3.multiply(up, DETECTOR_VERTICAL_OFFSET)); + position = Vec3.sum(position, Vec3.multiply(right, DETECTOR_RIGHT_OFFSET)); + position = Vec3.sum(position, Vec3.multiply(forward, DETECTOR_FORWARD_OFFSET)); + + return position; +}; + + +var createBall = function(position) { + var properties = { + name: 'Hifi Tilt Maze Ball', + type: 'Sphere', + position: getBallStartLocation(), + dynamic: true, + collisionless: false, + friction: BALL_FRICTION, + restitution: BALL_RESTITUTION, + angularDamping: BALL_ANGULAR_DAMPING, + damping: BALL_DAMPING, + gravity: BALL_GRAVITY, + density: BALL_DENSITY, + color: BALL_COLOR, + dimensions: BALL_DIMENSIONS, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) + + }; + + ball = Entities.addEntity(properties); + +}; + +var createBallSpawningAnchor = function() { + var properties = { + name: 'Hifi Tilt Maze Ball Detector', + parentID: tiltMaze, + type: 'Box', + color: DEBUG_COLOR, + dimensions: BALL_SPAWNER_DIMENSIONS, + position: getBallStartLocation(), + collisionless: true, + visible: false, + }; + + ballSpawningAnchor = Entities.addEntity(properties); +}; + + +var createBallDetector = function() { + + var properties = { + name: 'Hifi Tilt Maze Ball Detector', + parentID: tiltMaze, + type: 'Box', + color: DEBUG_COLOR, + shapeType: 'none', + dimensions: BALL_DETECTOR_DIMENSIONS, + position: getBallFinishLocation(), + collisionless: true, + dynamic: false, + visible: false, + }; + + ballDetector = Entities.addEntity(properties); + +}; + +var createTiltMaze = function(position) { + var properties = { + name: 'Hifi Tilt Maze', + type: 'Model', + modelURL: MAZE_MODEL_URL, + compoundShapeURL: MAZE_COLLISION_HULL, + dimensions: MAZE_DIMENSIONS, + position: position, + restitution: MAZE_RESTITUTION, + damping: MAZE_DAMPING, + angularDamping: MAZE_ANGULAR_DAMPING, + dynamic: true, + density: MAZE_DENSITY, + script: MAZE_SCRIPT + } + + tiltMaze = Entities.addEntity(properties); + +}; + +var createLightAtTheEnd = function() { + + var mazeProps = Entities.getEntityProperties(tiltMaze, 'position'); + + var up = Quat.getUp(mazeProps.rotation); + var down = Vec3.multiply(-1, up); + + var emitOrientation = Quat.rotationBetween(Vec3.UNIT_NEG_Z, down); + + var position = getBallFinishLocation(); + var lightProperties = { + parentID: tiltMaze, + name: 'Hifi Tilt Maze End Light', + type: "Light", + isSpotlight: true, + dimensions: END_LIGHT_DIMENSIONS, + color: END_LIGHT_COLOR, + intensity: END_LIGHT_INTENSITY, + exponent: END_LIGHT_EXPONENT, + cutoff: END_LIGHT_CUTOFF, + lifetime: -1, + position: position, + rotation: emitOrientation + }; + + lightAtTheEnd = Entities.addEntity(lightProperties); +}; + +var createAll = function() { + createTiltMaze(center); + createBallSpawningAnchor(); + createBallDetector(center); + createBall(center); + createLightAtTheEnd(); + Entities.editEntity(tiltMaze, { + userData: JSON.stringify({ + tiltMaze: { + firstBall: ball, + ballSpawner: ballSpawningAnchor, + detector: ballDetector, + lightAtTheEnd: lightAtTheEnd + } + }) + }); +}; + +createAll(); + +if (CLEANUP === true) { + Script.scriptEnding.connect(function() { + Entities.deleteEntity(tiltMaze); + Entities.deleteEntity(ball); + Entities.deleteEntity(ballSpawningAnchor); + Entities.deleteEntity(lightAtTheEnd); + }); +}; \ No newline at end of file diff --git a/unpublishedScripts/DomainContent/Home/tiltMaze/maze.js b/unpublishedScripts/DomainContent/Home/tiltMaze/maze.js new file mode 100644 index 0000000000..557861ba30 --- /dev/null +++ b/unpublishedScripts/DomainContent/Home/tiltMaze/maze.js @@ -0,0 +1,212 @@ +// +// maze.js +// +// +// Created by James B. Pollack @imgntn on 2/15/2016 +// Copyright 2016 High Fidelity, Inc. +// +// This script resets a ball to its original position when the ball enters it. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + + Script.include('../../../../libraries/utils.js'); + + var SCALE = 0.5; + var VICTORY_SOUND; + var BALL_DISTANCE_THRESHOLD = 1 * SCALE; + + var BALL_DETECTOR_THRESHOLD = 0.075 * SCALE; + var BALL_FORWARD_OFFSET = -0.2 * SCALE; + var BALL_RIGHT_OFFSET = -0.4 * SCALE; + var BALL_VERTICAL_OFFSET = 0.02 * SCALE; + + + var BALL_FRICTION = 0.7; + var BALL_RESTITUTION = 0.1; + var BALL_DAMPING = 0.6; + var BALL_ANGULAR_DAMPING = 0.2; + var BALL_DENSITY = 1000; + var BALL_GRAVITY = { + x: 0, + y: -9.8, + z: 0 + }; + + var BALL_DIMENSIONS = Vec3.multiply(SCALE, { + x: 0.05, + y: 0.05, + z: 0.05 + }); + + var BALL_COLOR = { + red: 255, + green: 0, + blue: 0 + }; + + var _this; + + function Maze() { + _this = this; + return; + } + + Maze.prototype = { + ball: null, + ballLocked: false, + preload: function(entityID) { + this.entityID = entityID; + VICTORY_SOUND = SoundCache.getSound("http://hifi-content.s3.amazonaws.com/DomainContent/Home/tiltMaze/levelUp.wav"); + }, + startNearGrab: function() { + //check to make sure a ball is in range, otherwise create one + this.testBallDistance(); + }, + continueNearGrab: function() { + this.testWinDistance(); + this.testBallDistance(); + }, + continueDistantGrab: function() { + this.testBallDistance(); + this.testWinDistance(); + }, + releaseGrab: function() { + this.testBallDistance(); + }, + getBallStartLocation: function() { + var mazeProps = Entities.getEntityProperties(this.entityID); + var right = Quat.getRight(mazeProps.rotation); + var front = Quat.getFront(mazeProps.rotation); + var vertical = { + x: 0, + y: BALL_VERTICAL_OFFSET, + z: 0 + }; + + var finalOffset = Vec3.sum(vertical, Vec3.multiply(right, BALL_RIGHT_OFFSET)); + finalOffset = Vec3.sum(finalOffset, Vec3.multiply(front, BALL_FORWARD_OFFSET)); + var location = Vec3.sum(mazeProps.position, finalOffset); + return location; + }, + createBall: function() { + if (this.ballLocked === true) { + return; + } + + var properties = { + name: 'Hifi Tilt Maze Ball', + type: 'Sphere', + position: this.getBallStartLocation(), + dynamic: true, + collisionless: false, + friction: BALL_FRICTION, + restitution: BALL_RESTITUTION, + angularDamping: BALL_ANGULAR_DAMPING, + damping: BALL_DAMPING, + gravity: BALL_GRAVITY, + density: BALL_DENSITY, + color: BALL_COLOR, + dimensions: BALL_DIMENSIONS + }; + + this.ball = Entities.addEntity(properties); + }, + destroyBall: function() { + var results = Entities.findEntities(MyAvatar.position, 10); + results.forEach(function(result) { + var props = Entities.getEntityProperties(result, ['name']); + var isAMazeBall = props.name.indexOf('Maze Ball'); + if (isAMazeBall > -1 && result === _this.ball) { + Entities.deleteEntity(result); + } + }) + }, + testBallDistance: function() { + if (this.ballLocked === true) { + return; + } + + var userData = Entities.getEntityProperties(this.entityID, 'userData').userData; + var data = null; + try { + data = JSON.parse(userData); + } catch (e) { + // print('error parsing json in maze userdata') + } + if (data === null) { + // print('data is null in userData') + return; + } + + var ballPosition; + if (this.ball === null) { + this.ball = data.tiltMaze.firstBall; + ballPosition = Entities.getEntityProperties(data.tiltMaze.firstBall, 'position').position; + + } else { + ballPosition = Entities.getEntityProperties(this.ball, 'position').position; + } + + var ballSpawnerPosition = Entities.getEntityProperties(data.tiltMaze.ballSpawner, 'position').position; + + var separation = Vec3.distance(ballPosition, ballSpawnerPosition); + if (separation > BALL_DISTANCE_THRESHOLD) { + this.destroyBall(); + this.createBall(); + } + }, + testWinDistance: function() { + if (this.ballLocked === true) { + return; + } + // print('testing win distance') + var userData = Entities.getEntityProperties(this.entityID, 'userData').userData; + var data = null; + try { + data = JSON.parse(userData); + } catch (e) { + // print('error parsing json in maze userdata') + } + if (data === null) { + // print('data is null in userData') + return; + } + + var ballPosition; + if (this.ball === null) { + this.ball = data.tiltMaze.firstBall; + ballPosition = Entities.getEntityProperties(data.tiltMaze.firstBall, 'position').position; + } else { + ballPosition = Entities.getEntityProperties(this.ball, 'position').position; + } + + var ballDetectorPosition = Entities.getEntityProperties(data.tiltMaze.detector, 'position').position; + var separation = Vec3.distance(ballPosition, ballDetectorPosition); + if (separation < BALL_DETECTOR_THRESHOLD) { + this.ballLocked = true; + this.destroyBall(); + this.playVictorySound(); + Script.setTimeout(function() { + _this.ballLocked = false; + _this.createBall(); + }, 1500) + } + }, + playVictorySound: function() { + var position = Entities.getEntityProperties(this.entityID, "position").position; + + var audioProperties = { + volume: 0.25, + position: position + }; + Audio.playSound(VICTORY_SOUND, audioProperties); + + }, + }; + + return new Maze(); +}); \ No newline at end of file