Merge branch 'master' of github.com:highfidelity/hifi into fix-grab-more

This commit is contained in:
Seth Alves 2016-02-29 11:35:58 -08:00
commit 95f9793999
110 changed files with 4194 additions and 2671 deletions

View file

@ -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() {

View file

@ -27,6 +27,8 @@ struct ViewerSendingStats {
quint64 lastEdited;
};
class SimpleEntitySimulation;
class EntityServer : public OctreeServer, public NewlyCreatedEntityHook {
Q_OBJECT
public:
@ -52,8 +54,8 @@ public:
virtual void readAdditionalConfiguration(const QJsonObject& settingsSectionObject) override;
virtual QString serverSubclassStats() override;
virtual void trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& viewerNode) override;
virtual void trackViewerGone(const QUuid& viewerNode) override;
virtual void trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& sessionID) override;
virtual void trackViewerGone(const QUuid& sessionID) override;
public slots:
void pruneDeletedEntities();
@ -65,7 +67,7 @@ private slots:
void handleEntityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
private:
EntitySimulation* _entitySimulation;
SimpleEntitySimulation* _entitySimulation;
QTimer* _pruneDeletedEntitiesTimer = nullptr;
QReadWriteLock _viewerSendingStatsLock;

View file

@ -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();

View file

@ -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() {

View file

@ -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

View file

@ -778,7 +778,7 @@ function chooseFromHighFidelityDomains(clickedButton) {
function createTemporaryDomain() {
swal({
title: 'Create temporary place name',
text: "This will create a temporary place name and domain ID"
text: "This will create a temporary place name and domain ID (valid for 30 days)"
+ " so other users can easily connect to your domain.</br></br>"
+ "In order to make your domain reachable, this will also enable full automatic networking.",
showCancelButton: true,

View file

@ -331,6 +331,7 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
QCryptographicHash::Sha256);
if (rsaPublicKey) {
QByteArray decryptedArray(RSA_size(rsaPublicKey), 0);
int decryptResult = RSA_verify(NID_sha256,
reinterpret_cast<const unsigned char*>(usernameWithToken.constData()),
usernameWithToken.size(),

View file

@ -96,7 +96,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
// make sure we hear about newly connected nodes from our gatekeeper
connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode);
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) {
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) {
// we either read a certificate and private key or were not passed one
// and completed login or did not need to
@ -198,6 +198,7 @@ bool DomainServer::optionallySetupOAuth() {
}
AccountManager& accountManager = AccountManager::getInstance();
accountManager.disableSettingsFilePersistence();
accountManager.setAuthURL(_oauthProviderURL);
_oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString();
@ -371,12 +372,20 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
packetReceiver.registerListener(PacketType::ICEPing, &_gatekeeper, "processICEPingPacket");
packetReceiver.registerListener(PacketType::ICEPingReply, &_gatekeeper, "processICEPingReplyPacket");
packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_gatekeeper, "processICEPeerInformationPacket");
packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied, this, "processICEServerHeartbeatDenialPacket");
// add whatever static assignments that have been parsed to the queue
addStaticAssignmentsToQueue();
}
bool DomainServer::didSetupAccountManagerWithAccessToken() {
if (AccountManager::getInstance().hasValidAccessToken()) {
// we already gave the account manager a valid access token
return true;
}
return resetAccountManagerAccessToken();
}
const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token";
bool DomainServer::resetAccountManagerAccessToken() {
@ -392,13 +401,9 @@ bool DomainServer::resetAccountManagerAccessToken() {
if (accessTokenVariant && accessTokenVariant->canConvert(QMetaType::QString)) {
accessToken = accessTokenVariant->toString();
} else {
qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present.";
qDebug() << "Set an access token via the web interface, in your user or master config"
qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present."
<< "Set an access token via the web interface, in your user or master config"
<< "at keypath metaverse.access_token or in your ENV at key DOMAIN_SERVER_ACCESS_TOKEN";
// clear any existing access token from AccountManager
AccountManager::getInstance().setAccessTokenForCurrentAuthURL(QString());
return false;
}
} else {
@ -424,6 +429,34 @@ bool DomainServer::resetAccountManagerAccessToken() {
}
}
bool DomainServer::optionallySetupAssignmentPayment() {
const QString PAY_FOR_ASSIGNMENTS_OPTION = "pay-for-assignments";
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
if (settingsMap.contains(PAY_FOR_ASSIGNMENTS_OPTION) &&
settingsMap.value(PAY_FOR_ASSIGNMENTS_OPTION).toBool() &&
didSetupAccountManagerWithAccessToken()) {
qDebug() << "Assignments will be paid for via" << qPrintable(_oauthProviderURL.toString());
// assume that the fact we are authing against HF data server means we will pay for assignments
// setup a timer to send transactions to pay assigned nodes every 30 seconds
QTimer* creditSetupTimer = new QTimer(this);
connect(creditSetupTimer, &QTimer::timeout, this, &DomainServer::setupPendingAssignmentCredits);
const qint64 CREDIT_CHECK_INTERVAL_MSECS = 5 * 1000;
creditSetupTimer->start(CREDIT_CHECK_INTERVAL_MSECS);
QTimer* nodePaymentTimer = new QTimer(this);
connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::sendPendingTransactionsToServer);
const qint64 TRANSACTION_SEND_INTERVAL_MSECS = 30 * 1000;
nodePaymentTimer->start(TRANSACTION_SEND_INTERVAL_MSECS);
}
return true;
}
void DomainServer::setupAutomaticNetworking() {
auto nodeList = DependencyManager::get<LimitedNodeList>();
@ -434,9 +467,9 @@ void DomainServer::setupAutomaticNetworking() {
setupICEHeartbeatForFullNetworking();
}
if (!resetAccountManagerAccessToken()) {
qDebug() << "Will not send heartbeat to Metaverse API without an access token.";
qDebug() << "If this is not a temporary domain add an access token to your config file or via the web interface.";
if (!didSetupAccountManagerWithAccessToken()) {
qDebug() << "Cannot send heartbeat to data server without an access token.";
qDebug() << "Add an access token to your config file or via the web interface.";
return;
}
@ -493,19 +526,6 @@ void DomainServer::setupICEHeartbeatForFullNetworking() {
// we need this DS to know what our public IP is - start trying to figure that out now
limitedNodeList->startSTUNPublicSocketUpdate();
// to send ICE heartbeats we'd better have a private key locally with an uploaded public key
auto& accountManager = AccountManager::getInstance();
auto domainID = accountManager.getAccountInfo().getDomainID();
// if we have an access token and we don't have a private key or the current domain ID has changed
// we should generate a new keypair
if (!accountManager.getAccountInfo().hasPrivateKey() || domainID != limitedNodeList->getSessionUUID()) {
accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID());
}
// hookup to the signal from account manager that tells us when keypair is available
connect(&accountManager, &AccountManager::newKeypair, this, &DomainServer::handleKeypairChange);
if (!_iceHeartbeatTimer) {
// setup a timer to heartbeat with the ice-server every so often
_iceHeartbeatTimer = new QTimer { this };
@ -1062,76 +1082,11 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) {
domainUpdateJSON.toUtf8());
}
// TODO: have data-web respond with ice-server hostname to use
void DomainServer::sendHeartbeatToIceServer() {
if (!_iceServerSocket.getAddress().isNull()) {
auto& accountManager = AccountManager::getInstance();
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
if (!accountManager.getAccountInfo().hasPrivateKey()) {
qWarning() << "Cannot send an ice-server heartbeat without a private key for signature.";
qWarning() << "Waiting for keypair generation to complete before sending ICE heartbeat.";
if (!limitedNodeList->getSessionUUID().isNull()) {
accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID());
} else {
qWarning() << "Attempting to send ICE server heartbeat with no domain ID. This is not supported";
}
return;
}
// NOTE: I'd love to specify the correct size for the packet here, but it's a little trickey with
// QDataStream and the possibility of IPv6 address for the sockets.
if (!_iceServerHeartbeatPacket) {
_iceServerHeartbeatPacket = NLPacket::create(PacketType::ICEServerHeartbeat);
}
bool shouldRecreatePacket = false;
if (_iceServerHeartbeatPacket->getPayloadSize() > 0) {
// if either of our sockets have changed we need to re-sign the heartbeat
// first read the sockets out from the current packet
_iceServerHeartbeatPacket->seek(0);
QDataStream heartbeatStream(_iceServerHeartbeatPacket.get());
QUuid senderUUID;
HifiSockAddr publicSocket, localSocket;
heartbeatStream >> senderUUID >> publicSocket >> localSocket;
if (senderUUID != limitedNodeList->getSessionUUID()
|| publicSocket != limitedNodeList->getPublicSockAddr()
|| localSocket != limitedNodeList->getLocalSockAddr()) {
shouldRecreatePacket = true;
}
} else {
shouldRecreatePacket = true;
}
if (shouldRecreatePacket) {
// either we don't have a heartbeat packet yet or some combination of sockets, ID and keypair have changed
// and we need to make a new one
// reset the position in the packet before writing
_iceServerHeartbeatPacket->reset();
// write our plaintext data to the packet
QDataStream heartbeatDataStream(_iceServerHeartbeatPacket.get());
heartbeatDataStream << limitedNodeList->getSessionUUID()
<< limitedNodeList->getPublicSockAddr() << limitedNodeList->getLocalSockAddr();
// setup a QByteArray that points to the plaintext data
auto plaintext = QByteArray::fromRawData(_iceServerHeartbeatPacket->getPayload(), _iceServerHeartbeatPacket->getPayloadSize());
// generate a signature for the plaintext data in the packet
auto signature = accountManager.getAccountInfo().signPlaintext(plaintext);
// pack the signature with the data
heartbeatDataStream << signature;
}
// send the heartbeat packet to the ice server now
limitedNodeList->sendUnreliablePacket(*_iceServerHeartbeatPacket, _iceServerSocket);
DependencyManager::get<LimitedNodeList>()->sendHeartbeatToIceServer(_iceServerSocket);
}
}
@ -2015,31 +1970,3 @@ void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMes
});
}
}
void DomainServer::processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message) {
static const int NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN = 3;
static int numHeartbeatDenials = 0;
if (++numHeartbeatDenials > NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN) {
qDebug() << "Received" << NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN << "heartbeat denials from ice-server"
<< "- re-generating keypair now";
// we've hit our threshold of heartbeat denials, trigger a keypair re-generation
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
AccountManager::getInstance().generateNewDomainKeypair(limitedNodeList->getSessionUUID());
// reset our number of heartbeat denials
numHeartbeatDenials = 0;
}
}
void DomainServer::handleKeypairChange() {
if (_iceServerHeartbeatPacket) {
// reset the payload size of the ice-server heartbeat packet - this causes the packet to be re-generated
// the next time we go to send an ice-server heartbeat
_iceServerHeartbeatPacket->setPayloadSize(0);
// send a heartbeat to the ice server immediately
sendHeartbeatToIceServer();
}
}

View file

@ -61,7 +61,6 @@ public slots:
void processNodeJSONStatsPacket(QSharedPointer<ReceivedMessage> packetList, SharedNodePointer sendingNode);
void processPathQueryPacket(QSharedPointer<ReceivedMessage> packet);
void processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMessage> message);
void processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message);
private slots:
void aboutToQuit();
@ -79,16 +78,16 @@ private slots:
void handleTempDomainError(QNetworkReply& requestReply);
void queuedQuit(QString quitMessage, int exitCode);
void handleKeypairChange();
private:
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
bool optionallySetupOAuth();
bool optionallyReadX509KeyAndCertificate();
bool optionallySetupAssignmentPayment();
void optionallyGetTemporaryName(const QStringList& arguments);
bool didSetupAccountManagerWithAccessToken();
bool resetAccountManagerAccessToken();
void setupAutomaticNetworking();
@ -154,7 +153,6 @@ private:
DomainServerSettingsManager _settingsManager;
HifiSockAddr _iceServerSocket;
std::unique_ptr<NLPacket> _iceServerHeartbeatPacket;
QTimer* _iceHeartbeatTimer { nullptr }; // this looks like it dangles when created but it's parented to the DomainServer

View file

@ -95,9 +95,9 @@ EntityViewer.setPosition({
y: 0,
z: 0
});
EntityViewer.setKeyholeRadius(60000);
EntityViewer.setCenterRadius(60000);
var octreeQueryInterval = Script.setInterval(function() {
EntityViewer.queryOctree();
}, 1000);
Script.update.connect(update);
Script.update.connect(update);

View file

@ -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
//

View file

@ -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) {

View file

@ -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;
}
}
}

View file

@ -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);
}
});

View file

@ -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,

View file

