mirror of
https://github.com/lubosz/overte.git
synced 2025-04-17 20:50:27 +02:00
Merge pull request #11129 from highfidelity/RC-50
Beta Release 50 - Includes up to Developer Release 6961
This commit is contained in:
commit
995ee60aa5
87 changed files with 1702 additions and 444 deletions
39
BUILD.md
39
BUILD.md
|
@ -1,28 +1,25 @@
|
|||
### Dependencies
|
||||
|
||||
* [cmake](https://cmake.org/download/) ~> 3.3.2
|
||||
* [Qt](https://www.qt.io/download-open-source) ~> 5.6.2
|
||||
* [OpenSSL](https://www.openssl.org/community/binaries.html)
|
||||
* IMPORTANT: Use the latest available version of OpenSSL to avoid security vulnerabilities.
|
||||
* [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional)
|
||||
- [cmake](https://cmake.org/download/): 3.9
|
||||
- [Qt](https://www.qt.io/download-open-source): 5.9.1
|
||||
- [OpenSSL](https://www.openssl.org/): Use the latest available version of OpenSSL to avoid security vulnerabilities.
|
||||
- [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional)
|
||||
|
||||
#### CMake External Project Dependencies
|
||||
### CMake External Project Dependencies
|
||||
|
||||
* [boostconfig](https://github.com/boostorg/config) ~> 1.58
|
||||
* [Bullet Physics Engine](https://github.com/bulletphysics/bullet3/releases) ~> 2.83
|
||||
* [GLEW](http://glew.sourceforge.net/)
|
||||
* [glm](https://glm.g-truc.net/0.9.5/index.html) ~> 0.9.5.4
|
||||
* [gverb](https://github.com/highfidelity/gverb)
|
||||
* [Oculus SDK](https://developer.oculus.com/downloads/) ~> 0.6 (Win32) / 0.5 (Mac / Linux)
|
||||
* [oglplus](http://oglplus.org/) ~> 0.63
|
||||
* [OpenVR](https://github.com/ValveSoftware/openvr) ~> 0.91 (Win32 only)
|
||||
* [Polyvox](http://www.volumesoffun.com/) ~> 0.2.1
|
||||
* [QuaZip](https://sourceforge.net/projects/quazip/files/quazip/) ~> 0.7.1
|
||||
* [SDL2](https://www.libsdl.org/download-2.0.php) ~> 2.0.3
|
||||
* [soxr](https://sourceforge.net/p/soxr/wiki/Home/) ~> 0.1.1
|
||||
* [Intel Threading Building Blocks](https://www.threadingbuildingblocks.org/) ~> 4.3
|
||||
* [Sixense](http://sixense.com/) ~> 071615
|
||||
* [zlib](http://www.zlib.net/) ~> 1.28 (Win32 only)
|
||||
These dependencies need not be installed manually. They are automatically downloaded on the platforms where they are required.
|
||||
- [Bullet Physics Engine](https://github.com/bulletphysics/bullet3/releases): 2.83
|
||||
- [GLEW](http://glew.sourceforge.net/): 1.13
|
||||
- [glm](https://glm.g-truc.net/0.9.8/index.html): 0.9.8
|
||||
- [Oculus SDK](https://developer.oculus.com/downloads/): 1.11 (Win32) / 0.5 (Mac)
|
||||
- [OpenVR](https://github.com/ValveSoftware/openvr): 1.0.6 (Win32 only)
|
||||
- [Polyvox](http://www.volumesoffun.com/): 0.2.1
|
||||
- [QuaZip](https://sourceforge.net/projects/quazip/files/quazip/): 0.7.3
|
||||
- [SDL2](https://www.libsdl.org/download-2.0.php): 2.0.3
|
||||
- [Intel Threading Building Blocks](https://www.threadingbuildingblocks.org/): 4.3
|
||||
- [Sixense](http://sixense.com/): 071615
|
||||
- [zlib](http://www.zlib.net/): 1.28 (Win32 only)
|
||||
- nVidia Texture Tools: 2.1
|
||||
|
||||
The above dependencies will be downloaded, built, linked and included automatically by CMake where we require them. The CMakeLists files that handle grabbing each of the following external dependencies can be found in the [cmake/externals folder](cmake/externals). The resulting downloads, source files and binaries will be placed in the `build/ext` folder in each of the subfolders for each external project.
|
||||
|
||||
|
|
BIN
Test Plan 2.docx
Normal file
BIN
Test Plan 2.docx
Normal file
Binary file not shown.
|
@ -13,7 +13,7 @@ setup_memory_debugger()
|
|||
link_hifi_libraries(
|
||||
audio avatars octree gpu model fbx entities
|
||||
networking animation recording shared script-engine embedded-webserver
|
||||
physics plugins
|
||||
controllers physics plugins midi
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
|
|
4
cmake/externals/wasapi/CMakeLists.txt
vendored
4
cmake/externals/wasapi/CMakeLists.txt
vendored
|
@ -6,8 +6,8 @@ if (WIN32)
|
|||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi8.zip
|
||||
URL_MD5 b01510437ea15527156bc25cdf733bd9
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi9.zip
|
||||
URL_MD5 94f4765bdbcd53cd099f349ae031e769
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
|
|
|
@ -22,6 +22,9 @@ macro(GENERATE_INSTALLERS)
|
|||
set(CPACK_PACKAGE_FILE_NAME "HighFidelity-Beta-${BUILD_VERSION}")
|
||||
set(CPACK_NSIS_DISPLAY_NAME ${_DISPLAY_NAME})
|
||||
set(CPACK_NSIS_PACKAGE_NAME ${_DISPLAY_NAME})
|
||||
if (PR_BUILD)
|
||||
set(CPACK_NSIS_COMPRESSOR "/SOLID bzip2")
|
||||
endif ()
|
||||
set(CPACK_PACKAGE_INSTALL_DIRECTORY ${_DISPLAY_NAME})
|
||||
|
||||
if (WIN32)
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
Var STR_CONTAINS_VAR_3
|
||||
Var STR_CONTAINS_VAR_4
|
||||
Var STR_RETURN_VAR
|
||||
|
||||
|
||||
Function StrContains
|
||||
Exch $STR_NEEDLE
|
||||
Exch 1
|
||||
|
@ -343,22 +343,29 @@ SectionEnd
|
|||
;--------------------------------
|
||||
;Pages
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
|
||||
|
||||
!insertmacro MUI_PAGE_LICENSE "@CPACK_RESOURCE_FILE_LICENSE@"
|
||||
|
||||
Page custom InstallTypesPage ReadInstallTypes
|
||||
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
|
||||
|
||||
;Start Menu Folder Page Configuration
|
||||
!define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKLM"
|
||||
!define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@"
|
||||
!define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
|
||||
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction
|
||||
!insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER
|
||||
|
||||
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction
|
||||
@CPACK_NSIS_PAGE_COMPONENTS@
|
||||
|
||||
|
||||
Page custom PostInstallOptionsPage ReadPostInstallOptions
|
||||
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
|
||||
|
||||
!insertmacro MUI_UNPAGE_CONFIRM
|
||||
!insertmacro MUI_UNPAGE_INSTFILES
|
||||
|
||||
|
@ -442,6 +449,10 @@ Var CleanInstallCheckbox
|
|||
Var CurrentOffset
|
||||
Var OffsetUnits
|
||||
Var CopyFromProductionCheckbox
|
||||
Var ExpressInstallRadioButton
|
||||
Var CustomInstallRadioButton
|
||||
Var InstallTypeDialog
|
||||
Var Express
|
||||
|
||||
!macro SetPostInstallOption Checkbox OptionName Default
|
||||
; reads the value for the given post install option to the registry
|
||||
|
@ -459,15 +470,71 @@ Var CopyFromProductionCheckbox
|
|||
${EndIf}
|
||||
!macroend
|
||||
|
||||
Function InstallTypesPage
|
||||
!insertmacro MUI_HEADER_TEXT "Choose Installation Type" "Express or Custom Install"
|
||||
|
||||
nsDialogs::Create 1018
|
||||
Pop $InstallTypeDialog
|
||||
|
||||
${If} $InstallTypeDialog == error
|
||||
Abort
|
||||
${EndIf}
|
||||
|
||||
StrCpy $CurrentOffset 0
|
||||
StrCpy $OffsetUnits u
|
||||
StrCpy $Express "0"
|
||||
|
||||
${NSD_CreateRadioButton} 30% $CurrentOffset$OffsetUnits 100% 10u "Express Install (Recommended)"; $\nInstalls High Fidelity Interface and High Fidelity Sandbox"
|
||||
pop $ExpressInstallRadioButton
|
||||
${NSD_OnClick} $ExpressInstallRadioButton ChangeExpressLabel
|
||||
IntOp $CurrentOffset $CurrentOffset + 15
|
||||
|
||||
${NSD_CreateRadiobutton} 30% $CurrentOffset$OffsetUnits 100% 10u "Custom Install (Advanced)"
|
||||
pop $CustomInstallRadioButton
|
||||
${NSD_OnClick} $CustomInstallRadioButton ChangeCustomLabel
|
||||
|
||||
; Express Install selected by default
|
||||
${NSD_Check} $ExpressInstallRadioButton
|
||||
Call ChangeExpressLabel
|
||||
|
||||
nsDialogs::Show
|
||||
FunctionEnd
|
||||
|
||||
Function ChangeExpressLabel
|
||||
Push $R1
|
||||
GetDlgItem $R1 $HWNDPARENT 1
|
||||
SendMessage $R1 ${WM_SETTEXT} 0 "STR:Install"
|
||||
Pop $R1
|
||||
FunctionEnd
|
||||
|
||||
Function ChangeCustomLabel
|
||||
Push $R1
|
||||
GetDlgItem $R1 $HWNDPARENT 1
|
||||
SendMessage $R1 ${WM_SETTEXT} 0 "STR:Next >"
|
||||
Pop $R1
|
||||
FunctionEnd
|
||||
|
||||
Function AbortFunction
|
||||
; Check if Express is set, if so, abort the post install options page
|
||||
StrCmp $Express "1" 0 end
|
||||
Abort
|
||||
end:
|
||||
FunctionEnd
|
||||
|
||||
Function PostInstallOptionsPage
|
||||
!insertmacro MUI_HEADER_TEXT "Setup Options" ""
|
||||
|
||||
nsDialogs::Create 1018
|
||||
Pop $PostInstallDialog
|
||||
|
||||
|
||||
${If} $PostInstallDialog == error
|
||||
Abort
|
||||
${EndIf}
|
||||
|
||||
; Check if Express is set, if so, abort the post install options page
|
||||
StrCmp $Express "1" 0 end
|
||||
Abort
|
||||
end:
|
||||
|
||||
StrCpy $CurrentOffset 0
|
||||
StrCpy $OffsetUnits u
|
||||
|
@ -549,7 +616,7 @@ Function PostInstallOptionsPage
|
|||
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Copy settings and content from production install"
|
||||
Pop $CopyFromProductionCheckbox
|
||||
|
||||
${NSD_SetState} $CopyFromProductionCheckbox ${BST_CHECKED}
|
||||
${NSD_SetState} $CopyFromProductionCheckbox ${BST_UNCHECKED}
|
||||
${EndIf}
|
||||
|
||||
nsDialogs::Show
|
||||
|
@ -567,6 +634,31 @@ Var LaunchServerNowState
|
|||
Var LaunchClientNowState
|
||||
Var CopyFromProductionState
|
||||
Var CleanInstallState
|
||||
Var ExpressInstallState
|
||||
Var CustomInstallState
|
||||
|
||||
Function ReadInstallTypes
|
||||
; check if the user asked for express/custom install
|
||||
${NSD_GetState} $ExpressInstallRadioButton $ExpressInstallState
|
||||
${NSD_GetState} $CustomInstallRadioButton $CustomInstallState
|
||||
|
||||
${If} $ExpressInstallState == ${BST_CHECKED}
|
||||
StrCpy $Express "1"
|
||||
|
||||
StrCpy $DesktopClientState ${BST_CHECKED}
|
||||
StrCpy $ServerStartupState ${BST_CHECKED}
|
||||
StrCpy $LaunchServerNowState ${BST_CHECKED}
|
||||
StrCpy $LaunchClientNowState ${BST_CHECKED}
|
||||
StrCpy $CleanInstallState ${BST_UNCHECKED}
|
||||
StrCpy $DesktopServerState ${BST_UNCHECKED}
|
||||
|
||||
${If} @PR_BUILD@ == 1
|
||||
StrCpy $CopyFromProductionState ${BST_UNCHECKED}
|
||||
${EndIf}
|
||||
|
||||
${EndIf}
|
||||
|
||||
FunctionEnd
|
||||
|
||||
Function ReadPostInstallOptions
|
||||
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
|
||||
|
@ -624,6 +716,7 @@ Function HandlePostInstallOptions
|
|||
!insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ NO
|
||||
${EndIf}
|
||||
|
||||
|
||||
; check if the user asked to have Sandbox launched every startup
|
||||
${If} $ServerStartupState == ${BST_CHECKED}
|
||||
; in case we added a shortcut in the global context, pull that now
|
||||
|
@ -749,6 +842,8 @@ Section "-Core installation"
|
|||
; Rename the incorrectly cased Raleway font
|
||||
Rename "$INSTDIR\resources\qml\styles-uit\RalewaySemibold.qml" "$INSTDIR\resources\qml\styles-uit\RalewaySemiBold.qml"
|
||||
|
||||
ExecWait "$INSTDIR\vcredist_x64.exe /install /q /norestart"
|
||||
|
||||
; Remove the Old Interface directory and vcredist_x64.exe (from installs prior to Server Console)
|
||||
RMDir /r "$INSTDIR\Interface"
|
||||
Delete "$INSTDIR\vcredist_x64.exe"
|
||||
|
@ -848,9 +943,9 @@ Section "-Core installation"
|
|||
Call ConditionalAddToRegisty
|
||||
|
||||
!insertmacro MUI_STARTMENU_WRITE_END
|
||||
|
||||
@CPACK_NSIS_EXTRA_INSTALL_COMMANDS@
|
||||
|
||||
|
||||
@CPACK_NSIS_EXTRA_INSTALL_COMMANDS@
|
||||
|
||||
; Handle whichever post install options were set
|
||||
Call HandlePostInstallOptions
|
||||
|
||||
|
|
|
@ -385,7 +385,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
// user is attempting to prove their identity to us, but we don't have enough information
|
||||
sendConnectionTokenPacket(username, nodeConnection.senderSockAddr);
|
||||
// ask for their public key right now to make sure we have it
|
||||
requestUserPublicKey(username);
|
||||
requestUserPublicKey(username, true);
|
||||
getGroupMemberships(username); // optimistically get started on group memberships
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login because we have no username-signature:" << username;
|
||||
|
@ -521,7 +521,10 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
|||
const HifiSockAddr& senderSockAddr) {
|
||||
// it's possible this user can be allowed to connect, but we need to check their username signature
|
||||
auto lowerUsername = username.toLower();
|
||||
QByteArray publicKeyArray = _userPublicKeys.value(lowerUsername);
|
||||
KeyFlagPair publicKeyPair = _userPublicKeys.value(lowerUsername);
|
||||
|
||||
QByteArray publicKeyArray = publicKeyPair.first;
|
||||
bool isOptimisticKey = publicKeyPair.second;
|
||||
|
||||
const QUuid& connectionToken = _connectionTokenHash.value(lowerUsername);
|
||||
|
||||
|
@ -555,10 +558,16 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
|||
return true;
|
||||
|
||||
} else {
|
||||
if (!senderSockAddr.isNull()) {
|
||||
qDebug() << "Error decrypting username signature for " << username << "- denying connection.";
|
||||
// we only send back a LoginError if this wasn't an "optimistic" key
|
||||
// (a key that we hoped would work but is probably stale)
|
||||
|
||||
if (!senderSockAddr.isNull() && !isOptimisticKey) {
|
||||
qDebug() << "Error decrypting username signature for" << username << "- denying connection.";
|
||||
sendConnectionDeniedPacket("Error decrypting username signature.", senderSockAddr,
|
||||
DomainHandler::ConnectionRefusedReason::LoginError);
|
||||
} else if (!senderSockAddr.isNull()) {
|
||||
qDebug() << "Error decrypting username signature for" << username << "with optimisitic key -"
|
||||
<< "re-requesting public key and delaying connection";
|
||||
}
|
||||
|
||||
// free up the public key, we don't need it anymore
|
||||
|
@ -604,20 +613,7 @@ bool DomainGatekeeper::isWithinMaxCapacity() {
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
void DomainGatekeeper::preloadAllowedUserPublicKeys() {
|
||||
QStringList allowedUsers = _server->_settingsManager.getAllNames();
|
||||
|
||||
if (allowedUsers.size() > 0) {
|
||||
// in the future we may need to limit how many requests here - for now assume that lists of allowed users are not
|
||||
// going to create > 100 requests
|
||||
foreach(const QString& username, allowedUsers) {
|
||||
requestUserPublicKey(username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DomainGatekeeper::requestUserPublicKey(const QString& username) {
|
||||
void DomainGatekeeper::requestUserPublicKey(const QString& username, bool isOptimistic) {
|
||||
// don't request public keys for the standard psuedo-account-names
|
||||
if (NodePermissions::standardNames.contains(username, Qt::CaseInsensitive)) {
|
||||
return;
|
||||
|
@ -628,7 +624,7 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username) {
|
|||
// public-key request for this username is already flight, not rerequesting
|
||||
return;
|
||||
}
|
||||
_inFlightPublicKeyRequests += lowerUsername;
|
||||
_inFlightPublicKeyRequests.insert(lowerUsername, isOptimistic);
|
||||
|
||||
// even if we have a public key for them right now, request a new one in case it has just changed
|
||||
JSONCallbackParameters callbackParams;
|
||||
|
@ -640,7 +636,7 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username) {
|
|||
|
||||
const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key";
|
||||
|
||||
qDebug() << "Requesting public key for user" << username;
|
||||
qDebug().nospace() << "Requesting " << (isOptimistic ? "optimistic " : " ") << "public key for user " << username;
|
||||
|
||||
DependencyManager::get<AccountManager>()->sendRequest(USER_PUBLIC_KEY_PATH.arg(username),
|
||||
AccountManagerAuth::None,
|
||||
|
@ -662,16 +658,21 @@ void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) {
|
|||
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||
QString username = extractUsernameFromPublicKeyRequest(requestReply);
|
||||
|
||||
bool isOptimisticKey = _inFlightPublicKeyRequests.take(username);
|
||||
|
||||
if (jsonObject["status"].toString() == "success" && !username.isEmpty()) {
|
||||
// pull the public key as a QByteArray from this response
|
||||
const QString JSON_DATA_KEY = "data";
|
||||
const QString JSON_PUBLIC_KEY_KEY = "public_key";
|
||||
|
||||
_userPublicKeys[username.toLower()] =
|
||||
QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8());
|
||||
}
|
||||
qDebug().nospace() << "Extracted " << (isOptimisticKey ? "optimistic " : " ") << "public key for " << username.toLower();
|
||||
|
||||
_inFlightPublicKeyRequests.remove(username);
|
||||
_userPublicKeys[username.toLower()] =
|
||||
{
|
||||
QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8()),
|
||||
isOptimisticKey
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void DomainGatekeeper::publicKeyJSONErrorCallback(QNetworkReply& requestReply) {
|
||||
|
|
|
@ -39,8 +39,6 @@ public:
|
|||
const QUuid& walletUUID, const QString& nodeVersion);
|
||||
QUuid assignmentUUIDForPendingAssignment(const QUuid& tempUUID);
|
||||
|
||||
void preloadAllowedUserPublicKeys();
|
||||
|
||||
void removeICEPeer(const QUuid& peerUUID) { _icePeers.remove(peerUUID); }
|
||||
|
||||
static void sendProtocolMismatchConnectionDenial(const HifiSockAddr& senderSockAddr);
|
||||
|
@ -93,7 +91,7 @@ private:
|
|||
|
||||
void pingPunchForConnectingPeer(const SharedNetworkPeer& peer);
|
||||
|
||||
void requestUserPublicKey(const QString& username);
|
||||
void requestUserPublicKey(const QString& username, bool isOptimistic = false);
|
||||
|
||||
DomainServer* _server;
|
||||
|
||||
|
@ -102,8 +100,17 @@ private:
|
|||
QHash<QUuid, SharedNetworkPeer> _icePeers;
|
||||
|
||||
QHash<QString, QUuid> _connectionTokenHash;
|
||||
QHash<QString, QByteArray> _userPublicKeys;
|
||||
QSet<QString> _inFlightPublicKeyRequests; // keep track of which we've already asked for
|
||||
|
||||
// the word "optimistic" below is used for keys that we request during user connection before the user has
|
||||
// had a chance to upload a new public key
|
||||
|
||||
// we don't send back user signature decryption errors for those keys so that there isn't a thrasing of key re-generation
|
||||
// and connection refusal
|
||||
|
||||
using KeyFlagPair = QPair<QByteArray, bool>;
|
||||
|
||||
QHash<QString, KeyFlagPair> _userPublicKeys; // keep track of keys and flag them as optimistic or not
|
||||
QHash<QString, bool> _inFlightPublicKeyRequests; // keep track of keys we've asked for (and if it was optimistic)
|
||||
QSet<QString> _domainOwnerFriends; // keep track of friends of the domain owner
|
||||
QSet<QString> _inFlightGroupMembershipsRequests; // keep track of which we've already asked for
|
||||
|
||||
|
|
|
@ -160,9 +160,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
getTemporaryName();
|
||||
}
|
||||
|
||||
_gatekeeper.preloadAllowedUserPublicKeys(); // so they can connect on first request
|
||||
|
||||
//send signal to DomainMetadata when descriptors changed
|
||||
// send signal to DomainMetadata when descriptors changed
|
||||
_metadata = new DomainMetadata(this);
|
||||
connect(&_settingsManager, &DomainServerSettingsManager::settingsUpdated,
|
||||
_metadata, &DomainMetadata::descriptorsChanged);
|
||||
|
|
|
@ -195,7 +195,7 @@ link_hifi_libraries(
|
|||
shared octree ktx gpu gl gpu-gl procedural model render
|
||||
recording fbx networking model-networking entities avatars trackers
|
||||
audio audio-client animation script-engine physics
|
||||
render-utils entities-renderer avatars-renderer ui auto-updater
|
||||
render-utils entities-renderer avatars-renderer ui auto-updater midi
|
||||
controllers plugins image trackers
|
||||
ui-plugins display-plugins input-plugins
|
||||
${NON_ANDROID_LIBRARIES}
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
{
|
||||
"name": "Vive to Standard",
|
||||
"channels": [
|
||||
{ "from": "Vive.LY", "to": "Standard.LeftIndexPoint",
|
||||
"peek": true,
|
||||
"filters": [ { "type": "hysteresis", "min": 0.7, "max": 0.75 } ]
|
||||
},
|
||||
{ "from": "Vive.RY", "to": "Standard.RightIndexPoint",
|
||||
"peek": true,
|
||||
"filters": [ { "type": "hysteresis", "min": 0.7, "max": 0.75 } ]
|
||||
},
|
||||
|
||||
{ "from": "Vive.LY", "when": "Vive.LSY", "filters": ["invert"], "to": "Standard.LY" },
|
||||
{ "from": "Vive.LX", "when": "Vive.LSX", "to": "Standard.LX" },
|
||||
{
|
||||
|
@ -13,6 +22,10 @@
|
|||
|
||||
{ "from": "Vive.LeftGrip", "to": "Standard.LeftGrip" },
|
||||
{ "from": "Vive.LS", "to": "Standard.LS" },
|
||||
{ "from": "Vive.LSTouch", "to": "Standard.LeftThumbUp",
|
||||
"peek": true,
|
||||
"filters": [ { "type": "logicalNot" } ]
|
||||
},
|
||||
{ "from": "Vive.LSTouch", "to": "Standard.LSTouch" },
|
||||
|
||||
{ "from": "Vive.RY", "when": "Vive.RSY", "filters": ["invert"], "to": "Standard.RY" },
|
||||
|
@ -27,6 +40,10 @@
|
|||
|
||||
{ "from": "Vive.RightGrip", "to": "Standard.RightGrip" },
|
||||
{ "from": "Vive.RS", "to": "Standard.RS" },
|
||||
{ "from": "Vive.RSTouch", "to": "Standard.RightThumbUp",
|
||||
"peek": true,
|
||||
"filters": [ { "type": "logicalNot" } ]
|
||||
},
|
||||
{ "from": "Vive.RSTouch", "to": "Standard.RSTouch" },
|
||||
|
||||
{ "from": "Vive.LSCenter", "to": "Standard.LeftPrimaryThumb" },
|
||||
|
@ -59,7 +76,7 @@
|
|||
|
||||
{ "from": "Vive.Head", "to" : "Standard.Head"},
|
||||
|
||||
{ "from": "Vive.RightArm", "to" : "Standard.RightArm"},
|
||||
{ "from": "Vive.LeftArm", "to" : "Standard.LeftArm"}
|
||||
{ "from": "Vive.RightArm", "to" : "Standard.RightArm" },
|
||||
{ "from": "Vive.LeftArm", "to" : "Standard.LeftArm" }
|
||||
]
|
||||
}
|
||||
|
|
BIN
interface/resources/images/cursor-arrow.png
Normal file
BIN
interface/resources/images/cursor-arrow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
BIN
interface/resources/images/cursor-link.png
Normal file
BIN
interface/resources/images/cursor-link.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
BIN
interface/resources/images/cursor-none.png
Normal file
BIN
interface/resources/images/cursor-none.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 133 B |
BIN
interface/resources/images/cursor-reticle.png
Normal file
BIN
interface/resources/images/cursor-reticle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9 KiB |
|
@ -28,6 +28,7 @@ ScrollingWindow {
|
|||
minSize: Qt.vector2d(200, 300)
|
||||
|
||||
property int colorScheme: hifi.colorSchemes.dark
|
||||
property int selectionMode: SelectionMode.ExtendedSelection
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
|
@ -35,7 +36,8 @@ ScrollingWindow {
|
|||
property var assetProxyModel: Assets.proxyModel;
|
||||
property var assetMappingsModel: Assets.mappingModel;
|
||||
property var currentDirectory;
|
||||
|
||||
property var selectedItems: treeView.selection.selectedIndexes.length;
|
||||
|
||||
Settings {
|
||||
category: "Overlay.AssetServer"
|
||||
property alias x: root.x
|
||||
|
@ -48,7 +50,7 @@ ScrollingWindow {
|
|||
assetMappingsModel.errorGettingMappings.connect(handleGetMappingsError);
|
||||
reload();
|
||||
}
|
||||
|
||||
|
||||
function doDeleteFile(path) {
|
||||
console.log("Deleting " + path);
|
||||
|
||||
|
@ -118,11 +120,23 @@ ScrollingWindow {
|
|||
|
||||
function canAddToWorld(path) {
|
||||
var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i];
|
||||
|
||||
if (selectedItems > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return supportedExtensions.reduce(function(total, current) {
|
||||
return total | new RegExp(current).test(path);
|
||||
}, false);
|
||||
}
|
||||
|
||||
function canRename() {
|
||||
if (treeView.selection.hasSelection && selectedItems == 1) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function clear() {
|
||||
Assets.mappingModel.clear();
|
||||
|
@ -151,13 +165,17 @@ ScrollingWindow {
|
|||
var SHAPE_TYPE_SIMPLE_HULL = 1;
|
||||
var SHAPE_TYPE_SIMPLE_COMPOUND = 2;
|
||||
var SHAPE_TYPE_STATIC_MESH = 3;
|
||||
|
||||
var SHAPE_TYPE_BOX = 4;
|
||||
var SHAPE_TYPE_SPHERE = 5;
|
||||
|
||||
var SHAPE_TYPES = [];
|
||||
SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision";
|
||||
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model";
|
||||
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes";
|
||||
SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons";
|
||||
|
||||
SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box";
|
||||
SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere";
|
||||
|
||||
var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH;
|
||||
var DYNAMIC_DEFAULT = false;
|
||||
var prompt = desktop.customInputDialog({
|
||||
|
@ -196,6 +214,12 @@ ScrollingWindow {
|
|||
case SHAPE_TYPE_STATIC_MESH:
|
||||
shapeType = "static-mesh";
|
||||
break;
|
||||
case SHAPE_TYPE_BOX:
|
||||
shapeType = "box";
|
||||
break;
|
||||
case SHAPE_TYPE_SPHERE:
|
||||
shapeType = "sphere";
|
||||
break;
|
||||
default:
|
||||
shapeType = "none";
|
||||
}
|
||||
|
@ -289,23 +313,37 @@ ScrollingWindow {
|
|||
});
|
||||
}
|
||||
function deleteFile(index) {
|
||||
var path = [];
|
||||
|
||||
if (!index) {
|
||||
index = treeView.selection.currentIndex;
|
||||
for (var i = 0; i < selectedItems; i++) {
|
||||
treeView.selection.setCurrentIndex(treeView.selection.selectedIndexes[i], 0x100);
|
||||
index = treeView.selection.currentIndex;
|
||||
path[i] = assetProxyModel.data(index, 0x100);
|
||||
}
|
||||
}
|
||||
var path = assetProxyModel.data(index, 0x100);
|
||||
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var modalMessage = "";
|
||||
var items = selectedItems.toString();
|
||||
var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101);
|
||||
var typeString = isFolder ? 'folder' : 'file';
|
||||
|
||||
|
||||
if (selectedItems > 1) {
|
||||
modalMessage = "You are about to delete " + items + " items \nDo you want to continue?";
|
||||
} else {
|
||||
modalMessage = "You are about to delete the following " + typeString + ":\n" + path + "\nDo you want to continue?";
|
||||
}
|
||||
|
||||
var object = desktop.messageBox({
|
||||
icon: hifi.icons.question,
|
||||
buttons: OriginalDialogs.StandardButton.Yes + OriginalDialogs.StandardButton.No,
|
||||
defaultButton: OriginalDialogs.StandardButton.Yes,
|
||||
title: "Delete",
|
||||
text: "You are about to delete the following " + typeString + ":\n" + path + "\nDo you want to continue?"
|
||||
text: modalMessage
|
||||
});
|
||||
object.selected.connect(function(button) {
|
||||
if (button === OriginalDialogs.StandardButton.Yes) {
|
||||
|
@ -445,20 +483,20 @@ ScrollingWindow {
|
|||
color: hifi.buttons.black
|
||||
colorScheme: root.colorScheme
|
||||
width: 120
|
||||
|
||||
|
||||
enabled: canAddToWorld(assetProxyModel.data(treeView.selection.currentIndex, 0x100))
|
||||
|
||||
|
||||
onClicked: root.addToWorld()
|
||||
}
|
||||
|
||||
|
||||
HifiControls.Button {
|
||||
text: "Rename"
|
||||
color: hifi.buttons.black
|
||||
colorScheme: root.colorScheme
|
||||
width: 80
|
||||
|
||||
|
||||
onClicked: root.renameFile()
|
||||
enabled: treeView.selection.hasSelection
|
||||
enabled: canRename()
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
|
@ -514,6 +552,7 @@ ScrollingWindow {
|
|||
treeModel: assetProxyModel
|
||||
canEdit: true
|
||||
colorScheme: root.colorScheme
|
||||
selectionMode: SelectionMode.ExtendedSelection
|
||||
|
||||
modifyEl: renameEl
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ Rectangle {
|
|||
|
||||
// only show the title if loaded through a "loader"
|
||||
function showTitle() {
|
||||
return root.parent.objectName == "loader";
|
||||
return (root.parent !== null) && root.parent.objectName == "loader";
|
||||
}
|
||||
|
||||
Column {
|
||||
|
|
|
@ -27,7 +27,7 @@ Rectangle {
|
|||
|
||||
color: "#00000000";
|
||||
border {
|
||||
width: (standalone || Audio.muted || mouseArea.containsMouse) ? 2 : 0;
|
||||
width: mouseArea.containsMouse || mouseArea.containsPress ? 2 : 0;
|
||||
color: colors.border;
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ Rectangle {
|
|||
drag.target: dragTarget;
|
||||
}
|
||||
|
||||
Item {
|
||||
QtObject {
|
||||
id: colors;
|
||||
|
||||
readonly property string unmuted: "#FFF";
|
||||
|
@ -72,7 +72,7 @@ Rectangle {
|
|||
readonly property string red: colors.muted;
|
||||
readonly property string fill: "#55000000";
|
||||
readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF";
|
||||
readonly property string icon: (Audio.muted && !mouseArea.containsMouse) ? muted : unmuted;
|
||||
readonly property string icon: Audio.muted ? muted : unmuted;
|
||||
}
|
||||
|
||||
Item {
|
||||
|
@ -92,10 +92,8 @@ Rectangle {
|
|||
readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg";
|
||||
readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg";
|
||||
|
||||
function exclusiveOr(a, b) { return (a || b) && !(a && b); }
|
||||
|
||||
id: image;
|
||||
source: exclusiveOr(Audio.muted, mouseArea.containsMouse) ? mutedIcon : unmutedIcon;
|
||||
source: Audio.muted ? mutedIcon : unmutedIcon;
|
||||
|
||||
width: 30;
|
||||
height: 30;
|
||||
|
@ -118,9 +116,9 @@ Rectangle {
|
|||
Item {
|
||||
id: status;
|
||||
|
||||
readonly property string color: (Audio.muted && !mouseArea.containsMouse) ? colors.muted : colors.unmuted;
|
||||
readonly property string color: Audio.muted ? colors.muted : colors.unmuted;
|
||||
|
||||
visible: Audio.muted || mouseArea.containsMouse;
|
||||
visible: Audio.muted;
|
||||
|
||||
anchors {
|
||||
left: parent.left;
|
||||
|
@ -133,14 +131,14 @@ Rectangle {
|
|||
|
||||
Text {
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter;
|
||||
horizontalCenter: parent.horizontalCenter;
|
||||
verticalCenter: parent.verticalCenter;
|
||||
}
|
||||
|
||||
color: parent.color;
|
||||
|
||||
text: Audio.muted ? (mouseArea.containsMouse ? "UNMUTE" : "MUTED") : "MUTE";
|
||||
font.pointSize: 12;
|
||||
text: Audio.muted ? "MUTED" : "MUTE";
|
||||
font.pointSize: 12;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
@ -150,7 +148,7 @@ Rectangle {
|
|||
}
|
||||
|
||||
width: 50;
|
||||
height: 4;
|
||||
height: 4;
|
||||
color: parent.color;
|
||||
}
|
||||
|
||||
|
@ -161,7 +159,7 @@ Rectangle {
|
|||
}
|
||||
|
||||
width: 50;
|
||||
height: 4;
|
||||
height: 4;
|
||||
color: parent.color;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ Rectangle {
|
|||
property var assetProxyModel: Assets.proxyModel;
|
||||
property var assetMappingsModel: Assets.mappingModel;
|
||||
property var currentDirectory;
|
||||
property var selectedItems: treeView.selection.selectedIndexes.length;
|
||||
|
||||
Settings {
|
||||
category: "Overlay.AssetServer"
|
||||
|
@ -119,11 +120,23 @@ Rectangle {
|
|||
|
||||
function canAddToWorld(path) {
|
||||
var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i];
|
||||
|
||||
if (selectedItems > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return supportedExtensions.reduce(function(total, current) {
|
||||
return total | new RegExp(current).test(path);
|
||||
}, false);
|
||||
}
|
||||
|
||||
function canRename() {
|
||||
if (treeView.selection.hasSelection && selectedItems == 1) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function clear() {
|
||||
Assets.mappingModel.clear();
|
||||
|
@ -152,13 +165,17 @@ Rectangle {
|
|||
var SHAPE_TYPE_SIMPLE_HULL = 1;
|
||||
var SHAPE_TYPE_SIMPLE_COMPOUND = 2;
|
||||
var SHAPE_TYPE_STATIC_MESH = 3;
|
||||
|
||||
var SHAPE_TYPE_BOX = 4;
|
||||
var SHAPE_TYPE_SPHERE = 5;
|
||||
|
||||
var SHAPE_TYPES = [];
|
||||
SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision";
|
||||
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model";
|
||||
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes";
|
||||
SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons";
|
||||
|
||||
SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box";
|
||||
SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere";
|
||||
|
||||
var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH;
|
||||
var DYNAMIC_DEFAULT = false;
|
||||
var prompt = tabletRoot.customInputDialog({
|
||||
|
@ -197,6 +214,12 @@ Rectangle {
|
|||
case SHAPE_TYPE_STATIC_MESH:
|
||||
shapeType = "static-mesh";
|
||||
break;
|
||||
case SHAPE_TYPE_BOX:
|
||||
shapeType = "box";
|
||||
break;
|
||||
case SHAPE_TYPE_SPHERE:
|
||||
shapeType = "sphere";
|
||||
break;
|
||||
default:
|
||||
shapeType = "none";
|
||||
}
|
||||
|
@ -290,23 +313,37 @@ Rectangle {
|
|||
});
|
||||
}
|
||||
function deleteFile(index) {
|
||||
var path = [];
|
||||
|
||||
if (!index) {
|
||||
index = treeView.selection.currentIndex;
|
||||
for (var i = 0; i < selectedItems; i++) {
|
||||
treeView.selection.setCurrentIndex(treeView.selection.selectedIndexes[i], 0x100);
|
||||
index = treeView.selection.currentIndex;
|
||||
path[i] = assetProxyModel.data(index, 0x100);
|
||||
}
|
||||
}
|
||||
var path = assetProxyModel.data(index, 0x100);
|
||||
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
|
||||
var modalMessage = "";
|
||||
var items = selectedItems.toString();
|
||||
var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101);
|
||||
var typeString = isFolder ? 'folder' : 'file';
|
||||
|
||||
if (selectedItems > 1) {
|
||||
modalMessage = "You are about to delete " + items + " items \nDo you want to continue?";
|
||||
} else {
|
||||
modalMessage = "You are about to delete the following " + typeString + ":\n" + path + "\nDo you want to continue?";
|
||||
}
|
||||
|
||||
var object = tabletRoot.messageBox({
|
||||
icon: hifi.icons.question,
|
||||
buttons: OriginalDialogs.StandardButton.Yes + OriginalDialogs.StandardButton.No,
|
||||
defaultButton: OriginalDialogs.StandardButton.Yes,
|
||||
title: "Delete",
|
||||
text: "You are about to delete the following " + typeString + ":\n" + path + "\nDo you want to continue?"
|
||||
text: modalMessage
|
||||
});
|
||||
object.selected.connect(function(button) {
|
||||
if (button === OriginalDialogs.StandardButton.Yes) {
|
||||
|
@ -459,7 +496,7 @@ Rectangle {
|
|||
width: 80
|
||||
|
||||
onClicked: root.renameFile()
|
||||
enabled: treeView.selection.hasSelection
|
||||
enabled: canRename()
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
|
@ -515,6 +552,7 @@ Rectangle {
|
|||
treeModel: assetProxyModel
|
||||
canEdit: true
|
||||
colorScheme: root.colorScheme
|
||||
selectionMode: SelectionMode.ExtendedSelection
|
||||
|
||||
modifyEl: renameEl
|
||||
|
||||
|
|
|
@ -145,7 +145,9 @@ Rectangle {
|
|||
model: ["No Collision",
|
||||
"Basic - Whole model",
|
||||
"Good - Sub-meshes",
|
||||
"Exact - All polygons"]
|
||||
"Exact - All polygons",
|
||||
"Box",
|
||||
"Sphere"]
|
||||
}
|
||||
|
||||
Row {
|
||||
|
|
|
@ -61,6 +61,7 @@
|
|||
#include <AssetClient.h>
|
||||
#include <AssetUpload.h>
|
||||
#include <AutoUpdater.h>
|
||||
#include <Midi.h>
|
||||
#include <AudioInjectorManager.h>
|
||||
#include <AvatarBookmarks.h>
|
||||
#include <CursorManager.h>
|
||||
|
@ -398,6 +399,10 @@ public:
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (message->message == WM_DEVICECHANGE) {
|
||||
Midi::USBchanged(); // re-scan the MIDI bus
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -567,6 +572,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
DependencyManager::set<SceneScriptingInterface>();
|
||||
DependencyManager::set<OffscreenUi>();
|
||||
DependencyManager::set<AutoUpdater>();
|
||||
DependencyManager::set<Midi>();
|
||||
DependencyManager::set<PathUtils>();
|
||||
DependencyManager::set<InterfaceDynamicFactory>();
|
||||
DependencyManager::set<AudioInjectorManager>();
|
||||
|
@ -618,6 +624,7 @@ const float DEFAULT_DESKTOP_TABLET_SCALE_PERCENT = 75.0f;
|
|||
const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true;
|
||||
const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false;
|
||||
const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = false;
|
||||
const QString DEFAULT_CURSOR_NAME = "DEFAULT";
|
||||
|
||||
Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runningMarkerExisted) :
|
||||
QApplication(argc, argv),
|
||||
|
@ -637,6 +644,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
_hmdTabletBecomesToolbarSetting("hmdTabletBecomesToolbar", DEFAULT_HMD_TABLET_BECOMES_TOOLBAR),
|
||||
_preferAvatarFingerOverStylusSetting("preferAvatarFingerOverStylus", DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS),
|
||||
_constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true),
|
||||
_preferredCursor("preferredCursor", DEFAULT_CURSOR_NAME),
|
||||
_scaleMirror(1.0f),
|
||||
_rotateMirror(0.0f),
|
||||
_raiseMirror(0.0f),
|
||||
|
@ -932,14 +940,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
_glWidget->setFocusPolicy(Qt::StrongFocus);
|
||||
_glWidget->setFocus();
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
auto cursorTarget = _window; // OSX doesn't seem to provide for hiding the cursor only on the GL widget
|
||||
#else
|
||||
// On windows and linux, hiding the top level cursor also means it's invisible when hovering over the
|
||||
// window menu, which is a pain, so only hide it for the GL surface
|
||||
auto cursorTarget = _glWidget;
|
||||
#endif
|
||||
cursorTarget->setCursor(Qt::BlankCursor);
|
||||
showCursor(Cursor::Manager::lookupIcon(_preferredCursor.get()));
|
||||
|
||||
// enable mouse tracking; otherwise, we only get drag events
|
||||
_glWidget->setMouseTracking(true);
|
||||
|
@ -1737,9 +1738,16 @@ void Application::checkChangeCursor() {
|
|||
}
|
||||
}
|
||||
|
||||
void Application::showCursor(const QCursor& cursor) {
|
||||
void Application::showCursor(const Cursor::Icon& cursor) {
|
||||
QMutexLocker locker(&_changeCursorLock);
|
||||
_desiredCursor = cursor;
|
||||
|
||||
auto managedCursor = Cursor::Manager::instance().getCursor();
|
||||
auto curIcon = managedCursor->getIcon();
|
||||
if (curIcon != cursor) {
|
||||
managedCursor->setIcon(cursor);
|
||||
curIcon = cursor;
|
||||
}
|
||||
_desiredCursor = cursor == Cursor::Icon::SYSTEM ? Qt::ArrowCursor : Qt::BlankCursor;
|
||||
_cursorNeedsChanging = true;
|
||||
}
|
||||
|
||||
|
@ -2159,9 +2167,11 @@ void Application::initializeUi() {
|
|||
_window->setMenuBar(new Menu());
|
||||
|
||||
auto compositorHelper = DependencyManager::get<CompositorHelper>();
|
||||
connect(compositorHelper.data(), &CompositorHelper::allowMouseCaptureChanged, [=] {
|
||||
connect(compositorHelper.data(), &CompositorHelper::allowMouseCaptureChanged, this, [=] {
|
||||
if (isHMDMode()) {
|
||||
showCursor(compositorHelper->getAllowMouseCapture() ? Qt::BlankCursor : Qt::ArrowCursor);
|
||||
showCursor(compositorHelper->getAllowMouseCapture() ?
|
||||
Cursor::Manager::lookupIcon(_preferredCursor.get()) :
|
||||
Cursor::Icon::SYSTEM);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -2313,7 +2323,7 @@ void Application::paintGL() {
|
|||
}
|
||||
} else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
|
||||
if (isHMDMode()) {
|
||||
auto mirrorBodyOrientation = myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f));
|
||||
auto mirrorBodyOrientation = myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f));
|
||||
|
||||
glm::quat hmdRotation = extractRotation(myAvatar->getHMDSensorMatrix());
|
||||
// Mirror HMD yaw and roll
|
||||
|
@ -2335,7 +2345,7 @@ void Application::paintGL() {
|
|||
+ mirrorBodyOrientation * glm::vec3(0.0f, 0.0f, 1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror
|
||||
+ mirrorBodyOrientation * hmdOffset);
|
||||
} else {
|
||||
_myCamera.setOrientation(myAvatar->getWorldAlignedOrientation()
|
||||
_myCamera.setOrientation(myAvatar->getOrientation()
|
||||
* glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
|
||||
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
|
||||
+ glm::vec3(0, _raiseMirror * myAvatar->getUniformScale(), 0)
|
||||
|
@ -2503,6 +2513,12 @@ void Application::setPreferAvatarFingerOverStylus(bool value) {
|
|||
_preferAvatarFingerOverStylusSetting.set(value);
|
||||
}
|
||||
|
||||
void Application::setPreferredCursor(const QString& cursorName) {
|
||||
qCDebug(interfaceapp) << "setPreferredCursor" << cursorName;
|
||||
_preferredCursor.set(cursorName.isEmpty() ? DEFAULT_CURSOR_NAME : cursorName);
|
||||
showCursor(Cursor::Manager::lookupIcon(_preferredCursor.get()));
|
||||
}
|
||||
|
||||
void Application::setSettingConstrainToolbarPosition(bool setting) {
|
||||
_constrainToolbarPosition.set(setting);
|
||||
DependencyManager::get<OffscreenUi>()->setConstrainToolbarToCenterX(setting);
|
||||
|
@ -3019,7 +3035,9 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
break;
|
||||
|
||||
case Qt::Key_F: {
|
||||
_physicsEngine->dumpNextStats();
|
||||
if (isOption) {
|
||||
_physicsEngine->dumpNextStats();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -3063,9 +3081,13 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
auto cursor = Cursor::Manager::instance().getCursor();
|
||||
auto curIcon = cursor->getIcon();
|
||||
if (curIcon == Cursor::Icon::DEFAULT) {
|
||||
cursor->setIcon(Cursor::Icon::LINK);
|
||||
showCursor(Cursor::Icon::RETICLE);
|
||||
} else if (curIcon == Cursor::Icon::RETICLE) {
|
||||
showCursor(Cursor::Icon::SYSTEM);
|
||||
} else if (curIcon == Cursor::Icon::SYSTEM) {
|
||||
showCursor(Cursor::Icon::LINK);
|
||||
} else {
|
||||
cursor->setIcon(Cursor::Icon::DEFAULT);
|
||||
showCursor(Cursor::Icon::DEFAULT);
|
||||
}
|
||||
} else {
|
||||
resetSensors(true);
|
||||
|
@ -4459,27 +4481,29 @@ void Application::cameraModeChanged() {
|
|||
|
||||
|
||||
void Application::cameraMenuChanged() {
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) {
|
||||
auto menu = Menu::getInstance();
|
||||
if (menu->isOptionChecked(MenuOption::FullscreenMirror)) {
|
||||
if (_myCamera.getMode() != CAMERA_MODE_MIRROR) {
|
||||
_myCamera.setMode(CAMERA_MODE_MIRROR);
|
||||
getMyAvatar()->reset(false, false, false); // to reset any active MyAvatar::FollowHelpers
|
||||
}
|
||||
} else if (Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson)) {
|
||||
} else if (menu->isOptionChecked(MenuOption::FirstPerson)) {
|
||||
if (_myCamera.getMode() != CAMERA_MODE_FIRST_PERSON) {
|
||||
_myCamera.setMode(CAMERA_MODE_FIRST_PERSON);
|
||||
getMyAvatar()->setBoomLength(MyAvatar::ZOOM_MIN);
|
||||
}
|
||||
} else if (Menu::getInstance()->isOptionChecked(MenuOption::ThirdPerson)) {
|
||||
} else if (menu->isOptionChecked(MenuOption::ThirdPerson)) {
|
||||
if (_myCamera.getMode() != CAMERA_MODE_THIRD_PERSON) {
|
||||
_myCamera.setMode(CAMERA_MODE_THIRD_PERSON);
|
||||
if (getMyAvatar()->getBoomLength() == MyAvatar::ZOOM_MIN) {
|
||||
getMyAvatar()->setBoomLength(MyAvatar::ZOOM_DEFAULT);
|
||||
}
|
||||
}
|
||||
} else if (Menu::getInstance()->isOptionChecked(MenuOption::IndependentMode)) {
|
||||
} else if (menu->isOptionChecked(MenuOption::IndependentMode)) {
|
||||
if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) {
|
||||
_myCamera.setMode(CAMERA_MODE_INDEPENDENT);
|
||||
}
|
||||
} else if (Menu::getInstance()->isOptionChecked(MenuOption::CameraEntityMode)) {
|
||||
} else if (menu->isOptionChecked(MenuOption::CameraEntityMode)) {
|
||||
if (_myCamera.getMode() != CAMERA_MODE_ENTITY) {
|
||||
_myCamera.setMode(CAMERA_MODE_ENTITY);
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
#include "BandwidthRecorder.h"
|
||||
#include "FancyCamera.h"
|
||||
#include "ConnectionMonitor.h"
|
||||
#include "CursorManager.h"
|
||||
#include "gpu/Context.h"
|
||||
#include "Menu.h"
|
||||
#include "octree/OctreePacketProcessor.h"
|
||||
|
@ -165,7 +166,7 @@ public:
|
|||
QSize getDeviceSize() const;
|
||||
bool hasFocus() const;
|
||||
|
||||
void showCursor(const QCursor& cursor);
|
||||
void showCursor(const Cursor::Icon& cursor);
|
||||
|
||||
bool isThrottleRendering() const;
|
||||
|
||||
|
@ -400,6 +401,9 @@ public slots:
|
|||
void loadDomainConnectionDialog();
|
||||
void showScriptLogs();
|
||||
|
||||
const QString getPreferredCursor() const { return _preferredCursor.get(); }
|
||||
void setPreferredCursor(const QString& cursor);
|
||||
|
||||
private slots:
|
||||
void showDesktop();
|
||||
void clearDomainOctreeDetails();
|
||||
|
@ -564,6 +568,7 @@ private:
|
|||
Setting::Handle<bool> _hmdTabletBecomesToolbarSetting;
|
||||
Setting::Handle<bool> _preferAvatarFingerOverStylusSetting;
|
||||
Setting::Handle<bool> _constrainToolbarPosition;
|
||||
Setting::Handle<QString> _preferredCursor;
|
||||
|
||||
float _scaleMirror;
|
||||
float _rotateMirror;
|
||||
|
@ -637,7 +642,7 @@ private:
|
|||
|
||||
void checkChangeCursor();
|
||||
mutable QMutex _changeCursorLock { QMutex::Recursive };
|
||||
QCursor _desiredCursor{ Qt::BlankCursor };
|
||||
Qt::CursorShape _desiredCursor{ Qt::BlankCursor };
|
||||
bool _cursorNeedsChanging { false };
|
||||
|
||||
QThread* _deadlockWatchdogThread;
|
||||
|
|
|
@ -222,19 +222,19 @@ Menu::Menu() {
|
|||
cameraModeGroup->setExclusive(true);
|
||||
|
||||
// View > First Person
|
||||
cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu,
|
||||
MenuOption::FirstPerson, 0,
|
||||
true, qApp, SLOT(cameraMenuChanged())));
|
||||
cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
|
||||
viewMenu, MenuOption::FirstPerson, Qt::CTRL | Qt::Key_F,
|
||||
true, qApp, SLOT(cameraMenuChanged())));
|
||||
|
||||
// View > Third Person
|
||||
cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu,
|
||||
MenuOption::ThirdPerson, 0,
|
||||
false, qApp, SLOT(cameraMenuChanged())));
|
||||
cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
|
||||
viewMenu, MenuOption::ThirdPerson, Qt::CTRL | Qt::Key_G,
|
||||
false, qApp, SLOT(cameraMenuChanged())));
|
||||
|
||||
// View > Mirror
|
||||
cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu,
|
||||
MenuOption::FullscreenMirror, 0,
|
||||
false, qApp, SLOT(cameraMenuChanged())));
|
||||
cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
|
||||
viewMenu, MenuOption::FullscreenMirror, Qt::CTRL | Qt::Key_H,
|
||||
false, qApp, SLOT(cameraMenuChanged())));
|
||||
|
||||
// View > Independent [advanced]
|
||||
cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu,
|
||||
|
|
|
@ -258,6 +258,7 @@ MyAvatar::~MyAvatar() {
|
|||
void MyAvatar::setDominantHand(const QString& hand) {
|
||||
if (hand == DOMINANT_LEFT_HAND || hand == DOMINANT_RIGHT_HAND) {
|
||||
_dominantHand = hand;
|
||||
emit dominantHandChanged(_dominantHand);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -613,7 +614,7 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
MovingEntitiesOperator moveOperator(entityTree);
|
||||
forEachDescendant([&](SpatiallyNestablePointer object) {
|
||||
// if the queryBox has changed, tell the entity-server
|
||||
if (object->computePuffedQueryAACube() && object->getNestableType() == NestableType::Entity) {
|
||||
if (object->getNestableType() == NestableType::Entity && object->checkAndMaybeUpdateQueryAACube()) {
|
||||
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
|
||||
bool success;
|
||||
AACube newCube = entity->getQueryAACube(success);
|
||||
|
@ -1298,7 +1299,7 @@ eyeContactTarget MyAvatar::getEyeContactTarget() {
|
|||
}
|
||||
|
||||
glm::vec3 MyAvatar::getDefaultEyePosition() const {
|
||||
return getPosition() + getWorldAlignedOrientation() * Quaternions::Y_180 * _skeletonModel->getDefaultEyeModelPosition();
|
||||
return getPosition() + getOrientation() * Quaternions::Y_180 * _skeletonModel->getDefaultEyeModelPosition();
|
||||
}
|
||||
|
||||
const float SCRIPT_PRIORITY = 1.0f + 1.0f;
|
||||
|
@ -1593,9 +1594,14 @@ void MyAvatar::updateMotors() {
|
|||
motorRotation = getMyHead()->getHeadOrientation();
|
||||
} else {
|
||||
// non-hovering = walking: follow camera twist about vertical but not lift
|
||||
// so we decompose camera's rotation and store the twist part in motorRotation
|
||||
// we decompose camera's rotation and store the twist part in motorRotation
|
||||
// however, we need to perform the decomposition in the avatar-frame
|
||||
// using the local UP axis and then transform back into world-frame
|
||||
glm::quat orientation = getOrientation();
|
||||
glm::quat headOrientation = glm::inverse(orientation) * getMyHead()->getHeadOrientation(); // avatar-frame
|
||||
glm::quat liftRotation;
|
||||
swingTwistDecomposition(getMyHead()->getHeadOrientation(), _worldUpDirection, liftRotation, motorRotation);
|
||||
swingTwistDecomposition(headOrientation, Vectors::UNIT_Y, liftRotation, motorRotation);
|
||||
motorRotation = orientation * motorRotation;
|
||||
}
|
||||
const float DEFAULT_MOTOR_TIMESCALE = 0.2f;
|
||||
const float INVALID_MOTOR_TIMESCALE = 1.0e6f;
|
||||
|
@ -1649,11 +1655,31 @@ void MyAvatar::prepareForPhysicsSimulation() {
|
|||
_prePhysicsRoomPose = AnimPose(_sensorToWorldMatrix);
|
||||
}
|
||||
|
||||
// There are a number of possible strategies for this set of tools through endRender, below.
|
||||
void MyAvatar::nextAttitude(glm::vec3 position, glm::quat orientation) {
|
||||
bool success;
|
||||
Transform trans = getTransform(success);
|
||||
if (!success) {
|
||||
qCWarning(interfaceapp) << "Warning -- MyAvatar::nextAttitude failed";
|
||||
return;
|
||||
}
|
||||
trans.setTranslation(position);
|
||||
trans.setRotation(orientation);
|
||||
SpatiallyNestable::setTransform(trans, success);
|
||||
if (!success) {
|
||||
qCWarning(interfaceapp) << "Warning -- MyAvatar::nextAttitude failed";
|
||||
}
|
||||
updateAttitude(orientation);
|
||||
}
|
||||
|
||||
void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) {
|
||||
glm::vec3 position = getPosition();
|
||||
glm::quat orientation = getOrientation();
|
||||
glm::vec3 position;
|
||||
glm::quat orientation;
|
||||
if (_characterController.isEnabledAndReady()) {
|
||||
_characterController.getPositionAndOrientation(position, orientation);
|
||||
} else {
|
||||
position = getPosition();
|
||||
orientation = getOrientation();
|
||||
}
|
||||
nextAttitude(position, orientation);
|
||||
_bodySensorMatrix = _follow.postPhysicsUpdate(*this, _bodySensorMatrix);
|
||||
|
@ -2848,7 +2874,8 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co
|
|||
void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix,
|
||||
const glm::mat4& currentBodyMatrix, bool hasDriveInput) {
|
||||
|
||||
if (myAvatar.getHMDLeanRecenterEnabled()) {
|
||||
if (myAvatar.getHMDLeanRecenterEnabled() &&
|
||||
qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) {
|
||||
if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
|
||||
activate(Rotation);
|
||||
}
|
||||
|
|
|
@ -438,6 +438,7 @@ public:
|
|||
|
||||
void updateMotors();
|
||||
void prepareForPhysicsSimulation();
|
||||
void nextAttitude(glm::vec3 position, glm::quat orientation); // Can be safely called at any time.
|
||||
void harvestResultsFromPhysicsSimulation(float deltaTime);
|
||||
|
||||
const QString& getCollisionSoundURL() { return _collisionSoundURL; }
|
||||
|
@ -557,7 +558,6 @@ public:
|
|||
Q_INVOKABLE bool isUp(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) > 0.0f; }; // true iff direction points up wrt avatar's definition of up.
|
||||
Q_INVOKABLE bool isDown(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) < 0.0f; };
|
||||
|
||||
|
||||
public slots:
|
||||
void increaseSize();
|
||||
void decreaseSize();
|
||||
|
@ -615,6 +615,7 @@ signals:
|
|||
void wentAway();
|
||||
void wentActive();
|
||||
void skeletonChanged();
|
||||
void dominantHandChanged(const QString& hand);
|
||||
|
||||
private:
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ MyHead::MyHead(MyAvatar* owningAvatar) : Head(owningAvatar) {
|
|||
glm::quat MyHead::getHeadOrientation() const {
|
||||
// NOTE: Head::getHeadOrientation() is not used for orienting the camera "view" while in Oculus mode, so
|
||||
// you may wonder why this code is here. This method will be called while in Oculus mode to determine how
|
||||
// to change the driving direction while in Oculus mode. It is used to support driving toward where you're
|
||||
// to change the driving direction while in Oculus mode. It is used to support driving toward where your
|
||||
// head is looking. Note that in oculus mode, your actual camera view and where your head is looking is not
|
||||
// always the same.
|
||||
|
||||
|
@ -39,7 +39,7 @@ glm::quat MyHead::getHeadOrientation() const {
|
|||
return headPose.rotation * Quaternions::Y_180;
|
||||
}
|
||||
|
||||
return myAvatar->getWorldAlignedOrientation() * glm::quat(glm::radians(glm::vec3(_basePitch, 0.0f, 0.0f)));
|
||||
return myAvatar->getOrientation() * glm::quat(glm::radians(glm::vec3(_basePitch, 0.0f, 0.0f)));
|
||||
}
|
||||
|
||||
void MyHead::simulate(float deltaTime) {
|
||||
|
|
|
@ -106,6 +106,12 @@ void setupPreferences() {
|
|||
auto setter = [](bool value) { qApp->setPreferAvatarFingerOverStylus(value); };
|
||||
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Avatar Finger Over Stylus", getter, setter));
|
||||
}
|
||||
{
|
||||
static const QString RETICLE_ICON_NAME = { Cursor::Manager::getIconName(Cursor::Icon::RETICLE) };
|
||||
auto getter = []()->bool { return qApp->getPreferredCursor() == RETICLE_ICON_NAME; };
|
||||
auto setter = [](bool value) { qApp->setPreferredCursor(value ? RETICLE_ICON_NAME : QString()); };
|
||||
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Use reticle cursor instead of arrow", getter, setter));
|
||||
}
|
||||
|
||||
// Snapshots
|
||||
static const QString SNAPSHOTS { "Snapshots" };
|
||||
|
|
|
@ -192,6 +192,7 @@ void Web3DOverlay::loadSourceURL() {
|
|||
_webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get<SoundCache>().data());
|
||||
|
||||
_webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../");
|
||||
|
||||
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data());
|
||||
|
||||
// mark the TabletProxy object as cpp ownership.
|
||||
|
|
|
@ -151,11 +151,6 @@ glm::vec3 Avatar::getNeckPosition() const {
|
|||
return _skeletonModel->getNeckPosition(neckPosition) ? neckPosition : getPosition();
|
||||
}
|
||||
|
||||
|
||||
glm::quat Avatar::getWorldAlignedOrientation () const {
|
||||
return computeRotationFromBodyToWorldUp() * getOrientation();
|
||||
}
|
||||
|
||||
AABox Avatar::getBounds() const {
|
||||
if (!_skeletonModel->isRenderable() || _skeletonModel->needsFixupInScene()) {
|
||||
// approximately 2m tall, scaled to user request.
|
||||
|
@ -436,6 +431,11 @@ void Avatar::slamPosition(const glm::vec3& newPosition) {
|
|||
_lastVelocity = glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
void Avatar::updateAttitude(const glm::quat& orientation) {
|
||||
_skeletonModel->updateAttitude(orientation);
|
||||
_worldUpDirection = orientation * Vectors::UNIT_Y;
|
||||
}
|
||||
|
||||
void Avatar::applyPositionDelta(const glm::vec3& delta) {
|
||||
setPosition(getPosition() + delta);
|
||||
_positionDeltaAccumulator += delta;
|
||||
|
@ -628,22 +628,6 @@ void Avatar::render(RenderArgs* renderArgs) {
|
|||
}
|
||||
}
|
||||
|
||||
glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const {
|
||||
glm::quat orientation = getOrientation();
|
||||
glm::vec3 currentUp = orientation * IDENTITY_UP;
|
||||
float angle = acosf(glm::clamp(glm::dot(currentUp, _worldUpDirection), -1.0f, 1.0f));
|
||||
if (angle < EPSILON) {
|
||||
return glm::quat();
|
||||
}
|
||||
glm::vec3 axis;
|
||||
if (angle > 179.99f * RADIANS_PER_DEGREE) { // 180 degree rotation; must use another axis
|
||||
axis = orientation * IDENTITY_RIGHT;
|
||||
} else {
|
||||
axis = glm::normalize(glm::cross(currentUp, _worldUpDirection));
|
||||
}
|
||||
return glm::angleAxis(angle * proportion, axis);
|
||||
}
|
||||
|
||||
void Avatar::fixupModelsInScene(const render::ScenePointer& scene) {
|
||||
_attachmentsToDelete.clear();
|
||||
|
||||
|
@ -915,17 +899,34 @@ glm::vec3 Avatar::getDefaultJointTranslation(int index) const {
|
|||
}
|
||||
|
||||
glm::quat Avatar::getAbsoluteDefaultJointRotationInObjectFrame(int index) const {
|
||||
glm::quat rotation;
|
||||
glm::quat rot = _skeletonModel->getRig().getAnimSkeleton()->getAbsoluteDefaultPose(index).rot();
|
||||
return Quaternions::Y_180 * rot;
|
||||
// To make this thread safe, we hold onto the model by smart ptr, which prevents it from being deleted while we are accessing it.
|
||||
auto model = getSkeletonModel();
|
||||
if (model) {
|
||||
auto skeleton = model->getRig().getAnimSkeleton();
|
||||
if (skeleton && index >= 0 && index < skeleton->getNumJoints()) {
|
||||
// The rotation part of the geometry-to-rig transform is always identity so we can skip it.
|
||||
// Y_180 is to convert from rig-frame into avatar-frame
|
||||
return Quaternions::Y_180 * skeleton->getAbsoluteDefaultPose(index).rot();
|
||||
}
|
||||
}
|
||||
return Quaternions::Y_180;
|
||||
}
|
||||
|
||||
glm::vec3 Avatar::getAbsoluteDefaultJointTranslationInObjectFrame(int index) const {
|
||||
glm::vec3 translation;
|
||||
const Rig& rig = _skeletonModel->getRig();
|
||||
glm::vec3 trans = rig.getAnimSkeleton()->getAbsoluteDefaultPose(index).trans();
|
||||
glm::mat4 y180Mat = createMatFromQuatAndPos(Quaternions::Y_180, glm::vec3());
|
||||
return transformPoint(y180Mat * rig.getGeometryToRigTransform(), trans);
|
||||
// To make this thread safe, we hold onto the model by smart ptr, which prevents it from being deleted while we are accessing it.
|
||||
auto model = getSkeletonModel();
|
||||
if (model) {
|
||||
const Rig& rig = model->getRig();
|
||||
auto skeleton = rig.getAnimSkeleton();
|
||||
if (skeleton && index >= 0 && index < skeleton->getNumJoints()) {
|
||||
// trans is in geometry frame.
|
||||
glm::vec3 trans = skeleton->getAbsoluteDefaultPose(index).trans();
|
||||
// Y_180 is to convert from rig-frame into avatar-frame
|
||||
glm::mat4 geomToAvatarMat = Matrices::Y_180 * rig.getGeometryToRigTransform();
|
||||
return transformPoint(geomToAvatarMat, trans);
|
||||
}
|
||||
}
|
||||
return Vectors::ZERO;
|
||||
}
|
||||
|
||||
glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const {
|
||||
|
@ -1401,14 +1402,14 @@ glm::quat Avatar::getUncachedRightPalmRotation() const {
|
|||
return rightPalmRotation;
|
||||
}
|
||||
|
||||
void Avatar::setPosition(const glm::vec3& position) {
|
||||
AvatarData::setPosition(position);
|
||||
updateAttitude();
|
||||
void Avatar::setPositionViaScript(const glm::vec3& position) {
|
||||
setPosition(position);
|
||||
updateAttitude(getOrientation());
|
||||
}
|
||||
|
||||
void Avatar::setOrientation(const glm::quat& orientation) {
|
||||
AvatarData::setOrientation(orientation);
|
||||
updateAttitude();
|
||||
void Avatar::setOrientationViaScript(const glm::quat& orientation) {
|
||||
setOrientation(orientation);
|
||||
updateAttitude(orientation);
|
||||
}
|
||||
|
||||
void Avatar::updatePalms() {
|
||||
|
|
|
@ -112,8 +112,6 @@ public:
|
|||
const Head* getHead() const { return static_cast<const Head*>(_headData); }
|
||||
Head* getHead() { return static_cast<Head*>(_headData); }
|
||||
|
||||
glm::quat getWorldAlignedOrientation() const;
|
||||
|
||||
AABox getBounds() const;
|
||||
|
||||
/// Returns the distance to use as a LOD parameter.
|
||||
|
@ -184,7 +182,7 @@ public:
|
|||
void scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const;
|
||||
|
||||
void slamPosition(const glm::vec3& position);
|
||||
virtual void updateAttitude() override { _skeletonModel->updateAttitude(); }
|
||||
virtual void updateAttitude(const glm::quat& orientation) override;
|
||||
|
||||
// Call this when updating Avatar position with a delta. This will allow us to
|
||||
// _accurately_ measure position changes and compute the resulting velocity
|
||||
|
@ -197,10 +195,8 @@ public:
|
|||
void getCapsule(glm::vec3& start, glm::vec3& end, float& radius);
|
||||
float computeMass();
|
||||
|
||||
using SpatiallyNestable::setPosition;
|
||||
virtual void setPosition(const glm::vec3& position) override;
|
||||
using SpatiallyNestable::setOrientation;
|
||||
virtual void setOrientation(const glm::quat& orientation) override;
|
||||
void setPositionViaScript(const glm::vec3& position) override;
|
||||
void setOrientationViaScript(const glm::quat& orientation) override;
|
||||
|
||||
// these call through to the SpatiallyNestable versions, but they are here to expose these to javascript.
|
||||
Q_INVOKABLE virtual const QUuid getParentID() const override { return SpatiallyNestable::getParentID(); }
|
||||
|
@ -240,7 +236,7 @@ public:
|
|||
bool hasNewJointData() const { return _hasNewJointData; }
|
||||
|
||||
float getBoundingRadius() const;
|
||||
|
||||
|
||||
void addToScene(AvatarSharedPointer self, const render::ScenePointer& scene);
|
||||
void ensureInScene(AvatarSharedPointer self, const render::ScenePointer& scene);
|
||||
bool isInScene() const { return render::Item::isValidID(_renderItemID); }
|
||||
|
@ -303,7 +299,6 @@ protected:
|
|||
|
||||
glm::vec3 getBodyRightDirection() const { return getOrientation() * IDENTITY_RIGHT; }
|
||||
glm::vec3 getBodyUpDirection() const { return getOrientation() * IDENTITY_UP; }
|
||||
glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const;
|
||||
void measureMotionDerivatives(float deltaTime);
|
||||
|
||||
float getSkeletonHeight() const;
|
||||
|
|
|
@ -118,16 +118,16 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
_rig.updateFromEyeParameters(eyeParams);
|
||||
}
|
||||
|
||||
void SkeletonModel::updateAttitude() {
|
||||
void SkeletonModel::updateAttitude(const glm::quat& orientation) {
|
||||
setTranslation(_owningAvatar->getSkeletonPosition());
|
||||
setRotation(_owningAvatar->getOrientation() * Quaternions::Y_180);
|
||||
setRotation(orientation * Quaternions::Y_180);
|
||||
setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningAvatar->getScale());
|
||||
}
|
||||
|
||||
// Called by Avatar::simulate after it has set the joint states (fullUpdate true if changed),
|
||||
// but just before head has been simulated.
|
||||
void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
|
||||
updateAttitude();
|
||||
updateAttitude(_owningAvatar->getOrientation());
|
||||
if (fullUpdate) {
|
||||
setBlendshapeCoefficients(_owningAvatar->getHead()->getSummedBlendshapeCoefficients());
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ public:
|
|||
|
||||
void simulate(float deltaTime, bool fullUpdate = true) override;
|
||||
void updateRig(float deltaTime, glm::mat4 parentTransform) override;
|
||||
void updateAttitude();
|
||||
void updateAttitude(const glm::quat& orientation);
|
||||
|
||||
/// Returns the index of the left hand joint, or -1 if not found.
|
||||
int getLeftHandJointIndex() const { return isActive() ? getFBXGeometry().leftHandJointIndex : -1; }
|
||||
|
|
|
@ -91,9 +91,6 @@ AvatarData::AvatarData() :
|
|||
_targetVelocity(0.0f),
|
||||
_density(DEFAULT_AVATAR_DENSITY)
|
||||
{
|
||||
setBodyPitch(0.0f);
|
||||
setBodyYaw(-90.0f);
|
||||
setBodyRoll(0.0f);
|
||||
}
|
||||
|
||||
AvatarData::~AvatarData() {
|
||||
|
@ -110,23 +107,6 @@ const QUrl& AvatarData::defaultFullAvatarModelUrl() {
|
|||
return _defaultFullAvatarModelUrl;
|
||||
}
|
||||
|
||||
// There are a number of possible strategies for this set of tools through endRender, below.
|
||||
void AvatarData::nextAttitude(glm::vec3 position, glm::quat orientation) {
|
||||
bool success;
|
||||
Transform trans = getTransform(success);
|
||||
if (!success) {
|
||||
qCWarning(avatars) << "Warning -- AvatarData::nextAttitude failed";
|
||||
return;
|
||||
}
|
||||
trans.setTranslation(position);
|
||||
trans.setRotation(orientation);
|
||||
SpatiallyNestable::setTransform(trans, success);
|
||||
if (!success) {
|
||||
qCWarning(avatars) << "Warning -- AvatarData::nextAttitude failed";
|
||||
}
|
||||
updateAttitude();
|
||||
}
|
||||
|
||||
void AvatarData::setTargetScale(float targetScale) {
|
||||
auto newValue = glm::clamp(targetScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE);
|
||||
if (_targetScale != newValue) {
|
||||
|
@ -2100,6 +2080,7 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) {
|
|||
currentBasis = std::make_shared<Transform>(Transform::fromJson(json[JSON_AVATAR_BASIS]));
|
||||
}
|
||||
|
||||
glm::quat orientation;
|
||||
if (json.contains(JSON_AVATAR_RELATIVE)) {
|
||||
// During playback you can either have the recording basis set to the avatar current state
|
||||
// meaning that all playback is relative to this avatars starting position, or
|
||||
|
@ -2111,12 +2092,14 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) {
|
|||
auto relativeTransform = Transform::fromJson(json[JSON_AVATAR_RELATIVE]);
|
||||
auto worldTransform = currentBasis->worldTransform(relativeTransform);
|
||||
setPosition(worldTransform.getTranslation());
|
||||
setOrientation(worldTransform.getRotation());
|
||||
orientation = worldTransform.getRotation();
|
||||
} else {
|
||||
// We still set the position in the case that there is no movement.
|
||||
setPosition(currentBasis->getTranslation());
|
||||
setOrientation(currentBasis->getRotation());
|
||||
orientation = currentBasis->getRotation();
|
||||
}
|
||||
setOrientation(orientation);
|
||||
updateAttitude(orientation);
|
||||
|
||||
// Do after avatar orientation because head look-at needs avatar orientation.
|
||||
if (json.contains(JSON_AVATAR_HEAD)) {
|
||||
|
@ -2234,11 +2217,11 @@ void AvatarData::setBodyRoll(float bodyRoll) {
|
|||
setOrientation(glm::quat(glm::radians(eulerAngles)));
|
||||
}
|
||||
|
||||
void AvatarData::setPosition(const glm::vec3& position) {
|
||||
void AvatarData::setPositionViaScript(const glm::vec3& position) {
|
||||
SpatiallyNestable::setPosition(position);
|
||||
}
|
||||
|
||||
void AvatarData::setOrientation(const glm::quat& orientation) {
|
||||
void AvatarData::setOrientationViaScript(const glm::quat& orientation) {
|
||||
SpatiallyNestable::setOrientation(orientation);
|
||||
}
|
||||
|
||||
|
|
|
@ -351,14 +351,14 @@ public:
|
|||
class AvatarData : public QObject, public SpatiallyNestable {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPosition)
|
||||
Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPositionViaScript)
|
||||
Q_PROPERTY(float scale READ getTargetScale WRITE setTargetScale)
|
||||
Q_PROPERTY(glm::vec3 handPosition READ getHandPosition WRITE setHandPosition)
|
||||
Q_PROPERTY(float bodyYaw READ getBodyYaw WRITE setBodyYaw)
|
||||
Q_PROPERTY(float bodyPitch READ getBodyPitch WRITE setBodyPitch)
|
||||
Q_PROPERTY(float bodyRoll READ getBodyRoll WRITE setBodyRoll)
|
||||
|
||||
Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientation)
|
||||
Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientationViaScript)
|
||||
Q_PROPERTY(glm::quat headOrientation READ getHeadOrientation WRITE setHeadOrientation)
|
||||
Q_PROPERTY(float headPitch READ getHeadPitch WRITE setHeadPitch)
|
||||
Q_PROPERTY(float headYaw READ getHeadYaw WRITE setHeadYaw)
|
||||
|
@ -440,13 +440,10 @@ public:
|
|||
float getBodyRoll() const;
|
||||
void setBodyRoll(float bodyRoll);
|
||||
|
||||
using SpatiallyNestable::setPosition;
|
||||
virtual void setPosition(const glm::vec3& position) override;
|
||||
using SpatiallyNestable::setOrientation;
|
||||
virtual void setOrientation(const glm::quat& orientation) override;
|
||||
virtual void setPositionViaScript(const glm::vec3& position);
|
||||
virtual void setOrientationViaScript(const glm::quat& orientation);
|
||||
|
||||
void nextAttitude(glm::vec3 position, glm::quat orientation); // Can be safely called at any time.
|
||||
virtual void updateAttitude() {} // Tell skeleton mesh about changes
|
||||
virtual void updateAttitude(const glm::quat& orientation) {}
|
||||
|
||||
glm::quat getHeadOrientation() const {
|
||||
lazyInitHeadData();
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "filters/DeadZoneFilter.h"
|
||||
#include "filters/HysteresisFilter.h"
|
||||
#include "filters/InvertFilter.h"
|
||||
#include "filters/NotFilter.h"
|
||||
#include "filters/PulseFilter.h"
|
||||
#include "filters/ScaleFilter.h"
|
||||
#include "filters/TranslateFilter.h"
|
||||
|
@ -40,6 +41,7 @@ REGISTER_FILTER_CLASS_INSTANCE(ConstrainToPositiveIntegerFilter, "constrainToPos
|
|||
REGISTER_FILTER_CLASS_INSTANCE(DeadZoneFilter, "deadZone")
|
||||
REGISTER_FILTER_CLASS_INSTANCE(HysteresisFilter, "hysteresis")
|
||||
REGISTER_FILTER_CLASS_INSTANCE(InvertFilter, "invert")
|
||||
REGISTER_FILTER_CLASS_INSTANCE(NotFilter, "logicalNot")
|
||||
REGISTER_FILTER_CLASS_INSTANCE(ScaleFilter, "scale")
|
||||
REGISTER_FILTER_CLASS_INSTANCE(PulseFilter, "pulse")
|
||||
REGISTER_FILTER_CLASS_INSTANCE(TranslateFilter, "translate")
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "filters/DeadZoneFilter.h"
|
||||
#include "filters/HysteresisFilter.h"
|
||||
#include "filters/InvertFilter.h"
|
||||
#include "filters/NotFilter.h"
|
||||
#include "filters/PulseFilter.h"
|
||||
#include "filters/ScaleFilter.h"
|
||||
#include "filters/TranslateFilter.h"
|
||||
|
@ -148,6 +149,11 @@ QObject* RouteBuilderProxy::pulse(float interval) {
|
|||
return this;
|
||||
}
|
||||
|
||||
QObject* RouteBuilderProxy::logicalNot() {
|
||||
addFilter(std::make_shared<NotFilter>());
|
||||
return this;
|
||||
}
|
||||
|
||||
void RouteBuilderProxy::addFilter(Filter::Pointer filter) {
|
||||
_route->filters.push_back(filter);
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ class RouteBuilderProxy : public QObject {
|
|||
Q_INVOKABLE QObject* postTransform(glm::mat4 transform);
|
||||
Q_INVOKABLE QObject* rotate(glm::quat rotation);
|
||||
Q_INVOKABLE QObject* lowVelocity(float rotationConstant, float translationConstant);
|
||||
Q_INVOKABLE QObject* logicalNot();
|
||||
|
||||
private:
|
||||
void to(const Endpoint::Pointer& destination);
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
#include "NotFilter.h"
|
||||
|
||||
using namespace controller;
|
||||
|
||||
NotFilter::NotFilter() {
|
||||
}
|
||||
|
||||
float NotFilter::apply(float value) const {
|
||||
return (value == 0.0f) ? 1.0f : 0.0f;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
#ifndef hifi_Controllers_Filters_Not_h
|
||||
#define hifi_Controllers_Filters_Not_h
|
||||
|
||||
#include "../Filter.h"
|
||||
|
||||
namespace controller {
|
||||
|
||||
class NotFilter : public Filter {
|
||||
REGISTER_FILTER_CLASS(NotFilter);
|
||||
public:
|
||||
NotFilter();
|
||||
|
||||
virtual float apply(float value) const override;
|
||||
virtual Pose apply(Pose value) const override { return value; }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -82,6 +82,80 @@ bool RenderableShapeEntityItem::isTransparent() {
|
|||
}
|
||||
}
|
||||
|
||||
void RenderableShapeEntityItem::computeShapeInfo(ShapeInfo& info) {
|
||||
|
||||
// This will be called whenever DIRTY_SHAPE flag (set by dimension change, etc)
|
||||
// is set.
|
||||
|
||||
const glm::vec3 entityDimensions = getDimensions();
|
||||
|
||||
switch (_shape){
|
||||
case entity::Shape::Quad:
|
||||
case entity::Shape::Cube: {
|
||||
_collisionShapeType = SHAPE_TYPE_BOX;
|
||||
}
|
||||
break;
|
||||
case entity::Shape::Sphere: {
|
||||
|
||||
float diameter = entityDimensions.x;
|
||||
const float MIN_DIAMETER = 0.001f;
|
||||
const float MIN_RELATIVE_SPHERICAL_ERROR = 0.001f;
|
||||
if (diameter > MIN_DIAMETER
|
||||
&& fabsf(diameter - entityDimensions.y) / diameter < MIN_RELATIVE_SPHERICAL_ERROR
|
||||
&& fabsf(diameter - entityDimensions.z) / diameter < MIN_RELATIVE_SPHERICAL_ERROR) {
|
||||
|
||||
_collisionShapeType = SHAPE_TYPE_SPHERE;
|
||||
}
|
||||
else {
|
||||
_collisionShapeType = SHAPE_TYPE_ELLIPSOID;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case entity::Shape::Cylinder: {
|
||||
_collisionShapeType = SHAPE_TYPE_CYLINDER_Y;
|
||||
// TODO WL21389: determine if rotation is axis-aligned
|
||||
//const Transform::Quat & rot = _transform.getRotation();
|
||||
|
||||
// TODO WL21389: some way to tell apart SHAPE_TYPE_CYLINDER_Y, _X, _Z based on rotation and
|
||||
// hull ( or dimensions, need circular cross section)
|
||||
// Should allow for minor variance along axes?
|
||||
|
||||
}
|
||||
break;
|
||||
case entity::Shape::Triangle:
|
||||
case entity::Shape::Hexagon:
|
||||
case entity::Shape::Octagon:
|
||||
case entity::Shape::Circle:
|
||||
case entity::Shape::Tetrahedron:
|
||||
case entity::Shape::Octahedron:
|
||||
case entity::Shape::Dodecahedron:
|
||||
case entity::Shape::Icosahedron:
|
||||
case entity::Shape::Cone: {
|
||||
//TODO WL21389: SHAPE_TYPE_SIMPLE_HULL and pointCollection (later)
|
||||
_collisionShapeType = SHAPE_TYPE_ELLIPSOID;
|
||||
}
|
||||
break;
|
||||
case entity::Shape::Torus:
|
||||
{
|
||||
// Not in GeometryCache::buildShapes, unsupported.
|
||||
_collisionShapeType = SHAPE_TYPE_ELLIPSOID;
|
||||
//TODO WL21389: SHAPE_TYPE_SIMPLE_HULL and pointCollection (later if desired support)
|
||||
}
|
||||
break;
|
||||
default:{
|
||||
_collisionShapeType = SHAPE_TYPE_ELLIPSOID;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
EntityItem::computeShapeInfo(info);
|
||||
}
|
||||
|
||||
// This value specifes how the shape should be treated by physics calculations.
|
||||
ShapeType RenderableShapeEntityItem::getShapeType() const {
|
||||
return _collisionShapeType;
|
||||
}
|
||||
|
||||
void RenderableShapeEntityItem::render(RenderArgs* args) {
|
||||
PerformanceTimer perfTimer("RenderableShapeEntityItem::render");
|
||||
//Q_ASSERT(getType() == EntityTypes::Shape);
|
||||
|
|
|
@ -28,9 +28,18 @@ public:
|
|||
|
||||
bool isTransparent() override;
|
||||
|
||||
virtual void computeShapeInfo(ShapeInfo& info) override;
|
||||
ShapeType getShapeType() const override;
|
||||
|
||||
|
||||
private:
|
||||
std::unique_ptr<Procedural> _procedural { nullptr };
|
||||
|
||||
//! This is SHAPE_TYPE_ELLIPSOID rather than SHAPE_TYPE_NONE to maintain
|
||||
//! prior functionality where new or unsupported shapes are treated as
|
||||
//! ellipsoids.
|
||||
ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_ELLIPSOID };
|
||||
|
||||
SIMPLE_RENDERABLE();
|
||||
};
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ void EntityEditPacketSender::queueEditAvatarEntityMessage(PacketType type,
|
|||
EntityItemProperties entityProperties = entity->getProperties();
|
||||
entityProperties.merge(properties);
|
||||
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
QScriptValue scriptProperties = EntityItemNonDefaultPropertiesToScriptValue(&_scriptEngine, entityProperties);
|
||||
QVariant variantProperties = scriptProperties.toVariant();
|
||||
QJsonDocument jsonProperties = QJsonDocument::fromVariant(variantProperties);
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
#include <OctreeEditPacketSender.h>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "EntityItem.h"
|
||||
#include "AvatarData.h"
|
||||
|
||||
|
@ -49,6 +51,7 @@ private:
|
|||
EntityItemID entityItemID, const EntityItemProperties& properties);
|
||||
|
||||
private:
|
||||
std::mutex _mutex;
|
||||
AvatarData* _myAvatar { nullptr };
|
||||
QScriptEngine _scriptEngine;
|
||||
};
|
||||
|
|
|
@ -182,7 +182,6 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
|
|||
|
||||
EntityPropertyFlags propertyFlags(PROP_LAST_ITEM);
|
||||
EntityPropertyFlags requestedProperties = getEntityProperties(params);
|
||||
EntityPropertyFlags propertiesDidntFit = requestedProperties;
|
||||
|
||||
// If we are being called for a subsequent pass at appendEntityData() that failed to completely encode this item,
|
||||
// then our entityTreeElementExtraEncodeData should include data about which properties we need to append.
|
||||
|
@ -190,6 +189,8 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
|
|||
requestedProperties = entityTreeElementExtraEncodeData->entities.value(getEntityItemID());
|
||||
}
|
||||
|
||||
EntityPropertyFlags propertiesDidntFit = requestedProperties;
|
||||
|
||||
LevelDetails entityLevel = packetData->startLevel();
|
||||
|
||||
quint64 lastEdited = getLastEdited();
|
||||
|
@ -1354,8 +1355,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lastEditedBy, setLastEditedBy);
|
||||
|
||||
AACube saveQueryAACube = _queryAACube;
|
||||
checkAndAdjustQueryAACube();
|
||||
if (saveQueryAACube != _queryAACube) {
|
||||
if (checkAndMaybeUpdateQueryAACube() && saveQueryAACube != _queryAACube) {
|
||||
somethingChanged = true;
|
||||
}
|
||||
|
||||
|
@ -1423,26 +1423,20 @@ void EntityItem::setDimensions(const glm::vec3& value) {
|
|||
///
|
||||
AACube EntityItem::getMaximumAACube(bool& success) const {
|
||||
if (_recalcMaxAACube) {
|
||||
// * we know that the position is the center of rotation
|
||||
glm::vec3 centerOfRotation = getPosition(success); // also where _registration point is
|
||||
if (success) {
|
||||
_recalcMaxAACube = false;
|
||||
// * we know that the registration point is the center of rotation
|
||||
// * we can calculate the length of the furthest extent from the registration point
|
||||
// as the dimensions * max (registrationPoint, (1.0,1.0,1.0) - registrationPoint)
|
||||
glm::vec3 dimensions = getDimensions();
|
||||
glm::vec3 registrationPoint = (dimensions * _registrationPoint);
|
||||
glm::vec3 registrationRemainder = (dimensions * (glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint));
|
||||
glm::vec3 furthestExtentFromRegistration = glm::max(registrationPoint, registrationRemainder);
|
||||
// we want to compute the furthestExtent that an entity can extend out from its "position"
|
||||
// to do this we compute the max of these two vec3s: registration and 1-registration
|
||||
// and then scale by dimensions
|
||||
glm::vec3 maxExtents = getDimensions() * glm::max(_registrationPoint, glm::vec3(1.0f) - _registrationPoint);
|
||||
|
||||
// * we know that if you rotate in any direction you would create a sphere
|
||||
// that has a radius of the length of furthest extent from registration point
|
||||
float radius = glm::length(furthestExtentFromRegistration);
|
||||
// there exists a sphere that contains maxExtents for all rotations
|
||||
float radius = glm::length(maxExtents);
|
||||
|
||||
// * we know that the minimum bounding cube of this maximum possible sphere is
|
||||
// (center - radius) to (center + radius)
|
||||
// put a cube around the sphere
|
||||
// TODO? replace _maxAACube with _boundingSphereRadius
|
||||
glm::vec3 minimumCorner = centerOfRotation - glm::vec3(radius, radius, radius);
|
||||
|
||||
_maxAACube = AACube(minimumCorner, radius * 2.0f);
|
||||
}
|
||||
} else {
|
||||
|
@ -1634,6 +1628,8 @@ void EntityItem::updateDimensions(const glm::vec3& value) {
|
|||
if (getDimensions() != value) {
|
||||
setDimensions(value);
|
||||
markDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);
|
||||
_queryAACubeSet = false;
|
||||
dimensionsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2157,12 +2157,17 @@ QList<QString> EntityItemProperties::listChangedProperties() {
|
|||
return out;
|
||||
}
|
||||
|
||||
bool EntityItemProperties::parentDependentPropertyChanged() const {
|
||||
return localPositionChanged() || positionChanged() ||
|
||||
localRotationChanged() || rotationChanged() ||
|
||||
localVelocityChanged() || localAngularVelocityChanged();
|
||||
bool EntityItemProperties::transformChanged() const {
|
||||
return positionChanged() || rotationChanged() ||
|
||||
localPositionChanged() || localRotationChanged();
|
||||
}
|
||||
|
||||
bool EntityItemProperties::parentRelatedPropertyChanged() const {
|
||||
return parentDependentPropertyChanged() || parentIDChanged() || parentJointIndexChanged();
|
||||
return positionChanged() || rotationChanged() ||
|
||||
localPositionChanged() || localRotationChanged() ||
|
||||
parentIDChanged() || parentJointIndexChanged();
|
||||
}
|
||||
|
||||
bool EntityItemProperties::queryAACubeRelatedPropertyChanged() const {
|
||||
return parentRelatedPropertyChanged() || dimensionsChanged();
|
||||
}
|
||||
|
|
|
@ -86,8 +86,9 @@ public:
|
|||
|
||||
EntityPropertyFlags getChangedProperties() const;
|
||||
|
||||
bool parentDependentPropertyChanged() const; // was there a changed in a property that requires parent info to interpret?
|
||||
bool parentRelatedPropertyChanged() const; // parentDependentPropertyChanged or parentID or parentJointIndex
|
||||
bool transformChanged() const;
|
||||
bool parentRelatedPropertyChanged() const;
|
||||
bool queryAACubeRelatedPropertyChanged() const;
|
||||
|
||||
AABox getAABox() const;
|
||||
|
||||
|
|
|
@ -221,7 +221,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
|
|||
_entityTree->withWriteLock([&] {
|
||||
EntityItemPointer entity = _entityTree->addEntity(id, propertiesWithSimID);
|
||||
if (entity) {
|
||||
if (propertiesWithSimID.parentRelatedPropertyChanged()) {
|
||||
if (propertiesWithSimID.queryAACubeRelatedPropertyChanged()) {
|
||||
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
|
||||
bool success;
|
||||
AACube queryAACube = entity->getQueryAACube(success);
|
||||
|
@ -435,7 +435,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
|
|||
entity->rememberHasSimulationOwnershipBid();
|
||||
}
|
||||
}
|
||||
if (properties.parentRelatedPropertyChanged() && entity->computePuffedQueryAACube()) {
|
||||
if (properties.queryAACubeRelatedPropertyChanged()) {
|
||||
properties.setQueryAACube(entity->getQueryAACube());
|
||||
}
|
||||
entity->setLastBroadcast(usecTimestampNow());
|
||||
|
@ -445,7 +445,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
|
|||
// if they've changed.
|
||||
entity->forEachDescendant([&](SpatiallyNestablePointer descendant) {
|
||||
if (descendant->getNestableType() == NestableType::Entity) {
|
||||
if (descendant->computePuffedQueryAACube()) {
|
||||
if (descendant->checkAndMaybeUpdateQueryAACube()) {
|
||||
EntityItemPointer entityDescendant = std::static_pointer_cast<EntityItem>(descendant);
|
||||
EntityItemProperties newQueryCubeProperties;
|
||||
newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube());
|
||||
|
|
|
@ -1675,6 +1675,7 @@ QVector<EntityItemID> EntityTree::sendEntities(EntityEditPacketSender* packetSen
|
|||
addToNeedsParentFixupList(entity);
|
||||
}
|
||||
entity->forceQueryAACubeUpdate();
|
||||
entity->checkAndMaybeUpdateQueryAACube();
|
||||
moveOperator.addEntityToMoveList(entity, entity->getQueryAACube());
|
||||
i++;
|
||||
} else {
|
||||
|
@ -1693,7 +1694,7 @@ QVector<EntityItemID> EntityTree::sendEntities(EntityEditPacketSender* packetSen
|
|||
EntityItemPointer entity = localTree->findEntityByEntityItemID(newID);
|
||||
if (entity) {
|
||||
// queue the packet to send to the server
|
||||
entity->computePuffedQueryAACube();
|
||||
entity->updateQueryAACube();
|
||||
EntityItemProperties properties = entity->getProperties();
|
||||
properties.markAllChanged(); // so the entire property set is considered new, since we're making a new entity
|
||||
packetSender->queueEditEntityMessage(PacketType::EntityAdd, localTree, newID, properties);
|
||||
|
|
|
@ -160,12 +160,6 @@ void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit
|
|||
APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha());
|
||||
}
|
||||
|
||||
// This value specifes how the shape should be treated by physics calculations.
|
||||
// For now, all polys will act as spheres
|
||||
ShapeType ShapeEntityItem::getShapeType() const {
|
||||
return (_shape == entity::Shape::Cube) ? SHAPE_TYPE_BOX : SHAPE_TYPE_ELLIPSOID;
|
||||
}
|
||||
|
||||
void ShapeEntityItem::setColor(const rgbColor& value) {
|
||||
memcpy(_color, value, sizeof(rgbColor));
|
||||
}
|
||||
|
@ -223,10 +217,12 @@ bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const
|
|||
void ShapeEntityItem::debugDump() const {
|
||||
quint64 now = usecTimestampNow();
|
||||
qCDebug(entities) << "SHAPE EntityItem id:" << getEntityItemID() << "---------------------------------------------";
|
||||
qCDebug(entities) << " shape:" << stringFromShape(_shape);
|
||||
qCDebug(entities) << " name:" << _name;
|
||||
qCDebug(entities) << " shape:" << stringFromShape(_shape) << " (EnumId: " << _shape << " )";
|
||||
qCDebug(entities) << " color:" << _color[0] << "," << _color[1] << "," << _color[2];
|
||||
qCDebug(entities) << " position:" << debugTreeVector(getPosition());
|
||||
qCDebug(entities) << " dimensions:" << debugTreeVector(getDimensions());
|
||||
qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now);
|
||||
qCDebug(entities) << "SHAPE EntityItem Ptr:" << this;
|
||||
}
|
||||
|
||||
|
|
|
@ -84,7 +84,6 @@ public:
|
|||
QColor getQColor() const;
|
||||
void setColor(const QColor& value);
|
||||
|
||||
ShapeType getShapeType() const override;
|
||||
bool shouldBePhysical() const override { return !isDead(); }
|
||||
|
||||
bool supportsDetailedRayIntersection() const override;
|
||||
|
|
|
@ -136,7 +136,7 @@ void SimpleEntitySimulation::sortEntitiesThatMoved() {
|
|||
SetOfEntities::iterator itemItr = _entitiesToSort.begin();
|
||||
while (itemItr != _entitiesToSort.end()) {
|
||||
EntityItemPointer entity = *itemItr;
|
||||
entity->computePuffedQueryAACube();
|
||||
entity->checkAndMaybeUpdateQueryAACube();
|
||||
++itemItr;
|
||||
}
|
||||
EntitySimulation::sortEntitiesThatMoved();
|
||||
|
|
3
libraries/midi/CMakeLists.txt
Normal file
3
libraries/midi/CMakeLists.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
set(TARGET_NAME midi)
|
||||
setup_hifi_library(Network)
|
||||
link_hifi_libraries(shared networking)
|
275
libraries/midi/src/Midi.cpp
Normal file
275
libraries/midi/src/Midi.cpp
Normal file
|
@ -0,0 +1,275 @@
|
|||
//
|
||||
// Midi.cpp
|
||||
// libraries/midi/src
|
||||
//
|
||||
// Created by Burt Sloane
|
||||
// Copyright 2015 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 "Midi.h"
|
||||
|
||||
|
||||
#include <QtCore/QLoggingCategory>
|
||||
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
#include "Windows.h"
|
||||
#endif
|
||||
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
const int MIDI_BYTE_MASK = 0x0FF;
|
||||
const int MIDI_SHIFT_NOTE = 8;
|
||||
const int MIDI_SHIFT_VELOCITY = 16;
|
||||
#endif
|
||||
const int MIDI_STATUS_MASK = 0x0F0;
|
||||
const int MIDI_NOTE_OFF = 0x080;
|
||||
const int MIDI_NOTE_ON = 0x090;
|
||||
const int MIDI_CONTROL_CHANGE = 0x0b0;
|
||||
const int MIDI_CHANNEL_MODE_ALL_NOTES_OFF = 0x07b;
|
||||
|
||||
|
||||
static Midi* instance = NULL; // communicate this to non-class callbacks
|
||||
static bool thruModeEnabled = false;
|
||||
|
||||
std::vector<QString> Midi::midiinexclude;
|
||||
std::vector<QString> Midi::midioutexclude;
|
||||
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
|
||||
#pragma comment(lib, "Winmm.lib")
|
||||
|
||||
//
|
||||
std::vector<HMIDIIN> midihin;
|
||||
std::vector<HMIDIOUT> midihout;
|
||||
|
||||
|
||||
void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
||||
switch (wMsg) {
|
||||
case MIM_OPEN:
|
||||
// message not used
|
||||
break;
|
||||
case MIM_CLOSE:
|
||||
for (int i = 0; i < midihin.size(); i++) {
|
||||
if (midihin[i] == hMidiIn) {
|
||||
midihin[i] = NULL;
|
||||
instance->allNotesOff();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MIM_DATA: {
|
||||
int status = MIDI_BYTE_MASK & dwParam1;
|
||||
int note = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE);
|
||||
int vel = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_VELOCITY);
|
||||
if (thruModeEnabled) {
|
||||
instance->sendNote(status, note, vel); // relay the note on to all other midi devices
|
||||
}
|
||||
instance->noteReceived(status, note, vel); // notify the javascript
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
||||
switch (wMsg) {
|
||||
case MOM_OPEN:
|
||||
// message not used
|
||||
break;
|
||||
case MOM_CLOSE:
|
||||
for (int i = 0; i < midihout.size(); i++) {
|
||||
if (midihout[i] == hmo) {
|
||||
midihout[i] = NULL;
|
||||
instance->allNotesOff();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Midi::sendNote(int status, int note, int vel) {
|
||||
for (int i = 0; i < midihout.size(); i++) {
|
||||
if (midihout[i] != NULL) {
|
||||
midiOutShortMsg(midihout[i], status + (note << MIDI_SHIFT_NOTE) + (vel << MIDI_SHIFT_VELOCITY));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Midi::MidiSetup() {
|
||||
midihin.clear();
|
||||
midihout.clear();
|
||||
|
||||
MIDIINCAPS incaps;
|
||||
for (unsigned int i = 0; i < midiInGetNumDevs(); i++) {
|
||||
midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS));
|
||||
|
||||
bool found = false;
|
||||
for (int j = 0; j < midiinexclude.size(); j++) {
|
||||
if (midiinexclude[j].toStdString().compare(incaps.szPname) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) { // EXCLUDE AN INPUT BY NAME
|
||||
HMIDIIN tmphin;
|
||||
midiInOpen(&tmphin, i, (DWORD_PTR)MidiInProc, NULL, CALLBACK_FUNCTION);
|
||||
midiInStart(tmphin);
|
||||
midihin.push_back(tmphin);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MIDIOUTCAPS outcaps;
|
||||
for (unsigned int i = 0; i < midiOutGetNumDevs(); i++) {
|
||||
midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS));
|
||||
|
||||
bool found = false;
|
||||
for (int j = 0; j < midioutexclude.size(); j++) {
|
||||
if (midioutexclude[j].toStdString().compare(outcaps.szPname) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) { // EXCLUDE AN OUTPUT BY NAME
|
||||
HMIDIOUT tmphout;
|
||||
midiOutOpen(&tmphout, i, (DWORD_PTR)MidiOutProc, NULL, CALLBACK_FUNCTION);
|
||||
midihout.push_back(tmphout);
|
||||
}
|
||||
}
|
||||
|
||||
allNotesOff();
|
||||
}
|
||||
|
||||
void Midi::MidiCleanup() {
|
||||
allNotesOff();
|
||||
|
||||
for (int i = 0; i < midihin.size(); i++) {
|
||||
if (midihin[i] != NULL) {
|
||||
midiInStop(midihin[i]);
|
||||
midiInClose(midihin[i]);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < midihout.size(); i++) {
|
||||
if (midihout[i] != NULL) {
|
||||
midiOutClose(midihout[i]);
|
||||
}
|
||||
}
|
||||
midihin.clear();
|
||||
midihout.clear();
|
||||
}
|
||||
#else
|
||||
void Midi::sendNote(int status, int note, int vel) {
|
||||
}
|
||||
|
||||
void Midi::MidiSetup() {
|
||||
allNotesOff();
|
||||
}
|
||||
|
||||
void Midi::MidiCleanup() {
|
||||
allNotesOff();
|
||||
}
|
||||
#endif
|
||||
|
||||
void Midi::noteReceived(int status, int note, int velocity) {
|
||||
if (((status & MIDI_STATUS_MASK) != MIDI_NOTE_OFF) &&
|
||||
((status & MIDI_STATUS_MASK) != MIDI_NOTE_ON)) {
|
||||
return; // NOTE: only sending note-on and note-off to Javascript
|
||||
}
|
||||
|
||||
QVariantMap eventData;
|
||||
eventData["status"] = status;
|
||||
eventData["note"] = note;
|
||||
eventData["velocity"] = velocity;
|
||||
emit midiNote(eventData);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
Midi::Midi() {
|
||||
instance = this;
|
||||
#if defined Q_OS_WIN32
|
||||
midioutexclude.push_back("Microsoft GS Wavetable Synth"); // we don't want to hear this thing
|
||||
#endif
|
||||
MidiSetup();
|
||||
}
|
||||
|
||||
Midi::~Midi() {
|
||||
}
|
||||
|
||||
void Midi::playMidiNote(int status, int note, int velocity) {
|
||||
sendNote(status, note, velocity);
|
||||
}
|
||||
|
||||
void Midi::allNotesOff() {
|
||||
sendNote(MIDI_CONTROL_CHANGE, MIDI_CHANNEL_MODE_ALL_NOTES_OFF, 0); // all notes off
|
||||
}
|
||||
|
||||
void Midi::resetDevices() {
|
||||
MidiCleanup();
|
||||
MidiSetup();
|
||||
}
|
||||
|
||||
void Midi::USBchanged() {
|
||||
instance->MidiCleanup();
|
||||
instance->MidiSetup();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
QStringList Midi::listMidiDevices(bool output) {
|
||||
QStringList rv;
|
||||
#if defined Q_OS_WIN32
|
||||
if (output) {
|
||||
MIDIOUTCAPS outcaps;
|
||||
for (unsigned int i = 0; i < midiOutGetNumDevs(); i++) {
|
||||
midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS));
|
||||
rv.append(outcaps.szPname);
|
||||
}
|
||||
} else {
|
||||
MIDIINCAPS incaps;
|
||||
for (unsigned int i = 0; i < midiInGetNumDevs(); i++) {
|
||||
midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS));
|
||||
rv.append(incaps.szPname);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return rv;
|
||||
}
|
||||
|
||||
void Midi::unblockMidiDevice(QString name, bool output) {
|
||||
if (output) {
|
||||
for (unsigned long i = 0; i < midioutexclude.size(); i++) {
|
||||
if (midioutexclude[i].toStdString().compare(name.toStdString()) == 0) {
|
||||
midioutexclude.erase(midioutexclude.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (unsigned long i = 0; i < midiinexclude.size(); i++) {
|
||||
if (midiinexclude[i].toStdString().compare(name.toStdString()) == 0) {
|
||||
midiinexclude.erase(midiinexclude.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Midi::blockMidiDevice(QString name, bool output) {
|
||||
unblockMidiDevice(name, output); // make sure it's only in there once
|
||||
if (output) {
|
||||
midioutexclude.push_back(name);
|
||||
} else {
|
||||
midiinexclude.push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
void Midi::thruModeEnable(bool enable) {
|
||||
thruModeEnabled = enable;
|
||||
}
|
||||
|
72
libraries/midi/src/Midi.h
Normal file
72
libraries/midi/src/Midi.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// Midi.h
|
||||
// libraries/midi/src
|
||||
//
|
||||
// Created by Burt Sloane
|
||||
// Copyright 2015 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_Midi_h
|
||||
#define hifi_Midi_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QAbstractNativeEventFilter>
|
||||
#include <DependencyManager.h>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
class Midi : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
||||
public:
|
||||
void noteReceived(int status, int note, int velocity); // relay a note to Javascript
|
||||
void sendNote(int status, int note, int vel); // relay a note to MIDI outputs
|
||||
static void USBchanged();
|
||||
|
||||
private:
|
||||
static std::vector<QString> midiinexclude;
|
||||
static std::vector<QString> midioutexclude;
|
||||
|
||||
private:
|
||||
void MidiSetup();
|
||||
void MidiCleanup();
|
||||
|
||||
signals:
|
||||
void midiNote(QVariantMap eventData);
|
||||
|
||||
public slots:
|
||||
/// play a note on all connected devices
|
||||
/// @param {int} status: 0x80 is noteoff, 0x90 is noteon (if velocity=0, noteoff), etc
|
||||
/// @param {int} note: midi note number
|
||||
/// @param {int} velocity: note velocity (0 means noteoff)
|
||||
Q_INVOKABLE void playMidiNote(int status, int note, int velocity);
|
||||
|
||||
/// turn off all notes on all connected devices
|
||||
Q_INVOKABLE void allNotesOff();
|
||||
|
||||
/// clean up and re-discover attached devices
|
||||
Q_INVOKABLE void resetDevices();
|
||||
|
||||
/// ask for a list of inputs/outputs
|
||||
Q_INVOKABLE QStringList listMidiDevices(bool output);
|
||||
|
||||
/// block an input/output by name
|
||||
Q_INVOKABLE void blockMidiDevice(QString name, bool output);
|
||||
|
||||
/// unblock an input/output by name
|
||||
Q_INVOKABLE void unblockMidiDevice(QString name, bool output);
|
||||
|
||||
/// repeat all incoming notes to all outputs (default disabled)
|
||||
Q_INVOKABLE void thruModeEnable(bool enable);
|
||||
|
||||
public:
|
||||
Midi();
|
||||
virtual ~Midi();
|
||||
};
|
||||
|
||||
#endif // hifi_Midi_h
|
|
@ -327,7 +327,7 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
<< "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
|
||||
// don't send the check in packet - wait for the new public key to be available to the domain-server first
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -445,7 +445,7 @@ void CharacterController::handleChangedCollisionGroup() {
|
|||
|
||||
void CharacterController::updateUpAxis(const glm::quat& rotation) {
|
||||
_currentUp = quatRotate(glmToBullet(rotation), LOCAL_UP_AXIS);
|
||||
if (_state != State::Hover && _rigidBody) {
|
||||
if (_rigidBody) {
|
||||
_rigidBody->setGravity(_gravity * _currentUp);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -484,11 +484,7 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (_entity->dynamicDataNeedsTransmit()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_entity->queryAABoxNeedsUpdate()) {
|
||||
if (_entity->dynamicDataNeedsTransmit() || _entity->queryAACubeNeedsUpdate()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -577,9 +573,11 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
|
|||
properties.setActionData(_serverActionData);
|
||||
}
|
||||
|
||||
if (properties.parentRelatedPropertyChanged() && _entity->computePuffedQueryAACube()) {
|
||||
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
|
||||
properties.setQueryAACube(_entity->getQueryAACube());
|
||||
if (properties.transformChanged()) {
|
||||
if (_entity->checkAndMaybeUpdateQueryAACube()) {
|
||||
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
|
||||
properties.setQueryAACube(_entity->getQueryAACube());
|
||||
}
|
||||
}
|
||||
|
||||
// set the LastEdited of the properties but NOT the entity itself
|
||||
|
@ -643,7 +641,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
|
|||
_entity->forEachDescendant([&](SpatiallyNestablePointer descendant) {
|
||||
if (descendant->getNestableType() == NestableType::Entity) {
|
||||
EntityItemPointer entityDescendant = std::static_pointer_cast<EntityItem>(descendant);
|
||||
if (descendant->computePuffedQueryAACube()) {
|
||||
if (descendant->checkAndMaybeUpdateQueryAACube()) {
|
||||
EntityItemProperties newQueryCubeProperties;
|
||||
newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube());
|
||||
newQueryCubeProperties.setLastEdited(properties.getLastEdited());
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#include "BulletUtil.h"
|
||||
|
||||
// These are the same normalized directions used by the btShapeHull class.
|
||||
// 12 points for the face centers of a duodecohedron plus another 30 points
|
||||
// 12 points for the face centers of a dodecahedron plus another 30 points
|
||||
// for the midpoints the edges, for a total of 42.
|
||||
const uint32_t NUM_UNIT_SPHERE_DIRECTIONS = 42;
|
||||
static const btVector3 _unitSphereDirections[NUM_UNIT_SPHERE_DIRECTIONS] = {
|
||||
|
@ -288,6 +288,38 @@ const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info)
|
|||
shape = new btCapsuleShape(radius, height);
|
||||
}
|
||||
break;
|
||||
case SHAPE_TYPE_CAPSULE_X: {
|
||||
glm::vec3 halfExtents = info.getHalfExtents();
|
||||
float radius = halfExtents.y;
|
||||
float height = 2.0f * halfExtents.x;
|
||||
shape = new btCapsuleShapeX(radius, height);
|
||||
}
|
||||
break;
|
||||
case SHAPE_TYPE_CAPSULE_Z: {
|
||||
glm::vec3 halfExtents = info.getHalfExtents();
|
||||
float radius = halfExtents.x;
|
||||
float height = 2.0f * halfExtents.z;
|
||||
shape = new btCapsuleShapeZ(radius, height);
|
||||
}
|
||||
break;
|
||||
case SHAPE_TYPE_CYLINDER_X: {
|
||||
const glm::vec3 halfExtents = info.getHalfExtents();
|
||||
const btVector3 btHalfExtents(halfExtents.x, halfExtents.y, halfExtents.z);
|
||||
shape = new btCylinderShapeX(btHalfExtents);
|
||||
}
|
||||
break;
|
||||
case SHAPE_TYPE_CYLINDER_Z: {
|
||||
const glm::vec3 halfExtents = info.getHalfExtents();
|
||||
const btVector3 btHalfExtents(halfExtents.x, halfExtents.y, halfExtents.z);
|
||||
shape = new btCylinderShapeZ(btHalfExtents);
|
||||
}
|
||||
break;
|
||||
case SHAPE_TYPE_CYLINDER_Y: {
|
||||
const glm::vec3 halfExtents = info.getHalfExtents();
|
||||
const btVector3 btHalfExtents(halfExtents.x, halfExtents.y, halfExtents.z);
|
||||
shape = new btCylinderShape(btHalfExtents);
|
||||
}
|
||||
break;
|
||||
case SHAPE_TYPE_COMPOUND:
|
||||
case SHAPE_TYPE_SIMPLE_HULL: {
|
||||
const ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
|
||||
|
|
|
@ -16,6 +16,6 @@ if (NOT ANDROID)
|
|||
|
||||
endif ()
|
||||
|
||||
link_hifi_libraries(shared networking octree gpu procedural model model-networking ktx recording avatars fbx entities controllers animation audio physics image)
|
||||
link_hifi_libraries(shared networking octree gpu procedural model model-networking ktx recording avatars fbx entities controllers animation audio physics image midi)
|
||||
# ui includes gl, but link_hifi_libraries does not use transitive includes, so gl must be explicit
|
||||
include_hifi_library_headers(gl)
|
||||
|
|
|
@ -77,6 +77,7 @@
|
|||
|
||||
#include <Profile.h>
|
||||
|
||||
#include "../../midi/src/Midi.h" // FIXME why won't a simpler include work?
|
||||
#include "MIDIEvent.h"
|
||||
|
||||
const QString ScriptEngine::_SETTINGS_ENABLE_EXTENDED_EXCEPTIONS {
|
||||
|
@ -667,6 +668,8 @@ void ScriptEngine::init() {
|
|||
|
||||
registerGlobalObject("Audio", DependencyManager::get<AudioScriptingInterface>().data());
|
||||
|
||||
registerGlobalObject("Midi", DependencyManager::get<Midi>().data());
|
||||
|
||||
registerGlobalObject("Entities", entityScriptingInterface.data());
|
||||
registerGlobalObject("Quat", &_quatLibrary);
|
||||
registerGlobalObject("Vec3", &_vec3Library);
|
||||
|
|
|
@ -25,8 +25,8 @@
|
|||
class PathUtils : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
Q_PROPERTY(QString resources READ resourcesPath)
|
||||
Q_PROPERTY(QUrl defaultScripts READ defaultScriptsLocation)
|
||||
Q_PROPERTY(QString resources READ resourcesPath CONSTANT)
|
||||
Q_PROPERTY(QUrl defaultScripts READ defaultScriptsLocation CONSTANT)
|
||||
public:
|
||||
static const QString& resourcesPath();
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ void ShapeInfo::clear() {
|
|||
}
|
||||
|
||||
void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString url) {
|
||||
//TODO WL21389: Does this need additional cases and handling added?
|
||||
_url = "";
|
||||
_type = type;
|
||||
setHalfExtents(halfExtents);
|
||||
|
@ -55,6 +56,9 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString
|
|||
}
|
||||
|
||||
void ShapeInfo::setBox(const glm::vec3& halfExtents) {
|
||||
//TODO WL21389: Should this pointlist clearance added in case
|
||||
// this is a re-purposed instance?
|
||||
// See https://github.com/highfidelity/hifi/pull/11024#discussion_r128885491
|
||||
_url = "";
|
||||
_type = SHAPE_TYPE_BOX;
|
||||
setHalfExtents(halfExtents);
|
||||
|
@ -62,6 +66,7 @@ void ShapeInfo::setBox(const glm::vec3& halfExtents) {
|
|||
}
|
||||
|
||||
void ShapeInfo::setSphere(float radius) {
|
||||
//TODO WL21389: See comment in setBox regarding clearance
|
||||
_url = "";
|
||||
_type = SHAPE_TYPE_SPHERE;
|
||||
radius = glm::max(radius, MIN_HALF_EXTENT);
|
||||
|
@ -70,12 +75,14 @@ void ShapeInfo::setSphere(float radius) {
|
|||
}
|
||||
|
||||
void ShapeInfo::setPointCollection(const ShapeInfo::PointCollection& pointCollection) {
|
||||
//TODO WL21389: May need to skip resetting type here.
|
||||
_pointCollection = pointCollection;
|
||||
_type = (_pointCollection.size() > 0) ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_NONE;
|
||||
_doubleHashKey.clear();
|
||||
}
|
||||
|
||||
void ShapeInfo::setCapsuleY(float radius, float halfHeight) {
|
||||
//TODO WL21389: See comment in setBox regarding clearance
|
||||
_url = "";
|
||||
_type = SHAPE_TYPE_CAPSULE_Y;
|
||||
radius = glm::max(radius, MIN_HALF_EXTENT);
|
||||
|
@ -117,6 +124,7 @@ int ShapeInfo::getLargestSubshapePointCount() const {
|
|||
}
|
||||
|
||||
float ShapeInfo::computeVolume() const {
|
||||
//TODO WL21389: Add support for other ShapeTypes( CYLINDER_X, CYLINDER_Y, etc).
|
||||
const float DEFAULT_VOLUME = 1.0f;
|
||||
float volume = DEFAULT_VOLUME;
|
||||
switch(_type) {
|
||||
|
@ -136,7 +144,10 @@ float ShapeInfo::computeVolume() const {
|
|||
}
|
||||
case SHAPE_TYPE_CAPSULE_Y: {
|
||||
float radius = _halfExtents.x;
|
||||
volume = PI * radius * radius * (2.0f * (_halfExtents.y - _halfExtents.x) + 4.0f * radius / 3.0f);
|
||||
// Need to offset halfExtents.y by x to account for the system treating
|
||||
// the y extent of the capsule as the cylindrical height + spherical radius.
|
||||
float cylinderHeight = 2.0f * (_halfExtents.y - _halfExtents.x);
|
||||
volume = PI * radius * radius * (cylinderHeight + 4.0f * radius / 3.0f);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -147,6 +158,7 @@ float ShapeInfo::computeVolume() const {
|
|||
}
|
||||
|
||||
bool ShapeInfo::contains(const glm::vec3& point) const {
|
||||
//TODO WL21389: Add support for other ShapeTypes like Ellipsoid/Compound.
|
||||
switch(_type) {
|
||||
case SHAPE_TYPE_SPHERE:
|
||||
return glm::length(point) <= _halfExtents.x;
|
||||
|
@ -191,6 +203,7 @@ bool ShapeInfo::contains(const glm::vec3& point) const {
|
|||
}
|
||||
|
||||
const DoubleHashKey& ShapeInfo::getHash() const {
|
||||
//TODO WL21389: Need to include the pointlist for SIMPLE_HULL in hash
|
||||
// NOTE: we cache the key so we only ever need to compute it once for any valid ShapeInfo instance.
|
||||
if (_doubleHashKey.isNull() && _type != SHAPE_TYPE_NONE) {
|
||||
bool useOffset = glm::length2(_offset) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET;
|
||||
|
|
|
@ -946,11 +946,35 @@ AACube SpatiallyNestable::getMaximumAACube(bool& success) const {
|
|||
return AACube(getPosition(success) - glm::vec3(defaultAACubeSize / 2.0f), defaultAACubeSize);
|
||||
}
|
||||
|
||||
bool SpatiallyNestable::checkAndAdjustQueryAACube() {
|
||||
bool success;
|
||||
const float PARENTED_EXPANSION_FACTOR = 3.0f;
|
||||
|
||||
bool SpatiallyNestable::checkAndMaybeUpdateQueryAACube() {
|
||||
bool success = false;
|
||||
AACube maxAACube = getMaximumAACube(success);
|
||||
if (success && (!_queryAACubeSet || !_queryAACube.contains(maxAACube))) {
|
||||
setQueryAACube(maxAACube);
|
||||
if (success) {
|
||||
// maybe update _queryAACube
|
||||
if (!_queryAACubeSet || (_parentID.isNull() && _children.size() == 0) || !_queryAACube.contains(maxAACube)) {
|
||||
if (_parentJointIndex != INVALID_JOINT_INDEX || _children.size() > 0 ) {
|
||||
// make an expanded AACube centered on the object
|
||||
float scale = PARENTED_EXPANSION_FACTOR * maxAACube.getScale();
|
||||
_queryAACube = AACube(maxAACube.calcCenter() - glm::vec3(0.5f * scale), scale);
|
||||
} else {
|
||||
_queryAACube = maxAACube;
|
||||
}
|
||||
|
||||
getThisPointer()->forEachDescendant([&](SpatiallyNestablePointer descendant) {
|
||||
bool childSuccess;
|
||||
AACube descendantAACube = descendant->getQueryAACube(childSuccess);
|
||||
if (childSuccess) {
|
||||
if (_queryAACube.contains(descendantAACube)) {
|
||||
return;
|
||||
}
|
||||
_queryAACube += descendantAACube.getMinimumPoint();
|
||||
_queryAACube += descendantAACube.getMaximumPoint();
|
||||
}
|
||||
});
|
||||
_queryAACubeSet = true;
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
@ -961,46 +985,34 @@ void SpatiallyNestable::setQueryAACube(const AACube& queryAACube) {
|
|||
return;
|
||||
}
|
||||
_queryAACube = queryAACube;
|
||||
if (queryAACube.getScale() > 0.0f) {
|
||||
_queryAACubeSet = true;
|
||||
}
|
||||
_queryAACubeSet = true;
|
||||
}
|
||||
|
||||
bool SpatiallyNestable::queryAABoxNeedsUpdate() const {
|
||||
bool success;
|
||||
AACube currentAACube = getMaximumAACube(success);
|
||||
if (!success) {
|
||||
qCDebug(shared) << "can't getMaximumAACube for" << getID();
|
||||
return false;
|
||||
bool SpatiallyNestable::queryAACubeNeedsUpdate() const {
|
||||
if (!_queryAACubeSet) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// make sure children are still in their boxes, also.
|
||||
bool childNeedsUpdate = false;
|
||||
getThisPointer()->forEachDescendant([&](SpatiallyNestablePointer descendant) {
|
||||
if (!childNeedsUpdate && descendant->queryAABoxNeedsUpdate()) {
|
||||
if (!childNeedsUpdate && descendant->queryAACubeNeedsUpdate()) {
|
||||
childNeedsUpdate = true;
|
||||
}
|
||||
});
|
||||
if (childNeedsUpdate) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_queryAACubeSet && _queryAACube.contains(currentAACube)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return childNeedsUpdate;
|
||||
}
|
||||
|
||||
bool SpatiallyNestable::computePuffedQueryAACube() {
|
||||
if (!queryAABoxNeedsUpdate()) {
|
||||
return false;
|
||||
}
|
||||
void SpatiallyNestable::updateQueryAACube() {
|
||||
bool success;
|
||||
AACube currentAACube = getMaximumAACube(success);
|
||||
// make an AACube with edges thrice as long and centered on the object
|
||||
_queryAACube = AACube(currentAACube.getCorner() - glm::vec3(currentAACube.getScale()), currentAACube.getScale() * 3.0f);
|
||||
_queryAACubeSet = true;
|
||||
AACube maxAACube = getMaximumAACube(success);
|
||||
if (_parentJointIndex != INVALID_JOINT_INDEX || _children.size() > 0 ) {
|
||||
// make an expanded AACube centered on the object
|
||||
float scale = PARENTED_EXPANSION_FACTOR * maxAACube.getScale();
|
||||
_queryAACube = AACube(maxAACube.calcCenter() - glm::vec3(0.5f * scale), scale);
|
||||
} else {
|
||||
_queryAACube = maxAACube;
|
||||
}
|
||||
|
||||
getThisPointer()->forEachDescendant([&](SpatiallyNestablePointer descendant) {
|
||||
bool success;
|
||||
|
@ -1013,8 +1025,7 @@ bool SpatiallyNestable::computePuffedQueryAACube() {
|
|||
_queryAACube += descendantAACube.getMaximumPoint();
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
_queryAACubeSet = true;
|
||||
}
|
||||
|
||||
AACube SpatiallyNestable::getQueryAACube(bool& success) const {
|
||||
|
|
|
@ -102,11 +102,11 @@ public:
|
|||
virtual glm::vec3 getParentAngularVelocity(bool& success) const;
|
||||
|
||||
virtual AACube getMaximumAACube(bool& success) const;
|
||||
virtual bool checkAndAdjustQueryAACube();
|
||||
virtual bool computePuffedQueryAACube();
|
||||
bool checkAndMaybeUpdateQueryAACube();
|
||||
void updateQueryAACube();
|
||||
|
||||
virtual void setQueryAACube(const AACube& queryAACube);
|
||||
virtual bool queryAABoxNeedsUpdate() const;
|
||||
virtual bool queryAACubeNeedsUpdate() const;
|
||||
void forceQueryAACubeUpdate() { _queryAACubeSet = false; }
|
||||
virtual AACube getQueryAACube(bool& success) const;
|
||||
virtual AACube getQueryAACube() const;
|
||||
|
@ -197,7 +197,7 @@ protected:
|
|||
mutable QHash<QUuid, SpatiallyNestableWeakPointer> _children;
|
||||
|
||||
virtual void locationChanged(bool tellPhysics = true); // called when a this object's location has changed
|
||||
virtual void dimensionsChanged() { } // called when a this object's dimensions have changed
|
||||
virtual void dimensionsChanged() { _queryAACubeSet = false; } // called when a this object's dimensions have changed
|
||||
virtual void parentDeleted() { } // called on children of a deleted parent
|
||||
|
||||
// _queryAACube is used to decide where something lives in the octree
|
||||
|
|
|
@ -127,7 +127,7 @@ private:
|
|||
glm::mat4 _projection;
|
||||
|
||||
// derived
|
||||
glm::vec3 _position;
|
||||
glm::vec3 _position { 0.0f, 0.0f, 0.0f };
|
||||
glm::quat _orientation;
|
||||
bool _isKeepLookingAt{ false };
|
||||
glm::vec3 _lookingAt;
|
||||
|
|
|
@ -31,12 +31,34 @@ namespace Cursor {
|
|||
}
|
||||
};
|
||||
|
||||
static QMap<uint16_t, QString> ICONS;
|
||||
QMap<uint16_t, QString> Manager::ICON_NAMES {
|
||||
{ Icon::SYSTEM, "SYSTEM", },
|
||||
{ Icon::DEFAULT, "DEFAULT", },
|
||||
{ Icon::LINK, "LINK", },
|
||||
{ Icon::ARROW, "ARROW", },
|
||||
{ Icon::RETICLE, "RETICLE", },
|
||||
};
|
||||
QMap<uint16_t, QString> Manager::ICONS;
|
||||
static uint16_t _customIconId = Icon::USER_BASE;
|
||||
|
||||
Manager::Manager() {
|
||||
ICONS[Icon::DEFAULT] = PathUtils::resourcesPath() + "images/arrow.png";
|
||||
ICONS[Icon::LINK] = PathUtils::resourcesPath() + "images/link.png";
|
||||
ICONS[Icon::SYSTEM] = PathUtils::resourcesPath() + "images/cursor-none.png";
|
||||
ICONS[Icon::DEFAULT] = PathUtils::resourcesPath() + "images/cursor-arrow.png";
|
||||
ICONS[Icon::LINK] = PathUtils::resourcesPath() + "images/cursor-link.png";
|
||||
ICONS[Icon::ARROW] = PathUtils::resourcesPath() + "images/cursor-arrow.png";
|
||||
ICONS[Icon::RETICLE] = PathUtils::resourcesPath() + "images/cursor-reticle.png";
|
||||
}
|
||||
|
||||
Icon Manager::lookupIcon(const QString& name) {
|
||||
for (const auto& kv : ICON_NAMES.toStdMap()) {
|
||||
if (kv.second == name) {
|
||||
return static_cast<Icon>(kv.first);
|
||||
}
|
||||
}
|
||||
return Icon::DEFAULT;
|
||||
}
|
||||
const QString& Manager::getIconName(const Icon& icon) {
|
||||
return ICON_NAMES.count(icon) ? ICON_NAMES[icon] : ICON_NAMES[Icon::DEFAULT];
|
||||
}
|
||||
|
||||
Manager& Manager::instance() {
|
||||
|
|
|
@ -18,16 +18,18 @@ namespace Cursor {
|
|||
};
|
||||
|
||||
enum Icon {
|
||||
SYSTEM,
|
||||
DEFAULT,
|
||||
LINK,
|
||||
GRAB,
|
||||
ARROW,
|
||||
RETICLE,
|
||||
|
||||
// Add new system cursors here
|
||||
|
||||
// User cursors will have ids over this value
|
||||
USER_BASE = 0xFF,
|
||||
};
|
||||
|
||||
class Instance {
|
||||
public:
|
||||
virtual Source getType() const = 0;
|
||||
|
@ -49,6 +51,11 @@ namespace Cursor {
|
|||
uint16_t registerIcon(const QString& path);
|
||||
QList<uint16_t> registeredIcons() const;
|
||||
const QString& getIconImage(uint16_t icon);
|
||||
|
||||
static QMap<uint16_t, QString> ICONS;
|
||||
static QMap<uint16_t, QString> ICON_NAMES;
|
||||
static Icon lookupIcon(const QString& name);
|
||||
static const QString& getIconName(const Icon& icon);
|
||||
private:
|
||||
float _scale{ 1.0f };
|
||||
};
|
||||
|
|
|
@ -228,7 +228,12 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
|
|||
connect(tabletRootWindow, &QmlWindowClass::fromQml, this, &TabletProxy::fromQml);
|
||||
} else {
|
||||
removeButtonsFromToolbar();
|
||||
addButtonsToHomeScreen();
|
||||
|
||||
if (_currentPathLoaded == TABLET_SOURCE_URL) {
|
||||
addButtonsToHomeScreen();
|
||||
} else {
|
||||
loadHomeScreen(true);
|
||||
}
|
||||
|
||||
// destroy desktop window
|
||||
if (_desktopWindow) {
|
||||
|
@ -236,8 +241,6 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
|
|||
_desktopWindow = nullptr;
|
||||
}
|
||||
}
|
||||
loadHomeScreen(true);
|
||||
emit screenChanged(QVariant("Home"), QVariant(TABLET_SOURCE_URL));
|
||||
}
|
||||
|
||||
static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) {
|
||||
|
@ -463,14 +466,14 @@ void TabletProxy::loadQMLSource(const QVariant& path) {
|
|||
}
|
||||
|
||||
if (root) {
|
||||
if (_state != State::QML) {
|
||||
removeButtonsFromHomeScreen();
|
||||
QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, path));
|
||||
_state = State::QML;
|
||||
removeButtonsFromHomeScreen(); //works only in Tablet
|
||||
QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, path));
|
||||
_state = State::QML;
|
||||
if (path != _currentPathLoaded) {
|
||||
emit screenChanged(QVariant("QML"), path);
|
||||
_currentPathLoaded = path;
|
||||
QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true)));
|
||||
}
|
||||
_currentPathLoaded = path;
|
||||
QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true)));
|
||||
} else {
|
||||
qCDebug(uiLogging) << "tablet cannot load QML because _qmlTabletRoot is null";
|
||||
}
|
||||
|
@ -483,6 +486,11 @@ bool TabletProxy::pushOntoStack(const QVariant& path) {
|
|||
return result;
|
||||
}
|
||||
|
||||
//set landscape off when pushing menu items while in Create mode
|
||||
if (_landscape) {
|
||||
setLandscape(false);
|
||||
}
|
||||
|
||||
QObject* root = nullptr;
|
||||
if (!_toolbarMode && _qmlTabletRoot) {
|
||||
root = _qmlTabletRoot;
|
||||
|
|
|
@ -13,7 +13,7 @@ if (APPLE)
|
|||
|
||||
set(TARGET_NAME oculusLegacy)
|
||||
setup_hifi_plugin()
|
||||
link_hifi_libraries(shared gl gpu gpu-gl plugins ui ui-plugins display-plugins input-plugins)
|
||||
link_hifi_libraries(shared gl gpu gpu-gl plugins ui ui-plugins display-plugins input-plugins midi)
|
||||
|
||||
include_hifi_library_headers(octree)
|
||||
|
||||
|
|
30
scripts/developer/tests/basicEntityTest/shapeSpawner.js
Normal file
30
scripts/developer/tests/basicEntityTest/shapeSpawner.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
// compute a position to create the object relative to avatar
|
||||
var forwardOffset = Vec3.multiply(2.0, Quat.getFront(MyAvatar.orientation));
|
||||
var objectPosition = Vec3.sum(MyAvatar.position, forwardOffset);
|
||||
|
||||
var LIFETIME = 1800; //seconds
|
||||
var DIM_HEIGHT = 1, DIM_WIDTH = 1, DIM_DEPTH = 1;
|
||||
var COLOR_R = 100, COLOR_G = 10, COLOR_B = 200;
|
||||
|
||||
var properties = {
|
||||
name: "ShapeSpawnTest",
|
||||
type: "Shape",
|
||||
shape: "Cylinder",
|
||||
dimensions: {x: DIM_WIDTH, y: DIM_HEIGHT, z: DIM_DEPTH},
|
||||
color: {red: COLOR_R, green: COLOR_G, blue: COLOR_B},
|
||||
position: objectPosition,
|
||||
lifetime: LIFETIME,
|
||||
};
|
||||
|
||||
// create the object
|
||||
var entityId = Entities.addEntity(properties);
|
||||
|
||||
function cleanup() {
|
||||
Entities.deleteEntity(entityId);
|
||||
}
|
||||
|
||||
// delete the object when this script is stopped
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
|
||||
|
||||
|
|
@ -43,6 +43,7 @@
|
|||
var speechBubbleOffset = {x: 0, y: 0.3, z: 0.0}; // The offset from the joint to whic the speech bubble is attached.
|
||||
var speechBubbleJointName = 'Head'; // The name of the joint to which the speech bubble is attached.
|
||||
var speechBubbleLineHeight = 0.05; // The height of a line of text in the speech bubble.
|
||||
var SPEECH_BUBBLE_MAX_WIDTH = 1; // meters
|
||||
|
||||
// Load the persistent variables from the Settings, with defaults.
|
||||
function loadSettings() {
|
||||
|
@ -645,8 +646,16 @@
|
|||
//print("updateSpeechBubble:", "speechBubbleMessage", speechBubbleMessage, "textSize", textSize.width, textSize.height);
|
||||
|
||||
var fudge = 0.02;
|
||||
|
||||
var width = textSize.width + fudge;
|
||||
var height = textSize.height + fudge;
|
||||
var height = speechBubbleLineHeight + fudge;
|
||||
|
||||
if (textSize.width >= SPEECH_BUBBLE_MAX_WIDTH) {
|
||||
var numLines = Math.ceil(width);
|
||||
height = speechBubbleLineHeight * numLines + fudge;
|
||||
width = SPEECH_BUBBLE_MAX_WIDTH;
|
||||
}
|
||||
|
||||
dimensions = {
|
||||
x: width,
|
||||
y: height,
|
||||
|
@ -672,6 +681,7 @@
|
|||
Vec3.sum(
|
||||
headPosition,
|
||||
rotatedOffset);
|
||||
position.y += height / 2; // offset based on half of bubble height
|
||||
speechBubbleParams.position = position;
|
||||
|
||||
if (!speechBubbleTextID) {
|
||||
|
|
|
@ -381,7 +381,13 @@ function getAvatarFootOffset() {
|
|||
}
|
||||
if (footJointIndex != -1) {
|
||||
// default vertical offset from foot to avatar root.
|
||||
return -MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(footJointIndex).y;
|
||||
var footPos = MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(footJointIndex);
|
||||
if (footPos.x === 0 && footPos.y === 0 && footPos.z === 0.0) {
|
||||
// if footPos is exactly zero, it's probably wrong because avatar is currently loading, fall back to default.
|
||||
return DEFAULT_ROOT_TO_FOOT_OFFSET * MyAvatar.scale;
|
||||
} else {
|
||||
return -footPos.y;
|
||||
}
|
||||
} else {
|
||||
return DEFAULT_ROOT_TO_FOOT_OFFSET * MyAvatar.scale;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, ParticleExplorerTool */
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
|
||||
"use strict";
|
||||
|
||||
var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
|
@ -26,11 +26,8 @@ Script.include([
|
|||
"libraries/stringHelpers.js",
|
||||
"libraries/dataViewHelpers.js",
|
||||
"libraries/progressDialog.js",
|
||||
|
||||
"libraries/entitySelectionTool.js",
|
||||
|
||||
"libraries/ToolTip.js",
|
||||
|
||||
"libraries/entityCameraTool.js",
|
||||
"libraries/gridTool.js",
|
||||
"libraries/entityList.js",
|
||||
|
@ -275,7 +272,8 @@ var toolBar = (function () {
|
|||
properties.userData = JSON.stringify({ grabbableKey: { grabbable: true } });
|
||||
}
|
||||
entityID = Entities.addEntity(properties);
|
||||
if (properties.type == "ParticleEffect") {
|
||||
|
||||
if (properties.type === "ParticleEffect") {
|
||||
selectParticleEntity(entityID);
|
||||
}
|
||||
|
||||
|
@ -337,6 +335,8 @@ var toolBar = (function () {
|
|||
var SHAPE_TYPE_SIMPLE_HULL = 1;
|
||||
var SHAPE_TYPE_SIMPLE_COMPOUND = 2;
|
||||
var SHAPE_TYPE_STATIC_MESH = 3;
|
||||
var SHAPE_TYPE_BOX = 4;
|
||||
var SHAPE_TYPE_SPHERE = 5;
|
||||
var DYNAMIC_DEFAULT = false;
|
||||
|
||||
function handleNewModelDialogResult(result) {
|
||||
|
@ -353,6 +353,12 @@ var toolBar = (function () {
|
|||
case SHAPE_TYPE_STATIC_MESH:
|
||||
shapeType = "static-mesh";
|
||||
break;
|
||||
case SHAPE_TYPE_BOX:
|
||||
shapeType = "box";
|
||||
break;
|
||||
case SHAPE_TYPE_SPHERE:
|
||||
shapeType = "sphere";
|
||||
break;
|
||||
default:
|
||||
shapeType = "none";
|
||||
}
|
||||
|
@ -411,7 +417,7 @@ var toolBar = (function () {
|
|||
createButton = activeButton;
|
||||
tablet.screenChanged.connect(function (type, url) {
|
||||
if (isActive && (type !== "QML" || url !== "Edit.qml")) {
|
||||
that.toggle();
|
||||
that.setActive(false)
|
||||
}
|
||||
});
|
||||
tablet.fromQml.connect(fromQml);
|
||||
|
@ -450,6 +456,8 @@ var toolBar = (function () {
|
|||
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model";
|
||||
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes";
|
||||
SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons";
|
||||
SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box";
|
||||
SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere";
|
||||
var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH;
|
||||
|
||||
// tablet version of new-model dialog
|
||||
|
@ -654,6 +662,7 @@ var toolBar = (function () {
|
|||
selectionDisplay.triggerMapping.enable();
|
||||
print("starting tablet in landscape mode");
|
||||
tablet.landscape = true;
|
||||
entityIconOverlayManager.setIconsSelectable(null,false);
|
||||
// Not sure what the following was meant to accomplish, but it currently causes
|
||||
// everybody else to think that Interface has lost focus overall. fogbugzid:558
|
||||
// Window.setFocus();
|
||||
|
@ -981,6 +990,7 @@ function mouseClickEvent(event) {
|
|||
} else {
|
||||
selectionManager.addEntity(foundEntity, true);
|
||||
}
|
||||
|
||||
if (wantDebug) {
|
||||
print("Model selected: " + foundEntity);
|
||||
}
|
||||
|
@ -2217,10 +2227,9 @@ var particleExplorerTool = new ParticleExplorerTool();
|
|||
var selectedParticleEntity = 0;
|
||||
var selectedParticleEntityID = null;
|
||||
|
||||
|
||||
function selectParticleEntity(entityID) {
|
||||
var properties = Entities.getEntityProperties(entityID);
|
||||
|
||||
selectedParticleEntityID = entityID;
|
||||
if (properties.emitOrientation) {
|
||||
properties.emitOrientation = Quat.safeEulerAngles(properties.emitOrientation);
|
||||
}
|
||||
|
@ -2262,7 +2271,6 @@ entityListTool.webView.webEventReceived.connect(function (data) {
|
|||
return;
|
||||
}
|
||||
// Destroy the old particles web view first
|
||||
selectParticleEntity(ids[0]);
|
||||
} else {
|
||||
selectedParticleEntity = 0;
|
||||
particleExplorerTool.destroyWebView();
|
||||
|
|
|
@ -40,9 +40,8 @@ function updateControllerDisplay() {
|
|||
var button;
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
|
||||
// Independent and Entity mode make people sick. Third Person and Mirror have traps that we need to work through.
|
||||
// Disable them in hmd.
|
||||
var desktopOnlyViews = ['Mirror', 'Independent Mode', 'Entity Mode'];
|
||||
// Independent and Entity mode make people sick; disable them in hmd.
|
||||
var desktopOnlyViews = ['Independent Mode', 'Entity Mode'];
|
||||
|
||||
function onHmdChanged(isHmd) {
|
||||
HMD.closeTablet();
|
||||
|
|
|
@ -475,6 +475,15 @@ function unbindAllInputs() {
|
|||
}
|
||||
}
|
||||
|
||||
function clearSelection() {
|
||||
if(document.selection && document.selection.empty) {
|
||||
document.selection.empty();
|
||||
} else if(window.getSelection) {
|
||||
var sel = window.getSelection();
|
||||
sel.removeAllRanges();
|
||||
}
|
||||
}
|
||||
|
||||
function loaded() {
|
||||
openEventBridge(function() {
|
||||
|
||||
|
@ -1051,6 +1060,7 @@ function loaded() {
|
|||
activeElement.select();
|
||||
}
|
||||
}
|
||||
clearSelection();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/* globals EntityIconOverlayManager:true */
|
||||
|
||||
EntityIconOverlayManager = function(entityTypes, getOverlayPropertiesFunc) {
|
||||
|
||||
var visible = false;
|
||||
|
||||
// List of all created overlays
|
||||
|
@ -69,6 +70,29 @@ EntityIconOverlayManager = function(entityTypes, getOverlayPropertiesFunc) {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
this.setIconsSelectable = function(arrayOfSelectedEntityIDs, isIconsSelectable) {
|
||||
if (arrayOfSelectedEntityIDs === null) {
|
||||
for (var id in entityOverlays) {
|
||||
Overlays.editOverlay(entityOverlays[id], {
|
||||
ignoreRayIntersection: isIconsSelectable
|
||||
});
|
||||
}
|
||||
} else {
|
||||
for (var id in entityOverlays) {
|
||||
if (arrayOfSelectedEntityIDs.indexOf(id) !== -1) { // in the entityOverlays array and selectable
|
||||
Overlays.editOverlay(entityOverlays[id], {
|
||||
ignoreRayIntersection: isIconsSelectable
|
||||
});
|
||||
} else {
|
||||
Overlays.editOverlay(entityOverlays[id], {
|
||||
ignoreRayIntersection: !isIconsSelectable
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Allocate or get an unused overlay
|
||||
function getOverlay() {
|
||||
var overlay;
|
||||
|
@ -114,6 +138,9 @@ EntityIconOverlayManager = function(entityTypes, getOverlayPropertiesFunc) {
|
|||
overlayProperties[key] = customProperties[key];
|
||||
}
|
||||
}
|
||||
if(properties.type === 'ParticleEffect' || properties.type === 'Light'){
|
||||
overlayProperties.ignoreRayIntersection = true;
|
||||
}
|
||||
Overlays.editOverlay(overlay, overlayProperties);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4060,6 +4060,8 @@ SelectionDisplay = (function() {
|
|||
return false;
|
||||
}
|
||||
|
||||
entityIconOverlayManager.setIconsSelectable(selectionManager.selections,true);
|
||||
|
||||
// ignore ray intersection for our selection box and yaw/pitch/roll
|
||||
result = Overlays.findRayIntersection(pickRay, true, null, [ yawHandle, pitchHandle, rollHandle, selectionBox ] );
|
||||
if (result.intersects) {
|
||||
|
|
50
scripts/tutorials/createMidiSphere.js
Normal file
50
scripts/tutorials/createMidiSphere.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// Created by James B. Pollack @imgntn on April 18, 2016.
|
||||
// Adapted by Burt
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
//var SCRIPT_URL = "file:///e:/hifi/scripts/tutorials/entity_scripts/midiSphere.js";
|
||||
var SCRIPT_URL = "http://hifi-files.s3-website-us-west-2.amazonaws.com/midiSphere.js";
|
||||
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(0.5, Quat.getForward(Camera.getOrientation())));
|
||||
|
||||
var BALL_GRAVITY = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
};
|
||||
|
||||
var BALL_DIMENSIONS = {
|
||||
x: 0.4,
|
||||
y: 0.4,
|
||||
z: 0.4
|
||||
};
|
||||
|
||||
|
||||
var BALL_COLOR = {
|
||||
red: 255,
|
||||
green: 0,
|
||||
blue: 0
|
||||
};
|
||||
|
||||
var midiSphereProperties = {
|
||||
name: 'MIDI Sphere',
|
||||
shapeType: 'sphere',
|
||||
type: 'Sphere',
|
||||
script: SCRIPT_URL,
|
||||
color: BALL_COLOR,
|
||||
dimensions: BALL_DIMENSIONS,
|
||||
gravity: BALL_GRAVITY,
|
||||
dynamic: false,
|
||||
position: center,
|
||||
collisionless: false,
|
||||
ignoreForCollisions: true
|
||||
};
|
||||
|
||||
var midiSphere = Entities.addEntity(midiSphereProperties);
|
||||
|
||||
Script.stop();
|
52
scripts/tutorials/entity_scripts/midiSphere.js
Normal file
52
scripts/tutorials/entity_scripts/midiSphere.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
// midiSphere.js
|
||||
//
|
||||
// Script Type: Entity
|
||||
// Created by James B. Pollack @imgntn on 9/21/2015
|
||||
// Adapted by Burt
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This script listens to MIDI and makes the ball change color.
|
||||
// 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 _this;
|
||||
|
||||
function MidiSphere() {
|
||||
_this = this;
|
||||
this.clicked = false;
|
||||
return;
|
||||
}
|
||||
|
||||
MidiSphere.prototype = {
|
||||
preload: function(entityID) {
|
||||
this.entityID = entityID;
|
||||
Midi.midiNote.connect(function(eventData) {
|
||||
print("MidiSphere.noteReceived: "+JSON.stringify(eventData));
|
||||
Entities.editEntity(entityID, { color: { red: 2*eventData.note, green: 2*eventData.note, blue: 2*eventData.note} });
|
||||
});
|
||||
print("MidiSphere.preload");
|
||||
},
|
||||
unload: function(entityID) {
|
||||
print("MidiSphere.unload");
|
||||
},
|
||||
|
||||
clickDownOnEntity: function(entityID, mouseEvent) {
|
||||
print("MidiSphere.clickDownOnEntity");
|
||||
if (this.clicked) {
|
||||
Entities.editEntity(entityID, { color: { red: 0, green: 255, blue: 255} });
|
||||
this.clicked = false;
|
||||
Midi.playMidiNote(144, 64, 0);
|
||||
} else {
|
||||
Entities.editEntity(entityID, { color: { red: 255, green: 255, blue: 0} });
|
||||
this.clicked = true;
|
||||
Midi.playMidiNote(144, 64, 100);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// entity scripts should return a newly constructed object of our type
|
||||
return new MidiSphere();
|
||||
});
|
|
@ -12,8 +12,7 @@ setup_hifi_project(Quick Gui OpenGL)
|
|||
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
|
||||
|
||||
# link in the shared libraries
|
||||
link_hifi_libraries(shared networking model fbx ktx image octree gl gpu gpu-gl render model-networking networking render-utils entities entities-renderer animation audio avatars script-engine physics procedural)
|
||||
|
||||
link_hifi_libraries(shared networking model fbx ktx image octree gl gpu gpu-gl render model-networking networking render-utils entities entities-renderer animation audio avatars script-engine physics procedural midi)
|
||||
|
||||
package_libraries_for_deployment()
|
||||
|
||||
|
|
36
unpublishedScripts/DomainContent/Cupcake/eatable.js
Normal file
36
unpublishedScripts/DomainContent/Cupcake/eatable.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// eatable.js
|
||||
//
|
||||
// Created by Alan-Michael Moody on 7/24/2017
|
||||
// Copyright 2017 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 NOMNOM_SOUND = SoundCache.getSound('http://hifi-content.s3.amazonaws.com/DomainContent/production/audio/vegcrunch.wav');
|
||||
|
||||
var _entityID;
|
||||
|
||||
this.preload = function (entityID) {
|
||||
_entityID = entityID;
|
||||
};
|
||||
|
||||
this.collisionWithEntity = function(entityUuid, collisionEntityID, collisionInfo) {
|
||||
var entity = Entities.getEntityProperties(collisionEntityID, ['userData', 'name']);
|
||||
var isClone = (entity.name.substring(1).split('-')[0] === 'clone');
|
||||
var isEatable = (JSON.parse(entity.userData).eatable);
|
||||
|
||||
if (isEatable && isClone) {
|
||||
Audio.playSound(NOMNOM_SOUND, {
|
||||
position: Entities.getEntityProperties(_entityID, ['position']).position,
|
||||
volume: 0.2,
|
||||
localOnly: false
|
||||
});
|
||||
|
||||
Entities.deleteEntity(collisionEntityID);
|
||||
}
|
||||
};
|
||||
});
|
91
unpublishedScripts/marketplace/dart/createDart.js
Normal file
91
unpublishedScripts/marketplace/dart/createDart.js
Normal file
|
@ -0,0 +1,91 @@
|
|||
"use strict";
|
||||
//
|
||||
// createDart.js
|
||||
//
|
||||
// Created by MrRoboman on 17/05/04
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Creates five throwing darts.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
var MODEL_URL = "https://hifi-content.s3.amazonaws.com/wadewatts/dart.fbx";
|
||||
var SCRIPT_URL = Script.resolvePath("./dart.js?v=" + Date.now());
|
||||
var START_POSITION = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), 0.75));
|
||||
var START_ROTATION = MyAvatar.orientation
|
||||
var SCALE_FACTOR = 1;
|
||||
|
||||
var dart = {
|
||||
type: "Model",
|
||||
shapeType: "box",
|
||||
name: "Dart",
|
||||
description: "Throw it at something!",
|
||||
script: SCRIPT_URL,
|
||||
modelURL: MODEL_URL,
|
||||
position: START_POSITION,
|
||||
rotation: START_ROTATION,
|
||||
lifetime: 300,
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: -9.8,
|
||||
z: 0
|
||||
},
|
||||
dimensions: {
|
||||
x: 0.1,
|
||||
y: 0.1,
|
||||
z: 0.3
|
||||
},
|
||||
dynamic: true,
|
||||
owningAvatarID: MyAvatar.sessionUUID,
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
grabbable: true,
|
||||
invertSolidWhileHeld: true,
|
||||
ignoreIK: false
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
var avatarUp = Quat.getUp(MyAvatar.orientation);
|
||||
var platformPosition = Vec3.sum(START_POSITION, Vec3.multiply(avatarUp, -0.05));
|
||||
var platform = {
|
||||
type: "Box",
|
||||
name: "Dart Platform",
|
||||
description: "Holds darts",
|
||||
position: platformPosition,
|
||||
rotation: START_ROTATION,
|
||||
lifetime: 60,
|
||||
dimensions: {
|
||||
x: 0.15 * 5,
|
||||
y: 0.01,
|
||||
z: 0.3
|
||||
},
|
||||
color: {
|
||||
red: 129,
|
||||
green: 92,
|
||||
blue: 11
|
||||
},
|
||||
owningAvatarID: MyAvatar.sessionUUID,
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
grabbable: true,
|
||||
invertSolidWhileHeld: true,
|
||||
ignoreIK: false
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
Entities.addEntity(platform);
|
||||
|
||||
var dartCount = 5;
|
||||
var avatarRight = Quat.getRight(MyAvatar.orientation);
|
||||
for (var i = 0; i < dartCount; i++) {
|
||||
var j = i - Math.floor(dartCount / 2);
|
||||
var position = Vec3.sum(START_POSITION, Vec3.multiply(avatarRight, 0.15 * j));
|
||||
dart.position = position;
|
||||
Entities.addEntity(dart);
|
||||
}
|
||||
|
||||
Script.stop();
|
81
unpublishedScripts/marketplace/dart/dart.js
Normal file
81
unpublishedScripts/marketplace/dart/dart.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
"use strict";
|
||||
//
|
||||
// dart.js
|
||||
//
|
||||
// Created by MrRoboman on 17/05/13
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Simple throwing dart. Sticks to static objects.
|
||||
//
|
||||
// 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 THROW_FACTOR = 3;
|
||||
var DART_SOUND_URL = Script.resolvePath('https://hifi-content.s3.amazonaws.com/wadewatts/dart.wav?v=' + Date.now());
|
||||
|
||||
var Dart = function() {};
|
||||
|
||||
Dart.prototype = {
|
||||
|
||||
preload: function(entityID) {
|
||||
this.entityID = entityID;
|
||||
this.actionID = null;
|
||||
this.soundHasPlayed = true;
|
||||
this.dartSound = SoundCache.getSound(DART_SOUND_URL);
|
||||
},
|
||||
|
||||
playDartSound: function(sound) {
|
||||
if (this.soundHasPlayed) {
|
||||
return;
|
||||
}
|
||||
this.soundHasPlayed = true;
|
||||
var position = Entities.getEntityProperties(this.entityID, 'position').position;
|
||||
var audioProperties = {
|
||||
volume: 0.15,
|
||||
position: position
|
||||
};
|
||||
Audio.playSound(this.dartSound, audioProperties);
|
||||
},
|
||||
|
||||
releaseGrab: function() {
|
||||
this.soundHasPlayed = false;
|
||||
var velocity = Entities.getEntityProperties(this.entityID, 'velocity').velocity;
|
||||
|
||||
var newVelocity = {};
|
||||
Object.keys(velocity).forEach(function(key) {
|
||||
newVelocity[key] = velocity[key] * THROW_FACTOR;
|
||||
});
|
||||
|
||||
Entities.editEntity(this.entityID, {
|
||||
velocity: newVelocity
|
||||
});
|
||||
|
||||
if (this.actionID) {
|
||||
Entities.deleteAction(this.entityID, this.actionID);
|
||||
}
|
||||
this.actionID = Entities.addAction("travel-oriented", this.entityID, {
|
||||
forward: { x: 0, y: 0, z: 1 },
|
||||
angularTimeScale: 0.1,
|
||||
tag: "throwing dart",
|
||||
ttl: 3600
|
||||
});
|
||||
},
|
||||
|
||||
collisionWithEntity: function(myID, otherID, collisionInfo) {
|
||||
this.playDartSound();
|
||||
|
||||
Entities.editEntity(myID, {
|
||||
velocity: {x: 0, y: 0, z: 0},
|
||||
angularVelocity: {x: 0, y: 0, z: 0}
|
||||
});
|
||||
|
||||
if (this.actionID) {
|
||||
Entities.deleteAction(myID, this.actionID);
|
||||
this.actionID = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return new Dart();
|
||||
});
|
|
@ -1,34 +0,0 @@
|
|||
//
|
||||
// pUtils.js
|
||||
//
|
||||
// Created by Patrick Gosch on 03/28/2017
|
||||
// Copyright 2017 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
|
||||
//
|
||||
|
||||
getEntityTextures = function(id) {
|
||||
var results = null;
|
||||
var properties = Entities.getEntityProperties(id, "textures");
|
||||
if (properties.textures) {
|
||||
try {
|
||||
results = JSON.parse(properties.textures);
|
||||
} catch (err) {
|
||||
logDebug(err);
|
||||
logDebug(properties.textures);
|
||||
}
|
||||
}
|
||||
return results ? results : {};
|
||||
};
|
||||
|
||||
setEntityTextures = function(id, textureList) {
|
||||
var json = JSON.stringify(textureList);
|
||||
Entities.editEntity(id, {textures: json});
|
||||
};
|
||||
|
||||
editEntityTextures = function(id, textureName, textureURL) {
|
||||
var textureList = getEntityTextures(id);
|
||||
textureList[textureName] = textureURL;
|
||||
setEntityTextures(id, textureList);
|
||||
};
|
|
@ -9,12 +9,12 @@
|
|||
//
|
||||
|
||||
(function() {
|
||||
Script.include(Script.resolvePath("pUtils.js"));
|
||||
var TIMEOUT = 150;
|
||||
var TEXGRAY = Script.resolvePath("xylotex_bar_gray.png");
|
||||
var TEXBLACK = Script.resolvePath("xylotex_bar_black.png");
|
||||
var TIMEOUT = 50; // at 30 ms, the key's color sometimes fails to switch when hit
|
||||
var TEXTURE_GRAY = Script.resolvePath("xylotex_bar_gray.png");
|
||||
var TEXTURE_BLACK = Script.resolvePath("xylotex_bar_black.png");
|
||||
var IS_DEBUG = false;
|
||||
var _this;
|
||||
|
||||
|
||||
function XylophoneKey() {
|
||||
_this = this;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
|||
XylophoneKey.prototype = {
|
||||
sound: null,
|
||||
isWaiting: false,
|
||||
homePos: null,
|
||||
homePosition: null,
|
||||
injector: null,
|
||||
|
||||
preload: function(entityID) {
|
||||
|
@ -34,31 +34,66 @@
|
|||
|
||||
collisionWithEntity: function(thisEntity, otherEntity, collision) {
|
||||
if (collision.type === 0) {
|
||||
_this.hit();
|
||||
_this.hit(otherEntity);
|
||||
}
|
||||
},
|
||||
|
||||
clickDownOnEntity: function() {
|
||||
_this.hit();
|
||||
clickDownOnEntity: function(otherEntity) {
|
||||
_this.hit(otherEntity);
|
||||
},
|
||||
|
||||
hit: function() {
|
||||
hit: function(otherEntity) {
|
||||
if (!_this.isWaiting) {
|
||||
_this.isWaiting = true;
|
||||
_this.homePos = Entities.getEntityProperties(_this.entityID, ["position"]).position;
|
||||
_this.injector = Audio.playSound(_this.sound, {position: _this.homePos, volume: 1});
|
||||
editEntityTextures(_this.entityID, "file5", TEXGRAY);
|
||||
_this.homePosition = Entities.getEntityProperties(_this.entityID, ["position"]).position;
|
||||
_this.injector = Audio.playSound(_this.sound, {position: _this.homePosition, volume: 1});
|
||||
_this.editEntityTextures(_this.entityID, "file5", TEXTURE_GRAY);
|
||||
|
||||
var HAPTIC_STRENGTH = 1;
|
||||
var HAPTIC_DURATION = 20;
|
||||
var userData = JSON.parse(Entities.getEntityProperties(otherEntity, 'userData').userData);
|
||||
if (userData.hasOwnProperty('hand')){
|
||||
Controller.triggerHapticPulse(HAPTIC_STRENGTH, HAPTIC_DURATION, userData.hand);
|
||||
}
|
||||
|
||||
_this.timeout();
|
||||
}
|
||||
},
|
||||
|
||||
timeout: function() {
|
||||
Script.setTimeout(function() {
|
||||
editEntityTextures(_this.entityID, "file5", TEXBLACK);
|
||||
_this.editEntityTextures(_this.entityID, "file5", TEXTURE_BLACK);
|
||||
_this.isWaiting = false;
|
||||
}, TIMEOUT);
|
||||
},
|
||||
|
||||
getEntityTextures: function(id) {
|
||||
var results = null;
|
||||
var properties = Entities.getEntityProperties(id, "textures");
|
||||
if (properties.textures) {
|
||||
try {
|
||||
results = JSON.parse(properties.textures);
|
||||
} catch (err) {
|
||||
if (IS_DEBUG) {
|
||||
print(err);
|
||||
print(properties.textures);
|
||||
}
|
||||
}
|
||||
}
|
||||
return results ? results : {};
|
||||
},
|
||||
|
||||
setEntityTextures: function(id, textureList) {
|
||||
var json = JSON.stringify(textureList);
|
||||
Entities.editEntity(id, {textures: json});
|
||||
},
|
||||
|
||||
editEntityTextures: function(id, textureName, textureURL) {
|
||||
var textureList = _this.getEntityTextures(id);
|
||||
textureList[textureName] = textureURL;
|
||||
_this.setEntityTextures(id, textureList);
|
||||
}
|
||||
};
|
||||
|
||||
return new XylophoneKey();
|
||||
|
||||
});
|
||||
|
|
25
unpublishedScripts/marketplace/xylophone/xylophoneMallet.js
Normal file
25
unpublishedScripts/marketplace/xylophone/xylophoneMallet.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// xylophoneMallet.js
|
||||
//
|
||||
// Created by Johnathan Franck on 07/30/2017
|
||||
// Copyright 2017 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() {
|
||||
function XylophoneMallet() {
|
||||
}
|
||||
|
||||
XylophoneMallet.prototype = {
|
||||
startEquip: function(entityID, args) {
|
||||
var LEFT_HAND = 0;
|
||||
var RIGHT_HAND = 1;
|
||||
var userData = JSON.parse(Entities.getEntityProperties(entityID, 'userData').userData);
|
||||
userData.hand = args[0] === "left" ? LEFT_HAND : RIGHT_HAND;
|
||||
Entities.editEntity(entityID, {userData: JSON.stringify(userData)});
|
||||
}
|
||||
};
|
||||
|
||||
return new XylophoneMallet();
|
||||
});
|
|
@ -8,65 +8,70 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var soundFiles = ["C4.wav", "D4.wav", "E4.wav", "F4.wav", "G4.wav", "A4.wav", "B4.wav", "C5.wav"];
|
||||
var keyModelURL = Script.resolvePath("xyloKey_2_a_e.fbx");
|
||||
var keyScriptURL = Script.resolvePath("xylophoneKey.js");
|
||||
var TEXBLACK = Script.resolvePath("xylotex_bar_black.png");
|
||||
var malletModelURL = Script.resolvePath("Mallet3-2pc.fbx");
|
||||
var malletModelColliderURL = Script.resolvePath("Mallet3-2bpc_phys.obj");
|
||||
var SOUND_FILES = ["C4.wav", "D4.wav", "E4.wav", "F4.wav", "G4.wav", "A4.wav", "B4.wav", "C5.wav"];
|
||||
var KEY_MODEL_URL = Script.resolvePath("xyloKey_2_a_e.fbx");
|
||||
var KEY_SCRIPT_URL = Script.resolvePath("xylophoneKey.js");
|
||||
var MALLET_SCRIPT_URL = Script.resolvePath("xylophoneMallet.js");
|
||||
var TEXTURE_BLACK = Script.resolvePath("xylotex_bar_black.png");
|
||||
var MALLET_MODEL_URL = Script.resolvePath("Mallet3-2pc.fbx");
|
||||
var MALLET_MODEL_COLLIDER_URL = Script.resolvePath("Mallet3-2bpc_phys.obj");
|
||||
var FORWARD = { x: 0, y: 0, z: -1 };
|
||||
var center = MyAvatar.position;
|
||||
var fwd = {x:0, y:0, z:-1};
|
||||
|
||||
var xyloFramePos = Vec3.sum(center, Vec3.multiply(fwd, 0.8));
|
||||
var xyloFrameID = Entities.addEntity( {
|
||||
var XYLOPHONE_FORWARD_OFFSET = 0.8;
|
||||
var xylophoneFramePosition = Vec3.sum(center, Vec3.multiply(FORWARD, XYLOPHONE_FORWARD_OFFSET));
|
||||
var xylophoneFrameID = Entities.addEntity({
|
||||
name: "Xylophone",
|
||||
type: "Model",
|
||||
modelURL: Script.resolvePath("xylophoneFrameWithWave.fbx"),
|
||||
position: xyloFramePos,
|
||||
rotation: Quat.fromVec3Radians({x:0, y:Math.PI, z:0}),
|
||||
position: xylophoneFramePosition,
|
||||
rotation: Quat.fromVec3Radians({ x: 0, y: Math.PI, z: 0 }),
|
||||
shapeType: "static-mesh"
|
||||
});
|
||||
|
||||
center.y += (0.45); // key Y offset from frame
|
||||
var keyPos, keyRot, ud, td, keyID;
|
||||
for (var i = 1; i <= soundFiles.length; i++) {
|
||||
var KEY_Y_OFFSET = 0.45;
|
||||
center.y += KEY_Y_OFFSET;
|
||||
var keyPosition, keyRotation, userData, textureData, keyID;
|
||||
var ROTATION_START = 0.9;
|
||||
var ROTATION_DELTA = 0.2;
|
||||
for (var i = 1; i <= SOUND_FILES.length; i++) {
|
||||
|
||||
keyRotation = Quat.fromVec3Radians({ x: 0, y: ROTATION_START - (i*ROTATION_DELTA), z: 0 });
|
||||
keyPosition = Vec3.sum(center, Vec3.multiplyQbyV(keyRotation, FORWARD));
|
||||
|
||||
keyRot = Quat.fromVec3Radians({x:0, y:(0.9 - (i*0.2)), z:0});
|
||||
keyPos = Vec3.sum(center, Vec3.multiplyQbyV(keyRot, fwd));
|
||||
|
||||
ud = {
|
||||
soundFile: soundFiles[i-1]
|
||||
userData = {
|
||||
soundFile: SOUND_FILES[i-1]
|
||||
};
|
||||
|
||||
td = {
|
||||
textureData = {
|
||||
"file4": Script.resolvePath("xylotex_bar" + i + ".png"),
|
||||
"file5": TEXBLACK
|
||||
"file5": TEXTURE_BLACK
|
||||
};
|
||||
|
||||
keyID = Entities.addEntity( {
|
||||
keyID = Entities.addEntity({
|
||||
name: ("XyloKey" + i),
|
||||
type: "Model",
|
||||
modelURL: keyModelURL,
|
||||
position: keyPos,
|
||||
rotation: keyRot,
|
||||
modelURL: KEY_MODEL_URL,
|
||||
position: keyPosition,
|
||||
rotation: keyRotation,
|
||||
shapeType: "static-mesh",
|
||||
script: keyScriptURL,
|
||||
textures: JSON.stringify(td),
|
||||
userData: JSON.stringify(ud),
|
||||
parentID: xyloFrameID
|
||||
} );
|
||||
script: KEY_SCRIPT_URL,
|
||||
textures: JSON.stringify(textureData),
|
||||
userData: JSON.stringify(userData),
|
||||
parentID: xylophoneFrameID
|
||||
});
|
||||
}
|
||||
|
||||
// if rezzed on/above something, wait until after model has loaded so you can read its dimensions then move object on to that surface.
|
||||
var pickRay = {origin: center, direction: {x:0, y:-1, z:0}};
|
||||
var pickRay = {origin: center, direction: {x: 0, y: -1, z: 0}};
|
||||
var intersection = Entities.findRayIntersection(pickRay, true);
|
||||
if (intersection.intersects && (intersection.distance < 10)) {
|
||||
var surfaceY = intersection.intersection.y;
|
||||
Script.setTimeout( function() {
|
||||
// should add loop to check for fbx loaded instead of delay
|
||||
var xyloDimensions = Entities.getEntityProperties(xyloFrameID, ["dimensions"]).dimensions;
|
||||
xyloFramePos.y = surfaceY + (xyloDimensions.y/2);
|
||||
Entities.editEntity(xyloFrameID, {position: xyloFramePos});
|
||||
var xylophoneDimensions = Entities.getEntityProperties(xylophoneFrameID, ["dimensions"]).dimensions;
|
||||
xylophoneFramePosition.y = surfaceY + (xylophoneDimensions.y/2);
|
||||
Entities.editEntity(xylophoneFrameID, {position: xylophoneFramePosition});
|
||||
rezMallets();
|
||||
}, 2000);
|
||||
} else {
|
||||
|
@ -75,28 +80,50 @@ if (intersection.intersects && (intersection.distance < 10)) {
|
|||
}
|
||||
|
||||
function rezMallets() {
|
||||
var malletProps = {
|
||||
var malletProperties = {
|
||||
name: "Xylophone Mallet",
|
||||
type: "Model",
|
||||
modelURL: malletModelURL,
|
||||
compoundShapeURL: malletModelColliderURL,
|
||||
collidesWith: "static,dynamic,kinematic,",
|
||||
modelURL: MALLET_MODEL_URL,
|
||||
compoundShapeURL: MALLET_MODEL_COLLIDER_URL,
|
||||
collidesWith: "static,dynamic,kinematic",
|
||||
collisionMask: 7,
|
||||
collisionsWillMove: 1,
|
||||
dynamic: 1,
|
||||
damping: 1,
|
||||
angularDamping: 1,
|
||||
shapeType: "compound",
|
||||
userData: "{\"grabbableKey\":{\"grabbable\":true}}",
|
||||
dimensions: {"x": 0.057845603674650192, "y": 0.057845607399940491, "z": 0.30429631471633911} // not being set from fbx for some reason.
|
||||
script: MALLET_SCRIPT_URL,
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
invertSolidWhileHeld: true
|
||||
},
|
||||
wearable: {
|
||||
joints: {
|
||||
LeftHand: [
|
||||
{ x: 0, y: 0.2, z: 0.04 },
|
||||
Quat.fromVec3Degrees({ x: 0, y: 90, z: 90 })
|
||||
],
|
||||
RightHand: [
|
||||
{ x: 0, y: 0.2, z: 0.04 },
|
||||
Quat.fromVec3Degrees({ x: 0, y: 90, z: 90 })
|
||||
]
|
||||
}
|
||||
}
|
||||
}),
|
||||
dimensions: { "x": 0.057845603674650192, "y": 0.057845607399940491, "z": 0.30429631471633911 } // not being set from fbx for some reason.
|
||||
};
|
||||
|
||||
malletProps.position = Vec3.sum(xyloFramePos, {x: 0.1, y: 0.55, z: 0});
|
||||
malletProps.rotation = Quat.fromVec3Radians({x:0, y:Math.PI - 0.1, z:0});
|
||||
Entities.addEntity(malletProps);
|
||||
var LEFT_MALLET_POSITION = { x: 0.1, y: 0.55, z: 0 };
|
||||
var LEFT_MALLET_ROTATION = { x: 0, y: Math.PI - 0.1, z: 0 };
|
||||
var RIGHT_MALLET_POSITION = { x: -0.1, y: 0.55, z: 0 };
|
||||
var RIGHT_MALLET_ROTATION = { x: 0, y: Math.PI + 0.1, z: 0 };
|
||||
|
||||
malletProps.position = Vec3.sum(xyloFramePos, {x: -0.1, y: 0.55, z: 0});
|
||||
malletProps.rotation = Quat.fromVec3Radians({x:0, y:Math.PI + 0.1, z:0});
|
||||
Entities.addEntity(malletProps);
|
||||
malletProperties.position = Vec3.sum(xylophoneFramePosition, LEFT_MALLET_POSITION);
|
||||
malletProperties.rotation = Quat.fromVec3Radians(LEFT_MALLET_ROTATION);
|
||||
Entities.addEntity(malletProperties);
|
||||
|
||||
malletProperties.position = Vec3.sum(xylophoneFramePosition, RIGHT_MALLET_POSITION);
|
||||
malletProperties.rotation = Quat.fromVec3Radians(RIGHT_MALLET_ROTATION);
|
||||
Entities.addEntity(malletProperties);
|
||||
Script.stop();
|
||||
}
|
Loading…
Reference in a new issue