mirror of
https://github.com/overte-org/overte.git
synced 2025-08-08 19:16:56 +02:00
Merge branch 'master' into 21186
# Conflicts: # scripts/system/controllers/handControllerGrab.js
This commit is contained in:
commit
482c23ead5
56 changed files with 1794 additions and 930 deletions
13
BUILD.md
13
BUILD.md
|
@ -1,7 +1,7 @@
|
||||||
###Dependencies
|
###Dependencies
|
||||||
|
|
||||||
* [cmake](http://www.cmake.org/cmake/resources/software.html) ~> 3.3.2
|
* [cmake](https://cmake.org/download/) ~> 3.3.2
|
||||||
* [Qt](http://www.qt.io/download-open-source) ~> 5.6.1
|
* [Qt](https://www.qt.io/download-open-source) ~> 5.6.1
|
||||||
* [OpenSSL](https://www.openssl.org/community/binaries.html)
|
* [OpenSSL](https://www.openssl.org/community/binaries.html)
|
||||||
* IMPORTANT: Use the latest available version of OpenSSL to avoid security vulnerabilities.
|
* IMPORTANT: Use the latest available version of OpenSSL to avoid security vulnerabilities.
|
||||||
* [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional)
|
* [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional)
|
||||||
|
@ -9,18 +9,17 @@
|
||||||
####CMake External Project Dependencies
|
####CMake External Project Dependencies
|
||||||
|
|
||||||
* [boostconfig](https://github.com/boostorg/config) ~> 1.58
|
* [boostconfig](https://github.com/boostorg/config) ~> 1.58
|
||||||
* [Bullet Physics Engine](https://code.google.com/p/bullet/downloads/list) ~> 2.82
|
* [Bullet Physics Engine](https://github.com/bulletphysics/bullet3/releases) ~> 2.83
|
||||||
* [Faceshift](http://www.faceshift.com/) ~> 4.3
|
|
||||||
* [GLEW](http://glew.sourceforge.net/)
|
* [GLEW](http://glew.sourceforge.net/)
|
||||||
* [glm](http://glm.g-truc.net/0.9.5/index.html) ~> 0.9.5.4
|
* [glm](https://glm.g-truc.net/0.9.5/index.html) ~> 0.9.5.4
|
||||||
* [gverb](https://github.com/highfidelity/gverb)
|
* [gverb](https://github.com/highfidelity/gverb)
|
||||||
* [Oculus SDK](https://developer.oculus.com/downloads/) ~> 0.6 (Win32) / 0.5 (Mac / Linux)
|
* [Oculus SDK](https://developer.oculus.com/downloads/) ~> 0.6 (Win32) / 0.5 (Mac / Linux)
|
||||||
* [oglplus](http://oglplus.org/) ~> 0.63
|
* [oglplus](http://oglplus.org/) ~> 0.63
|
||||||
* [OpenVR](https://github.com/ValveSoftware/openvr) ~> 0.91 (Win32 only)
|
* [OpenVR](https://github.com/ValveSoftware/openvr) ~> 0.91 (Win32 only)
|
||||||
* [Polyvox](http://www.volumesoffun.com/) ~> 0.2.1
|
* [Polyvox](http://www.volumesoffun.com/) ~> 0.2.1
|
||||||
* [QuaZip](http://sourceforge.net/projects/quazip/files/quazip/) ~> 0.7.1
|
* [QuaZip](https://sourceforge.net/projects/quazip/files/quazip/) ~> 0.7.1
|
||||||
* [SDL2](https://www.libsdl.org/download-2.0.php) ~> 2.0.3
|
* [SDL2](https://www.libsdl.org/download-2.0.php) ~> 2.0.3
|
||||||
* [soxr](http://soxr.sourceforge.net) ~> 0.1.1
|
* [soxr](https://sourceforge.net/p/soxr/wiki/Home/) ~> 0.1.1
|
||||||
* [Intel Threading Building Blocks](https://www.threadingbuildingblocks.org/) ~> 4.3
|
* [Intel Threading Building Blocks](https://www.threadingbuildingblocks.org/) ~> 4.3
|
||||||
* [Sixense](http://sixense.com/) ~> 071615
|
* [Sixense](http://sixense.com/) ~> 071615
|
||||||
* [zlib](http://www.zlib.net/) ~> 1.28 (Win32 only)
|
* [zlib](http://www.zlib.net/) ~> 1.28 (Win32 only)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only OS X specific instructions are found in this file.
|
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only OS X specific instructions are found in this file.
|
||||||
|
|
||||||
###Homebrew
|
###Homebrew
|
||||||
[Homebrew](http://brew.sh/) is an excellent package manager for OS X. It makes install of some High Fidelity dependencies very simple.
|
[Homebrew](https://brew.sh/) is an excellent package manager for OS X. It makes install of some High Fidelity dependencies very simple.
|
||||||
|
|
||||||
brew tap homebrew/versions
|
brew tap homebrew/versions
|
||||||
brew install cmake openssl
|
brew install cmake openssl
|
||||||
|
@ -18,11 +18,11 @@ Note that this uses the version from the homebrew formula at the time of this wr
|
||||||
###Qt
|
###Qt
|
||||||
You can use the online installer or the offline installer.
|
You can use the online installer or the offline installer.
|
||||||
|
|
||||||
* [Download the online installer](http://www.qt.io/download-open-source/#section-2)
|
* [Download the online installer](https://www.qt.io/download-open-source/#section-2)
|
||||||
* When it asks you to select components, select the following:
|
* When it asks you to select components, select the following:
|
||||||
* Qt > Qt 5.6
|
* Qt > Qt 5.6
|
||||||
|
|
||||||
* [Download the offline installer](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-mac-x64-clang-5.6.1-1.dmg)
|
* [Download the offline installer](https://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-mac-x64-clang-5.6.1-1.dmg)
|
||||||
|
|
||||||
Once Qt is installed, you need to manually configure the following:
|
Once Qt is installed, you need to manually configure the following:
|
||||||
* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt5.6.1/5.6/clang_64/lib/cmake/` directory.
|
* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt5.6.1/5.6/clang_64/lib/cmake/` directory.
|
||||||
|
|
|
@ -33,8 +33,8 @@ You can use the online installer or the offline installer. If you use the offlin
|
||||||
* Qt > Qt 5.6.1 > **msvc2013 64-bit**
|
* Qt > Qt 5.6.1 > **msvc2013 64-bit**
|
||||||
|
|
||||||
* Download the offline installer, 32- or 64-bit to match your build preference:
|
* Download the offline installer, 32- or 64-bit to match your build preference:
|
||||||
* [32-bit](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-windows-x86-msvc2013-5.6.1-1.exe)
|
* [32-bit](https://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-windows-x86-msvc2013-5.6.1-1.exe)
|
||||||
* [64-bit](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-windows-x86-msvc2013_64-5.6.1-1.exe)
|
* [64-bit](https://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-windows-x86-msvc2013_64-5.6.1-1.exe)
|
||||||
|
|
||||||
Once Qt is installed, you need to manually configure the following:
|
Once Qt is installed, you need to manually configure the following:
|
||||||
* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt\5.6.1\msvc2013\lib\cmake` or `Qt\5.6.1\msvc2013_64\lib\cmake` directory.
|
* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt\5.6.1\msvc2013\lib\cmake` or `Qt\5.6.1\msvc2013_64\lib\cmake` directory.
|
||||||
|
@ -72,7 +72,7 @@ Your system may already have several versions of the OpenSSL DLL's (ssleay32.dll
|
||||||
QSslSocket: cannot resolve SSL_CTX_set_next_proto_select_cb
|
QSslSocket: cannot resolve SSL_CTX_set_next_proto_select_cb
|
||||||
QSslSocket: cannot resolve SSL_get0_next_proto_negotiated
|
QSslSocket: cannot resolve SSL_get0_next_proto_negotiated
|
||||||
|
|
||||||
To prevent these problems, install OpenSSL yourself. Download one of the following binary packages [from this website](http://slproweb.com/products/Win32OpenSSL.html):
|
To prevent these problems, install OpenSSL yourself. Download one of the following binary packages [from this website](https://slproweb.com/products/Win32OpenSSL.html):
|
||||||
* Win32 OpenSSL v1.0.1q
|
* Win32 OpenSSL v1.0.1q
|
||||||
* Win64 OpenSSL v1.0.1q
|
* Win64 OpenSSL v1.0.1q
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,11 @@ We're hiring! We're looking for skilled developers;
|
||||||
send your resume to hiring@highfidelity.com
|
send your resume to hiring@highfidelity.com
|
||||||
|
|
||||||
##### Chat with us
|
##### Chat with us
|
||||||
Come chat with us in [our Gitter](http://gitter.im/highfidelity/hifi) if you have any questions or just want to say hi!
|
Come chat with us in [our Gitter](https://gitter.im/highfidelity/hifi) if you have any questions or just want to say hi!
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
=========
|
=========
|
||||||
Documentation is available at [docs.highfidelity.com](http://docs.highfidelity.com), if something is missing, please suggest it via a new job on Worklist (add to the hifi-docs project).
|
Documentation is available at [docs.highfidelity.com](https://docs.highfidelity.com), if something is missing, please suggest it via a new job on Worklist (add to the hifi-docs project).
|
||||||
|
|
||||||
Build Instructions
|
Build Instructions
|
||||||
=========
|
=========
|
||||||
|
|
|
@ -168,7 +168,6 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
|
||||||
QList<AvatarSharedPointer> avatarList;
|
QList<AvatarSharedPointer> avatarList;
|
||||||
std::unordered_map<AvatarSharedPointer, SharedNodePointer> avatarDataToNodes;
|
std::unordered_map<AvatarSharedPointer, SharedNodePointer> avatarDataToNodes;
|
||||||
|
|
||||||
int listItem = 0;
|
|
||||||
std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) {
|
std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) {
|
||||||
const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(otherNode->getLinkedData());
|
const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(otherNode->getLinkedData());
|
||||||
|
|
||||||
|
@ -176,7 +175,6 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
|
||||||
// but not have yet sent data that's linked to the node. Check for that case and don't
|
// but not have yet sent data that's linked to the node. Check for that case and don't
|
||||||
// consider those nodes.
|
// consider those nodes.
|
||||||
if (otherNodeData) {
|
if (otherNodeData) {
|
||||||
listItem++;
|
|
||||||
AvatarSharedPointer otherAvatar = otherNodeData->getAvatarSharedPointer();
|
AvatarSharedPointer otherAvatar = otherNodeData->getAvatarSharedPointer();
|
||||||
avatarList << otherAvatar;
|
avatarList << otherAvatar;
|
||||||
avatarDataToNodes[otherAvatar] = otherNode;
|
avatarDataToNodes[otherAvatar] = otherNode;
|
||||||
|
@ -185,8 +183,8 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
|
||||||
|
|
||||||
AvatarSharedPointer thisAvatar = nodeData->getAvatarSharedPointer();
|
AvatarSharedPointer thisAvatar = nodeData->getAvatarSharedPointer();
|
||||||
ViewFrustum cameraView = nodeData->getViewFrustom();
|
ViewFrustum cameraView = nodeData->getViewFrustom();
|
||||||
std::priority_queue<AvatarPriority> sortedAvatars = AvatarData::sortAvatars(
|
std::priority_queue<AvatarPriority> sortedAvatars;
|
||||||
avatarList, cameraView,
|
AvatarData::sortAvatars(avatarList, cameraView, sortedAvatars,
|
||||||
|
|
||||||
[&](AvatarSharedPointer avatar)->uint64_t{
|
[&](AvatarSharedPointer avatar)->uint64_t{
|
||||||
auto avatarNode = avatarDataToNodes[avatar];
|
auto avatarNode = avatarDataToNodes[avatar];
|
||||||
|
|
BIN
interface/resources/avatar/animations/sitting.fbx
Normal file
BIN
interface/resources/avatar/animations/sitting.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/sitting_idle.fbx
Normal file
BIN
interface/resources/avatar/animations/sitting_idle.fbx
Normal file
Binary file not shown.
BIN
interface/resources/fonts/hifi-glyphs.ttf
Normal file → Executable file
BIN
interface/resources/fonts/hifi-glyphs.ttf
Normal file → Executable file
Binary file not shown.
|
@ -31,6 +31,7 @@ Item {
|
||||||
property real displayNameTextPixelSize: 18
|
property real displayNameTextPixelSize: 18
|
||||||
property int usernameTextHeight: 12
|
property int usernameTextHeight: 12
|
||||||
property real audioLevel: 0.0
|
property real audioLevel: 0.0
|
||||||
|
property real avgAudioLevel: 0.0
|
||||||
property bool isMyCard: false
|
property bool isMyCard: false
|
||||||
property bool selected: false
|
property bool selected: false
|
||||||
property bool isAdmin: false
|
property bool isAdmin: false
|
||||||
|
@ -55,7 +56,7 @@ Item {
|
||||||
id: textContainer
|
id: textContainer
|
||||||
// Size
|
// Size
|
||||||
width: parent.width - /*avatarImage.width - parent.spacing - */parent.anchors.leftMargin - parent.anchors.rightMargin
|
width: parent.width - /*avatarImage.width - parent.spacing - */parent.anchors.leftMargin - parent.anchors.rightMargin
|
||||||
height: childrenRect.height
|
height: selected || isMyCard ? childrenRect.height : childrenRect.height - 15
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
// DisplayName field for my card
|
// DisplayName field for my card
|
||||||
|
@ -273,6 +274,7 @@ Item {
|
||||||
// Style
|
// Style
|
||||||
radius: 4
|
radius: 4
|
||||||
color: "#c5c5c5"
|
color: "#c5c5c5"
|
||||||
|
visible: isMyCard || selected
|
||||||
// Rectangle for the zero-gain point on the VU meter
|
// Rectangle for the zero-gain point on the VU meter
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: vuMeterZeroGain
|
id: vuMeterZeroGain
|
||||||
|
@ -303,6 +305,7 @@ Item {
|
||||||
id: vuMeterBase
|
id: vuMeterBase
|
||||||
// Anchors
|
// Anchors
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
visible: isMyCard || selected
|
||||||
// Style
|
// Style
|
||||||
color: parent.color
|
color: parent.color
|
||||||
radius: parent.radius
|
radius: parent.radius
|
||||||
|
@ -310,6 +313,7 @@ Item {
|
||||||
// Rectangle for the VU meter audio level
|
// Rectangle for the VU meter audio level
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: vuMeterLevel
|
id: vuMeterLevel
|
||||||
|
visible: isMyCard || selected
|
||||||
// Size
|
// Size
|
||||||
width: (thisNameCard.audioLevel) * parent.width
|
width: (thisNameCard.audioLevel) * parent.width
|
||||||
// Style
|
// Style
|
||||||
|
@ -335,7 +339,7 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Per-Avatar Gain Slider
|
// Per-Avatar Gain Slider
|
||||||
Slider {
|
Slider {
|
||||||
id: gainSlider
|
id: gainSlider
|
||||||
// Size
|
// Size
|
||||||
|
@ -345,7 +349,7 @@ Item {
|
||||||
anchors.verticalCenter: nameCardVUMeter.verticalCenter
|
anchors.verticalCenter: nameCardVUMeter.verticalCenter
|
||||||
// Properties
|
// Properties
|
||||||
visible: !isMyCard && selected
|
visible: !isMyCard && selected
|
||||||
value: pal.gainSliderValueDB[uuid] ? pal.gainSliderValueDB[uuid] : 0.0
|
value: Users.getAvatarGain(uuid)
|
||||||
minimumValue: -60.0
|
minimumValue: -60.0
|
||||||
maximumValue: 20.0
|
maximumValue: 20.0
|
||||||
stepSize: 5
|
stepSize: 5
|
||||||
|
@ -369,7 +373,7 @@ Item {
|
||||||
mouse.accepted = false
|
mouse.accepted = false
|
||||||
}
|
}
|
||||||
onReleased: {
|
onReleased: {
|
||||||
// the above mouse.accepted seems to make this
|
// the above mouse.accepted seems to make this
|
||||||
// never get called, nonetheless...
|
// never get called, nonetheless...
|
||||||
mouse.accepted = false
|
mouse.accepted = false
|
||||||
}
|
}
|
||||||
|
@ -393,14 +397,9 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateGainFromQML(avatarUuid, sliderValue, isReleased) {
|
function updateGainFromQML(avatarUuid, sliderValue, isReleased) {
|
||||||
if (isReleased || pal.gainSliderValueDB[avatarUuid] !== sliderValue) {
|
Users.setAvatarGain(avatarUuid, sliderValue);
|
||||||
pal.gainSliderValueDB[avatarUuid] = sliderValue;
|
if (isReleased) {
|
||||||
var data = {
|
UserActivityLogger.palAction("avatar_gain_changed", avatarUuid);
|
||||||
sessionId: avatarUuid,
|
|
||||||
gain: sliderValue,
|
|
||||||
isReleased: isReleased
|
|
||||||
};
|
|
||||||
pal.sendToScript({method: 'updateGain', params: data});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// Pal.qml
|
// Pal.qml
|
||||||
// qml/hifi
|
// qml/hifi
|
||||||
//
|
//
|
||||||
// People Action List
|
// People Action List
|
||||||
//
|
//
|
||||||
// Created by Howard Stearns on 12/12/2016
|
// Created by Howard Stearns on 12/12/2016
|
||||||
// Copyright 2016 High Fidelity, Inc.
|
// Copyright 2016 High Fidelity, Inc.
|
||||||
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
import QtQuick 2.5
|
import QtQuick 2.5
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
|
import QtGraphicalEffects 1.0
|
||||||
import Qt.labs.settings 1.0
|
import Qt.labs.settings 1.0
|
||||||
import "../styles-uit"
|
import "../styles-uit"
|
||||||
import "../controls-uit" as HifiControls
|
import "../controls-uit" as HifiControls
|
||||||
|
@ -33,13 +34,10 @@ Rectangle {
|
||||||
property int actionButtonAllowance: actionButtonWidth * 2
|
property int actionButtonAllowance: actionButtonWidth * 2
|
||||||
property int minNameCardWidth: palContainer.width - (actionButtonAllowance * 2) - 4 - hifi.dimensions.scrollbarBackgroundWidth
|
property int minNameCardWidth: palContainer.width - (actionButtonAllowance * 2) - 4 - hifi.dimensions.scrollbarBackgroundWidth
|
||||||
property int nameCardWidth: minNameCardWidth + (iAmAdmin ? 0 : actionButtonAllowance)
|
property int nameCardWidth: minNameCardWidth + (iAmAdmin ? 0 : actionButtonAllowance)
|
||||||
property var myData: ({displayName: "", userName: "", audioLevel: 0.0, admin: true}) // valid dummy until set
|
property var myData: ({displayName: "", userName: "", audioLevel: 0.0, avgAudioLevel: 0.0, admin: true}) // valid dummy until set
|
||||||
property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring.
|
property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring.
|
||||||
property var userModelData: [] // This simple list is essentially a mirror of the userModel listModel without all the extra complexities.
|
property var userModelData: [] // This simple list is essentially a mirror of the userModel listModel without all the extra complexities.
|
||||||
property bool iAmAdmin: false
|
property bool iAmAdmin: false
|
||||||
// Keep a local list of per-avatar gainSliderValueDBs. Far faster than fetching this data from the server.
|
|
||||||
// NOTE: if another script modifies the per-avatar gain, this value won't be accurate!
|
|
||||||
property var gainSliderValueDB: ({});
|
|
||||||
|
|
||||||
HifiConstants { id: hifi }
|
HifiConstants { id: hifi }
|
||||||
|
|
||||||
|
@ -60,6 +58,8 @@ Rectangle {
|
||||||
category: "pal"
|
category: "pal"
|
||||||
property bool filtered: false
|
property bool filtered: false
|
||||||
property int nearDistance: 30
|
property int nearDistance: 30
|
||||||
|
property int sortIndicatorColumn: 1
|
||||||
|
property int sortIndicatorOrder: Qt.AscendingOrder
|
||||||
}
|
}
|
||||||
function refreshWithFilter() {
|
function refreshWithFilter() {
|
||||||
// We should just be able to set settings.filtered to filter.checked, but see #3249, so send to .js for saving.
|
// We should just be able to set settings.filtered to filter.checked, but see #3249, so send to .js for saving.
|
||||||
|
@ -99,6 +99,7 @@ Rectangle {
|
||||||
displayName: myData.displayName
|
displayName: myData.displayName
|
||||||
userName: myData.userName
|
userName: myData.userName
|
||||||
audioLevel: myData.audioLevel
|
audioLevel: myData.audioLevel
|
||||||
|
avgAudioLevel: myData.avgAudioLevel
|
||||||
isMyCard: true
|
isMyCard: true
|
||||||
// Size
|
// Size
|
||||||
width: minNameCardWidth
|
width: minNameCardWidth
|
||||||
|
@ -193,8 +194,24 @@ Rectangle {
|
||||||
centerHeaderText: true
|
centerHeaderText: true
|
||||||
sortIndicatorVisible: true
|
sortIndicatorVisible: true
|
||||||
headerVisible: true
|
headerVisible: true
|
||||||
onSortIndicatorColumnChanged: sortModel()
|
sortIndicatorColumn: settings.sortIndicatorColumn
|
||||||
onSortIndicatorOrderChanged: sortModel()
|
sortIndicatorOrder: settings.sortIndicatorOrder
|
||||||
|
onSortIndicatorColumnChanged: {
|
||||||
|
settings.sortIndicatorColumn = sortIndicatorColumn
|
||||||
|
sortModel()
|
||||||
|
}
|
||||||
|
onSortIndicatorOrderChanged: {
|
||||||
|
settings.sortIndicatorOrder = sortIndicatorOrder
|
||||||
|
sortModel()
|
||||||
|
}
|
||||||
|
|
||||||
|
TableViewColumn {
|
||||||
|
role: "avgAudioLevel"
|
||||||
|
title: "LOUD"
|
||||||
|
width: actionButtonWidth
|
||||||
|
movable: false
|
||||||
|
resizable: false
|
||||||
|
}
|
||||||
|
|
||||||
TableViewColumn {
|
TableViewColumn {
|
||||||
id: displayNameHeader
|
id: displayNameHeader
|
||||||
|
@ -204,13 +221,6 @@ Rectangle {
|
||||||
movable: false
|
movable: false
|
||||||
resizable: false
|
resizable: false
|
||||||
}
|
}
|
||||||
TableViewColumn {
|
|
||||||
role: "personalMute"
|
|
||||||
title: "MUTE"
|
|
||||||
width: actionButtonWidth
|
|
||||||
movable: false
|
|
||||||
resizable: false
|
|
||||||
}
|
|
||||||
TableViewColumn {
|
TableViewColumn {
|
||||||
role: "ignore"
|
role: "ignore"
|
||||||
title: "IGNORE"
|
title: "IGNORE"
|
||||||
|
@ -241,7 +251,7 @@ Rectangle {
|
||||||
// This Rectangle refers to each Row in the table.
|
// This Rectangle refers to each Row in the table.
|
||||||
rowDelegate: Rectangle { // The only way I know to specify a row height.
|
rowDelegate: Rectangle { // The only way I know to specify a row height.
|
||||||
// Size
|
// Size
|
||||||
height: rowHeight
|
height: styleData.selected ? rowHeight : rowHeight - 15
|
||||||
color: styleData.selected
|
color: styleData.selected
|
||||||
? hifi.colors.orangeHighlight
|
? hifi.colors.orangeHighlight
|
||||||
: styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd
|
: styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd
|
||||||
|
@ -252,6 +262,8 @@ Rectangle {
|
||||||
id: itemCell
|
id: itemCell
|
||||||
property bool isCheckBox: styleData.role === "personalMute" || styleData.role === "ignore"
|
property bool isCheckBox: styleData.role === "personalMute" || styleData.role === "ignore"
|
||||||
property bool isButton: styleData.role === "mute" || styleData.role === "kick"
|
property bool isButton: styleData.role === "mute" || styleData.role === "kick"
|
||||||
|
property bool isAvgAudio: styleData.role === "avgAudioLevel"
|
||||||
|
|
||||||
// This NameCard refers to the cell that contains an avatar's
|
// This NameCard refers to the cell that contains an avatar's
|
||||||
// DisplayName and UserName
|
// DisplayName and UserName
|
||||||
NameCard {
|
NameCard {
|
||||||
|
@ -260,7 +272,8 @@ Rectangle {
|
||||||
displayName: styleData.value
|
displayName: styleData.value
|
||||||
userName: model ? model.userName : ""
|
userName: model ? model.userName : ""
|
||||||
audioLevel: model ? model.audioLevel : 0.0
|
audioLevel: model ? model.audioLevel : 0.0
|
||||||
visible: !isCheckBox && !isButton
|
avgAudioLevel: model ? model.avgAudioLevel : 0.0
|
||||||
|
visible: !isCheckBox && !isButton && !isAvgAudio
|
||||||
uuid: model ? model.sessionId : ""
|
uuid: model ? model.sessionId : ""
|
||||||
selected: styleData.selected
|
selected: styleData.selected
|
||||||
isAdmin: model && model.admin
|
isAdmin: model && model.admin
|
||||||
|
@ -270,7 +283,34 @@ Rectangle {
|
||||||
// Anchors
|
// Anchors
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
}
|
}
|
||||||
|
HifiControls.GlyphButton {
|
||||||
|
function getGlyph() {
|
||||||
|
var fileName = "vol_";
|
||||||
|
if (model["personalMute"]) {
|
||||||
|
fileName += "x_";
|
||||||
|
}
|
||||||
|
fileName += (4.0*(model ? model.avgAudioLevel : 0.0)).toFixed(0);
|
||||||
|
return hifi.glyphs[fileName];
|
||||||
|
}
|
||||||
|
id: avgAudioVolume
|
||||||
|
visible: isAvgAudio
|
||||||
|
glyph: getGlyph()
|
||||||
|
width: 32
|
||||||
|
size: height
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
onClicked: {
|
||||||
|
// cannot change mute status when ignoring
|
||||||
|
if (!model["ignore"]) {
|
||||||
|
var newValue = !model["personalMute"];
|
||||||
|
userModel.setProperty(model.userIndex, "personalMute", newValue)
|
||||||
|
userModelData[model.userIndex]["personalMute"] = newValue // Defensive programming
|
||||||
|
Users["personalMute"](model.sessionId, newValue)
|
||||||
|
UserActivityLogger["palAction"](newValue ? "personalMute" : "un-personalMute", model.sessionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This CheckBox belongs in the columns that contain the stateful action buttons ("Mute" & "Ignore" for now)
|
// This CheckBox belongs in the columns that contain the stateful action buttons ("Mute" & "Ignore" for now)
|
||||||
// KNOWN BUG with the Checkboxes: When clicking in the center of the sorting header, the checkbox
|
// KNOWN BUG with the Checkboxes: When clicking in the center of the sorting header, the checkbox
|
||||||
// will appear in the "hovered" state. Hovering over the checkbox will fix it.
|
// will appear in the "hovered" state. Hovering over the checkbox will fix it.
|
||||||
|
@ -299,6 +339,7 @@ Rectangle {
|
||||||
} else {
|
} else {
|
||||||
delete ignored[model.sessionId]
|
delete ignored[model.sessionId]
|
||||||
}
|
}
|
||||||
|
avgAudioVolume.glyph = avgAudioVolume.getGlyph()
|
||||||
}
|
}
|
||||||
// http://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html#creating-property-bindings-from-javascript
|
// http://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html#creating-property-bindings-from-javascript
|
||||||
// I'm using an explicit binding here because clicking a checkbox breaks the implicit binding as set by
|
// I'm using an explicit binding here because clicking a checkbox breaks the implicit binding as set by
|
||||||
|
@ -306,7 +347,7 @@ Rectangle {
|
||||||
checked = Qt.binding(function() { return (model[styleData.role])})
|
checked = Qt.binding(function() { return (model[styleData.role])})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This Button belongs in the columns that contain the stateless action buttons ("Silence" & "Ban" for now)
|
// This Button belongs in the columns that contain the stateless action buttons ("Silence" & "Ban" for now)
|
||||||
HifiControls.Button {
|
HifiControls.Button {
|
||||||
id: actionButton
|
id: actionButton
|
||||||
|
@ -314,7 +355,7 @@ Rectangle {
|
||||||
visible: isButton
|
visible: isButton
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: 32
|
width: 32
|
||||||
height: 24
|
height: 32
|
||||||
onClicked: {
|
onClicked: {
|
||||||
Users[styleData.role](model.sessionId)
|
Users[styleData.role](model.sessionId)
|
||||||
UserActivityLogger["palAction"](styleData.role, model.sessionId)
|
UserActivityLogger["palAction"](styleData.role, model.sessionId)
|
||||||
|
@ -366,7 +407,7 @@ Rectangle {
|
||||||
anchors.left: table.left
|
anchors.left: table.left
|
||||||
anchors.top: table.top
|
anchors.top: table.top
|
||||||
anchors.topMargin: 1
|
anchors.topMargin: 1
|
||||||
anchors.leftMargin: nameCardWidth/2 + displayNameHeaderMetrics.width/2 + 6
|
anchors.leftMargin: actionButtonWidth + nameCardWidth/2 + displayNameHeaderMetrics.width/2 + 6
|
||||||
RalewayRegular {
|
RalewayRegular {
|
||||||
id: helpText
|
id: helpText
|
||||||
text: "[?]"
|
text: "[?]"
|
||||||
|
@ -538,25 +579,29 @@ Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'updateAudioLevel':
|
case 'updateAudioLevel':
|
||||||
for (var userId in message.params) {
|
for (var userId in message.params) {
|
||||||
var audioLevel = message.params[userId];
|
var audioLevel = message.params[userId][0];
|
||||||
|
var avgAudioLevel = message.params[userId][1];
|
||||||
// If the userId is 0, we're updating "myData".
|
// If the userId is 0, we're updating "myData".
|
||||||
if (userId == 0) {
|
if (userId == 0) {
|
||||||
myData.audioLevel = audioLevel;
|
myData.audioLevel = audioLevel;
|
||||||
myCard.audioLevel = audioLevel; // Defensive programming
|
myCard.audioLevel = audioLevel; // Defensive programming
|
||||||
|
myData.avgAudioLevel = avgAudioLevel;
|
||||||
|
myCard.avgAudioLevel = avgAudioLevel;
|
||||||
} else {
|
} else {
|
||||||
var userIndex = findSessionIndex(userId);
|
var userIndex = findSessionIndex(userId);
|
||||||
if (userIndex != -1) {
|
if (userIndex != -1) {
|
||||||
userModel.setProperty(userIndex, "audioLevel", audioLevel);
|
userModel.setProperty(userIndex, "audioLevel", audioLevel);
|
||||||
userModelData[userIndex].audioLevel = audioLevel; // Defensive programming
|
userModelData[userIndex].audioLevel = audioLevel; // Defensive programming
|
||||||
|
userModel.setProperty(userIndex, "avgAudioLevel", avgAudioLevel);
|
||||||
|
userModelData[userIndex].avgAudioLevel = avgAudioLevel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'clearLocalQMLData':
|
case 'clearLocalQMLData':
|
||||||
ignored = {};
|
ignored = {};
|
||||||
gainSliderValueDB = {};
|
|
||||||
break;
|
break;
|
||||||
case 'avatarDisconnected':
|
case 'avatarDisconnected':
|
||||||
var sessionID = message.params[0];
|
var sessionID = message.params[0];
|
||||||
|
|
|
@ -318,5 +318,15 @@ Item {
|
||||||
readonly property string deg: "\\"
|
readonly property string deg: "\\"
|
||||||
readonly property string px: "|"
|
readonly property string px: "|"
|
||||||
readonly property string editPencil: "\ue00d"
|
readonly property string editPencil: "\ue00d"
|
||||||
|
readonly property string vol_0: "\ue00e"
|
||||||
|
readonly property string vol_1: "\ue00f"
|
||||||
|
readonly property string vol_2: "\ue010"
|
||||||
|
readonly property string vol_3: "\ue011"
|
||||||
|
readonly property string vol_4: "\ue012"
|
||||||
|
readonly property string vol_x_0: "\ue013"
|
||||||
|
readonly property string vol_x_1: "\ue014"
|
||||||
|
readonly property string vol_x_2: "\ue015"
|
||||||
|
readonly property string vol_x_3: "\ue016"
|
||||||
|
readonly property string vol_x_4: "\ue017"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2918,10 +2918,12 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::Key_P: {
|
case Qt::Key_P: {
|
||||||
bool isFirstPersonChecked = Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson);
|
if (!(isShifted || isMeta || isOption)) {
|
||||||
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, !isFirstPersonChecked);
|
bool isFirstPersonChecked = Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson);
|
||||||
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, isFirstPersonChecked);
|
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, !isFirstPersonChecked);
|
||||||
cameraMenuChanged();
|
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, isFirstPersonChecked);
|
||||||
|
cameraMenuChanged();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -577,7 +577,7 @@ Menu::Menu() {
|
||||||
nodeList.data(), SLOT(toggleSendNewerDSConnectVersion(bool)));
|
nodeList.data(), SLOT(toggleSendNewerDSConnectVersion(bool)));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// Developer >> Tests >>>
|
// Developer >> Tests >>>
|
||||||
MenuWrapper* testMenu = developerMenu->addMenu("Tests");
|
MenuWrapper* testMenu = developerMenu->addMenu("Tests");
|
||||||
addActionToQMenuAndActionHash(testMenu, MenuOption::RunClientScriptTests, 0, dialogsManager.data(), SLOT(showTestingResults()));
|
addActionToQMenuAndActionHash(testMenu, MenuOption::RunClientScriptTests, 0, dialogsManager.data(), SLOT(showTestingResults()));
|
||||||
|
@ -628,9 +628,9 @@ Menu::Menu() {
|
||||||
|
|
||||||
auto scope = DependencyManager::get<AudioScope>();
|
auto scope = DependencyManager::get<AudioScope>();
|
||||||
MenuWrapper* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope");
|
MenuWrapper* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope");
|
||||||
addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope, Qt::CTRL | Qt::Key_P, false,
|
addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope, Qt::CTRL | Qt::Key_F2, false,
|
||||||
scope.data(), SLOT(toggle()));
|
scope.data(), SLOT(toggle()));
|
||||||
addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopePause, Qt::CTRL | Qt::SHIFT | Qt::Key_P, false,
|
addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopePause, Qt::CTRL | Qt::SHIFT | Qt::Key_F2, false,
|
||||||
scope.data(), SLOT(togglePause()));
|
scope.data(), SLOT(togglePause()));
|
||||||
|
|
||||||
addDisabledActionAndSeparator(audioScopeMenu, "Display Frames");
|
addDisabledActionAndSeparator(audioScopeMenu, "Display Frames");
|
||||||
|
|
|
@ -348,10 +348,11 @@ void Avatar::simulate(float deltaTime, bool inView) {
|
||||||
PROFILE_RANGE(simulation, "updateJoints");
|
PROFILE_RANGE(simulation, "updateJoints");
|
||||||
if (inView && _hasNewJointData) {
|
if (inView && _hasNewJointData) {
|
||||||
_skeletonModel->getRig()->copyJointsFromJointData(_jointData);
|
_skeletonModel->getRig()->copyJointsFromJointData(_jointData);
|
||||||
|
glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset());
|
||||||
|
_skeletonModel->getRig()->computeExternalPoses(rootTransform);
|
||||||
_jointDataSimulationRate.increment();
|
_jointDataSimulationRate.increment();
|
||||||
|
|
||||||
_skeletonModel->simulate(deltaTime, true);
|
_skeletonModel->simulate(deltaTime, true);
|
||||||
_skeletonModelSimulationRate.increment();
|
|
||||||
|
|
||||||
locationChanged(); // joints changed, so if there are any children, update them.
|
locationChanged(); // joints changed, so if there are any children, update them.
|
||||||
_hasNewJointData = false;
|
_hasNewJointData = false;
|
||||||
|
@ -367,8 +368,8 @@ void Avatar::simulate(float deltaTime, bool inView) {
|
||||||
} else {
|
} else {
|
||||||
// a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated.
|
// a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated.
|
||||||
_skeletonModel->simulate(deltaTime, false);
|
_skeletonModel->simulate(deltaTime, false);
|
||||||
_skeletonModelSimulationRate.increment();
|
|
||||||
}
|
}
|
||||||
|
_skeletonModelSimulationRate.increment();
|
||||||
}
|
}
|
||||||
|
|
||||||
// update animation for display name fade in/out
|
// update animation for display name fade in/out
|
||||||
|
|
|
@ -157,15 +157,14 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
|
||||||
PerformanceTimer perfTimer("otherAvatars");
|
PerformanceTimer perfTimer("otherAvatars");
|
||||||
uint64_t startTime = usecTimestampNow();
|
|
||||||
|
|
||||||
auto avatarMap = getHashCopy();
|
auto avatarMap = getHashCopy();
|
||||||
QList<AvatarSharedPointer> avatarList = avatarMap.values();
|
QList<AvatarSharedPointer> avatarList = avatarMap.values();
|
||||||
ViewFrustum cameraView;
|
ViewFrustum cameraView;
|
||||||
qApp->copyDisplayViewFrustum(cameraView);
|
qApp->copyDisplayViewFrustum(cameraView);
|
||||||
|
|
||||||
std::priority_queue<AvatarPriority> sortedAvatars = AvatarData::sortAvatars(
|
std::priority_queue<AvatarPriority> sortedAvatars;
|
||||||
avatarList, cameraView,
|
AvatarData::sortAvatars(avatarList, cameraView, sortedAvatars,
|
||||||
|
|
||||||
[](AvatarSharedPointer avatar)->uint64_t{
|
[](AvatarSharedPointer avatar)->uint64_t{
|
||||||
return std::static_pointer_cast<Avatar>(avatar)->getLastRenderUpdateTime();
|
return std::static_pointer_cast<Avatar>(avatar)->getLastRenderUpdateTime();
|
||||||
|
@ -194,10 +193,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
||||||
});
|
});
|
||||||
|
|
||||||
render::PendingChanges pendingChanges;
|
render::PendingChanges pendingChanges;
|
||||||
const uint64_t RENDER_UPDATE_BUDGET = 1500; // usec
|
uint64_t startTime = usecTimestampNow();
|
||||||
const uint64_t MAX_UPDATE_BUDGET = 2000; // usec
|
const uint64_t UPDATE_BUDGET = 2000; // usec
|
||||||
uint64_t renderExpiry = startTime + RENDER_UPDATE_BUDGET;
|
uint64_t updateExpiry = startTime + UPDATE_BUDGET;
|
||||||
uint64_t maxExpiry = startTime + MAX_UPDATE_BUDGET;
|
|
||||||
|
|
||||||
int numAvatarsUpdated = 0;
|
int numAvatarsUpdated = 0;
|
||||||
int numAVatarsNotUpdated = 0;
|
int numAVatarsNotUpdated = 0;
|
||||||
|
@ -223,7 +221,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
||||||
|
|
||||||
const float OUT_OF_VIEW_THRESHOLD = 0.5f * AvatarData::OUT_OF_VIEW_PENALTY;
|
const float OUT_OF_VIEW_THRESHOLD = 0.5f * AvatarData::OUT_OF_VIEW_PENALTY;
|
||||||
uint64_t now = usecTimestampNow();
|
uint64_t now = usecTimestampNow();
|
||||||
if (now < renderExpiry) {
|
if (now < updateExpiry) {
|
||||||
// we're within budget
|
// we're within budget
|
||||||
bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD;
|
bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD;
|
||||||
if (inView && avatar->hasNewJointData()) {
|
if (inView && avatar->hasNewJointData()) {
|
||||||
|
@ -232,21 +230,13 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
||||||
avatar->simulate(deltaTime, inView);
|
avatar->simulate(deltaTime, inView);
|
||||||
avatar->updateRenderItem(pendingChanges);
|
avatar->updateRenderItem(pendingChanges);
|
||||||
avatar->setLastRenderUpdateTime(startTime);
|
avatar->setLastRenderUpdateTime(startTime);
|
||||||
} else if (now < maxExpiry) {
|
|
||||||
// we've spent most of our time budget, but we still simulate() the avatar as it if were out of view
|
|
||||||
// --> some avatars may freeze until their priority trickles up
|
|
||||||
bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD;
|
|
||||||
if (inView && avatar->hasNewJointData()) {
|
|
||||||
numAVatarsNotUpdated++;
|
|
||||||
}
|
|
||||||
avatar->simulate(deltaTime, false);
|
|
||||||
} else {
|
} else {
|
||||||
// we've spent ALL of our time budget --> bail on the rest of the avatar updates
|
// we've spent our full time budget --> bail on the rest of the avatar updates
|
||||||
// --> more avatars may freeze until their priority trickles up
|
// --> more avatars may freeze until their priority trickles up
|
||||||
// --> some scale or fade animations may glitch
|
// --> some scale or fade animations may glitch
|
||||||
// --> some avatar velocity measurements may be a little off
|
// --> some avatar velocity measurements may be a little off
|
||||||
|
|
||||||
// HACK: no time simulate, but we will take the time to count how many were tragically missed
|
// no time simulate, but we take the time to count how many were tragically missed
|
||||||
bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD;
|
bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD;
|
||||||
if (!inView) {
|
if (!inView) {
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -95,12 +95,6 @@ void CauterizedModel::createCollisionRenderItemSet() {
|
||||||
Model::createCollisionRenderItemSet();
|
Model::createCollisionRenderItemSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called within Model::simulate call, below.
|
|
||||||
void CauterizedModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|
||||||
Model::updateRig(deltaTime, parentTransform);
|
|
||||||
_needsUpdateClusterMatrices = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CauterizedModel::updateClusterMatrices() {
|
void CauterizedModel::updateClusterMatrices() {
|
||||||
PerformanceTimer perfTimer("CauterizedModel::updateClusterMatrices");
|
PerformanceTimer perfTimer("CauterizedModel::updateClusterMatrices");
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,6 @@ public:
|
||||||
void createVisibleRenderItemSet() override;
|
void createVisibleRenderItemSet() override;
|
||||||
void createCollisionRenderItemSet() override;
|
void createCollisionRenderItemSet() override;
|
||||||
|
|
||||||
virtual void updateRig(float deltaTime, glm::mat4 parentTransform) override;
|
|
||||||
virtual void updateClusterMatrices() override;
|
virtual void updateClusterMatrices() override;
|
||||||
void updateRenderItems() override;
|
void updateRenderItems() override;
|
||||||
|
|
||||||
|
|
|
@ -260,6 +260,11 @@ QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) {
|
||||||
return AvatarData::toByteArrayStateful(dataDetail);
|
return AvatarData::toByteArrayStateful(dataDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MyAvatar::resetSensorsAndBody() {
|
||||||
|
qApp->getActiveDisplayPlugin()->resetSensors();
|
||||||
|
reset(true, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
void MyAvatar::centerBody() {
|
void MyAvatar::centerBody() {
|
||||||
if (QThread::currentThread() != thread()) {
|
if (QThread::currentThread() != thread()) {
|
||||||
QMetaObject::invokeMethod(this, "centerBody");
|
QMetaObject::invokeMethod(this, "centerBody");
|
||||||
|
@ -2483,6 +2488,45 @@ glm::vec3 MyAvatar::getAbsoluteJointTranslationInObjectFrame(int index) const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MyAvatar::pinJoint(int index, const glm::vec3& position, const glm::quat& orientation) {
|
||||||
|
auto hipsIndex = getJointIndex("Hips");
|
||||||
|
if (index != hipsIndex) {
|
||||||
|
qWarning() << "Pinning is only supported for the hips joint at the moment.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPosition(position);
|
||||||
|
setOrientation(orientation);
|
||||||
|
|
||||||
|
_rig->setMaxHipsOffsetLength(0.05f);
|
||||||
|
|
||||||
|
auto it = std::find(_pinnedJoints.begin(), _pinnedJoints.end(), index);
|
||||||
|
if (it == _pinnedJoints.end()) {
|
||||||
|
_pinnedJoints.push_back(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MyAvatar::clearPinOnJoint(int index) {
|
||||||
|
auto it = std::find(_pinnedJoints.begin(), _pinnedJoints.end(), index);
|
||||||
|
if (it != _pinnedJoints.end()) {
|
||||||
|
_pinnedJoints.erase(it);
|
||||||
|
|
||||||
|
auto hipsIndex = getJointIndex("Hips");
|
||||||
|
if (index == hipsIndex) {
|
||||||
|
_rig->setMaxHipsOffsetLength(FLT_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float MyAvatar::getIKErrorOnLastSolve() const {
|
||||||
|
return _rig->getIKErrorOnLastSolve();
|
||||||
|
}
|
||||||
|
|
||||||
// thread-safe
|
// thread-safe
|
||||||
void MyAvatar::addHoldAction(AvatarActionHold* holdAction) {
|
void MyAvatar::addHoldAction(AvatarActionHold* holdAction) {
|
||||||
std::lock_guard<std::mutex> guard(_holdActionsMutex);
|
std::lock_guard<std::mutex> guard(_holdActionsMutex);
|
||||||
|
|
|
@ -99,6 +99,7 @@ public:
|
||||||
|
|
||||||
void reset(bool andRecenter = false, bool andReload = true, bool andHead = true);
|
void reset(bool andRecenter = false, bool andReload = true, bool andHead = true);
|
||||||
|
|
||||||
|
Q_INVOKABLE void resetSensorsAndBody();
|
||||||
Q_INVOKABLE void centerBody(); // thread-safe
|
Q_INVOKABLE void centerBody(); // thread-safe
|
||||||
Q_INVOKABLE void clearIKJointLimitHistory(); // thread-safe
|
Q_INVOKABLE void clearIKJointLimitHistory(); // thread-safe
|
||||||
|
|
||||||
|
@ -216,6 +217,11 @@ public:
|
||||||
virtual void clearJointData(int index) override;
|
virtual void clearJointData(int index) override;
|
||||||
virtual void clearJointsData() override;
|
virtual void clearJointsData() override;
|
||||||
|
|
||||||
|
Q_INVOKABLE bool pinJoint(int index, const glm::vec3& position, const glm::quat& orientation);
|
||||||
|
Q_INVOKABLE bool clearPinOnJoint(int index);
|
||||||
|
|
||||||
|
Q_INVOKABLE float getIKErrorOnLastSolve() const;
|
||||||
|
|
||||||
Q_INVOKABLE void useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName = QString());
|
Q_INVOKABLE void useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName = QString());
|
||||||
Q_INVOKABLE QUrl getFullAvatarURLFromPreferences() const { return _fullAvatarURLFromPreferences; }
|
Q_INVOKABLE QUrl getFullAvatarURLFromPreferences() const { return _fullAvatarURLFromPreferences; }
|
||||||
Q_INVOKABLE QString getFullAvatarModelName() const { return _fullAvatarModelName; }
|
Q_INVOKABLE QString getFullAvatarModelName() const { return _fullAvatarModelName; }
|
||||||
|
@ -527,6 +533,8 @@ private:
|
||||||
bool didTeleport();
|
bool didTeleport();
|
||||||
bool getIsAway() const { return _isAway; }
|
bool getIsAway() const { return _isAway; }
|
||||||
void setAway(bool value);
|
void setAway(bool value);
|
||||||
|
|
||||||
|
std::vector<int> _pinnedJoints;
|
||||||
};
|
};
|
||||||
|
|
||||||
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);
|
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);
|
||||||
|
|
|
@ -166,7 +166,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
||||||
_rig->computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState);
|
_rig->computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState);
|
||||||
|
|
||||||
// evaluate AnimGraph animation and update jointStates.
|
// evaluate AnimGraph animation and update jointStates.
|
||||||
CauterizedModel::updateRig(deltaTime, parentTransform);
|
Model::updateRig(deltaTime, parentTransform);
|
||||||
|
|
||||||
Rig::EyeParameters eyeParams;
|
Rig::EyeParameters eyeParams;
|
||||||
eyeParams.worldHeadOrientation = headParams.worldHeadOrientation;
|
eyeParams.worldHeadOrientation = headParams.worldHeadOrientation;
|
||||||
|
@ -179,7 +179,9 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
||||||
|
|
||||||
_rig->updateFromEyeParameters(eyeParams);
|
_rig->updateFromEyeParameters(eyeParams);
|
||||||
} else {
|
} else {
|
||||||
CauterizedModel::updateRig(deltaTime, parentTransform);
|
// no need to call Model::updateRig() because otherAvatars get their joint state
|
||||||
|
// copied directly from AvtarData::_jointData (there are no Rig animations to blend)
|
||||||
|
_needsUpdateClusterMatrices = true;
|
||||||
|
|
||||||
// This is a little more work than we really want.
|
// This is a little more work than we really want.
|
||||||
//
|
//
|
||||||
|
|
|
@ -23,10 +23,15 @@ Line3DOverlay::Line3DOverlay() :
|
||||||
|
|
||||||
Line3DOverlay::Line3DOverlay(const Line3DOverlay* line3DOverlay) :
|
Line3DOverlay::Line3DOverlay(const Line3DOverlay* line3DOverlay) :
|
||||||
Base3DOverlay(line3DOverlay),
|
Base3DOverlay(line3DOverlay),
|
||||||
_start(line3DOverlay->_start),
|
|
||||||
_end(line3DOverlay->_end),
|
|
||||||
_geometryCacheID(DependencyManager::get<GeometryCache>()->allocateID())
|
_geometryCacheID(DependencyManager::get<GeometryCache>()->allocateID())
|
||||||
{
|
{
|
||||||
|
setParentID(line3DOverlay->getParentID());
|
||||||
|
setParentJointIndex(line3DOverlay->getParentJointIndex());
|
||||||
|
setLocalTransform(line3DOverlay->getLocalTransform());
|
||||||
|
_direction = line3DOverlay->getDirection();
|
||||||
|
_length = line3DOverlay->getLength();
|
||||||
|
_endParentID = line3DOverlay->getEndParentID();
|
||||||
|
_endParentJointIndex = line3DOverlay->getEndJointIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
Line3DOverlay::~Line3DOverlay() {
|
Line3DOverlay::~Line3DOverlay() {
|
||||||
|
@ -37,17 +42,23 @@ Line3DOverlay::~Line3DOverlay() {
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec3 Line3DOverlay::getStart() const {
|
glm::vec3 Line3DOverlay::getStart() const {
|
||||||
bool success;
|
return getPosition();
|
||||||
glm::vec3 worldStart = localToWorld(_start, getParentID(), getParentJointIndex(), success);
|
|
||||||
if (!success) {
|
|
||||||
qDebug() << "Line3DOverlay::getStart failed";
|
|
||||||
}
|
|
||||||
return worldStart;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec3 Line3DOverlay::getEnd() const {
|
glm::vec3 Line3DOverlay::getEnd() const {
|
||||||
bool success;
|
bool success;
|
||||||
glm::vec3 worldEnd = localToWorld(_end, getParentID(), getParentJointIndex(), success);
|
glm::vec3 localEnd;
|
||||||
|
glm::vec3 worldEnd;
|
||||||
|
|
||||||
|
if (_endParentID != QUuid()) {
|
||||||
|
glm::vec3 localOffset = _direction * _length;
|
||||||
|
bool success;
|
||||||
|
worldEnd = localToWorld(localOffset, _endParentID, _endParentJointIndex, success);
|
||||||
|
return worldEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
localEnd = getLocalEnd();
|
||||||
|
worldEnd = localToWorld(localEnd, getParentID(), getParentJointIndex(), success);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
qDebug() << "Line3DOverlay::getEnd failed";
|
qDebug() << "Line3DOverlay::getEnd failed";
|
||||||
}
|
}
|
||||||
|
@ -55,27 +66,55 @@ glm::vec3 Line3DOverlay::getEnd() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Line3DOverlay::setStart(const glm::vec3& start) {
|
void Line3DOverlay::setStart(const glm::vec3& start) {
|
||||||
bool success;
|
setPosition(start);
|
||||||
_start = worldToLocal(start, getParentID(), getParentJointIndex(), success);
|
|
||||||
if (!success) {
|
|
||||||
qDebug() << "Line3DOverlay::setStart failed";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Line3DOverlay::setEnd(const glm::vec3& end) {
|
void Line3DOverlay::setEnd(const glm::vec3& end) {
|
||||||
bool success;
|
bool success;
|
||||||
_end = worldToLocal(end, getParentID(), getParentJointIndex(), success);
|
glm::vec3 localStart;
|
||||||
|
glm::vec3 localEnd;
|
||||||
|
glm::vec3 offset;
|
||||||
|
|
||||||
|
if (_endParentID != QUuid()) {
|
||||||
|
offset = worldToLocal(end, _endParentID, _endParentJointIndex, success);
|
||||||
|
} else {
|
||||||
|
localStart = getLocalStart();
|
||||||
|
localEnd = worldToLocal(end, getParentID(), getParentJointIndex(), success);
|
||||||
|
offset = localEnd - localStart;
|
||||||
|
}
|
||||||
if (!success) {
|
if (!success) {
|
||||||
qDebug() << "Line3DOverlay::setEnd failed";
|
qDebug() << "Line3DOverlay::setEnd failed";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_length = glm::length(offset);
|
||||||
|
if (_length > 0.0f) {
|
||||||
|
_direction = glm::normalize(offset);
|
||||||
|
} else {
|
||||||
|
_direction = glm::vec3(0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Line3DOverlay::setLocalEnd(const glm::vec3& localEnd) {
|
||||||
|
glm::vec3 offset;
|
||||||
|
if (_endParentID != QUuid()) {
|
||||||
|
offset = localEnd;
|
||||||
|
} else {
|
||||||
|
glm::vec3 localStart = getLocalStart();
|
||||||
|
offset = localEnd - localStart;
|
||||||
|
}
|
||||||
|
_length = glm::length(offset);
|
||||||
|
if (_length > 0.0f) {
|
||||||
|
_direction = glm::normalize(offset);
|
||||||
|
} else {
|
||||||
|
_direction = glm::vec3(0.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AABox Line3DOverlay::getBounds() const {
|
AABox Line3DOverlay::getBounds() const {
|
||||||
auto extents = Extents{};
|
auto extents = Extents{};
|
||||||
extents.addPoint(_start);
|
extents.addPoint(getStart());
|
||||||
extents.addPoint(_end);
|
extents.addPoint(getEnd());
|
||||||
extents.transform(getTransform());
|
|
||||||
|
|
||||||
return AABox(extents);
|
return AABox(extents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,18 +129,20 @@ void Line3DOverlay::render(RenderArgs* args) {
|
||||||
glm::vec4 colorv4(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha);
|
glm::vec4 colorv4(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha);
|
||||||
auto batch = args->_batch;
|
auto batch = args->_batch;
|
||||||
if (batch) {
|
if (batch) {
|
||||||
batch->setModelTransform(getTransform());
|
batch->setModelTransform(Transform());
|
||||||
|
glm::vec3 start = getStart();
|
||||||
|
glm::vec3 end = getEnd();
|
||||||
|
|
||||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||||
if (getIsDashedLine()) {
|
if (getIsDashedLine()) {
|
||||||
// TODO: add support for color to renderDashedLine()
|
// TODO: add support for color to renderDashedLine()
|
||||||
geometryCache->bindSimpleProgram(*batch, false, false, false, true, true);
|
geometryCache->bindSimpleProgram(*batch, false, false, false, true, true);
|
||||||
geometryCache->renderDashedLine(*batch, _start, _end, colorv4, _geometryCacheID);
|
geometryCache->renderDashedLine(*batch, start, end, colorv4, _geometryCacheID);
|
||||||
} else if (_glow > 0.0f) {
|
} else if (_glow > 0.0f) {
|
||||||
geometryCache->renderGlowLine(*batch, _start, _end, colorv4, _glow, _glowWidth, _geometryCacheID);
|
geometryCache->renderGlowLine(*batch, start, end, colorv4, _glow, _glowWidth, _geometryCacheID);
|
||||||
} else {
|
} else {
|
||||||
geometryCache->bindSimpleProgram(*batch, false, false, false, true, true);
|
geometryCache->bindSimpleProgram(*batch, false, false, false, true, true);
|
||||||
geometryCache->renderLine(*batch, _start, _end, colorv4, _geometryCacheID);
|
geometryCache->renderLine(*batch, start, end, colorv4, _geometryCacheID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,6 +157,10 @@ const render::ShapeKey Line3DOverlay::getShapeKey() {
|
||||||
|
|
||||||
void Line3DOverlay::setProperties(const QVariantMap& originalProperties) {
|
void Line3DOverlay::setProperties(const QVariantMap& originalProperties) {
|
||||||
QVariantMap properties = originalProperties;
|
QVariantMap properties = originalProperties;
|
||||||
|
glm::vec3 newStart(0.0f);
|
||||||
|
bool newStartSet { false };
|
||||||
|
glm::vec3 newEnd(0.0f);
|
||||||
|
bool newEndSet { false };
|
||||||
|
|
||||||
auto start = properties["start"];
|
auto start = properties["start"];
|
||||||
// if "start" property was not there, check to see if they included aliases: startPoint
|
// if "start" property was not there, check to see if they included aliases: startPoint
|
||||||
|
@ -123,30 +168,57 @@ void Line3DOverlay::setProperties(const QVariantMap& originalProperties) {
|
||||||
start = properties["startPoint"];
|
start = properties["startPoint"];
|
||||||
}
|
}
|
||||||
if (start.isValid()) {
|
if (start.isValid()) {
|
||||||
setStart(vec3FromVariant(start));
|
newStart = vec3FromVariant(start);
|
||||||
|
newStartSet = true;
|
||||||
}
|
}
|
||||||
properties.remove("start"); // so that Base3DOverlay doesn't respond to it
|
properties.remove("start"); // so that Base3DOverlay doesn't respond to it
|
||||||
|
|
||||||
auto localStart = properties["localStart"];
|
|
||||||
if (localStart.isValid()) {
|
|
||||||
_start = vec3FromVariant(localStart);
|
|
||||||
}
|
|
||||||
properties.remove("localStart"); // so that Base3DOverlay doesn't respond to it
|
|
||||||
|
|
||||||
auto end = properties["end"];
|
auto end = properties["end"];
|
||||||
// if "end" property was not there, check to see if they included aliases: endPoint
|
// if "end" property was not there, check to see if they included aliases: endPoint
|
||||||
if (!end.isValid()) {
|
if (!end.isValid()) {
|
||||||
end = properties["endPoint"];
|
end = properties["endPoint"];
|
||||||
}
|
}
|
||||||
if (end.isValid()) {
|
if (end.isValid()) {
|
||||||
setEnd(vec3FromVariant(end));
|
newEnd = vec3FromVariant(end);
|
||||||
|
newEndSet = true;
|
||||||
|
}
|
||||||
|
properties.remove("end"); // so that Base3DOverlay doesn't respond to it
|
||||||
|
|
||||||
|
auto length = properties["length"];
|
||||||
|
if (length.isValid()) {
|
||||||
|
_length = length.toFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
Base3DOverlay::setProperties(properties);
|
||||||
|
|
||||||
|
auto endParentIDProp = properties["endParentID"];
|
||||||
|
if (endParentIDProp.isValid()) {
|
||||||
|
_endParentID = QUuid(endParentIDProp.toString());
|
||||||
|
}
|
||||||
|
auto endParentJointIndexProp = properties["endParentJointIndex"];
|
||||||
|
if (endParentJointIndexProp.isValid()) {
|
||||||
|
_endParentJointIndex = endParentJointIndexProp.toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto localStart = properties["localStart"];
|
||||||
|
if (localStart.isValid()) {
|
||||||
|
glm::vec3 tmpLocalEnd = getLocalEnd();
|
||||||
|
setLocalStart(vec3FromVariant(localStart));
|
||||||
|
setLocalEnd(tmpLocalEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto localEnd = properties["localEnd"];
|
auto localEnd = properties["localEnd"];
|
||||||
if (localEnd.isValid()) {
|
if (localEnd.isValid()) {
|
||||||
_end = vec3FromVariant(localEnd);
|
setLocalEnd(vec3FromVariant(localEnd));
|
||||||
|
}
|
||||||
|
|
||||||
|
// these are saved until after Base3DOverlay::setProperties so parenting infomation can be set, first
|
||||||
|
if (newStartSet) {
|
||||||
|
setStart(newStart);
|
||||||
|
}
|
||||||
|
if (newEndSet) {
|
||||||
|
setEnd(newEnd);
|
||||||
}
|
}
|
||||||
properties.remove("localEnd"); // so that Base3DOverlay doesn't respond to it
|
|
||||||
|
|
||||||
auto glow = properties["glow"];
|
auto glow = properties["glow"];
|
||||||
if (glow.isValid()) {
|
if (glow.isValid()) {
|
||||||
|
@ -161,7 +233,6 @@ void Line3DOverlay::setProperties(const QVariantMap& originalProperties) {
|
||||||
setGlow(glowWidth.toFloat());
|
setGlow(glowWidth.toFloat());
|
||||||
}
|
}
|
||||||
|
|
||||||
Base3DOverlay::setProperties(properties);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant Line3DOverlay::getProperty(const QString& property) {
|
QVariant Line3DOverlay::getProperty(const QString& property) {
|
||||||
|
@ -171,6 +242,15 @@ QVariant Line3DOverlay::getProperty(const QString& property) {
|
||||||
if (property == "end" || property == "endPoint" || property == "p2") {
|
if (property == "end" || property == "endPoint" || property == "p2") {
|
||||||
return vec3toVariant(getEnd());
|
return vec3toVariant(getEnd());
|
||||||
}
|
}
|
||||||
|
if (property == "localStart") {
|
||||||
|
return vec3toVariant(getLocalStart());
|
||||||
|
}
|
||||||
|
if (property == "localEnd") {
|
||||||
|
return vec3toVariant(getLocalEnd());
|
||||||
|
}
|
||||||
|
if (property == "length") {
|
||||||
|
return QVariant(getLength());
|
||||||
|
}
|
||||||
|
|
||||||
return Base3DOverlay::getProperty(property);
|
return Base3DOverlay::getProperty(property);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
class Line3DOverlay : public Base3DOverlay {
|
class Line3DOverlay : public Base3DOverlay {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static QString const TYPE;
|
static QString const TYPE;
|
||||||
virtual QString getType() const override { return TYPE; }
|
virtual QString getType() const override { return TYPE; }
|
||||||
|
@ -37,6 +37,9 @@ public:
|
||||||
void setStart(const glm::vec3& start);
|
void setStart(const glm::vec3& start);
|
||||||
void setEnd(const glm::vec3& end);
|
void setEnd(const glm::vec3& end);
|
||||||
|
|
||||||
|
void setLocalStart(const glm::vec3& localStart) { setLocalPosition(localStart); }
|
||||||
|
void setLocalEnd(const glm::vec3& localEnd);
|
||||||
|
|
||||||
void setGlow(const float& glow) { _glow = glow; }
|
void setGlow(const float& glow) { _glow = glow; }
|
||||||
void setGlowWidth(const float& glowWidth) { _glowWidth = glowWidth; }
|
void setGlowWidth(const float& glowWidth) { _glowWidth = glowWidth; }
|
||||||
|
|
||||||
|
@ -47,13 +50,26 @@ public:
|
||||||
|
|
||||||
virtual void locationChanged(bool tellPhysics = true) override;
|
virtual void locationChanged(bool tellPhysics = true) override;
|
||||||
|
|
||||||
protected:
|
glm::vec3 getDirection() const { return _direction; }
|
||||||
glm::vec3 _start;
|
float getLength() const { return _length; }
|
||||||
glm::vec3 _end;
|
glm::vec3 getLocalStart() const { return getLocalPosition(); }
|
||||||
|
glm::vec3 getLocalEnd() const { return getLocalStart() + _direction * _length; }
|
||||||
|
QUuid getEndParentID() const { return _endParentID; }
|
||||||
|
quint16 getEndJointIndex() const { return _endParentJointIndex; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
QUuid _endParentID;
|
||||||
|
quint16 _endParentJointIndex { INVALID_JOINT_INDEX };
|
||||||
|
|
||||||
|
// _direction and _length are in the parent's frame. If _endParentID is set, they are
|
||||||
|
// relative to that. Otherwise, they are relative to the local-start-position (which is the
|
||||||
|
// same as localPosition)
|
||||||
|
glm::vec3 _direction; // in parent frame
|
||||||
|
float _length { 1.0 }; // in parent frame
|
||||||
|
|
||||||
float _glow { 0.0 };
|
float _glow { 0.0 };
|
||||||
float _glowWidth { 0.0 };
|
float _glowWidth { 0.0 };
|
||||||
int _geometryCacheID;
|
int _geometryCacheID;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif // hifi_Line3DOverlay_h
|
#endif // hifi_Line3DOverlay_h
|
||||||
|
|
|
@ -189,6 +189,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_maxErrorOnLastSolve = maxError;
|
||||||
|
|
||||||
// finally set the relative rotation of each tip to agree with absolute target rotation
|
// finally set the relative rotation of each tip to agree with absolute target rotation
|
||||||
for (auto& target: targets) {
|
for (auto& target: targets) {
|
||||||
|
@ -268,13 +269,13 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
|
||||||
|
|
||||||
glm::quat deltaRotation;
|
glm::quat deltaRotation;
|
||||||
if (targetType == IKTarget::Type::RotationAndPosition ||
|
if (targetType == IKTarget::Type::RotationAndPosition ||
|
||||||
targetType == IKTarget::Type::HipsRelativeRotationAndPosition) {
|
targetType == IKTarget::Type::HipsRelativeRotationAndPosition) {
|
||||||
// compute the swing that would get get tip closer
|
// compute the swing that would get get tip closer
|
||||||
glm::vec3 targetLine = target.getTranslation() - jointPosition;
|
glm::vec3 targetLine = target.getTranslation() - jointPosition;
|
||||||
|
|
||||||
const float MIN_AXIS_LENGTH = 1.0e-4f;
|
const float MIN_AXIS_LENGTH = 1.0e-4f;
|
||||||
RotationConstraint* constraint = getConstraint(pivotIndex);
|
RotationConstraint* constraint = getConstraint(pivotIndex);
|
||||||
if (constraint && constraint->isLowerSpine()) {
|
if (constraint && constraint->isLowerSpine() && tipIndex != _headIndex) {
|
||||||
// for these types of targets we only allow twist at the lower-spine
|
// for these types of targets we only allow twist at the lower-spine
|
||||||
// (this prevents the hand targets from bending the spine too much and thereby driving the hips too far)
|
// (this prevents the hand targets from bending the spine too much and thereby driving the hips too far)
|
||||||
glm::vec3 twistAxis = absolutePoses[pivotIndex].trans() - absolutePoses[pivotsParentIndex].trans();
|
glm::vec3 twistAxis = absolutePoses[pivotIndex].trans() - absolutePoses[pivotsParentIndex].trans();
|
||||||
|
@ -300,8 +301,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
|
||||||
const float MIN_ADJUSTMENT_ANGLE = 1.0e-4f;
|
const float MIN_ADJUSTMENT_ANGLE = 1.0e-4f;
|
||||||
if (angle > MIN_ADJUSTMENT_ANGLE) {
|
if (angle > MIN_ADJUSTMENT_ANGLE) {
|
||||||
// reduce angle by a fraction (for stability)
|
// reduce angle by a fraction (for stability)
|
||||||
const float FRACTION = 0.5f;
|
const float STABILITY_FRACTION = 0.5f;
|
||||||
angle *= FRACTION;
|
angle *= STABILITY_FRACTION;
|
||||||
deltaRotation = glm::angleAxis(angle, axis);
|
deltaRotation = glm::angleAxis(angle, axis);
|
||||||
|
|
||||||
// The swing will re-orient the tip but there will tend to be be a non-zero delta between the tip's
|
// The swing will re-orient the tip but there will tend to be be a non-zero delta between the tip's
|
||||||
|
@ -323,7 +324,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
|
||||||
glm::vec3 axis = glm::normalize(deltaRotation * leverArm);
|
glm::vec3 axis = glm::normalize(deltaRotation * leverArm);
|
||||||
swingTwistDecomposition(missingRotation, axis, swingPart, twistPart);
|
swingTwistDecomposition(missingRotation, axis, swingPart, twistPart);
|
||||||
float dotSign = copysignf(1.0f, twistPart.w);
|
float dotSign = copysignf(1.0f, twistPart.w);
|
||||||
deltaRotation = glm::normalize(glm::lerp(glm::quat(), dotSign * twistPart, FRACTION)) * deltaRotation;
|
const float LIMIT_LEAK_FRACTION = 0.1f;
|
||||||
|
deltaRotation = glm::normalize(glm::lerp(glm::quat(), dotSign * twistPart, LIMIT_LEAK_FRACTION)) * deltaRotation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -486,7 +488,13 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
||||||
// measure new _hipsOffset for next frame
|
// measure new _hipsOffset for next frame
|
||||||
// by looking for discrepancies between where a targeted endEffector is
|
// by looking for discrepancies between where a targeted endEffector is
|
||||||
// and where it wants to be (after IK solutions are done)
|
// and where it wants to be (after IK solutions are done)
|
||||||
glm::vec3 newHipsOffset = Vectors::ZERO;
|
|
||||||
|
// use weighted average between HMD and other targets
|
||||||
|
float HMD_WEIGHT = 10.0f;
|
||||||
|
float OTHER_WEIGHT = 1.0f;
|
||||||
|
float totalWeight = 0.0f;
|
||||||
|
|
||||||
|
glm::vec3 additionalHipsOffset = Vectors::ZERO;
|
||||||
for (auto& target: targets) {
|
for (auto& target: targets) {
|
||||||
int targetIndex = target.getIndex();
|
int targetIndex = target.getIndex();
|
||||||
if (targetIndex == _headIndex && _headIndex != -1) {
|
if (targetIndex == _headIndex && _headIndex != -1) {
|
||||||
|
@ -497,32 +505,61 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
||||||
glm::vec3 under = _skeleton->getAbsolutePose(_headIndex, underPoses).trans();
|
glm::vec3 under = _skeleton->getAbsolutePose(_headIndex, underPoses).trans();
|
||||||
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans();
|
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans();
|
||||||
const float HEAD_OFFSET_SLAVE_FACTOR = 0.65f;
|
const float HEAD_OFFSET_SLAVE_FACTOR = 0.65f;
|
||||||
newHipsOffset += HEAD_OFFSET_SLAVE_FACTOR * (actual - under);
|
additionalHipsOffset += (OTHER_WEIGHT * HEAD_OFFSET_SLAVE_FACTOR) * (under- actual);
|
||||||
|
totalWeight += OTHER_WEIGHT;
|
||||||
} else if (target.getType() == IKTarget::Type::HmdHead) {
|
} else if (target.getType() == IKTarget::Type::HmdHead) {
|
||||||
// we want to shift the hips to bring the head to its designated position
|
|
||||||
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans();
|
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans();
|
||||||
_hipsOffset += target.getTranslation() - actual;
|
glm::vec3 thisOffset = target.getTranslation() - actual;
|
||||||
// and ignore all other targets
|
glm::vec3 futureHipsOffset = _hipsOffset + thisOffset;
|
||||||
newHipsOffset = _hipsOffset;
|
if (glm::length(glm::vec2(futureHipsOffset.x, futureHipsOffset.z)) < _maxHipsOffsetLength) {
|
||||||
break;
|
// it is imperative to shift the hips and bring the head to its designated position
|
||||||
|
// so we slam newHipsOffset here and ignore all other targets
|
||||||
|
additionalHipsOffset = futureHipsOffset - _hipsOffset;
|
||||||
|
totalWeight = 0.0f;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
additionalHipsOffset += HMD_WEIGHT * (target.getTranslation() - actual);
|
||||||
|
totalWeight += HMD_WEIGHT;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (target.getType() == IKTarget::Type::RotationAndPosition) {
|
} else if (target.getType() == IKTarget::Type::RotationAndPosition) {
|
||||||
glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans();
|
glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans();
|
||||||
glm::vec3 targetPosition = target.getTranslation();
|
glm::vec3 targetPosition = target.getTranslation();
|
||||||
newHipsOffset += targetPosition - actualPosition;
|
additionalHipsOffset += OTHER_WEIGHT * (targetPosition - actualPosition);
|
||||||
|
totalWeight += OTHER_WEIGHT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (totalWeight > 1.0f) {
|
||||||
|
additionalHipsOffset /= totalWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add downward pressure on the hips
|
||||||
|
additionalHipsOffset *= 0.95f;
|
||||||
|
additionalHipsOffset -= 1.0f;
|
||||||
|
|
||||||
// smooth transitions by relaxing _hipsOffset toward the new value
|
// smooth transitions by relaxing _hipsOffset toward the new value
|
||||||
const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.15f;
|
const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.10f;
|
||||||
float tau = dt < HIPS_OFFSET_SLAVE_TIMESCALE ? dt / HIPS_OFFSET_SLAVE_TIMESCALE : 1.0f;
|
float tau = dt < HIPS_OFFSET_SLAVE_TIMESCALE ? dt / HIPS_OFFSET_SLAVE_TIMESCALE : 1.0f;
|
||||||
_hipsOffset += (newHipsOffset - _hipsOffset) * tau;
|
_hipsOffset += additionalHipsOffset * tau;
|
||||||
|
|
||||||
|
// clamp the hips offset
|
||||||
|
float hipsOffsetLength = glm::length(_hipsOffset);
|
||||||
|
if (hipsOffsetLength > _maxHipsOffsetLength) {
|
||||||
|
_hipsOffset *= _maxHipsOffsetLength / hipsOffsetLength;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return _relativePoses;
|
return _relativePoses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AnimInverseKinematics::setMaxHipsOffsetLength(float maxLength) {
|
||||||
|
// manually adjust scale here
|
||||||
|
const float METERS_TO_CENTIMETERS = 100.0f;
|
||||||
|
_maxHipsOffsetLength = METERS_TO_CENTIMETERS * maxLength;
|
||||||
|
}
|
||||||
|
|
||||||
void AnimInverseKinematics::clearIKJointLimitHistory() {
|
void AnimInverseKinematics::clearIKJointLimitHistory() {
|
||||||
for (auto& pair : _constraints) {
|
for (auto& pair : _constraints) {
|
||||||
pair.second->clearHistory();
|
pair.second->clearHistory();
|
||||||
|
@ -740,7 +777,7 @@ void AnimInverseKinematics::initConstraints() {
|
||||||
stConstraint->setTwistLimits(-MAX_SPINE_TWIST, MAX_SPINE_TWIST);
|
stConstraint->setTwistLimits(-MAX_SPINE_TWIST, MAX_SPINE_TWIST);
|
||||||
|
|
||||||
std::vector<float> minDots;
|
std::vector<float> minDots;
|
||||||
const float MAX_SPINE_SWING = PI / 14.0f;
|
const float MAX_SPINE_SWING = PI / 10.0f;
|
||||||
minDots.push_back(cosf(MAX_SPINE_SWING));
|
minDots.push_back(cosf(MAX_SPINE_SWING));
|
||||||
stConstraint->setSwingLimits(minDots);
|
stConstraint->setSwingLimits(minDots);
|
||||||
if (0 == baseName.compare("Spine1", Qt::CaseSensitive)
|
if (0 == baseName.compare("Spine1", Qt::CaseSensitive)
|
||||||
|
@ -776,11 +813,11 @@ void AnimInverseKinematics::initConstraints() {
|
||||||
} else if (0 == baseName.compare("Head", Qt::CaseSensitive)) {
|
} else if (0 == baseName.compare("Head", Qt::CaseSensitive)) {
|
||||||
SwingTwistConstraint* stConstraint = new SwingTwistConstraint();
|
SwingTwistConstraint* stConstraint = new SwingTwistConstraint();
|
||||||
stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot());
|
stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot());
|
||||||
const float MAX_HEAD_TWIST = PI / 9.0f;
|
const float MAX_HEAD_TWIST = PI / 6.0f;
|
||||||
stConstraint->setTwistLimits(-MAX_HEAD_TWIST, MAX_HEAD_TWIST);
|
stConstraint->setTwistLimits(-MAX_HEAD_TWIST, MAX_HEAD_TWIST);
|
||||||
|
|
||||||
std::vector<float> minDots;
|
std::vector<float> minDots;
|
||||||
const float MAX_HEAD_SWING = PI / 10.0f;
|
const float MAX_HEAD_SWING = PI / 6.0f;
|
||||||
minDots.push_back(cosf(MAX_HEAD_SWING));
|
minDots.push_back(cosf(MAX_HEAD_SWING));
|
||||||
stConstraint->setSwingLimits(minDots);
|
stConstraint->setSwingLimits(minDots);
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,10 @@ public:
|
||||||
|
|
||||||
void clearIKJointLimitHistory();
|
void clearIKJointLimitHistory();
|
||||||
|
|
||||||
|
void setMaxHipsOffsetLength(float maxLength);
|
||||||
|
|
||||||
|
float getMaxErrorOnLastSolve() { return _maxErrorOnLastSolve; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses);
|
void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses);
|
||||||
void solveWithCyclicCoordinateDescent(const std::vector<IKTarget>& targets);
|
void solveWithCyclicCoordinateDescent(const std::vector<IKTarget>& targets);
|
||||||
|
@ -83,6 +87,7 @@ protected:
|
||||||
|
|
||||||
// experimental data for moving hips during IK
|
// experimental data for moving hips during IK
|
||||||
glm::vec3 _hipsOffset { Vectors::ZERO };
|
glm::vec3 _hipsOffset { Vectors::ZERO };
|
||||||
|
float _maxHipsOffsetLength{ FLT_MAX };
|
||||||
int _headIndex { -1 };
|
int _headIndex { -1 };
|
||||||
int _hipsIndex { -1 };
|
int _hipsIndex { -1 };
|
||||||
int _hipsParentIndex { -1 };
|
int _hipsParentIndex { -1 };
|
||||||
|
@ -90,6 +95,8 @@ protected:
|
||||||
// _maxTargetIndex is tracked to help optimize the recalculation of absolute poses
|
// _maxTargetIndex is tracked to help optimize the recalculation of absolute poses
|
||||||
// during the the cyclic coordinate descent algorithm
|
// during the the cyclic coordinate descent algorithm
|
||||||
int _maxTargetIndex { 0 };
|
int _maxTargetIndex { 0 };
|
||||||
|
|
||||||
|
float _maxErrorOnLastSolve { FLT_MAX };
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_AnimInverseKinematics_h
|
#endif // hifi_AnimInverseKinematics_h
|
||||||
|
|
|
@ -319,6 +319,39 @@ void Rig::clearIKJointLimitHistory() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Rig::setMaxHipsOffsetLength(float maxLength) {
|
||||||
|
_maxHipsOffsetLength = maxLength;
|
||||||
|
|
||||||
|
if (_animNode) {
|
||||||
|
_animNode->traverse([&](AnimNode::Pointer node) {
|
||||||
|
auto ikNode = std::dynamic_pointer_cast<AnimInverseKinematics>(node);
|
||||||
|
if (ikNode) {
|
||||||
|
ikNode->setMaxHipsOffsetLength(_maxHipsOffsetLength);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float Rig::getMaxHipsOffsetLength() const {
|
||||||
|
return _maxHipsOffsetLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Rig::getIKErrorOnLastSolve() const {
|
||||||
|
float result = 0.0f;
|
||||||
|
|
||||||
|
if (_animNode) {
|
||||||
|
_animNode->traverse([&](AnimNode::Pointer node) {
|
||||||
|
auto ikNode = std::dynamic_pointer_cast<AnimInverseKinematics>(node);
|
||||||
|
if (ikNode) {
|
||||||
|
result = ikNode->getMaxErrorOnLastSolve();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
int Rig::getJointParentIndex(int childIndex) const {
|
int Rig::getJointParentIndex(int childIndex) const {
|
||||||
if (_animSkeleton && isIndexValid(childIndex)) {
|
if (_animSkeleton && isIndexValid(childIndex)) {
|
||||||
return _animSkeleton->getParentIndex(childIndex);
|
return _animSkeleton->getParentIndex(childIndex);
|
||||||
|
@ -1274,39 +1307,55 @@ void Rig::copyJointsIntoJointData(QVector<JointData>& jointDataVec) const {
|
||||||
void Rig::copyJointsFromJointData(const QVector<JointData>& jointDataVec) {
|
void Rig::copyJointsFromJointData(const QVector<JointData>& jointDataVec) {
|
||||||
PerformanceTimer perfTimer("copyJoints");
|
PerformanceTimer perfTimer("copyJoints");
|
||||||
PROFILE_RANGE(simulation_animation_detail, "copyJoints");
|
PROFILE_RANGE(simulation_animation_detail, "copyJoints");
|
||||||
if (_animSkeleton && jointDataVec.size() == (int)_internalPoseSet._relativePoses.size()) {
|
if (!_animSkeleton) {
|
||||||
// make a vector of rotations in absolute-geometry-frame
|
return;
|
||||||
const AnimPoseVec& absoluteDefaultPoses = _animSkeleton->getAbsoluteDefaultPoses();
|
}
|
||||||
std::vector<glm::quat> rotations;
|
if (jointDataVec.size() != (int)_internalPoseSet._relativePoses.size()) {
|
||||||
rotations.reserve(absoluteDefaultPoses.size());
|
// animations haven't fully loaded yet.
|
||||||
const glm::quat rigToGeometryRot(glmExtractRotation(_rigToGeometryTransform));
|
_internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||||
for (int i = 0; i < jointDataVec.size(); i++) {
|
}
|
||||||
const JointData& data = jointDataVec.at(i);
|
|
||||||
if (data.rotationSet) {
|
|
||||||
// JointData rotations are in absolute rig-frame so we rotate them to absolute geometry-frame
|
|
||||||
rotations.push_back(rigToGeometryRot * data.rotation);
|
|
||||||
} else {
|
|
||||||
rotations.push_back(absoluteDefaultPoses[i].rot());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert rotations from absolute to parent relative.
|
// make a vector of rotations in absolute-geometry-frame
|
||||||
_animSkeleton->convertAbsoluteRotationsToRelative(rotations);
|
const AnimPoseVec& absoluteDefaultPoses = _animSkeleton->getAbsoluteDefaultPoses();
|
||||||
|
std::vector<glm::quat> rotations;
|
||||||
// store new relative poses
|
rotations.reserve(absoluteDefaultPoses.size());
|
||||||
const AnimPoseVec& relativeDefaultPoses = _animSkeleton->getRelativeDefaultPoses();
|
const glm::quat rigToGeometryRot(glmExtractRotation(_rigToGeometryTransform));
|
||||||
for (int i = 0; i < jointDataVec.size(); i++) {
|
for (int i = 0; i < jointDataVec.size(); i++) {
|
||||||
const JointData& data = jointDataVec.at(i);
|
const JointData& data = jointDataVec.at(i);
|
||||||
_internalPoseSet._relativePoses[i].scale() = Vectors::ONE;
|
if (data.rotationSet) {
|
||||||
_internalPoseSet._relativePoses[i].rot() = rotations[i];
|
// JointData rotations are in absolute rig-frame so we rotate them to absolute geometry-frame
|
||||||
if (data.translationSet) {
|
rotations.push_back(rigToGeometryRot * data.rotation);
|
||||||
// JointData translations are in scaled relative-frame so we scale back to regular relative-frame
|
} else {
|
||||||
_internalPoseSet._relativePoses[i].trans() = _invGeometryOffset.scale() * data.translation;
|
rotations.push_back(absoluteDefaultPoses[i].rot());
|
||||||
} else {
|
|
||||||
_internalPoseSet._relativePoses[i].trans() = relativeDefaultPoses[i].trans();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convert rotations from absolute to parent relative.
|
||||||
|
_animSkeleton->convertAbsoluteRotationsToRelative(rotations);
|
||||||
|
|
||||||
|
// store new relative poses
|
||||||
|
const AnimPoseVec& relativeDefaultPoses = _animSkeleton->getRelativeDefaultPoses();
|
||||||
|
for (int i = 0; i < jointDataVec.size(); i++) {
|
||||||
|
const JointData& data = jointDataVec.at(i);
|
||||||
|
_internalPoseSet._relativePoses[i].scale() = Vectors::ONE;
|
||||||
|
_internalPoseSet._relativePoses[i].rot() = rotations[i];
|
||||||
|
if (data.translationSet) {
|
||||||
|
// JointData translations are in scaled relative-frame so we scale back to regular relative-frame
|
||||||
|
_internalPoseSet._relativePoses[i].trans() = _invGeometryOffset.scale() * data.translation;
|
||||||
|
} else {
|
||||||
|
_internalPoseSet._relativePoses[i].trans() = relativeDefaultPoses[i].trans();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Rig::computeExternalPoses(const glm::mat4& modelOffsetMat) {
|
||||||
|
_modelOffset = AnimPose(modelOffsetMat);
|
||||||
|
_geometryToRigTransform = _modelOffset * _geometryOffset;
|
||||||
|
_rigToGeometryTransform = glm::inverse(_geometryToRigTransform);
|
||||||
|
|
||||||
|
buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses);
|
||||||
|
QWriteLocker writeLock(&_externalPoseSetLock);
|
||||||
|
_externalPoseSet = _internalPoseSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Rig::computeAvatarBoundingCapsule(
|
void Rig::computeAvatarBoundingCapsule(
|
||||||
|
|
|
@ -104,6 +104,10 @@ public:
|
||||||
void clearJointAnimationPriority(int index);
|
void clearJointAnimationPriority(int index);
|
||||||
|
|
||||||
void clearIKJointLimitHistory();
|
void clearIKJointLimitHistory();
|
||||||
|
void setMaxHipsOffsetLength(float maxLength);
|
||||||
|
float getMaxHipsOffsetLength() const;
|
||||||
|
|
||||||
|
float getIKErrorOnLastSolve() const;
|
||||||
|
|
||||||
int getJointParentIndex(int childIndex) const;
|
int getJointParentIndex(int childIndex) const;
|
||||||
|
|
||||||
|
@ -206,6 +210,7 @@ public:
|
||||||
|
|
||||||
void copyJointsIntoJointData(QVector<JointData>& jointDataVec) const;
|
void copyJointsIntoJointData(QVector<JointData>& jointDataVec) const;
|
||||||
void copyJointsFromJointData(const QVector<JointData>& jointDataVec);
|
void copyJointsFromJointData(const QVector<JointData>& jointDataVec);
|
||||||
|
void computeExternalPoses(const glm::mat4& modelOffsetMat);
|
||||||
|
|
||||||
void computeAvatarBoundingCapsule(const FBXGeometry& geometry, float& radiusOut, float& heightOut, glm::vec3& offsetOut) const;
|
void computeAvatarBoundingCapsule(const FBXGeometry& geometry, float& radiusOut, float& heightOut, glm::vec3& offsetOut) const;
|
||||||
|
|
||||||
|
@ -318,6 +323,8 @@ protected:
|
||||||
bool _enabledAnimations { true };
|
bool _enabledAnimations { true };
|
||||||
|
|
||||||
mutable uint32_t _jointNameWarningCount { 0 };
|
mutable uint32_t _jointNameWarningCount { 0 };
|
||||||
|
float _maxHipsOffsetLength { 1.0f };
|
||||||
|
float _maxErrorOnLastSolve { 0.0f };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QMap<int, StateHandler> _stateHandlers;
|
QMap<int, StateHandler> _stateHandlers;
|
||||||
|
|
|
@ -2324,61 +2324,57 @@ float AvatarData::_avatarSortCoefficientSize { 0.5f };
|
||||||
float AvatarData::_avatarSortCoefficientCenter { 0.25 };
|
float AvatarData::_avatarSortCoefficientCenter { 0.25 };
|
||||||
float AvatarData::_avatarSortCoefficientAge { 1.0f };
|
float AvatarData::_avatarSortCoefficientAge { 1.0f };
|
||||||
|
|
||||||
std::priority_queue<AvatarPriority> AvatarData::sortAvatars(
|
void AvatarData::sortAvatars(
|
||||||
QList<AvatarSharedPointer> avatarList,
|
QList<AvatarSharedPointer> avatarList,
|
||||||
const ViewFrustum& cameraView,
|
const ViewFrustum& cameraView,
|
||||||
std::function<uint64_t(AvatarSharedPointer)> getLastUpdated,
|
std::priority_queue<AvatarPriority>& sortedAvatarsOut,
|
||||||
std::function<float(AvatarSharedPointer)> getBoundingRadius,
|
std::function<uint64_t(AvatarSharedPointer)> getLastUpdated,
|
||||||
std::function<bool(AvatarSharedPointer)> shouldIgnore) {
|
std::function<float(AvatarSharedPointer)> getBoundingRadius,
|
||||||
|
std::function<bool(AvatarSharedPointer)> shouldIgnore) {
|
||||||
|
|
||||||
uint64_t startTime = usecTimestampNow();
|
PROFILE_RANGE(simulation, "sort");
|
||||||
|
uint64_t now = usecTimestampNow();
|
||||||
|
|
||||||
glm::vec3 frustumCenter = cameraView.getPosition();
|
glm::vec3 frustumCenter = cameraView.getPosition();
|
||||||
|
const glm::vec3& forward = cameraView.getDirection();
|
||||||
|
for (int32_t i = 0; i < avatarList.size(); ++i) {
|
||||||
|
const auto& avatar = avatarList.at(i);
|
||||||
|
|
||||||
std::priority_queue<AvatarPriority> sortedAvatars;
|
if (shouldIgnore(avatar)) {
|
||||||
{
|
continue;
|
||||||
PROFILE_RANGE(simulation, "sort");
|
|
||||||
for (int32_t i = 0; i < avatarList.size(); ++i) {
|
|
||||||
const auto& avatar = avatarList.at(i);
|
|
||||||
|
|
||||||
if (shouldIgnore(avatar)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// priority = weighted linear combination of:
|
|
||||||
// (a) apparentSize
|
|
||||||
// (b) proximity to center of view
|
|
||||||
// (c) time since last update
|
|
||||||
glm::vec3 avatarPosition = avatar->getPosition();
|
|
||||||
glm::vec3 offset = avatarPosition - frustumCenter;
|
|
||||||
float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero
|
|
||||||
|
|
||||||
// FIXME - AvatarData has something equivolent to this
|
|
||||||
float radius = getBoundingRadius(avatar);
|
|
||||||
|
|
||||||
const glm::vec3& forward = cameraView.getDirection();
|
|
||||||
float apparentSize = 2.0f * radius / distance;
|
|
||||||
float cosineAngle = glm::length(glm::dot(offset, forward) * forward) / distance;
|
|
||||||
float age = (float)(startTime - getLastUpdated(avatar)) / (float)(USECS_PER_SECOND);
|
|
||||||
|
|
||||||
// NOTE: we are adding values of different units to get a single measure of "priority".
|
|
||||||
// Thus we multiply each component by a conversion "weight" that scales its units relative to the others.
|
|
||||||
// These weights are pure magic tuning and should be hard coded in the relation below,
|
|
||||||
// but are currently exposed for anyone who would like to explore fine tuning:
|
|
||||||
float priority = _avatarSortCoefficientSize * apparentSize
|
|
||||||
+ _avatarSortCoefficientCenter * cosineAngle
|
|
||||||
+ _avatarSortCoefficientAge * age;
|
|
||||||
|
|
||||||
// decrement priority of avatars outside keyhole
|
|
||||||
if (distance > cameraView.getCenterRadius()) {
|
|
||||||
if (!cameraView.sphereIntersectsFrustum(avatarPosition, radius)) {
|
|
||||||
priority += OUT_OF_VIEW_PENALTY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sortedAvatars.push(AvatarPriority(avatar, priority));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// priority = weighted linear combination of:
|
||||||
|
// (a) apparentSize
|
||||||
|
// (b) proximity to center of view
|
||||||
|
// (c) time since last update
|
||||||
|
glm::vec3 avatarPosition = avatar->getPosition();
|
||||||
|
glm::vec3 offset = avatarPosition - frustumCenter;
|
||||||
|
float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero
|
||||||
|
|
||||||
|
// FIXME - AvatarData has something equivolent to this
|
||||||
|
float radius = getBoundingRadius(avatar);
|
||||||
|
|
||||||
|
float apparentSize = 2.0f * radius / distance;
|
||||||
|
float cosineAngle = glm::dot(offset, forward) / distance;
|
||||||
|
float age = (float)(now - getLastUpdated(avatar)) / (float)(USECS_PER_SECOND);
|
||||||
|
|
||||||
|
// NOTE: we are adding values of different units to get a single measure of "priority".
|
||||||
|
// Thus we multiply each component by a conversion "weight" that scales its units relative to the others.
|
||||||
|
// These weights are pure magic tuning and should be hard coded in the relation below,
|
||||||
|
// but are currently exposed for anyone who would like to explore fine tuning:
|
||||||
|
float priority = _avatarSortCoefficientSize * apparentSize
|
||||||
|
+ _avatarSortCoefficientCenter * cosineAngle
|
||||||
|
+ _avatarSortCoefficientAge * age;
|
||||||
|
|
||||||
|
// decrement priority of avatars outside keyhole
|
||||||
|
if (distance > cameraView.getCenterRadius()) {
|
||||||
|
if (!cameraView.sphereIntersectsFrustum(avatarPosition, radius)) {
|
||||||
|
priority += OUT_OF_VIEW_PENALTY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sortedAvatarsOut.push(AvatarPriority(avatar, priority));
|
||||||
}
|
}
|
||||||
return sortedAvatars;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value) {
|
QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value) {
|
||||||
|
|
|
@ -597,9 +597,10 @@ public:
|
||||||
|
|
||||||
static const float OUT_OF_VIEW_PENALTY;
|
static const float OUT_OF_VIEW_PENALTY;
|
||||||
|
|
||||||
static std::priority_queue<AvatarPriority> sortAvatars(
|
static void sortAvatars(
|
||||||
QList<AvatarSharedPointer> avatarList,
|
QList<AvatarSharedPointer> avatarList,
|
||||||
const ViewFrustum& cameraView,
|
const ViewFrustum& cameraView,
|
||||||
|
std::priority_queue<AvatarPriority>& sortedAvatarsOut,
|
||||||
std::function<uint64_t(AvatarSharedPointer)> getLastUpdated,
|
std::function<uint64_t(AvatarSharedPointer)> getLastUpdated,
|
||||||
std::function<float(AvatarSharedPointer)> getBoundingRadius,
|
std::function<float(AvatarSharedPointer)> getBoundingRadius,
|
||||||
std::function<bool(AvatarSharedPointer)> shouldIgnore);
|
std::function<bool(AvatarSharedPointer)> shouldIgnore);
|
||||||
|
|
|
@ -944,7 +944,10 @@ void EntityTreeRenderer::entityScriptChanging(const EntityItemID& entityID, cons
|
||||||
void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const bool reload, const bool unloadFirst) {
|
void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const bool reload, const bool unloadFirst) {
|
||||||
if (_tree && !_shuttingDown) {
|
if (_tree && !_shuttingDown) {
|
||||||
EntityItemPointer entity = getTree()->findEntityByEntityItemID(entityID);
|
EntityItemPointer entity = getTree()->findEntityByEntityItemID(entityID);
|
||||||
bool shouldLoad = entity && entity->shouldPreloadScript() && _entitiesScriptEngine;
|
if (!entity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool shouldLoad = entity->shouldPreloadScript() && _entitiesScriptEngine;
|
||||||
QString scriptUrl = entity->getScript();
|
QString scriptUrl = entity->getScript();
|
||||||
if ((unloadFirst && shouldLoad) || scriptUrl.isEmpty()) {
|
if ((unloadFirst && shouldLoad) || scriptUrl.isEmpty()) {
|
||||||
_entitiesScriptEngine->unloadEntityScript(entityID);
|
_entitiesScriptEngine->unloadEntityScript(entityID);
|
||||||
|
|
|
@ -266,6 +266,35 @@ void RenderablePolyVoxEntityItem::forEachVoxelValue(quint16 voxelXSize, quint16
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray RenderablePolyVoxEntityItem::volDataToArray(quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize) const {
|
||||||
|
int totalSize = voxelXSize * voxelYSize * voxelZSize;
|
||||||
|
QByteArray result = QByteArray(totalSize, '\0');
|
||||||
|
int index = 0;
|
||||||
|
int lowX = 0;
|
||||||
|
int lowY = 0;
|
||||||
|
int lowZ = 0;
|
||||||
|
|
||||||
|
withReadLock([&] {
|
||||||
|
if (isEdged(_voxelSurfaceStyle)) {
|
||||||
|
lowX++;
|
||||||
|
lowY++;
|
||||||
|
lowZ++;
|
||||||
|
voxelXSize++;
|
||||||
|
voxelYSize++;
|
||||||
|
voxelZSize++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int z = lowZ; z < voxelZSize; z++) {
|
||||||
|
for (int y = lowY; y < voxelYSize; y++) {
|
||||||
|
for (int x = lowX; x < voxelXSize; x++) {
|
||||||
|
result[index++] = _volData->getVoxelAt(x, y, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
bool RenderablePolyVoxEntityItem::setAll(uint8_t toValue) {
|
bool RenderablePolyVoxEntityItem::setAll(uint8_t toValue) {
|
||||||
bool result = false;
|
bool result = false;
|
||||||
|
@ -365,12 +394,28 @@ bool RenderablePolyVoxEntityItem::setSphere(glm::vec3 centerWorldCoords, float r
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::mat4 vtwMatrix = voxelToWorldMatrix();
|
glm::mat4 vtwMatrix = voxelToWorldMatrix();
|
||||||
|
glm::mat4 wtvMatrix = glm::inverse(vtwMatrix);
|
||||||
|
|
||||||
// This three-level for loop iterates over every voxel in the volume
|
glm::vec3 dimensions = getDimensions();
|
||||||
|
glm::vec3 voxelSize = dimensions / _voxelVolumeSize;
|
||||||
|
float smallestDimensionSize = voxelSize.x;
|
||||||
|
smallestDimensionSize = glm::min(smallestDimensionSize, voxelSize.y);
|
||||||
|
smallestDimensionSize = glm::min(smallestDimensionSize, voxelSize.z);
|
||||||
|
|
||||||
|
glm::vec3 maxRadiusInVoxelCoords = glm::vec3(radiusWorldCoords / smallestDimensionSize);
|
||||||
|
glm::vec3 centerInVoxelCoords = wtvMatrix * glm::vec4(centerWorldCoords, 1.0f);
|
||||||
|
|
||||||
|
glm::vec3 low = glm::floor(centerInVoxelCoords - maxRadiusInVoxelCoords);
|
||||||
|
glm::vec3 high = glm::ceil(centerInVoxelCoords + maxRadiusInVoxelCoords);
|
||||||
|
|
||||||
|
glm::ivec3 lowI = glm::clamp(low, glm::vec3(0.0f), _voxelVolumeSize);
|
||||||
|
glm::ivec3 highI = glm::clamp(high, glm::vec3(0.0f), _voxelVolumeSize);
|
||||||
|
|
||||||
|
// This three-level for loop iterates over every voxel in the volume that might be in the sphere
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
for (int z = 0; z < _voxelVolumeSize.z; z++) {
|
for (int z = lowI.z; z < highI.z; z++) {
|
||||||
for (int y = 0; y < _voxelVolumeSize.y; y++) {
|
for (int y = lowI.y; y < highI.y; y++) {
|
||||||
for (int x = 0; x < _voxelVolumeSize.x; x++) {
|
for (int x = lowI.x; x < highI.x; x++) {
|
||||||
// Store our current position as a vector...
|
// Store our current position as a vector...
|
||||||
glm::vec4 pos(x + 0.5f, y + 0.5f, z + 0.5f, 1.0); // consider voxels cenetered on their coordinates
|
glm::vec4 pos(x + 0.5f, y + 0.5f, z + 0.5f, 1.0); // consider voxels cenetered on their coordinates
|
||||||
// convert to world coordinates
|
// convert to world coordinates
|
||||||
|
@ -392,6 +437,59 @@ bool RenderablePolyVoxEntityItem::setSphere(glm::vec3 centerWorldCoords, float r
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RenderablePolyVoxEntityItem::setCapsule(glm::vec3 startWorldCoords, glm::vec3 endWorldCoords,
|
||||||
|
float radiusWorldCoords, uint8_t toValue) {
|
||||||
|
bool result = false;
|
||||||
|
if (_locked) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::mat4 vtwMatrix = voxelToWorldMatrix();
|
||||||
|
glm::mat4 wtvMatrix = glm::inverse(vtwMatrix);
|
||||||
|
|
||||||
|
glm::vec3 dimensions = getDimensions();
|
||||||
|
glm::vec3 voxelSize = dimensions / _voxelVolumeSize;
|
||||||
|
float smallestDimensionSize = voxelSize.x;
|
||||||
|
smallestDimensionSize = glm::min(smallestDimensionSize, voxelSize.y);
|
||||||
|
smallestDimensionSize = glm::min(smallestDimensionSize, voxelSize.z);
|
||||||
|
|
||||||
|
glm::vec3 maxRadiusInVoxelCoords = glm::vec3(radiusWorldCoords / smallestDimensionSize);
|
||||||
|
|
||||||
|
glm::vec3 startInVoxelCoords = wtvMatrix * glm::vec4(startWorldCoords, 1.0f);
|
||||||
|
glm::vec3 endInVoxelCoords = wtvMatrix * glm::vec4(endWorldCoords, 1.0f);
|
||||||
|
|
||||||
|
glm::vec3 low = glm::min(glm::floor(startInVoxelCoords - maxRadiusInVoxelCoords),
|
||||||
|
glm::floor(endInVoxelCoords - maxRadiusInVoxelCoords));
|
||||||
|
glm::vec3 high = glm::max(glm::ceil(startInVoxelCoords + maxRadiusInVoxelCoords),
|
||||||
|
glm::ceil(endInVoxelCoords + maxRadiusInVoxelCoords));
|
||||||
|
|
||||||
|
glm::ivec3 lowI = glm::clamp(low, glm::vec3(0.0f), _voxelVolumeSize);
|
||||||
|
glm::ivec3 highI = glm::clamp(high, glm::vec3(0.0f), _voxelVolumeSize);
|
||||||
|
|
||||||
|
// This three-level for loop iterates over every voxel in the volume that might be in the capsule
|
||||||
|
withWriteLock([&] {
|
||||||
|
for (int z = lowI.z; z < highI.z; z++) {
|
||||||
|
for (int y = lowI.y; y < highI.y; y++) {
|
||||||
|
for (int x = lowI.x; x < highI.x; x++) {
|
||||||
|
// Store our current position as a vector...
|
||||||
|
glm::vec4 pos(x + 0.5f, y + 0.5f, z + 0.5f, 1.0); // consider voxels cenetered on their coordinates
|
||||||
|
// convert to world coordinates
|
||||||
|
glm::vec3 worldPos = glm::vec3(vtwMatrix * pos);
|
||||||
|
if (pointInCapsule(worldPos, startWorldCoords, endWorldCoords, radiusWorldCoords)) {
|
||||||
|
result |= setVoxelInternal(x, y, z, toValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
compressVolumeDataAndSendEditPacket();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class RaycastFunctor
|
class RaycastFunctor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -501,6 +599,9 @@ PolyVox::RaycastResult RenderablePolyVoxEntityItem::doRayCast(glm::vec4 originIn
|
||||||
|
|
||||||
// virtual
|
// virtual
|
||||||
ShapeType RenderablePolyVoxEntityItem::getShapeType() const {
|
ShapeType RenderablePolyVoxEntityItem::getShapeType() const {
|
||||||
|
if (_collisionless) {
|
||||||
|
return SHAPE_TYPE_NONE;
|
||||||
|
}
|
||||||
return SHAPE_TYPE_COMPOUND;
|
return SHAPE_TYPE_COMPOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -512,6 +613,11 @@ void RenderablePolyVoxEntityItem::updateRegistrationPoint(const glm::vec3& value
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RenderablePolyVoxEntityItem::isReadyToComputeShape() {
|
bool RenderablePolyVoxEntityItem::isReadyToComputeShape() {
|
||||||
|
ShapeType shapeType = getShapeType();
|
||||||
|
if (shapeType == SHAPE_TYPE_NONE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// we determine if we are ready to compute the physics shape by actually doing so.
|
// we determine if we are ready to compute the physics shape by actually doing so.
|
||||||
// if _voxelDataDirty or _volDataDirty is set, don't do this yet -- wait for their
|
// if _voxelDataDirty or _volDataDirty is set, don't do this yet -- wait for their
|
||||||
// threads to finish before creating the collision shape.
|
// threads to finish before creating the collision shape.
|
||||||
|
@ -524,6 +630,12 @@ bool RenderablePolyVoxEntityItem::isReadyToComputeShape() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) {
|
void RenderablePolyVoxEntityItem::computeShapeInfo(ShapeInfo& info) {
|
||||||
|
ShapeType shapeType = getShapeType();
|
||||||
|
if (shapeType == SHAPE_TYPE_NONE) {
|
||||||
|
info.setParams(getShapeType(), 0.5f * getDimensions());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// the shape was actually computed in isReadyToComputeShape. Just hand it off, here.
|
// the shape was actually computed in isReadyToComputeShape. Just hand it off, here.
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
info = _shapeInfo;
|
info = _shapeInfo;
|
||||||
|
@ -736,7 +848,7 @@ glm::vec3 RenderablePolyVoxEntityItem::localCoordsToVoxelCoords(glm::vec3& local
|
||||||
|
|
||||||
void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) {
|
void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) {
|
||||||
// This controls how many individual voxels are in the entity. This is unrelated to
|
// This controls how many individual voxels are in the entity. This is unrelated to
|
||||||
// the dimentions of the entity -- it defines the size of the arrays that hold voxel values.
|
// the dimentions of the entity -- it defines the sizes of the arrays that hold voxel values.
|
||||||
// In addition to setting the number of voxels, this is used in a few places for its
|
// In addition to setting the number of voxels, this is used in a few places for its
|
||||||
// side-effect of allocating _volData to be the correct size.
|
// side-effect of allocating _volData to be the correct size.
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
|
@ -807,7 +919,7 @@ uint8_t RenderablePolyVoxEntityItem::getVoxel(int x, int y, int z) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
uint8_t RenderablePolyVoxEntityItem::getVoxelInternal(int x, int y, int z) {
|
uint8_t RenderablePolyVoxEntityItem::getVoxelInternal(int x, int y, int z) const {
|
||||||
if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) {
|
if (!inUserBounds(_volData, _voxelSurfaceStyle, x, y, z)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -949,17 +1061,8 @@ void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() {
|
||||||
EntityTreePointer tree = element ? element->getTree() : nullptr;
|
EntityTreePointer tree = element ? element->getTree() : nullptr;
|
||||||
|
|
||||||
QtConcurrent::run([voxelXSize, voxelYSize, voxelZSize, entity, tree] {
|
QtConcurrent::run([voxelXSize, voxelYSize, voxelZSize, entity, tree] {
|
||||||
int rawSize = voxelXSize * voxelYSize * voxelZSize;
|
|
||||||
QByteArray uncompressedData = QByteArray(rawSize, '\0');
|
|
||||||
|
|
||||||
auto polyVoxEntity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity);
|
auto polyVoxEntity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity);
|
||||||
polyVoxEntity->forEachVoxelValue(voxelXSize, voxelYSize, voxelZSize, [&] (int x, int y, int z, uint8_t uVoxelValue) {
|
QByteArray uncompressedData = polyVoxEntity->volDataToArray(voxelXSize, voxelYSize, voxelZSize);
|
||||||
int uncompressedIndex =
|
|
||||||
z * voxelYSize * voxelXSize +
|
|
||||||
y * voxelXSize +
|
|
||||||
x;
|
|
||||||
uncompressedData[uncompressedIndex] = uVoxelValue;
|
|
||||||
});
|
|
||||||
|
|
||||||
QByteArray newVoxelData;
|
QByteArray newVoxelData;
|
||||||
QDataStream writer(&newVoxelData, QIODevice::WriteOnly | QIODevice::Truncate);
|
QDataStream writer(&newVoxelData, QIODevice::WriteOnly | QIODevice::Truncate);
|
||||||
|
@ -1174,7 +1277,9 @@ void RenderablePolyVoxEntityItem::setMesh(model::MeshPointer mesh) {
|
||||||
// this catches the payload from getMesh
|
// this catches the payload from getMesh
|
||||||
bool neighborsNeedUpdate;
|
bool neighborsNeedUpdate;
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
|
if (!_collisionless) {
|
||||||
|
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
|
||||||
|
}
|
||||||
_mesh = mesh;
|
_mesh = mesh;
|
||||||
_meshDirty = true;
|
_meshDirty = true;
|
||||||
_meshInitialized = true;
|
_meshInitialized = true;
|
||||||
|
|
|
@ -94,6 +94,8 @@ public:
|
||||||
|
|
||||||
// coords are in world-space
|
// coords are in world-space
|
||||||
virtual bool setSphere(glm::vec3 center, float radius, uint8_t toValue) override;
|
virtual bool setSphere(glm::vec3 center, float radius, uint8_t toValue) override;
|
||||||
|
virtual bool setCapsule(glm::vec3 startWorldCoords, glm::vec3 endWorldCoords,
|
||||||
|
float radiusWorldCoords, uint8_t toValue) override;
|
||||||
virtual bool setAll(uint8_t toValue) override;
|
virtual bool setAll(uint8_t toValue) override;
|
||||||
virtual bool setCuboid(const glm::vec3& lowPosition, const glm::vec3& cuboidSize, int toValue) override;
|
virtual bool setCuboid(const glm::vec3& lowPosition, const glm::vec3& cuboidSize, int toValue) override;
|
||||||
|
|
||||||
|
@ -128,12 +130,13 @@ public:
|
||||||
void setVoxelsFromData(QByteArray uncompressedData, quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize);
|
void setVoxelsFromData(QByteArray uncompressedData, quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize);
|
||||||
void forEachVoxelValue(quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize,
|
void forEachVoxelValue(quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize,
|
||||||
std::function<void(int, int, int, uint8_t)> thunk);
|
std::function<void(int, int, int, uint8_t)> thunk);
|
||||||
|
QByteArray volDataToArray(quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize) const;
|
||||||
|
|
||||||
void setMesh(model::MeshPointer mesh);
|
void setMesh(model::MeshPointer mesh);
|
||||||
void setCollisionPoints(ShapeInfo::PointCollection points, AABox box);
|
void setCollisionPoints(ShapeInfo::PointCollection points, AABox box);
|
||||||
PolyVox::SimpleVolume<uint8_t>* getVolData() { return _volData; }
|
PolyVox::SimpleVolume<uint8_t>* getVolData() { return _volData; }
|
||||||
|
|
||||||
uint8_t getVoxelInternal(int x, int y, int z);
|
uint8_t getVoxelInternal(int x, int y, int z) const;
|
||||||
bool setVoxelInternal(int x, int y, int z, uint8_t toValue);
|
bool setVoxelInternal(int x, int y, int z, uint8_t toValue);
|
||||||
|
|
||||||
void setVolDataDirty() { withWriteLock([&] { _volDataDirty = true; }); }
|
void setVolDataDirty() { withWriteLock([&] { _volDataDirty = true; }); }
|
||||||
|
|
|
@ -37,6 +37,8 @@ static uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND;
|
||||||
|
|
||||||
static int MAX_WINDOW_SIZE = 4096;
|
static int MAX_WINDOW_SIZE = 4096;
|
||||||
static float OPAQUE_ALPHA_THRESHOLD = 0.99f;
|
static float OPAQUE_ALPHA_THRESHOLD = 0.99f;
|
||||||
|
static int DEFAULT_MAX_FPS = 10;
|
||||||
|
static int YOUTUBE_MAX_FPS = 30;
|
||||||
|
|
||||||
EntityItemPointer RenderableWebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
EntityItemPointer RenderableWebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||||
EntityItemPointer entity{ new RenderableWebEntityItem(entityID) };
|
EntityItemPointer entity{ new RenderableWebEntityItem(entityID) };
|
||||||
|
@ -113,7 +115,7 @@ bool RenderableWebEntityItem::buildWebSurface(QSharedPointer<EntityTreeRenderer>
|
||||||
|
|
||||||
// FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces
|
// FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces
|
||||||
// and the current rendering load)
|
// and the current rendering load)
|
||||||
_webSurface->setMaxFps(10);
|
_webSurface->setMaxFps(DEFAULT_MAX_FPS);
|
||||||
|
|
||||||
// The lifetime of the QML surface MUST be managed by the main thread
|
// The lifetime of the QML surface MUST be managed by the main thread
|
||||||
// Additionally, we MUST use local variables copied by value, rather than
|
// Additionally, we MUST use local variables copied by value, rather than
|
||||||
|
@ -256,9 +258,18 @@ void RenderableWebEntityItem::loadSourceURL() {
|
||||||
_sourceUrl.toLower().endsWith(".htm") || _sourceUrl.toLower().endsWith(".html")) {
|
_sourceUrl.toLower().endsWith(".htm") || _sourceUrl.toLower().endsWith(".html")) {
|
||||||
_contentType = htmlContent;
|
_contentType = htmlContent;
|
||||||
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "qml/controls/"));
|
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "qml/controls/"));
|
||||||
|
|
||||||
|
// We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS.
|
||||||
|
if (sourceUrl.host().endsWith("youtube.com", Qt::CaseInsensitive)) {
|
||||||
|
_webSurface->setMaxFps(YOUTUBE_MAX_FPS);
|
||||||
|
} else {
|
||||||
|
_webSurface->setMaxFps(DEFAULT_MAX_FPS);
|
||||||
|
}
|
||||||
|
|
||||||
_webSurface->load("WebView.qml", [&](QQmlContext* context, QObject* obj) {
|
_webSurface->load("WebView.qml", [&](QQmlContext* context, QObject* obj) {
|
||||||
context->setContextProperty("eventBridgeJavaScriptToInject", QVariant(_javaScriptToInject));
|
context->setContextProperty("eventBridgeJavaScriptToInject", QVariant(_javaScriptToInject));
|
||||||
});
|
});
|
||||||
|
|
||||||
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
|
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
|
||||||
_webSurface->getRootContext()->setContextProperty("desktop", QVariant());
|
_webSurface->getRootContext()->setContextProperty("desktop", QVariant());
|
||||||
|
|
||||||
|
|
|
@ -285,7 +285,7 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit
|
||||||
desiredProperties = entity->getEntityProperties(params);
|
desiredProperties = entity->getEntityProperties(params);
|
||||||
desiredProperties.setHasProperty(PROP_LOCAL_POSITION);
|
desiredProperties.setHasProperty(PROP_LOCAL_POSITION);
|
||||||
desiredProperties.setHasProperty(PROP_LOCAL_ROTATION);
|
desiredProperties.setHasProperty(PROP_LOCAL_ROTATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
results = entity->getProperties(desiredProperties);
|
results = entity->getProperties(desiredProperties);
|
||||||
|
|
||||||
|
@ -825,7 +825,7 @@ bool EntityScriptingInterface::setVoxels(QUuid entityID,
|
||||||
|
|
||||||
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
|
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
|
||||||
if (!entity) {
|
if (!entity) {
|
||||||
qCDebug(entities) << "EntityScriptingInterface::setVoxelSphere no entity with ID" << entityID;
|
qCDebug(entities) << "EntityScriptingInterface::setVoxels no entity with ID" << entityID;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -887,24 +887,34 @@ bool EntityScriptingInterface::setVoxelSphere(QUuid entityID, const glm::vec3& c
|
||||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||||
|
|
||||||
return setVoxels(entityID, [center, radius, value](PolyVoxEntityItem& polyVoxEntity) {
|
return setVoxels(entityID, [center, radius, value](PolyVoxEntityItem& polyVoxEntity) {
|
||||||
return polyVoxEntity.setSphere(center, radius, value);
|
return polyVoxEntity.setSphere(center, radius, value);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EntityScriptingInterface::setVoxelCapsule(QUuid entityID,
|
||||||
|
const glm::vec3& start, const glm::vec3& end,
|
||||||
|
float radius, int value) {
|
||||||
|
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||||
|
|
||||||
|
return setVoxels(entityID, [start, end, radius, value](PolyVoxEntityItem& polyVoxEntity) {
|
||||||
|
return polyVoxEntity.setCapsule(start, end, radius, value);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntityScriptingInterface::setVoxel(QUuid entityID, const glm::vec3& position, int value) {
|
bool EntityScriptingInterface::setVoxel(QUuid entityID, const glm::vec3& position, int value) {
|
||||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||||
|
|
||||||
return setVoxels(entityID, [position, value](PolyVoxEntityItem& polyVoxEntity) {
|
return setVoxels(entityID, [position, value](PolyVoxEntityItem& polyVoxEntity) {
|
||||||
return polyVoxEntity.setVoxelInVolume(position, value);
|
return polyVoxEntity.setVoxelInVolume(position, value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntityScriptingInterface::setAllVoxels(QUuid entityID, int value) {
|
bool EntityScriptingInterface::setAllVoxels(QUuid entityID, int value) {
|
||||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||||
|
|
||||||
return setVoxels(entityID, [value](PolyVoxEntityItem& polyVoxEntity) {
|
return setVoxels(entityID, [value](PolyVoxEntityItem& polyVoxEntity) {
|
||||||
return polyVoxEntity.setAll(value);
|
return polyVoxEntity.setAll(value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntityScriptingInterface::setVoxelsInCuboid(QUuid entityID, const glm::vec3& lowPosition,
|
bool EntityScriptingInterface::setVoxelsInCuboid(QUuid entityID, const glm::vec3& lowPosition,
|
||||||
|
@ -912,8 +922,8 @@ bool EntityScriptingInterface::setVoxelsInCuboid(QUuid entityID, const glm::vec3
|
||||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||||
|
|
||||||
return setVoxels(entityID, [lowPosition, cuboidSize, value](PolyVoxEntityItem& polyVoxEntity) {
|
return setVoxels(entityID, [lowPosition, cuboidSize, value](PolyVoxEntityItem& polyVoxEntity) {
|
||||||
return polyVoxEntity.setCuboid(lowPosition, cuboidSize, value);
|
return polyVoxEntity.setCuboid(lowPosition, cuboidSize, value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntityScriptingInterface::setAllPoints(QUuid entityID, const QVector<glm::vec3>& points) {
|
bool EntityScriptingInterface::setAllPoints(QUuid entityID, const QVector<glm::vec3>& points) {
|
||||||
|
@ -1020,25 +1030,25 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString,
|
||||||
auto actionFactory = DependencyManager::get<EntityActionFactoryInterface>();
|
auto actionFactory = DependencyManager::get<EntityActionFactoryInterface>();
|
||||||
bool success = false;
|
bool success = false;
|
||||||
actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
|
actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
|
||||||
// create this action even if the entity doesn't have physics info. it will often be the
|
// create this action even if the entity doesn't have physics info. it will often be the
|
||||||
// case that a script adds an action immediately after an object is created, and the physicsInfo
|
// case that a script adds an action immediately after an object is created, and the physicsInfo
|
||||||
// is computed asynchronously.
|
// is computed asynchronously.
|
||||||
// if (!entity->getPhysicsInfo()) {
|
// if (!entity->getPhysicsInfo()) {
|
||||||
// return false;
|
// return false;
|
||||||
// }
|
// }
|
||||||
EntityActionType actionType = EntityActionInterface::actionTypeFromString(actionTypeString);
|
EntityActionType actionType = EntityActionInterface::actionTypeFromString(actionTypeString);
|
||||||
if (actionType == ACTION_TYPE_NONE) {
|
if (actionType == ACTION_TYPE_NONE) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
EntityActionPointer action = actionFactory->factory(actionType, actionID, entity, arguments);
|
EntityActionPointer action = actionFactory->factory(actionType, actionID, entity, arguments);
|
||||||
if (!action) {
|
if (!action) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
action->setIsMine(true);
|
action->setIsMine(true);
|
||||||
success = entity->addAction(simulation, action);
|
success = entity->addAction(simulation, action);
|
||||||
entity->grabSimulationOwnership();
|
entity->grabSimulationOwnership();
|
||||||
return false; // Physics will cause a packet to be sent, so don't send from here.
|
return false; // Physics will cause a packet to be sent, so don't send from here.
|
||||||
});
|
});
|
||||||
if (success) {
|
if (success) {
|
||||||
return actionID;
|
return actionID;
|
||||||
}
|
}
|
||||||
|
@ -1050,12 +1060,12 @@ bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid&
|
||||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||||
|
|
||||||
return actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
|
return actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
|
||||||
bool success = entity->updateAction(simulation, actionID, arguments);
|
bool success = entity->updateAction(simulation, actionID, arguments);
|
||||||
if (success) {
|
if (success) {
|
||||||
entity->grabSimulationOwnership();
|
entity->grabSimulationOwnership();
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid& actionID) {
|
bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid& actionID) {
|
||||||
|
@ -1063,13 +1073,13 @@ bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid&
|
||||||
|
|
||||||
bool success = false;
|
bool success = false;
|
||||||
actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
|
actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
|
||||||
success = entity->removeAction(simulation, actionID);
|
success = entity->removeAction(simulation, actionID);
|
||||||
if (success) {
|
if (success) {
|
||||||
// reduce from grab to poke
|
// reduce from grab to poke
|
||||||
entity->pokeSimulationOwnership();
|
entity->pokeSimulationOwnership();
|
||||||
}
|
}
|
||||||
return false; // Physics will cause a packet to be sent, so don't send from here.
|
return false; // Physics will cause a packet to be sent, so don't send from here.
|
||||||
});
|
});
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1078,10 +1088,10 @@ QVector<QUuid> EntityScriptingInterface::getActionIDs(const QUuid& entityID) {
|
||||||
|
|
||||||
QVector<QUuid> result;
|
QVector<QUuid> result;
|
||||||
actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
|
actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
|
||||||
QList<QUuid> actionIDs = entity->getActionIDs();
|
QList<QUuid> actionIDs = entity->getActionIDs();
|
||||||
result = QVector<QUuid>::fromList(actionIDs);
|
result = QVector<QUuid>::fromList(actionIDs);
|
||||||
return false; // don't send an edit packet
|
return false; // don't send an edit packet
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1090,9 +1100,9 @@ QVariantMap EntityScriptingInterface::getActionArguments(const QUuid& entityID,
|
||||||
|
|
||||||
QVariantMap result;
|
QVariantMap result;
|
||||||
actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
|
actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
|
||||||
result = entity->getActionArguments(actionID);
|
result = entity->getActionArguments(actionID);
|
||||||
return false; // don't send an edit packet
|
return false; // don't send an edit packet
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1523,3 +1533,11 @@ QObject* EntityScriptingInterface::getWebViewRoot(const QUuid& entityID) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO move this someplace that makes more sense...
|
||||||
|
bool EntityScriptingInterface::AABoxIntersectsCapsule(const glm::vec3& low, const glm::vec3& dimensions,
|
||||||
|
const glm::vec3& start, const glm::vec3& end, float radius) {
|
||||||
|
glm::vec3 penetration;
|
||||||
|
AABox aaBox(low, dimensions);
|
||||||
|
return aaBox.findCapsulePenetration(start, end, radius, penetration);
|
||||||
|
}
|
||||||
|
|
|
@ -223,6 +223,8 @@ public slots:
|
||||||
Q_INVOKABLE bool getDrawZoneBoundaries() const;
|
Q_INVOKABLE bool getDrawZoneBoundaries() const;
|
||||||
|
|
||||||
Q_INVOKABLE bool setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value);
|
Q_INVOKABLE bool setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value);
|
||||||
|
Q_INVOKABLE bool setVoxelCapsule(QUuid entityID, const glm::vec3& start, const glm::vec3& end, float radius, int value);
|
||||||
|
|
||||||
Q_INVOKABLE bool setVoxel(QUuid entityID, const glm::vec3& position, int value);
|
Q_INVOKABLE bool setVoxel(QUuid entityID, const glm::vec3& position, int value);
|
||||||
Q_INVOKABLE bool setAllVoxels(QUuid entityID, int value);
|
Q_INVOKABLE bool setAllVoxels(QUuid entityID, int value);
|
||||||
Q_INVOKABLE bool setVoxelsInCuboid(QUuid entityID, const glm::vec3& lowPosition,
|
Q_INVOKABLE bool setVoxelsInCuboid(QUuid entityID, const glm::vec3& lowPosition,
|
||||||
|
@ -287,6 +289,10 @@ public slots:
|
||||||
|
|
||||||
Q_INVOKABLE QObject* getWebViewRoot(const QUuid& entityID);
|
Q_INVOKABLE QObject* getWebViewRoot(const QUuid& entityID);
|
||||||
|
|
||||||
|
Q_INVOKABLE bool AABoxIntersectsCapsule(const glm::vec3& low, const glm::vec3& dimensions,
|
||||||
|
const glm::vec3& start, const glm::vec3& end, float radius);
|
||||||
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
|
void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,8 @@ class PolyVoxEntityItem : public EntityItem {
|
||||||
|
|
||||||
// coords are in world-space
|
// coords are in world-space
|
||||||
virtual bool setSphere(glm::vec3 center, float radius, uint8_t toValue) { return false; }
|
virtual bool setSphere(glm::vec3 center, float radius, uint8_t toValue) { return false; }
|
||||||
|
virtual bool setCapsule(glm::vec3 startWorldCoords, glm::vec3 endWorldCoords,
|
||||||
|
float radiusWorldCoords, uint8_t toValue) { return false; }
|
||||||
virtual bool setAll(uint8_t toValue) { return false; }
|
virtual bool setAll(uint8_t toValue) { return false; }
|
||||||
virtual bool setCuboid(const glm::vec3& lowPosition, const glm::vec3& cuboidSize, int value) { return false; }
|
virtual bool setCuboid(const glm::vec3& lowPosition, const glm::vec3& cuboidSize, int value) { return false; }
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort)
|
||||||
setCustomDeleter([](Dependency* dependency){
|
setCustomDeleter([](Dependency* dependency){
|
||||||
static_cast<NodeList*>(dependency)->deleteLater();
|
static_cast<NodeList*>(dependency)->deleteLater();
|
||||||
});
|
});
|
||||||
|
|
||||||
auto addressManager = DependencyManager::get<AddressManager>();
|
auto addressManager = DependencyManager::get<AddressManager>();
|
||||||
|
|
||||||
// handle domain change signals from AddressManager
|
// handle domain change signals from AddressManager
|
||||||
|
@ -85,8 +85,8 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort)
|
||||||
connect(&_domainHandler, &DomainHandler::icePeerSocketsReceived, this, &NodeList::pingPunchForDomainServer);
|
connect(&_domainHandler, &DomainHandler::icePeerSocketsReceived, this, &NodeList::pingPunchForDomainServer);
|
||||||
|
|
||||||
auto accountManager = DependencyManager::get<AccountManager>();
|
auto accountManager = DependencyManager::get<AccountManager>();
|
||||||
|
|
||||||
// assume that we may need to send a new DS check in anytime a new keypair is generated
|
// assume that we may need to send a new DS check in anytime a new keypair is generated
|
||||||
connect(accountManager.data(), &AccountManager::newKeypair, this, &NodeList::sendDomainServerCheckIn);
|
connect(accountManager.data(), &AccountManager::newKeypair, this, &NodeList::sendDomainServerCheckIn);
|
||||||
|
|
||||||
// clear out NodeList when login is finished
|
// clear out NodeList when login is finished
|
||||||
|
@ -101,7 +101,7 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort)
|
||||||
|
|
||||||
// anytime we get a new node we may need to re-send our set of ignored node IDs to it
|
// anytime we get a new node we may need to re-send our set of ignored node IDs to it
|
||||||
connect(this, &LimitedNodeList::nodeActivated, this, &NodeList::maybeSendIgnoreSetToNode);
|
connect(this, &LimitedNodeList::nodeActivated, this, &NodeList::maybeSendIgnoreSetToNode);
|
||||||
|
|
||||||
// setup our timer to send keepalive pings (it's started and stopped on domain connect/disconnect)
|
// setup our timer to send keepalive pings (it's started and stopped on domain connect/disconnect)
|
||||||
_keepAlivePingTimer.setInterval(KEEPALIVE_PING_INTERVAL_MS); // 1s, Qt::CoarseTimer acceptable
|
_keepAlivePingTimer.setInterval(KEEPALIVE_PING_INTERVAL_MS); // 1s, Qt::CoarseTimer acceptable
|
||||||
connect(&_keepAlivePingTimer, &QTimer::timeout, this, &NodeList::sendKeepAlivePings);
|
connect(&_keepAlivePingTimer, &QTimer::timeout, this, &NodeList::sendKeepAlivePings);
|
||||||
|
@ -161,11 +161,11 @@ qint64 NodeList::sendStatsToDomainServer(QJsonObject statsObject) {
|
||||||
|
|
||||||
void NodeList::timePingReply(ReceivedMessage& message, const SharedNodePointer& sendingNode) {
|
void NodeList::timePingReply(ReceivedMessage& message, const SharedNodePointer& sendingNode) {
|
||||||
PingType_t pingType;
|
PingType_t pingType;
|
||||||
|
|
||||||
quint64 ourOriginalTime, othersReplyTime;
|
quint64 ourOriginalTime, othersReplyTime;
|
||||||
|
|
||||||
message.seek(0);
|
message.seek(0);
|
||||||
|
|
||||||
message.readPrimitive(&pingType);
|
message.readPrimitive(&pingType);
|
||||||
message.readPrimitive(&ourOriginalTime);
|
message.readPrimitive(&ourOriginalTime);
|
||||||
message.readPrimitive(&othersReplyTime);
|
message.readPrimitive(&othersReplyTime);
|
||||||
|
@ -199,7 +199,7 @@ void NodeList::timePingReply(ReceivedMessage& message, const SharedNodePointer&
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeList::processPingPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
void NodeList::processPingPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||||
|
|
||||||
// send back a reply
|
// send back a reply
|
||||||
auto replyPacket = constructPingReplyPacket(*message);
|
auto replyPacket = constructPingReplyPacket(*message);
|
||||||
const HifiSockAddr& senderSockAddr = message->getSenderSockAddr();
|
const HifiSockAddr& senderSockAddr = message->getSenderSockAddr();
|
||||||
|
@ -252,6 +252,11 @@ void NodeList::reset() {
|
||||||
_personalMutedNodeIDs.clear();
|
_personalMutedNodeIDs.clear();
|
||||||
_personalMutedSetLock.unlock();
|
_personalMutedSetLock.unlock();
|
||||||
|
|
||||||
|
// lock and clear out set of avatarGains
|
||||||
|
_avatarGainMapLock.lockForWrite();
|
||||||
|
_avatarGainMap.clear();
|
||||||
|
_avatarGainMapLock.unlock();
|
||||||
|
|
||||||
// refresh the owner UUID to the NULL UUID
|
// refresh the owner UUID to the NULL UUID
|
||||||
setSessionUUID(QUuid());
|
setSessionUUID(QUuid());
|
||||||
|
|
||||||
|
@ -329,7 +334,7 @@ void NodeList::sendDomainServerCheckIn() {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto domainPacket = NLPacket::create(domainPacketType);
|
auto domainPacket = NLPacket::create(domainPacketType);
|
||||||
|
|
||||||
QDataStream packetStream(domainPacket.get());
|
QDataStream packetStream(domainPacket.get());
|
||||||
|
|
||||||
if (domainPacketType == PacketType::DomainConnectRequest) {
|
if (domainPacketType == PacketType::DomainConnectRequest) {
|
||||||
|
@ -488,7 +493,7 @@ void NodeList::processDomainServerPathResponse(QSharedPointer<ReceivedMessage> m
|
||||||
qCDebug(networking) << "Could not read query path from DomainServerPathQueryResponse. Bailing.";
|
qCDebug(networking) << "Could not read query path from DomainServerPathQueryResponse. Bailing.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString pathQuery = QString::fromUtf8(message->getRawMessage() + message->getPosition(), numPathBytes);
|
QString pathQuery = QString::fromUtf8(message->getRawMessage() + message->getPosition(), numPathBytes);
|
||||||
message->seek(message->getPosition() + numPathBytes);
|
message->seek(message->getPosition() + numPathBytes);
|
||||||
|
|
||||||
|
@ -500,10 +505,10 @@ void NodeList::processDomainServerPathResponse(QSharedPointer<ReceivedMessage> m
|
||||||
qCDebug(networking) << "Could not read resulting viewpoint from DomainServerPathQueryReponse. Bailing";
|
qCDebug(networking) << "Could not read resulting viewpoint from DomainServerPathQueryReponse. Bailing";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// pull the viewpoint from the packet
|
// pull the viewpoint from the packet
|
||||||
QString viewpoint = QString::fromUtf8(message->getRawMessage() + message->getPosition(), numViewpointBytes);
|
QString viewpoint = QString::fromUtf8(message->getRawMessage() + message->getPosition(), numViewpointBytes);
|
||||||
|
|
||||||
// Hand it off to the AddressManager so it can handle it as a relative viewpoint
|
// Hand it off to the AddressManager so it can handle it as a relative viewpoint
|
||||||
if (DependencyManager::get<AddressManager>()->goToViewpointForPath(viewpoint, pathQuery)) {
|
if (DependencyManager::get<AddressManager>()->goToViewpointForPath(viewpoint, pathQuery)) {
|
||||||
qCDebug(networking) << "Going to viewpoint" << viewpoint << "which was the lookup result for path" << pathQuery;
|
qCDebug(networking) << "Going to viewpoint" << viewpoint << "which was the lookup result for path" << pathQuery;
|
||||||
|
@ -664,16 +669,16 @@ void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeList::sendAssignment(Assignment& assignment) {
|
void NodeList::sendAssignment(Assignment& assignment) {
|
||||||
|
|
||||||
PacketType assignmentPacketType = assignment.getCommand() == Assignment::CreateCommand
|
PacketType assignmentPacketType = assignment.getCommand() == Assignment::CreateCommand
|
||||||
? PacketType::CreateAssignment
|
? PacketType::CreateAssignment
|
||||||
: PacketType::RequestAssignment;
|
: PacketType::RequestAssignment;
|
||||||
|
|
||||||
auto assignmentPacket = NLPacket::create(assignmentPacketType);
|
auto assignmentPacket = NLPacket::create(assignmentPacketType);
|
||||||
|
|
||||||
QDataStream packetStream(assignmentPacket.get());
|
QDataStream packetStream(assignmentPacket.get());
|
||||||
packetStream << assignment;
|
packetStream << assignment;
|
||||||
|
|
||||||
sendPacket(std::move(assignmentPacket), _assignmentServerSocket);
|
sendPacket(std::move(assignmentPacket), _assignmentServerSocket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -833,7 +838,7 @@ void NodeList::ignoreNodeBySessionID(const QUuid& nodeID, bool ignoreEnabled) {
|
||||||
_ignoredNodeIDs.insert(nodeID);
|
_ignoredNodeIDs.insert(nodeID);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
QReadLocker personalMutedSetLocker{ &_personalMutedSetLock }; // read lock for insert
|
QReadLocker personalMutedSetLocker{ &_personalMutedSetLock }; // read lock for insert
|
||||||
// add this nodeID to our set of personal muted IDs
|
// add this nodeID to our set of personal muted IDs
|
||||||
_personalMutedNodeIDs.insert(nodeID);
|
_personalMutedNodeIDs.insert(nodeID);
|
||||||
}
|
}
|
||||||
|
@ -896,7 +901,7 @@ void NodeList::personalMuteNodeBySessionID(const QUuid& nodeID, bool muteEnabled
|
||||||
|
|
||||||
|
|
||||||
if (muteEnabled) {
|
if (muteEnabled) {
|
||||||
QReadLocker personalMutedSetLocker{ &_personalMutedSetLock }; // read lock for insert
|
QReadLocker personalMutedSetLocker{ &_personalMutedSetLock }; // read lock for insert
|
||||||
// add this nodeID to our set of personal muted IDs
|
// add this nodeID to our set of personal muted IDs
|
||||||
_personalMutedNodeIDs.insert(nodeID);
|
_personalMutedNodeIDs.insert(nodeID);
|
||||||
} else {
|
} else {
|
||||||
|
@ -981,7 +986,7 @@ void NodeList::setAvatarGain(const QUuid& nodeID, float gain) {
|
||||||
if (audioMixer) {
|
if (audioMixer) {
|
||||||
// setup the packet
|
// setup the packet
|
||||||
auto setAvatarGainPacket = NLPacket::create(PacketType::PerAvatarGainSet, NUM_BYTES_RFC4122_UUID + sizeof(float), true);
|
auto setAvatarGainPacket = NLPacket::create(PacketType::PerAvatarGainSet, NUM_BYTES_RFC4122_UUID + sizeof(float), true);
|
||||||
|
|
||||||
// write the node ID to the packet
|
// write the node ID to the packet
|
||||||
setAvatarGainPacket->write(nodeID.toRfc4122());
|
setAvatarGainPacket->write(nodeID.toRfc4122());
|
||||||
// We need to convert the gain in dB (from the script) to an amplitude before packing it.
|
// We need to convert the gain in dB (from the script) to an amplitude before packing it.
|
||||||
|
@ -990,6 +995,9 @@ void NodeList::setAvatarGain(const QUuid& nodeID, float gain) {
|
||||||
qCDebug(networking) << "Sending Set Avatar Gain packet UUID: " << uuidStringWithoutCurlyBraces(nodeID) << "Gain:" << gain;
|
qCDebug(networking) << "Sending Set Avatar Gain packet UUID: " << uuidStringWithoutCurlyBraces(nodeID) << "Gain:" << gain;
|
||||||
|
|
||||||
sendPacket(std::move(setAvatarGainPacket), *audioMixer);
|
sendPacket(std::move(setAvatarGainPacket), *audioMixer);
|
||||||
|
QWriteLocker{ &_avatarGainMapLock };
|
||||||
|
_avatarGainMap[nodeID] = gain;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "Couldn't find audio mixer to send set gain request";
|
qWarning() << "Couldn't find audio mixer to send set gain request";
|
||||||
}
|
}
|
||||||
|
@ -998,6 +1006,15 @@ void NodeList::setAvatarGain(const QUuid& nodeID, float gain) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float NodeList::getAvatarGain(const QUuid& nodeID) {
|
||||||
|
QReadLocker{ &_avatarGainMapLock };
|
||||||
|
auto it = _avatarGainMap.find(nodeID);
|
||||||
|
if (it != _avatarGainMap.cend()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
void NodeList::kickNodeBySessionID(const QUuid& nodeID) {
|
void NodeList::kickNodeBySessionID(const QUuid& nodeID) {
|
||||||
// send a request to domain-server to kick the node with the given session ID
|
// send a request to domain-server to kick the node with the given session ID
|
||||||
// the domain-server will handle the persistence of the kick (via username or IP)
|
// the domain-server will handle the persistence of the kick (via username or IP)
|
||||||
|
@ -1036,7 +1053,7 @@ void NodeList::muteNodeBySessionID(const QUuid& nodeID) {
|
||||||
mutePacket->write(nodeID.toRfc4122());
|
mutePacket->write(nodeID.toRfc4122());
|
||||||
|
|
||||||
qCDebug(networking) << "Sending packet to mute node" << uuidStringWithoutCurlyBraces(nodeID);
|
qCDebug(networking) << "Sending packet to mute node" << uuidStringWithoutCurlyBraces(nodeID);
|
||||||
|
|
||||||
sendPacket(std::move(mutePacket), *audioMixer);
|
sendPacket(std::move(mutePacket), *audioMixer);
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "Couldn't find audio mixer to send node mute request";
|
qWarning() << "Couldn't find audio mixer to send node mute request";
|
||||||
|
|
|
@ -68,7 +68,7 @@ public:
|
||||||
|
|
||||||
void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; }
|
void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; }
|
||||||
void sendAssignment(Assignment& assignment);
|
void sendAssignment(Assignment& assignment);
|
||||||
|
|
||||||
void setIsShuttingDown(bool isShuttingDown) { _isShuttingDown = isShuttingDown; }
|
void setIsShuttingDown(bool isShuttingDown) { _isShuttingDown = isShuttingDown; }
|
||||||
|
|
||||||
void ignoreNodesInRadius(bool enabled = true);
|
void ignoreNodesInRadius(bool enabled = true);
|
||||||
|
@ -83,6 +83,7 @@ public:
|
||||||
void personalMuteNodeBySessionID(const QUuid& nodeID, bool muteEnabled);
|
void personalMuteNodeBySessionID(const QUuid& nodeID, bool muteEnabled);
|
||||||
bool isPersonalMutingNode(const QUuid& nodeID) const;
|
bool isPersonalMutingNode(const QUuid& nodeID) const;
|
||||||
void setAvatarGain(const QUuid& nodeID, float gain);
|
void setAvatarGain(const QUuid& nodeID, float gain);
|
||||||
|
float getAvatarGain(const QUuid& nodeID);
|
||||||
|
|
||||||
void kickNodeBySessionID(const QUuid& nodeID);
|
void kickNodeBySessionID(const QUuid& nodeID);
|
||||||
void muteNodeBySessionID(const QUuid& nodeID);
|
void muteNodeBySessionID(const QUuid& nodeID);
|
||||||
|
@ -103,7 +104,7 @@ public slots:
|
||||||
void processDomainServerPathResponse(QSharedPointer<ReceivedMessage> message);
|
void processDomainServerPathResponse(QSharedPointer<ReceivedMessage> message);
|
||||||
|
|
||||||
void processDomainServerConnectionTokenPacket(QSharedPointer<ReceivedMessage> message);
|
void processDomainServerConnectionTokenPacket(QSharedPointer<ReceivedMessage> message);
|
||||||
|
|
||||||
void processPingPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
void processPingPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||||
void processPingReplyPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
void processPingReplyPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||||
|
|
||||||
|
@ -131,11 +132,11 @@ private slots:
|
||||||
void handleNodePingTimeout();
|
void handleNodePingTimeout();
|
||||||
|
|
||||||
void pingPunchForDomainServer();
|
void pingPunchForDomainServer();
|
||||||
|
|
||||||
void sendKeepAlivePings();
|
void sendKeepAlivePings();
|
||||||
|
|
||||||
void maybeSendIgnoreSetToNode(SharedNodePointer node);
|
void maybeSendIgnoreSetToNode(SharedNodePointer node);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NodeList() : LimitedNodeList(INVALID_PORT, INVALID_PORT) { assert(false); } // Not implemented, needed for DependencyManager templates compile
|
NodeList() : LimitedNodeList(INVALID_PORT, INVALID_PORT) { assert(false); } // Not implemented, needed for DependencyManager templates compile
|
||||||
NodeList(char ownerType, int socketListenPort = INVALID_PORT, int dtlsListenPort = INVALID_PORT);
|
NodeList(char ownerType, int socketListenPort = INVALID_PORT, int dtlsListenPort = INVALID_PORT);
|
||||||
|
@ -148,7 +149,7 @@ private:
|
||||||
void timePingReply(ReceivedMessage& message, const SharedNodePointer& sendingNode);
|
void timePingReply(ReceivedMessage& message, const SharedNodePointer& sendingNode);
|
||||||
|
|
||||||
void sendDSPathQuery(const QString& newPath);
|
void sendDSPathQuery(const QString& newPath);
|
||||||
|
|
||||||
void parseNodeFromPacketStream(QDataStream& packetStream);
|
void parseNodeFromPacketStream(QDataStream& packetStream);
|
||||||
|
|
||||||
void pingPunchForInactiveNode(const SharedNodePointer& node);
|
void pingPunchForInactiveNode(const SharedNodePointer& node);
|
||||||
|
@ -170,6 +171,8 @@ private:
|
||||||
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _ignoredNodeIDs;
|
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _ignoredNodeIDs;
|
||||||
mutable QReadWriteLock _personalMutedSetLock;
|
mutable QReadWriteLock _personalMutedSetLock;
|
||||||
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _personalMutedNodeIDs;
|
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _personalMutedNodeIDs;
|
||||||
|
mutable QReadWriteLock _avatarGainMapLock;
|
||||||
|
tbb::concurrent_unordered_map<QUuid, float, UUIDHasher> _avatarGainMap;
|
||||||
|
|
||||||
void sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationNode);
|
void sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationNode);
|
||||||
Setting::Handle<bool> _ignoreRadiusEnabled { "IgnoreRadiusEnabled", true };
|
Setting::Handle<bool> _ignoreRadiusEnabled { "IgnoreRadiusEnabled", true };
|
||||||
|
|
|
@ -160,13 +160,14 @@ RenderDeferredTask::RenderDeferredTask(RenderFetchCullSortTask::Output items) {
|
||||||
addJob<DrawOverlay3D>("DrawOverlay3DOpaque", overlayOpaquesInputs, true);
|
addJob<DrawOverlay3D>("DrawOverlay3DOpaque", overlayOpaquesInputs, true);
|
||||||
addJob<DrawOverlay3D>("DrawOverlay3DTransparent", overlayTransparentsInputs, false);
|
addJob<DrawOverlay3D>("DrawOverlay3DTransparent", overlayTransparentsInputs, false);
|
||||||
|
|
||||||
|
|
||||||
// Debugging stages
|
// Debugging stages
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
// Bounds do not draw on stencil buffer, so they must come last
|
// Bounds do not draw on stencil buffer, so they must come last
|
||||||
addJob<DrawBounds>("DrawMetaBounds", metas);
|
addJob<DrawBounds>("DrawMetaBounds", metas);
|
||||||
|
addJob<DrawBounds>("DrawOverlayOpaqueBounds", overlayOpaques);
|
||||||
|
addJob<DrawBounds>("DrawOverlayTransparentBounds", overlayTransparents);
|
||||||
|
|
||||||
// Debugging Deferred buffer job
|
// Debugging Deferred buffer job
|
||||||
const auto debugFramebuffers = render::Varying(DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer));
|
const auto debugFramebuffers = render::Varying(DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer));
|
||||||
|
|
|
@ -47,6 +47,10 @@ void UsersScriptingInterface::setAvatarGain(const QUuid& nodeID, float gain) {
|
||||||
DependencyManager::get<NodeList>()->setAvatarGain(nodeID, gain);
|
DependencyManager::get<NodeList>()->setAvatarGain(nodeID, gain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float UsersScriptingInterface::getAvatarGain(const QUuid& nodeID) {
|
||||||
|
return DependencyManager::get<NodeList>()->getAvatarGain(nodeID);
|
||||||
|
}
|
||||||
|
|
||||||
void UsersScriptingInterface::kick(const QUuid& nodeID) {
|
void UsersScriptingInterface::kick(const QUuid& nodeID) {
|
||||||
// ask the NodeList to kick the user with the given session ID
|
// ask the NodeList to kick the user with the given session ID
|
||||||
DependencyManager::get<NodeList>()->kickNodeBySessionID(nodeID);
|
DependencyManager::get<NodeList>()->kickNodeBySessionID(nodeID);
|
||||||
|
@ -88,4 +92,4 @@ bool UsersScriptingInterface::getRequestsDomainListData() {
|
||||||
}
|
}
|
||||||
void UsersScriptingInterface::setRequestsDomainListData(bool isRequesting) {
|
void UsersScriptingInterface::setRequestsDomainListData(bool isRequesting) {
|
||||||
DependencyManager::get<NodeList>()->setRequestsDomainListData(isRequesting);
|
DependencyManager::get<NodeList>()->setRequestsDomainListData(isRequesting);
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,14 @@ public slots:
|
||||||
*/
|
*/
|
||||||
void setAvatarGain(const QUuid& nodeID, float gain);
|
void setAvatarGain(const QUuid& nodeID, float gain);
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Gets an avatar's gain for you and you only.
|
||||||
|
* @function Users.getAvatarGain
|
||||||
|
* @param {nodeID} nodeID The node or session ID of the user whose gain you want to get.
|
||||||
|
* @return {float} gain (in dB)
|
||||||
|
*/
|
||||||
|
float getAvatarGain(const QUuid& nodeID);
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Kick another user.
|
* Kick another user.
|
||||||
* @function Users.kick
|
* @function Users.kick
|
||||||
|
|
|
@ -205,6 +205,33 @@ bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& directi
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool pointInSphere(const glm::vec3& origin, const glm::vec3& center, float radius) {
|
||||||
|
glm::vec3 relativeOrigin = origin - center;
|
||||||
|
float c = glm::dot(relativeOrigin, relativeOrigin) - radius * radius;
|
||||||
|
return c <= 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool pointInCapsule(const glm::vec3& origin, const glm::vec3& start, const glm::vec3& end, float radius) {
|
||||||
|
glm::vec3 relativeOrigin = origin - start;
|
||||||
|
glm::vec3 relativeEnd = end - start;
|
||||||
|
float capsuleLength = glm::length(relativeEnd);
|
||||||
|
relativeEnd /= capsuleLength;
|
||||||
|
float originProjection = glm::dot(relativeEnd, relativeOrigin);
|
||||||
|
glm::vec3 constant = relativeOrigin - relativeEnd * originProjection;
|
||||||
|
float c = glm::dot(constant, constant) - radius * radius;
|
||||||
|
if (c < 0.0f) { // starts inside cylinder
|
||||||
|
if (originProjection < 0.0f) { // below start
|
||||||
|
return pointInSphere(origin, start, radius);
|
||||||
|
} else if (originProjection > capsuleLength) { // above end
|
||||||
|
return pointInSphere(origin, end, radius);
|
||||||
|
} else { // between start and end
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool findRayCapsuleIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
bool findRayCapsuleIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
const glm::vec3& start, const glm::vec3& end, float radius, float& distance) {
|
const glm::vec3& start, const glm::vec3& end, float radius, float& distance) {
|
||||||
if (start == end) {
|
if (start == end) {
|
||||||
|
|
|
@ -73,6 +73,9 @@ glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3&
|
||||||
bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
const glm::vec3& center, float radius, float& distance);
|
const glm::vec3& center, float radius, float& distance);
|
||||||
|
|
||||||
|
bool pointInSphere(const glm::vec3& origin, const glm::vec3& center, float radius);
|
||||||
|
bool pointInCapsule(const glm::vec3& origin, const glm::vec3& start, const glm::vec3& end, float radius);
|
||||||
|
|
||||||
bool findRayCapsuleIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
bool findRayCapsuleIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
const glm::vec3& start, const glm::vec3& end, float radius, float& distance);
|
const glm::vec3& start, const glm::vec3& end, float radius, float& distance);
|
||||||
|
|
||||||
|
|
|
@ -545,7 +545,7 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
|
||||||
// HACK: when interface is launched and steam vr is NOT running, openvr will return bad HMD poses for a few frames
|
// HACK: when interface is launched and steam vr is NOT running, openvr will return bad HMD poses for a few frames
|
||||||
// To workaround this, filter out any hmd poses that are obviously bad, i.e. beneath the floor.
|
// To workaround this, filter out any hmd poses that are obviously bad, i.e. beneath the floor.
|
||||||
if (isBadPose(&nextSimPoseData.vrPoses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking)) {
|
if (isBadPose(&nextSimPoseData.vrPoses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking)) {
|
||||||
qDebug() << "WARNING: ignoring bad hmd pose from openvr";
|
// qDebug() << "WARNING: ignoring bad hmd pose from openvr";
|
||||||
|
|
||||||
// use the last known good HMD pose
|
// use the last known good HMD pose
|
||||||
nextSimPoseData.vrPoses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking = _lastGoodHMDPose;
|
nextSimPoseData.vrPoses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking = _lastGoodHMDPose;
|
||||||
|
|
BIN
scripts/system/assets/models/teleport-seat.fbx
Normal file
BIN
scripts/system/assets/models/teleport-seat.fbx
Normal file
Binary file not shown.
|
@ -272,6 +272,27 @@ CONTROLLER_STATE_MACHINE[STATE_OVERLAY_STYLUS_TOUCHING] = {
|
||||||
};
|
};
|
||||||
CONTROLLER_STATE_MACHINE[STATE_OVERLAY_LASER_TOUCHING] = CONTROLLER_STATE_MACHINE[STATE_OVERLAY_STYLUS_TOUCHING];
|
CONTROLLER_STATE_MACHINE[STATE_OVERLAY_LASER_TOUCHING] = CONTROLLER_STATE_MACHINE[STATE_OVERLAY_STYLUS_TOUCHING];
|
||||||
|
|
||||||
|
// Object assign polyfill
|
||||||
|
if (typeof Object.assign != 'function') {
|
||||||
|
Object.assign = function(target, varArgs) {
|
||||||
|
'use strict';
|
||||||
|
if (target == null) {
|
||||||
|
throw new TypeError('Cannot convert undefined or null to object');
|
||||||
|
}
|
||||||
|
var to = Object(target);
|
||||||
|
for (var index = 1; index < arguments.length; index++) {
|
||||||
|
var nextSource = arguments[index];
|
||||||
|
if (nextSource != null) {
|
||||||
|
for (var nextKey in nextSource) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
|
||||||
|
to[nextKey] = nextSource[nextKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return to;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function distanceBetweenPointAndEntityBoundingBox(point, entityProps) {
|
function distanceBetweenPointAndEntityBoundingBox(point, entityProps) {
|
||||||
var entityXform = new Xform(entityProps.rotation, entityProps.position);
|
var entityXform = new Xform(entityProps.rotation, entityProps.position);
|
||||||
|
@ -746,6 +767,10 @@ function MyController(hand) {
|
||||||
this.stylus = null;
|
this.stylus = null;
|
||||||
this.homeButtonTouched = false;
|
this.homeButtonTouched = false;
|
||||||
|
|
||||||
|
this.controllerJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ?
|
||||||
|
"_CONTROLLER_RIGHTHAND" :
|
||||||
|
"_CONTROLLER_LEFTHAND");
|
||||||
|
|
||||||
// Until there is some reliable way to keep track of a "stack" of parentIDs, we'll have problems
|
// Until there is some reliable way to keep track of a "stack" of parentIDs, we'll have problems
|
||||||
// when more than one avatar does parenting grabs on things. This script tries to work
|
// when more than one avatar does parenting grabs on things. This script tries to work
|
||||||
// around this with two associative arrays: previousParentID and previousParentJointIndex. If
|
// around this with two associative arrays: previousParentID and previousParentJointIndex. If
|
||||||
|
@ -797,9 +822,6 @@ function MyController(hand) {
|
||||||
|
|
||||||
// for visualizations
|
// for visualizations
|
||||||
this.overlayLine = null;
|
this.overlayLine = null;
|
||||||
|
|
||||||
// for lights
|
|
||||||
this.overlayLine = null;
|
|
||||||
this.searchSphere = null;
|
this.searchSphere = null;
|
||||||
|
|
||||||
this.waitForTriggerRelease = false;
|
this.waitForTriggerRelease = false;
|
||||||
|
@ -927,9 +949,7 @@ function MyController(hand) {
|
||||||
ignoreRayIntersection: true,
|
ignoreRayIntersection: true,
|
||||||
drawInFront: false,
|
drawInFront: false,
|
||||||
parentID: AVATAR_SELF_ID,
|
parentID: AVATAR_SELF_ID,
|
||||||
parentJointIndex: MyAvatar.getJointIndex(this.hand === RIGHT_HAND ?
|
parentJointIndex: this.controllerJointIndex
|
||||||
"_CONTROLLER_RIGHTHAND" :
|
|
||||||
"_CONTROLLER_LEFTHAND")
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1014,32 +1034,38 @@ function MyController(hand) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.overlayLineOn = function(closePoint, farPoint, color) {
|
this.overlayLineOn = function(closePoint, farPoint, color, farParentID) {
|
||||||
if (this.overlayLine === null) {
|
if (this.overlayLine === null) {
|
||||||
var lineProperties = {
|
var lineProperties = {
|
||||||
name: "line",
|
name: "line",
|
||||||
glow: 1.0,
|
glow: 1.0,
|
||||||
start: closePoint,
|
|
||||||
end: farPoint,
|
|
||||||
color: color,
|
|
||||||
ignoreRayIntersection: true, // always ignore this
|
|
||||||
drawInFront: true, // Even when burried inside of something, show it.
|
|
||||||
visible: true,
|
|
||||||
alpha: 1
|
|
||||||
};
|
|
||||||
this.overlayLine = Overlays.addOverlay("line3d", lineProperties);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
Overlays.editOverlay(this.overlayLine, {
|
|
||||||
lineWidth: 5,
|
lineWidth: 5,
|
||||||
start: closePoint,
|
start: closePoint,
|
||||||
end: farPoint,
|
end: farPoint,
|
||||||
color: color,
|
color: color,
|
||||||
visible: true,
|
|
||||||
ignoreRayIntersection: true, // always ignore this
|
ignoreRayIntersection: true, // always ignore this
|
||||||
drawInFront: true, // Even when burried inside of something, show it.
|
drawInFront: true, // Even when burried inside of something, show it.
|
||||||
alpha: 1
|
visible: true,
|
||||||
|
alpha: 1,
|
||||||
|
parentID: AVATAR_SELF_ID,
|
||||||
|
parentJointIndex: this.controllerJointIndex,
|
||||||
|
endParentID: farParentID
|
||||||
|
};
|
||||||
|
this.overlayLine = Overlays.addOverlay("line3d", lineProperties);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (farParentID && farParentID != NULL_UUID) {
|
||||||
|
Overlays.editOverlay(this.overlayLine, {
|
||||||
|
color: color,
|
||||||
|
endParentID: farParentID
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
Overlays.editOverlay(this.overlayLine, {
|
||||||
|
length: Vec3.distance(farPoint, closePoint),
|
||||||
|
color: color,
|
||||||
|
endParentID: farParentID
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1463,7 +1489,18 @@ function MyController(hand) {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
this.entityIsCloneable = function(entityID) {
|
||||||
|
var entityProps = entityPropertiesCache.getGrabbableProps(entityID);
|
||||||
|
var props = entityPropertiesCache.getProps(entityID);
|
||||||
|
if (!props) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entityProps.hasOwnProperty("cloneable")) {
|
||||||
|
return entityProps.cloneable;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
this.entityIsGrabbable = function(entityID) {
|
this.entityIsGrabbable = function(entityID) {
|
||||||
var grabbableProps = entityPropertiesCache.getGrabbableProps(entityID);
|
var grabbableProps = entityPropertiesCache.getGrabbableProps(entityID);
|
||||||
var props = entityPropertiesCache.getProps(entityID);
|
var props = entityPropertiesCache.getProps(entityID);
|
||||||
|
@ -1543,7 +1580,7 @@ function MyController(hand) {
|
||||||
|
|
||||||
this.entityIsNearGrabbable = function(entityID, handPosition, maxDistance) {
|
this.entityIsNearGrabbable = function(entityID, handPosition, maxDistance) {
|
||||||
|
|
||||||
if (!this.entityIsGrabbable(entityID)) {
|
if (!this.entityIsCloneable(entityID) && !this.entityIsGrabbable(entityID)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1742,7 +1779,7 @@ function MyController(hand) {
|
||||||
if (this.getOtherHandController().state === STATE_DISTANCE_HOLDING) {
|
if (this.getOtherHandController().state === STATE_DISTANCE_HOLDING) {
|
||||||
this.setState(STATE_DISTANCE_ROTATING, "distance rotate '" + name + "'");
|
this.setState(STATE_DISTANCE_ROTATING, "distance rotate '" + name + "'");
|
||||||
} else {
|
} else {
|
||||||
this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'");
|
this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
@ -2232,8 +2269,10 @@ function MyController(hand) {
|
||||||
|
|
||||||
// visualizations
|
// visualizations
|
||||||
var rayPickInfo = this.calcRayPickInfo(this.hand);
|
var rayPickInfo = this.calcRayPickInfo(this.hand);
|
||||||
this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition),
|
this.overlayLineOn(rayPickInfo.searchRay.origin,
|
||||||
COLORS_GRAB_DISTANCE_HOLD);
|
Vec3.subtract(grabbedProperties.position, this.offsetPosition),
|
||||||
|
COLORS_GRAB_DISTANCE_HOLD,
|
||||||
|
this.grabbedThingID);
|
||||||
|
|
||||||
var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition));
|
var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition));
|
||||||
var success = Entities.updateAction(this.grabbedThingID, this.actionID, {
|
var success = Entities.updateAction(this.grabbedThingID, this.actionID, {
|
||||||
|
@ -2305,7 +2344,7 @@ function MyController(hand) {
|
||||||
|
|
||||||
var rayPickInfo = this.calcRayPickInfo(this.hand);
|
var rayPickInfo = this.calcRayPickInfo(this.hand);
|
||||||
this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition),
|
this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition),
|
||||||
COLORS_GRAB_DISTANCE_HOLD);
|
COLORS_GRAB_DISTANCE_HOLD, this.grabbedThingID);
|
||||||
|
|
||||||
this.previousWorldControllerRotation = worldControllerRotation;
|
this.previousWorldControllerRotation = worldControllerRotation;
|
||||||
}
|
}
|
||||||
|
@ -2463,6 +2502,9 @@ function MyController(hand) {
|
||||||
this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset);
|
this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This boolean is used to check if the object that is grabbed has just been cloned
|
||||||
|
// It is only set true, if the object that is grabbed creates a new clone.
|
||||||
|
var isClone = false;
|
||||||
var isPhysical = propsArePhysical(grabbedProperties) ||
|
var isPhysical = propsArePhysical(grabbedProperties) ||
|
||||||
(!this.grabbedIsOverlay && entityHasActions(this.grabbedThingID));
|
(!this.grabbedIsOverlay && entityHasActions(this.grabbedThingID));
|
||||||
if (isPhysical && this.state == STATE_NEAR_GRABBING && grabbedProperties.parentID === NULL_UUID) {
|
if (isPhysical && this.state == STATE_NEAR_GRABBING && grabbedProperties.parentID === NULL_UUID) {
|
||||||
|
@ -2480,9 +2522,7 @@ function MyController(hand) {
|
||||||
this.actionID = null;
|
this.actionID = null;
|
||||||
var handJointIndex;
|
var handJointIndex;
|
||||||
if (this.ignoreIK) {
|
if (this.ignoreIK) {
|
||||||
handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ?
|
handJointIndex = this.controllerJointIndex;
|
||||||
"_CONTROLLER_RIGHTHAND" :
|
|
||||||
"_CONTROLLER_LEFTHAND");
|
|
||||||
} else {
|
} else {
|
||||||
handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand");
|
handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand");
|
||||||
}
|
}
|
||||||
|
@ -2501,6 +2541,54 @@ function MyController(hand) {
|
||||||
if (this.grabbedIsOverlay) {
|
if (this.grabbedIsOverlay) {
|
||||||
Overlays.editOverlay(this.grabbedThingID, reparentProps);
|
Overlays.editOverlay(this.grabbedThingID, reparentProps);
|
||||||
} else {
|
} else {
|
||||||
|
if (grabbedProperties.userData.length > 0) {
|
||||||
|
try{
|
||||||
|
var userData = JSON.parse(grabbedProperties.userData);
|
||||||
|
var grabInfo = userData.grabbableKey;
|
||||||
|
if (grabInfo && grabInfo.cloneable) {
|
||||||
|
// Check if
|
||||||
|
var worldEntities = Entities.findEntitiesInBox(Vec3.subtract(MyAvatar.position, {x:25,y:25, z:25}), {x:50, y: 50, z: 50})
|
||||||
|
var count = 0;
|
||||||
|
worldEntities.forEach(function(item) {
|
||||||
|
var item = Entities.getEntityProperties(item, ["name"]);
|
||||||
|
if (item.name === grabbedProperties.name) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
var cloneableProps = Entities.getEntityProperties(grabbedProperties.id);
|
||||||
|
var lifetime = grabInfo.cloneLifetime ? grabInfo.cloneLifetime : 300;
|
||||||
|
var limit = grabInfo.cloneLimit ? grabInfo.cloneLimit : 10;
|
||||||
|
var dynamic = grabInfo.cloneDynamic ? grabInfo.cloneDynamic : false;
|
||||||
|
var cUserData = Object.assign({}, userData);
|
||||||
|
var cProperties = Object.assign({}, cloneableProps);
|
||||||
|
isClone = true;
|
||||||
|
|
||||||
|
if (count > limit) {
|
||||||
|
delete cloneableProps;
|
||||||
|
delete lifetime;
|
||||||
|
delete cUserData;
|
||||||
|
delete cProperties;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete cUserData.grabbableKey.cloneLifetime;
|
||||||
|
delete cUserData.grabbableKey.cloneable;
|
||||||
|
delete cUserData.grabbableKey.cloneDynamic;
|
||||||
|
delete cUserData.grabbableKey.cloneLimit;
|
||||||
|
delete cProperties.id
|
||||||
|
|
||||||
|
cProperties.dynamic = dynamic;
|
||||||
|
cProperties.locked = false;
|
||||||
|
cUserData.grabbableKey.triggerable = true;
|
||||||
|
cUserData.grabbableKey.grabbable = true;
|
||||||
|
cProperties.lifetime = lifetime;
|
||||||
|
cProperties.userData = JSON.stringify(cUserData);
|
||||||
|
var cloneID = Entities.addEntity(cProperties);
|
||||||
|
this.grabbedThingID = cloneID;
|
||||||
|
grabbedProperties = Entities.getEntityProperties(cloneID);
|
||||||
|
}
|
||||||
|
}catch(e) {}
|
||||||
|
}
|
||||||
Entities.editEntity(this.grabbedThingID, reparentProps);
|
Entities.editEntity(this.grabbedThingID, reparentProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2512,7 +2600,6 @@ function MyController(hand) {
|
||||||
this.previousParentID[this.grabbedThingID] = grabbedProperties.parentID;
|
this.previousParentID[this.grabbedThingID] = grabbedProperties.parentID;
|
||||||
this.previousParentJointIndex[this.grabbedThingID] = grabbedProperties.parentJointIndex;
|
this.previousParentJointIndex[this.grabbedThingID] = grabbedProperties.parentJointIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
|
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
|
||||||
action: 'equip',
|
action: 'equip',
|
||||||
grabbedEntity: this.grabbedThingID,
|
grabbedEntity: this.grabbedThingID,
|
||||||
|
@ -2528,22 +2615,37 @@ function MyController(hand) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state == STATE_NEAR_GRABBING) {
|
var _this = this;
|
||||||
this.callEntityMethodOnGrabbed("startNearGrab");
|
/*
|
||||||
|
* Setting context for function that is either called via timer or directly, depending if
|
||||||
|
* if the object in question is a clone. If it is a clone, we need to make sure that the intial equipment event
|
||||||
|
* is called correctly, as these just freshly created entity may not have completely initialized.
|
||||||
|
*/
|
||||||
|
var grabEquipCheck = function () {
|
||||||
|
if (_this.state == STATE_NEAR_GRABBING) {
|
||||||
|
_this.callEntityMethodOnGrabbed("startNearGrab");
|
||||||
} else { // this.state == STATE_HOLD
|
} else { // this.state == STATE_HOLD
|
||||||
this.callEntityMethodOnGrabbed("startEquip");
|
_this.callEntityMethodOnGrabbed("startEquip");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentHandControllerTipPosition =
|
_this.currentHandControllerTipPosition =
|
||||||
(this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition;
|
(_this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition;
|
||||||
this.currentObjectTime = Date.now();
|
_this.currentObjectTime = Date.now();
|
||||||
|
|
||||||
this.currentObjectPosition = grabbedProperties.position;
|
_this.currentObjectPosition = grabbedProperties.position;
|
||||||
this.currentObjectRotation = grabbedProperties.rotation;
|
_this.currentObjectRotation = grabbedProperties.rotation;
|
||||||
this.currentVelocity = ZERO_VEC;
|
_this.currentVelocity = ZERO_VEC;
|
||||||
this.currentAngularVelocity = ZERO_VEC;
|
_this.currentAngularVelocity = ZERO_VEC;
|
||||||
|
|
||||||
this.prevDropDetected = false;
|
_this.prevDropDetected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isClone) {
|
||||||
|
// 100 ms seems to be sufficient time to force the check even occur after the object has been initialized.
|
||||||
|
Script.setTimeout(grabEquipCheck, 100);
|
||||||
|
} else {
|
||||||
|
grabEquipCheck();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.nearGrabbing = function(deltaTime, timestamp) {
|
this.nearGrabbing = function(deltaTime, timestamp) {
|
||||||
|
@ -3227,9 +3329,7 @@ function MyController(hand) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var controllerJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ?
|
var controllerJointIndex = this.controllerJointIndex;
|
||||||
"_CONTROLLER_RIGHTHAND" :
|
|
||||||
"_CONTROLLER_LEFTHAND");
|
|
||||||
if (props.parentJointIndex == controllerJointIndex) {
|
if (props.parentJointIndex == controllerJointIndex) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -3255,9 +3355,7 @@ function MyController(hand) {
|
||||||
children = children.concat(Entities.getChildrenIDsOfJoint(AVATAR_SELF_ID, handJointIndex));
|
children = children.concat(Entities.getChildrenIDsOfJoint(AVATAR_SELF_ID, handJointIndex));
|
||||||
|
|
||||||
// find children of faux controller joint
|
// find children of faux controller joint
|
||||||
var controllerJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ?
|
var controllerJointIndex = this.controllerJointIndex;
|
||||||
"_CONTROLLER_RIGHTHAND" :
|
|
||||||
"_CONTROLLER_LEFTHAND");
|
|
||||||
children = children.concat(Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, controllerJointIndex));
|
children = children.concat(Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, controllerJointIndex));
|
||||||
children = children.concat(Entities.getChildrenIDsOfJoint(AVATAR_SELF_ID, controllerJointIndex));
|
children = children.concat(Entities.getChildrenIDsOfJoint(AVATAR_SELF_ID, controllerJointIndex));
|
||||||
|
|
||||||
|
@ -3269,7 +3367,8 @@ function MyController(hand) {
|
||||||
children = children.concat(Entities.getChildrenIDsOfJoint(AVATAR_SELF_ID, controllerCRJointIndex));
|
children = children.concat(Entities.getChildrenIDsOfJoint(AVATAR_SELF_ID, controllerCRJointIndex));
|
||||||
|
|
||||||
children.forEach(function(childID) {
|
children.forEach(function(childID) {
|
||||||
if (childID !== _this.stylus) {
|
if (childID !== _this.stylus &&
|
||||||
|
childID !== _this.overlayLine) {
|
||||||
// we appear to be holding something and this script isn't in a state that would be holding something.
|
// we appear to be holding something and this script isn't in a state that would be holding something.
|
||||||
// unhook it. if we previously took note of this entity's parent, put it back where it was. This
|
// unhook it. if we previously took note of this entity's parent, put it back where it was. This
|
||||||
// works around some problems that happen when more than one hand or avatar is passing something around.
|
// works around some problems that happen when more than one hand or avatar is passing something around.
|
||||||
|
@ -3365,6 +3464,7 @@ Messages.subscribe('Hifi-Hand-Disabler');
|
||||||
Messages.subscribe('Hifi-Hand-Grab');
|
Messages.subscribe('Hifi-Hand-Grab');
|
||||||
Messages.subscribe('Hifi-Hand-RayPick-Blacklist');
|
Messages.subscribe('Hifi-Hand-RayPick-Blacklist');
|
||||||
Messages.subscribe('Hifi-Object-Manipulation');
|
Messages.subscribe('Hifi-Object-Manipulation');
|
||||||
|
Messages.subscribe('Hifi-Hand-Drop');
|
||||||
|
|
||||||
var handleHandMessages = function(channel, message, sender) {
|
var handleHandMessages = function(channel, message, sender) {
|
||||||
var data;
|
var data;
|
||||||
|
@ -3450,6 +3550,15 @@ var handleHandMessages = function(channel, message, sender) {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("WARNING: handControllerGrab.js -- error parsing Hifi-Hand-RayPick-Blacklist message: " + message);
|
print("WARNING: handControllerGrab.js -- error parsing Hifi-Hand-RayPick-Blacklist message: " + message);
|
||||||
}
|
}
|
||||||
|
} else if (channel === 'Hifi-Hand-Drop') {
|
||||||
|
if (message === 'left') {
|
||||||
|
leftController.release();
|
||||||
|
} else if (message === 'right') {
|
||||||
|
rightController.release();
|
||||||
|
} else if (message === 'both') {
|
||||||
|
leftController.release();
|
||||||
|
rightController.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,12 +17,20 @@ var NUMBER_OF_STEPS = 6;
|
||||||
|
|
||||||
var TARGET_MODEL_URL = Script.resolvePath("../assets/models/teleport-destination.fbx");
|
var TARGET_MODEL_URL = Script.resolvePath("../assets/models/teleport-destination.fbx");
|
||||||
var TOO_CLOSE_MODEL_URL = Script.resolvePath("../assets/models/teleport-cancel.fbx");
|
var TOO_CLOSE_MODEL_URL = Script.resolvePath("../assets/models/teleport-cancel.fbx");
|
||||||
|
var SEAT_MODEL_URL = Script.resolvePath("../assets/models/teleport-seat.fbx");
|
||||||
|
|
||||||
var TARGET_MODEL_DIMENSIONS = {
|
var TARGET_MODEL_DIMENSIONS = {
|
||||||
x: 1.15,
|
x: 1.15,
|
||||||
y: 0.5,
|
y: 0.5,
|
||||||
z: 1.15
|
z: 1.15
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var COLORS_TELEPORT_SEAT = {
|
||||||
|
red: 255,
|
||||||
|
green: 0,
|
||||||
|
blue: 170
|
||||||
|
}
|
||||||
|
|
||||||
var COLORS_TELEPORT_CAN_TELEPORT = {
|
var COLORS_TELEPORT_CAN_TELEPORT = {
|
||||||
red: 97,
|
red: 97,
|
||||||
green: 247,
|
green: 247,
|
||||||
|
@ -35,29 +43,30 @@ var COLORS_TELEPORT_CANNOT_TELEPORT = {
|
||||||
blue: 141
|
blue: 141
|
||||||
};
|
};
|
||||||
|
|
||||||
var COLORS_TELEPORT_TOO_CLOSE = {
|
var COLORS_TELEPORT_CANCEL = {
|
||||||
red: 255,
|
red: 255,
|
||||||
green: 184,
|
green: 184,
|
||||||
blue: 73
|
blue: 73
|
||||||
};
|
};
|
||||||
|
|
||||||
var TELEPORT_CANCEL_RANGE = 1;
|
var TELEPORT_CANCEL_RANGE = 1;
|
||||||
var USE_COOL_IN = true;
|
|
||||||
var COOL_IN_DURATION = 500;
|
var COOL_IN_DURATION = 500;
|
||||||
|
|
||||||
|
const handInfo = {
|
||||||
|
right: {
|
||||||
|
controllerInput: Controller.Standard.RightHand
|
||||||
|
},
|
||||||
|
left: {
|
||||||
|
controllerInput: Controller.Standard.LeftHand
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function ThumbPad(hand) {
|
function ThumbPad(hand) {
|
||||||
this.hand = hand;
|
this.hand = hand;
|
||||||
var _thisPad = this;
|
var _thisPad = this;
|
||||||
|
|
||||||
this.buttonPress = function(value) {
|
this.buttonPress = function(value) {
|
||||||
_thisPad.buttonValue = value;
|
_thisPad.buttonValue = value;
|
||||||
if (value === 0) {
|
|
||||||
if (activationTimeout !== null) {
|
|
||||||
Script.clearTimeout(activationTimeout);
|
|
||||||
activationTimeout = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +76,6 @@ function Trigger(hand) {
|
||||||
|
|
||||||
this.buttonPress = function(value) {
|
this.buttonPress = function(value) {
|
||||||
_this.buttonValue = value;
|
_this.buttonValue = value;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.down = function() {
|
this.down = function() {
|
||||||
|
@ -78,347 +86,224 @@ function Trigger(hand) {
|
||||||
|
|
||||||
var coolInTimeout = null;
|
var coolInTimeout = null;
|
||||||
|
|
||||||
|
var TELEPORTER_STATES = {
|
||||||
|
IDLE: 'idle',
|
||||||
|
COOL_IN: 'cool_in',
|
||||||
|
TARGETTING_INVALID: 'targetting_invalid',
|
||||||
|
}
|
||||||
|
|
||||||
|
var TARGET = {
|
||||||
|
NONE: 'none', // Not currently targetting anything
|
||||||
|
INVISIBLE: 'invisible', // The current target is an invvsible surface
|
||||||
|
INVALID: 'invalid', // The current target is invalid (wall, ceiling, etc.)
|
||||||
|
SURFACE: 'surface', // The current target is a valid surface
|
||||||
|
SEAT: 'seat', // The current target is a seat
|
||||||
|
}
|
||||||
|
|
||||||
function Teleporter() {
|
function Teleporter() {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
this.intersection = null;
|
this.active = false;
|
||||||
this.rightOverlayLine = null;
|
this.state = TELEPORTER_STATES.IDLE;
|
||||||
this.leftOverlayLine = null;
|
this.currentTarget = TARGET.INVALID;
|
||||||
this.targetOverlay = null;
|
|
||||||
this.cancelOverlay = null;
|
|
||||||
this.updateConnected = null;
|
|
||||||
this.smoothArrivalInterval = null;
|
|
||||||
this.teleportHand = null;
|
|
||||||
this.tooClose = false;
|
|
||||||
this.inCoolIn = false;
|
|
||||||
|
|
||||||
this.initialize = function() {
|
this.overlayLines = {
|
||||||
this.createMappings();
|
left: null,
|
||||||
|
right: null,
|
||||||
};
|
};
|
||||||
|
this.updateConnected = null;
|
||||||
|
this.activeHand = null;
|
||||||
|
|
||||||
this.createMappings = function() {
|
this.telporterMappingInternalName = 'Hifi-Teleporter-Internal-Dev-' + Math.random();
|
||||||
teleporter.telporterMappingInternalName = 'Hifi-Teleporter-Internal-Dev-' + Math.random();
|
this.teleportMappingInternal = Controller.newMapping(this.telporterMappingInternalName);
|
||||||
teleporter.teleportMappingInternal = Controller.newMapping(teleporter.telporterMappingInternalName);
|
|
||||||
|
|
||||||
Controller.enableMapping(teleporter.telporterMappingInternalName);
|
// Setup overlays
|
||||||
|
this.cancelOverlay = Overlays.addOverlay("model", {
|
||||||
|
url: TOO_CLOSE_MODEL_URL,
|
||||||
|
dimensions: TARGET_MODEL_DIMENSIONS,
|
||||||
|
visible: false
|
||||||
|
});
|
||||||
|
this.targetOverlay = Overlays.addOverlay("model", {
|
||||||
|
url: TARGET_MODEL_URL,
|
||||||
|
dimensions: TARGET_MODEL_DIMENSIONS,
|
||||||
|
visible: false
|
||||||
|
});
|
||||||
|
this.seatOverlay = Overlays.addOverlay("model", {
|
||||||
|
url: SEAT_MODEL_URL,
|
||||||
|
dimensions: TARGET_MODEL_DIMENSIONS,
|
||||||
|
visible: false
|
||||||
|
});
|
||||||
|
|
||||||
|
this.enableMappings = function() {
|
||||||
|
Controller.enableMapping(this.telporterMappingInternalName);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.disableMappings = function() {
|
this.disableMappings = function() {
|
||||||
Controller.disableMapping(teleporter.telporterMappingInternalName);
|
Controller.disableMapping(teleporter.telporterMappingInternalName);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.enterTeleportMode = function(hand) {
|
this.cleanup = function() {
|
||||||
|
this.disableMappings();
|
||||||
|
|
||||||
|
Overlays.deleteOverlay(this.targetOverlay);
|
||||||
|
this.targetOverlay = null;
|
||||||
|
|
||||||
|
Overlays.deleteOverlay(this.cancelOverlay);
|
||||||
|
this.cancelOverlay = null;
|
||||||
|
|
||||||
|
Overlays.deleteOverlay(this.seatOverlay);
|
||||||
|
this.seatOverlay = null;
|
||||||
|
|
||||||
|
this.deleteOverlayBeams();
|
||||||
|
if (this.updateConnected === true) {
|
||||||
|
Script.update.disconnect(this, this.update);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.enterTeleportMode = function(hand) {
|
||||||
if (inTeleportMode === true) {
|
if (inTeleportMode === true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isDisabled === 'both') {
|
if (isDisabled === 'both' || isDisabled === hand) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
inTeleportMode = true;
|
inTeleportMode = true;
|
||||||
this.inCoolIn = true;
|
|
||||||
if (coolInTimeout !== null) {
|
if (coolInTimeout !== null) {
|
||||||
Script.clearTimeout(coolInTimeout);
|
Script.clearTimeout(coolInTimeout);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.state = TELEPORTER_STATES.COOL_IN;
|
||||||
coolInTimeout = Script.setTimeout(function() {
|
coolInTimeout = Script.setTimeout(function() {
|
||||||
_this.inCoolIn = false;
|
if (_this.state === TELEPORTER_STATES.COOL_IN) {
|
||||||
|
_this.state = TELEPORTER_STATES.TARGETTING;
|
||||||
|
}
|
||||||
}, COOL_IN_DURATION)
|
}, COOL_IN_DURATION)
|
||||||
|
|
||||||
if (this.smoothArrivalInterval !== null) {
|
this.activeHand = hand;
|
||||||
Script.clearInterval(this.smoothArrivalInterval);
|
this.enableMappings();
|
||||||
}
|
Script.update.connect(this, this.update);
|
||||||
if (activationTimeout !== null) {
|
|
||||||
Script.clearInterval(activationTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.teleportHand = hand;
|
|
||||||
this.initialize();
|
|
||||||
Script.update.connect(this.update);
|
|
||||||
this.updateConnected = true;
|
this.updateConnected = true;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.createTargetOverlay = function(visible) {
|
|
||||||
if (visible == undefined) {
|
|
||||||
visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_this.targetOverlay !== null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var targetOverlayProps = {
|
|
||||||
url: TARGET_MODEL_URL,
|
|
||||||
dimensions: TARGET_MODEL_DIMENSIONS,
|
|
||||||
visible: visible
|
|
||||||
};
|
|
||||||
|
|
||||||
_this.targetOverlay = Overlays.addOverlay("model", targetOverlayProps);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
this.createCancelOverlay = function(visible) {
|
|
||||||
if (visible == undefined) {
|
|
||||||
visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_this.cancelOverlay !== null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cancelOverlayProps = {
|
|
||||||
url: TOO_CLOSE_MODEL_URL,
|
|
||||||
dimensions: TARGET_MODEL_DIMENSIONS,
|
|
||||||
visible: visible
|
|
||||||
};
|
|
||||||
|
|
||||||
_this.cancelOverlay = Overlays.addOverlay("model", cancelOverlayProps);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.deleteCancelOverlay = function() {
|
|
||||||
if (this.cancelOverlay === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Overlays.deleteOverlay(this.cancelOverlay);
|
|
||||||
this.cancelOverlay = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hideCancelOverlay = function() {
|
|
||||||
if (this.cancelOverlay === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.intersection = null;
|
|
||||||
Overlays.editOverlay(this.cancelOverlay, { visible: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.showCancelOverlay = function() {
|
|
||||||
if (this.cancelOverlay === null) {
|
|
||||||
return this.createCancelOverlay();
|
|
||||||
}
|
|
||||||
Overlays.editOverlay(this.cancelOverlay, { visible: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
this.deleteTargetOverlay = function() {
|
|
||||||
if (this.targetOverlay === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Overlays.deleteOverlay(this.targetOverlay);
|
|
||||||
this.intersection = null;
|
|
||||||
this.targetOverlay = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hideTargetOverlay = function() {
|
|
||||||
if (this.targetOverlay === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.intersection = null;
|
|
||||||
Overlays.editOverlay(this.targetOverlay, { visible: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.showTargetOverlay = function() {
|
|
||||||
if (this.targetOverlay === null) {
|
|
||||||
return this.createTargetOverlay();
|
|
||||||
}
|
|
||||||
Overlays.editOverlay(this.targetOverlay, { visible: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.turnOffOverlayBeams = function() {
|
|
||||||
this.rightOverlayOff();
|
|
||||||
this.leftOverlayOff();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.exitTeleportMode = function(value) {
|
this.exitTeleportMode = function(value) {
|
||||||
if (activationTimeout !== null) {
|
|
||||||
Script.clearTimeout(activationTimeout);
|
|
||||||
activationTimeout = null;
|
|
||||||
}
|
|
||||||
if (this.updateConnected === true) {
|
if (this.updateConnected === true) {
|
||||||
Script.update.disconnect(this.update);
|
Script.update.disconnect(this, this.update);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.disableMappings();
|
this.disableMappings();
|
||||||
this.turnOffOverlayBeams();
|
this.deleteOverlayBeams();
|
||||||
|
this.hideTargetOverlay();
|
||||||
|
this.hideCancelOverlay();
|
||||||
|
|
||||||
this.updateConnected = null;
|
this.updateConnected = null;
|
||||||
this.inCoolIn = false;
|
this.state = TELEPORTER_STATES.IDLE;
|
||||||
inTeleportMode = false;
|
inTeleportMode = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.update = function() {
|
this.deleteOverlayBeams = function() {
|
||||||
if (isDisabled === 'both') {
|
for (key in this.overlayLines) {
|
||||||
return;
|
if (this.overlayLines[key] !== null) {
|
||||||
}
|
Overlays.deleteOverlay(this.overlayLines[key]);
|
||||||
|
this.overlayLines[key] = null;
|
||||||
if (teleporter.teleportHand === 'left') {
|
|
||||||
if (isDisabled === 'left') {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
teleporter.leftRay();
|
|
||||||
if ((leftPad.buttonValue === 0) && inTeleportMode === true) {
|
|
||||||
if (_this.inCoolIn === true) {
|
|
||||||
_this.exitTeleportMode();
|
|
||||||
_this.hideTargetOverlay();
|
|
||||||
_this.hideCancelOverlay();
|
|
||||||
} else {
|
|
||||||
_this.teleport();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (isDisabled === 'right') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
teleporter.rightRay();
|
|
||||||
if ((rightPad.buttonValue === 0) && inTeleportMode === true) {
|
|
||||||
if (_this.inCoolIn === true) {
|
|
||||||
_this.exitTeleportMode();
|
|
||||||
_this.hideTargetOverlay();
|
|
||||||
_this.hideCancelOverlay();
|
|
||||||
} else {
|
|
||||||
_this.teleport();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
this.rightRay = function() {
|
|
||||||
var pose = Controller.getPoseValue(Controller.Standard.RightHand);
|
|
||||||
var rightPosition = pose.valid ? Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position) : MyAvatar.getHeadPosition();
|
|
||||||
var rightRotation = pose.valid ? Quat.multiply(MyAvatar.orientation, pose.rotation) :
|
|
||||||
Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, {
|
|
||||||
x: 1,
|
|
||||||
y: 0,
|
|
||||||
z: 0
|
|
||||||
}));
|
|
||||||
|
|
||||||
var rightPickRay = {
|
|
||||||
origin: rightPosition,
|
|
||||||
direction: Quat.getUp(rightRotation),
|
|
||||||
};
|
|
||||||
|
|
||||||
this.rightPickRay = rightPickRay;
|
|
||||||
|
|
||||||
var location = Vec3.sum(rightPickRay.origin, Vec3.multiply(rightPickRay.direction, 50));
|
|
||||||
|
|
||||||
|
|
||||||
var rightIntersection = Entities.findRayIntersection(teleporter.rightPickRay, true, [], [this.targetEntity], true, true);
|
|
||||||
|
|
||||||
if (rightIntersection.intersects) {
|
|
||||||
if (this.tooClose === true) {
|
|
||||||
this.hideTargetOverlay();
|
|
||||||
|
|
||||||
this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE);
|
|
||||||
if (this.cancelOverlay !== null) {
|
|
||||||
this.updateCancelOverlay(rightIntersection);
|
|
||||||
} else {
|
|
||||||
this.createCancelOverlay();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.inCoolIn === true) {
|
|
||||||
this.hideTargetOverlay();
|
|
||||||
this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE);
|
|
||||||
if (this.cancelOverlay !== null) {
|
|
||||||
this.updateCancelOverlay(rightIntersection);
|
|
||||||
} else {
|
|
||||||
this.createCancelOverlay();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.hideCancelOverlay();
|
|
||||||
|
|
||||||
this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, COLORS_TELEPORT_CAN_TELEPORT);
|
|
||||||
if (this.targetOverlay !== null) {
|
|
||||||
this.updateTargetOverlay(rightIntersection);
|
|
||||||
} else {
|
|
||||||
this.createTargetOverlay();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
this.hideTargetOverlay();
|
|
||||||
this.rightLineOn(rightPickRay.origin, location, COLORS_TELEPORT_CANNOT_TELEPORT);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.update = function() {
|
||||||
|
if (_this.state === TELEPORTER_STATES.IDLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.leftRay = function() {
|
// Get current hand pose information so that we can get the direction of the teleport beam
|
||||||
var pose = Controller.getPoseValue(Controller.Standard.LeftHand);
|
var pose = Controller.getPoseValue(handInfo[_this.activeHand].controllerInput);
|
||||||
var leftPosition = pose.valid ? Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position) : MyAvatar.getHeadPosition();
|
var handPosition = pose.valid ? Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position) : MyAvatar.getHeadPosition();
|
||||||
var leftRotation = pose.valid ? Quat.multiply(MyAvatar.orientation, pose.rotation) :
|
var handRotation = pose.valid ? Quat.multiply(MyAvatar.orientation, pose.rotation) :
|
||||||
Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, {
|
Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, {
|
||||||
x: 1,
|
x: 1,
|
||||||
y: 0,
|
y: 0,
|
||||||
z: 0
|
z: 0
|
||||||
}));
|
}));
|
||||||
|
|
||||||
var leftPickRay = {
|
var pickRay = {
|
||||||
origin: leftPosition,
|
origin: handPosition,
|
||||||
direction: Quat.getUp(leftRotation),
|
direction: Quat.getUp(handRotation),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.leftPickRay = leftPickRay;
|
// We do up to 2 ray picks to find a teleport location.
|
||||||
|
// There are 2 types of teleport locations we are interested in:
|
||||||
|
// 1. A visible floor. This can be any entity surface that points within some degree of "up"
|
||||||
|
// 2. A seat. The seat can be visible or invisible.
|
||||||
|
//
|
||||||
|
// * In the first pass we pick against visible and invisible entities so that we can find invisible seats.
|
||||||
|
// We might hit an invisible entity that is not a seat, so we need to do a second pass.
|
||||||
|
// * In the second pass we pick against visible entities only.
|
||||||
|
//
|
||||||
|
var intersection = Entities.findRayIntersection(pickRay, true, [], [this.targetEntity], false, true);
|
||||||
|
|
||||||
var location = Vec3.sum(MyAvatar.position, Vec3.multiply(leftPickRay.direction, 50));
|
var teleportLocationType = getTeleportTargetType(intersection);
|
||||||
|
if (teleportLocationType === TARGET.INVISIBLE) {
|
||||||
|
intersection = Entities.findRayIntersection(pickRay, true, [], [this.targetEntity], true, true);
|
||||||
var leftIntersection = Entities.findRayIntersection(teleporter.leftPickRay, true, [], [this.targetEntity], true, true);
|
teleportLocationType = getTeleportTargetType(intersection);
|
||||||
|
}
|
||||||
if (leftIntersection.intersects) {
|
|
||||||
|
|
||||||
if (this.tooClose === true) {
|
|
||||||
this.hideTargetOverlay();
|
|
||||||
this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE);
|
|
||||||
if (this.cancelOverlay !== null) {
|
|
||||||
this.updateCancelOverlay(leftIntersection);
|
|
||||||
} else {
|
|
||||||
this.createCancelOverlay();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.inCoolIn === true) {
|
|
||||||
this.hideTargetOverlay();
|
|
||||||
this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, COLORS_TELEPORT_TOO_CLOSE);
|
|
||||||
if (this.cancelOverlay !== null) {
|
|
||||||
this.updateCancelOverlay(leftIntersection);
|
|
||||||
} else {
|
|
||||||
this.createCancelOverlay();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.hideCancelOverlay();
|
|
||||||
this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, COLORS_TELEPORT_CAN_TELEPORT);
|
|
||||||
|
|
||||||
if (this.targetOverlay !== null) {
|
|
||||||
this.updateTargetOverlay(leftIntersection);
|
|
||||||
} else {
|
|
||||||
this.createTargetOverlay();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
|
if (teleportLocationType === TARGET.NONE) {
|
||||||
this.hideTargetOverlay();
|
this.hideTargetOverlay();
|
||||||
this.leftLineOn(leftPickRay.origin, location, COLORS_TELEPORT_CANNOT_TELEPORT);
|
this.hideCancelOverlay();
|
||||||
|
this.hideSeatOverlay();
|
||||||
|
|
||||||
|
var farPosition = Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, 50));
|
||||||
|
this.updateLineOverlay(_this.activeHand, pickRay.origin, farPosition, COLORS_TELEPORT_CANNOT_TELEPORT);
|
||||||
|
} else if (teleportLocationType === TARGET.INVALID || teleportLocationType === TARGET.INVISIBLE) {
|
||||||
|
this.hideTargetOverlay();
|
||||||
|
this.hideSeatOverlay();
|
||||||
|
|
||||||
|
this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_CANCEL);
|
||||||
|
this.updateDestinationOverlay(this.cancelOverlay, intersection);
|
||||||
|
} else if (teleportLocationType === TARGET.SURFACE) {
|
||||||
|
if (this.state === TELEPORTER_STATES.COOL_IN) {
|
||||||
|
this.hideTargetOverlay();
|
||||||
|
this.hideSeatOverlay();
|
||||||
|
|
||||||
|
this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_CANCEL);
|
||||||
|
this.updateDestinationOverlay(this.cancelOverlay, intersection);
|
||||||
|
} else {
|
||||||
|
this.hideCancelOverlay();
|
||||||
|
this.hideSeatOverlay();
|
||||||
|
|
||||||
|
this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_CAN_TELEPORT);
|
||||||
|
this.updateDestinationOverlay(this.targetOverlay, intersection);
|
||||||
|
}
|
||||||
|
} else if (teleportLocationType === TARGET.SEAT) {
|
||||||
|
this.hideCancelOverlay();
|
||||||
|
this.hideTargetOverlay();
|
||||||
|
|
||||||
|
this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_SEAT);
|
||||||
|
this.updateDestinationOverlay(this.seatOverlay, intersection);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (((_this.activeHand == 'left' ? leftPad : rightPad).buttonValue === 0) && inTeleportMode === true) {
|
||||||
|
this.exitTeleportMode();
|
||||||
|
this.hideCancelOverlay();
|
||||||
|
this.hideTargetOverlay();
|
||||||
|
this.hideSeatOverlay();
|
||||||
|
|
||||||
|
if (teleportLocationType === TARGET.NONE || teleportLocationType === TARGET.INVALID || this.state === TELEPORTER_STATES.COOL_IN) {
|
||||||
|
// Do nothing
|
||||||
|
} else if (teleportLocationType === TARGET.SEAT) {
|
||||||
|
Entities.callEntityMethod(intersection.entityID, 'sit');
|
||||||
|
} else if (teleportLocationType === TARGET.SURFACE) {
|
||||||
|
var offset = getAvatarFootOffset();
|
||||||
|
intersection.intersection.y += offset;
|
||||||
|
MyAvatar.position = intersection.intersection;
|
||||||
|
HMD.centerUI();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.rightLineOn = function(closePoint, farPoint, color) {
|
this.updateLineOverlay = function(hand, closePoint, farPoint, color) {
|
||||||
if (this.rightOverlayLine === null) {
|
if (this.overlayLines[hand] === null) {
|
||||||
var lineProperties = {
|
var lineProperties = {
|
||||||
start: closePoint,
|
start: closePoint,
|
||||||
end: farPoint,
|
end: farPoint,
|
||||||
|
@ -431,10 +316,10 @@ function Teleporter() {
|
||||||
glow: 1.0
|
glow: 1.0
|
||||||
};
|
};
|
||||||
|
|
||||||
this.rightOverlayLine = Overlays.addOverlay("line3d", lineProperties);
|
this.overlayLines[hand] = Overlays.addOverlay("line3d", lineProperties);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
var success = Overlays.editOverlay(this.rightOverlayLine, {
|
var success = Overlays.editOverlay(this.overlayLines[hand], {
|
||||||
start: closePoint,
|
start: closePoint,
|
||||||
end: farPoint,
|
end: farPoint,
|
||||||
color: color
|
color: color
|
||||||
|
@ -442,47 +327,19 @@ function Teleporter() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.leftLineOn = function(closePoint, farPoint, color) {
|
this.hideCancelOverlay = function() {
|
||||||
if (this.leftOverlayLine === null) {
|
Overlays.editOverlay(this.cancelOverlay, { visible: false });
|
||||||
var lineProperties = {
|
|
||||||
ignoreRayIntersection: true,
|
|
||||||
start: closePoint,
|
|
||||||
end: farPoint,
|
|
||||||
color: color,
|
|
||||||
visible: true,
|
|
||||||
alpha: 1,
|
|
||||||
solid: true,
|
|
||||||
glow: 1.0,
|
|
||||||
drawInFront: true
|
|
||||||
};
|
|
||||||
|
|
||||||
this.leftOverlayLine = Overlays.addOverlay("line3d", lineProperties);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
var success = Overlays.editOverlay(this.leftOverlayLine, {
|
|
||||||
start: closePoint,
|
|
||||||
end: farPoint,
|
|
||||||
color: color
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.rightOverlayOff = function() {
|
|
||||||
if (this.rightOverlayLine !== null) {
|
|
||||||
Overlays.deleteOverlay(this.rightOverlayLine);
|
|
||||||
this.rightOverlayLine = null;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.leftOverlayOff = function() {
|
this.hideTargetOverlay = function() {
|
||||||
if (this.leftOverlayLine !== null) {
|
Overlays.editOverlay(this.targetOverlay, { visible: false });
|
||||||
Overlays.deleteOverlay(this.leftOverlayLine);
|
|
||||||
this.leftOverlayLine = null;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.updateTargetOverlay = function(intersection) {
|
this.hideSeatOverlay = function() {
|
||||||
_this.intersection = intersection;
|
Overlays.editOverlay(this.seatOverlay, { visible: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
this.updateDestinationOverlay = function(overlayID, intersection) {
|
||||||
var rotation = Quat.lookAt(intersection.intersection, MyAvatar.position, Vec3.UP);
|
var rotation = Quat.lookAt(intersection.intersection, MyAvatar.position, Vec3.UP);
|
||||||
var euler = Quat.safeEulerAngles(rotation);
|
var euler = Quat.safeEulerAngles(rotation);
|
||||||
var position = {
|
var position = {
|
||||||
|
@ -491,115 +348,15 @@ function Teleporter() {
|
||||||
z: intersection.intersection.z
|
z: intersection.intersection.z
|
||||||
};
|
};
|
||||||
|
|
||||||
this.tooClose = isValidTeleportLocation(position, intersection.surfaceNormal);
|
|
||||||
var towardUs = Quat.fromPitchYawRollDegrees(0, euler.y, 0);
|
var towardUs = Quat.fromPitchYawRollDegrees(0, euler.y, 0);
|
||||||
|
|
||||||
Overlays.editOverlay(this.targetOverlay, {
|
Overlays.editOverlay(overlayID, {
|
||||||
visible: true,
|
visible: true,
|
||||||
position: position,
|
position: position,
|
||||||
rotation: towardUs
|
rotation: towardUs
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.updateCancelOverlay = function(intersection) {
|
|
||||||
_this.intersection = intersection;
|
|
||||||
|
|
||||||
var rotation = Quat.lookAt(intersection.intersection, MyAvatar.position, Vec3.UP);
|
|
||||||
var euler = Quat.safeEulerAngles(rotation);
|
|
||||||
var position = {
|
|
||||||
x: intersection.intersection.x,
|
|
||||||
y: intersection.intersection.y + TARGET_MODEL_DIMENSIONS.y / 2,
|
|
||||||
z: intersection.intersection.z
|
|
||||||
};
|
|
||||||
|
|
||||||
this.tooClose = isValidTeleportLocation(position, intersection.surfaceNormal);
|
|
||||||
var towardUs = Quat.fromPitchYawRollDegrees(0, euler.y, 0);
|
|
||||||
|
|
||||||
Overlays.editOverlay(this.cancelOverlay, {
|
|
||||||
visible: true,
|
|
||||||
position: position,
|
|
||||||
rotation: towardUs
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.triggerHaptics = function() {
|
|
||||||
var hand = this.teleportHand === 'left' ? 0 : 1;
|
|
||||||
var haptic = Controller.triggerShortHapticPulse(0.2, hand);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.teleport = function(value) {
|
|
||||||
|
|
||||||
if (value === undefined) {
|
|
||||||
this.exitTeleportMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.intersection !== null) {
|
|
||||||
if (this.tooClose === true) {
|
|
||||||
this.exitTeleportMode();
|
|
||||||
this.hideCancelOverlay();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var offset = getAvatarFootOffset();
|
|
||||||
this.intersection.intersection.y += offset;
|
|
||||||
this.exitTeleportMode();
|
|
||||||
// Disable smooth arrival, possibly temporarily
|
|
||||||
//this.smoothArrival();
|
|
||||||
MyAvatar.position = _this.intersection.intersection;
|
|
||||||
_this.hideTargetOverlay();
|
|
||||||
_this.hideCancelOverlay();
|
|
||||||
HMD.centerUI();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.findMidpoint = function(start, end) {
|
|
||||||
var xy = Vec3.sum(start, end);
|
|
||||||
var midpoint = Vec3.multiply(0.5, xy);
|
|
||||||
return midpoint
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getArrivalPoints = function(startPoint, endPoint) {
|
|
||||||
var arrivalPoints = [];
|
|
||||||
var i;
|
|
||||||
var lastPoint;
|
|
||||||
|
|
||||||
for (i = 0; i < NUMBER_OF_STEPS; i++) {
|
|
||||||
if (i === 0) {
|
|
||||||
lastPoint = startPoint;
|
|
||||||
}
|
|
||||||
var newPoint = _this.findMidpoint(lastPoint, endPoint);
|
|
||||||
lastPoint = newPoint;
|
|
||||||
arrivalPoints.push(newPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
arrivalPoints.push(endPoint);
|
|
||||||
|
|
||||||
return arrivalPoints;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.smoothArrival = function() {
|
|
||||||
|
|
||||||
_this.arrivalPoints = _this.getArrivalPoints(MyAvatar.position, _this.intersection.intersection);
|
|
||||||
_this.smoothArrivalInterval = Script.setInterval(function() {
|
|
||||||
if (_this.arrivalPoints.length === 0) {
|
|
||||||
Script.clearInterval(_this.smoothArrivalInterval);
|
|
||||||
HMD.centerUI();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var landingPoint = _this.arrivalPoints.shift();
|
|
||||||
MyAvatar.position = landingPoint;
|
|
||||||
|
|
||||||
if (_this.arrivalPoints.length === 1 || _this.arrivalPoints.length === 0) {
|
|
||||||
_this.hideTargetOverlay();
|
|
||||||
_this.hideCancelOverlay();
|
|
||||||
}
|
|
||||||
|
|
||||||
}, SMOOTH_ARRIVAL_SPACING);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.createTargetOverlay(false);
|
|
||||||
this.createCancelOverlay(false);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//related to repositioning the avatar after you teleport
|
//related to repositioning the avatar after you teleport
|
||||||
|
@ -611,20 +368,16 @@ function getAvatarFootOffset() {
|
||||||
var jointName = d.joint;
|
var jointName = d.joint;
|
||||||
if (jointName === "RightUpLeg") {
|
if (jointName === "RightUpLeg") {
|
||||||
upperLeg = d.translation.y;
|
upperLeg = d.translation.y;
|
||||||
}
|
} else if (jointName === "RightLeg") {
|
||||||
if (jointName === "RightLeg") {
|
|
||||||
lowerLeg = d.translation.y;
|
lowerLeg = d.translation.y;
|
||||||
}
|
} else if (jointName === "RightFoot") {
|
||||||
if (jointName === "RightFoot") {
|
|
||||||
foot = d.translation.y;
|
foot = d.translation.y;
|
||||||
}
|
} else if (jointName === "RightToeBase") {
|
||||||
if (jointName === "RightToeBase") {
|
|
||||||
toe = d.translation.y;
|
toe = d.translation.y;
|
||||||
}
|
} else if (jointName === "RightToe_End") {
|
||||||
if (jointName === "RightToe_End") {
|
|
||||||
toeTop = d.translation.y;
|
toeTop = d.translation.y;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
var offset = upperLeg + lowerLeg + foot + toe + toeTop;
|
var offset = upperLeg + lowerLeg + foot + toe + toeTop;
|
||||||
offset = offset / 100;
|
offset = offset / 100;
|
||||||
|
@ -655,7 +408,6 @@ var rightTrigger = new Trigger('right');
|
||||||
|
|
||||||
var mappingName, teleportMapping;
|
var mappingName, teleportMapping;
|
||||||
|
|
||||||
var activationTimeout = null;
|
|
||||||
var TELEPORT_DELAY = 0;
|
var TELEPORT_DELAY = 0;
|
||||||
|
|
||||||
function isMoving() {
|
function isMoving() {
|
||||||
|
@ -668,17 +420,44 @@ function isMoving() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function parseJSON(json) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(json);
|
||||||
|
} catch (e) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
// When determininig whether you can teleport to a location, the normal of the
|
// When determininig whether you can teleport to a location, the normal of the
|
||||||
// point that is being intersected with is looked at. If this normal is more
|
// point that is being intersected with is looked at. If this normal is more
|
||||||
// than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from <0, 1, 0> (straight up), then
|
// than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from <0, 1, 0> (straight up), then
|
||||||
// you can't teleport there.
|
// you can't teleport there.
|
||||||
var MAX_ANGLE_FROM_UP_TO_TELEPORT = 70;
|
const MAX_ANGLE_FROM_UP_TO_TELEPORT = 70;
|
||||||
function isValidTeleportLocation(position, surfaceNormal) {
|
function getTeleportTargetType(intersection) {
|
||||||
|
if (!intersection.intersects) {
|
||||||
|
return TARGET.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
var props = Entities.getEntityProperties(intersection.entityID, ['userData', 'visible']);
|
||||||
|
var data = parseJSON(props.userData);
|
||||||
|
if (data !== undefined && data.seat !== undefined) {
|
||||||
|
return TARGET.SEAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!props.visible) {
|
||||||
|
return TARGET.INVISIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
var surfaceNormal = intersection.surfaceNormal;
|
||||||
var adj = Math.sqrt(surfaceNormal.x * surfaceNormal.x + surfaceNormal.z * surfaceNormal.z);
|
var adj = Math.sqrt(surfaceNormal.x * surfaceNormal.x + surfaceNormal.z * surfaceNormal.z);
|
||||||
var angleUp = Math.atan2(surfaceNormal.y, adj) * (180 / Math.PI);
|
var angleUp = Math.atan2(surfaceNormal.y, adj) * (180 / Math.PI);
|
||||||
return angleUp < (90 - MAX_ANGLE_FROM_UP_TO_TELEPORT) ||
|
|
||||||
angleUp > (90 + MAX_ANGLE_FROM_UP_TO_TELEPORT) ||
|
if (angleUp < (90 - MAX_ANGLE_FROM_UP_TO_TELEPORT) ||
|
||||||
Vec3.distance(MyAvatar.position, position) <= TELEPORT_CANCEL_RANGE;
|
angleUp > (90 + MAX_ANGLE_FROM_UP_TO_TELEPORT) ||
|
||||||
|
Vec3.distance(MyAvatar.position, intersection.intersection) <= TELEPORT_CANCEL_RANGE) {
|
||||||
|
return TARGET.INVALID;
|
||||||
|
} else {
|
||||||
|
return TARGET.SURFACE;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function registerMappings() {
|
function registerMappings() {
|
||||||
|
@ -695,20 +474,13 @@ function registerMappings() {
|
||||||
if (isDisabled === 'left' || isDisabled === 'both') {
|
if (isDisabled === 'left' || isDisabled === 'both') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (activationTimeout !== null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (leftTrigger.down()) {
|
if (leftTrigger.down()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isMoving() === true) {
|
if (isMoving() === true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
activationTimeout = Script.setTimeout(function() {
|
teleporter.enterTeleportMode('left')
|
||||||
Script.clearTimeout(activationTimeout);
|
|
||||||
activationTimeout = null;
|
|
||||||
teleporter.enterTeleportMode('left')
|
|
||||||
}, TELEPORT_DELAY)
|
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
teleportMapping.from(Controller.Standard.RightPrimaryThumb)
|
teleportMapping.from(Controller.Standard.RightPrimaryThumb)
|
||||||
|
@ -716,9 +488,6 @@ function registerMappings() {
|
||||||
if (isDisabled === 'right' || isDisabled === 'both') {
|
if (isDisabled === 'right' || isDisabled === 'both') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (activationTimeout !== null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (rightTrigger.down()) {
|
if (rightTrigger.down()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -726,11 +495,7 @@ function registerMappings() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
activationTimeout = Script.setTimeout(function() {
|
teleporter.enterTeleportMode('right')
|
||||||
teleporter.enterTeleportMode('right')
|
|
||||||
Script.clearTimeout(activationTimeout);
|
|
||||||
activationTimeout = null;
|
|
||||||
}, TELEPORT_DELAY)
|
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -741,18 +506,11 @@ var teleporter = new Teleporter();
|
||||||
|
|
||||||
Controller.enableMapping(mappingName);
|
Controller.enableMapping(mappingName);
|
||||||
|
|
||||||
Script.scriptEnding.connect(cleanup);
|
|
||||||
|
|
||||||
function cleanup() {
|
function cleanup() {
|
||||||
teleportMapping.disable();
|
teleportMapping.disable();
|
||||||
teleporter.disableMappings();
|
teleporter.cleanup();
|
||||||
teleporter.deleteTargetOverlay();
|
|
||||||
teleporter.deleteCancelOverlay();
|
|
||||||
teleporter.turnOffOverlayBeams();
|
|
||||||
if (teleporter.updateConnected !== null) {
|
|
||||||
Script.update.disconnect(teleporter.update);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Script.scriptEnding.connect(cleanup);
|
||||||
|
|
||||||
var isDisabled = false;
|
var isDisabled = false;
|
||||||
var handleHandMessages = function(channel, message, sender) {
|
var handleHandMessages = function(channel, message, sender) {
|
||||||
|
|
|
@ -56,6 +56,7 @@ selectionManager.addEventListener(function () {
|
||||||
lightOverlayManager.updatePositions();
|
lightOverlayManager.updatePositions();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const KEY_P = 80; //Key code for letter p used for Parenting hotkey.
|
||||||
var DEGREES_TO_RADIANS = Math.PI / 180.0;
|
var DEGREES_TO_RADIANS = Math.PI / 180.0;
|
||||||
var RADIANS_TO_DEGREES = 180.0 / Math.PI;
|
var RADIANS_TO_DEGREES = 180.0 / Math.PI;
|
||||||
var epsilon = 0.001;
|
var epsilon = 0.001;
|
||||||
|
@ -843,7 +844,6 @@ function setupModelMenus() {
|
||||||
});
|
});
|
||||||
modelMenuAddedDelete = true;
|
modelMenuAddedDelete = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu.addMenuItem({
|
Menu.addMenuItem({
|
||||||
menuName: "Edit",
|
menuName: "Edit",
|
||||||
menuItemName: "Entity List...",
|
menuItemName: "Entity List...",
|
||||||
|
@ -851,11 +851,25 @@ function setupModelMenus() {
|
||||||
afterItem: "Entities",
|
afterItem: "Entities",
|
||||||
grouping: "Advanced"
|
grouping: "Advanced"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Menu.addMenuItem({
|
||||||
|
menuName: "Edit",
|
||||||
|
menuItemName: "Parent Entity to Last",
|
||||||
|
afterItem: "Entity List...",
|
||||||
|
grouping: "Advanced"
|
||||||
|
});
|
||||||
|
|
||||||
|
Menu.addMenuItem({
|
||||||
|
menuName: "Edit",
|
||||||
|
menuItemName: "Unparent Entity",
|
||||||
|
afterItem: "Parent Entity to Last",
|
||||||
|
grouping: "Advanced"
|
||||||
|
});
|
||||||
Menu.addMenuItem({
|
Menu.addMenuItem({
|
||||||
menuName: "Edit",
|
menuName: "Edit",
|
||||||
menuItemName: "Allow Selecting of Large Models",
|
menuItemName: "Allow Selecting of Large Models",
|
||||||
shortcutKey: "CTRL+META+L",
|
shortcutKey: "CTRL+META+L",
|
||||||
afterItem: "Entity List...",
|
afterItem: "Unparent Entity",
|
||||||
isCheckable: true,
|
isCheckable: true,
|
||||||
isChecked: true,
|
isChecked: true,
|
||||||
grouping: "Advanced"
|
grouping: "Advanced"
|
||||||
|
@ -958,6 +972,8 @@ function cleanupModelMenus() {
|
||||||
Menu.removeMenuItem("Edit", "Delete");
|
Menu.removeMenuItem("Edit", "Delete");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Menu.removeMenuItem("Edit", "Parent Entity to Last");
|
||||||
|
Menu.removeMenuItem("Edit", "Unparent Entity");
|
||||||
Menu.removeMenuItem("Edit", "Entity List...");
|
Menu.removeMenuItem("Edit", "Entity List...");
|
||||||
Menu.removeMenuItem("Edit", "Allow Selecting of Large Models");
|
Menu.removeMenuItem("Edit", "Allow Selecting of Large Models");
|
||||||
Menu.removeMenuItem("Edit", "Allow Selecting of Small Models");
|
Menu.removeMenuItem("Edit", "Allow Selecting of Small Models");
|
||||||
|
@ -990,6 +1006,9 @@ Script.scriptEnding.connect(function () {
|
||||||
|
|
||||||
Overlays.deleteOverlay(importingSVOImageOverlay);
|
Overlays.deleteOverlay(importingSVOImageOverlay);
|
||||||
Overlays.deleteOverlay(importingSVOTextOverlay);
|
Overlays.deleteOverlay(importingSVOTextOverlay);
|
||||||
|
|
||||||
|
Controller.keyReleaseEvent.disconnect(keyReleaseEvent);
|
||||||
|
Controller.keyPressEvent.disconnect(keyPressEvent);
|
||||||
});
|
});
|
||||||
|
|
||||||
var lastOrientation = null;
|
var lastOrientation = null;
|
||||||
|
@ -1101,7 +1120,68 @@ function recursiveDelete(entities, childrenList) {
|
||||||
Entities.deleteEntity(entityID);
|
Entities.deleteEntity(entityID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function unparentSelectedEntities() {
|
||||||
|
if (SelectionManager.hasSelection()) {
|
||||||
|
var selectedEntities = selectionManager.selections;
|
||||||
|
var parentCheck = false;
|
||||||
|
|
||||||
|
if (selectedEntities.length < 1) {
|
||||||
|
Window.notifyEditError("You must have an entity selected inorder to unparent it.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectedEntities.forEach(function (id, index) {
|
||||||
|
var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID;
|
||||||
|
if (parentId !== null && parentId.length > 0 && parentId !== "{00000000-0000-0000-0000-000000000000}") {
|
||||||
|
parentCheck = true;
|
||||||
|
}
|
||||||
|
Entities.editEntity(id, {parentID: null})
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (parentCheck) {
|
||||||
|
if (selectedEntities.length > 1) {
|
||||||
|
Window.notify("Entities unparented");
|
||||||
|
} else {
|
||||||
|
Window.notify("Entity unparented");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (selectedEntities.length > 1) {
|
||||||
|
Window.notify("Selected Entities have no parents");
|
||||||
|
} else {
|
||||||
|
Window.notify("Selected Entity does not have a parent");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Window.notifyEditError("You have nothing selected to unparent");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function parentSelectedEntities() {
|
||||||
|
if (SelectionManager.hasSelection()) {
|
||||||
|
var selectedEntities = selectionManager.selections;
|
||||||
|
if (selectedEntities.length <= 1) {
|
||||||
|
Window.notifyEditError("You must have multiple entities selected in order to parent them");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var parentCheck = false;
|
||||||
|
var lastEntityId = selectedEntities[selectedEntities.length-1];
|
||||||
|
selectedEntities.forEach(function (id, index) {
|
||||||
|
if (lastEntityId !== id) {
|
||||||
|
var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID;
|
||||||
|
if (parentId !== lastEntityId) {
|
||||||
|
parentCheck = true;
|
||||||
|
}
|
||||||
|
Entities.editEntity(id, {parentID: lastEntityId})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(parentCheck) {
|
||||||
|
Window.notify("Entities parented");
|
||||||
|
}else {
|
||||||
|
Window.notify("Entities are already parented to last");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Window.notifyEditError("You have nothing selected to parent");
|
||||||
|
}
|
||||||
|
}
|
||||||
function deleteSelectedEntities() {
|
function deleteSelectedEntities() {
|
||||||
if (SelectionManager.hasSelection()) {
|
if (SelectionManager.hasSelection()) {
|
||||||
selectedParticleEntity = 0;
|
selectedParticleEntity = 0;
|
||||||
|
@ -1164,6 +1244,10 @@ function handeMenuEvent(menuItem) {
|
||||||
Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights"));
|
Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights"));
|
||||||
} else if (menuItem === "Delete") {
|
} else if (menuItem === "Delete") {
|
||||||
deleteSelectedEntities();
|
deleteSelectedEntities();
|
||||||
|
} else if (menuItem === "Parent Entity to Last") {
|
||||||
|
parentSelectedEntities();
|
||||||
|
} else if (menuItem === "Unparent Entity") {
|
||||||
|
unparentSelectedEntities();
|
||||||
} else if (menuItem === "Export Entities") {
|
} else if (menuItem === "Export Entities") {
|
||||||
if (!selectionManager.hasSelection()) {
|
if (!selectionManager.hasSelection()) {
|
||||||
Window.notifyEditError("No entities have been selected.");
|
Window.notifyEditError("No entities have been selected.");
|
||||||
|
@ -1289,13 +1373,12 @@ Window.svoImportRequested.connect(importSVO);
|
||||||
|
|
||||||
Menu.menuItemEvent.connect(handeMenuEvent);
|
Menu.menuItemEvent.connect(handeMenuEvent);
|
||||||
|
|
||||||
Controller.keyPressEvent.connect(function (event) {
|
var keyPressEvent = function (event) {
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
cameraManager.keyPressEvent(event);
|
cameraManager.keyPressEvent(event);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
var keyReleaseEvent = function (event) {
|
||||||
Controller.keyReleaseEvent.connect(function (event) {
|
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
cameraManager.keyReleaseEvent(event);
|
cameraManager.keyReleaseEvent(event);
|
||||||
}
|
}
|
||||||
|
@ -1329,8 +1412,16 @@ Controller.keyReleaseEvent.connect(function (event) {
|
||||||
});
|
});
|
||||||
grid.setPosition(newPosition);
|
grid.setPosition(newPosition);
|
||||||
}
|
}
|
||||||
|
} else if (event.key === KEY_P && event.isControl && !event.isAutoRepeat ) {
|
||||||
|
if (event.isShifted) {
|
||||||
|
unparentSelectedEntities();
|
||||||
|
} else {
|
||||||
|
parentSelectedEntities();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
Controller.keyReleaseEvent.connect(keyReleaseEvent);
|
||||||
|
Controller.keyPressEvent.connect(keyPressEvent);
|
||||||
|
|
||||||
function recursiveAdd(newParentID, parentData) {
|
function recursiveAdd(newParentID, parentData) {
|
||||||
var children = parentData.children;
|
var children = parentData.children;
|
||||||
|
@ -1580,6 +1671,10 @@ var PropertiesTool = function (opts) {
|
||||||
}
|
}
|
||||||
pushCommandForSelections();
|
pushCommandForSelections();
|
||||||
selectionManager._update();
|
selectionManager._update();
|
||||||
|
} else if(data.type === 'parent') {
|
||||||
|
parentSelectedEntities();
|
||||||
|
} else if(data.type === 'unparent') {
|
||||||
|
unparentSelectedEntities();
|
||||||
} else if(data.type === 'saveUserData'){
|
} else if(data.type === 'saveUserData'){
|
||||||
//the event bridge and json parsing handle our avatar id string differently.
|
//the event bridge and json parsing handle our avatar id string differently.
|
||||||
var actualID = data.id.split('"')[1];
|
var actualID = data.id.split('"')[1];
|
||||||
|
@ -1837,6 +1932,9 @@ var PopupMenu = function () {
|
||||||
for (var i = 0; i < overlays.length; i++) {
|
for (var i = 0; i < overlays.length; i++) {
|
||||||
Overlays.deleteOverlay(overlays[i]);
|
Overlays.deleteOverlay(overlays[i]);
|
||||||
}
|
}
|
||||||
|
Controller.mousePressEvent.disconnect(self.mousePressEvent);
|
||||||
|
Controller.mouseMoveEvent.disconnect(self.mouseMoveEvent);
|
||||||
|
Controller.mouseReleaseEvent.disconnect(self.mouseReleaseEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller.mousePressEvent.connect(self.mousePressEvent);
|
Controller.mousePressEvent.connect(self.mousePressEvent);
|
||||||
|
@ -1864,7 +1962,11 @@ var particleExplorerTool = new ParticleExplorerTool();
|
||||||
var selectedParticleEntity = 0;
|
var selectedParticleEntity = 0;
|
||||||
entityListTool.webView.webEventReceived.connect(function (data) {
|
entityListTool.webView.webEventReceived.connect(function (data) {
|
||||||
data = JSON.parse(data);
|
data = JSON.parse(data);
|
||||||
if (data.type === "selectionUpdate") {
|
if(data.type === 'parent') {
|
||||||
|
parentSelectedEntities();
|
||||||
|
} else if(data.type === 'unparent') {
|
||||||
|
unparentSelectedEntities();
|
||||||
|
} else if (data.type === "selectionUpdate") {
|
||||||
var ids = data.entityIds;
|
var ids = data.entityIds;
|
||||||
if (ids.length === 1) {
|
if (ids.length === 1) {
|
||||||
if (Entities.getEntityProperties(ids[0], "type").type === "ParticleEffect") {
|
if (Entities.getEntityProperties(ids[0], "type").type === "ParticleEffect") {
|
||||||
|
|
|
@ -89,6 +89,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div id="no-entities">
|
<div id="no-entities">
|
||||||
No entities found <span id="no-entities-in-view">in view</span> within a <span id="no-entities-radius">100</span> meter radius. Try moving to a different location and refreshing.
|
No entities found <span id="no-entities-in-view">in view</span> within a <span id="no-entities-radius">100</span> meter radius. Try moving to a different location and refreshing.
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
<label for="property-description">Description</label>
|
<label for="property-description">Description</label>
|
||||||
<input type="text" id="property-description">
|
<input type="text" id="property-description">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="property textarea">
|
<div class="property textarea">
|
||||||
<label for="property-user-data">User data</label>
|
<label for="property-user-data">User data</label>
|
||||||
<br>
|
<br>
|
||||||
|
@ -295,12 +295,29 @@
|
||||||
<input type="checkbox" id="property-wants-trigger">
|
<input type="checkbox" id="property-wants-trigger">
|
||||||
<label for="property-wants-trigger">Triggerable</label>
|
<label for="property-wants-trigger">Triggerable</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="property checkbox">
|
||||||
|
<input type="checkbox" id="property-cloneable">
|
||||||
|
<label for="property-cloneable">Cloneable</label>
|
||||||
|
</div>
|
||||||
<div class="property checkbox">
|
<div class="property checkbox">
|
||||||
<input type="checkbox" id="property-ignore-ik">
|
<input type="checkbox" id="property-ignore-ik">
|
||||||
<label for="property-ignore-ik">Ignore inverse kinematics</label>
|
<label for="property-ignore-ik">Ignore inverse kinematics</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="column" id="group-cloneable-group" style="display:none;">
|
||||||
|
<div class="sub-section-header">
|
||||||
|
<span>Cloneable Settings</span>
|
||||||
|
</div>
|
||||||
|
<div class="cloneable-group property gen">
|
||||||
|
<div><label>Clone Lifetime</label><input type="number" data-user-data-type="cloneLifetime" id="property-cloneable-lifetime"></div>
|
||||||
|
<div><label>Clone Limit</label><input type="number" data-user-data-type="cloneLimit" id="property-cloneable-limit"></div>
|
||||||
|
<div class="property checkbox">
|
||||||
|
<input type="checkbox" id="property-cloneable-dynamic">
|
||||||
|
<label for="property-cloneable-dynamic">Clone Dynamic</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr class="behavior-group" />
|
<hr class="behavior-group" />
|
||||||
<div class="behavior-group property url ">
|
<div class="behavior-group property url ">
|
||||||
|
|
|
@ -19,6 +19,7 @@ const VISIBLE_GLYPH = "";
|
||||||
const TRANSPARENCY_GLYPH = "";
|
const TRANSPARENCY_GLYPH = "";
|
||||||
const SCRIPT_GLYPH = "k";
|
const SCRIPT_GLYPH = "k";
|
||||||
const DELETE = 46; // Key code for the delete key.
|
const DELETE = 46; // Key code for the delete key.
|
||||||
|
const KEY_P = 80; // Key code for letter p used for Parenting hotkey.
|
||||||
const MAX_ITEMS = Number.MAX_VALUE; // Used to set the max length of the list of discovered entities.
|
const MAX_ITEMS = Number.MAX_VALUE; // Used to set the max length of the list of discovered entities.
|
||||||
|
|
||||||
debugPrint = function (message) {
|
debugPrint = function (message) {
|
||||||
|
@ -26,7 +27,7 @@ debugPrint = function (message) {
|
||||||
};
|
};
|
||||||
|
|
||||||
function loaded() {
|
function loaded() {
|
||||||
openEventBridge(function() {
|
openEventBridge(function() {
|
||||||
entityList = new List('entity-list', { valueNames: ['name', 'type', 'url', 'locked', 'visible'], page: MAX_ITEMS});
|
entityList = new List('entity-list', { valueNames: ['name', 'type', 'url', 'locked', 'visible'], page: MAX_ITEMS});
|
||||||
entityList.clear();
|
entityList.clear();
|
||||||
elEntityTable = document.getElementById("entity-table");
|
elEntityTable = document.getElementById("entity-table");
|
||||||
|
@ -48,7 +49,7 @@ function loaded() {
|
||||||
elNoEntitiesInView = document.getElementById("no-entities-in-view");
|
elNoEntitiesInView = document.getElementById("no-entities-in-view");
|
||||||
elNoEntitiesRadius = document.getElementById("no-entities-radius");
|
elNoEntitiesRadius = document.getElementById("no-entities-radius");
|
||||||
elEntityTableScroll = document.getElementById("entity-table-scroll");
|
elEntityTableScroll = document.getElementById("entity-table-scroll");
|
||||||
|
|
||||||
document.getElementById("entity-name").onclick = function() {
|
document.getElementById("entity-name").onclick = function() {
|
||||||
setSortColumn('name');
|
setSortColumn('name');
|
||||||
};
|
};
|
||||||
|
@ -90,7 +91,7 @@ function loaded() {
|
||||||
selection = selection.concat(selectedEntities);
|
selection = selection.concat(selectedEntities);
|
||||||
} else if (clickEvent.shiftKey && selectedEntities.length > 0) {
|
} else if (clickEvent.shiftKey && selectedEntities.length > 0) {
|
||||||
var previousItemFound = -1;
|
var previousItemFound = -1;
|
||||||
var clickedItemFound = -1;
|
var clickedItemFound = -1;
|
||||||
for (var entity in entityList.visibleItems) {
|
for (var entity in entityList.visibleItems) {
|
||||||
if (clickedItemFound === -1 && this.dataset.entityId == entityList.visibleItems[entity].values().id) {
|
if (clickedItemFound === -1 && this.dataset.entityId == entityList.visibleItems[entity].values().id) {
|
||||||
clickedItemFound = entity;
|
clickedItemFound = entity;
|
||||||
|
@ -113,11 +114,11 @@ function loaded() {
|
||||||
selection = selection.concat(betweenItems, selectedEntities);
|
selection = selection.concat(betweenItems, selectedEntities);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedEntities = selection;
|
selectedEntities = selection;
|
||||||
|
|
||||||
this.className = 'selected';
|
this.className = 'selected';
|
||||||
|
|
||||||
EventBridge.emitWebEvent(JSON.stringify({
|
EventBridge.emitWebEvent(JSON.stringify({
|
||||||
type: "selectionUpdate",
|
type: "selectionUpdate",
|
||||||
focus: false,
|
focus: false,
|
||||||
|
@ -126,7 +127,7 @@ function loaded() {
|
||||||
|
|
||||||
refreshFooter();
|
refreshFooter();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onRowDoubleClicked() {
|
function onRowDoubleClicked() {
|
||||||
EventBridge.emitWebEvent(JSON.stringify({
|
EventBridge.emitWebEvent(JSON.stringify({
|
||||||
type: "selectionUpdate",
|
type: "selectionUpdate",
|
||||||
|
@ -134,7 +135,7 @@ function loaded() {
|
||||||
entityIds: [this.dataset.entityId],
|
entityIds: [this.dataset.entityId],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const BYTES_PER_MEGABYTE = 1024 * 1024;
|
const BYTES_PER_MEGABYTE = 1024 * 1024;
|
||||||
|
|
||||||
function decimalMegabytes(number) {
|
function decimalMegabytes(number) {
|
||||||
|
@ -173,7 +174,7 @@ function loaded() {
|
||||||
currentElement.onclick = onRowClicked;
|
currentElement.onclick = onRowClicked;
|
||||||
currentElement.ondblclick = onRowDoubleClicked;
|
currentElement.ondblclick = onRowDoubleClicked;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (refreshEntityListTimer) {
|
if (refreshEntityListTimer) {
|
||||||
clearTimeout(refreshEntityListTimer);
|
clearTimeout(refreshEntityListTimer);
|
||||||
}
|
}
|
||||||
|
@ -183,13 +184,13 @@ function loaded() {
|
||||||
item.values({ name: name, url: filename, locked: locked, visible: visible });
|
item.values({ name: name, url: filename, locked: locked, visible: visible });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearEntities() {
|
function clearEntities() {
|
||||||
entities = {};
|
entities = {};
|
||||||
entityList.clear();
|
entityList.clear();
|
||||||
refreshFooter();
|
refreshFooter();
|
||||||
}
|
}
|
||||||
|
|
||||||
var elSortOrder = {
|
var elSortOrder = {
|
||||||
name: document.querySelector('#entity-name .sort-order'),
|
name: document.querySelector('#entity-name .sort-order'),
|
||||||
type: document.querySelector('#entity-type .sort-order'),
|
type: document.querySelector('#entity-type .sort-order'),
|
||||||
|
@ -215,12 +216,12 @@ function loaded() {
|
||||||
entityList.sort(currentSortColumn, { order: currentSortOrder });
|
entityList.sort(currentSortColumn, { order: currentSortOrder });
|
||||||
}
|
}
|
||||||
setSortColumn('type');
|
setSortColumn('type');
|
||||||
|
|
||||||
function refreshEntities() {
|
function refreshEntities() {
|
||||||
clearEntities();
|
clearEntities();
|
||||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'refresh' }));
|
EventBridge.emitWebEvent(JSON.stringify({ type: 'refresh' }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshFooter() {
|
function refreshFooter() {
|
||||||
if (selectedEntities.length > 1) {
|
if (selectedEntities.length > 1) {
|
||||||
elFooter.firstChild.nodeValue = selectedEntities.length + " entities selected";
|
elFooter.firstChild.nodeValue = selectedEntities.length + " entities selected";
|
||||||
|
@ -239,7 +240,7 @@ function loaded() {
|
||||||
entityList.search(elFilter.value);
|
entityList.search(elFilter.value);
|
||||||
refreshFooter();
|
refreshFooter();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSelectedEntities(selectedIDs) {
|
function updateSelectedEntities(selectedIDs) {
|
||||||
var notFound = false;
|
var notFound = false;
|
||||||
for (var id in entities) {
|
for (var id in entities) {
|
||||||
|
@ -262,7 +263,7 @@ function loaded() {
|
||||||
|
|
||||||
return notFound;
|
return notFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
elRefresh.onclick = function() {
|
elRefresh.onclick = function() {
|
||||||
refreshEntities();
|
refreshEntities();
|
||||||
}
|
}
|
||||||
|
@ -282,7 +283,7 @@ function loaded() {
|
||||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
|
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
|
||||||
refreshEntities();
|
refreshEntities();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("keydown", function (keyDownEvent) {
|
document.addEventListener("keydown", function (keyDownEvent) {
|
||||||
if (keyDownEvent.target.nodeName === "INPUT") {
|
if (keyDownEvent.target.nodeName === "INPUT") {
|
||||||
return;
|
return;
|
||||||
|
@ -292,8 +293,15 @@ function loaded() {
|
||||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
|
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
|
||||||
refreshEntities();
|
refreshEntities();
|
||||||
}
|
}
|
||||||
|
if (keyDownEvent.keyCode === KEY_P && keyDownEvent.ctrlKey) {
|
||||||
|
if (keyDownEvent.shiftKey) {
|
||||||
|
EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' }));
|
||||||
|
} else {
|
||||||
|
EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' }));
|
||||||
|
}
|
||||||
|
}
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
var isFilterInView = false;
|
var isFilterInView = false;
|
||||||
var FILTER_IN_VIEW_ATTRIBUTE = "pressed";
|
var FILTER_IN_VIEW_ATTRIBUTE = "pressed";
|
||||||
elNoEntitiesInView.style.display = "none";
|
elNoEntitiesInView.style.display = "none";
|
||||||
|
@ -320,7 +328,7 @@ function loaded() {
|
||||||
if (window.EventBridge !== undefined) {
|
if (window.EventBridge !== undefined) {
|
||||||
EventBridge.scriptEventReceived.connect(function(data) {
|
EventBridge.scriptEventReceived.connect(function(data) {
|
||||||
data = JSON.parse(data);
|
data = JSON.parse(data);
|
||||||
|
|
||||||
if (data.type === "clearEntityList") {
|
if (data.type === "clearEntityList") {
|
||||||
clearEntities();
|
clearEntities();
|
||||||
} else if (data.type == "selectionUpdate") {
|
} else if (data.type == "selectionUpdate") {
|
||||||
|
@ -426,4 +434,3 @@ function loaded() {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}, false);
|
}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,9 +24,10 @@ var ICON_FOR_TYPE = {
|
||||||
}
|
}
|
||||||
|
|
||||||
var EDITOR_TIMEOUT_DURATION = 1500;
|
var EDITOR_TIMEOUT_DURATION = 1500;
|
||||||
|
const KEY_P = 80; //Key code for letter p used for Parenting hotkey.
|
||||||
var colorPickers = [];
|
var colorPickers = [];
|
||||||
var lastEntityID = null;
|
var lastEntityID = null;
|
||||||
|
|
||||||
debugPrint = function(message) {
|
debugPrint = function(message) {
|
||||||
EventBridge.emitWebEvent(
|
EventBridge.emitWebEvent(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
@ -273,7 +274,7 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen
|
||||||
propertyValue += subPropertyString + ',';
|
propertyValue += subPropertyString + ',';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We've unchecked, so remove
|
// We've unchecked, so remove
|
||||||
propertyValue = propertyValue.replace(subPropertyString + ",", "");
|
propertyValue = propertyValue.replace(subPropertyString + ",", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,13 +324,9 @@ function setUserDataFromEditor(noUpdate) {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults) {
|
||||||
function userDataChanger(groupName, keyName, checkBoxElement, userDataElement, defaultValue) {
|
|
||||||
var properties = {};
|
var properties = {};
|
||||||
var parsedData = {};
|
var parsedData = {};
|
||||||
try {
|
try {
|
||||||
|
@ -339,17 +336,31 @@ function userDataChanger(groupName, keyName, checkBoxElement, userDataElement, d
|
||||||
} else {
|
} else {
|
||||||
parsedData = JSON.parse(userDataElement.value);
|
parsedData = JSON.parse(userDataElement.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
if (!(groupName in parsedData)) {
|
if (!(groupName in parsedData)) {
|
||||||
parsedData[groupName] = {}
|
parsedData[groupName] = {}
|
||||||
}
|
}
|
||||||
delete parsedData[groupName][keyName];
|
var keys = Object.keys(updateKeyPair);
|
||||||
if (checkBoxElement.checked !== defaultValue) {
|
keys.forEach(function (key) {
|
||||||
parsedData[groupName][keyName] = checkBoxElement.checked;
|
delete parsedData[groupName][key];
|
||||||
}
|
if (updateKeyPair[key] !== null && updateKeyPair[key] !== "null") {
|
||||||
|
if (updateKeyPair[key] instanceof Element) {
|
||||||
|
if(updateKeyPair[key].type === "checkbox") {
|
||||||
|
if (updateKeyPair[key].checked !== defaults[key]) {
|
||||||
|
parsedData[groupName][key] = updateKeyPair[key].checked;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var val = isNaN(updateKeyPair[key].value) ? updateKeyPair[key].value : parseInt(updateKeyPair[key].value);
|
||||||
|
if (val !== defaults[key]) {
|
||||||
|
parsedData[groupName][key] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parsedData[groupName][key] = updateKeyPair[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
if (Object.keys(parsedData[groupName]).length == 0) {
|
if (Object.keys(parsedData[groupName]).length == 0) {
|
||||||
delete parsedData[groupName];
|
delete parsedData[groupName];
|
||||||
}
|
}
|
||||||
|
@ -368,6 +379,12 @@ function userDataChanger(groupName, keyName, checkBoxElement, userDataElement, d
|
||||||
properties: properties,
|
properties: properties,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
function userDataChanger(groupName, keyName, values, userDataElement, defaultValue) {
|
||||||
|
var val = {}, def = {};
|
||||||
|
val[keyName] = values;
|
||||||
|
def[keyName] = defaultValue;
|
||||||
|
multiDataUpdater(groupName, val, userDataElement, def);
|
||||||
};
|
};
|
||||||
|
|
||||||
function setTextareaScrolling(element) {
|
function setTextareaScrolling(element) {
|
||||||
|
@ -521,6 +538,7 @@ function unbindAllInputs() {
|
||||||
|
|
||||||
function loaded() {
|
function loaded() {
|
||||||
openEventBridge(function() {
|
openEventBridge(function() {
|
||||||
|
|
||||||
var allSections = [];
|
var allSections = [];
|
||||||
var elID = document.getElementById("property-id");
|
var elID = document.getElementById("property-id");
|
||||||
var elType = document.getElementById("property-type");
|
var elType = document.getElementById("property-type");
|
||||||
|
@ -584,6 +602,13 @@ function loaded() {
|
||||||
var elCollisionSoundURL = document.getElementById("property-collision-sound-url");
|
var elCollisionSoundURL = document.getElementById("property-collision-sound-url");
|
||||||
|
|
||||||
var elGrabbable = document.getElementById("property-grabbable");
|
var elGrabbable = document.getElementById("property-grabbable");
|
||||||
|
|
||||||
|
var elCloneable = document.getElementById("property-cloneable");
|
||||||
|
var elCloneableDynamic = document.getElementById("property-cloneable-dynamic");
|
||||||
|
var elCloneableGroup = document.getElementById("group-cloneable-group");
|
||||||
|
var elCloneableLifetime = document.getElementById("property-cloneable-lifetime");
|
||||||
|
var elCloneableLimit = document.getElementById("property-cloneable-limit");
|
||||||
|
|
||||||
var elWantsTrigger = document.getElementById("property-wants-trigger");
|
var elWantsTrigger = document.getElementById("property-wants-trigger");
|
||||||
var elIgnoreIK = document.getElementById("property-ignore-ik");
|
var elIgnoreIK = document.getElementById("property-ignore-ik");
|
||||||
|
|
||||||
|
@ -780,7 +805,7 @@ function loaded() {
|
||||||
if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null && editor !== null) {
|
if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null && editor !== null) {
|
||||||
saveJSONUserData(true);
|
saveJSONUserData(true);
|
||||||
}
|
}
|
||||||
//the event bridge and json parsing handle our avatar id string differently.
|
//the event bridge and json parsing handle our avatar id string differently.
|
||||||
|
|
||||||
lastEntityID = '"' + properties.id + '"';
|
lastEntityID = '"' + properties.id + '"';
|
||||||
elID.innerHTML = properties.id;
|
elID.innerHTML = properties.id;
|
||||||
|
@ -847,8 +872,16 @@ function loaded() {
|
||||||
elCollideOtherAvatar.checked = properties.collidesWith.indexOf("otherAvatar") > -1;
|
elCollideOtherAvatar.checked = properties.collidesWith.indexOf("otherAvatar") > -1;
|
||||||
|
|
||||||
elGrabbable.checked = properties.dynamic;
|
elGrabbable.checked = properties.dynamic;
|
||||||
|
|
||||||
elWantsTrigger.checked = false;
|
elWantsTrigger.checked = false;
|
||||||
elIgnoreIK.checked = true;
|
elIgnoreIK.checked = true;
|
||||||
|
|
||||||
|
elCloneable.checked = false;
|
||||||
|
elCloneableDynamic.checked = false;
|
||||||
|
elCloneableGroup.style.display = elCloneable.checked ? "block": "none";
|
||||||
|
elCloneableLimit.value = 10;
|
||||||
|
elCloneableLifetime.value = 300;
|
||||||
|
|
||||||
var parsedUserData = {}
|
var parsedUserData = {}
|
||||||
try {
|
try {
|
||||||
parsedUserData = JSON.parse(properties.userData);
|
parsedUserData = JSON.parse(properties.userData);
|
||||||
|
@ -863,8 +896,25 @@ function loaded() {
|
||||||
if ("ignoreIK" in parsedUserData["grabbableKey"]) {
|
if ("ignoreIK" in parsedUserData["grabbableKey"]) {
|
||||||
elIgnoreIK.checked = parsedUserData["grabbableKey"].ignoreIK;
|
elIgnoreIK.checked = parsedUserData["grabbableKey"].ignoreIK;
|
||||||
}
|
}
|
||||||
|
if ("cloneable" in parsedUserData["grabbableKey"]) {
|
||||||
|
elCloneable.checked = parsedUserData["grabbableKey"].cloneable;
|
||||||
|
elCloneableGroup.style.display = elCloneable.checked ? "block": "none";
|
||||||
|
elCloneableLimit.value = elCloneable.checked ? 10: 0;
|
||||||
|
elCloneableLifetime.value = elCloneable.checked ? 300: 0;
|
||||||
|
elCloneableDynamic.checked = parsedUserData["grabbableKey"].cloneDynamic ? parsedUserData["grabbableKey"].cloneDynamic : properties.dynamic;
|
||||||
|
elDynamic.checked = elCloneable.checked ? false: properties.dynamic;
|
||||||
|
if (elCloneable.checked) {
|
||||||
|
if ("cloneLifetime" in parsedUserData["grabbableKey"]) {
|
||||||
|
elCloneableLifetime.value = parsedUserData["grabbableKey"].cloneLifetime ? parsedUserData["grabbableKey"].cloneLifetime : 300;
|
||||||
|
}
|
||||||
|
if ("cloneLimit" in parsedUserData["grabbableKey"]) {
|
||||||
|
elCloneableLimit.value = parsedUserData["grabbableKey"].cloneLimit ? parsedUserData["grabbableKey"].cloneLimit : 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
}
|
||||||
|
|
||||||
elCollisionSoundURL.value = properties.collisionSoundURL;
|
elCollisionSoundURL.value = properties.collisionSoundURL;
|
||||||
elLifetime.value = properties.lifetime;
|
elLifetime.value = properties.lifetime;
|
||||||
|
@ -1154,8 +1204,38 @@ function loaded() {
|
||||||
});
|
});
|
||||||
|
|
||||||
elGrabbable.addEventListener('change', function() {
|
elGrabbable.addEventListener('change', function() {
|
||||||
|
if(elCloneable.checked) {
|
||||||
|
elGrabbable.checked = false;
|
||||||
|
}
|
||||||
userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, properties.dynamic);
|
userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, properties.dynamic);
|
||||||
});
|
});
|
||||||
|
elCloneableDynamic.addEventListener('change', function (event){
|
||||||
|
userDataChanger("grabbableKey", "cloneDynamic", event.target, elUserData, -1);
|
||||||
|
});
|
||||||
|
elCloneable.addEventListener('change', function (event) {
|
||||||
|
var checked = event.target.checked;
|
||||||
|
if (checked) {
|
||||||
|
multiDataUpdater("grabbableKey",
|
||||||
|
{cloneLifetime: elCloneableLifetime, cloneLimit: elCloneableLimit, cloneDynamic: elCloneableDynamic, cloneable: event.target},
|
||||||
|
elUserData, {});
|
||||||
|
elCloneableGroup.style.display = "block";
|
||||||
|
EventBridge.emitWebEvent(
|
||||||
|
'{"id":' + lastEntityID + ', "type":"update", "properties":{"dynamic":false, "grabbable": false}}'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
multiDataUpdater("grabbableKey",
|
||||||
|
{cloneLifetime: null, cloneLimit: null, cloneDynamic: null, cloneable: false},
|
||||||
|
elUserData, {});
|
||||||
|
elCloneableGroup.style.display = "none";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var numberListener = function (event) {
|
||||||
|
userDataChanger("grabbableKey", event.target.getAttribute("data-user-data-type"), parseInt(event.target.value), elUserData, false);
|
||||||
|
};
|
||||||
|
elCloneableLifetime.addEventListener('change', numberListener);
|
||||||
|
elCloneableLimit.addEventListener('change', numberListener);
|
||||||
|
|
||||||
elWantsTrigger.addEventListener('change', function() {
|
elWantsTrigger.addEventListener('change', function() {
|
||||||
userDataChanger("grabbableKey", "wantsTrigger", elWantsTrigger, elUserData, false);
|
userDataChanger("grabbableKey", "wantsTrigger", elWantsTrigger, elUserData, false);
|
||||||
});
|
});
|
||||||
|
@ -1390,7 +1470,7 @@ function loaded() {
|
||||||
elZoneFlyingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('flyingAllowed'));
|
elZoneFlyingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('flyingAllowed'));
|
||||||
elZoneGhostingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('ghostingAllowed'));
|
elZoneGhostingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('ghostingAllowed'));
|
||||||
elZoneFilterURL.addEventListener('change', createEmitTextPropertyUpdateFunction('filterURL'));
|
elZoneFilterURL.addEventListener('change', createEmitTextPropertyUpdateFunction('filterURL'));
|
||||||
|
|
||||||
var voxelVolumeSizeChangeFunction = createEmitVec3PropertyUpdateFunction(
|
var voxelVolumeSizeChangeFunction = createEmitVec3PropertyUpdateFunction(
|
||||||
'voxelVolumeSize', elVoxelVolumeSizeX, elVoxelVolumeSizeY, elVoxelVolumeSizeZ);
|
'voxelVolumeSize', elVoxelVolumeSizeX, elVoxelVolumeSizeY, elVoxelVolumeSizeZ);
|
||||||
elVoxelVolumeSizeX.addEventListener('change', voxelVolumeSizeChangeFunction);
|
elVoxelVolumeSizeX.addEventListener('change', voxelVolumeSizeChangeFunction);
|
||||||
|
@ -1441,7 +1521,15 @@ function loaded() {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.addEventListener("keydown", function (keyDown) {
|
||||||
|
if (keyDown.keyCode === KEY_P && keyDown.ctrlKey) {
|
||||||
|
if (keyDown.shiftKey) {
|
||||||
|
EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' }));
|
||||||
|
} else {
|
||||||
|
EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
window.onblur = function() {
|
window.onblur = function() {
|
||||||
// Fake a change event
|
// Fake a change event
|
||||||
var ev = document.createEvent("HTMLEvents");
|
var ev = document.createEvent("HTMLEvents");
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
// Distributed under the Apache License, Version 2.0.
|
// Distributed under the Apache License, Version 2.0.
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
|
||||||
|
const KEY_P = 80; //Key code for letter p used for Parenting hotkey.
|
||||||
|
|
||||||
function loaded() {
|
function loaded() {
|
||||||
openEventBridge(function() {
|
openEventBridge(function() {
|
||||||
elPosY = document.getElementById("horiz-y");
|
elPosY = document.getElementById("horiz-y");
|
||||||
|
@ -131,10 +133,17 @@ function loaded() {
|
||||||
|
|
||||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'init' }));
|
EventBridge.emitWebEvent(JSON.stringify({ type: 'init' }));
|
||||||
});
|
});
|
||||||
|
document.addEventListener("keydown", function (keyDown) {
|
||||||
|
if (keyDown.keyCode === KEY_P && keyDown.ctrlKey) {
|
||||||
|
if (keyDown.shiftKey) {
|
||||||
|
EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' }));
|
||||||
|
} else {
|
||||||
|
EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
// Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked
|
// Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked
|
||||||
document.addEventListener("contextmenu", function (event) {
|
document.addEventListener("contextmenu", function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}, false);
|
}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1170,14 +1170,14 @@ SelectionDisplay = (function() {
|
||||||
// determine which bottom corner we are closest to
|
// determine which bottom corner we are closest to
|
||||||
/*------------------------------
|
/*------------------------------
|
||||||
example:
|
example:
|
||||||
|
|
||||||
BRF +--------+ BLF
|
BRF +--------+ BLF
|
||||||
| |
|
| |
|
||||||
| |
|
| |
|
||||||
BRN +--------+ BLN
|
BRN +--------+ BLN
|
||||||
|
|
||||||
*
|
*
|
||||||
|
|
||||||
------------------------------*/
|
------------------------------*/
|
||||||
|
|
||||||
var cameraPosition = Camera.getPosition();
|
var cameraPosition = Camera.getPosition();
|
||||||
|
@ -2189,8 +2189,12 @@ SelectionDisplay = (function() {
|
||||||
offset = Vec3.multiplyQbyV(properties.rotation, offset);
|
offset = Vec3.multiplyQbyV(properties.rotation, offset);
|
||||||
var boxPosition = Vec3.sum(properties.position, offset);
|
var boxPosition = Vec3.sum(properties.position, offset);
|
||||||
|
|
||||||
|
var color = {red: 255, green: 128, blue: 0};
|
||||||
|
if (i >= selectionManager.selections.length - 1) color = {red: 255, green: 255, blue: 64};
|
||||||
|
|
||||||
Overlays.editOverlay(selectionBoxes[i], {
|
Overlays.editOverlay(selectionBoxes[i], {
|
||||||
position: boxPosition,
|
position: boxPosition,
|
||||||
|
color: color,
|
||||||
rotation: properties.rotation,
|
rotation: properties.rotation,
|
||||||
dimensions: properties.dimensions,
|
dimensions: properties.dimensions,
|
||||||
visible: true,
|
visible: true,
|
||||||
|
@ -2395,7 +2399,7 @@ SelectionDisplay = (function() {
|
||||||
if (wantDebug) {
|
if (wantDebug) {
|
||||||
print("Start Elevation: " + translateXZTool.startingElevation + ", elevation: " + elevation);
|
print("Start Elevation: " + translateXZTool.startingElevation + ", elevation: " + elevation);
|
||||||
}
|
}
|
||||||
if ((translateXZTool.startingElevation > 0.0 && elevation < MIN_ELEVATION) ||
|
if ((translateXZTool.startingElevation > 0.0 && elevation < MIN_ELEVATION) ||
|
||||||
(translateXZTool.startingElevation < 0.0 && elevation > -MIN_ELEVATION)) {
|
(translateXZTool.startingElevation < 0.0 && elevation > -MIN_ELEVATION)) {
|
||||||
if (wantDebug) {
|
if (wantDebug) {
|
||||||
print("too close to horizon!");
|
print("too close to horizon!");
|
||||||
|
@ -3857,7 +3861,7 @@ SelectionDisplay = (function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
that.mousePressEvent = function(event) {
|
that.mousePressEvent = function(event) {
|
||||||
var wantDebug = false;
|
var wantDebug = false;
|
||||||
if (!event.isLeftButton && !that.triggered) {
|
if (!event.isLeftButton && !that.triggered) {
|
||||||
// if another mouse button than left is pressed ignore it
|
// if another mouse button than left is pressed ignore it
|
||||||
return false;
|
return false;
|
||||||
|
@ -3889,7 +3893,7 @@ SelectionDisplay = (function() {
|
||||||
|
|
||||||
if (result.intersects) {
|
if (result.intersects) {
|
||||||
|
|
||||||
|
|
||||||
if (wantDebug) {
|
if (wantDebug) {
|
||||||
print("something intersects... ");
|
print("something intersects... ");
|
||||||
print(" result.overlayID:" + result.overlayID + "[" + overlayNames[result.overlayID] + "]");
|
print(" result.overlayID:" + result.overlayID + "[" + overlayNames[result.overlayID] + "]");
|
||||||
|
@ -3989,7 +3993,7 @@ SelectionDisplay = (function() {
|
||||||
if (wantDebug) {
|
if (wantDebug) {
|
||||||
print("rotate handle case...");
|
print("rotate handle case...");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// After testing our stretch handles, then check out rotate handles
|
// After testing our stretch handles, then check out rotate handles
|
||||||
Overlays.editOverlay(yawHandle, {
|
Overlays.editOverlay(yawHandle, {
|
||||||
|
@ -4211,7 +4215,7 @@ SelectionDisplay = (function() {
|
||||||
case selectionBox:
|
case selectionBox:
|
||||||
activeTool = translateXZTool;
|
activeTool = translateXZTool;
|
||||||
translateXZTool.pickPlanePosition = result.intersection;
|
translateXZTool.pickPlanePosition = result.intersection;
|
||||||
translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y),
|
translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y),
|
||||||
SelectionManager.worldDimensions.z);
|
SelectionManager.worldDimensions.z);
|
||||||
if (wantDebug) {
|
if (wantDebug) {
|
||||||
print("longest dimension: " + translateXZTool.greatestDimension);
|
print("longest dimension: " + translateXZTool.greatestDimension);
|
||||||
|
@ -4220,7 +4224,7 @@ SelectionDisplay = (function() {
|
||||||
translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition);
|
translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition);
|
||||||
print(" starting elevation: " + translateXZTool.startingElevation);
|
print(" starting elevation: " + translateXZTool.startingElevation);
|
||||||
}
|
}
|
||||||
|
|
||||||
mode = translateXZTool.mode;
|
mode = translateXZTool.mode;
|
||||||
activeTool.onBegin(event);
|
activeTool.onBegin(event);
|
||||||
somethingClicked = 'selectionBox';
|
somethingClicked = 'selectionBox';
|
||||||
|
|
|
@ -521,6 +521,9 @@ function onEditError(msg) {
|
||||||
createNotification(wordWrap(msg), NotificationType.EDIT_ERROR);
|
createNotification(wordWrap(msg), NotificationType.EDIT_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onNotify(msg) {
|
||||||
|
createNotification(wordWrap(msg), NotificationType.UNKNOWN); // Needs a generic notification system for user feedback, thus using this
|
||||||
|
}
|
||||||
|
|
||||||
function onSnapshotTaken(pathStillSnapshot, pathAnimatedSnapshot, notify) {
|
function onSnapshotTaken(pathStillSnapshot, pathAnimatedSnapshot, notify) {
|
||||||
if (notify) {
|
if (notify) {
|
||||||
|
@ -637,6 +640,7 @@ Window.domainConnectionRefused.connect(onDomainConnectionRefused);
|
||||||
Window.snapshotTaken.connect(onSnapshotTaken);
|
Window.snapshotTaken.connect(onSnapshotTaken);
|
||||||
Window.processingGif.connect(processingGif);
|
Window.processingGif.connect(processingGif);
|
||||||
Window.notifyEditError = onEditError;
|
Window.notifyEditError = onEditError;
|
||||||
|
Window.notify = onNotify;
|
||||||
|
|
||||||
setup();
|
setup();
|
||||||
|
|
||||||
|
|
|
@ -206,6 +206,17 @@ HighlightedEntity.updateOverlays = function updateHighlightedEntities() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* this contains current gain for a given node (by session id). More efficient than
|
||||||
|
* querying it, plus there isn't a getGain function so why write one */
|
||||||
|
var sessionGains = {};
|
||||||
|
function convertDbToLinear(decibels) {
|
||||||
|
// +20db = 10x, 0dB = 1x, -10dB = 0.1x, etc...
|
||||||
|
// but, your perception is that something 2x as loud is +10db
|
||||||
|
// so we go from -60 to +20 or 1/64x to 4x. For now, we can
|
||||||
|
// maybe scale the signal this way??
|
||||||
|
return Math.pow(2, decibels/10.0);
|
||||||
|
}
|
||||||
|
|
||||||
function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml.
|
function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml.
|
||||||
var data;
|
var data;
|
||||||
switch (message.method) {
|
switch (message.method) {
|
||||||
|
@ -245,18 +256,6 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
||||||
populateUserList(message.params.selected);
|
populateUserList(message.params.selected);
|
||||||
UserActivityLogger.palAction("refresh", "");
|
UserActivityLogger.palAction("refresh", "");
|
||||||
break;
|
break;
|
||||||
case 'updateGain':
|
|
||||||
data = message.params;
|
|
||||||
if (data['isReleased']) {
|
|
||||||
// isReleased=true happens once at the end of a cycle of dragging
|
|
||||||
// the slider about, but with same gain as last isReleased=false so
|
|
||||||
// we don't set the gain in that case, and only here do we want to
|
|
||||||
// send an analytic event.
|
|
||||||
UserActivityLogger.palAction("avatar_gain_changed", data['sessionId']);
|
|
||||||
} else {
|
|
||||||
Users.setAvatarGain(data['sessionId'], data['gain']);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'displayNameUpdate':
|
case 'displayNameUpdate':
|
||||||
if (MyAvatar.displayName !== message.params) {
|
if (MyAvatar.displayName !== message.params) {
|
||||||
MyAvatar.displayName = message.params;
|
MyAvatar.displayName = message.params;
|
||||||
|
@ -323,6 +322,7 @@ function populateUserList(selectData) {
|
||||||
userName: '',
|
userName: '',
|
||||||
sessionId: id || '',
|
sessionId: id || '',
|
||||||
audioLevel: 0.0,
|
audioLevel: 0.0,
|
||||||
|
avgAudioLevel: 0.0,
|
||||||
admin: false,
|
admin: false,
|
||||||
personalMute: !!id && Users.getPersonalMuteStatus(id), // expects proper boolean, not null
|
personalMute: !!id && Users.getPersonalMuteStatus(id), // expects proper boolean, not null
|
||||||
ignore: !!id && Users.getIgnoreStatus(id) // ditto
|
ignore: !!id && Users.getIgnoreStatus(id) // ditto
|
||||||
|
@ -616,41 +616,54 @@ function receiveMessage(channel, messageString, senderID) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var AVERAGING_RATIO = 0.05;
|
var AVERAGING_RATIO = 0.05;
|
||||||
var LOUDNESS_FLOOR = 11.0;
|
var LOUDNESS_FLOOR = 11.0;
|
||||||
var LOUDNESS_SCALE = 2.8 / 5.0;
|
var LOUDNESS_SCALE = 2.8 / 5.0;
|
||||||
var LOG2 = Math.log(2.0);
|
var LOG2 = Math.log(2.0);
|
||||||
|
var AUDIO_PEAK_DECAY = 0.02;
|
||||||
var myData = {}; // we're not includied in ExtendedOverlay.get.
|
var myData = {}; // we're not includied in ExtendedOverlay.get.
|
||||||
|
|
||||||
|
function scaleAudio(val) {
|
||||||
|
var audioLevel = 0.0;
|
||||||
|
if (val <= LOUDNESS_FLOOR) {
|
||||||
|
audioLevel = val / LOUDNESS_FLOOR * LOUDNESS_SCALE;
|
||||||
|
} else {
|
||||||
|
audioLevel = (val -(LOUDNESS_FLOOR -1 )) * LOUDNESS_SCALE;
|
||||||
|
}
|
||||||
|
if (audioLevel > 1.0) {
|
||||||
|
audioLevel = 1;
|
||||||
|
}
|
||||||
|
return audioLevel;
|
||||||
|
}
|
||||||
|
|
||||||
function getAudioLevel(id) {
|
function getAudioLevel(id) {
|
||||||
// the VU meter should work similarly to the one in AvatarInputs: log scale, exponentially averaged
|
// the VU meter should work similarly to the one in AvatarInputs: log scale, exponentially averaged
|
||||||
// But of course it gets the data at a different rate, so we tweak the averaging ratio and frequency
|
// But of course it gets the data at a different rate, so we tweak the averaging ratio and frequency
|
||||||
// of updating (the latter for efficiency too).
|
// of updating (the latter for efficiency too).
|
||||||
var avatar = AvatarList.getAvatar(id);
|
var avatar = AvatarList.getAvatar(id);
|
||||||
var audioLevel = 0.0;
|
var audioLevel = 0.0;
|
||||||
|
var avgAudioLevel = 0.0;
|
||||||
var data = id ? ExtendedOverlay.get(id) : myData;
|
var data = id ? ExtendedOverlay.get(id) : myData;
|
||||||
if (!data) {
|
if (data) {
|
||||||
return audioLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
// we will do exponential moving average by taking some the last loudness and averaging
|
// we will do exponential moving average by taking some the last loudness and averaging
|
||||||
data.accumulatedLevel = AVERAGING_RATIO * (data.accumulatedLevel || 0) + (1 - AVERAGING_RATIO) * (avatar.audioLoudness);
|
data.accumulatedLevel = AVERAGING_RATIO * (data.accumulatedLevel || 0) + (1 - AVERAGING_RATIO) * (avatar.audioLoudness);
|
||||||
|
|
||||||
// add 1 to insure we don't go log() and hit -infinity. Math.log is
|
// add 1 to insure we don't go log() and hit -infinity. Math.log is
|
||||||
// natural log, so to get log base 2, just divide by ln(2).
|
// natural log, so to get log base 2, just divide by ln(2).
|
||||||
var logLevel = Math.log(data.accumulatedLevel + 1) / LOG2;
|
audioLevel = scaleAudio(Math.log(data.accumulatedLevel + 1) / LOG2);
|
||||||
|
|
||||||
if (logLevel <= LOUDNESS_FLOOR) {
|
// decay avgAudioLevel
|
||||||
audioLevel = logLevel / LOUDNESS_FLOOR * LOUDNESS_SCALE;
|
avgAudioLevel = Math.max((1-AUDIO_PEAK_DECAY) * (data.avgAudioLevel || 0), audioLevel);
|
||||||
} else {
|
|
||||||
audioLevel = (logLevel - (LOUDNESS_FLOOR - 1.0)) * LOUDNESS_SCALE;
|
data.avgAudioLevel = avgAudioLevel;
|
||||||
|
data.audioLevel = audioLevel;
|
||||||
|
|
||||||
|
// now scale for the gain. Also, asked to boost the low end, so one simple way is
|
||||||
|
// to take sqrt of the value. Lets try that, see how it feels.
|
||||||
|
avgAudioLevel = Math.min(1.0, Math.sqrt(avgAudioLevel *(sessionGains[id] || 0.75)));
|
||||||
}
|
}
|
||||||
if (audioLevel > 1.0) {
|
return [audioLevel, avgAudioLevel];
|
||||||
audioLevel = 1;
|
|
||||||
}
|
|
||||||
data.audioLevel = audioLevel;
|
|
||||||
return audioLevel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAudioInterval(interval) {
|
function createAudioInterval(interval) {
|
||||||
|
|
257
scripts/tutorials/entity_scripts/sit.js
Normal file
257
scripts/tutorials/entity_scripts/sit.js
Normal file
|
@ -0,0 +1,257 @@
|
||||||
|
(function() {
|
||||||
|
Script.include("/~/system/libraries/utils.js");
|
||||||
|
|
||||||
|
var SETTING_KEY = "com.highfidelity.avatar.isSitting";
|
||||||
|
var ROLE = "fly";
|
||||||
|
var ANIMATION_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/animations/sitting_idle.fbx";
|
||||||
|
var ANIMATION_FPS = 30;
|
||||||
|
var ANIMATION_FIRST_FRAME = 1;
|
||||||
|
var ANIMATION_LAST_FRAME = 10;
|
||||||
|
var RELEASE_KEYS = ['w', 'a', 's', 'd', 'UP', 'LEFT', 'DOWN', 'RIGHT'];
|
||||||
|
var RELEASE_TIME = 500; // ms
|
||||||
|
var RELEASE_DISTANCE = 0.2; // meters
|
||||||
|
var MAX_IK_ERROR = 20;
|
||||||
|
var DESKTOP_UI_CHECK_INTERVAL = 250;
|
||||||
|
var DESKTOP_MAX_DISTANCE = 5;
|
||||||
|
var SIT_DELAY = 25
|
||||||
|
|
||||||
|
this.entityID = null;
|
||||||
|
this.timers = {};
|
||||||
|
this.animStateHandlerID = null;
|
||||||
|
|
||||||
|
this.preload = function(entityID) {
|
||||||
|
this.entityID = entityID;
|
||||||
|
}
|
||||||
|
this.unload = function() {
|
||||||
|
if (MyAvatar.sessionUUID === this.getSeatUser()) {
|
||||||
|
this.sitUp(this.entityID);
|
||||||
|
}
|
||||||
|
if (this.interval) {
|
||||||
|
Script.clearInterval(this.interval);
|
||||||
|
this.interval = null;
|
||||||
|
}
|
||||||
|
this.cleanupOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setSeatUser = function(user) {
|
||||||
|
var userData = Entities.getEntityProperties(this.entityID, ["userData"]).userData;
|
||||||
|
userData = JSON.parse(userData);
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
userData.seat.user = user;
|
||||||
|
} else {
|
||||||
|
delete userData.seat.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entities.editEntity(this.entityID, {
|
||||||
|
userData: JSON.stringify(userData)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.getSeatUser = function() {
|
||||||
|
var properties = Entities.getEntityProperties(this.entityID, ["userData", "position"]);
|
||||||
|
var userData = JSON.parse(properties.userData);
|
||||||
|
|
||||||
|
if (userData.seat.user && userData.seat.user !== MyAvatar.sessionUUID) {
|
||||||
|
var avatar = AvatarList.getAvatar(userData.seat.user);
|
||||||
|
if (avatar && Vec3.distance(avatar.position, properties.position) > RELEASE_DISTANCE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return userData.seat.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.checkSeatForAvatar = function() {
|
||||||
|
var seatUser = this.getSeatUser();
|
||||||
|
var avatarIdentifiers = AvatarList.getAvatarIdentifiers();
|
||||||
|
for (var i in avatarIdentifiers) {
|
||||||
|
var avatar = AvatarList.getAvatar(avatarIdentifiers[i]);
|
||||||
|
if (avatar && avatar.sessionUUID === seatUser) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sitDown = function() {
|
||||||
|
if (this.checkSeatForAvatar()) {
|
||||||
|
print("Someone is already sitting in that chair.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setSeatUser(MyAvatar.sessionUUID);
|
||||||
|
|
||||||
|
var previousValue = Settings.getValue(SETTING_KEY);
|
||||||
|
Settings.setValue(SETTING_KEY, this.entityID);
|
||||||
|
if (previousValue === "") {
|
||||||
|
MyAvatar.characterControllerEnabled = false;
|
||||||
|
MyAvatar.hmdLeanRecenterEnabled = false;
|
||||||
|
MyAvatar.overrideRoleAnimation(ROLE, ANIMATION_URL, ANIMATION_FPS, true, ANIMATION_FIRST_FRAME, ANIMATION_LAST_FRAME);
|
||||||
|
MyAvatar.resetSensorsAndBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
var that = this;
|
||||||
|
Script.setTimeout(function() {
|
||||||
|
var properties = Entities.getEntityProperties(that.entityID, ["position", "rotation"]);
|
||||||
|
var index = MyAvatar.getJointIndex("Hips");
|
||||||
|
MyAvatar.pinJoint(index, properties.position, properties.rotation);
|
||||||
|
|
||||||
|
that.animStateHandlerID = MyAvatar.addAnimationStateHandler(function(properties) {
|
||||||
|
return { headType: 0 };
|
||||||
|
}, ["headType"]);
|
||||||
|
Script.update.connect(that, that.update);
|
||||||
|
Controller.keyPressEvent.connect(that, that.keyPressed);
|
||||||
|
Controller.keyReleaseEvent.connect(that, that.keyReleased);
|
||||||
|
for (var i in RELEASE_KEYS) {
|
||||||
|
Controller.captureKeyEvents({ text: RELEASE_KEYS[i] });
|
||||||
|
}
|
||||||
|
}, SIT_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sitUp = function() {
|
||||||
|
this.setSeatUser(null);
|
||||||
|
|
||||||
|
if (Settings.getValue(SETTING_KEY) === this.entityID) {
|
||||||
|
MyAvatar.restoreRoleAnimation(ROLE);
|
||||||
|
MyAvatar.characterControllerEnabled = true;
|
||||||
|
MyAvatar.hmdLeanRecenterEnabled = true;
|
||||||
|
|
||||||
|
var index = MyAvatar.getJointIndex("Hips");
|
||||||
|
MyAvatar.clearPinOnJoint(index);
|
||||||
|
|
||||||
|
MyAvatar.resetSensorsAndBody();
|
||||||
|
|
||||||
|
Script.setTimeout(function() {
|
||||||
|
MyAvatar.bodyPitch = 0.0;
|
||||||
|
MyAvatar.bodyRoll = 0.0;
|
||||||
|
}, SIT_DELAY);
|
||||||
|
|
||||||
|
Settings.setValue(SETTING_KEY, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
MyAvatar.removeAnimationStateHandler(this.animStateHandlerID);
|
||||||
|
Script.update.disconnect(this, this.update);
|
||||||
|
Controller.keyPressEvent.disconnect(this, this.keyPressed);
|
||||||
|
Controller.keyReleaseEvent.disconnect(this, this.keyReleased);
|
||||||
|
for (var i in RELEASE_KEYS) {
|
||||||
|
Controller.releaseKeyEvents({ text: RELEASE_KEYS[i] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sit = function () {
|
||||||
|
this.sitDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.createOverlay = function() {
|
||||||
|
var text = "Click to sit";
|
||||||
|
var textMargin = 0.05;
|
||||||
|
var lineHeight = 0.15;
|
||||||
|
|
||||||
|
this.overlay = Overlays.addOverlay("text3d", {
|
||||||
|
position: { x: 0.0, y: 0.0, z: 0.0},
|
||||||
|
dimensions: { x: 0.1, y: 0.1 },
|
||||||
|
backgroundColor: { red: 0, green: 0, blue: 0 },
|
||||||
|
color: { red: 255, green: 255, blue: 255 },
|
||||||
|
topMargin: textMargin,
|
||||||
|
leftMargin: textMargin,
|
||||||
|
bottomMargin: textMargin,
|
||||||
|
rightMargin: textMargin,
|
||||||
|
text: text,
|
||||||
|
lineHeight: lineHeight,
|
||||||
|
alpha: 0.9,
|
||||||
|
backgroundAlpha: 0.9,
|
||||||
|
ignoreRayIntersection: true,
|
||||||
|
visible: true,
|
||||||
|
isFacingAvatar: true
|
||||||
|
});
|
||||||
|
var textSize = Overlays.textSize(this.overlay, text);
|
||||||
|
var overlayDimensions = {
|
||||||
|
x: textSize.width + 2 * textMargin,
|
||||||
|
y: textSize.height + 2 * textMargin
|
||||||
|
}
|
||||||
|
var properties = Entities.getEntityProperties(this.entityID, ["position", "registrationPoint", "dimensions"]);
|
||||||
|
var yOffset = (1.0 - properties.registrationPoint.y) * properties.dimensions.y + (overlayDimensions.y / 2.0);
|
||||||
|
var overlayPosition = Vec3.sum(properties.position, { x: 0, y: yOffset, z: 0 });
|
||||||
|
Overlays.editOverlay(this.overlay, {
|
||||||
|
position: overlayPosition,
|
||||||
|
dimensions: overlayDimensions
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.cleanupOverlay = function() {
|
||||||
|
if (this.overlay !== null) {
|
||||||
|
Overlays.deleteOverlay(this.overlay);
|
||||||
|
this.overlay = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.update = function(dt) {
|
||||||
|
if (MyAvatar.sessionUUID === this.getSeatUser()) {
|
||||||
|
var properties = Entities.getEntityProperties(this.entityID, ["position"]);
|
||||||
|
var avatarDistance = Vec3.distance(MyAvatar.position, properties.position);
|
||||||
|
var ikError = MyAvatar.getIKErrorOnLastSolve();
|
||||||
|
if (avatarDistance > RELEASE_DISTANCE || ikError > MAX_IK_ERROR) {
|
||||||
|
print("IK error: " + ikError + ", distance from chair: " + avatarDistance);
|
||||||
|
this.sitUp(this.entityID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.keyPressed = function(event) {
|
||||||
|
if (isInEditMode()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RELEASE_KEYS.indexOf(event.text) !== -1) {
|
||||||
|
var that = this;
|
||||||
|
this.timers[event.text] = Script.setTimeout(function() {
|
||||||
|
that.sitUp();
|
||||||
|
}, RELEASE_TIME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.keyReleased = function(event) {
|
||||||
|
if (RELEASE_KEYS.indexOf(event.text) !== -1) {
|
||||||
|
if (this.timers[event.text]) {
|
||||||
|
Script.clearTimeout(this.timers[event.text]);
|
||||||
|
delete this.timers[event.text];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.canSitDesktop = function() {
|
||||||
|
var properties = Entities.getEntityProperties(this.entityID, ["position"]);
|
||||||
|
var distanceFromSeat = Vec3.distance(MyAvatar.position, properties.position);
|
||||||
|
return distanceFromSeat < DESKTOP_MAX_DISTANCE && !this.checkSeatForAvatar();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hoverEnterEntity = function(event) {
|
||||||
|
if (isInEditMode() || (MyAvatar.sessionUUID === this.getSeatUser())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var that = this;
|
||||||
|
this.interval = Script.setInterval(function() {
|
||||||
|
if (that.overlay === null) {
|
||||||
|
if (that.canSitDesktop()) {
|
||||||
|
that.createOverlay();
|
||||||
|
}
|
||||||
|
} else if (!that.canSitDesktop()) {
|
||||||
|
that.cleanupOverlay();
|
||||||
|
}
|
||||||
|
}, DESKTOP_UI_CHECK_INTERVAL);
|
||||||
|
}
|
||||||
|
this.hoverLeaveEntity = function(event) {
|
||||||
|
if (this.interval) {
|
||||||
|
Script.clearInterval(this.interval);
|
||||||
|
this.interval = null;
|
||||||
|
}
|
||||||
|
this.cleanupOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clickDownOnEntity = function () {
|
||||||
|
if (isInEditMode() || (MyAvatar.sessionUUID === this.getSeatUser())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.canSitDesktop()) {
|
||||||
|
this.sitDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
Loading…
Reference in a new issue