@ -72,10 +72,13 @@
};
}
function createEmitNumberPropertyUpdateFunction(propertyName) {
function createEmitNumberPropertyUpdateFunction(propertyName, decimals) {
decimals = decimals == undefined ? 4 : decimals;
return function() {
var value = parseFloat(this.value).toFixed(decimals);
EventBridge.emitWebEvent(
'{ "type":"update", "properties":{"' + propertyName + '":' + parseFloat(this.value).toFixed(4) + '}}'
'{ "type":"update", "properties":{"' + propertyName + '":' + value + '}}'
);
};
}
@ -323,6 +326,7 @@
var elLightColorBlue = document.getElementById("property-light-color-blue");
var elLightIntensity = document.getElementById("property-light-intensity");
var elLightFalloffRadius = document.getElementById("property-light-falloff-radius");
var elLightExponent = document.getElementById("property-light-exponent");
var elLightCutoff = document.getElementById("property-light-cutoff");
@ -604,9 +608,10 @@
elLightColorGreen.value = properties.color.green;
elLightColorBlue.value = properties.color.blue;
elLightIntensity.value = properties.intensity;
elLightExponent.value = properties.exponent;
elLightCutoff.value = properties.cutoff;
elLightIntensity.value = properties.intensity.toFixed(1);
elLightFalloffRadius.value = properties.falloffRadius.toFixed(1);
elLightExponent.value = properties.exponent.toFixed(2);
elLightCutoff.value = properties.cutoff.toFixed(2);
} else if (properties.type == "Zone") {
for (var i = 0; i < elZoneSections.length; i++) {
elZoneSections[i].style.display = 'block';
@ -795,9 +800,10 @@
}
})
elLightIntensity.addEventListener('change', createEmitNumberPropertyUpdateFunction('intensity'));
elLightExponent.addEventListener('change', createEmitNumberPropertyUpdateFunction('exponent'));
elLightCutoff.addEventListener('change', createEmitNumberPropertyUpdateFunction('cutoff'));
elLightIntensity.addEventListener('change', createEmitNumberPropertyUpdateFunction('intensity', 1));
elLightFalloffRadius.addEventListener('change', createEmitNumberPropertyUpdateFunction('falloffRadius', 1));
elLightExponent.addEventListener('change', createEmitNumberPropertyUpdateFunction('exponent', 2));
elLightCutoff.addEventListener('change', createEmitNumberPropertyUpdateFunction('cutoff', 2));
elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl'));
@ -1043,25 +1049,25 @@
<div class="text-section property">
<div class="label">Line Height</div>
<div class="value">
<input class="coord" type='number' id="property-text-line-height" min="0" step="0.005">
<input class="coord" type="number" id="property-text-line-height" min="0" step="0.005">
</div>
</div>
<div class="text-section property">
<div class="label">Text Color</div>
<div class="value">
<div class='color-picker' id="property-text-text-color"></div>
<div class="input-area">R <input class="coord" type='number' id="property-text-text-color-red"></div>
<div class="input-area">G <input class="coord" type='number' id="property-text-text-color-green"></div>
<div class="input-area">B <input class="coord" type='number' id="property-text-text-color-blue"></div>
<div class="input-area">R <input class="coord" type="number" id="property-text-text-color-red"></div>
<div class="input-area">G <input class="coord" type="number" id="property-text-text-color-green"></div>
<div class="input-area">B <input class="coord" type="number" id="property-text-text-color-blue"></div>
</div>
</div>
<div class="text-section property">
<div class="label">Background Color</div>
<div class="value">
<div class='color-picker' id="property-text-background-color"></div>
<div class="input-area">R <input class="coord" type='number' id="property-text-background-color-red"></div>
<div class="input-area">G <input class="coord" type='number' id="property-text-background-color-green"></div>
<div class="input-area">B <input class="coord" type='number' id="property-text-background-color-blue"></div>
<div class="input-area">R <input class="coord" type="number" id="property-text-background-color-red"></div>
<div class="input-area">G <input class="coord" type="number" id="property-text-background-color-green"></div>
<div class="input-area">B <input class="coord" type="number" id="property-text-background-color-blue"></div>
</div>
</div>
@ -1085,32 +1091,32 @@
<div class="label">Light Color</div>
<div class="value">
<div class='color-picker' id="property-zone-key-light-color"></div>
<div class="input-area">R <input class="coord" type='number' id="property-zone-key-light-color-red" min="0" max="255" step="1"></div>
<div class="input-area">G <input class="coord" type='number' id="property-zone-key-light-color-green" min="0" max="255" step="1"></div>
<div class="input-area">B <input class="coord" type='number' id="property-zone-key-light-color-blue" min="0" max="255" step="1"></div>
<div class="input-area">R <input class="coord" type="number" id="property-zone-key-light-color-red" min="0" max="255" step="1"></div>
<div class="input-area">G <input class="coord" type="number" id="property-zone-key-light-color-green" min="0" max="255" step="1"></div>
<div class="input-area">B <input class="coord" type="number" id="property-zone-key-light-color-blue" min="0" max="255" step="1"></div>
</div>
</div>
<div class="zone-section keyLight-section property">
<div class="label">Light Intensity</div>
<div class="value">
<input class="coord" type='number' id="property-zone-key-intensity" min="0" max="10" step="0.1">
<input class="coord" type="number" id="property-zone-key-intensity" min="0" max="10" step="0.1">
</div>
</div>
<div class="zone-section keyLight-section property">
<div class="label">Light Direction</div>
<div class="value">
<div class="input-area">Pitch <input class="coord" type='number' id="property-zone-key-light-direction-x"></div>
<div class="input-area">Yaw <input class="coord" type='number' id="property-zone-key-light-direction-y"></div>
<div class="input-area">Roll <input class="coord" type='number' id="property-zone-key-light-direction-z"></div>
<div class="input-area">Pitch <input class="coord" type="number" id="property-zone-key-light-direction-x"></div>
<div class="input-area">Yaw <input class="coord" type="number" id="property-zone-key-light-direction-y"></div>
<div class="input-area">Roll <input class="coord" type="number" id="property-zone-key-light-direction-z"></div>
</div>
</div>
<div class="zone-section keyLight-section property">
<div class="label">Ambient Intensity</div>
<div class="value">
<input class="coord" type='number' id="property-zone-key-ambient-intensity" min="0" max="10" step="0.1">
<input class="coord" type="number" id="property-zone-key-ambient-intensity" min="0" max="10" step="0.1">
</div>
</div>
@ -1129,19 +1135,19 @@
<div class="zone-section stage-section property">
<div class="label">Stage Latitude</div>
<div class="value">
<input class="coord" type='number' id="property-zone-stage-latitude" min="-90" max="90" step="1">
<input class="coord" type="number" id="property-zone-stage-latitude" min="-90" max="90" step="1">
</div>
</div>
<div class="zone-section stage-section property">
<div class="label">Stage Longitude</div>
<div class="value">
<input class="coord" type='number' id="property-zone-stage-longitude" min="-180" max="180" step="1">
<input class="coord" type="number" id="property-zone-stage-longitude" min="-180" max="180" step="1">
</div>
</div>
<div class="zone-section stage-section property">
<div class="label">Stage Altitude</div>
<div class="value">
<input class="coord" type='number' id="property-zone-stage-altitude" step="1">
<input class="coord" type="number" id="property-zone-stage-altitude" step="1">
</div>
</div>
@ -1155,13 +1161,13 @@
<div class="zone-section stage-section property">
<div class="label">Stage Day</div>
<div class="value">
<input class="coord" type='number' id="property-zone-stage-day" min="0" max="365" step="1">
<input class="coord" type="number" id="property-zone-stage-day" min="0" max="365" step="1">
</div>
</div>
<div class="zone-section stage-section property">
<div class="label">Stage Hour</div>
<div class="value">
<input class="coord" type='number' id="property-zone-stage-hour" min="0" max="24" step="0.5">
<input class="coord" type="number" id="property-zone-stage-hour" min="0" max="24" step="0.5">
</div>
</div>
@ -1186,9 +1192,9 @@
<div class="label">Skybox Color</div>
<div class="value">
<div class='color-picker' id="property-zone-skybox-color"></div>
<div class="input-area">R <input class="coord" type='number' id="property-zone-skybox-color-red"></div>
<div class="input-area">G <input class="coord" type='number' id="property-zone-skybox-color-green"></div>
<div class="input-area">B <input class="coord" type='number' id="property-zone-skybox-color-blue"></div>
<div class="input-area">R <input class="coord" type="number" id="property-zone-skybox-color-red"></div>
<div class="input-area">G <input class="coord" type="number" id="property-zone-skybox-color-green"></div>
<div class="input-area">B <input class="coord" type="number" id="property-zone-skybox-color-blue"></div>
</div>
</div>
@ -1264,23 +1270,23 @@
<div class="property">
<div class="label">Registration</div>
<div class="value">
<div class="input-area">X <input class="coord" type='number' id="property-reg-x"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type='number' id="property-reg-y"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type='number' id="property-reg-z"><div class="prop-z"></div></div>
<div class="input-area">X <input class="coord" type="number" id="property-reg-x"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type="number" id="property-reg-y"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type="number" id="property-reg-z"><div class="prop-z"></div></div>
</div>
</div>
<div class="property">
<div class="label">Dimensions</div>
<div class="value">
<div class="input-area">X <input class="coord" type='number' id="property-dim-x" step="0.1"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type='number' id="property-dim-y" step="0.1"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type='number' id="property-dim-z" step="0.1"><div class="prop-z"></div></div>
<div class="input-area">X <input class="coord" type="number" id="property-dim-x" step="0.1"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type="number" id="property-dim-y" step="0.1"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type="number" id="property-dim-z" step="0.1"><div class="prop-z"></div></div>
<div>
<input type="button" id="reset-to-natural-dimensions" value="Reset to Natural Dimensions">
</div>
<div class="input-area">
<input class="" type='number' id="dimension-rescale-pct" value=100>%
<input class="" type="number" id="dimension-rescale-pct" value=100>%
</div>
<span>
<input type="button" id="dimension-rescale-button" value="Rescale">
@ -1291,9 +1297,9 @@
<div class="poly-vox-section property">
<div class="label">Voxel Volume Size</div>
<div class="value">
<div class="input-area">X <br> <input class="coord" type='number' id="property-voxel-volume-size-x"></div>
<div class="input-area">Y <br><input class="coord" type='number' id="property-voxel-volume-size-y"></div>
<div class="input-area">Z <br><input class="coord" type='number' id="property-voxel-volume-size-z"></div>
<div class="input-area">X <br> <input class="coord" type="number" id="property-voxel-volume-size-x"></div>
<div class="input-area">Y <br><input class="coord" type="number" id="property-voxel-volume-size-y"></div>
<div class="input-area">Z <br><input class="coord" type="number" id="property-voxel-volume-size-z"></div>
</div>
<div class="label">Surface Extractor</div>
@ -1325,9 +1331,9 @@
<div class="property">
<div class="label">Rotation</div>
<div class="value">
<div class="input-area">Pitch <input class="coord" type='number' id="property-rot-x" step="0.1"></div>
<div class="input-area">Yaw <input class="coord" type='number' id="property-rot-y" step="0.1"></div>
<div class="input-area">Roll <input class="coord" type='number' id="property-rot-z"step="0.1"></div>
<div class="input-area">Pitch <input class="coord" type="number" id="property-rot-x" step="0.1"></div>
<div class="input-area">Yaw <input class="coord" type="number" id="property-rot-y" step="0.1"></div>
<div class="input-area">Roll <input class="coord" type="number" id="property-rot-z"step="0.1"></div>
</div>
</div>
@ -1339,66 +1345,66 @@
<div class="property">
<div class="label">Linear Velocity</div>
<div class="value">
<div class="input-area">X <input class="coord" type='number' id="property-lvel-x"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type='number' id="property-lvel-y"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type='number' id="property-lvel-z"><div class="prop-z"></div></div>
<div class="input-area">X <input class="coord" type="number" id="property-lvel-x"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type="number" id="property-lvel-y"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type="number" id="property-lvel-z"><div class="prop-z"></div></div>
</div>
</div>
<div class="property">
<div class="label">Linear Damping</div>
<div class="value">
<input class="coord" type='number' id="property-ldamping">
<input class="coord" type="number" id="property-ldamping">
</div>
</div>
<div class="property">
<div class="label">Angular Velocity</div>
<div class="value">
<div class="input-area">Pitch <input class="coord" type='number' id="property-avel-x"></div>
<div class="input-area">Yaw <input class="coord" type='number' id="property-avel-y"></div>
<div class="input-area">Roll <input class="coord" type='number' id="property-avel-z"></div>
<div class="input-area">Pitch <input class="coord" type="number" id="property-avel-x"></div>
<div class="input-area">Yaw <input class="coord" type="number" id="property-avel-y"></div>
<div class="input-area">Roll <input class="coord" type="number" id="property-avel-z"></div>
</div>
</div>
<div class="property">
<div class="label">Angular Damping</div>
<div class="value">
<input class="coord" type='number' id="property-adamping">
<input class="coord" type="number" id="property-adamping">
</div>
</div>
<div class="property">
<div class="label">Restitution</div>
<div class="value">
<input class="coord" type='number' id="property-restitution">
<input class="coord" type="number" id="property-restitution">
</div>
</div>
<div class="property">
<div class="label">Friction</div>
<div class="value">
<input class="coord" type='number' id="property-friction">
<input class="coord" type="number" id="property-friction">
</div>
</div>
<div class="property">
<div class="label">Gravity</div>
<div class="value">
<div class="input-area">X <input class="coord" type='number' id="property-grav-x"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type='number' id="property-grav-y"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type='number' id="property-grav-z"><div class="prop-z"></div></div>
<div class="input-area">X <input class="coord" type="number" id="property-grav-x"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type="number" id="property-grav-y"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type="number" id="property-grav-z"><div class="prop-z"></div></div>
</div>
</div>
<div class="property">
<div class="label">Acceleration</div>
<div class="value">
<div class="input-area">X <input class="coord" type='number' id="property-lacc-x"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type='number' id="property-lacc-y"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type='number' id="property-lacc-z"><div class="prop-z"></div></div>
<div class="input-area">X <input class="coord" type="number" id="property-lacc-x"><div class="prop-x"></div></div>
<div class="input-area">Y <input class="coord" type="number" id="property-lacc-y"><div class="prop-y"></div></div>
<div class="input-area">Z <input class="coord" type="number" id="property-lacc-z"><div class="prop-z"></div></div>
</div>
</div>
<div class="property">
<div class="label">Density</div>
<div>
<input type='number' id="property-density">
<input type="number" id="property-density">
</div>
</div>
@ -1406,9 +1412,9 @@
<div class="label">Color</div>
<div class="value">
<div id="property-color" class='color-picker'></div>
<div class="input-area">R <input class="coord" type='number' id="property-color-red"></div>
<div class="input-area">G <input class="coord" type='number' id="property-color-green"></div>
<div class="input-area">B <input class="coord" type='number' id="property-color-blue"></div>
<div class="input-area">R <input class="coord" type="number" id="property-color-red"></div>
<div class="input-area">G <input class="coord" type="number" id="property-color-green"></div>
<div class="input-area">B <input class="coord" type="number" id="property-color-blue"></div>
</div>
</div>
@ -1483,7 +1489,7 @@
<div class="property">
<div class="label">Lifetime</div>
<div class="value">
<input type='number' id="property-lifetime">
<input type="number" id="property-lifetime">
</div>
</div>
@ -1541,25 +1547,25 @@
<div class="model-section property">
<div class="label">Animation FPS</div>
<div class="value">
<input class="coord" type='number' id="property-model-animation-fps">
<input class="coord" type="number" id="property-model-animation-fps">
</div>
</div>
<div class="model-section property">
<div class="label">Animation Frame</div>
<div class="value">
<input class="coord" type='number' id="property-model-animation-frame">
<input class="coord" type="number" id="property-model-animation-frame">
</div>
</div>
<div class="model-section property">
<div class="label">Animation First Frame</div>
<div class="value">
<input class="coord" type='number' id="property-model-animation-first-frame">
<input class="coord" type="number" id="property-model-animation-first-frame">
</div>
</div>
<div class="model-section property">
<div class="label">Animation Last Frame</div>
<div class="value">
<input class="coord" type='number' id="property-model-animation-last-frame">
<input class="coord" type="number" id="property-model-animation-last-frame">
</div>
</div>
<div class="model-section property">
@ -1591,37 +1597,43 @@
<label>Light</label>
</div>
<div class="light-section property">
<div class="label">Color</div>
<div class="value">
<div class='color-picker' id="property-light-color"></div>
<div class="input-area">R <input class="coord" type="number" id="property-light-color-red"></div>
<div class="input-area">G <input class="coord" type="number" id="property-light-color-green"></div>
<div class="input-area">B <input class="coord" type="number" id="property-light-color-blue"></div>
</div>
</div>
<div class="light-section property">
<div class="label">Intensity</div>
<div class="value">
<input class="coord" type="number" id="property-light-intensity" min="0" step="0.1">
</div>
</div>
<div class="light-section property">
<div class="label">Falloff Radius</div>
<div class="value">
<input class="coord" type="number" id="property-light-falloff-radius" min="0" step="0.1">
</div>
</div>
<div class="light-section property">
<span class="label">Spot Light</span>
<span class="value">
<input type='checkbox' id="property-light-spot-light">
</span>
</div>
<div class="light-section property">
<div class="label">Color</div>
<div class="value">
<div class='color-picker' id="property-light-color"></div>
<div class="input-area">R <input class="coord" type='number' id="property-light-color-red"></div>
<div class="input-area">G <input class="coord" type='number' id="property-light-color-green"></div>
<div class="input-area">B <input class="coord" type='number' id="property-light-color-blue"></div>
</div>
</div>
<div class="light-section property">
<div class="label">Intensity</div>
<div class="value">
<input class="coord" type='number' id="property-light-intensity">
</div>
</div>
<div class="light-section property">
<div class="label">Spot Light Exponent</div>
<div class="value">
<input class="coord" type='number' id="property-light-exponent">
<input class="coord" type="number" id="property-light-exponent" step="0.01">
</div>
</div>
<div class="light-section property">
<div class="label">Spot Light Cutoff (degrees)</div>
<div class="value">
<input class="coord" type='number' id="property-light-cutoff">
<input class="coord" type="number" id="property-light-cutoff" step="0.01">
</div>
</div>
</div>

View file

@ -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})

View file

@ -9,21 +9,14 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "IceServer.h"
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <QtCore/QJsonDocument>
#include <QtCore/QTimer>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>
#include <QTimer>
#include <LimitedNodeList.h>
#include <NetworkingConstants.h>
#include <udt/PacketHeaders.h>
#include <SharedUtil.h>
#include "IceServer.h"
const int CLEAR_INACTIVE_PEERS_INTERVAL_MSECS = 1 * 1000;
const int PEER_SILENCE_THRESHOLD_MSECS = 5 * 1000;
@ -52,6 +45,7 @@ IceServer::IceServer(int argc, char* argv[]) :
QTimer* inactivePeerTimer = new QTimer(this);
connect(inactivePeerTimer, &QTimer::timeout, this, &IceServer::clearInactivePeers);
inactivePeerTimer->start(CLEAR_INACTIVE_PEERS_INTERVAL_MSECS);
}
bool IceServer::packetVersionMatch(const udt::Packet& packet) {
@ -76,14 +70,9 @@ void IceServer::processPacket(std::unique_ptr<udt::Packet> packet) {
if (nlPacket->getType() == PacketType::ICEServerHeartbeat) {
SharedNetworkPeer peer = addOrUpdateHeartbeatingPeer(*nlPacket);
if (peer) {
// so that we can send packets to the heartbeating peer when we need, we need to activate a socket now
peer->activateMatchingOrNewSymmetricSocket(nlPacket->getSenderSockAddr());
} else {
// we couldn't verify this peer - respond back to them so they know they may need to perform keypair re-generation
static auto deniedPacket = NLPacket::create(PacketType::ICEServerHeartbeatDenied);
_serverSocket.writePacket(*deniedPacket, nlPacket->getSenderSockAddr());
}
// so that we can send packets to the heartbeating peer when we need, we need to activate a socket now
peer->activateMatchingOrNewSymmetricSocket(nlPacket->getSenderSockAddr());
} else if (nlPacket->getType() == PacketType::ICEServerQuery) {
QDataStream heartbeatStream(nlPacket.get());
@ -125,135 +114,31 @@ SharedNetworkPeer IceServer::addOrUpdateHeartbeatingPeer(NLPacket& packet) {
// pull the UUID, public and private sock addrs for this peer
QUuid senderUUID;
HifiSockAddr publicSocket, localSocket;
QByteArray signature;
QDataStream heartbeatStream(&packet);
heartbeatStream >> senderUUID >> publicSocket >> localSocket;
heartbeatStream >> senderUUID;
heartbeatStream >> publicSocket >> localSocket;
auto signedPlaintext = QByteArray::fromRawData(packet.getPayload(), heartbeatStream.device()->pos());
heartbeatStream >> signature;
// make sure we have this sender in our peer hash
SharedNetworkPeer matchingPeer = _activePeers.value(senderUUID);
// make sure this is a verified heartbeat before performing any more processing
if (isVerifiedHeartbeat(senderUUID, signedPlaintext, signature)) {
// make sure we have this sender in our peer hash
SharedNetworkPeer matchingPeer = _activePeers.value(senderUUID);
if (!matchingPeer) {
// if we don't have this sender we need to create them now
matchingPeer = QSharedPointer<NetworkPeer>::create(senderUUID, publicSocket, localSocket);
_activePeers.insert(senderUUID, matchingPeer);
if (!matchingPeer) {
// if we don't have this sender we need to create them now
matchingPeer = QSharedPointer<NetworkPeer>::create(senderUUID, publicSocket, localSocket);
_activePeers.insert(senderUUID, matchingPeer);
qDebug() << "Added a new network peer" << *matchingPeer;
} else {
// we already had the peer so just potentially update their sockets
matchingPeer->setPublicSocket(publicSocket);
matchingPeer->setLocalSocket(localSocket);
}
// update our last heard microstamp for this network peer to now
matchingPeer->setLastHeardMicrostamp(usecTimestampNow());
return matchingPeer;
qDebug() << "Added a new network peer" << *matchingPeer;
} else {
// not verified, return the empty peer object
return SharedNetworkPeer();
}
}
bool IceServer::isVerifiedHeartbeat(const QUuid& domainID, const QByteArray& plaintext, const QByteArray& signature) {
// check if we have a private key for this domain ID - if we do not then fire off the request for it
auto it = _domainPublicKeys.find(domainID);
if (it != _domainPublicKeys.end()) {
// attempt to verify the signature for this heartbeat
const unsigned char* publicKeyData = reinterpret_cast<const unsigned char*>(it->second.constData());
// first load up the public key into an RSA struct
RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, it->second.size());
if (rsaPublicKey) {
auto hashedPlaintext = QCryptographicHash::hash(plaintext, QCryptographicHash::Sha256);
int verificationResult = RSA_verify(NID_sha256,
reinterpret_cast<const unsigned char*>(hashedPlaintext.constData()),
hashedPlaintext.size(),
reinterpret_cast<const unsigned char*>(signature.constData()),
signature.size(),
rsaPublicKey);
// free up the public key and remove connection token before we return
RSA_free(rsaPublicKey);
if (verificationResult == 1) {
// this is the only success case - we return true here to indicate that the heartbeat is verified
return true;
} else {
qDebug() << "Failed to verify heartbeat for" << domainID << "- re-requesting public key from API.";
}
} else {
// we can't let this user in since we couldn't convert their public key to an RSA key we could use
qWarning() << "Could not convert in-memory public key for" << domainID << "to usable RSA public key.";
qWarning() << "Re-requesting public key from API";
}
// we already had the peer so just potentially update their sockets
matchingPeer->setPublicSocket(publicSocket);
matchingPeer->setLocalSocket(localSocket);
}
// we could not verify this heartbeat (missing public key, could not load public key, bad actor)
// ask the metaverse API for the right public key and return false to indicate that this is not verified
requestDomainPublicKey(domainID);
// update our last heard microstamp for this network peer to now
matchingPeer->setLastHeardMicrostamp(usecTimestampNow());
return false;
}
void IceServer::requestDomainPublicKey(const QUuid& domainID) {
// send a request to the metaverse API for the public key for this domain
QNetworkAccessManager* manager = new QNetworkAccessManager { this };
connect(manager, &QNetworkAccessManager::finished, this, &IceServer::publicKeyReplyFinished);
QUrl publicKeyURL { NetworkingConstants::METAVERSE_SERVER_URL };
QString publicKeyPath = QString("/api/v1/domains/%1/public_key").arg(uuidStringWithoutCurlyBraces(domainID));
publicKeyURL.setPath(publicKeyPath);
QNetworkRequest publicKeyRequest { publicKeyURL };
publicKeyRequest.setAttribute(QNetworkRequest::User, domainID);
qDebug() << "Requesting public key for domain with ID" << domainID;
manager->get(publicKeyRequest);
}
void IceServer::publicKeyReplyFinished(QNetworkReply* reply) {
// get the domain ID from the QNetworkReply attribute
QUuid domainID = reply->request().attribute(QNetworkRequest::User).toUuid();
if (reply->error() == QNetworkReply::NoError) {
// pull out the public key and store it for this domain
// the response should be JSON
QJsonDocument responseDocument = QJsonDocument::fromJson(reply->readAll());
static const QString DATA_KEY = "data";
static const QString PUBLIC_KEY_KEY = "public_key";
static const QString STATUS_KEY = "status";
static const QString SUCCESS_VALUE = "success";
auto responseObject = responseDocument.object();
if (responseObject[STATUS_KEY].toString() == SUCCESS_VALUE) {
auto dataObject = responseObject[DATA_KEY].toObject();
if (dataObject.contains(PUBLIC_KEY_KEY)) {
_domainPublicKeys[domainID] = QByteArray::fromBase64(dataObject[PUBLIC_KEY_KEY].toString().toUtf8());
} else {
qWarning() << "There was no public key present in response for domain with ID" << domainID;
}
} else {
qWarning() << "The metaverse API did not return success for public key request for domain with ID" << domainID;
}
} else {
// there was a problem getting the public key for the domain
// log it since it will be re-requested on the next heartbeat
qWarning() << "Error retreiving public key for domain with ID" << domainID << "-" << reply->errorString();
}
return matchingPeer;
}
void IceServer::sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr) {

View file

@ -16,15 +16,13 @@
#include <QtCore/QSharedPointer>
#include <QUdpSocket>
#include <UUIDHasher.h>
#include <NetworkPeer.h>
#include <HTTPConnection.h>
#include <HTTPManager.h>
#include <NLPacket.h>
#include <udt/Socket.h>
class QNetworkReply;
typedef QHash<QUuid, SharedNetworkPeer> NetworkPeerHash;
class IceServer : public QCoreApplication, public HTTPRequestHandler {
Q_OBJECT
@ -33,7 +31,6 @@ public:
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
private slots:
void clearInactivePeers();
void publicKeyReplyFinished(QNetworkReply* reply);
private:
bool packetVersionMatch(const udt::Packet& packet);
void processPacket(std::unique_ptr<udt::Packet> packet);
@ -41,19 +38,10 @@ private:
SharedNetworkPeer addOrUpdateHeartbeatingPeer(NLPacket& incomingPacket);
void sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr);
bool isVerifiedHeartbeat(const QUuid& domainID, const QByteArray& plaintext, const QByteArray& signature);
void requestDomainPublicKey(const QUuid& domainID);
QUuid _id;
udt::Socket _serverSocket;
using NetworkPeerHash = QHash<QUuid, SharedNetworkPeer>;
NetworkPeerHash _activePeers;
HTTPManager _httpManager;
using DomainPublicKeyHash = std::unordered_map<QUuid, QByteArray>;
DomainPublicKeyHash _domainPublicKeys;
};
#endif // hifi_IceServer_h

View file

@ -210,8 +210,6 @@ static const QString INFO_EDIT_ENTITIES_PATH = "html/edit-commands.html";
static const unsigned int THROTTLED_SIM_FRAMERATE = 15;
static const int THROTTLED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / THROTTLED_SIM_FRAMERATE;
static const unsigned int CAPPED_SIM_FRAMERATE = 120;
static const int CAPPED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / CAPPED_SIM_FRAMERATE;
static const uint32_t INVALID_FRAME = UINT32_MAX;
@ -425,11 +423,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
_maxOctreePPS(maxOctreePacketsPerSecond.get()),
_lastFaceTrackerUpdate(0)
{
// FIXME this may be excessivly conservative. On the other hand
// FIXME this may be excessivly conservative. On the other hand
// maybe I'm used to having an 8-core machine
// Perhaps find the ideal thread count and subtract 2 or 3
// Perhaps find the ideal thread count and subtract 2 or 3
// (main thread, present thread, random OS load)
// More threads == faster concurrent loads, but also more concurrent
// More threads == faster concurrent loads, but also more concurrent
// load on the GPU until we can serialize GPU transfers (off the main thread)
QThreadPool::globalInstance()->setMaxThreadCount(2);
thread()->setPriority(QThread::HighPriority);
@ -553,13 +551,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle()));
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle()));
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails()));
connect(&domainHandler, &DomainHandler::settingsReceived, this, &Application::domainSettingsReceived);
// update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one
const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000;
auto discoverabilityManager = DependencyManager::get<DiscoverabilityManager>();
connect(&locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation);
connect(&locationUpdateTimer, &QTimer::timeout,
connect(&locationUpdateTimer, &QTimer::timeout,
DependencyManager::get<AddressManager>().data(), &AddressManager::storeCurrentAddress);
locationUpdateTimer.start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS);
@ -589,7 +588,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
connect(&accountManager, &AccountManager::usernameChanged, this, &Application::updateWindowTitle);
// set the account manager's root URL and trigger a login request if we don't have the access token
accountManager.setIsAgent(true);
accountManager.setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL);
UserActivityLogger::getInstance().launch(applicationVersion());
@ -604,7 +602,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
connect(addressManager.data(), &AddressManager::hostChanged, this, &Application::updateWindowTitle);
connect(this, &QCoreApplication::aboutToQuit, addressManager.data(), &AddressManager::storeCurrentAddress);
// Save avatar location immediately after a teleport.
connect(getMyAvatar(), &MyAvatar::positionGoneTo,
DependencyManager::get<AddressManager>().data(), &AddressManager::storeCurrentAddress);
@ -625,7 +623,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
getEntities()->reloadEntityScripts();
}, Qt::QueuedConnection);
connect(scriptEngines, &ScriptEngines::scriptLoadError,
connect(scriptEngines, &ScriptEngines::scriptLoadError,
scriptEngines, [](const QString& filename, const QString& error){
OffscreenUi::warning(nullptr, "Error Loading Script", filename + " failed to load.");
}, Qt::QueuedConnection);
@ -817,6 +815,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
} else if (action == controller::toInt(controller::Action::RETICLE_Y)) {
auto oldPos = _compositor.getReticlePosition();
_compositor.setReticlePosition({ oldPos.x, oldPos.y + state });
} else if (action == controller::toInt(controller::Action::TOGGLE_OVERLAY)) {
toggleOverlays();
}
}
});
@ -903,6 +903,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
SpacemouseManager::getInstance().init();
#endif
auto& packetReceiver = nodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::DomainConnectionDenied, this, "handleDomainConnectionDeniedPacket");
// If the user clicks an an entity, we will check that it's an unlocked web entity, and if so, set the focus to it
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity,
@ -964,6 +967,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
connect(this, &Application::applicationStateChanged, this, &Application::activeChanged);
qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0);
_idleTimer = new QTimer(this);
connect(_idleTimer, &QTimer::timeout, [=] {
idle(usecTimestampNow());
@ -972,7 +976,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
disconnect(_idleTimer);
});
// Setting the interval to zero forces this to get called whenever there are no messages
// in the queue, which can be pretty damn frequent. Hence the idle function has a bunch
// in the queue, which can be pretty damn frequent. Hence the idle function has a bunch
// of logic to abort early if it's being called too often.
_idleTimer->start(0);
}
@ -1020,7 +1024,7 @@ void Application::cleanupBeforeQuit() {
getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts
DependencyManager::get<ScriptEngines>()->saveScripts();
DependencyManager::get<ScriptEngines>()->shutdownScripting(); // stop all currently running global scripts
DependencyManager::destroy<ScriptEngines>();
DependencyManager::destroy<ScriptEngines>();
// first stop all timers directly or by invokeMethod
// depending on what thread they run in
@ -1195,7 +1199,6 @@ void Application::initializeUi() {
// OffscreenUi is a subclass of OffscreenQmlSurface specifically designed to
// support the window management and scripting proxies for VR use
offscreenUi->createDesktop(QString("hifi/Desktop.qml"));
connect(offscreenUi.data(), &OffscreenUi::showDesktop, this, &Application::showDesktop);
// FIXME either expose so that dialogs can set this themselves or
// do better detection in the offscreen UI of what has focus
@ -1209,10 +1212,10 @@ void Application::initializeUi() {
setupPreferences();
// For some reason there is already an "Application" object in the QML context,
// For some reason there is already an "Application" object in the QML context,
// though I can't find it. Hence, "ApplicationInterface"
rootContext->setContextProperty("SnapshotUploader", new SnapshotUploader());
rootContext->setContextProperty("ApplicationInterface", this);
rootContext->setContextProperty("ApplicationInterface", this);
rootContext->setContextProperty("AnimationCache", DependencyManager::get<AnimationCache>().data());
rootContext->setContextProperty("Audio", &AudioScriptingInterface::getInstance());
rootContext->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
@ -1293,6 +1296,7 @@ void Application::initializeUi() {
}
void Application::paintGL() {
// paintGL uses a queued connection, so we can get messages from the queue even after we've quit
// and the plugins have shutdown
if (_aboutToQuit) {
@ -1318,10 +1322,6 @@ void Application::paintGL() {
_lastFramesPerSecondUpdate = now;
}
if (_isGLInitialized) {
idle(now);
}
PROFILE_RANGE(__FUNCTION__);
PerformanceTimer perfTimer("paintGL");
@ -1616,7 +1616,6 @@ void Application::paintGL() {
}
_lastInstantaneousFps = instantaneousFps;
_pendingPaint = false;
}
void Application::runTests() {
@ -2027,9 +2026,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, !Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson));
cameraMenuChanged();
break;
case Qt::Key_O:
_overlayConductor.setEnabled(!_overlayConductor.getEnabled());
break;
case Qt::Key_Slash:
Menu::getInstance()->triggerOption(MenuOption::Stats);
break;
@ -2429,10 +2426,24 @@ bool Application::acceptSnapshot(const QString& urlString) {
static uint32_t _renderedFrameIndex { INVALID_FRAME };
void Application::idle(uint64_t now) {
if (_aboutToQuit) {
return; // bail early, nothing to do here.
}
Stats::getInstance()->updateStats();
AvatarInputs::getInstance()->update();
// These tasks need to be done on our first idle, because we don't want the showing of
// overlay subwindows to do a showDesktop() until after the first time through
static bool firstIdle = true;
if (firstIdle) {
firstIdle = false;
auto offscreenUi = DependencyManager::get<OffscreenUi>();
connect(offscreenUi.data(), &OffscreenUi::showDesktop, this, &Application::showDesktop);
_overlayConductor.setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Overlays));
}
auto displayPlugin = getActiveDisplayPlugin();
// depending on whether we're throttling or not.
// Once rendering is off on another thread we should be able to have Application::idle run at start(0) in
@ -2453,30 +2464,16 @@ void Application::idle(uint64_t now) {
_renderedFrameIndex = INVALID_FRAME;
}
// Nested ifs are for clarity in the logic. Don't collapse them into a giant single if.
// Don't saturate the main thread with rendering, no paint calls until the last one is complete
if (!_pendingPaint) {
// Also no paint calls until the display plugin has increased by at least one frame
// (don't render at 90fps if the display plugin only goes at 60)
if (_renderedFrameIndex == INVALID_FRAME || presentCount > _renderedFrameIndex) {
// Record what present frame we're on
_renderedFrameIndex = presentCount;
// Don't allow paint requests to stack up in the event queue
_pendingPaint = true;
// But when we DO request a paint, get to it as soon as possible: high priority
postEvent(this, new QEvent(static_cast<QEvent::Type>(Paint)), Qt::HighEventPriority);
}
}
// For the rest of idle, we want to cap at the max sim rate, so we might not call
// the remaining idle work every paint frame, or vice versa
// In theory this means we could call idle processing more often than painting,
// but in practice, when the paintGL calls aren't keeping up, there's no room left
// in the main thread to call idle more often than paint.
// This check is mostly to keep idle from burning up CPU cycles by running at
// hundreds of idles per second when the rendering is that fast
if ((timeSinceLastUpdateUs / USECS_PER_MSEC) < CAPPED_SIM_FRAME_PERIOD_MS) {
// No paint this round, but might be time for a new idle, otherwise return
// Don't saturate the main thread with rendering and simulation,
// unless display plugin has increased by at least one frame
if (_renderedFrameIndex == INVALID_FRAME || presentCount > _renderedFrameIndex) {
// Record what present frame we're on
_renderedFrameIndex = presentCount;
// request a paint, get to it as soon as possible: high priority
postEvent(this, new QEvent(static_cast<QEvent::Type>(Paint)), Qt::HighEventPriority);
} else {
// there's no use in simulating or rendering faster then the present rate.
return;
}
@ -2990,6 +2987,16 @@ void Application::updateThreads(float deltaTime) {
}
}
void Application::toggleOverlays() {
auto newOverlaysVisible = !_overlayConductor.getEnabled();
Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, newOverlaysVisible);
_overlayConductor.setEnabled(newOverlaysVisible);
}
void Application::setOverlaysVisible(bool visible) {
_overlayConductor.setEnabled(visible);
}
void Application::cycleCamera() {
auto menu = Menu::getInstance();
if (menu->isOptionChecked(MenuOption::FullscreenMirror)) {
@ -3384,7 +3391,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
_octreeQuery.setCameraNearClip(_viewFrustum.getNearClip());
_octreeQuery.setCameraFarClip(_viewFrustum.getFarClip());
_octreeQuery.setCameraEyeOffsetPosition(glm::vec3());
_octreeQuery.setKeyholeRadius(_viewFrustum.getKeyholeRadius());
_octreeQuery.setCameraCenterRadius(_viewFrustum.getCenterRadius());
auto lodManager = DependencyManager::get<LODManager>();
_octreeQuery.setOctreeSizeScale(lodManager->getOctreeSizeScale());
_octreeQuery.setBoundaryLevelAdjust(lodManager->getBoundaryLevelAdjust());
@ -3420,9 +3427,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
rootDetails.y * TREE_SCALE,
rootDetails.z * TREE_SCALE) - glm::vec3(HALF_TREE_SCALE),
rootDetails.s * TREE_SCALE);
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
if (_viewFrustum.cubeIntersectsKeyhole(serverBounds)) {
inViewServers++;
}
}
@ -3488,12 +3493,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
rootDetails.s * TREE_SCALE);
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
inView = true;
} else {
inView = false;
}
inView = _viewFrustum.cubeIntersectsKeyhole(serverBounds);
} else {
if (wantExtraDebugging) {
qCDebug(interfaceapp) << "Jurisdiction without RootCode for node " << *node << ". That's unusual!";
@ -3954,10 +3954,30 @@ void Application::clearDomainOctreeDetails() {
void Application::domainChanged(const QString& domainHostname) {
updateWindowTitle();
clearDomainOctreeDetails();
_domainConnectionRefusals.clear();
// disable physics until we have enough information about our new location to not cause craziness.
_physicsEnabled = false;
}
void Application::handleDomainConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message) {
// Read deny reason from packet
quint16 reasonSize;
message->readPrimitive(&reasonSize);
QString reason = QString::fromUtf8(message->readWithoutCopy(reasonSize));
// output to the log so the user knows they got a denied connection request
// and check and signal for an access token so that we can make sure they are logged in
qCDebug(interfaceapp) << "The domain-server denied a connection request: " << reason;
qCDebug(interfaceapp) << "You may need to re-log to generate a keypair so you can provide a username signature.";
if (!_domainConnectionRefusals.contains(reason)) {
_domainConnectionRefusals.append(reason);
emit domainConnectionRefused(reason);
}
AccountManager::getInstance().checkAndSignalForAccessToken();
}
void Application::connectedToDomain(const QString& hostname) {
AccountManager& accountManager = AccountManager::getInstance();
const QUuid& domainID = DependencyManager::get<NodeList>()->getDomainHandler().getUUID();
@ -4501,6 +4521,33 @@ void Application::openUrl(const QUrl& url) {
}
}
void Application::domainSettingsReceived(const QJsonObject& domainSettingsObject) {
// from the domain-handler, figure out the satoshi cost per voxel and per meter cubed
const QString VOXEL_SETTINGS_KEY = "voxels";
const QString PER_VOXEL_COST_KEY = "per-voxel-credits";
const QString PER_METER_CUBED_COST_KEY = "per-meter-cubed-credits";
const QString VOXEL_WALLET_UUID = "voxel-wallet";
const QJsonObject& voxelObject = domainSettingsObject[VOXEL_SETTINGS_KEY].toObject();
qint64 satoshisPerVoxel = 0;
qint64 satoshisPerMeterCubed = 0;
QUuid voxelWalletUUID;
if (!domainSettingsObject.isEmpty()) {
float perVoxelCredits = (float) voxelObject[PER_VOXEL_COST_KEY].toDouble();
float perMeterCubedCredits = (float) voxelObject[PER_METER_CUBED_COST_KEY].toDouble();
satoshisPerVoxel = (qint64) floorf(perVoxelCredits * SATOSHIS_PER_CREDIT);
satoshisPerMeterCubed = (qint64) floorf(perMeterCubedCredits * SATOSHIS_PER_CREDIT);
voxelWalletUUID = QUuid(voxelObject[VOXEL_WALLET_UUID].toString());
}
qCDebug(interfaceapp) << "Octree edits costs are" << satoshisPerVoxel << "per octree cell and" << satoshisPerMeterCubed << "per meter cubed";
qCDebug(interfaceapp) << "Destination wallet UUID for edit payments is" << voxelWalletUUID;
}
void Application::loadDialog() {
auto scriptEngines = DependencyManager::get<ScriptEngines>();
QString fileNameString = OffscreenUi::getOpenFileName(
@ -4702,7 +4749,7 @@ static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool acti
groupingMenu = "Developer";
break;
default:
groupingMenu = "Standard";
groupingMenu = "Standard";
break;
}

View file

@ -149,6 +149,7 @@ public:
const ApplicationOverlay& getApplicationOverlay() const { return _applicationOverlay; }
ApplicationCompositor& getApplicationCompositor() { return _compositor; }
const ApplicationCompositor& getApplicationCompositor() const { return _compositor; }
Overlays& getOverlays() { return _overlays; }
bool isForeground() const { return _isForeground; }
@ -224,6 +225,7 @@ signals:
void svoImportRequested(const QString& url);
void checkBackgroundDownloads();
void domainConnectionRefused(const QString& reason);
void fullAvatarURLChanged(const QString& newValue, const QString& modelName);
@ -269,6 +271,8 @@ public slots:
void cycleCamera();
void cameraMenuChanged();
void toggleOverlays();
void setOverlaysVisible(bool visible);
void reloadResourceCaches();
@ -291,6 +295,9 @@ private slots:
void activeChanged(Qt::ApplicationState state);
void domainSettingsReceived(const QJsonObject& domainSettingsObject);
void handleDomainConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message);
void notifyPacketVersionMismatch();
void loadSettings();
@ -469,6 +476,7 @@ private:
typedef bool (Application::* AcceptURLMethod)(const QString &);
static const QHash<QString, AcceptURLMethod> _acceptedExtensions;
QList<QString> _domainConnectionRefusals;
glm::uvec2 _renderResolution;
int _maxOctreePPS = DEFAULT_MAX_OCTREE_PPS;
@ -504,7 +512,6 @@ private:
int _avatarAttachmentRequest = 0;
bool _settingsLoaded { false };
bool _pendingPaint { false };
QTimer* _idleTimer { nullptr };
bool _fakedMouseEvent { false };

View file

@ -22,8 +22,11 @@
#include <QStandardPaths>
#include <QVBoxLayout>
#include "DataServerAccountInfo.h"
#include "Menu.h"
Q_DECLARE_METATYPE(DataServerAccountInfo)
static const QString RUNNING_MARKER_FILENAME = "Interface.running";
void CrashHandler::checkForAndHandleCrash() {
@ -54,7 +57,7 @@ CrashHandler::Action CrashHandler::promptUserForAction() {
layout->addWidget(label);
QRadioButton* option1 = new QRadioButton("Reset all my settings");
QRadioButton* option2 = new QRadioButton("Reset my settings but retain avatar info.");
QRadioButton* option2 = new QRadioButton("Reset my settings but retain login and avatar info.");
QRadioButton* option3 = new QRadioButton("Continue with my current settings");
option3->setChecked(true);
layout->addWidget(option1);
@ -76,7 +79,7 @@ CrashHandler::Action CrashHandler::promptUserForAction() {
return CrashHandler::DELETE_INTERFACE_INI;
}
if (option2->isChecked()) {
return CrashHandler::RETAIN_AVATAR_INFO;
return CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO;
}
}
@ -85,7 +88,7 @@ CrashHandler::Action CrashHandler::promptUserForAction() {
}
void CrashHandler::handleCrash(CrashHandler::Action action) {
if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_AVATAR_INFO) {
if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) {
// CrashHandler::DO_NOTHING or unexpected value
return;
}
@ -98,13 +101,18 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
const QString DISPLAY_NAME_KEY = "displayName";
const QString FULL_AVATAR_URL_KEY = "fullAvatarURL";
const QString FULL_AVATAR_MODEL_NAME_KEY = "fullAvatarModelName";
const QString ACCOUNTS_GROUP = "accounts";
QString displayName;
QUrl fullAvatarURL;
QString fullAvatarModelName;
QUrl address;
QMap<QString, DataServerAccountInfo> accounts;
if (action == CrashHandler::RETAIN_AVATAR_INFO) {
// Read avatar info
if (action == CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) {
// Read login and avatar info
qRegisterMetaType<DataServerAccountInfo>("DataServerAccountInfo");
qRegisterMetaTypeStreamOperators<DataServerAccountInfo>("DataServerAccountInfo");
// Location and orientation
settings.beginGroup(ADDRESS_MANAGER_GROUP);
@ -117,6 +125,13 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
fullAvatarURL = settings.value(FULL_AVATAR_URL_KEY).toUrl();
fullAvatarModelName = settings.value(FULL_AVATAR_MODEL_NAME_KEY).toString();
settings.endGroup();
// Accounts
settings.beginGroup(ACCOUNTS_GROUP);
foreach(const QString& key, settings.allKeys()) {
accounts.insert(key, settings.value(key).value<DataServerAccountInfo>());
}
settings.endGroup();
}
// Delete Interface.ini
@ -125,8 +140,8 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
settingsFile.remove();
}
if (action == CrashHandler::RETAIN_AVATAR_INFO) {
// Write avatar info
if (action == CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) {
// Write login and avatar info
// Location and orientation
settings.beginGroup(ADDRESS_MANAGER_GROUP);
@ -139,6 +154,13 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
settings.setValue(FULL_AVATAR_URL_KEY, fullAvatarURL);
settings.setValue(FULL_AVATAR_MODEL_NAME_KEY, fullAvatarModelName);
settings.endGroup();
// Accounts
settings.beginGroup(ACCOUNTS_GROUP);
foreach(const QString& key, accounts.keys()) {
settings.setValue(key, QVariant::fromValue(accounts.value(key)));
}
settings.endGroup();
}
}

View file

@ -25,7 +25,7 @@ public:
private:
enum Action {
DELETE_INTERFACE_INI,
RETAIN_AVATAR_INFO,
RETAIN_LOGIN_AND_AVATAR_INFO,
DO_NOTHING
};

View file

@ -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");

View file

@ -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";

View file

@ -136,7 +136,7 @@ glm::quat Avatar::getWorldAlignedOrientation () const {
AABox Avatar::getBounds() const {
// Our skeleton models are rigged, and this method call safely produces the static bounds of the model.
// Except, that getPartBounds produces an infinite, uncentered bounding box when the model is not yet parsed,
// Except, that getPartBounds produces an infinite, uncentered bounding box when the model is not yet parsed,
// and we want a centered one. NOTE: There is code that may never try to render, and thus never load and get the
// real model bounds, if this is unrealistically small.
if (!_skeletonModel.isRenderable()) {
@ -188,15 +188,14 @@ void Avatar::simulate(float deltaTime) {
// simple frustum check
float boundingRadius = getBoundingRadius();
bool inViewFrustum = qApp->getViewFrustum()->sphereInFrustum(getPosition(), boundingRadius) !=
ViewFrustum::OUTSIDE;
bool inView = qApp->getViewFrustum()->sphereIntersectsFrustum(getPosition(), boundingRadius);
{
PerformanceTimer perfTimer("hand");
getHand()->simulate(deltaTime, false);
}
if (_shouldAnimate && !_shouldSkipRender && inViewFrustum) {
if (_shouldAnimate && !_shouldSkipRender && inView) {
{
PerformanceTimer perfTimer("skeleton");
_skeletonModel.getRig()->copyJointsFromJointData(_jointData);
@ -401,7 +400,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
frustum = qApp->getDisplayViewFrustum();
}
if (frustum->sphereInFrustum(getPosition(), boundingRadius) == ViewFrustum::OUTSIDE) {
if (!frustum->sphereIntersectsFrustum(getPosition(), boundingRadius)) {
endRender();
return;
}
@ -430,6 +429,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE) {
// add local lights
const float BASE_LIGHT_DISTANCE = 2.0f;
const float LIGHT_FALLOFF_RADIUS = 0.01f;
const float LIGHT_EXPONENT = 1.0f;
const float LIGHT_CUTOFF = glm::radians(80.0f);
float distance = BASE_LIGHT_DISTANCE * getUniformScale();
@ -438,7 +438,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
foreach (const AvatarManager::LocalLight& light, DependencyManager::get<AvatarManager>()->getLocalLights()) {
glm::vec3 direction = orientation * light.direction;
DependencyManager::get<DeferredLightingEffect>()->addSpotLight(position - direction * distance,
distance * 2.0f, light.color, 0.5f, orientation, LIGHT_EXPONENT, LIGHT_CUTOFF);
distance * 2.0f, light.color, 0.5f, LIGHT_FALLOFF_RADIUS, orientation, LIGHT_EXPONENT, LIGHT_CUTOFF);
}
}
@ -516,7 +516,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
auto& frustum = *renderArgs->_viewFrustum;
auto textPosition = getDisplayNamePosition();
if (frustum.pointInFrustum(textPosition, true) == ViewFrustum::INSIDE) {
if (frustum.pointIntersectsFrustum(textPosition)) {
renderDisplayName(batch, frustum, textPosition);
}
}
@ -669,10 +669,10 @@ glm::vec3 Avatar::getDisplayNamePosition() const {
return namePosition;
}
Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, const glm::vec3& textPosition) const {
Q_ASSERT_X(frustum.pointInFrustum(textPosition, true) == ViewFrustum::INSIDE,
Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& view, const glm::vec3& textPosition) const {
Q_ASSERT_X(view.pointIntersectsFrustum(textPosition),
"Avatar::calculateDisplayNameTransform", "Text not in viewfrustum.");
glm::vec3 toFrustum = frustum.getPosition() - textPosition;
glm::vec3 toFrustum = view.getPosition() - textPosition;
// Compute orientation
// If x and z are 0, atan(x, z) adais undefined, so default to 0 degrees
@ -694,7 +694,7 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, cons
return result;
}
void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, const glm::vec3& textPosition) const {
void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition) const {
PROFILE_RANGE_BATCH(batch, __FUNCTION__);
bool shouldShowReceiveStats = DependencyManager::get<AvatarManager>()->shouldShowReceiveStats() && !isMyAvatar();
@ -702,7 +702,7 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, co
// If we have nothing to draw, or it's totally transparent, or it's too close or behind the camera, return
static const float CLIP_DISTANCE = 0.2f;
if ((_displayName.isEmpty() && !shouldShowReceiveStats) || _displayNameAlpha == 0.0f
|| (glm::dot(frustum.getDirection(), getDisplayNamePosition() - frustum.getPosition()) <= CLIP_DISTANCE)) {
|| (glm::dot(view.getDirection(), getDisplayNamePosition() - view.getPosition()) <= CLIP_DISTANCE)) {
return;
}
auto renderer = textRenderer(DISPLAYNAME);
@ -743,7 +743,7 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, co
(_displayNameAlpha / DISPLAYNAME_ALPHA) * DISPLAYNAME_BACKGROUND_ALPHA);
// Compute display name transform
auto textTransform = calculateDisplayNameTransform(frustum, textPosition);
auto textTransform = calculateDisplayNameTransform(view, textPosition);
// Test on extent above insures abs(height) > 0.0f
textTransform.postScale(1.0f / height);
batch.setModelTransform(textTransform);

View file

@ -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();

View file

@ -25,7 +25,7 @@
WindowScriptingInterface::WindowScriptingInterface() {
const DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
connect(&domainHandler, &DomainHandler::connectedToDomain, this, &WindowScriptingInterface::domainChanged);
connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused);
connect(qApp, &Application::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused);
connect(qApp, &Application::svoImportRequested, [this](const QString& urlString) {
static const QMetaMethod svoImportRequestedSignal =

View file

@ -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);

View file

@ -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 };

View file

@ -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) {

View file

@ -110,19 +110,12 @@ void OverlayConductor::setEnabled(bool enabled) {
return;
}
Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, enabled);
_enabled = enabled; // set the new value
// if the new state is visible/enabled...
if (_enabled) {
// alpha fadeOut the overlay mesh.
qApp->getApplicationCompositor().fadeOut();
// disable mouse clicks from script
qApp->getOverlays().disable();
// disable QML events
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->getRootItem()->setEnabled(false);
_enabled = false;
} else {
// alpha fadeIn the overlay mesh.
qApp->getApplicationCompositor().fadeIn();
@ -142,8 +135,16 @@ void OverlayConductor::setEnabled(bool enabled) {
t.setRotation(glm::quat_cast(camMat));
qApp->getApplicationCompositor().setModelTransform(t);
}
} else { // other wise, if the new state is hidden/not enabled
// alpha fadeOut the overlay mesh.
qApp->getApplicationCompositor().fadeOut();
_enabled = true;
// disable mouse clicks from script
qApp->getOverlays().disable();
// disable QML events
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->getRootItem()->setEnabled(false);
}
}

View file

@ -29,8 +29,8 @@ private:
STANDING
};
Mode _mode = FLAT;
bool _enabled = true;
Mode _mode { FLAT };
bool _enabled { false };
};
#endif

View file

@ -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"),

View file

@ -52,6 +52,7 @@ enum class Action {
CONTEXT_MENU,
TOGGLE_MUTE,
CYCLE_CAMERA,
TOGGLE_OVERLAY,
SHIFT,

View file

@ -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);

View file

@ -36,15 +36,16 @@ void RenderableLightEntityItem::render(RenderArgs* args) {
glm::vec3 color = toGlm(getXColor());
float intensity = getIntensity();
float falloffRadius = getFalloffRadius();
float exponent = getExponent();
float cutoff = glm::radians(getCutoff());
if (_isSpotlight) {
DependencyManager::get<DeferredLightingEffect>()->addSpotLight(position, largestDiameter / 2.0f,
color, intensity, rotation, exponent, cutoff);
color, intensity, falloffRadius, rotation, exponent, cutoff);
} else {
DependencyManager::get<DeferredLightingEffect>()->addPointLight(position, largestDiameter / 2.0f,
color, intensity);
color, intensity, falloffRadius);
}
#ifdef WANT_DEBUG

View file

@ -245,6 +245,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_DYNAMIC, dynamic);
CHECK_PROPERTY_CHANGE(PROP_IS_SPOTLIGHT, isSpotlight);
CHECK_PROPERTY_CHANGE(PROP_INTENSITY, intensity);
CHECK_PROPERTY_CHANGE(PROP_FALLOFF_RADIUS, falloffRadius);
CHECK_PROPERTY_CHANGE(PROP_EXPONENT, exponent);
CHECK_PROPERTY_CHANGE(PROP_CUTOFF, cutoff);
CHECK_PROPERTY_CHANGE(PROP_LOCKED, locked);
@ -445,6 +446,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
if (_type == EntityTypes::Light) {
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IS_SPOTLIGHT, isSpotlight);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_INTENSITY, intensity);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FALLOFF_RADIUS, falloffRadius);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EXPONENT, exponent);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CUTOFF, cutoff);
}
@ -597,6 +599,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE(dynamic, bool, setDynamic);
COPY_PROPERTY_FROM_QSCRIPTVALUE(isSpotlight, bool, setIsSpotlight);
COPY_PROPERTY_FROM_QSCRIPTVALUE(intensity, float, setIntensity);
COPY_PROPERTY_FROM_QSCRIPTVALUE(falloffRadius, float, setFalloffRadius);
COPY_PROPERTY_FROM_QSCRIPTVALUE(exponent, float, setExponent);
COPY_PROPERTY_FROM_QSCRIPTVALUE(cutoff, float, setCutoff);
COPY_PROPERTY_FROM_QSCRIPTVALUE(locked, bool, setLocked);
@ -762,6 +765,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
ADD_PROPERTY_TO_MAP(PROP_DYNAMIC, unused, dynamic, unused);
ADD_PROPERTY_TO_MAP(PROP_IS_SPOTLIGHT, IsSpotlight, isSpotlight, bool);
ADD_PROPERTY_TO_MAP(PROP_INTENSITY, Intensity, intensity, float);
ADD_PROPERTY_TO_MAP(PROP_FALLOFF_RADIUS, FalloffRadius, falloffRadius, float);
ADD_PROPERTY_TO_MAP(PROP_EXPONENT, Exponent, exponent, float);
ADD_PROPERTY_TO_MAP(PROP_CUTOFF, Cutoff, cutoff, float);
ADD_PROPERTY_TO_MAP(PROP_LOCKED, Locked, locked, bool);
@ -1043,6 +1047,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
APPEND_ENTITY_PROPERTY(PROP_IS_SPOTLIGHT, properties.getIsSpotlight());
APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor());
APPEND_ENTITY_PROPERTY(PROP_INTENSITY, properties.getIntensity());
APPEND_ENTITY_PROPERTY(PROP_FALLOFF_RADIUS, properties.getFalloffRadius());
APPEND_ENTITY_PROPERTY(PROP_EXPONENT, properties.getExponent());
APPEND_ENTITY_PROPERTY(PROP_CUTOFF, properties.getCutoff());
}
@ -1332,6 +1337,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IS_SPOTLIGHT, bool, setIsSpotlight);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, xColor, setColor);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_INTENSITY, float, setIntensity);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FALLOFF_RADIUS, float, setFalloffRadius);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EXPONENT, float, setExponent);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CUTOFF, float, setCutoff);
}
@ -1477,6 +1483,7 @@ void EntityItemProperties::markAllChanged() {
_dynamicChanged = true;
_intensityChanged = true;
_falloffRadiusChanged = true;
_exponentChanged = true;
_cutoffChanged = true;
_lockedChanged = true;
@ -1719,6 +1726,9 @@ QList<QString> EntityItemProperties::listChangedProperties() {
if (intensityChanged()) {
out += "intensity";
}
if (falloffRadiusChanged()) {
out += "falloffRadius";
}
if (exponentChanged()) {
out += "exponent";
}

View file

@ -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, "");

View file

@ -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

View file

@ -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,

View file

@ -85,7 +85,7 @@ void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params)
forEachEntity([&](EntityItemPointer entity) {
entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params));
});
// TODO: some of these inserts might be redundant!!!
extraEncodeData->insert(this, entityTreeElementExtraEncodeData);
}
@ -96,39 +96,39 @@ bool EntityTreeElement::shouldIncludeChildData(int childIndex, EncodeBitstreamPa
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
if (extraEncodeData->contains(this)) {
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this));
bool childCompleted = entityTreeElementExtraEncodeData->childCompleted[childIndex];
// If we haven't completely sent the child yet, then we should include it
return !childCompleted;
}
// I'm not sure this should ever happen, since we should have the extra encode data if we're considering
// the child data for this element
assert(false);
return false;
}
bool EntityTreeElement::shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const {
bool EntityTreeElement::shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const {
EntityTreeElementPointer childElement = getChildAtIndex(childIndex);
if (childElement->alreadyFullyEncoded(params)) {
return false;
}
return true; // if we don't know otherwise than recurse!
}
bool EntityTreeElement::alreadyFullyEncoded(EncodeBitstreamParams& params) const {
bool EntityTreeElement::alreadyFullyEncoded(EncodeBitstreamParams& params) const {
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
if (extraEncodeData->contains(this)) {
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this));
// If we know that ALL subtrees below us have already been recursed, then we don't
// If we know that ALL subtrees below us have already been recursed, then we don't
// need to recurse this child.
return entityTreeElementExtraEncodeData->subtreeCompleted;
}
@ -139,7 +139,7 @@ void EntityTreeElement::updateEncodedData(int childIndex, AppendState childAppen
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes
if (extraEncodeData->contains(this)) {
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(this));
if (childAppendState == OctreeElement::COMPLETED) {
@ -155,7 +155,7 @@ void EntityTreeElement::updateEncodedData(int childIndex, AppendState childAppen
void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) const {
const bool wantDebug = false;
if (wantDebug) {
qCDebug(entities) << "EntityTreeElement::elementEncodeComplete() element:" << _cube;
}
@ -188,7 +188,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con
// If we've encoding this element before... but we're coming back a second time in an attempt to
// encoud our parent... this might happen.
if (extraEncodeData->contains(childElement.get())) {
EntityTreeElementExtraEncodeData* childExtraEncodeData
EntityTreeElementExtraEncodeData* childExtraEncodeData
= static_cast<EntityTreeElementExtraEncodeData*>(extraEncodeData->value(childElement.get()));
if (wantDebug) {
@ -197,7 +197,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con
qCDebug(entities) << " childExtraEncodeData->elementCompleted:" << childExtraEncodeData->elementCompleted;
qCDebug(entities) << " childExtraEncodeData->subtreeCompleted:" << childExtraEncodeData->subtreeCompleted;
}
if (childElement->isLeaf() && childExtraEncodeData->elementCompleted) {
if (wantDebug) {
qCDebug(entities) << " CHILD IS LEAF -- AND CHILD ELEMENT DATA COMPLETED!!!";
@ -217,24 +217,24 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con
qCDebug(entities) << " WAS elementCompleted:" << thisExtraEncodeData->elementCompleted;
qCDebug(entities) << " WAS subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted;
}
thisExtraEncodeData->subtreeCompleted = !someChildTreeNotComplete;
if (wantDebug) {
qCDebug(entities) << " NOW elementCompleted:" << thisExtraEncodeData->elementCompleted;
qCDebug(entities) << " NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted;
if (thisExtraEncodeData->subtreeCompleted) {
qCDebug(entities) << " YEAH!!!!! >>>>>>>>>>>>>> NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted;
}
}
}
OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData* packetData,
OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData* packetData,
EncodeBitstreamParams& params) const {
OctreeElement::AppendState appendElementState = OctreeElement::COMPLETED; // assume the best...
// first, check the params.extraEncodeData to see if there's any partial re-encode data for this element
OctreeElementExtraEncodeData* extraEncodeData = params.extraEncodeData;
EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData = NULL;
@ -280,7 +280,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
QVector<uint16_t> indexesOfEntitiesToInclude;
// It's possible that our element has been previous completed. In this case we'll simply not include any of our
// entities for encoding. This is needed because we encode the element data at the "parent" level, and so we
// entities for encoding. This is needed because we encode the element data at the "parent" level, and so we
// need to handle the case where our sibling elements need encoding but we don't.
if (!entityTreeElementExtraEncodeData->elementCompleted) {
for (uint16_t i = 0; i < _entityItems.size(); i++) {
@ -304,15 +304,13 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
// frustum culling on rendering.
bool success;
AACube entityCube = entity->getQueryAACube(success);
if (!success || params.viewFrustum->cubeInFrustum(entityCube) == ViewFrustum::OUTSIDE) {
if (!success || !params.viewFrustum->cubeIntersectsKeyhole(entityCube)) {
includeThisEntity = false; // out of view, don't include it
}
// Now check the size of the entity, it's possible that a "too small to see" entity is included in a
// larger octree cell because of its position (for example if it crosses the boundary of a cell it
// pops to the next higher cell. So we want to check to see that the entity is large enough to be seen
// before we consider including it.
if (includeThisEntity) {
} else {
// Check the size of the entity, it's possible that a "too small to see" entity is included in a
// larger octree cell because of its position (for example if it crosses the boundary of a cell it
// pops to the next higher cell. So we want to check to see that the entity is large enough to be seen
// before we consider including it.
success = true;
// we can't cull a parent-entity by its dimensions because the child may be larger. we need to
// avoid sending details about a child but not the parent. the parent's queryAACube should have
@ -397,7 +395,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
// this octree element.
if (extraEncodeData && entityTreeElementExtraEncodeData) {
// After processing, if we are PARTIAL or COMPLETED then we need to re-include our extra data.
// After processing, if we are PARTIAL or COMPLETED then we need to re-include our extra data.
// Only our parent can remove our extra data in these cases and only after it knows that all of its
// children have been encoded.
// If we weren't able to encode ANY data about ourselves, then we go ahead and remove our element data
@ -412,7 +410,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
extraEncodeData->insert(this, entityTreeElementExtraEncodeData);
}
} else {
// If we weren't previously completed, check to see if we are
if (!entityTreeElementExtraEncodeData->elementCompleted) {
// If all of our items have been encoded, then we are complete as an element.
@ -426,9 +424,9 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData
}
}
// Determine if no entities at all were able to fit
// Determine if no entities at all were able to fit
bool noEntitiesFit = (numberOfEntities > 0 && actualNumberOfEntities == 0);
// If we wrote fewer entities than we expected, update the number of entities in our packet
bool successUpdateEntityCount = true;
if (numberOfEntities != actualNumberOfEntities) {
@ -504,7 +502,7 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3
glm::vec3 clampedMax = glm::clamp(maxPoint, (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE);
if (_cube.contains(clampedMin) && _cube.contains(clampedMax)) {
// If our child would be smaller than our smallest reasonable element, then we are the best fit.
float childScale = _cube.getScale() / 2.0f;
if (childScale <= SMALLEST_REASONABLE_OCTREE_ELEMENT_SCALE) {
@ -524,7 +522,7 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3
bool EntityTreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
const QVector<EntityItemID>& entityIdsToDiscard, void** intersectedObject, bool precisionPicking) {
keepSearching = true; // assume that we will continue searching after this.
@ -607,7 +605,7 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con
// we can use the AABox's ray intersection by mapping our origin and direction into the entity frame
// and testing intersection there.
if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance,
if (entityFrameBox.findRayIntersection(entityFrameOrigin, entityFrameDirection, localDistance,
localFace, localSurfaceNormal)) {
if (localDistance < distance) {
// now ask the entity if we actually intersect
@ -862,12 +860,12 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
if (this == _myTree->getRoot().get() && args.bitstreamVersion < VERSION_ROOT_ELEMENT_HAS_DATA) {
return 0;
}
const unsigned char* dataAt = data;
int bytesRead = 0;
uint16_t numberOfEntities = 0;
int expectedBytesPerEntity = EntityItem::expectedBytes();
args.elementsPerPacket++;
if (bytesLeftToRead >= (int)sizeof(numberOfEntities)) {
@ -947,7 +945,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
entityItem->recordCreationTime();
}
} else {
qDebug() << "Recieved packet for previously deleted entity [" <<
qDebug() << "Recieved packet for previously deleted entity [" <<
entityItem->getID() << "] ignoring. (inside " << __FUNCTION__ << ")";
}
}
@ -959,7 +957,7 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
}
}
}
return bytesRead;
}
@ -990,7 +988,7 @@ bool EntityTreeElement::pruneChildren() {
bool somethingPruned = false;
for (int childIndex = 0; childIndex < NUMBER_OF_CHILDREN; childIndex++) {
EntityTreeElementPointer child = getChildAtIndex(childIndex);
// if my child is a leaf, but has no entities, then it's safe to delete my child
if (child && child->isLeaf() && !child->hasEntities()) {
deleteChildAtIndex(childIndex);
@ -1040,4 +1038,4 @@ void EntityTreeElement::debugDump() {
}
});
}

View file

@ -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());
}

View file

@ -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;
};

View file

@ -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();
}

View file

@ -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

View file

@ -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;
}

View file

@ -135,10 +135,15 @@ public:
static uint32 getMaxNumRenderBuffers() { return MAX_NUM_RENDER_BUFFERS; }
const GPUObjectPointer gpuObject {};
Stamp getDepthStamp() const { return _depthStamp; }
const std::vector<Stamp>& getColorStamps() const { return _colorStamps; }
protected:
SwapchainPointer _swapchain;
Stamp _depthStamp { 0 };
std::vector<Stamp> _colorStamps;
TextureViews _renderBuffers;
TextureView _depthStencilBuffer;

View file

@ -173,6 +173,9 @@ public:
public:
GLuint _fbo = 0;
std::vector<GLenum> _colorBuffers;
Stamp _depthStamp { 0 };
std::vector<Stamp> _colorStamps;
GLFramebuffer();
~GLFramebuffer();

View file

@ -27,8 +27,15 @@ GLBackend::GLFramebuffer::~GLFramebuffer() {
GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffer) {
GLFramebuffer* object = Backend::getGPUObject<GLBackend::GLFramebuffer>(framebuffer);
bool needsUpate { false };
if (!object ||
framebuffer.getDepthStamp() != object->_depthStamp ||
framebuffer.getColorStamps() != object->_colorStamps) {
needsUpate = true;
}
// If GPU object already created and in sync
if (object) {
if (!needsUpate) {
return object;
} else if (framebuffer.isEmpty()) {
// NO framebuffer definition yet so let's avoid thinking
@ -37,94 +44,112 @@ GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffe
// need to have a gpu object?
if (!object) {
GLint currentFBO;
// All is green, assign the gpuobject to the Framebuffer
object = new GLFramebuffer();
Backend::setGPUObject(framebuffer, object);
glGenFramebuffers(1, &object->_fbo);
(void)CHECK_GL_ERROR();
}
if (needsUpate) {
GLint currentFBO = -1;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &currentFBO);
GLuint fbo;
glGenFramebuffers(1, &fbo);
(void) CHECK_GL_ERROR();
glBindFramebuffer(GL_FRAMEBUFFER, object->_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
GLTexture* gltexture = nullptr;
TexturePointer surface;
if (framebuffer.getColorStamps() != object->_colorStamps) {
if (framebuffer.hasColor()) {
object->_colorBuffers.clear();
static const GLenum colorAttachments[] = {
GL_COLOR_ATTACHMENT0,
GL_COLOR_ATTACHMENT1,
GL_COLOR_ATTACHMENT2,
GL_COLOR_ATTACHMENT3,
GL_COLOR_ATTACHMENT4,
GL_COLOR_ATTACHMENT5,
GL_COLOR_ATTACHMENT6,
GL_COLOR_ATTACHMENT7,
GL_COLOR_ATTACHMENT8,
GL_COLOR_ATTACHMENT9,
GL_COLOR_ATTACHMENT10,
GL_COLOR_ATTACHMENT11,
GL_COLOR_ATTACHMENT12,
GL_COLOR_ATTACHMENT13,
GL_COLOR_ATTACHMENT14,
GL_COLOR_ATTACHMENT15 };
std::vector<GLenum> colorBuffers;
if (framebuffer.hasColor()) {
static const GLenum colorAttachments[] = {
GL_COLOR_ATTACHMENT0,
GL_COLOR_ATTACHMENT1,
GL_COLOR_ATTACHMENT2,
GL_COLOR_ATTACHMENT3,
GL_COLOR_ATTACHMENT4,
GL_COLOR_ATTACHMENT5,
GL_COLOR_ATTACHMENT6,
GL_COLOR_ATTACHMENT7,
GL_COLOR_ATTACHMENT8,
GL_COLOR_ATTACHMENT9,
GL_COLOR_ATTACHMENT10,
GL_COLOR_ATTACHMENT11,
GL_COLOR_ATTACHMENT12,
GL_COLOR_ATTACHMENT13,
GL_COLOR_ATTACHMENT14,
GL_COLOR_ATTACHMENT15 };
int unit = 0;
for (auto& b : framebuffer.getRenderBuffers()) {
surface = b._texture;
if (surface) {
gltexture = GLBackend::syncGPUObject(*surface);
} else {
gltexture = nullptr;
}
int unit = 0;
for (auto& b : framebuffer.getRenderBuffers()) {
auto surface = b._texture;
if (surface) {
auto gltexture = GLBackend::syncGPUObject(*surface);
if (gltexture) {
glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, gltexture->_texture, 0);
object->_colorBuffers.push_back(colorAttachments[unit]);
} else {
glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, 0, 0);
}
colorBuffers.push_back(colorAttachments[unit]);
unit++;
}
}
}
#if (GPU_FEATURE_PROFILE == GPU_LEGACY)
// for reasons that i don't understand yet, it seems that on mac gl, a fbo must have a color buffer...
else {
GLuint renderBuffer = 0;
glGenRenderbuffers(1, &renderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, framebuffer.getWidth(), framebuffer.getHeight());
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer);
(void) CHECK_GL_ERROR();
}
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
#endif
if (framebuffer.hasDepthStencil()) {
auto surface = framebuffer.getDepthStencilBuffer();
if (surface) {
auto gltexture = GLBackend::syncGPUObject(*surface);
if (gltexture) {
GLenum attachement = GL_DEPTH_STENCIL_ATTACHMENT;
if (!framebuffer.hasStencil()) {
attachement = GL_DEPTH_ATTACHMENT;
glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0);
} else if (!framebuffer.hasDepth()) {
attachement = GL_STENCIL_ATTACHMENT;
glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0);
} else {
attachement = GL_DEPTH_STENCIL_ATTACHMENT;
glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0);
}
(void) CHECK_GL_ERROR();
}
#if (GPU_FEATURE_PROFILE == GPU_LEGACY)
// for reasons that i don't understand yet, it seems that on mac gl, a fbo must have a color buffer...
else {
GLuint renderBuffer = 0;
glGenRenderbuffers(1, &renderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, framebuffer.getWidth(), framebuffer.getHeight());
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer);
(void) CHECK_GL_ERROR();
}
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
#endif
object->_colorStamps = framebuffer.getColorStamps();
}
GLenum attachement = GL_DEPTH_STENCIL_ATTACHMENT;
if (!framebuffer.hasStencil()) {
attachement = GL_DEPTH_ATTACHMENT;
} else if (!framebuffer.hasDepth()) {
attachement = GL_STENCIL_ATTACHMENT;
}
if (framebuffer.getDepthStamp() != object->_depthStamp) {
auto surface = framebuffer.getDepthStencilBuffer();
if (framebuffer.hasDepthStencil() && surface) {
gltexture = GLBackend::syncGPUObject(*surface);
}
if (gltexture) {
glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0);
} else {
glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, 0, 0);
}
object->_depthStamp = framebuffer.getDepthStamp();
}
// Last but not least, define where we draw
if (!colorBuffers.empty()) {
glDrawBuffers((GLsizei)colorBuffers.size(), colorBuffers.data());
if (!object->_colorBuffers.empty()) {
glDrawBuffers((GLsizei)object->_colorBuffers.size(), object->_colorBuffers.data());
} else {
glDrawBuffer( GL_NONE );
}
// Now check for completness
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
// restore the current framebuffer
if (currentFBO != -1) {
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, currentFBO);
}
bool result = false;
switch (status) {
case GL_FRAMEBUFFER_COMPLETE :
@ -147,20 +172,10 @@ GLBackend::GLFramebuffer* GLBackend::syncGPUObject(const Framebuffer& framebuffe
qCDebug(gpulogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED.";
break;
}
if (!result && fbo) {
glDeleteFramebuffers( 1, &fbo );
if (!result && object->_fbo) {
glDeleteFramebuffers(1, &object->_fbo);
return nullptr;
}
// All is green, assign the gpuobject to the Framebuffer
object = new GLFramebuffer();
object->_fbo = fbo;
object->_colorBuffers = colorBuffers;
Backend::setGPUObject(framebuffer, object);
// restore the current framebuffer
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, currentFBO);
}
return object;

View file

@ -12,19 +12,18 @@
using namespace model;
Light::Light() :
_flags(0),
_schemaBuffer(),
_transform() {
Light::Light() {
// only if created from nothing shall we create the Buffer to store the properties
Schema schema;
_schemaBuffer = std::make_shared<gpu::Buffer>(sizeof(Schema), (const gpu::Byte*) &schema);
updateLightRadius();
}
Light::Light(const Light& light) :
_flags(light._flags),
_schemaBuffer(light._schemaBuffer),
_transform(light._transform) {
_transform(light._transform)
{
}
Light& Light::operator= (const Light& light) {
@ -70,18 +69,36 @@ void Light::setAmbientIntensity(float intensity) {
editSchema()._ambientIntensity = intensity;
}
void Light::setFalloffRadius(float radius) {
if (radius <= 0.0f) {
radius = 0.1f;
}
editSchema()._attenuation.x = radius;
updateLightRadius();
}
void Light::setMaximumRadius(float radius) {
if (radius <= 0.f) {
radius = 1.0f;
}
editSchema()._attenuation.w = radius;
editSchema()._attenuation.y = radius;
updateLightRadius();
}
void Light::updateLightRadius() {
float CutOffIntensityRatio = 0.05f;
float surfaceRadius = getMaximumRadius() / (sqrtf((getIntensity() * std::max(std::max(getColor().x, getColor().y), getColor().z)) / CutOffIntensityRatio) - 1.0f);
editSchema()._attenuation = Vec4(surfaceRadius, 1.0f/surfaceRadius, CutOffIntensityRatio, getMaximumRadius());
// This function relies on the attenuation equation:
// I = Li / (1 + (d + Lr)/Lr)^2
// where I = calculated intensity, Li = light intensity, Lr = light falloff radius, d = distance from surface
// see: https://imdoingitwrong.wordpress.com/2011/01/31/light-attenuation/
// note that falloff radius replaces surface radius in linked example
// This equation is biased back by Lr so that all lights act as true points, regardless of surface radii
const float MIN_CUTOFF_INTENSITY = 0.001f;
// Get cutoff radius at minimum intensity
float intensity = getIntensity() * std::max(std::max(getColor().x, getColor().y), getColor().z);
float cutoffRadius = getFalloffRadius() * ((glm::sqrt(intensity / MIN_CUTOFF_INTENSITY) - 1) - 1);
// If it is less than max radius, store it to buffer to avoid extra shading
editSchema()._attenuation.z = std::min(getMaximumRadius(), cutoffRadius);
}
#include <math.h>

View file

@ -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;

View file

@ -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;
}

View file

@ -142,6 +142,8 @@ SunSkyStage::SunSkyStage() :
_skybox(std::make_shared<Skybox>())
{
_sunLight->setType(Light::SUN);
// Default ambient sphere (for lack of skybox)
_sunLight->setAmbientSpherePreset(gpu::SphericalHarmonics::Preset::OLD_TOWN_SQUARE);
setSunIntensity(1.0f);
setSunAmbientIntensity(0.5f);

View file

@ -12,12 +12,10 @@
#include <memory>
#include <QtCore/QDataStream>
#include <QtCore/QFile>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QMap>
#include <QtCore/QStringList>
#include <QtCore/QStandardPaths>
#include <QtCore/QUrlQuery>
#include <QtNetwork/QHttpMultiPart>
#include <QtNetwork/QNetworkRequest>
@ -62,12 +60,13 @@ JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, co
updateReciever(updateReceiver),
updateSlot(updateSlot)
{
}
AccountManager::AccountManager() :
_authURL(),
_pendingCallbackMap()
_pendingCallbackMap(),
_accountInfo(),
_shouldPersistToSettingsFile(true)
{
qRegisterMetaType<OAuthAccessToken>("OAuthAccessToken");
qRegisterMetaTypeStreamOperators<OAuthAccessToken>("OAuthAccessToken");
@ -81,6 +80,9 @@ AccountManager::AccountManager() :
qRegisterMetaType<QHttpMultiPart*>("QHttpMultiPart*");
connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged);
// once we have a profile in account manager make sure we generate a new keypair
connect(this, &AccountManager::profileChanged, this, &AccountManager::generateNewKeypair);
}
const QString DOUBLE_SLASH_SUBSTITUTE = "slashslash";
@ -91,9 +93,16 @@ void AccountManager::logout() {
emit balanceChanged(0);
connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged);
// remove this account from the account settings file
removeAccountFromFile();
if (_shouldPersistToSettingsFile) {
QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE));
QStringList path = QStringList() << ACCOUNTS_GROUP << keyURLString;
Setting::Handle<DataServerAccountInfo>(path).remove();
qCDebug(networking) << "Removed account info for" << _authURL << "from in-memory accounts and .ini file";
} else {
qCDebug(networking) << "Cleared data server account info in account manager.";
}
emit logoutComplete();
// the username has changed to blank
@ -115,83 +124,35 @@ void AccountManager::accountInfoBalanceChanged(qint64 newBalance) {
emit balanceChanged(newBalance);
}
QString accountFilePath() {
return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/AccountInfo.bin";
}
QVariantMap accountMapFromFile(bool& success) {
QFile accountFile { accountFilePath() };
if (accountFile.open(QIODevice::ReadOnly)) {
// grab the current QVariantMap from the settings file
QDataStream readStream(&accountFile);
QVariantMap accountMap;
readStream >> accountMap;
// close the file now that we have read the data
accountFile.close();
success = true;
return accountMap;
} else {
// failed to open file, return empty QVariantMap
// there was only an error if the account file existed when we tried to load it
success = !accountFile.exists();
return QVariantMap();
}
}
void AccountManager::setAuthURL(const QUrl& authURL) {
if (_authURL != authURL) {
_authURL = authURL;
qCDebug(networking) << "AccountManager URL for authenticated requests has been changed to" << qPrintable(_authURL.toString());
// check if there are existing access tokens to load from settings
QFile accountsFile { accountFilePath() };
bool loadedMap = false;
auto accountsMap = accountMapFromFile(loadedMap);
if (accountsFile.exists() && loadedMap) {
// pull out the stored account info and store it in memory
_accountInfo = accountsMap[_authURL.toString()].value<DataServerAccountInfo>();
qCDebug(networking) << "Found metaverse API account information for" << qPrintable(_authURL.toString());
} else {
// we didn't have a file - see if we can migrate old settings and store them in the new file
if (_shouldPersistToSettingsFile) {
// check if there are existing access tokens to load from settings
Settings settings;
settings.beginGroup(ACCOUNTS_GROUP);
foreach(const QString& key, settings.allKeys()) {
// take a key copy to perform the double slash replacement
QString keyCopy(key);
QUrl keyURL(keyCopy.replace(DOUBLE_SLASH_SUBSTITUTE, "//"));
if (keyURL == _authURL) {
// pull out the stored access token and store it in memory
_accountInfo = settings.value(key).value<DataServerAccountInfo>();
qCDebug(networking) << "Migrated an access token for" << qPrintable(keyURL.toString())
<< "from previous settings file";
qCDebug(networking) << "Found a data-server access token for" << qPrintable(keyURL.toString());
// profile info isn't guaranteed to be saved too
if (_accountInfo.hasProfile()) {
emit profileChanged();
} else {
requestProfile();
}
}
}
if (_accountInfo.getAccessToken().token.isEmpty()) {
qCWarning(networking) << "Unable to load account file. No existing account settings will be loaded.";
} else {
// persist the migrated settings to file
persistAccountToFile();
}
}
if (_isAgent && !_accountInfo.getAccessToken().token.isEmpty() && !_accountInfo.hasProfile()) {
// we are missing profile information, request it now
requestProfile();
}
// tell listeners that the auth endpoint has changed
@ -338,11 +299,9 @@ void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) {
} else {
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
qCDebug(networking) << "Received JSON response from metaverse API that has no matching callback.";
qCDebug(networking) << "Received JSON response from data-server that has no matching callback.";
qCDebug(networking) << QJsonDocument::fromJson(requestReply->readAll());
}
requestReply->deleteLater();
}
}
@ -358,69 +317,22 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) {
_pendingCallbackMap.remove(requestReply);
} else {
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
qCDebug(networking) << "Received error response from metaverse API that has no matching callback.";
qCDebug(networking) << "Received error response from data-server that has no matching callback.";
qCDebug(networking) << "Error" << requestReply->error() << "-" << requestReply->errorString();
qCDebug(networking) << requestReply->readAll();
}
requestReply->deleteLater();
}
}
bool writeAccountMapToFile(const QVariantMap& accountMap) {
// re-open the file and truncate it
QFile accountFile { accountFilePath() };
if (accountFile.open(QIODevice::WriteOnly)) {
QDataStream writeStream(&accountFile);
// persist the updated account QVariantMap to file
writeStream << accountMap;
// close the file with the newly persisted settings
accountFile.close();
return true;
} else {
return false;
void AccountManager::persistAccountToSettings() {
if (_shouldPersistToSettingsFile) {
// store this access token into the local settings
QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE));
QStringList path = QStringList() << ACCOUNTS_GROUP << keyURLString;
Setting::Handle<QVariant>(path).set(QVariant::fromValue(_accountInfo));
}
}
void AccountManager::persistAccountToFile() {
qCDebug(networking) << "Persisting AccountManager accounts to" << accountFilePath();
bool wasLoaded = false;
auto accountMap = accountMapFromFile(wasLoaded);
if (wasLoaded) {
// replace the current account information for this auth URL in the account map
accountMap[_authURL.toString()] = QVariant::fromValue(_accountInfo);
// re-open the file and truncate it
if (writeAccountMapToFile(accountMap)) {
return;
}
}
qCWarning(networking) << "Could not load accounts file - unable to persist account information to file.";
}
void AccountManager::removeAccountFromFile() {
bool wasLoaded = false;
auto accountMap = accountMapFromFile(wasLoaded);
if (wasLoaded) {
accountMap.remove(_authURL.toString());
if (writeAccountMapToFile(accountMap)) {
qCDebug(networking) << "Removed account info for" << _authURL << "from settings file.";
return;
}
}
qCWarning(networking) << "Count not load accounts file - unable to remove account information for" << _authURL
<< "from settings file.";
}
bool AccountManager::hasValidAccessToken() {
if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) {
@ -447,19 +359,16 @@ bool AccountManager::checkAndSignalForAccessToken() {
}
void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) {
// replace the account info access token with a new OAuthAccessToken
// clear our current DataServerAccountInfo
_accountInfo = DataServerAccountInfo();
// start the new account info with a new OAuthAccessToken
OAuthAccessToken newOAuthToken;
newOAuthToken.token = accessToken;
if (!accessToken.isEmpty()) {
qCDebug(networking) << "Setting new AccountManager OAuth token. F2C:" << accessToken.left(2) << "L2C:" << accessToken.right(2);
} else if (!_accountInfo.getAccessToken().token.isEmpty()) {
qCDebug(networking) << "Clearing AccountManager OAuth token.";
}
qCDebug(networking) << "Setting new account manager access token. F2C:" << accessToken.left(2) << "L2C:" << accessToken.right(2);
_accountInfo.setAccessToken(newOAuthToken);
persistAccountToFile();
}
void AccountManager::requestAccessToken(const QString& login, const QString& password) {
@ -514,7 +423,7 @@ void AccountManager::requestAccessTokenFinished() {
emit loginComplete(rootURL);
persistAccountToFile();
persistAccountToSettings();
requestProfile();
}
@ -560,7 +469,7 @@ void AccountManager::requestProfileFinished() {
emit usernameChanged(_accountInfo.getUsername());
// store the whole profile into the local settings
persistAccountToFile();
persistAccountToSettings();
} else {
// TODO: error handling
@ -573,141 +482,57 @@ void AccountManager::requestProfileError(QNetworkReply::NetworkError error) {
qCDebug(networking) << "AccountManager requestProfileError - " << error;
}
void AccountManager::generateNewKeypair(bool isUserKeypair, const QUuid& domainID) {
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this, "generateNewKeypair", Q_ARG(bool, isUserKeypair), Q_ARG(QUuid, domainID));
return;
}
if (!isUserKeypair && domainID.isNull()) {
qCWarning(networking) << "AccountManager::generateNewKeypair called for domain keypair with no domain ID. Will not generate keypair.";
return;
}
// make sure we don't already have an outbound keypair generation request
if (!_isWaitingForKeypairResponse) {
_isWaitingForKeypairResponse = true;
// clear the current private key
qDebug() << "Clearing current private key in DataServerAccountInfo";
_accountInfo.setPrivateKey(QByteArray());
// setup a new QThread to generate the keypair on, in case it takes a while
QThread* generateThread = new QThread(this);
generateThread->setObjectName("Account Manager Generator Thread");
// setup a keypair generator
RSAKeypairGenerator* keypairGenerator = new RSAKeypairGenerator;
if (!isUserKeypair) {
keypairGenerator->setDomainID(domainID);
_accountInfo.setDomainID(domainID);
}
// start keypair generation when the thread starts
connect(generateThread, &QThread::started, keypairGenerator, &RSAKeypairGenerator::generateKeypair);
// handle success or failure of keypair generation
connect(keypairGenerator, &RSAKeypairGenerator::generatedKeypair, this, &AccountManager::processGeneratedKeypair);
connect(keypairGenerator, &RSAKeypairGenerator::errorGeneratingKeypair,
this, &AccountManager::handleKeypairGenerationError);
connect(keypairGenerator, &QObject::destroyed, generateThread, &QThread::quit);
connect(generateThread, &QThread::finished, generateThread, &QThread::deleteLater);
keypairGenerator->moveToThread(generateThread);
qCDebug(networking) << "Starting worker thread to generate 2048-bit RSA keypair.";
generateThread->start();
}
}
void AccountManager::processGeneratedKeypair() {
void AccountManager::generateNewKeypair() {
// setup a new QThread to generate the keypair on, in case it takes a while
QThread* generateThread = new QThread(this);
generateThread->setObjectName("Account Manager Generator Thread");
qCDebug(networking) << "Generated 2048-bit RSA keypair. Uploading public key now.";
RSAKeypairGenerator* keypairGenerator = qobject_cast<RSAKeypairGenerator*>(sender());
if (keypairGenerator) {
// hold the private key to later set our metaverse API account info if upload succeeds
_pendingPrivateKey = keypairGenerator->getPrivateKey();
// upload the public key so data-web has an up-to-date key
const QString USER_PUBLIC_KEY_UPDATE_PATH = "api/v1/user/public_key";
const QString DOMAIN_PUBLIC_KEY_UPDATE_PATH = "api/v1/domains/%1/public_key";
QString uploadPath;
if (keypairGenerator->getDomainID().isNull()) {
uploadPath = USER_PUBLIC_KEY_UPDATE_PATH;
} else {
uploadPath = DOMAIN_PUBLIC_KEY_UPDATE_PATH.arg(uuidStringWithoutCurlyBraces(keypairGenerator->getDomainID()));
}
// setup a multipart upload to send up the public key
QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
QHttpPart keyPart;
keyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
keyPart.setHeader(QNetworkRequest::ContentDispositionHeader,
QVariant("form-data; name=\"public_key\"; filename=\"public_key\""));
keyPart.setBody(keypairGenerator->getPublicKey());
requestMultiPart->append(keyPart);
// setup callback parameters so we know once the keypair upload has succeeded or failed
JSONCallbackParameters callbackParameters;
callbackParameters.jsonCallbackReceiver = this;
callbackParameters.jsonCallbackMethod = "publicKeyUploadSucceeded";
callbackParameters.errorCallbackReceiver = this;
callbackParameters.errorCallbackMethod = "publicKeyUploadFailed";
sendRequest(uploadPath, AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation,
callbackParameters, QByteArray(), requestMultiPart);
keypairGenerator->deleteLater();
} else {
qCWarning(networking) << "Expected processGeneratedKeypair to be called by a live RSAKeypairGenerator"
<< "but the casted sender is NULL. Will not process generated keypair.";
}
// setup a keypair generator
RSAKeypairGenerator* keypairGenerator = new RSAKeypairGenerator();
connect(generateThread, &QThread::started, keypairGenerator, &RSAKeypairGenerator::generateKeypair);
connect(keypairGenerator, &RSAKeypairGenerator::generatedKeypair, this, &AccountManager::processGeneratedKeypair);
connect(keypairGenerator, &RSAKeypairGenerator::errorGeneratingKeypair,
this, &AccountManager::handleKeypairGenerationError);
connect(keypairGenerator, &QObject::destroyed, generateThread, &QThread::quit);
connect(generateThread, &QThread::finished, generateThread, &QThread::deleteLater);
keypairGenerator->moveToThread(generateThread);
qCDebug(networking) << "Starting worker thread to generate 2048-bit RSA key-pair.";
generateThread->start();
}
void AccountManager::publicKeyUploadSucceeded(QNetworkReply& reply) {
qDebug() << "Uploaded public key to Metaverse API. RSA keypair generation is completed.";
// public key upload complete - store the matching private key and persist the account to settings
_accountInfo.setPrivateKey(_pendingPrivateKey);
_pendingPrivateKey.clear();
persistAccountToFile();
// clear our waiting state
_isWaitingForKeypairResponse = false;
emit newKeypair();
// delete the reply object now that we are done with it
reply.deleteLater();
}
void AccountManager::publicKeyUploadFailed(QNetworkReply& reply) {
// the public key upload has failed
qWarning() << "Public key upload failed from AccountManager" << reply.errorString();
// we aren't waiting for a response any longer
_isWaitingForKeypairResponse = false;
// clear our pending private key
_pendingPrivateKey.clear();
// delete the reply object now that we are done with it
reply.deleteLater();
void AccountManager::processGeneratedKeypair(const QByteArray& publicKey, const QByteArray& privateKey) {
qCDebug(networking) << "Generated 2048-bit RSA key-pair. Storing private key and uploading public key.";
// set the private key on our data-server account info
_accountInfo.setPrivateKey(privateKey);
persistAccountToSettings();
// upload the public key so data-web has an up-to-date key
const QString PUBLIC_KEY_UPDATE_PATH = "api/v1/user/public_key";
// setup a multipart upload to send up the public key
QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
QHttpPart keyPart;
keyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
keyPart.setHeader(QNetworkRequest::ContentDispositionHeader,
QVariant("form-data; name=\"public_key\"; filename=\"public_key\""));
keyPart.setBody(publicKey);
requestMultiPart->append(keyPart);
sendRequest(PUBLIC_KEY_UPDATE_PATH, AccountManagerAuth::Required, QNetworkAccessManager::PutOperation,
JSONCallbackParameters(), QByteArray(), requestMultiPart);
// get rid of the keypair generator now that we don't need it anymore
sender()->deleteLater();
}
void AccountManager::handleKeypairGenerationError() {
qCritical() << "Error generating keypair - this is likely to cause authentication issues.";
// reset our waiting state for keypair response
_isWaitingForKeypairResponse = false;
// for now there isn't anything we do with this except get the worker thread to clean up
sender()->deleteLater();
}

View file

@ -62,12 +62,12 @@ public:
QHttpMultiPart* dataMultiPart = NULL,
const QVariantMap& propertyMap = QVariantMap());
void setIsAgent(bool isAgent) { _isAgent = isAgent; }
const QUrl& getAuthURL() const { return _authURL; }
void setAuthURL(const QUrl& authURL);
bool hasAuthEndpoint() { return !_authURL.isEmpty(); }
void disableSettingsFilePersistence() { _shouldPersistToSettingsFile = false; }
bool isLoggedIn() { return !_authURL.isEmpty() && hasValidAccessToken(); }
bool hasValidAccessToken();
Q_INVOKABLE bool checkAndSignalForAccessToken();
@ -87,9 +87,7 @@ public slots:
void logout();
void updateBalance();
void accountInfoBalanceChanged(qint64 newBalance);
void generateNewUserKeypair() { generateNewKeypair(); }
void generateNewDomainKeypair(const QUuid& domainID) { generateNewKeypair(false, domainID); }
void generateNewKeypair();
signals:
void authRequired();
void authEndpointChanged();
@ -99,36 +97,25 @@ signals:
void loginFailed();
void logoutComplete();
void balanceChanged(qint64 newBalance);
void newKeypair();
private slots:
void processReply();
void handleKeypairGenerationError();
void processGeneratedKeypair();
void publicKeyUploadSucceeded(QNetworkReply& reply);
void publicKeyUploadFailed(QNetworkReply& reply);
void generateNewKeypair(bool isUserKeypair = true, const QUuid& domainID = QUuid());
void processGeneratedKeypair(const QByteArray& publicKey, const QByteArray& privateKey);
private:
AccountManager();
AccountManager(AccountManager const& other) = delete;
void operator=(AccountManager const& other) = delete;
AccountManager(AccountManager const& other); // not implemented
void operator=(AccountManager const& other); // not implemented
void persistAccountToFile();
void removeAccountFromFile();
void persistAccountToSettings();
void passSuccessToCallback(QNetworkReply* reply);
void passErrorToCallback(QNetworkReply* reply);
QUrl _authURL;
QMap<QNetworkReply*, JSONCallbackParameters> _pendingCallbackMap;
DataServerAccountInfo _accountInfo;
bool _isAgent { false };
bool _isWaitingForKeypairResponse { false };
QByteArray _pendingPrivateKey;
bool _shouldPersistToSettingsFile;
};
#endif // hifi_AccountManager_h

View file

@ -25,6 +25,19 @@
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif
DataServerAccountInfo::DataServerAccountInfo() :
_accessToken(),
_username(),
_xmppPassword(),
_discourseApiKey(),
_walletID(),
_balance(0),
_hasBalance(false),
_privateKey()
{
}
DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) : QObject() {
_accessToken = otherInfo._accessToken;
_username = otherInfo._username;
@ -34,7 +47,6 @@ DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherI
_balance = otherInfo._balance;
_hasBalance = otherInfo._hasBalance;
_privateKey = otherInfo._privateKey;
_domainID = otherInfo._domainID;
}
DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) {
@ -54,7 +66,6 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) {
swap(_balance, otherInfo._balance);
swap(_hasBalance, otherInfo._hasBalance);
swap(_privateKey, otherInfo._privateKey);
swap(_domainID, otherInfo._domainID);
}
void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) {
@ -117,62 +128,59 @@ void DataServerAccountInfo::setProfileInfoFromJSON(const QJsonObject& jsonObject
}
QByteArray DataServerAccountInfo::getUsernameSignature(const QUuid& connectionToken) {
auto lowercaseUsername = _username.toLower().toUtf8();
auto plaintext = lowercaseUsername.append(connectionToken.toRfc4122());
if (!_privateKey.isEmpty()) {
const char* privateKeyData = _privateKey.constData();
RSA* rsaPrivateKey = d2i_RSAPrivateKey(NULL,
reinterpret_cast<const unsigned char**>(&privateKeyData),
_privateKey.size());
if (rsaPrivateKey) {
QByteArray lowercaseUsername = _username.toLower().toUtf8();
QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()),
QCryptographicHash::Sha256);
QByteArray usernameSignature(RSA_size(rsaPrivateKey), 0);
unsigned int usernameSignatureSize = 0;
int encryptReturn = RSA_sign(NID_sha256,
reinterpret_cast<const unsigned char*>(usernameWithToken.constData()),
usernameWithToken.size(),
reinterpret_cast<unsigned char*>(usernameSignature.data()),
&usernameSignatureSize,
rsaPrivateKey);
// free the private key RSA struct now that we are done with it
RSA_free(rsaPrivateKey);
auto signature = signPlaintext(plaintext);
if (!signature.isEmpty()) {
qDebug(networking) << "Returning username" << _username
<< "signed with connection UUID" << uuidStringWithoutCurlyBraces(connectionToken);
} else {
qCDebug(networking) << "Error signing username with connection token";
qCDebug(networking) << "Will re-attempt on next domain-server check in.";
}
return signature;
if (encryptReturn == -1) {
qCDebug(networking) << "Error encrypting username signature.";
qCDebug(networking) << "Will re-attempt on next domain-server check in.";
} else {
qDebug(networking) << "Returning username" << _username << "signed with connection UUID" << uuidStringWithoutCurlyBraces(connectionToken);
return usernameSignature;
}
} else {
qCDebug(networking) << "Could not create RSA struct from QByteArray private key.";
qCDebug(networking) << "Will re-attempt on next domain-server check in.";
}
}
return QByteArray();
}
QByteArray DataServerAccountInfo::signPlaintext(const QByteArray& plaintext) {
if (!_privateKey.isEmpty()) {
const char* privateKeyData = _privateKey.constData();
RSA* rsaPrivateKey = d2i_RSAPrivateKey(NULL,
reinterpret_cast<const unsigned char**>(&privateKeyData),
_privateKey.size());
if (rsaPrivateKey) {
QByteArray signature(RSA_size(rsaPrivateKey), 0);
unsigned int signatureBytes = 0;
QByteArray hashedPlaintext = QCryptographicHash::hash(plaintext, QCryptographicHash::Sha256);
int encryptReturn = RSA_sign(NID_sha256,
reinterpret_cast<const unsigned char*>(hashedPlaintext.constData()),
hashedPlaintext.size(),
reinterpret_cast<unsigned char*>(signature.data()),
&signatureBytes,
rsaPrivateKey);
// free the private key RSA struct now that we are done with it
RSA_free(rsaPrivateKey);
if (encryptReturn != -1) {
return signature;
}
} else {
qCDebug(networking) << "Could not create RSA struct from QByteArray private key.";
}
}
return QByteArray();
void DataServerAccountInfo::setPrivateKey(const QByteArray& privateKey) {
_privateKey = privateKey;
}
QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) {
out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey
<< info._walletID << info._privateKey << info._domainID;
<< info._walletID << info._privateKey;
return out;
}
QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) {
in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey
>> info._walletID >> info._privateKey >> info._domainID;
>> info._walletID >> info._privateKey;
return in;
}

View file

@ -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;
};

View file

@ -92,9 +92,7 @@ void DomainHandler::softReset() {
disconnect();
clearSettings();
_connectionDenialsSinceKeypairRegen = 0;
// cancel the failure timeout for any pending requests for settings
QMetaObject::invokeMethod(&_settingsTimer, "stop");
}
@ -108,9 +106,6 @@ void DomainHandler::hardReset() {
_hostname = QString();
_sockAddr.clear();
_hasCheckedForAccessToken = false;
_domainConnectionRefusals.clear();
// clear any pending path we may have wanted to ask the previous DS about
_pendingPath.clear();
}
@ -352,35 +347,3 @@ void DomainHandler::processICEResponsePacket(QSharedPointer<ReceivedMessage> mes
emit icePeerSocketsReceived();
}
}
void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message) {
// Read deny reason from packet
quint16 reasonSize;
message->readPrimitive(&reasonSize);
QString reason = QString::fromUtf8(message->readWithoutCopy(reasonSize));
// output to the log so the user knows they got a denied connection request
// and check and signal for an access token so that we can make sure they are logged in
qCWarning(networking) << "The domain-server denied a connection request: " << reason;
qCWarning(networking) << "Make sure you are logged in.";
if (!_domainConnectionRefusals.contains(reason)) {
_domainConnectionRefusals.append(reason);
emit domainConnectionRefused(reason);
}
auto& accountManager = AccountManager::getInstance();
if (!_hasCheckedForAccessToken) {
accountManager.checkAndSignalForAccessToken();
_hasCheckedForAccessToken = true;
}
static const int CONNECTION_DENIALS_FOR_KEYPAIR_REGEN = 3;
// force a re-generation of key-pair after CONNECTION_DENIALS_FOR_KEYPAIR_REGEN failed connection attempts
if (++_connectionDenialsSinceKeypairRegen >= CONNECTION_DENIALS_FOR_KEYPAIR_REGEN) {
accountManager.generateNewUserKeypair();
_connectionDenialsSinceKeypairRegen = 0;
}
}

View file

@ -92,7 +92,6 @@ public slots:
void processICEPingReplyPacket(QSharedPointer<ReceivedMessage> message);
void processDTLSRequirementPacket(QSharedPointer<ReceivedMessage> dtlsRequirementPacket);
void processICEResponsePacket(QSharedPointer<ReceivedMessage> icePacket);
void processDomainServerConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message);
private slots:
void completedHostnameLookup(const QHostInfo& hostInfo);
@ -114,8 +113,6 @@ signals:
void settingsReceived(const QJsonObject& domainSettingsObject);
void settingsReceiveFail();
void domainConnectionRefused(QString reason);
private:
void sendDisconnectPacket();
void hardReset();
@ -133,10 +130,6 @@ private:
QJsonObject _settingsObject;
QString _pendingPath;
QTimer _settingsTimer;
QStringList _domainConnectionRefusals;
bool _hasCheckedForAccessToken { false };
int _connectionDenialsSinceKeypairRegen { 0 };
};
#endif // hifi_DomainHandler_h

View file

@ -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);

View file

@ -143,7 +143,6 @@ public:
bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; }
const HifiSockAddr& getLocalSockAddr() const { return _localSockAddr; }
const HifiSockAddr& getPublicSockAddr() const { return _publicSockAddr; }
const HifiSockAddr& getSTUNSockAddr() const { return _stunSockAddr; }
void processKillNode(ReceivedMessage& message);
@ -162,6 +161,7 @@ public:
std::unique_ptr<NLPacket> constructICEPingPacket(PingType_t pingType, const QUuid& iceID);
std::unique_ptr<NLPacket> constructICEPingReplyPacket(ReceivedMessage& message, const QUuid& iceID);
void sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr);
void sendPeerQueryToIceServer(const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerID);
SharedNodePointer findNodeWithAddr(const HifiSockAddr& addr);

View file

@ -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;
}
}
}

View file

@ -85,12 +85,12 @@ void RSAKeypairGenerator::generateKeypair() {
// we can cleanup the RSA struct before we continue on
RSA_free(keyPair);
_publicKey = QByteArray { reinterpret_cast<char*>(publicKeyDER), publicKeyLength };
_privateKey = QByteArray { reinterpret_cast<char*>(privateKeyDER), privateKeyLength };
QByteArray publicKeyArray(reinterpret_cast<char*>(publicKeyDER), publicKeyLength);
QByteArray privateKeyArray(reinterpret_cast<char*>(privateKeyDER), privateKeyLength);
// cleanup the publicKeyDER and publicKeyDER data
OPENSSL_free(publicKeyDER);
OPENSSL_free(privateKeyDER);
emit generatedKeypair();
emit generatedKeypair(publicKeyArray, privateKeyArray);
}

View file

@ -12,31 +12,17 @@
#ifndef hifi_RSAKeypairGenerator_h
#define hifi_RSAKeypairGenerator_h
#include <QtCore/QObject>
#include <QtCore/QUuid>
#include <qobject.h>
class RSAKeypairGenerator : public QObject {
Q_OBJECT
public:
RSAKeypairGenerator(QObject* parent = 0);
void setDomainID(const QUuid& domainID) { _domainID = domainID; }
const QUuid& getDomainID() const { return _domainID; }
const QByteArray& getPublicKey() const { return _publicKey; }
const QByteArray& getPrivateKey() const { return _privateKey; }
public slots:
void generateKeypair();
signals:
void errorGeneratingKeypair();
void generatedKeypair();
private:
QUuid _domainID;
QByteArray _publicKey;
QByteArray _privateKey;
void generatedKeypair(const QByteArray& publicKey, const QByteArray& privateKey);
};
#endif // hifi_RSAKeypairGenerator_h
#endif // hifi_RSAKeypairGenerator_h

