Merge branch 'master' of github.com:highfidelity/hifi into feat/js-script-console-auto-complete

This commit is contained in:
Thijs Wenker 2018-01-22 23:37:24 +01:00
commit e8955fcd32
65 changed files with 1507 additions and 841 deletions

View file

@ -1,5 +1,7 @@
# this guide is specific to Ubuntu 16.04.
# deb packages of High Fidelity domain server and assignment client are stored on debian.highfidelity.com
## This guide is specific to Ubuntu 16.04.
Deb packages of High Fidelity domain server and assignment client are stored on debian.highfidelity.com
```
sudo su -
apt-get -y update
apt-get install -y software-properties-common
@ -8,20 +10,27 @@ add-apt-repository "deb http://debian.highfidelity.com stable main"
apt-get -y update
apt-get install -y hifi-domain-server
apt-get install -y hifi-assignment-client
```
# When installing master/dev builds, the packages are slightly different and you just need to change the last 2 steps to:
When installing master/dev builds, the packages are slightly different and you just need to change the last 2 steps to:
```
apt-get install -y hifi-dev-domain-server
apt-get install -y hifi-dev-assignment-client
```
# domain server and assignment clients should already be running. The processes are controlled via:
Domain server and assignment clients should already be running. The processes are controlled via:
```
systemctl start hifi-domain-server
systemctl stop hifi-domain-server
```
# Once the machine is setup and processes are running you should ensure that your firewall exposes port 40100 on TCP and all UDP ports. This will get your domain up and running and you could connect to it (for now) by using High Fidelity Interface and typing in the IP for the place name. (further customizations can be done via http://IPAddress:40100).
# The server always depends on both hifi-domain-server and hifi-assignment-client running at the same time.
# As an additional step, you should ensure that your packages are automatically updated when a new version goes out. You could, for example, set the automatic update checks to happen every hour (though this could potentially result in the domain being unreachable for a whole hour by new clients when they are released - adjust the update checks accordingly).
Once the machine is setup and processes are running, you should ensure that your firewall exposes port 40100 on TCP and all UDP ports. This will get your domain up and running and you could connect to it (for now) by using High Fidelity Interface and typing in the IP for the place name. (Further customizations can be done via http://IPAddress:40100).
The server always depends on both hifi-domain-server and hifi-assignment-client running at the same time.
As an additional step, you should ensure that your packages are automatically updated when a new version goes out. You could, for example, set the automatic update checks to happen every hour (though this could potentially result in the domain being unreachable for a whole hour by new clients when they are released - adjust the update checks accordingly).
To do this you can modify /etc/crontab by adding the following lines
```
0 */1 * * * root apt-get update
1 */1 * * * root apt-get install --only-upgrade -y hifi-domain-server
2 */1 * * * root apt-get install --only-upgrade -y hifi-assignment-client
```

View file

@ -123,12 +123,12 @@ void ScriptableAvatar::update(float deltatime) {
AnimPose& absPose = absPoses[i];
if (data.rotation != absPose.rot()) {
data.rotation = absPose.rot();
data.rotationSet = true;
data.rotationIsDefaultPose = false;
}
AnimPose& relPose = poses[i];
if (data.translation != relPose.trans()) {
data.translation = relPose.trans();
data.translationSet = true;
data.translationIsDefaultPose = false;
}
}

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 100 100.8" style="enable-background:new 0 0 100 100.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M26.7,83.9c7.3,1.2,14.8,1.8,22.1,1.8c0.4,0,0.8,0,1.2,0c7.8-0.1,15.6-0.8,23.4-2.2l0,0
c5.7-1.1,11.3-6.6,12.5-12.3C87.3,64.2,88,57,88,50s-0.7-14.2-2.1-21.2c-1.2-5.6-6.8-11.1-12.5-12.2c-7.7-1.4-15.6-2.2-23.4-2.2
c-7.7-0.1-15.6,0.5-23.4,1.8c-5.7,1-11.4,6.5-12.6,12.3c-1.4,7.2-2.1,14.4-2.1,21.6s0.7,14.4,2.1,21.7
C15.3,77.4,20.9,82.9,26.7,83.9z M20.9,29.8c0.6-2.9,4-6.3,6.9-6.8c7-1.1,14-1.7,21-1.7c0.4,0,0.8,0,1.2,0
c7.4,0.1,14.8,0.8,22.1,2.1c2.9,0.6,6.4,3.9,6.9,6.7c1.3,6.6,1.9,13.3,1.9,19.9c0,6.6-0.6,13.3-1.9,19.8c-0.6,2.8-4,6.2-6.9,6.8
c-7.3,1.3-14.8,2.1-22.1,2.1c-7.4,0.1-14.8-0.5-22.1-1.7c-2.9-0.5-6.3-3.9-6.9-6.7c-1.3-6.7-2-13.5-2-20.3
C19,43.3,19.6,36.4,20.9,29.8z"/>
<path class="st0" d="M32.3,61.4c-0.5,1.3-0.1,2.8,0.9,3.8c0.3,0.3,7.2,6.6,15.9,6.6c0.8,0,1.7-0.1,2.6-0.2
c9.8-1.5,15.5-11.1,15.8-11.5c0.7-1.2,0.6-2.8-0.2-3.9c-0.9-1.1-2.3-1.6-3.7-1.3c-9.2,2.5-18.6,3.9-28.1,4.2
C34,59.1,32.8,60,32.3,61.4z"/>
<circle class="st0" cx="36.5" cy="42.8" r="9"/>
<path class="st0" d="M61.4,44.1h6.1c1.9,0,3.3-1.5,3.3-3.3c0-1.9-1.5-3.3-3.3-3.3h-6.1c-1.9,0-3.3,1.5-3.3,3.3
C58.1,42.7,59.6,44.1,61.4,44.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View file

@ -16,10 +16,9 @@ import "controls-uit" as HifiControls
Item {
id: root
width: parent.width
height: parent.height
height: parent.height
property var hideQtMethods: true
property var maxUpdateValues: 20
property var maxReloadValues: 200
property var apiMembers: []
@ -30,7 +29,7 @@ Item {
property Component keyboard
Rectangle {
color: "white"
color: hifi.colors.baseGray
width: parent.width
height: parent.height
}
@ -51,32 +50,22 @@ Item {
Row {
id: topBar
anchors.left: parent.left
anchors.leftMargin: 8
anchors.leftMargin: 30
anchors.top: parent.top
anchors.topMargin: 30
width: parent.width
height: 50
HifiControls.GlyphButton {
id: search
enabled: true
glyph: hifi.glyphs.search
color: hifi.colors.text
size: 48
width: 50
height: 50
onClicked: {
addListElements(searchBar.text);
focus = true;
}
}
height: 40
HifiControls.GlyphButton {
id: back;
enabled: true;
color: hifi.buttons.black
glyph: hifi.glyphs.backward
color: hifi.colors.text
size: 48
width: 30
height: 50
anchors.margins: 2
size: 40
width: 40
height: 40
anchors.left: search.right
anchors.leftMargin: 12
onClicked: {
var text = searchBar.text;
var chain = text.split(".");
@ -99,17 +88,20 @@ Item {
}
}
TextField {
id: searchBar
HifiControls.TextField {
id: searchBar
focus: true
font.pixelSize: 16
width: 2*(parent.width-back.width-search.width-reload.width-update.width-evaluate.width-addMember.width-16)/3
height: parent.height
font.family: ralewayRegular.name
isSearchField: true
width: parent.width - 112
height: 40
colorScheme: hifi.colorSchemes.dark
anchors.left: back.right
anchors.leftMargin: 10
font.family: firaSansSemiBold.name
placeholderText: "Search"
onAccepted: {
console.log("Enter Pressed");
search.clicked();
addListElements(searchBar.text);
}
onActiveFocusChanged: {
if (activeFocus && HMD.mounted) {
@ -119,15 +111,27 @@ Item {
}
}
}
}
}
HifiControls.Button {
Row {
id: topBar2
anchors.left: parent.left
anchors.leftMargin: 30
anchors.top: topBar.bottom
anchors.topMargin: 30
width: parent.width -60
height: 40
HifiControls.GlyphButton {
id: addMember;
enabled: true;
text: "+"
width: 50
height: 50
anchors.margins: 2
color: hifi.buttons.black
glyph: hifi.glyphs.maximize
width: 40
height: 40
anchors.top: parent.top
anchors.left: parent.left
onClicked: {
addNewMember();
updateList.start();
@ -138,36 +142,48 @@ Item {
HifiControls.Button {
id: evaluate;
enabled: true;
color: hifi.buttons.black
text: "Eval"
width: 50
height: 50
anchors.margins: 2
width: 40
height: 40
anchors.left: addMember.right
anchors.leftMargin: 12
onClicked: {
evaluateMember();
focus = true;
}
}
TextField {
id: valueBar
focus: true
HifiControls.TextField {
id: valueBar
isSearchField: false
font.pixelSize: 16
width: (parent.width-back.width-search.width-reload.width-update.width-evaluate.width-addMember.width-16)/3
height: parent.height
font.family: ralewayRegular.name
width: parent.width - 208
height: 40
colorScheme: hifi.colorSchemes.dark
font.family: firaSansSemiBold.name
placeholderText: "Value"
textColor: "#4466DD"
anchors.margins: 2
anchors.left: evaluate.right
anchors.leftMargin: 12
onActiveFocusChanged: {
if (activeFocus && HMD.mounted) {
keyboard.raised = true;
} else {
keyboard.raised = false;
}
}
}
HifiControls.GlyphButton {
id: reload;
enabled: false;
color: hifi.buttons.black
glyph: hifi.glyphs.reload
color: hifi.colors.text
size: 48
width: 50
height: 50
anchors.margins: 2
size: 40
width: 40
height: 40
anchors.right: update.left
anchors.rightMargin: 12
onClicked: {
reloadListValues();
focus = true;
@ -177,11 +193,12 @@ Item {
HifiControls.GlyphButton {
id: update;
enabled: false;
color: hifi.buttons.black
glyph: hifi.glyphs.playback_play
size: 48
width: 50
height: 50
anchors.margins: 2
size: 40
width: 40
height: 40
anchors.right: parent.right
onClicked: {
if (isReloading) {
update.glyph = hifi.glyphs.playback_play
@ -196,71 +213,104 @@ Item {
}
}
}
ListModel {
id: memberModel
}
Component {
id: memberDelegate
Row {
id: memberRow
property var isMainKey: apiType === "class";
spacing: 10
Rectangle {
width: isMainKey ? 20 : 40;
height: parent.height
}
RalewayRegular {
text: apiMember
size: !isMainKey ? 16 : 22
MouseArea {
width: list.width
height: parent.height
onClicked: {
searchBar.text = apiType=="function()" ? apiMember + "()" : apiMember;
valueBar.text = !apiValue ? "" : apiValue;
list.currentIndex = index;
evaluatingIdx = index;
}
onDoubleClicked: {
if (apiType === "class") {
addListElements(apiMember+".");
} else {
isolateElement(evaluatingIdx);
}
}
}
}
RalewayRegular {
text: apiType
size: 14
color: hifi.colors.baseGrayHighlight
}
RalewayRegular {
text: !apiValue ? "" : apiValue;
size: 16
color: "#4466DD"
}
}
}
Rectangle {
id: membersBackground
anchors {
left: parent.left; right: parent.right; top: topBar.bottom; bottom: parent.bottom;
margins: hifi.dimensions.contentMargin.x
bottomMargin: hifi.dimensions.contentSpacing.y + 40
left: parent.left; right: parent.right; top: topBar2.bottom; bottom: bottomBar.top;
margins: 30
}
color: hifi.colors.tableBackgroundDark
border.color: hifi.colors.lightGray
border.width: 2
radius: 5
ListModel {
id: memberModel
}
Component {
id: memberDelegate
Item {
id: item
width: parent.width
anchors.left: parent.left
height: 26
clip: true
Rectangle {
width: parent.width
height: parent.height
color: index % 2 == 0 ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd
anchors.verticalCenter: parent.verticalCenter
Row {
id: memberRow
anchors.bottom: parent.bottom
anchors.verticalCenter: parent.verticalCenter
spacing: 10
FiraSansSemiBold {
property var isMainKey: apiType === "class";
text: apiMember
size: isMainKey ? 17 : 15
font.bold: true
anchors.verticalCenter: parent.verticalCenter
color: isMainKey ? hifi.colors.faintGray : hifi.colors.lightGrayText
MouseArea {
width: list.width
height: parent.height
onClicked: {
searchBar.text = apiType=="function()" ? apiMember + "()" : apiMember;
valueBar.text = !apiValue ? "" : apiValue;
list.currentIndex = index;
evaluatingIdx = index;
}
onDoubleClicked: {
if (apiType === "class") {
addListElements(apiMember+".");
} else {
isolateElement(evaluatingIdx);
}
}
}
}
FiraSansRegular {
text: apiType
anchors.left: apiMember.right
anchors.verticalCenter: parent.verticalCenter
size: 13
color: hifi.colors.lightGrayText
}
FiraSansRegular {
text: !apiValue ? "" : apiValue;
anchors.left: apiType.right
anchors.verticalCenter: parent.verticalCenter
size: 14
color: hifi.colors.primaryHighlight
}
}
}
}
}
Component {
id: highlight
Rectangle {
anchors {
left: list.left
right: scrollBar.left
leftMargin: 2
rightMargin: 2
}
color: hifi.colors.primaryHighlight
radius: 4
z: 10
opacity: 0.5
}
}
color: "white"
radius: 4
ListView {
id: list
@ -269,23 +319,16 @@ Item {
left: parent.left
right: scrollBar.left
bottom: parent.bottom
margins: 4
topMargin: 2
leftMargin: 2
bottomMargin: 2
}
clip: true
cacheBuffer: 4000
model: memberModel
delegate: memberDelegate
highlightMoveDuration: 0
highlight: Rectangle {
anchors {
left: parent ? parent.left : undefined
right: parent ? parent.right : undefined
leftMargin: hifi.dimensions.borderWidth
rightMargin: hifi.dimensions.borderWidth
}
color: "#BBDDFF"
}
highlight: highlight
onMovementStarted: {
scrollSlider.manual = true;
}
@ -310,12 +353,11 @@ Item {
top: parent.top
right: parent.right
bottom: parent.bottom
topMargin: 4
bottomMargin: 4
margins: 2
}
width: scrolling ? 18 : 0
radius: 4
color: hifi.colors.baseGrayShadow
width: 22
height: parent.height - 4
color: hifi.colors.tableScrollBackgroundDark
MouseArea {
anchors.fill: parent
@ -344,14 +386,12 @@ Item {
y = index*(scrollBar.height - scrollSlider.height)/(list.count - 1);
}
anchors {
right: parent.right
rightMargin: 3
}
width: 12
height: (list.height / list.contentHeight) * list.height
radius: width / 4
color: "white"
anchors.right: parent.right
anchors.margins: 2
width: 18
height: ((list.height / list.contentHeight) * list.height) < 15 ? 15 : (list.height / list.contentHeight) * list.height
radius: 5
color: hifi.colors.tableScrollHandleDark
visible: scrollBar.scrolling;
@ -373,66 +413,75 @@ Item {
}
}
}
HifiControls.GlyphButton {
id: clipboard;
enabled: true;
glyph: hifi.glyphs.scriptNew
size: 38
width: 50
height: 50
Row {
id: bottomBar
anchors.left: parent.left
anchors.leftMargin: 30
anchors.bottom: parent.bottom
anchors.margins: 2
anchors.leftMargin: 8
onClicked: {
var buffer = "";
for (var i = 0; i < memberModel.count; i++) {
var datarow = memberModel.get(i);
buffer += "\n" + datarow.apiMember + " " + datarow.apiType + " " + datarow.apiValue;
}
Window.copyToClipboard(buffer);
focus = true;
}
}
HifiControls.Button {
id: debug;
enabled: true;
text: "Debug Script"
width: 120
height: 50
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: 2
anchors.rightMargin: 8
onClicked: {
sendToScript({type: "selectScript"});
}
}
anchors.bottomMargin: 30
width: parent.width
height: 40
HifiControls.CheckBox {
id: hideQt
boxSize: 25
boxRadius: 3
checked: true
anchors.left: clipboard.right
anchors.leftMargin: 8
anchors.verticalCenter: clipboard.verticalCenter
anchors.margins: 2
onClicked: {
hideQtMethods = checked;
addListElements();
HifiControls.GlyphButton {
id: clipboard;
enabled: true;
color: hifi.buttons.black
glyph: hifi.glyphs.scriptNew
size: 25
width: 40
height: 40
anchors.left: parent.left
onClicked: {
var buffer = "";
for (var i = 0; i < memberModel.count; i++) {
var datarow = memberModel.get(i);
buffer += "\n" + datarow.apiMember + " " + datarow.apiType + " " + datarow.apiValue;
}
Window.copyToClipboard(buffer);
focus = true;
}
}
}
HifiControls.Label {
id: hideLabel
anchors.left: hideQt.right
anchors.verticalCenter: clipboard.verticalCenter
anchors.margins: 2
font.pixelSize: 15
text: "Hide Qt Methods"
HifiControls.CheckBox {
id: hideQt
colorScheme: hifi.checkbox.dark
boxSize: 25
boxRadius: 3
checked: true
anchors.left: clipboard.right
anchors.leftMargin: 10
anchors.verticalCenter: clipboard.verticalCenter
onClicked: {
hideQtMethods = checked;
addListElements();
}
}
HifiControls.Label {
id: hideLabel
anchors.left: hideQt.right
anchors.verticalCenter: clipboard.verticalCenter
anchors.margins: 2
font.pixelSize: 15
text: "Hide Qt Methods"
}
HifiControls.Button {
id: debug;
enabled: true;
color: hifi.buttons.black
text: "Debug Script"
width: 120
height: 40
anchors.right: parent.right
anchors.rightMargin: 60
anchors.bottom: parent.bottom
onClicked: {
sendToScript({type: "selectScript"});
}
}
}
HifiControls.Keyboard {
@ -639,4 +688,4 @@ Item {
}
signal sendToScript(var message);
}
}

View file

@ -24,10 +24,13 @@ TextField {
property bool isSearchField: false
property string label: ""
property real controlHeight: height + (textFieldLabel.visible ? textFieldLabel.height + 1 : 0)
property bool hasDefocusedBorder: true;
property bool hasRoundedBorder: false
property int roundedBorderRadius: 4
property bool error: false;
property bool hasClearButton: false;
property string leftPlaceholderGlyph: "";
property string leftPermanentGlyph: "";
property string centerPlaceholderGlyph: "";
placeholderText: textField.placeholderText
@ -101,12 +104,12 @@ TextField {
}
}
border.color: textField.error ? hifi.colors.redHighlight :
(textField.activeFocus ? hifi.colors.primaryHighlight : (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray))
(textField.activeFocus ? hifi.colors.primaryHighlight : (hasDefocusedBorder ? (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray) : color))
border.width: textField.activeFocus || hasRoundedBorder || textField.error ? 1 : 0
radius: isSearchField ? textField.height / 2 : (hasRoundedBorder ? 4 : 0)
radius: isSearchField ? textField.height / 2 : (hasRoundedBorder ? roundedBorderRadius : 0)
HiFiGlyphs {
text: textField.leftPlaceholderGlyph;
text: textField.leftPermanentGlyph;
color: textColor;
size: hifi.fontSizes.textFieldSearchIcon;
anchors.left: parent.left;
@ -115,6 +118,15 @@ TextField {
visible: text;
}
HiFiGlyphs {
text: textField.centerPlaceholderGlyph;
color: textColor;
size: parent.height;
anchors.horizontalCenter: parent.horizontalCenter;
anchors.verticalCenter: parent.verticalCenter;
visible: text && !textField.focus && textField.text === "";
}
HiFiGlyphs {
text: hifi.glyphs.search
color: textColor
@ -145,7 +157,7 @@ TextField {
placeholderTextColor: isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray
selectedTextColor: hifi.colors.black
selectionColor: hifi.colors.primaryHighlight
padding.left: ((isSearchField || textField.leftPlaceholderGlyph !== "") ? textField.height - 2 : 0) + hifi.dimensions.textPadding
padding.left: hasRoundedBorder ? textField.height / 2 : ((isSearchField || textField.leftPermanentGlyph !== "") ? textField.height - 2 : 0) + hifi.dimensions.textPadding
padding.right: (hasClearButton ? textField.height - 2 : 0) + hifi.dimensions.textPadding
}

View file

@ -389,7 +389,7 @@ Rectangle {
//
Item {
id: tabButtonsContainer;
visible: !needsLogIn.visible && root.activeView !== "passphraseChange" && root.activeView !== "securityImageChange";
visible: !needsLogIn.visible && root.activeView !== "passphraseChange" && root.activeView !== "securityImageChange" && sendMoney.currentActiveView !== "sendMoneyStep";
property int numTabs: 5;
// Size
width: root.width;

View file

@ -29,13 +29,13 @@ Item {
property string userName;
property string profilePicUrl;
height: 65;
height: 75;
width: parent.width;
Rectangle {
id: mainContainer;
// Style
color: root.isSelected ? hifi.colors.faintGray : hifi.colors.white;
color: root.isSelected ? hifi.colors.faintGray80 : hifi.colors.white;
// Size
anchors.left: parent.left;
anchors.right: parent.right;
@ -49,7 +49,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter;
anchors.left: parent.left;
anchors.leftMargin: 36;
height: root.height - 15;
height: 50;
width: visible ? height : 0;
clip: true;
Image {
@ -83,15 +83,15 @@ Item {
RalewaySemiBold {
id: userName;
anchors.left: avatarImage.right;
anchors.leftMargin: 16;
anchors.leftMargin: 12;
anchors.top: parent.top;
anchors.bottom: parent.bottom;
anchors.right: chooseButton.visible ? chooseButton.left : parent.right;
anchors.rightMargin: chooseButton.visible ? 10 : 0;
// Text size
size: 20;
size: 18;
// Style
color: hifi.colors.baseGray;
color: hifi.colors.blueAccent;
text: root.userName;
elide: Text.ElideRight;
// Alignment
@ -107,9 +107,9 @@ Item {
colorScheme: hifi.colorSchemes.dark;
anchors.verticalCenter: parent.verticalCenter;
anchors.right: parent.right;
anchors.rightMargin: 24;
height: root.height - 20;
width: 110;
anchors.rightMargin: 28;
height: 35;
width: 100;
text: "CHOOSE";
onClicked: {
var msg = { method: 'chooseConnection', userName: root.userName, profilePicUrl: root.profilePicUrl };

View file

@ -29,6 +29,7 @@ Item {
property string displayName;
property string userName;
property string profilePic;
property string textColor: hifi.colors.white;
Item {
visible: root.isDisplayingNearby;
@ -46,7 +47,7 @@ Item {
// Text size
size: 18;
// Style
color: hifi.colors.baseGray;
color: root.textColor;
verticalAlignment: Text.AlignBottom;
elide: Text.ElideRight;
}
@ -63,7 +64,7 @@ Item {
// Text size
size: 16;
// Style
color: hifi.colors.baseGray;
color: root.textColor;
verticalAlignment: Text.AlignTop;
elide: Text.ElideRight;
}
@ -108,7 +109,7 @@ Item {
// Text size
size: 16;
// Style
color: hifi.colors.baseGray;
color: root.textColor;
verticalAlignment: Text.AlignVCenter;
elide: Text.ElideRight;
}

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<style type="text/css">
.st0{fill:#1398BB;}
</style>
<path class="st0" d="M256.8,42C138.5,42,42.2,138.3,42.2,256.6s96.3,214.6,214.6,214.6c118.3,0,214.6-96.3,214.6-214.6
S375.1,42,256.8,42z M256.8,444.4C153.2,444.4,69,360.1,69,256.6C69,153,153.2,68.7,256.8,68.7c103.6,0,187.8,84.3,187.8,187.8
C444.6,360.1,360.4,444.4,256.8,444.4z"/>
<circle class="st0" cx="260.6" cy="189.4" r="60.6"/>
<path class="st0" d="M306.4,282.6h-87.6c-36.5,0-66.4,30.2-66.4,66.7v33.9c29.3,22.6,65.4,36.1,105,36.1c44.4,0,84.7-17,115.2-44.7
v-25.3C372.7,312.7,342.9,282.6,306.4,282.6z"/>
</svg>

After

Width:  |  Height:  |  Size: 921 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<style type="text/css">
.st0{fill:#1398BB;}
</style>
<path class="st0" d="M144.3,155c-8.7-8.8-16.7-17.1-25.2-25.7c-22.4,25.4-36.6,53.9-43.6,86.2c-26.9,125.7,81.2,241.5,208.6,223.4
C385.9,424.4,458,329.2,443.5,228.2c-4.4-30.6-15.4-58.9-34.1-83.9c-1.6-2.2-3.3-4.3-4.7-6.6c-4.4-7.4-3.1-14.9,3-19.5
c6-4.5,14.2-3.6,19.2,3.5c8.2,11.7,16.6,23.4,22.9,36.1c41.9,84.9,25.7,181.7-41,248.7c-67,67.4-175.1,81.3-257.1,33.3
c-35.3-20.7-63.1-48.6-82.7-84.5c-41-75-31.5-172.5,23.4-237.9c2.2-2.7,4.4-5.4,7.1-8.7c-8.1-8.5-16.1-16.9-24-25.3
c-7.2-7.7-7.9-16.1-1.9-22.1c5.9-5.9,15-5.1,22.1,2.4c56.8,59.5,113.6,119,170.4,178.6c7.2,7.6,7.8,15.4,1.9,21.4
c-6.1,6.2-14.7,5.5-22-2c-10.2-10.4-20.2-21-30.3-31.5c-17.4,24.7-3.3,62,27.2,72.5c23,7.9,48.8-2,60.7-23.1
c1.2-2.1,2.3-4.3,3.7-6.3c4.5-6.6,11.7-8.6,18.3-5c6.6,3.5,8.9,10.5,5.9,18.1c-12.4,31.6-47.9,52.2-82.3,47.7
c-36.1-4.8-64.3-32.6-68.4-67.7c-2.4-20.7,2.4-39.7,14.9-57.5c-10.4-10.9-20.8-21.6-31.7-33c-9.7,10.8-16.2,22.7-20.9,35.7
c-31.3,86.4,38.9,175.1,130.1,164.3c74-8.7,122.3-80.9,103.3-154.3c-3.4-13.1-1-20.2,7.8-22.7c9.5-2.8,15.2,1.8,19.2,15.4
c20.1,67.4-16,144.8-79.8,175.3c-67.5,32.2-147.5,9.7-188.8-49c-37.8-53.8-36.1-127.7,4.7-179.1C141.2,159.4,142.5,157.4,144.3,155z
"/>
<path class="st0" d="M236.2,262.2c-4.2-13.6,3.4-28.2,16.8-32.4c13.5-4.2,28.1,3.3,32.5,16.8c4.5,13.9-3.1,28.4-17.2,32.7
C254.6,283.5,240.4,275.9,236.2,262.2z"/>
<path class="st0" d="M319.6,101.9c-3.7-11,1.8-22.3,12.7-26c11.3-3.9,22.8,1.9,26.4,13.2c3.5,11-2.3,22.2-13.2,25.8
C334.5,118.5,323.3,112.9,319.6,101.9z"/>
<path class="st0" d="M214.9,66.2c3.2,10.2-2.2,20.5-12.5,23.6c-9.8,3-20-2.3-23.3-12c-3.3-9.9,2.2-20.8,12-24
C201.1,50.5,211.8,56.2,214.9,66.2z"/>
<path class="st0" d="M230.8,164.6c-3.7-11,1.8-22.3,12.7-26c11.3-3.9,22.8,1.9,26.4,13.2c3.5,11-2.3,22.2-13.2,25.8
C245.7,181.2,234.5,175.6,230.8,164.6z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 143.1 414.2" style="enable-background:new 0 0 143.1 414.2;" xml:space="preserve">
<style type="text/css">
.st0{fill:#B9B9B9;}
.st1{fill:#EE982D;}
.st2{fill:#FF630A;}
</style>
<path class="st0" d="M78.8,7.1c3.9,4.3,7.7,8.7,11.8,12.9c3.7,3.8,5.1,7.9,4.4,12.9c-1,7.1-1.8,14-7.1,20.1
c-4.2,4.7-1.3,12,4.6,14.5c5.6,2.3,11.1,4.6,16.7,7c8.1,3.5,12.3,9.6,13.2,17.4c0.8,6.3,0.8,12.6,1.2,18.9c0.1,2.2,0.3,4.3,0.7,6.4
c0.3,2.2,1.9,4.4-1.3,6.1c-0.6,0.3-0.6,2.2-0.3,3.3c5.4,25.1-0.3,49.7-4.9,74.3c-1.2,6.3-3.1,12-5.5,17.9
c-2.3,5.5-0.9,12.4-0.4,18.7c0.6,7.8,2,15.5,3.2,23.7c-2.5,0.5-4.5,1.4-6.3,1.2c-6.1-0.5-6.8,2.6-6.4,6.7c0.7,6.6,1.1,13.3,2.4,19.8
c0.6,3.3,3.5,6.1,4.5,9.4c5,15.6,1.1,30.9-1.3,46.5c-1.7,10.6-0.6,21.5-1.5,32.2c-0.5,6.5-1.5,13.3-4,19.4c-2.2,5.4-9,3.9-14.1,4
c-2.8,0.1-5.6-0.3-8.2-1.1c-1.2-0.3-2.6-2.2-2.5-3.3c0.1-1.9,0.7-4.1,1.9-5.7c11.5-16,9.1-33.7,8.2-51.2c-0.5-9.8-1.6-19.6-2.9-29.3
c-1.2-8.8-3.1-17.5-5-26.2c-1.2-5.5-3.4-10.9-4.4-16.5c-0.8-3.9-2.4-5.3-7.4-3.8c-0.5,8.5-3.1,17.7-0.9,25.9
c2.7,10.1,4.5,20,3.3,30.2c-1,8.8-2.9,17.5-3.9,26.2c-0.7,6.6-1.1,13,1.7,19.8c2.4,5.9,0.6,13.1,0.3,19.7c0,0.9-2.1,2.3-3.4,2.5
c-4.8,0.7-9.8,0.8-14.5,1.6c-5.1,0.9-9.9,3-15,3.6c-5.7,0.6-11.5,0.1-17.3,0.1c-2.4-4,0-6.2,3.2-8.4c6.9-4.5,13.7-9,20.5-13.6
c6.4-4.4,7.5-10.2,6.6-17.1c-2.1-17.2-6.4-34.1-5.2-51.6c0.8-11.3-0.7-22.5-4.2-33.5c-1.7-5.2-3.4-9.1-10.4-7.8
c-0.4-0.8-0.9-1.2-0.9-1.7c-0.4-17.6-1.7-35.3-0.9-52.9c0.6-12.6,3.9-25,6-37.5c0.1-0.6,0.9-1.5,0.7-1.8c-5-8,2.9-15.7,0.6-23.9
c-2.2-8-3.5-16.4-3.7-24.6c-0.2-6.9,2.3-13.8,2.8-20.7c0.8-12.2,8.8-18.8,20.6-23c7.3-2.6,9.1-5.2,8.2-11.3c-0.1-1.1-0.6-2.7-1.4-3
c-10-4.1-8.9-12.3-9.9-19.9c-0.9-7.2-2.7-14.2-3.6-21.4c-0.3-2.9,0.2-5.9,3.6-8.1c6.4-4,13.1-5.3,20.7-3.4
C73.7,8.3,76.3,7.4,78.8,7.1z M107.1,130.2c-1,0-2,0.1-3.1,0.1c0,3.1-0.2,6.1,0,9.2c0.7,9.3,5.9,19-2.4,27.7
c-0.2,0.2-0.2,0.6-0.1,0.9c1.9,7.2,3.8,14.5,5.6,21.7c0.7-0.1,1.4-0.1,2.1-0.2C108.5,169.9,107.8,150,107.1,130.2z"/>
<circle class="st1" cx="70.9" cy="109.9" r="26"/>
<circle class="st2" cx="70.9" cy="109.9" r="19"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 143.1 414.2" style="enable-background:new 0 0 143.1 414.2;" xml:space="preserve">
<style type="text/css">
.st0{fill:#B9B9B9;}
.st1{fill:#009175;}
.st2{fill:#1FC6A6;}
.st3{fill:#FFFFFF;}
</style>
<path class="st0" d="M78.8,7.1c3.9,4.3,7.7,8.7,11.8,12.9c3.7,3.8,5.1,7.9,4.4,12.9c-1,7.1-1.8,14-7.1,20.1
c-4.2,4.7-1.3,12,4.6,14.5c5.6,2.3,11.1,4.6,16.7,7c8.1,3.5,12.3,9.6,13.2,17.4c0.8,6.3,0.8,12.6,1.2,18.9c0.1,2.2,0.3,4.3,0.7,6.4
c0.3,2.2,1.9,4.4-1.3,6.1c-0.6,0.3-0.6,2.2-0.3,3.3c5.4,25.1-0.3,49.7-4.9,74.3c-1.2,6.3-3.1,12-5.5,17.9
c-2.3,5.5-0.9,12.4-0.4,18.7c0.6,7.8,2,15.5,3.2,23.7c-2.5,0.5-4.5,1.4-6.3,1.2c-6.1-0.5-6.8,2.6-6.4,6.7c0.7,6.6,1.1,13.3,2.4,19.8
c0.6,3.3,3.5,6.1,4.5,9.4c5,15.6,1.1,30.9-1.3,46.5c-1.7,10.6-0.6,21.5-1.5,32.2c-0.5,6.5-1.5,13.3-4,19.4c-2.2,5.4-9,3.9-14.1,4
c-2.8,0.1-5.6-0.3-8.2-1.1c-1.2-0.3-2.6-2.2-2.5-3.3c0.1-1.9,0.7-4.1,1.9-5.7c11.5-16,9.1-33.7,8.2-51.2c-0.5-9.8-1.6-19.6-2.9-29.3
c-1.2-8.8-3.1-17.5-5-26.2c-1.2-5.5-3.4-10.9-4.4-16.5c-0.8-3.9-2.4-5.3-7.4-3.8c-0.5,8.5-3.1,17.7-0.9,25.9
c2.7,10.1,4.5,20,3.3,30.2c-1,8.8-2.9,17.5-3.9,26.2c-0.7,6.6-1.1,13,1.7,19.8c2.4,5.9,0.6,13.1,0.3,19.7c0,0.9-2.1,2.3-3.4,2.5
c-4.8,0.7-9.8,0.8-14.5,1.6c-5.1,0.9-9.9,3-15,3.6c-5.7,0.6-11.5,0.1-17.3,0.1c-2.4-4,0-6.2,3.2-8.4c6.9-4.5,13.7-9,20.5-13.6
c6.4-4.4,7.5-10.2,6.6-17.1c-2.1-17.2-6.4-34.1-5.2-51.6c0.8-11.3-0.7-22.5-4.2-33.5c-1.7-5.2-3.4-9.1-10.4-7.8
c-0.4-0.8-0.9-1.2-0.9-1.7c-0.4-17.6-1.7-35.3-0.9-52.9c0.6-12.6,3.9-25,6-37.5c0.1-0.6,0.9-1.5,0.7-1.8c-5-8,2.9-15.7,0.6-23.9
c-2.2-8-3.5-16.4-3.7-24.6c-0.2-6.9,2.3-13.8,2.8-20.7c0.8-12.2,8.8-18.8,20.6-23c7.3-2.6,9.1-5.2,8.2-11.3c-0.1-1.1-0.6-2.7-1.4-3
c-10-4.1-8.9-12.3-9.9-19.9c-0.9-7.2-2.7-14.2-3.6-21.4c-0.3-2.9,0.2-5.9,3.6-8.1c6.4-4,13.1-5.3,20.7-3.4
C73.7,8.3,76.3,7.4,78.8,7.1z M107.1,130.2c-1,0-2,0.1-3.1,0.1c0,3.1-0.2,6.1,0,9.2c0.7,9.3,5.9,19-2.4,27.7
c-0.2,0.2-0.2,0.6-0.1,0.9c1.9,7.2,3.8,14.5,5.6,21.7c0.7-0.1,1.4-0.1,2.1-0.2C108.5,169.9,107.8,150,107.1,130.2z"/>
<circle class="st1" cx="70.9" cy="109.9" r="26"/>
<circle class="st2" cx="70.9" cy="109.9" r="23"/>
<path class="st3" d="M70.9,127.8c-9.9,0-17.9-8-17.9-17.9s8-17.9,17.9-17.9s17.9,8,17.9,17.9S80.7,127.8,70.9,127.8z M70.9,94.8
c-8.3,0-15.1,6.8-15.1,15.1s6.8,15.1,15.1,15.1S86,118.3,86,109.9S79.2,94.8,70.9,94.8z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -209,6 +209,7 @@
#include "commerce/QmlCommerce.h"
#include "webbrowser/WebBrowserSuggestionsEngine.h"
#include <DesktopPreviewProvider.h>
// On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
// FIXME seems to be broken.
@ -503,7 +504,13 @@ public:
}
if (message->message == WM_DEVICECHANGE) {
Midi::USBchanged(); // re-scan the MIDI bus
const float MIN_DELTA_SECONDS = 2.0f; // de-bounce signal
static float lastTriggerTime = 0.0f;
const float deltaSeconds = secTimestampNow() - lastTriggerTime;
lastTriggerTime = secTimestampNow();
if (deltaSeconds > MIN_DELTA_SECONDS) {
Midi::USBchanged(); // re-scan the MIDI bus
}
}
}
return false;
@ -631,6 +638,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<PointerScriptingInterface>();
DependencyManager::set<PickScriptingInterface>();
DependencyManager::set<Cursor::Manager>();
DependencyManager::set<DesktopPreviewProvider>();
DependencyManager::set<AccountManager>(std::bind(&Application::getUserAgent, qApp));
DependencyManager::set<StatTracker>();
DependencyManager::set<ScriptEngines>(ScriptEngine::CLIENT_SCRIPT);
@ -5763,6 +5771,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
scriptEngine->registerFunction("OverlayWindow", QmlWindowClass::constructor);
scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("DesktopPreviewProvider", DependencyManager::get<DesktopPreviewProvider>().data());
scriptEngine->registerGlobalObject("Stats", Stats::getInstance());
scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("Snapshot", DependencyManager::get<Snapshot>().data());

View file

@ -2019,8 +2019,7 @@ void MyAvatar::updateOrientation(float deltaTime) {
_smoothOrientationTimer = 0.0f;
}
getHead()->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime);
Head* head = getHead();
auto headPose = getControllerPoseInAvatarFrame(controller::Action::HEAD);
if (headPose.isValid()) {
glm::quat localOrientation = headPose.rotation * Quaternions::Y_180;
@ -2032,6 +2031,10 @@ void MyAvatar::updateOrientation(float deltaTime) {
head->setBaseYaw(YAW(euler));
head->setBasePitch(PITCH(euler));
head->setBaseRoll(ROLL(euler));
} else {
head->setBaseYaw(0.0f);
head->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime);
head->setBaseRoll(0.0f);
}
}

View file

@ -277,7 +277,8 @@ void WindowScriptingInterface::browseAsync(const QString& title, const QString&
if (!result.isEmpty()) {
setPreviousBrowseLocation(QFileInfo(result).absolutePath());
}
emit openFileChanged(result);
emit browseChanged(result);
emit openFileChanged(result); // Deprecated signal; to be removed in due course.
});
}

View file

@ -197,18 +197,19 @@ public slots:
/**jsdoc
* Prompt the user to choose a file. Displays a non-modal dialog that navigates the directory tree. A
* {@link Window.openFileChanged|openFileChanged} signal is emitted when a file is chosen; no signal is emitted if the user
* {@link Window.browseChanged|browseChanged} signal is emitted when a file is chosen; no signal is emitted if the user
* cancels the dialog.
* @deprecated A deprecated {@link Window.openFileChanged|openFileChanged} signal is also emitted when a file is chosen.
* @function Window.browseAsync
* @param {string} title="" - The title to display at the top of the dialog.
* @param {string} directory="" - The initial directory to start browsing at.
* @param {string} nameFilter="" - The types of files to display. Examples: <code>"*.json"</code> and
* <code>"Images (*.png *.jpg *.svg)"</code>. All files are displayed if a filter isn't specified.
* @example <caption>Ask the user to choose an image file without waiting for the answer.</caption>
* function onOpenFileChanged(filename) {
* function onBrowseChanged(filename) {
* print("File: " + filename);
* }
* Window.openFileChanged.connect(onOpenFileChanged);
* Window.browseChanged.connect(onBrowseChanged);
*
* Window.browseAsync("Select Image File", Paths.resources, "Images (*.png *.jpg *.svg)");
* print("Script continues without waiting");
@ -659,9 +660,18 @@ signals:
*/
void saveFileChanged(QString filename);
/**jsdoc
* Triggered when the user chooses a file in a {@link Window.browseAsync|browseAsync} dialog.
* @function Window.browseChanged
* @param {string} filename - The path and name of the file the user chose in the dialog.
* @returns {Signal}
*/
void browseChanged(QString filename);
/**jsdoc
* Triggered when the user chooses a file in a {@link Window.browseAsync|browseAsync} dialog.
* @function Window.openFileChanged
* @deprecated This signal is being replaced with {@link Window.browseChanged|browseChanged} and will be removed.
* @param {string} filename - The path and name of the file the user chose in the dialog.
* @returns {Signal}
*/

View file

@ -564,9 +564,9 @@ void ModelOverlay::animate() {
rotationMat * fbxJoints[index].postTransform);
auto& jointData = jointsData[j];
jointData.translation = extractTranslation(finalMat);
jointData.translationSet = true;
jointData.translationIsDefaultPose = false;
jointData.rotation = glmExtractRotation(finalMat);
jointData.rotationSet = true;
jointData.rotationIsDefaultPose = false;
}
}
// Set the data in the model

View file

@ -1705,16 +1705,16 @@ void Rig::copyJointsIntoJointData(QVector<JointData>& jointDataVec) const {
// rotations are in absolute rig frame.
glm::quat defaultAbsRot = geometryToRigPose.rot() * _animSkeleton->getAbsoluteDefaultPose(i).rot();
data.rotation = _internalPoseSet._absolutePoses[i].rot();
data.rotationSet = !isEqual(data.rotation, defaultAbsRot);
data.rotationIsDefaultPose = isEqual(data.rotation, defaultAbsRot);
// translations are in relative frame but scaled so that they are in meters,
// instead of geometry units.
glm::vec3 defaultRelTrans = _geometryOffset.scale() * _animSkeleton->getRelativeDefaultPose(i).trans();
data.translation = _geometryOffset.scale() * _internalPoseSet._relativePoses[i].trans();
data.translationSet = !isEqual(data.translation, defaultRelTrans);
data.translationIsDefaultPose = isEqual(data.translation, defaultRelTrans);
} else {
data.translationSet = false;
data.rotationSet = false;
data.translationIsDefaultPose = true;
data.rotationIsDefaultPose = true;
}
}
}
@ -1739,11 +1739,11 @@ void Rig::copyJointsFromJointData(const QVector<JointData>& jointDataVec) {
const glm::quat rigToGeometryRot(glmExtractRotation(_rigToGeometryTransform));
for (int i = 0; i < numJoints; i++) {
const JointData& data = jointDataVec.at(i);
if (data.rotationSet) {
if (data.rotationIsDefaultPose) {
rotations.push_back(absoluteDefaultPoses[i].rot());
} else {
// 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());
}
}
@ -1759,11 +1759,11 @@ void Rig::copyJointsFromJointData(const QVector<JointData>& jointDataVec) {
const JointData& data = jointDataVec.at(i);
_internalPoseSet._relativePoses[i].scale() = Vectors::ONE;
_internalPoseSet._relativePoses[i].rot() = rotations[i];
if (data.translationSet) {
if (data.translationIsDefaultPose) {
_internalPoseSet._relativePoses[i].trans() = relativeDefaultPoses[i].trans();
} else {
// 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();
}
}
}

View file

@ -791,10 +791,19 @@ bool Avatar::shouldRenderHead(const RenderArgs* renderArgs) const {
// virtual
void Avatar::simulateAttachments(float deltaTime) {
assert(_attachmentModels.size() == _attachmentModelsTexturesLoaded.size());
PerformanceTimer perfTimer("attachments");
for (int i = 0; i < (int)_attachmentModels.size(); i++) {
const AttachmentData& attachment = _attachmentData.at(i);
auto& model = _attachmentModels.at(i);
bool texturesLoaded = _attachmentModelsTexturesLoaded.at(i);
// Watch for texture loading
if (!texturesLoaded && model->getGeometry() && model->getGeometry()->areTexturesLoaded()) {
_attachmentModelsTexturesLoaded[i] = true;
model->updateRenderItems();
}
int jointIndex = getJointIndex(attachment.jointName);
glm::vec3 jointPosition;
glm::quat jointRotation;
@ -1319,6 +1328,7 @@ void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
while ((int)_attachmentModels.size() > attachmentData.size()) {
auto attachmentModel = _attachmentModels.back();
_attachmentModels.pop_back();
_attachmentModelsTexturesLoaded.pop_back();
_attachmentsToRemove.push_back(attachmentModel);
}
@ -1326,11 +1336,16 @@ void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
if (i == (int)_attachmentModels.size()) {
// if number of attachments has been increased, we need to allocate a new model
_attachmentModels.push_back(allocateAttachmentModel(attachmentData[i].isSoft, _skeletonModel->getRig(), isMyAvatar()));
}
else if (i < oldAttachmentData.size() && oldAttachmentData[i].isSoft != attachmentData[i].isSoft) {
_attachmentModelsTexturesLoaded.push_back(false);
} else if (i < oldAttachmentData.size() && oldAttachmentData[i].isSoft != attachmentData[i].isSoft) {
// if the attachment has changed type, we need to re-allocate a new one.
_attachmentsToRemove.push_back(_attachmentModels[i]);
_attachmentModels[i] = allocateAttachmentModel(attachmentData[i].isSoft, _skeletonModel->getRig(), isMyAvatar());
_attachmentModelsTexturesLoaded[i] = false;
}
// If the model URL has changd, we need to wait for the textures to load
if (_attachmentModels[i]->getURL() != attachmentData[i].modelURL) {
_attachmentModelsTexturesLoaded[i] = false;
}
_attachmentModels[i]->setURL(attachmentData[i].modelURL);
}

View file

@ -306,6 +306,7 @@ protected:
glm::vec3 _skeletonOffset;
std::vector<std::shared_ptr<Model>> _attachmentModels;
std::vector<bool> _attachmentModelsTexturesLoaded;
std::vector<std::shared_ptr<Model>> _attachmentsToRemove;
std::vector<std::shared_ptr<Model>> _attachmentsToDelete;

View file

@ -39,6 +39,7 @@
#include <AudioHelpers.h>
#include <Profile.h>
#include <VariantMapToScriptValue.h>
#include <BitVectorHelpers.h>
#include "AvatarLogging.h"
@ -77,6 +78,16 @@ size_t AvatarDataPacket::maxJointDataSize(size_t numJoints) {
return totalSize;
}
size_t AvatarDataPacket::maxJointDefaultPoseFlagsSize(size_t numJoints) {
const size_t bitVectorSize = calcBitVectorSize((int)numJoints);
size_t totalSize = sizeof(uint8_t); // numJoints
// one set of bits for rotation and one for translation
const size_t NUM_BIT_VECTORS_IN_DEFAULT_POSE_FLAGS_SECTION = 2;
totalSize += NUM_BIT_VECTORS_IN_DEFAULT_POSE_FLAGS_SECTION * bitVectorSize;
return totalSize;
}
AvatarData::AvatarData() :
SpatiallyNestable(NestableType::Avatar, QUuid()),
@ -272,6 +283,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
bool hasFaceTrackerInfo = false;
bool hasJointData = false;
bool hasJointDefaultPoseFlags = false;
if (sendPALMinimum) {
hasAudioLoudness = true;
@ -290,6 +302,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
hasFaceTrackerInfo = !dropFaceTracking && hasFaceTracker() && (sendAll || faceTrackerInfoChangedSince(lastSentTime));
hasJointData = sendAll || !sendMinimum;
hasJointDefaultPoseFlags = hasJointData;
}
@ -314,7 +327,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
| (hasParentInfo ? AvatarDataPacket::PACKET_HAS_PARENT_INFO : 0)
| (hasAvatarLocalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION : 0)
| (hasFaceTrackerInfo ? AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO : 0)
| (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0);
| (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0)
| (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0);
memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags));
destinationBuffer += sizeof(packetStateFlags);
@ -541,14 +555,19 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
for (int i = 0; i < _jointData.size(); i++) {
const JointData& data = _jointData[i];
const JointData& last = lastSentJointData[i];
// The dot product for smaller rotations is a smaller number.
// So if the dot() is less than the value, then the rotation is a larger angle of rotation
bool largeEnoughRotation = fabsf(glm::dot(data.rotation, lastSentJointData[i].rotation)) < minRotationDOT;
if (!data.rotationIsDefaultPose) {
if (sendAll || last.rotationIsDefaultPose || last.rotation != data.rotation) {
if (sendAll || lastSentJointData[i].rotation != data.rotation) {
if (sendAll || !cullSmallChanges || largeEnoughRotation) {
if (data.rotationSet) {
bool largeEnoughRotation = true;
if (cullSmallChanges) {
// The dot product for smaller rotations is a smaller number.
// So if the dot() is less than the value, then the rotation is a larger angle of rotation
largeEnoughRotation = fabsf(glm::dot(last.rotation, data.rotation)) < minRotationDOT;
}
if (sendAll || !cullSmallChanges || largeEnoughRotation) {
validity |= (1 << validityBit);
#ifdef WANT_DEBUG
rotationSentCount++;
@ -557,8 +576,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
if (sentJointDataOut) {
localSentJointDataOut[i].rotation = data.rotation;
localSentJointDataOut[i].rotationIsDefaultPose = false;
}
}
}
}
@ -588,11 +607,10 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
float maxTranslationDimension = 0.0;
for (int i = 0; i < _jointData.size(); i++) {
const JointData& data = _jointData[i];
if (sendAll || lastSentJointData[i].translation != data.translation) {
if (sendAll ||
!cullSmallChanges ||
glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) {
if (data.translationSet) {
if (!data.translationIsDefaultPose) {
if (sendAll || lastSentJointData[i].translation != data.translation) {
if (sendAll || !cullSmallChanges || glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) {
validity |= (1 << validityBit);
#ifdef WANT_DEBUG
translationSentCount++;
@ -606,8 +624,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
if (sentJointDataOut) {
localSentJointDataOut[i].translation = data.translation;
localSentJointDataOut[i].translationIsDefaultPose = false;
}
}
}
}
@ -655,6 +673,30 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
}
}
if (hasJointDefaultPoseFlags) {
auto startSection = destinationBuffer;
QReadLocker readLock(&_jointDataLock);
// write numJoints
int numJoints = _jointData.size();
*destinationBuffer++ = (uint8_t)numJoints;
// write rotationIsDefaultPose bits
destinationBuffer += writeBitVector(destinationBuffer, numJoints, [&](int i) {
return _jointData[i].rotationIsDefaultPose;
});
// write translationIsDefaultPose bits
destinationBuffer += writeBitVector(destinationBuffer, numJoints, [&](int i) {
return _jointData[i].translationIsDefaultPose;
});
if (outboundDataRateOut) {
size_t numBytes = destinationBuffer - startSection;
outboundDataRateOut->jointDefaultPoseFlagsRate.increment(numBytes);
}
}
int avatarDataSize = destinationBuffer - startPosition;
if (avatarDataSize > (int)byteArraySize) {
@ -664,6 +706,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
return avatarDataByteArray.left(avatarDataSize);
}
// NOTE: This is never used in a "distanceAdjust" mode, so it's ok that it doesn't use a variable minimum rotation/translation
void AvatarData::doneEncoding(bool cullSmallChanges) {
// The server has finished sending this version of the joint-data to other nodes. Update _lastSentJointData.
@ -674,7 +717,7 @@ void AvatarData::doneEncoding(bool cullSmallChanges) {
if (_lastSentJointData[i].rotation != data.rotation) {
if (!cullSmallChanges ||
fabsf(glm::dot(data.rotation, _lastSentJointData[i].rotation)) <= AVATAR_MIN_ROTATION_DOT) {
if (data.rotationSet) {
if (!data.rotationIsDefaultPose) {
_lastSentJointData[i].rotation = data.rotation;
}
}
@ -682,7 +725,7 @@ void AvatarData::doneEncoding(bool cullSmallChanges) {
if (_lastSentJointData[i].translation != data.translation) {
if (!cullSmallChanges ||
glm::distance(data.translation, _lastSentJointData[i].translation) > AVATAR_MIN_TRANSLATION) {
if (data.translationSet) {
if (!data.translationIsDefaultPose) {
_lastSentJointData[i].translation = data.translation;
}
}
@ -730,6 +773,7 @@ const unsigned char* unpackFauxJoint(const unsigned char* sourceBuffer, ThreadSa
// read data in packet starting at byte offset and return number of bytes parsed
int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
// lazily allocate memory for HeadData in case we're not an Avatar instance
lazyInitHeadData();
@ -745,18 +789,19 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
#define HAS_FLAG(B,F) ((B & F) == F)
bool hasAvatarGlobalPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION);
bool hasAvatarBoundingBox = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX);
bool hasAvatarOrientation = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION);
bool hasAvatarScale = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_SCALE);
bool hasLookAtPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_LOOK_AT_POSITION);
bool hasAudioLoudness = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AUDIO_LOUDNESS);
bool hasSensorToWorldMatrix = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_SENSOR_TO_WORLD_MATRIX);
bool hasAdditionalFlags = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS);
bool hasParentInfo = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_PARENT_INFO);
bool hasAvatarLocalPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION);
bool hasFaceTrackerInfo = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO);
bool hasJointData = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_JOINT_DATA);
bool hasAvatarGlobalPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION);
bool hasAvatarBoundingBox = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX);
bool hasAvatarOrientation = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION);
bool hasAvatarScale = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_SCALE);
bool hasLookAtPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_LOOK_AT_POSITION);
bool hasAudioLoudness = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AUDIO_LOUDNESS);
bool hasSensorToWorldMatrix = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_SENSOR_TO_WORLD_MATRIX);
bool hasAdditionalFlags = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS);
bool hasParentInfo = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_PARENT_INFO);
bool hasAvatarLocalPosition = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION);
bool hasFaceTrackerInfo = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO);
bool hasJointData = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_JOINT_DATA);
bool hasJointDefaultPoseFlags = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS);
quint64 now = usecTimestampNow();
@ -1055,7 +1100,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
if (validRotations[i]) {
sourceBuffer += unpackOrientationQuatFromSixBytes(sourceBuffer, data.rotation);
_hasNewJointData = true;
data.rotationSet = true;
data.rotationIsDefaultPose = false;
}
}
@ -1090,7 +1135,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
if (validTranslations[i]) {
sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX);
_hasNewJointData = true;
data.translationSet = true;
data.translationIsDefaultPose = false;
}
}
@ -1110,6 +1155,32 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
_jointDataUpdateRate.increment();
}
if (hasJointDefaultPoseFlags) {
auto startSection = sourceBuffer;
QWriteLocker writeLock(&_jointDataLock);
PACKET_READ_CHECK(JointDefaultPoseFlagsNumJoints, sizeof(uint8_t));
int numJoints = (int)*sourceBuffer++;
_jointData.resize(numJoints);
size_t bitVectorSize = calcBitVectorSize(numJoints);
PACKET_READ_CHECK(JointDefaultPoseFlagsRotationFlags, bitVectorSize);
sourceBuffer += readBitVector(sourceBuffer, numJoints, [&](int i, bool value) {
_jointData[i].rotationIsDefaultPose = value;
});
PACKET_READ_CHECK(JointDefaultPoseFlagsTranslationFlags, bitVectorSize);
sourceBuffer += readBitVector(sourceBuffer, numJoints, [&](int i, bool value) {
_jointData[i].translationIsDefaultPose = value;
});
int numBytesRead = sourceBuffer - startSection;
_jointDefaultPoseFlagsRate.increment(numBytesRead);
_jointDefaultPoseFlagsUpdateRate.increment();
}
int numBytesRead = sourceBuffer - startPosition;
_averageBytesReceived.updateAverage(numBytesRead);
@ -1146,6 +1217,8 @@ float AvatarData::getDataRate(const QString& rateName) const {
return _faceTrackerRate.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "jointData") {
return _jointDataRate.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "jointDefaultPoseFlagsRate") {
return _jointDefaultPoseFlagsRate.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "globalPositionOutbound") {
return _outboundDataRate.globalPositionRate.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "localPositionOutbound") {
@ -1170,6 +1243,8 @@ float AvatarData::getDataRate(const QString& rateName) const {
return _outboundDataRate.faceTrackerRate.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "jointDataOutbound") {
return _outboundDataRate.jointDataRate.rate() / BYTES_PER_KILOBIT;
} else if (rateName == "jointDefaultPoseFlagsOutbound") {
return _outboundDataRate.jointDefaultPoseFlagsRate.rate() / BYTES_PER_KILOBIT;
}
return 0.0f;
}
@ -1236,9 +1311,9 @@ void AvatarData::setJointData(int index, const glm::quat& rotation, const glm::v
}
JointData& data = _jointData[index];
data.rotation = rotation;
data.rotationSet = true;
data.rotationIsDefaultPose = false;
data.translation = translation;
data.translationSet = true;
data.translationIsDefaultPose = false;
}
void AvatarData::clearJointData(int index) {
@ -1294,7 +1369,8 @@ void AvatarData::setJointData(const QString& name, const glm::quat& rotation, co
auto& jointData = _jointData[index];
jointData.rotation = rotation;
jointData.translation = translation;
jointData.rotationSet = jointData.translationSet = true;
jointData.rotationIsDefaultPose = false;
jointData.translationIsDefaultPose = false;
});
}
@ -1304,7 +1380,7 @@ void AvatarData::setJointRotation(const QString& name, const glm::quat& rotation
writeLockWithNamedJointIndex(name, [&](int index) {
auto& data = _jointData[index];
data.rotation = rotation;
data.rotationSet = true;
data.rotationIsDefaultPose = false;
});
}
@ -1314,7 +1390,7 @@ void AvatarData::setJointTranslation(const QString& name, const glm::vec3& trans
writeLockWithNamedJointIndex(name, [&](int index) {
auto& data = _jointData[index];
data.translation = translation;
data.translationSet = true;
data.translationIsDefaultPose = false;
});
}
@ -1328,7 +1404,7 @@ void AvatarData::setJointRotation(int index, const glm::quat& rotation) {
}
JointData& data = _jointData[index];
data.rotation = rotation;
data.rotationSet = true;
data.rotationIsDefaultPose = false;
}
void AvatarData::setJointTranslation(int index, const glm::vec3& translation) {
@ -1341,7 +1417,7 @@ void AvatarData::setJointTranslation(int index, const glm::vec3& translation) {
}
JointData& data = _jointData[index];
data.translation = translation;
data.translationSet = true;
data.translationIsDefaultPose = false;
}
void AvatarData::clearJointData(const QString& name) {
@ -1397,7 +1473,7 @@ void AvatarData::setJointRotations(const QVector<glm::quat>& jointRotations) {
for (int i = 0; i < size; ++i) {
auto& data = _jointData[i];
data.rotation = jointRotations[i];
data.rotationSet = true;
data.rotationIsDefaultPose = false;
}
}
@ -1419,7 +1495,7 @@ void AvatarData::setJointTranslations(const QVector<glm::vec3>& jointTranslation
for (int i = 0; i < size; ++i) {
auto& data = _jointData[i];
data.translation = jointTranslations[i];
data.translationSet = true;
data.translationIsDefaultPose = false;
}
}
@ -1996,9 +2072,9 @@ JointData jointDataFromJsonValue(const QJsonValue& json) {
if (json.isArray()) {
QJsonArray array = json.toArray();
result.rotation = quatFromJsonValue(array[0]);
result.rotationSet = true;
result.rotationIsDefaultPose = false;
result.translation = vec3FromJsonValue(array[1]);
result.translationSet = true;
result.translationIsDefaultPose = false;
}
return result;
}

View file

@ -113,18 +113,19 @@ namespace AvatarDataPacket {
// Packet State Flags - we store the details about the existence of other records in this bitset:
// AvatarGlobalPosition, Avatar face tracker, eye tracking, and existence of
using HasFlags = uint16_t;
const HasFlags PACKET_HAS_AVATAR_GLOBAL_POSITION = 1U << 0;
const HasFlags PACKET_HAS_AVATAR_BOUNDING_BOX = 1U << 1;
const HasFlags PACKET_HAS_AVATAR_ORIENTATION = 1U << 2;
const HasFlags PACKET_HAS_AVATAR_SCALE = 1U << 3;
const HasFlags PACKET_HAS_LOOK_AT_POSITION = 1U << 4;
const HasFlags PACKET_HAS_AUDIO_LOUDNESS = 1U << 5;
const HasFlags PACKET_HAS_SENSOR_TO_WORLD_MATRIX = 1U << 6;
const HasFlags PACKET_HAS_ADDITIONAL_FLAGS = 1U << 7;
const HasFlags PACKET_HAS_PARENT_INFO = 1U << 8;
const HasFlags PACKET_HAS_AVATAR_LOCAL_POSITION = 1U << 9;
const HasFlags PACKET_HAS_FACE_TRACKER_INFO = 1U << 10;
const HasFlags PACKET_HAS_JOINT_DATA = 1U << 11;
const HasFlags PACKET_HAS_AVATAR_GLOBAL_POSITION = 1U << 0;
const HasFlags PACKET_HAS_AVATAR_BOUNDING_BOX = 1U << 1;
const HasFlags PACKET_HAS_AVATAR_ORIENTATION = 1U << 2;
const HasFlags PACKET_HAS_AVATAR_SCALE = 1U << 3;
const HasFlags PACKET_HAS_LOOK_AT_POSITION = 1U << 4;
const HasFlags PACKET_HAS_AUDIO_LOUDNESS = 1U << 5;
const HasFlags PACKET_HAS_SENSOR_TO_WORLD_MATRIX = 1U << 6;
const HasFlags PACKET_HAS_ADDITIONAL_FLAGS = 1U << 7;
const HasFlags PACKET_HAS_PARENT_INFO = 1U << 8;
const HasFlags PACKET_HAS_AVATAR_LOCAL_POSITION = 1U << 9;
const HasFlags PACKET_HAS_FACE_TRACKER_INFO = 1U << 10;
const HasFlags PACKET_HAS_JOINT_DATA = 1U << 11;
const HasFlags PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS = 1U << 12;
const size_t AVATAR_HAS_FLAGS_SIZE = 2;
using SixByteQuat = uint8_t[6];
@ -256,6 +257,15 @@ namespace AvatarDataPacket {
};
*/
size_t maxJointDataSize(size_t numJoints);
/*
struct JointDefaultPoseFlags {
uint8_t numJoints;
uint8_t rotationIsDefaultPoseBits[ceil(numJoints / 8)];
uint8_t translationIsDefaultPoseBits[ceil(numJoints / 8)];
};
*/
size_t maxJointDefaultPoseFlagsSize(size_t numJoints);
}
const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation
@ -321,6 +331,7 @@ public:
RateCounter<> parentInfoRate;
RateCounter<> faceTrackerRate;
RateCounter<> jointDataRate;
RateCounter<> jointDefaultPoseFlagsRate;
};
class AvatarPriority {
@ -810,6 +821,7 @@ protected:
RateCounter<> _parentInfoRate;
RateCounter<> _faceTrackerRate;
RateCounter<> _jointDataRate;
RateCounter<> _jointDefaultPoseFlagsRate;
// Some rate data for incoming data updates
RateCounter<> _parseBufferUpdateRate;
@ -825,6 +837,7 @@ protected:
RateCounter<> _parentInfoUpdateRate;
RateCounter<> _faceTrackerUpdateRate;
RateCounter<> _jointDataUpdateRate;
RateCounter<> _jointDefaultPoseFlagsUpdateRate;
// Some rate data for outgoing data
AvatarDataRate _outboundDataRate;

View file

@ -33,6 +33,7 @@
#include "../Logging.h"
#include "../CompositorHelper.h"
#include "DesktopPreviewProvider.h"
#include "render-utils/hmd_ui_vert.h"
#include "render-utils/hmd_ui_frag.h"
@ -254,17 +255,9 @@ void HmdDisplayPlugin::internalPresent() {
swapBuffers();
} else if (_clearPreviewFlag) {
QImage image;
if (_vsyncEnabled) {
image = QImage(PathUtils::resourcesPath() + "images/preview.png");
} else {
image = QImage(PathUtils::resourcesPath() + "images/preview-disabled.png");
}
image = image.mirrored();
image = image.convertToFormat(QImage::Format_RGBA8888);
if (!_previewTexture) {
_previewTexture = gpu::Texture::createStrict(
QImage image = DesktopPreviewProvider::getInstance()->getPreviewDisabledImage(_vsyncEnabled);
_previewTexture = gpu::Texture::createStrict(
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
image.width(), image.height(),
gpu::Texture::MAX_NUM_MIPS,
@ -274,7 +267,6 @@ void HmdDisplayPlugin::internalPresent() {
_previewTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
_previewTexture->assignStoredMip(0, image.byteCount(), image.constBits());
_previewTexture->setAutoGenerateMips(true);
}
auto viewport = getViewportForSourceSize(uvec2(_previewTexture->getDimensions()));

View file

@ -1050,7 +1050,7 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) {
return;
}
QVector<JointData> jointsData;
QVector<EntityJointData> jointsData;
const QVector<FBXAnimationFrame>& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy
int frameCount = frames.size();
@ -1394,8 +1394,14 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
// That is where _currentFrame and _lastAnimated were updated.
if (_animating) {
DETAILED_PROFILE_RANGE(simulation_physics, "Animate");
if (!jointsMapped()) {
mapJoints(entity, model->getJointNames());
//else the joint have been mapped before but we have a new animation to load
} else if (_animation && (_animation->getURL().toString() != entity->getAnimationURL())) {
_animation = DependencyManager::get<AnimationCache>()->getAnimation(entity->getAnimationURL());
_jointMappingCompleted = false;
mapJoints(entity, model->getJointNames());
}
if (!(entity->getAnimationFirstFrame() < 0) && !(entity->getAnimationFirstFrame() > entity->getAnimationLastFrame())) {
animate(entity);

View file

@ -368,6 +368,7 @@ public:
void* getPhysicsInfo() const { return _physicsInfo; }
void setPhysicsInfo(void* data) { _physicsInfo = data; }
EntityTreeElementPointer getElement() const { return _element; }
EntityTreePointer getTree() const;
virtual SpatialParentTree* getParentTree() const override;

View file

@ -452,7 +452,7 @@ void ModelEntityItem::resizeJointArrays(int newSize) {
});
}
void ModelEntityItem::setAnimationJointsData(const QVector<JointData>& jointsData) {
void ModelEntityItem::setAnimationJointsData(const QVector<EntityJointData>& jointsData) {
resizeJointArrays(jointsData.size());
_jointDataLock.withWriteLock([&] {
for (auto index = 0; index < jointsData.size(); ++index) {

View file

@ -124,7 +124,7 @@ public:
virtual void setJointTranslations(const QVector<glm::vec3>& translations);
virtual void setJointTranslationsSet(const QVector<bool>& translationsSet);
virtual void setAnimationJointsData(const QVector<JointData>& jointsData);
virtual void setAnimationJointsData(const QVector<EntityJointData>& jointsData);
QVector<glm::quat> getJointRotations() const;
QVector<bool> getJointRotationsSet() const;
@ -150,7 +150,7 @@ protected:
bool _jointTranslationsExplicitlySet{ false }; // were the joints set as a property or just side effect of animations
struct ModelJointData {
JointData joint;
EntityJointData joint;
bool rotationDirty { false };
bool translationDirty { false };
};

View file

@ -163,7 +163,7 @@ void Midi::sendRawMessage(int device, int raw) {
void Midi::sendMessage(int device, int channel, int type, int note, int velocity) {
int message = (channel - 1) | (type << MIDI_SHIFT_STATUS);
if (broadcastEnabled) {
for (int i = 0; i < midihout.size(); i++) {
for (int i = 1; i < midihout.size(); i++) { // Skip 0 (Microsoft GS Wavetable Synth)
if (midihout[i] != NULL) {
midiOutShortMsg(midihout[i], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY));
}
@ -174,9 +174,9 @@ void Midi::sendMessage(int device, int channel, int type, int note, int velocity
}
void Midi::sendNote(int status, int note, int velocity) {
for (int i = 0; i < midihout.size(); i++) {
for (int i = 1; i < midihout.size(); i++) { // Skip 0 (Microsoft GS Wavetable Synth)
if (midihout[i] != NULL) {
midiOutShortMsg(midihout[i], status + (note << MIDI_SHIFT_NOTE) + (velocity << MIDI_SHIFT_VELOCITY));
midiOutShortMsg(midihout[i], status | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY));
}
}
}
@ -283,9 +283,6 @@ void Midi::midiHardwareChange() {
Midi::Midi() {
instance = this;
#if defined Q_OS_WIN32
midiOutExclude.push_back("Microsoft GS Wavetable Synth"); // we don't want to hear this thing (Lags)
#endif
MidiSetup();
}

View file

@ -38,7 +38,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::AvatarData:
case PacketType::BulkAvatarData:
case PacketType::KillAvatar:
return static_cast<PacketVersion>(AvatarMixerPacketVersion::UpdatedMannequinDefaultAvatar);
return static_cast<PacketVersion>(AvatarMixerPacketVersion::AvatarJointDefaultPoseFlags);
case PacketType::MessagesData:
return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData);
case PacketType::ICEServerHeartbeat:

View file

@ -247,7 +247,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
AvatarIdentitySequenceFront,
IsReplicatedInAvatarIdentity,
AvatarIdentityLookAtSnapping,
UpdatedMannequinDefaultAvatar
UpdatedMannequinDefaultAvatar,
AvatarJointDefaultPoseFlags
};
enum class DomainConnectRequestVersion : PacketVersion {

View file

@ -256,25 +256,32 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
assert(_entity);
assert(entityTreeIsLocked());
measureBodyAcceleration();
bool positionSuccess;
_entity->setWorldPosition(bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset(), positionSuccess, false);
if (!positionSuccess) {
static QString repeatedMessage =
LogHandler::getInstance().addRepeatedMessageRegex("EntityMotionState::setWorldTransform "
"setPosition failed.*");
qCDebug(physics) << "EntityMotionState::setWorldTransform setPosition failed" << _entity->getID();
// If transform or velocities are flagged as dirty it means a network or scripted change
// occured between the beginning and end of the stepSimulation() and we DON'T want to apply
// these physics simulation results.
uint32_t flags = _entity->getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES);
if (!flags) {
// flags are clear
_entity->setWorldTransform(bulletToGLM(worldTrans.getOrigin()), bulletToGLM(worldTrans.getRotation()));
_entity->setWorldVelocity(getBodyLinearVelocity());
_entity->setWorldAngularVelocity(getBodyAngularVelocity());
_entity->setLastSimulated(usecTimestampNow());
} else {
// only set properties NOT flagged
if (!(flags & Simulation::DIRTY_TRANSFORM)) {
_entity->setWorldTransform(bulletToGLM(worldTrans.getOrigin()), bulletToGLM(worldTrans.getRotation()));
}
if (!(flags & Simulation::DIRTY_LINEAR_VELOCITY)) {
_entity->setWorldVelocity(getBodyLinearVelocity());
}
if (!(flags & Simulation::DIRTY_ANGULAR_VELOCITY)) {
_entity->setWorldAngularVelocity(getBodyAngularVelocity());
}
if (flags != (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES)) {
_entity->setLastSimulated(usecTimestampNow());
}
}
bool orientationSuccess;
_entity->setWorldOrientation(bulletToGLM(worldTrans.getRotation()), orientationSuccess, false);
if (!orientationSuccess) {
static QString repeatedMessage =
LogHandler::getInstance().addRepeatedMessageRegex("EntityMotionState::setWorldTransform "
"setOrientation failed.*");
qCDebug(physics) << "EntityMotionState::setWorldTransform setOrientation failed" << _entity->getID();
}
_entity->setVelocity(getBodyLinearVelocity());
_entity->setAngularVelocity(getBodyAngularVelocity());
_entity->setLastSimulated(usecTimestampNow());
if (_entity->getSimulatorID().isNull()) {
_loopsWithoutOwner++;
@ -530,9 +537,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
if (!_body->isActive()) {
// make sure all derivatives are zero
_entity->setVelocity(Vectors::ZERO);
_entity->setAngularVelocity(Vectors::ZERO);
_entity->setAcceleration(Vectors::ZERO);
zeroCleanObjectVelocities();
_numInactiveUpdates++;
} else {
glm::vec3 gravity = _entity->getGravity();
@ -559,9 +564,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
if (movingSlowly) {
// velocities might not be zero, but we'll fake them as such, which will hopefully help convince
// other simulating observers to deactivate their own copies
glm::vec3 zero(0.0f);
_entity->setVelocity(zero);
_entity->setAngularVelocity(zero);
zeroCleanObjectVelocities();
}
}
_numInactiveUpdates = 0;
@ -818,3 +821,22 @@ bool EntityMotionState::shouldBeLocallyOwned() const {
void EntityMotionState::upgradeOutgoingPriority(uint8_t priority) {
_outgoingPriority = glm::max<uint8_t>(_outgoingPriority, priority);
}
void EntityMotionState::zeroCleanObjectVelocities() const {
// If transform or velocities are flagged as dirty it means a network or scripted change
// occured between the beginning and end of the stepSimulation() and we DON'T want to apply
// these physics simulation results.
uint32_t flags = _entity->getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES);
if (!flags) {
_entity->setWorldVelocity(glm::vec3(0.0f));
_entity->setWorldAngularVelocity(glm::vec3(0.0f));
} else {
if (!(flags & Simulation::DIRTY_LINEAR_VELOCITY)) {
_entity->setWorldVelocity(glm::vec3(0.0f));
}
if (!(flags & Simulation::DIRTY_ANGULAR_VELOCITY)) {
_entity->setWorldAngularVelocity(glm::vec3(0.0f));
}
}
_entity->setAcceleration(glm::vec3(0.0f));
}

View file

@ -87,6 +87,7 @@ public:
protected:
// changes _outgoingPriority only if priority is larger
void upgradeOutgoingPriority(uint8_t priority);
void zeroCleanObjectVelocities() const;
#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS
bool entityTreeIsLocked() const;

View file

@ -186,7 +186,7 @@ float specularDistribution(SurfaceData surface) {
// Add geometric factors G1(n,l) and G1(n,v)
float smithInvG1NdotL = evalSmithInvG1(surface.roughness4, surface.ndotl);
denom *= surface.smithInvG1NdotV * smithInvG1NdotL;
// Don't divide by PI as it will be done later
// Don't divide by PI as this is part of the light normalization factor
float power = surface.roughness4 / denom;
return power;
}
@ -202,12 +202,11 @@ vec4 evalPBRShading(float metallic, vec3 fresnel, SurfaceData surface) {
vec3 specular = fresnelColor * power * angleAttenuation;
float diffuse = (1.0 - metallic) * angleAttenuation * (1.0 - fresnelColor.x);
diffuse /= 3.1415926;
// Diffuse is divided by PI but specular isn't because an infinitesimal volume light source
// has a multiplier of PI, says Naty Hoffman.
// We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that
// we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit
// by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means".
// (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf
// page 23 paragraph "Punctual light sources")
return vec4(specular, diffuse);
}
@ -222,9 +221,9 @@ vec4 evalPBRShadingDielectric(SurfaceData surface, float fresnel) {
vec3 specular = vec3(fresnelScalar) * power * angleAttenuation;
float diffuse = angleAttenuation * (1.0 - fresnelScalar);
diffuse /= 3.1415926;
// Diffuse is divided by PI but specular isn't because an infinitesimal volume light source
// has a multiplier of PI, says Naty Hoffman.
// We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that
// we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit
// by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means".
// (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf
// page 23 paragraph "Punctual light sources")
return vec4(specular, diffuse);
@ -239,8 +238,9 @@ vec4 evalPBRShadingMetallic(SurfaceData surface, vec3 fresnel) {
float power = specularDistribution(surface);
vec3 specular = fresnelColor * power * angleAttenuation;
// Specular isn't divided by PI because an infinitesimal volume light source
// has a multiplier of PI, says Naty Hoffman.
// We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that
// we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit
// by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means".
// (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf
// page 23 paragraph "Punctual light sources")
return vec4(specular, 0.f);

View file

@ -0,0 +1,58 @@
//
// BitVectorHelpers.h
// libraries/shared/src
//
// Created by Anthony Thibault on 1/19/18.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_BitVectorHelpers_h
#define hifi_BitVectorHelpers_h
size_t calcBitVectorSize(int numBits) {
return ((numBits - 1) >> 3) + 1;
}
// func should be of type bool func(int index)
template <typename F>
size_t writeBitVector(uint8_t* destinationBuffer, int numBits, const F& func) {
size_t totalBytes = ((numBits - 1) >> 3) + 1;
uint8_t* cursor = destinationBuffer;
uint8_t byte = 0;
uint8_t bit = 0;
for (int i = 0; i < numBits; i++) {
if (func(i)) {
byte |= (1 << bit);
}
if (++bit == BITS_IN_BYTE) {
*cursor++ = byte;
byte = 0;
bit = 0;
}
}
return totalBytes;
}
// func should be of type 'void func(int index, bool value)'
template <typename F>
size_t readBitVector(const uint8_t* sourceBuffer, int numBits, const F& func) {
size_t totalBytes = ((numBits - 1) >> 3) + 1;
const uint8_t* cursor = sourceBuffer;
uint8_t bit = 0;
for (int i = 0; i < numBits; i++) {
bool value = (bool)(*cursor & (1 << bit));
func(i, value);
if (++bit == BITS_IN_BYTE) {
cursor++;
bit = 0;
}
}
return totalBytes;
}
#endif

View file

@ -253,6 +253,7 @@ glm::vec2 getFacingDir2D(const glm::mat4& m);
inline bool isNaN(const glm::vec3& value) { return isNaN(value.x) || isNaN(value.y) || isNaN(value.z); }
inline bool isNaN(const glm::quat& value) { return isNaN(value.w) || isNaN(value.x) || isNaN(value.y) || isNaN(value.z); }
inline bool isNaN(const glm::mat3& value) { return isNaN(value * glm::vec3(1.0f)); }
glm::mat4 orthoInverse(const glm::mat4& m);

View file

@ -5,14 +5,27 @@
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
// Used by the avatar mixer to describe a single joint
// These are relative to their parent and translations are in meters
class JointData {
class EntityJointData {
public:
glm::quat rotation;
glm::vec3 translation;
bool rotationSet = false;
glm::vec3 translation; // meters
bool translationSet = false;
};
// Used by the avatar mixer to describe a single joint
// Translations relative to their parent and are in meters.
// Rotations are absolute (i.e. not relative to parent) and are in rig space.
class JointData {
public:
glm::quat rotation;
glm::vec3 translation;
// This indicates that the rotation or translation is the same as the defaultPose for the avatar.
// if true, it also means that the rotation or translation value in this structure is not valid and
// should be replaced by the avatar's actual default pose value.
bool rotationIsDefaultPose = true;
bool translationIsDefaultPose = true;
};
#endif

View file

@ -464,6 +464,36 @@ glm::vec3 SpatiallyNestable::localToWorldDimensions(const glm::vec3& dimensions,
return dimensions;
}
void SpatiallyNestable::setWorldTransform(const glm::vec3& position, const glm::quat& orientation) {
// guard against introducing NaN into the transform
if (isNaN(orientation) || isNaN(position)) {
return;
}
bool changed = false;
bool success = true;
Transform parentTransform = getParentTransform(success);
_transformLock.withWriteLock([&] {
Transform myWorldTransform;
Transform::mult(myWorldTransform, parentTransform, _transform);
if (myWorldTransform.getRotation() != orientation) {
changed = true;
myWorldTransform.setRotation(orientation);
}
if (myWorldTransform.getTranslation() != position) {
changed = true;
myWorldTransform.setTranslation(position);
}
if (changed) {
Transform::inverseMult(_transform, parentTransform, myWorldTransform);
_translationChanged = usecTimestampNow();
}
});
if (success && changed) {
locationChanged(false);
}
}
glm::vec3 SpatiallyNestable::getWorldPosition(bool& success) const {
return getTransform(success).getTranslation();
}

View file

@ -87,6 +87,7 @@ public:
virtual Transform getParentTransform(bool& success, int depth = 0) const;
void setWorldTransform(const glm::vec3& position, const glm::quat& orientation);
virtual glm::vec3 getWorldPosition(bool& success) const;
virtual glm::vec3 getWorldPosition() const;
virtual void setWorldPosition(const glm::vec3& position, bool& success, bool tellPhysics = true);

View file

@ -0,0 +1,46 @@
#include "DesktopPreviewProvider.h"
#include <PathUtils.h>
#include <QMetaEnum>
#include <QtPlugin>
#include <cassert>
DesktopPreviewProvider::DesktopPreviewProvider() {
}
constexpr const char* DesktopPreviewProvider::imagePaths[];
QSharedPointer<DesktopPreviewProvider> DesktopPreviewProvider::getInstance() {
static QSharedPointer<DesktopPreviewProvider> instance = DependencyManager::get<DesktopPreviewProvider>();
return instance;
}
QImage DesktopPreviewProvider::getPreviewDisabledImage(bool vsyncEnabled) const {
auto imageIndex = vsyncEnabled ? VSYNC : m_previewDisabledReason;
assert(imageIndex >= 0 && imageIndex <= VSYNC);
return !m_previewDisabled[imageIndex].isNull() ? m_previewDisabled[imageIndex] : loadPreviewImage(m_previewDisabled[imageIndex], PathUtils::resourcesPath() + imagePaths[imageIndex]);
}
void DesktopPreviewProvider::setPreviewDisabledReason(PreviewDisabledReasons reason) {
if (reason == VSYNC) {
qDebug() << "Preview disabled reason can't be forced to " << QMetaEnum::fromType<DesktopPreviewProvider::PreviewDisabledReasons>().valueToKey(reason);
return; // Not settable via this interface, as VSYNC is controlled by HMD plugin..
}
m_previewDisabledReason = reason;
}
void DesktopPreviewProvider::setPreviewDisabledReason(const QString& reasonString) {
PreviewDisabledReasons reason = USER;
bool ok = false;
reason = (PreviewDisabledReasons) QMetaEnum::fromType<DesktopPreviewProvider::PreviewDisabledReasons>().keyToValue(reasonString.toLatin1().data(), &ok);
if (ok) {
setPreviewDisabledReason(reason);
}
}
QImage& DesktopPreviewProvider::loadPreviewImage(QImage& image, const QString& path) const {
return image = QImage(path).mirrored().convertToFormat(QImage::Format_RGBA8888);
}

View file

@ -0,0 +1,47 @@
//
// Created by Alexander Ivash on 2018/01/08
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <DependencyManager.h>
#include <QImage>
class DesktopPreviewProvider : public QObject, public Dependency {
SINGLETON_DEPENDENCY
Q_OBJECT
DesktopPreviewProvider();
DesktopPreviewProvider(const DesktopPreviewProvider& other) = delete;
constexpr static const char* imagePaths[] = {
"images/preview-disabled.png", // USER
"images/preview-privacy.png", // SECURE_SCREEN
"images/preview.png", // VSYNC
};
public:
enum PreviewDisabledReasons {
USER = 0,
SECURE_SCREEN,
VSYNC // Not settable via this interface, as VSYNC is controlled by HMD plugin..
};
Q_ENUM(PreviewDisabledReasons)
static QSharedPointer<DesktopPreviewProvider> getInstance();
QImage getPreviewDisabledImage(bool vsyncEnabled) const;
void setPreviewDisabledReason(PreviewDisabledReasons reason);
public slots:
void setPreviewDisabledReason(const QString& reason);
private:
QImage& loadPreviewImage(QImage& image, const QString& path) const;
PreviewDisabledReasons m_previewDisabledReason = { USER };
mutable QImage m_previewDisabled[3];
};

View file

@ -31,6 +31,8 @@
const QString SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system";
const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system";
const QString TabletScriptingInterface::QML = "hifi/tablet/TabletRoot.qml";
const QString BUTTON_SORT_ORDER_KEY = "sortOrder";
const int DEFAULT_BUTTON_SORT_ORDER = 100;
static QString getUsername() {
QString username = "Unknown user";
@ -74,11 +76,21 @@ QVariant TabletButtonListModel::data(const QModelIndex& index, int role) const {
}
TabletButtonProxy* TabletButtonListModel::addButton(const QVariant& properties) {
auto tabletButtonProxy = QSharedPointer<TabletButtonProxy>(new TabletButtonProxy(properties.toMap()));
QVariantMap newProperties = properties.toMap();
if (newProperties.find(BUTTON_SORT_ORDER_KEY) == newProperties.end()) {
newProperties[BUTTON_SORT_ORDER_KEY] = DEFAULT_BUTTON_SORT_ORDER;
}
int index = computeNewButtonIndex(newProperties);
auto button = QSharedPointer<TabletButtonProxy>(new TabletButtonProxy(newProperties));
beginResetModel();
_buttons.push_back(tabletButtonProxy);
int numButtons = (int)_buttons.size();
if (index < numButtons) {
_buttons.insert(_buttons.begin() + index, button);
} else {
_buttons.push_back(button);
}
endResetModel();
return tabletButtonProxy.data();
return button.data();
}
void TabletButtonListModel::removeButton(TabletButtonProxy* button) {
@ -92,6 +104,20 @@ void TabletButtonListModel::removeButton(TabletButtonProxy* button) {
endResetModel();
}
int TabletButtonListModel::computeNewButtonIndex(const QVariantMap& newButtonProperties) {
int numButtons = (int)_buttons.size();
int newButtonSortOrder = newButtonProperties[BUTTON_SORT_ORDER_KEY].toInt();
if (newButtonSortOrder == DEFAULT_BUTTON_SORT_ORDER) return numButtons;
for (int i = 0; i < numButtons; i++) {
QVariantMap tabletButtonProperties = _buttons[i]->getProperties();
int tabletButtonSortOrder = tabletButtonProperties[BUTTON_SORT_ORDER_KEY].toInt();
if (newButtonSortOrder <= tabletButtonSortOrder) {
return i;
}
}
return numButtons;
}
TabletButtonsProxyModel::TabletButtonsProxyModel(QObject *parent)
: QSortFilterProxyModel(parent) {
}

View file

@ -115,6 +115,7 @@ protected:
friend class TabletProxy;
TabletButtonProxy* addButton(const QVariant& properties);
void removeButton(TabletButtonProxy* button);
int computeNewButtonIndex(const QVariantMap& newButtonProperties);
using List = std::list<QSharedPointer<TabletButtonProxy>>;
static QHash<int, QByteArray> _roles;
static Qt::ItemFlags _flags;

View file

@ -22,8 +22,7 @@
var button = tablet.addButton({
icon: Script.resolvePath("dynamicsTests.svg"),
text: "Dynamics",
sortOrder: 15
text: "Dynamics"
});

View file

@ -32,8 +32,7 @@
var button = tablet.addButton({
text: TABLET_BUTTON_NAME,
icon: ICON_URL,
activeIcon: ACTIVE_ICON_URL,
sortOrder: 1
activeIcon: ACTIVE_ICON_URL
});
var hasEventBridge = false;

View file

@ -30,8 +30,7 @@
var button = tablet.addButton({
text: TABLET_BUTTON_NAME,
icon: ICON_URL,
activeIcon: ACTIVE_ICON_URL,
sortOrder: 1
activeIcon: ACTIVE_ICON_URL
});
var hasEventBridge = false;

View file

@ -31,8 +31,7 @@
var button = tablet.addButton({
text: TABLET_BUTTON_NAME,
icon: ICON_URL,
activeIcon: ACTIVE_ICON_URL,
sortOrder: 1
activeIcon: ACTIVE_ICON_URL
});
var hasEventBridge = false;

View file

@ -28,8 +28,8 @@
var window = new OverlayWindow({
title: 'API Debugger',
source: qml,
width: 1200,
height: 500
width: 500,
height: 700
});
window.closed.connect(function () {

View file

@ -16,10 +16,10 @@
var button;
// Used for animating and disappearing the bubble
var bubbleOverlayTimestamp;
// Used for rate limiting the bubble sound
var lastBubbleSoundTimestamp = 0;
// Used for flashing the HUD button upon activation
var bubbleButtonFlashState = false;
// Used for flashing the HUD button upon activation
var bubbleButtonTimestamp;
// Affects bubble height
var BUBBLE_HEIGHT_SCALE = 0.15;
// The bubble model itself
@ -36,9 +36,11 @@
var bubbleActivateSound = SoundCache.getSound(Script.resolvePath("assets/sounds/bubble.wav"));
// Is the update() function connected?
var updateConnected = false;
var bubbleFlashTimer = false;
var BUBBLE_VISIBLE_DURATION_MS = 3000;
var BUBBLE_RAISE_ANIMATION_DURATION_MS = 750;
var BUBBLE_SOUND_RATE_LIMIT_MS = 15000;
// Hides the bubble model overlay and resets the button flash state
function hideOverlays() {
@ -50,11 +52,15 @@
// Make the bubble overlay visible, set its position, and play the sound
function createOverlays() {
Audio.playSound(bubbleActivateSound, {
position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z },
localOnly: true,
volume: 0.2
});
var nowTimestamp = Date.now();
if (nowTimestamp - lastBubbleSoundTimestamp >= BUBBLE_SOUND_RATE_LIMIT_MS) {
Audio.playSound(bubbleActivateSound, {
position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z },
localOnly: true,
volume: 0.2
});
lastBubbleSoundTimestamp = nowTimestamp;
}
hideOverlays();
if (updateConnected === true) {
updateConnected = false;
@ -80,10 +86,17 @@
},
visible: true
});
bubbleOverlayTimestamp = Date.now();
bubbleButtonTimestamp = bubbleOverlayTimestamp;
bubbleOverlayTimestamp = nowTimestamp;
Script.update.connect(update);
updateConnected = true;
// Flash button
if (!bubbleFlashTimer) {
bubbleFlashTimer = Script.setInterval(function () {
writeButtonProperties(bubbleButtonFlashState);
bubbleButtonFlashState = !bubbleButtonFlashState;
}, 500);
}
}
// Called from the C++ scripting interface to show the bubble overlay
@ -103,12 +116,6 @@
var delay = (timestamp - bubbleOverlayTimestamp);
var overlayAlpha = 1.0 - (delay / BUBBLE_VISIBLE_DURATION_MS);
if (overlayAlpha > 0) {
// Flash button
if ((timestamp - bubbleButtonTimestamp) >= BUBBLE_VISIBLE_DURATION_MS) {
writeButtonProperties(bubbleButtonFlashState);
bubbleButtonTimestamp = timestamp;
bubbleButtonFlashState = !bubbleButtonFlashState;
}
if (delay < BUBBLE_RAISE_ANIMATION_DURATION_MS) {
Overlays.editOverlay(bubbleOverlay, {
@ -157,8 +164,11 @@
Script.update.disconnect(update);
updateConnected = false;
}
var bubbleActive = Users.getIgnoreRadiusEnabled();
writeButtonProperties(bubbleActive);
if (bubbleFlashTimer) {
Script.clearTimeout(bubbleFlashTimer);
bubbleFlashTimer = false;
}
writeButtonProperties(Users.getIgnoreRadiusEnabled());
}
}
@ -166,6 +176,10 @@
// NOTE: the c++ calls this with just the first param -- we added a second
// just for not logging the initial state of the bubble when we startup.
function onBubbleToggled(enabled, doNotLog) {
if (bubbleFlashTimer) {
Script.clearTimeout(bubbleFlashTimer);
bubbleFlashTimer = false;
}
writeButtonProperties(enabled);
if (doNotLog !== true) {
UserActivityLogger.bubbleToggled(enabled);
@ -200,6 +214,10 @@
// Cleanup the tablet button and overlays when script is stopped
Script.scriptEnding.connect(function () {
button.clicked.disconnect(Users.toggleIgnoreRadius);
if (bubbleFlashTimer) {
Script.clearTimeout(bubbleFlashTimer);
bubbleFlashTimer = false;
}
if (tablet) {
tablet.removeButton(button);
}

View file

@ -945,8 +945,7 @@
tabletButton = tablet.addButton({
icon: tabletButtonIcon,
activeIcon: tabletButtonActiveIcon,
text: tabletButtonName,
sortOrder: 0
text: tabletButtonName
});
Messages.subscribe(channelName);

View file

@ -562,9 +562,11 @@
break;
case 'disableHmdPreview':
isHmdPreviewDisabled = Menu.isOptionChecked("Disable Preview");
DesktopPreviewProvider.setPreviewDisabledReason("SECURE_SCREEN");
Menu.setIsOptionChecked("Disable Preview", true);
break;
case 'maybeEnableHmdPreview':
DesktopPreviewProvider.setPreviewDisabledReason("USER");
Menu.setIsOptionChecked("Disable Preview", isHmdPreviewDisabled);
break;
case 'passphraseReset':
@ -591,10 +593,16 @@
getConnectionData(false);
break;
case 'enable_ChooseRecipientNearbyMode':
Script.update.connect(updateOverlays);
if (!isUpdateOverlaysWired) {
Script.update.connect(updateOverlays);
isUpdateOverlaysWired = true;
}
break;
case 'disable_ChooseRecipientNearbyMode':
Script.update.disconnect(updateOverlays);
if (isUpdateOverlaysWired) {
Script.update.disconnect(updateOverlays);
isUpdateOverlaysWired = false;
}
removeOverlays();
break;
default:
@ -635,7 +643,11 @@
// -Called when the TabletScriptingInterface::screenChanged() signal is emitted. The "type" argument can be either the string
// value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML.
function onTabletScreenChanged(type, url) {
onWalletScreen = (type === "QML" && url === WALLET_QML_SOURCE);
var onWalletScreenNow = (type === "QML" && url === WALLET_QML_SOURCE);
if (!onWalletScreenNow && onWalletScreen) {
DesktopPreviewProvider.setPreviewDisabledReason("USER");
}
onWalletScreen = onWalletScreenNow;
wireEventBridge(onWalletScreen);
// Change button to active when window is first openend, false otherwise.
if (button) {
@ -675,14 +687,18 @@
}
}
var isWired = false;
var isUpdateOverlaysWired = false;
function off() {
if (isWired) { // It is not ok to disconnect these twice, hence guard.
Users.usernameFromIDReply.disconnect(usernameFromIDReply);
Script.update.disconnect(updateOverlays);
Controller.mousePressEvent.disconnect(handleMouseEvent);
Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
isWired = false;
}
if (isUpdateOverlaysWired) {
Script.update.disconnect(updateOverlays);
isUpdateOverlaysWired = false;
}
triggerMapping.disable(); // It's ok if we disable twice.
triggerPressMapping.disable(); // see above
removeOverlays();

View file

@ -370,20 +370,23 @@ Script.include("/~/system/libraries/Xform.js");
};
this.isReady = function (controllerData) {
if (this.notPointingAtEntity(controllerData)) {
return makeRunningValues(false, [], []);
}
if (HMD.active) {
if (this.notPointingAtEntity(controllerData)) {
return makeRunningValues(false, [], []);
}
this.distanceHolding = false;
this.distanceRotating = false;
this.distanceHolding = false;
this.distanceRotating = false;
if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) {
this.prepareDistanceRotatingData(controllerData);
return makeRunningValues(true, [], []);
} else {
this.destroyContextOverlay();
return makeRunningValues(false, [], []);
if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) {
this.prepareDistanceRotatingData(controllerData);
return makeRunningValues(true, [], []);
} else {
this.destroyContextOverlay();
return makeRunningValues(false, [], []);
}
}
return makeRunningValues(false, [], []);
};
this.run = function (controllerData) {

View file

@ -89,7 +89,7 @@
this.isReady = function (controllerData) {
var otherModuleRunning = this.getOtherModule().running;
if (!otherModuleRunning) {
if (!otherModuleRunning && HMD.active) {
if (this.processLaser(controllerData)) {
this.running = true;
return ControllerDispatcherUtils.makeRunningValues(true, [], []);

View file

@ -448,7 +448,7 @@ var toolBar = (function () {
});
addButton("importEntitiesButton", "assets-01.svg", function() {
Window.openFileChanged.connect(onFileOpenChanged);
Window.browseChanged.connect(onFileOpenChanged);
Window.browseAsync("Select Model to Import", "", "*.json");
});
@ -1497,7 +1497,7 @@ function onFileOpenChanged(filename) {
// disconnect the event, otherwise the requests will stack up
try {
// Not all calls to onFileOpenChanged() connect an event.
Window.openFileChanged.disconnect(onFileOpenChanged);
Window.browseChanged.disconnect(onFileOpenChanged);
} catch (e) {
// Ignore.
}
@ -1549,7 +1549,7 @@ function handeMenuEvent(menuItem) {
}
} else if (menuItem === "Import Entities" || menuItem === "Import Entities from URL") {
if (menuItem === "Import Entities") {
Window.openFileChanged.connect(onFileOpenChanged);
Window.browseChanged.connect(onFileOpenChanged);
Window.browseAsync("Select Model to Import", "", "*.json");
} else {
Window.promptTextChanged.connect(onPromptTextChanged);

View file

@ -43,7 +43,7 @@ var activeTimer = false; // used to cancel active timer if a user plays an amima
var activeEmote = false; // to keep track of the currently playing emote
button = tablet.addButton({
//icon: "icons/tablet-icons/emote.svg", // TODO - we need graphics for this
icon: "icons/tablet-icons/EmoteAppIcon.svg",
text: EMOTE_LABEL,
sortOrder: EMOTE_APP_SORT_ORDER
});

View file

@ -37,8 +37,7 @@
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
button = tablet.addButton({
icon: "icons/tablet-icons/goto-i.svg",
text: buttonName,
sortOrder: 8
text: buttonName
});
}

View file

@ -41,8 +41,7 @@ if (Settings.getValue("HUDUIEnabled")) {
button = tablet.addButton({
icon: "icons/tablet-icons/goto-i.svg",
activeIcon: "icons/tablet-icons/goto-a.svg",
text: buttonName,
sortOrder: 8
text: buttonName
});
}

View file

@ -44,11 +44,11 @@
input[type=button] {
font-family: 'Raleway';
font-weight: bold;
font-size: 13px;
font-size: 20px;
text-transform: uppercase;
vertical-align: top;
height: 28px;
min-width: 120px;
height: 105px;
min-width: 190px;
padding: 0px 18px;
margin-right: 6px;
border-radius: 5px;
@ -98,14 +98,14 @@
</div>
<div class="content">
<p>Click an emotion to Emote:<p>
<p><input type="button" class="emote-button white" value="Crying"></p>
<p><input type="button" class="emote-button white" value="Surprised"></p>
<p><input type="button" class="emote-button white" value="Dancing"></p>
<p><input type="button" class="emote-button white" value="Cheering"></p>
<p><input type="button" class="emote-button white" value="Waving"></p>
<p><input type="button" class="emote-button white" value="Fall"></p>
<p><input type="button" class="emote-button white" value="Pointing"></p>
<p><input type="button" class="emote-button white" value="Clapping"></p>
<p><input type="button" class="emote-button white" value="Crying">
<input type="button" class="emote-button white" value="Surprised"></p>
<p><input type="button" class="emote-button white" value="Dancing">
<input type="button" class="emote-button white" value="Cheering"></p>
<p><input type="button" class="emote-button white" value="Waving">
<input type="button" class="emote-button white" value="Fall"></p>
<p><input type="button" class="emote-button white" value="Pointing">
<input type="button" class="emote-button white" value="Clapping"></p>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>

View file

@ -539,8 +539,14 @@
return startingUp;
}
function onDomainConnectionRefused(reason) {
createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED);
function onDomainConnectionRefused(reason, reasonCode) {
// the "login error" reason means that the DS couldn't decrypt the username signature
// since this eventually resolves itself for good actors we don't need to show a notification for it
var LOGIN_ERROR_REASON_CODE = 2;
if (reasonCode != LOGIN_ERROR_REASON_CODE) {
createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED);
}
}
function onEditError(msg) {

View file

@ -10,8 +10,7 @@
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var button = tablet.addButton({
icon: Script.resolvePath("assets/images/run.svg"),
text: "RUN",
sortOrder: 15
text: "RUN"
});
function onClicked() {

View file

@ -37,8 +37,7 @@
var button = tablet.addButton({
icon: "icons/tablet-icons/users-i.svg",
activeIcon: "icons/tablet-icons/users-a.svg",
text: "USERS",
sortOrder: 11
text: "USERS"
});
var onUsersScreen = false;

View file

@ -31,8 +31,7 @@ var activeButton = tablet.addButton({
icon: whiteIcon,
activeIcon: blackIcon,
text: APP_NAME,
isActive: isActive,
sortOrder: 11
isActive: isActive
});
if (isActive) {

View file

@ -32,8 +32,7 @@
var button = tablet.addButton({
icon: ICONS.icon,
activeIcon: ICONS.activeIcon,
text: TABLET_BUTTON_NAME,
sortOrder: 1
text: TABLET_BUTTON_NAME
});
var hasEventBridge = false;