mirror of
https://github.com/overte-org/overte.git
synced 2025-08-04 04:23:33 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into tablet-ui-edit-js
This commit is contained in:
commit
cf37845f9e
24 changed files with 453 additions and 179 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
|
||||||
=========
|
=========
|
||||||
|
|
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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,9 +179,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
||||||
|
|
||||||
_rig->updateFromEyeParameters(eyeParams);
|
_rig->updateFromEyeParameters(eyeParams);
|
||||||
} else {
|
} else {
|
||||||
// no need to call Model::updateRig() because otherAvatars get their joint state
|
Model::updateRig(deltaTime, parentTransform);
|
||||||
// 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.
|
||||||
//
|
//
|
||||||
|
|
|
@ -951,7 +951,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 };
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue