Merge pull request #11129 from highfidelity/RC-50

Beta Release 50 - Includes up to Developer Release 6961
This commit is contained in:
Chris Collins 2017-08-08 14:39:17 -07:00 committed by GitHub
commit 995ee60aa5
87 changed files with 1702 additions and 444 deletions

View file

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

Binary file not shown.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

View file

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

View file

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

View file

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

View file

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

View file

@ -145,7 +145,9 @@ Rectangle {
model: ["No Collision",
"Basic - Whole model",
"Good - Sub-meshes",
"Exact - All polygons"]
"Exact - All polygons",
"Box",
"Sphere"]
}
Row {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

View file

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

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

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

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

View file

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

View file

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

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

View file

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