View file

@ -30,7 +30,7 @@ const QSet<PacketType> NON_SOURCED_PACKETS = QSet<PacketType>()
<< PacketType::DomainServerAddedNode << PacketType::DomainServerConnectionToken
<< PacketType::DomainSettingsRequest << PacketType::DomainSettings
<< PacketType::ICEServerPeerInformation << PacketType::ICEServerQuery << PacketType::ICEServerHeartbeat
<< PacketType::ICEPing << PacketType::ICEPingReply << PacketType::ICEServerHeartbeatDenied
<< PacketType::ICEPing << PacketType::ICEPingReply
<< PacketType::AssignmentClientStatus << PacketType::StopNode
<< PacketType::DomainServerRemovedNode;
@ -41,12 +41,10 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::EntityAdd:
case PacketType::EntityEdit:
case PacketType::EntityData:
return VERSION_ATMOSPHERE_REMOVED;
return VERSION_LIGHT_HAS_FALLOFF_RADIUS;
case PacketType::AvatarData:
case PacketType::BulkAvatarData:
return static_cast<PacketVersion>(AvatarMixerPacketVersion::SoftAttachmentSupport);
case PacketType::ICEServerHeartbeat:
return 18; // ICE Server Heartbeat signing
default:
return 17;
}

View file

@ -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,

View file

@ -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()) {

View file

@ -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);

View file

@ -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"

View file

@ -49,20 +49,20 @@ protected:
OctreeElement();
virtual OctreeElementPointer createNewElement(unsigned char * octalCode = NULL) = 0;
public:
virtual void init(unsigned char * octalCode); /// Your subclass must call init on construction.
virtual ~OctreeElement();
// methods you can and should override to implement your tree functionality
/// Adds a child to the current element. Override this if there is additional child initialization your class needs.
virtual OctreeElementPointer addChildAtIndex(int childIndex);
/// Override this to implement LOD averaging on changes to the tree.
/// Override this to implement LOD averaging on changes to the tree.
virtual void calculateAverageFromChildren() { }
/// Override this to implement LOD collapsing and identical child pruning on changes to the tree.
/// Override this to implement LOD collapsing and identical child pruning on changes to the tree.
virtual bool collapseChildren() { return false; }
/// Should this element be considered to have content in it. This will be used in collision and ray casting methods.
@ -72,12 +72,12 @@ public:
/// Should this element be considered to have detailed content in it. Specifically should it be rendered.
/// By default we assume that only leaves have detailed content, but some octrees may have different semantics.
virtual bool hasDetailedContent() const { return isLeaf(); }
/// Override this to break up large octree elements when an edit operation is performed on a smaller octree element.
/// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the
/// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the
/// meaningful split would be to break the larger cube into smaller cubes of the same color/texture.
virtual void splitChildren() { }
/// Override to indicate that this element requires a split before editing lower elements in the octree
virtual bool requiresSplit() const { return false; }
@ -88,17 +88,17 @@ public:
virtual void initializeExtraEncodeData(EncodeBitstreamParams& params) { }
virtual bool shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const { return true; }
virtual bool shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const { return true; }
virtual void updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const { }
virtual void elementEncodeComplete(EncodeBitstreamParams& params) const { }
/// Override to serialize the state of this element. This is used for persistance and for transmission across the network.
virtual AppendState appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const
virtual AppendState appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const
{ return COMPLETED; }
/// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading
/// from the network.
virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args)
virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args)
{ return 0; }
/// Override to indicate that the item is currently rendered in the rendering engine. By default we assume that if
@ -106,7 +106,7 @@ public:
/// where an element is not actually rendering all should render elements. If the isRendered() state doesn't match the
/// shouldRender() state, the tree will remark elements as changed even in cases there the elements have not changed.
virtual bool isRendered() const { return getShouldRender(); }
virtual bool deleteApproved() const { return true; }
virtual bool canRayIntersect() const { return isLeaf(); }
@ -114,7 +114,7 @@ public:
/// \param radius radius of sphere in meters
/// \param[out] penetration pointing into cube from sphere
/// \param penetratedObject unused
virtual bool findSpherePenetration(const glm::vec3& center, float radius,
virtual bool findSpherePenetration(const glm::vec3& center, float radius,
glm::vec3& penetration, void** penetratedObject) const;
// Base class methods you don't need to implement
@ -125,7 +125,7 @@ public:
bool isParentOf(OctreeElementPointer possibleChild) const;
/// handles deletion of all descendants, returns false if delete not approved
bool safeDeepDeleteChildAtIndex(int childIndex, int recursionCount = 0);
bool safeDeepDeleteChildAtIndex(int childIndex, int recursionCount = 0);
const AACube& getAACube() const { return _cube; }
@ -134,8 +134,8 @@ public:
int getLevel() const { return numberOfThreeBitSectionsInCode(getOctalCode()) + 1; }
float getEnclosingRadius() const;
bool isInView(const ViewFrustum& viewFrustum) const { return inFrustum(viewFrustum) != ViewFrustum::OUTSIDE; }
ViewFrustum::location inFrustum(const ViewFrustum& viewFrustum) const;
bool isInView(const ViewFrustum& viewFrustum) const { return computeViewIntersection(viewFrustum) != ViewFrustum::OUTSIDE; }
ViewFrustum::intersection computeViewIntersection(const ViewFrustum& viewFrustum) const;
float distanceToCamera(const ViewFrustum& viewFrustum) const;
float furthestDistanceToCamera(const ViewFrustum& viewFrustum) const;
@ -257,7 +257,7 @@ protected:
static std::map<QString, uint16_t> _mapSourceUUIDsToKeys;
static std::map<uint16_t, QString> _mapKeysToSourceUUIDs;
unsigned char _childBitmask; // 1 byte
unsigned char _childBitmask; // 1 byte
bool _falseColored : 1, /// Client only, is this voxel false colored, 1 bit
_isDirty : 1, /// Client only, has this voxel changed since being rendered, 1 bit

View file

@ -27,9 +27,9 @@ void OctreeHeadlessViewer::init() {
void OctreeHeadlessViewer::queryOctree() {
char serverType = getMyNodeType();
PacketType packetType = getMyQueryMessageType();
NodeToJurisdictionMap& jurisdictions = *_jurisdictionListener->getJurisdictions();
bool wantExtraDebugging = false;
if (wantExtraDebugging) {
@ -52,7 +52,7 @@ void OctreeHeadlessViewer::queryOctree() {
_octreeQuery.setCameraNearClip(_viewFrustum.getNearClip());
_octreeQuery.setCameraFarClip(_viewFrustum.getFarClip());
_octreeQuery.setCameraEyeOffsetPosition(glm::vec3());
_octreeQuery.setKeyholeRadius(_viewFrustum.getKeyholeRadius());
_octreeQuery.setCameraCenterRadius(_viewFrustum.getCenterRadius());
_octreeQuery.setOctreeSizeScale(_voxelSizeScale);
_octreeQuery.setBoundaryLevelAdjust(_boundaryLevelAdjust);
@ -77,7 +77,7 @@ void OctreeHeadlessViewer::queryOctree() {
if (jurisdictions.find(nodeUUID) == jurisdictions.end()) {
unknownJurisdictionServers++;
return;
}
}
const JurisdictionMap& map = (jurisdictions)[nodeUUID];
unsigned char* rootCode = map.getRootOctalCode();
@ -91,9 +91,7 @@ void OctreeHeadlessViewer::queryOctree() {
if (foundRootDetails) {
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
if ((bool)(_viewFrustum.calculateCubeKeyholeIntersection(serverBounds))) {
inViewServers++;
}
}
@ -164,13 +162,7 @@ void OctreeHeadlessViewer::queryOctree() {
if (foundRootDetails) {
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
ViewFrustum::location serverFrustumLocation = _viewFrustum.cubeInFrustum(serverBounds);
if (serverFrustumLocation != ViewFrustum::OUTSIDE) {
inView = true;
} else {
inView = false;
}
inView = (bool)(_viewFrustum.calculateCubeKeyholeIntersection(serverBounds));
}
if (inView) {
@ -208,7 +200,7 @@ void OctreeHeadlessViewer::queryOctree() {
// setup the query packet
auto queryPacket = NLPacket::create(packetType);
// read the data to our packet and set the payload size to fit the query
int querySize = _octreeQuery.getBroadcastData(reinterpret_cast<unsigned char*>(queryPacket->getPayload()));
queryPacket->setPayloadSize(querySize);

View file

@ -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; }

View file

@ -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;
}

View file

@ -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

View file

@ -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 dont 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) {

View file

@ -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;

View file

@ -272,7 +272,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP;
if (_numInactiveUpdates > 0) {
const uint8_t MAX_NUM_INACTIVE_UPDATES = 3;
const uint8_t MAX_NUM_INACTIVE_UPDATES = 20;
if (_numInactiveUpdates > MAX_NUM_INACTIVE_UPDATES) {
// clear local ownership (stop sending updates) and let the server clear itself
_entity->clearSimulationOwnership();
@ -282,7 +282,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
// until it is removed from the outgoing updates
// (which happens when we don't own the simulation and it isn't touching our simulation)
const float INACTIVE_UPDATE_PERIOD = 0.5f;
return (dt > INACTIVE_UPDATE_PERIOD);
return (dt > INACTIVE_UPDATE_PERIOD * (float)_numInactiveUpdates);
}
if (!_body->isActive()) {
@ -404,8 +404,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q
assert(_entity);
assert(entityTreeIsLocked());
bool active = _body->isActive();
if (!active) {
if (!_body->isActive()) {
// make sure all derivatives are zero
glm::vec3 zero(0.0f);
_entity->setVelocity(zero);
@ -495,16 +494,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q
qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now);
#endif //def WANT_DEBUG
if (sessionID == _entity->getSimulatorID()) {
// we think we own the simulation
if (!active) {
// we own the simulation but the entity has stopped, so we tell the server that we're clearing simulatorID
// but we remember that we do still own it... and rely on the server to tell us that we don't
properties.clearSimulationOwner();
_outgoingPriority = ZERO_SIMULATION_PRIORITY;
}
// else the ownership is not changing so we don't bother to pack it
} else {
if (_numInactiveUpdates > 0) {
// we own the simulation but the entity has stopped, so we tell the server that we're clearing simulatorID
// but we remember that we do still own it... and rely on the server to tell us that we don't
properties.clearSimulationOwner();
_outgoingPriority = ZERO_SIMULATION_PRIORITY;
} else if (sessionID != _entity->getSimulatorID()) {
// we don't own the simulation for this entity yet, but we're sending a bid for it
properties.setSimulationOwner(sessionID, glm::max<uint8_t>(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY));
_nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS;

View file

@ -87,23 +87,20 @@ void DeferredLightingEffect::init() {
_allocatedLights.push_back(std::make_shared<model::Light>());
model::LightPointer lp = _allocatedLights[0];
lp->setType(model::Light::SUN);
// Add the global light to the light stage (for later shadow rendering)
_lightStage.addLight(lp);
lp->setDirection(-glm::vec3(1.0f, 1.0f, 1.0f));
lp->setColor(glm::vec3(1.0f));
lp->setIntensity(1.0f);
lp->setType(model::Light::SUN);
lp->setAmbientSpherePreset(gpu::SphericalHarmonics::Preset(_ambientLightMode % gpu::SphericalHarmonics::NUM_PRESET));
}
void DeferredLightingEffect::addPointLight(const glm::vec3& position, float radius, const glm::vec3& color,
float intensity) {
addSpotLight(position, radius, color, intensity);
float intensity, float falloffRadius) {
addSpotLight(position, radius, color, intensity, falloffRadius);
}
void DeferredLightingEffect::addSpotLight(const glm::vec3& position, float radius, const glm::vec3& color,
float intensity, const glm::quat& orientation, float exponent, float cutoff) {
float intensity, float falloffRadius, const glm::quat& orientation, float exponent, float cutoff) {
unsigned int lightID = (unsigned int)(_pointLights.size() + _spotLights.size() + _globalLights.size());
if (lightID >= _allocatedLights.size()) {
@ -115,7 +112,7 @@ void DeferredLightingEffect::addSpotLight(const glm::vec3& position, float radiu
lp->setMaximumRadius(radius);
lp->setColor(color);
lp->setIntensity(intensity);
//lp->setShowContour(quadraticAttenuation);
lp->setFalloffRadius(falloffRadius);
if (exponent == 0.0f && cutoff == PI) {
lp->setType(model::Light::POINT);
@ -560,7 +557,13 @@ static void loadLightProgram(const char* vertSource, const char* fragSource, boo
}
void DeferredLightingEffect::setGlobalLight(const model::LightPointer& light, const gpu::TexturePointer& skyboxTexture) {
_allocatedLights.front() = light;
auto globalLight = _allocatedLights.front();
globalLight->setDirection(light->getDirection());
globalLight->setColor(light->getColor());
globalLight->setIntensity(light->getIntensity());
globalLight->setAmbientIntensity(light->getAmbientIntensity());
globalLight->setAmbientSphere(light->getAmbientSphere());
_skyboxTexture = skyboxTexture;
}

View file

@ -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);

View file

@ -413,9 +413,9 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const {
void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) const {
if (!_isBlendShaped) {
batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
batch.setInputFormat((_drawMesh->getVertexFormat()));
batch.setInputStream(0, _drawMesh->getVertexStream());
} else {
batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
@ -426,7 +426,7 @@ void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) const {
batch.setInputBuffer(1, _model->_blendedVertexBuffers[_meshIndex], _drawMesh->getNumVertices() * sizeof(glm::vec3), sizeof(glm::vec3));
batch.setInputStream(2, _drawMesh->getVertexStream().makeRangedStream(2));
}
// TODO: Get rid of that extra call
if (!_hasColorAttrib) {
batch._glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
@ -474,17 +474,14 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
#ifdef DEBUG_BOUNDING_PARTS
{
AABox partBounds = getPartBounds(_meshIndex, partIndex);
bool inView = args->_viewFrustum->boxInFrustum(partBounds) != ViewFrustum::OUTSIDE;
glm::vec4 cubeColor;
glm::vec4 cubeColor(1.0f, 1.0f, 0.0f, 1.0f);
if (isSkinned) {
cubeColor = glm::vec4(0.0f, 1.0f, 1.0f, 1.0f);
} else if (inView) {
} else if (args->_viewFrustum->boxIntersectsFrustum(partBounds)) {
cubeColor = glm::vec4(1.0f, 0.0f, 1.0f, 1.0f);
} else {
cubeColor = glm::vec4(1.0f, 1.0f, 0.0f, 1.0f);
}
Transform transform;
transform.setTranslation(partBounds.calcCenter());
transform.setScale(partBounds.getDimensions());
@ -492,7 +489,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
DependencyManager::get<GeometryCache>()->renderWireCube(batch, 1.0f, cubeColor);
}
#endif //def DEBUG_BOUNDING_PARTS
auto locations = args->_pipeline->locations;
assert(locations);
@ -500,23 +497,23 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
bool canCauterize = args->_renderMode != RenderArgs::SHADOW_RENDER_MODE;
_model->updateClusterMatrices(_transform.getTranslation(), _transform.getRotation());
bindTransform(batch, locations, canCauterize);
//Bind the index buffer and vertex buffer and Blend shapes if needed
bindMesh(batch);
// apply material properties
bindMaterial(batch, locations);
if (args) {
args->_details._materialSwitches++;
}
// Draw!
{
PerformanceTimer perfTimer("batch.drawIndexed()");
drawCall(batch);
}
if (args) {
const int INDICES_PER_TRIANGLE = 3;
args->_details._trianglesRendered += _drawPart._numIndices / INDICES_PER_TRIANGLE;

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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());
}
}

View file

@ -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 << ")"

View file

@ -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);

View file

@ -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 << ")"

View file

@ -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));
}

View file

@ -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;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,32 @@
//
// ViewFrustumTests.h
// tests/octree/src
//
// Created by Andrew Meadows on 2016.02.19
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ViewFruxtumTests_h
#define hifi_ViewFruxtumTests_h
#include <QtTest/QtTest>
class ViewFrustumTests : public QObject {
Q_OBJECT
private slots:
void testInit();
void testCubeFrustumIntersection();
void testCubeKeyholeIntersection();
void testPointIntersectsFrustum();
void testSphereIntersectsFrustum();
void testBoxIntersectsFrustum();
void testSphereIntersectsKeyhole();
void testCubeIntersectsKeyhole();
void testBoxIntersectsKeyhole();
};
#endif // hifi_ViewFruxtumTests_h

View file

@ -0,0 +1,153 @@
//
// AABoxTests.cpp
// tests/shared/src
//
// Created by Andrew Meadows on 2016.02.19
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <iostream>
#include "AABoxTests.h"
#include <GLMHelpers.h>
#include <NumericalConstants.h>
#include <StreamUtils.h>
#include <../GLMTestUtils.h>
#include <../QTestExtensions.h>
QTEST_MAIN(AABoxTests)
void AABoxTests::testCtorsAndSetters() {
const glm::vec3 corner(1.23f, 4.56f, 7.89f);
const glm::vec3 scale(2.34f, 7.53f, 9.14f);
// test ctor
AABox box(corner, scale);
QCOMPARE_WITH_ABS_ERROR(box.getCorner(), corner, EPSILON);
QCOMPARE_WITH_ABS_ERROR(box.getScale(), scale, EPSILON);
// test copy ctor
AABox copyBox(box);
QCOMPARE_WITH_ABS_ERROR(copyBox.getCorner(), corner, EPSILON);
QCOMPARE_WITH_ABS_ERROR(copyBox.getScale(), scale, EPSILON);
// test setBox()
const glm::vec3 newCorner(9.87f, 6.54f, 3.21f);
const glm::vec3 newScale = glm::vec3(4.32f, 8.95f, 10.31f);
box.setBox(newCorner, newScale);
QCOMPARE_WITH_ABS_ERROR(box.getCorner(), newCorner, EPSILON);
QCOMPARE_WITH_ABS_ERROR(box.getScale(), newScale, EPSILON);
// test misc
QCOMPARE_WITH_ABS_ERROR(newCorner, box.getMinimumPoint(), EPSILON);
glm::vec3 expectedMaxCorner = newCorner + glm::vec3(newScale);
QCOMPARE_WITH_ABS_ERROR(expectedMaxCorner, box.getMaximumPoint(), EPSILON);
glm::vec3 expectedCenter = newCorner + glm::vec3(0.5f * newScale);
QCOMPARE_WITH_ABS_ERROR(expectedCenter, box.calcCenter(), EPSILON);
}
void AABoxTests::testContainsPoint() {
const glm::vec3 corner(4.56f, 7.89f, -1.35f);
const glm::vec3 scale(2.34f, 7.53f, 9.14f);
AABox box(corner, scale);
float delta = 0.00001f;
glm::vec3 center = box.calcCenter();
QCOMPARE(box.contains(center), true);
for (int i = 0; i < 3; ++i) {
glm::vec3 halfScale = Vectors::ZERO;
halfScale[i] = 0.5f * scale[i];
glm::vec3 deltaOffset = Vectors::ZERO;
deltaOffset[i] = delta;
QCOMPARE(box.contains(center + halfScale + deltaOffset), false); // outside +face
QCOMPARE(box.contains(center + halfScale - deltaOffset), true); // inside +face
QCOMPARE(box.contains(center - halfScale + deltaOffset), true); // inside -face
QCOMPARE(box.contains(center - halfScale - deltaOffset), false); // outside -face
}
}
void AABoxTests::testTouchesSphere() {
glm::vec3 corner(-4.56f, 7.89f, -1.35f);
float scale = 1.23f;
AABox box(corner, scale);
float delta = 0.00001f;
glm::vec3 cubeCenter = box.calcCenter();
float sphereRadius = 0.468f;
for (int i = 0; i < 3; ++i) {
int j = (i + 1) % 3;
int k = (j + 1) % 3;
{ // faces
glm::vec3 scaleOffset = Vectors::ZERO;
scaleOffset[i] = 0.5f * scale + sphereRadius;
glm::vec3 deltaOffset = Vectors::ZERO;
deltaOffset[i] = delta;
// outside +face
glm::vec3 sphereCenter = cubeCenter + scaleOffset + deltaOffset;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
// inside +face
sphereCenter = cubeCenter + scaleOffset - deltaOffset;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
// inside -face
sphereCenter = cubeCenter - scaleOffset + deltaOffset;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
// outside -face
sphereCenter = cubeCenter - scaleOffset - deltaOffset;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
}
{ // edges
glm::vec3 edgeOffset = Vectors::ZERO;
edgeOffset[i] = 0.5f * scale;
edgeOffset[j] = 0.5f * scale;
glm::vec3 edgeDirection = glm::normalize(edgeOffset);
glm::vec3 sphereCenter;
// inside ij
sphereCenter = cubeCenter + edgeOffset + (sphereRadius - delta) * edgeDirection;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
sphereCenter = cubeCenter - edgeOffset - (sphereRadius - delta) * edgeDirection;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
// outside ij
sphereCenter = cubeCenter + edgeOffset + (sphereRadius + delta) * edgeDirection;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
sphereCenter = cubeCenter - edgeOffset - (sphereRadius + delta) * edgeDirection;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
edgeOffset[j] = 0.0f;
edgeOffset[k] = 0.5f * scale;
edgeDirection = glm::normalize(edgeOffset);
// inside ik
sphereCenter = cubeCenter + edgeOffset + (sphereRadius - delta) * edgeDirection;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
sphereCenter = cubeCenter - edgeOffset - (sphereRadius - delta) * edgeDirection;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), true);
// outside ik
sphereCenter = cubeCenter + edgeOffset + (sphereRadius + delta) * edgeDirection;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
sphereCenter = cubeCenter - edgeOffset - (sphereRadius + delta) * edgeDirection;
QCOMPARE(box.touchesSphere(sphereCenter, sphereRadius), false);
}
}
}

View file

@ -0,0 +1,28 @@
//
// AABoxTests.h
// tests/shared/src
//
// Created by Andrew Meadows on 2016.02.19
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AABoxTests_h
#define hifi_AABoxTests_h
#include <QtTest/QtTest>
#include <glm/glm.hpp>
#include <AABox.h>
class AABoxTests : public QObject {
Q_OBJECT
private slots:
void testCtorsAndSetters();
void testContainsPoint();
void testTouchesSphere();
};
#endif // hifi_AABoxTests_h

View file

@ -0,0 +1,154 @@
//
// AACubeTests.cpp
// tests/shared/src
//
// Created by Andrew Meadows on 2016.02.19
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <iostream>
#include "AACubeTests.h"
#include <GLMHelpers.h>
#include <NumericalConstants.h>
#include <StreamUtils.h>
#include <../GLMTestUtils.h>
#include <../QTestExtensions.h>
QTEST_MAIN(AACubeTests)
void AACubeTests::ctorsAndSetters() {
const glm::vec3 corner(1.23f, 4.56f, 7.89f);
const float scale = 2.34f;
// test ctor
AACube cube(corner, scale);
QCOMPARE_WITH_ABS_ERROR(cube.getCorner(), corner, EPSILON);
QCOMPARE_WITH_ABS_ERROR(cube.getScale(), scale, EPSILON);
// test copy ctor
AACube copyCube(cube);
QCOMPARE_WITH_ABS_ERROR(copyCube.getCorner(), corner, EPSILON);
QCOMPARE_WITH_ABS_ERROR(copyCube.getScale(), scale, EPSILON);
// test setBox()
const glm::vec3 newCorner(9.87f, 6.54f, 3.21f);
const float newScale = 4.32f;
cube.setBox(newCorner, newScale);
QCOMPARE_WITH_ABS_ERROR(cube.getCorner(), newCorner, EPSILON);
QCOMPARE_WITH_ABS_ERROR(cube.getScale(), newScale, EPSILON);
// test misc
QCOMPARE_WITH_ABS_ERROR(cube.getMinimumPoint(), newCorner, EPSILON);
glm::vec3 expectedMaxCorner = newCorner + glm::vec3(newScale);
QCOMPARE_WITH_ABS_ERROR(cube.getMaximumPoint(), expectedMaxCorner, EPSILON);
glm::vec3 expectedCenter = newCorner + glm::vec3(0.5f * newScale);
QCOMPARE_WITH_ABS_ERROR(cube.calcCenter(), expectedCenter, EPSILON);
}
void AACubeTests::containsPoint() {
const glm::vec3 corner(4.56f, 7.89f, -1.35f);
const float scale = 1.23f;
AACube cube(corner, scale);
const float delta = scale / 1000.0f;
const glm::vec3 center = cube.calcCenter();
QCOMPARE(cube.contains(center), true);
for (int i = 0; i < 3; ++i) {
glm::vec3 scaleOffset = Vectors::ZERO;
scaleOffset[i] = 0.5f * scale;
glm::vec3 deltaOffset = Vectors::ZERO;
deltaOffset[i] = delta;
QCOMPARE(cube.contains(center + scaleOffset + deltaOffset), false); // outside +face
QCOMPARE(cube.contains(center + scaleOffset - deltaOffset), true); // inside +face
QCOMPARE(cube.contains(center - scaleOffset + deltaOffset), true); // inside -face
QCOMPARE(cube.contains(center - scaleOffset - deltaOffset), false); // outside -face
}
}
void AACubeTests::touchesSphere() {
const glm::vec3 corner(-4.56f, 7.89f, -1.35f);
const float scale = 1.23f;
AACube cube(corner, scale);
const float delta = scale / 1000.0f;
const glm::vec3 cubeCenter = cube.calcCenter();
const float sphereRadius = 0.468f;
for (int i = 0; i < 3; ++i) {
int j = (i + 1) % 3;
int k = (j + 1) % 3;
{ // faces
glm::vec3 scaleOffset = Vectors::ZERO;
scaleOffset[i] = 0.5f * scale + sphereRadius;
glm::vec3 deltaOffset = Vectors::ZERO;
deltaOffset[i] = delta;
// outside +face
glm::vec3 sphereCenter = cubeCenter + scaleOffset + deltaOffset;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
// inside +face
sphereCenter = cubeCenter + scaleOffset - deltaOffset;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
// inside -face
sphereCenter = cubeCenter - scaleOffset + deltaOffset;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
// outside -face
sphereCenter = cubeCenter - scaleOffset - deltaOffset;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
}
{ // edges
glm::vec3 edgeOffset = Vectors::ZERO;
edgeOffset[i] = 0.5f * scale;
edgeOffset[j] = 0.5f * scale;
glm::vec3 edgeDirection = glm::normalize(edgeOffset);
glm::vec3 sphereCenter;
// inside ij
sphereCenter = cubeCenter + edgeOffset + (sphereRadius - delta) * edgeDirection;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
sphereCenter = cubeCenter - edgeOffset - (sphereRadius - delta) * edgeDirection;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
// outside ij
sphereCenter = cubeCenter + edgeOffset + (sphereRadius + delta) * edgeDirection;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
sphereCenter = cubeCenter - edgeOffset - (sphereRadius + delta) * edgeDirection;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
edgeOffset[j] = 0.0f;
edgeOffset[k] = 0.5f * scale;
edgeDirection = glm::normalize(edgeOffset);
// inside ik
sphereCenter = cubeCenter + edgeOffset + (sphereRadius - delta) * edgeDirection;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
sphereCenter = cubeCenter - edgeOffset - (sphereRadius - delta) * edgeDirection;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), true);
// outside ik
sphereCenter = cubeCenter + edgeOffset + (sphereRadius + delta) * edgeDirection;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
sphereCenter = cubeCenter - edgeOffset - (sphereRadius + delta) * edgeDirection;
QCOMPARE(cube.touchesSphere(sphereCenter, sphereRadius), false);
}
}
}

View file

@ -0,0 +1,28 @@
//
// AACubeTests.h
// tests/shared/src
//
// Created by Andrew Meadows on 2016.02.19
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AACubeTests_h
#define hifi_AACubeTests_h
#include <QtTest/QtTest>
#include <glm/glm.hpp>
#include <AACube.h>
class AACubeTests : public QObject {
Q_OBJECT
private slots:
void ctorsAndSetters();
void containsPoint();
void touchesSphere();
};
#endif // hifi_AACubeTests_h

View file

@ -1,316 +0,0 @@
//
// AngularConstraintTests.cpp
// tests/physics/src
//
// Created by Andrew Meadows on 2014.05.30
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AngularConstraintTests.h"
#include <iostream>
#include <AngularConstraint.h>
#include <NumericalConstants.h>
#include <StreamUtils.h>
#include "../QTestExtensions.h"
QTEST_MAIN(AngularConstraintTests)
void AngularConstraintTests::testHingeConstraint() {
float minAngle = -PI;
float maxAngle = 0.0f;
glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
glm::vec3 minAngles(0.0f, -PI, 0.0f);
glm::vec3 maxAngles(0.0f, 0.0f, 0.0f);
AngularConstraint* c = AngularConstraint::newAngularConstraint(minAngles, maxAngles);
QVERIFY2(c != nullptr, "newAngularConstraint should make a constraint");
{ // test in middle of constraint
float angle = 0.5f * (minAngle + maxAngle);
glm::quat rotation = glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
QVERIFY2(constrained == false, "HingeConstraint should not clamp()");
QVERIFY2(rotation == newRotation, "HingeConstraint should not change rotation");
}
{ // test just inside min edge of constraint
float angle = minAngle + 10.0f * EPSILON;
glm::quat rotation = glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
QVERIFY2(!constrained, "HingeConstraint should not clamp()");
QVERIFY2(newRotation == rotation, "HingeConstraint should not change rotation");
}
{ // test just inside max edge of constraint
float angle = maxAngle - 10.0f * EPSILON;
glm::quat rotation = glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
QVERIFY2(!constrained, "HingeConstraint should not clamp()");
QVERIFY2(newRotation == rotation, "HingeConstraint should not change rotation");
}
{ // test just outside min edge of constraint
float angle = minAngle - 0.001f;
glm::quat rotation = glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis);
QVERIFY2(constrained, "HingeConstraint should clamp()");
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
{ // test just outside max edge of constraint
float angle = maxAngle + 0.001f;
glm::quat rotation = glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
QVERIFY2(constrained, "HingeConstraint should clamp()");
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, rotation, EPSILON);
}
{ // test far outside min edge of constraint (wraps around to max)
float angle = minAngle - 0.75f * (TWO_PI - (maxAngle - minAngle));
glm::quat rotation = glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis);
QVERIFY2(constrained, "HingeConstraint should clamp()");
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
{ // test far outside max edge of constraint (wraps around to min)
float angle = maxAngle + 0.75f * (TWO_PI - (maxAngle - minAngle));
glm::quat rotation = glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis);
QVERIFY2(constrained, "HingeConstraint should clamp()");
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
float ACCEPTABLE_ERROR = 1.0e-4f;
{ // test nearby but off-axis rotation
float offAngle = 0.1f;
glm::quat offRotation(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
float angle = 0.5f * (maxAngle + minAngle);
glm::quat rotation = offRotation * glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRotation = glm::angleAxis(angle, yAxis);
QVERIFY2(constrained, "HingeConstraint should clamp()");
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, ACCEPTABLE_ERROR);
}
{ // test way off rotation > maxAngle
float offAngle = 0.5f;
glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
float angle = maxAngle + 0.2f * (TWO_PI - (maxAngle - minAngle));
glm::quat rotation = glm::angleAxis(angle, yAxis);
rotation = offRotation * glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis);
QVERIFY2(constrained, "HingeConstraint should clamp()");
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
{ // test way off rotation < minAngle
float offAngle = 0.5f;
glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
float angle = minAngle - 0.2f * (TWO_PI - (maxAngle - minAngle));
glm::quat rotation = glm::angleAxis(angle, yAxis);
rotation = offRotation * glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis);
QVERIFY2(constrained, "HingeConstraint should clamp()");
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
{ // test way off rotation > maxAngle with wrap over to minAngle
float offAngle = -0.5f;
glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
float angle = maxAngle + 0.6f * (TWO_PI - (maxAngle - minAngle));
glm::quat rotation = glm::angleAxis(angle, yAxis);
rotation = offRotation * glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis);
QVERIFY2(constrained, "HingeConstraint should clamp()");
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
{ // test way off rotation < minAngle with wrap over to maxAngle
float offAngle = -0.6f;
glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
float angle = minAngle - 0.7f * (TWO_PI - (maxAngle - minAngle));
glm::quat rotation = glm::angleAxis(angle, yAxis);
rotation = offRotation * glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis);
QVERIFY2(constrained, "HingeConstraint should clamp()");
QVERIFY2(newRotation != rotation, "HingeConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
delete c;
}
void AngularConstraintTests::testConeRollerConstraint() {
float minAngleX = -PI / 5.0f;
float minAngleY = -PI / 5.0f;
float minAngleZ = -PI / 8.0f;
float maxAngleX = PI / 4.0f;
float maxAngleY = PI / 3.0f;
float maxAngleZ = PI / 4.0f;
glm::vec3 minAngles(minAngleX, minAngleY, minAngleZ);
glm::vec3 maxAngles(maxAngleX, maxAngleY, maxAngleZ);
AngularConstraint* c = AngularConstraint::newAngularConstraint(minAngles, maxAngles);
float expectedConeAngle = 0.25f * (maxAngleX - minAngleX + maxAngleY - minAngleY);
glm::vec3 middleAngles = 0.5f * (maxAngles + minAngles);
glm::quat yaw = glm::angleAxis(middleAngles[1], glm::vec3(0.0f, 1.0f, 0.0f));
glm::quat pitch = glm::angleAxis(middleAngles[0], glm::vec3(1.0f, 0.0f, 0.0f));
glm::vec3 expectedConeAxis = pitch * yaw * glm::vec3(0.0f, 0.0f, 1.0f);
glm::vec3 xAxis(1.0f, 0.0f, 0.0f);
glm::vec3 perpAxis = glm::normalize(xAxis - glm::dot(xAxis, expectedConeAxis) * expectedConeAxis);
QVERIFY2(c != nullptr, "newAngularConstraint() should make a constraint");
{ // test in middle of constraint
glm::vec3 angles(PI/20.0f, 0.0f, PI/10.0f);
glm::quat rotation(angles);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
QVERIFY2(!constrained, "ConeRollerConstraint should not clamp()");
QVERIFY2(newRotation == rotation, "ConeRollerConstraint should not change rotation");
}
float deltaAngle = 0.001f;
{ // test just inside edge of cone
glm::quat rotation = glm::angleAxis(expectedConeAngle - deltaAngle, perpAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
QVERIFY2(!constrained, "ConeRollerConstraint should not clamp()");
QVERIFY2(newRotation == rotation, "ConeRollerConstraint should not change rotation");
}
{ // test just outside edge of cone
glm::quat rotation = glm::angleAxis(expectedConeAngle + deltaAngle, perpAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
QVERIFY2(constrained, "ConeRollerConstraint should clamp()");
QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation");
}
{ // test just inside min edge of roll
glm::quat rotation = glm::angleAxis(minAngleZ + deltaAngle, expectedConeAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
QVERIFY2(!constrained, "ConeRollerConstraint should not clamp()");
QVERIFY2(newRotation == rotation, "ConeRollerConstraint should not change rotation");
}
{ // test just inside max edge of roll
glm::quat rotation = glm::angleAxis(maxAngleZ - deltaAngle, expectedConeAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
QVERIFY2(!constrained, "ConeRollerConstraint should not clamp()");
QVERIFY2(newRotation == rotation, "ConeRollerConstraint should not change rotation");
}
{ // test just outside min edge of roll
glm::quat rotation = glm::angleAxis(minAngleZ - deltaAngle, expectedConeAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRotation = glm::angleAxis(minAngleZ, expectedConeAxis);
QVERIFY2(constrained, "ConeRollerConstraint should clamp()");
QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
{ // test just outside max edge of roll
glm::quat rotation = glm::angleAxis(maxAngleZ + deltaAngle, expectedConeAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRotation = glm::angleAxis(maxAngleZ, expectedConeAxis);
QVERIFY2(constrained, "ConeRollerConstraint should clamp()");
QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
deltaAngle = 0.25f * expectedConeAngle;
{ // test far outside cone and min roll
glm::quat roll = glm::angleAxis(minAngleZ - deltaAngle, expectedConeAxis);
glm::quat pitchYaw = glm::angleAxis(expectedConeAngle + deltaAngle, perpAxis);
glm::quat rotation = pitchYaw * roll;
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRoll = glm::angleAxis(minAngleZ, expectedConeAxis);
glm::quat expectedPitchYaw = glm::angleAxis(expectedConeAngle, perpAxis);
glm::quat expectedRotation = expectedPitchYaw * expectedRoll;
QVERIFY2(constrained, "ConeRollerConstraint should clamp()");
QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
{ // test far outside cone and max roll
glm::quat roll = glm::angleAxis(maxAngleZ + deltaAngle, expectedConeAxis);
glm::quat pitchYaw = glm::angleAxis(- expectedConeAngle - deltaAngle, perpAxis);
glm::quat rotation = pitchYaw * roll;
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
glm::quat expectedRoll = glm::angleAxis(maxAngleZ, expectedConeAxis);
glm::quat expectedPitchYaw = glm::angleAxis(- expectedConeAngle, perpAxis);
glm::quat expectedRotation = expectedPitchYaw * expectedRoll;
QVERIFY2(constrained, "ConeRollerConstraint should clamp()");
QVERIFY2(newRotation != rotation, "ConeRollerConstraint should change rotation");
QCOMPARE_WITH_ABS_ERROR(newRotation, expectedRotation, EPSILON);
}
delete c;
}

View file

@ -1,27 +0,0 @@
//
// AngularConstraintTests.h
// tests/physics/src
//
// Created by Andrew Meadows on 2014.05.30
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AngularConstraintTests_h
#define hifi_AngularConstraintTests_h
#include <glm/glm.hpp>
#include <QtTest/QtTest>
class AngularConstraintTests : public QObject {
Q_OBJECT
private slots:
void testHingeConstraint();
void testConeRollerConstraint();
};
float getErrorDifference(const glm::quat& a, const glm::quat& b);
#endif // hifi_AngularConstraintTests_h

View file

@ -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);
}
}
})

View file

@ -7,7 +7,7 @@
(function() {
var version = 11;
var version = 12;
var added = false;
this.frame = 0;
var utilsScript = Script.resolvePath('utils.js');
@ -23,22 +23,21 @@
}
this.initialize = function(entityId) {
print('JBP nav button should initialize' + entityId)
var properties = Entities.getEntityProperties(entityId);
if (properties.userData.length === 0 || properties.hasOwnProperty('userData') === false) {
self.initTimeout = Script.setTimeout(function() {
print('JBP no user data yet, try again in one second')
// print(' no user data yet, try again in one second')
self.initialize(entityId);
}, 1000)
} else {
print('JBP userdata before parse attempt' + properties.userData)
// print('userdata before parse attempt' + properties.userData)
self.userData = null;
try {
self.userData = JSON.parse(properties.userData);
} catch (err) {
print('JBP error parsing json');
print('JBP properties are:' + properties.userData);
// print(' error parsing json');
// print(' properties are:' + properties.userData);
return;
}
@ -46,9 +45,9 @@
var mySavedSettings = Settings.getValue(entityId);
if (mySavedSettings.buttons !== undefined) {
print('JBP preload buttons' + mySavedSettings.buttons)
// print(' preload buttons' + mySavedSettings.buttons)
mySavedSettings.buttons.forEach(function(b) {
print('JBP deleting button' + b)
// print(' deleting button' + b)
Overlays.deleteOverlay(b);
})
Settings.setValue(entityId, '')
@ -56,16 +55,15 @@
self.buttonImageURL = baseURL + "GUI/GUI_" + self.userData.name + ".png?" + version;
print('JBP BUTTON IMAGE URL:' + self.buttonImageURL)
// print(' BUTTON IMAGE URL:' + self.buttonImageURL)
if (self.button === undefined) {
// print('NAV NO BUTTON ADDING ONE!!')
// print(' NO BUTTON ADDING ONE!!')
self.button = true;
self.addButton();
} else {
// print('NAV SELF ALREADY HAS A BUTTON!!')
//print(' SELF ALREADY HAS A BUTTON!!')
}
}
}

Some files were not shown because too many files have changed in this diff Show more