mirror of
https://github.com/lubosz/overte.git
synced 2025-04-08 10:43:56 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into tablet-ui-merge
This commit is contained in:
commit
668bc35227
14 changed files with 396 additions and 126 deletions
Binary file not shown.
|
@ -76,7 +76,7 @@ OriginalDesktop.Desktop {
|
|||
WebEngine.settings.localContentCanAccessRemoteUrls = true;
|
||||
|
||||
[ // Allocate the standard buttons in the correct order. They will get images, etc., via scripts.
|
||||
"hmdToggle", "mute", "mod", "bubble", "help",
|
||||
"hmdToggle", "mute", "pal", "bubble", "help",
|
||||
"hudToggle",
|
||||
"com.highfidelity.interface.system.editButton", "marketplace", "snapshot", "goto"
|
||||
].forEach(function (name) {
|
||||
|
|
52
interface/resources/qml/hifi/LetterboxMessage.qml
Normal file
52
interface/resources/qml/hifi/LetterboxMessage.qml
Normal file
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// LetterboxMessage.qml
|
||||
// qml/hifi
|
||||
//
|
||||
// Created by Zach Fox and Howard Stearns on 1/5/2017
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import "../styles-uit"
|
||||
|
||||
Item {
|
||||
property alias text: popupText.text
|
||||
property real radius: hifi.dimensions.borderRadius
|
||||
visible: false
|
||||
id: letterbox
|
||||
anchors.fill: parent
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "black"
|
||||
opacity: 0.5
|
||||
radius: radius
|
||||
}
|
||||
Rectangle {
|
||||
width: Math.max(parent.width * 0.75, 400)
|
||||
height: popupText.contentHeight*1.5
|
||||
anchors.centerIn: parent
|
||||
radius: radius
|
||||
color: "white"
|
||||
FiraSansSemiBold {
|
||||
id: popupText
|
||||
size: hifi.fontSizes.textFieldInput
|
||||
color: hifi.colors.darkGray
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 15
|
||||
anchors.rightMargin: 15
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onClicked: {
|
||||
letterbox.visible = false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,21 +11,6 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
/* TODO:
|
||||
|
||||
prototype:
|
||||
- only show kick/mute when canKick
|
||||
- margins everywhere
|
||||
- column head centering
|
||||
- column head font
|
||||
- proper button .svg on toolbar
|
||||
|
||||
mvp:
|
||||
- Show all participants, including ignored, and populate initial ignore/mute status.
|
||||
- If name is elided, hover should scroll name left so the full name can be read.
|
||||
|
||||
*/
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import "../styles-uit"
|
||||
|
@ -326,6 +311,11 @@ Item {
|
|||
visible: iAmAdmin
|
||||
color: hifi.colors.lightGrayText
|
||||
}
|
||||
function letterbox(message) {
|
||||
letterboxMessage.text = message;
|
||||
letterboxMessage.visible = true
|
||||
|
||||
}
|
||||
// This Rectangle refers to the [?] popup button next to "NAMES"
|
||||
Rectangle {
|
||||
color: hifi.colors.tableBackgroundLight
|
||||
|
@ -349,7 +339,9 @@ Item {
|
|||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
hoverEnabled: true
|
||||
onClicked: namesPopup.visible = true
|
||||
onClicked: letterbox("Bold names in the list are Avatar Display Names.\n" +
|
||||
"If a Display Name isn't set, a unique Session Display Name is assigned." +
|
||||
"\n\nAdministrators of this domain can also see the Username or Machine ID associated with each avatar present.")
|
||||
onEntered: helpText.color = hifi.colors.baseGrayHighlight
|
||||
onExited: helpText.color = hifi.colors.darkGray
|
||||
}
|
||||
|
@ -378,87 +370,14 @@ Item {
|
|||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
hoverEnabled: true
|
||||
onClicked: adminPopup.visible = true
|
||||
onClicked: letterbox('Silencing a user mutes their microphone. Silenced users can unmute themselves by clicking the "UNMUTE" button on their HUD.\n\n' +
|
||||
"Banning a user will remove them from this domain and prevent them from returning. You can un-ban users from your domain's settings page.)")
|
||||
onEntered: adminHelpText.color = "#94132e"
|
||||
onExited: adminHelpText.color = hifi.colors.redHighlight
|
||||
}
|
||||
}
|
||||
// Explanitory popup upon clicking "[?]" next to "NAMES"
|
||||
Item {
|
||||
visible: false
|
||||
id: namesPopup
|
||||
anchors.fill: pal
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "black"
|
||||
opacity: 0.5
|
||||
radius: hifi.dimensions.borderRadius
|
||||
}
|
||||
Rectangle {
|
||||
width: Math.max(parent.width * 0.75, 400)
|
||||
height: popupText.contentHeight*1.5
|
||||
anchors.centerIn: parent
|
||||
radius: hifi.dimensions.borderRadius
|
||||
color: "white"
|
||||
FiraSansSemiBold {
|
||||
id: popupText
|
||||
text: "Bold names in the list are Avatar Display Names.\n" +
|
||||
"If a Display Name isn't set, a unique Session Display Name is assigned." +
|
||||
"\n\nAdministrators of this domain can also see the Username or Machine ID associated with each avatar present."
|
||||
size: hifi.fontSizes.textFieldInput
|
||||
color: hifi.colors.darkGray
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 15
|
||||
anchors.rightMargin: 15
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onClicked: {
|
||||
namesPopup.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
// Explanitory popup upon clicking "[?]" next to "ADMIN"
|
||||
Item {
|
||||
visible: false
|
||||
id: adminPopup
|
||||
anchors.fill: pal
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "black"
|
||||
opacity: 0.5
|
||||
radius: hifi.dimensions.borderRadius
|
||||
}
|
||||
Rectangle {
|
||||
width: Math.max(parent.width * 0.75, 400)
|
||||
height: adminPopupText.contentHeight*1.5
|
||||
anchors.centerIn: parent
|
||||
radius: hifi.dimensions.borderRadius
|
||||
color: "white"
|
||||
FiraSansSemiBold {
|
||||
id: adminPopupText
|
||||
text: 'Silencing a user mutes their microphone. Silenced users can unmute themselves by clicking the "UNMUTE" button on their HUD.\n\n' +
|
||||
"Banning a user will remove them from this domain and prevent them from returning. You can un-ban users from your domain's settings page."
|
||||
size: hifi.fontSizes.textFieldInput
|
||||
color: hifi.colors.darkGray
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 15
|
||||
anchors.rightMargin: 15
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onClicked: {
|
||||
adminPopup.visible = false
|
||||
}
|
||||
}
|
||||
LetterboxMessage {
|
||||
id: letterboxMessage
|
||||
}
|
||||
|
||||
function findSessionIndex(sessionId, optionalData) { // no findIndex in .qml
|
||||
|
@ -495,14 +414,20 @@ Item {
|
|||
sortModel();
|
||||
break;
|
||||
case 'select':
|
||||
var sessionId = message.params[0];
|
||||
var sessionIds = message.params[0];
|
||||
var selected = message.params[1];
|
||||
var userIndex = findSessionIndex(sessionId);
|
||||
if (selected) {
|
||||
table.selection.clear(); // for now, no multi-select
|
||||
table.selection.select(userIndex);
|
||||
var userIndex = findSessionIndex(sessionIds[0]);
|
||||
if (sessionIds.length > 1) {
|
||||
letterbox('Only one user can be selected at a time.');
|
||||
} else if (userIndex < 0) {
|
||||
letterbox('The last editor is not among this list of users.');
|
||||
} else {
|
||||
table.selection.deselect(userIndex);
|
||||
if (selected) {
|
||||
table.selection.clear(); // for now, no multi-select
|
||||
table.selection.select(userIndex);
|
||||
} else {
|
||||
table.selection.deselect(userIndex);
|
||||
}
|
||||
}
|
||||
break;
|
||||
// Received an "updateUsername()" request from the JS
|
||||
|
|
|
@ -6159,7 +6159,7 @@ void Application::loadScriptURLDialog() const {
|
|||
|
||||
void Application::toggleLogDialog() {
|
||||
if (! _logDialog) {
|
||||
_logDialog = new LogDialog(_glWidget, getLogger());
|
||||
_logDialog = new LogDialog(nullptr, getLogger());
|
||||
}
|
||||
|
||||
if (_logDialog->isVisible()) {
|
||||
|
|
|
@ -44,7 +44,7 @@ const QString HIGHLIGHT_COLOR = "#3366CC";
|
|||
int qTextCursorMeta = qRegisterMetaType<QTextCursor>("QTextCursor");
|
||||
int qTextBlockMeta = qRegisterMetaType<QTextBlock>("QTextBlock");
|
||||
|
||||
LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : QDialog(parent, Qt::Dialog) {
|
||||
LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : QDialog(parent, Qt::Window) {
|
||||
|
||||
_logger = logger;
|
||||
setWindowTitle("Log");
|
||||
|
|
|
@ -20,7 +20,7 @@ var DEFAULT_SCRIPTS = [
|
|||
"system/hmd.js",
|
||||
"system/marketplaces/marketplace.js",
|
||||
"system/edit.js",
|
||||
"system/mod.js",
|
||||
"system/pal.js", //"system/mod.js", // older UX, if you prefer
|
||||
"system/selectAudioDevice.js",
|
||||
"system/notifications.js",
|
||||
"system/controllers/controllerDisplayManager.js",
|
||||
|
|
BIN
scripts/system/assets/models/Avatar-Overlay-v1.fbx
Normal file
BIN
scripts/system/assets/models/Avatar-Overlay-v1.fbx
Normal file
Binary file not shown.
|
@ -20,6 +20,9 @@
|
|||
// When partially squeezing over a HUD element, a laser or the reticle is shown where the active hand
|
||||
// controller beam intersects the HUD.
|
||||
|
||||
var systemLaserOn = false;
|
||||
|
||||
|
||||
Script.include("../libraries/controllers.js");
|
||||
|
||||
// UTILITIES -------------
|
||||
|
@ -121,6 +124,12 @@ function ignoreMouseActivity() {
|
|||
if (!Reticle.allowMouseCapture) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if the lasers are on, then reticle/mouse should be hidden and we can ignore it for seeking or depth updating
|
||||
if (systemLaserOn) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var pos = Reticle.position;
|
||||
if (!pos || (pos.x == -1 && pos.y == -1)) {
|
||||
return true;
|
||||
|
@ -261,6 +270,12 @@ var ONE_MINUS_WEIGHTING = 1 - WEIGHTING;
|
|||
var AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO = 20;
|
||||
function isShakingMouse() { // True if the person is waving the mouse around trying to find it.
|
||||
var now = Date.now(), mouse = Reticle.position, isShaking = false;
|
||||
|
||||
// if the lasers are on, then we ignore mouse shaking
|
||||
if (systemLaserOn) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lastIntegration && (lastIntegration !== now)) {
|
||||
var velocity = Vec3.length(Vec3.subtract(mouse, lastMouse)) / (now - lastIntegration);
|
||||
averageMouseVelocity = (ONE_MINUS_WEIGHTING * averageMouseVelocity) + (WEIGHTING * velocity);
|
||||
|
@ -275,9 +290,14 @@ function isShakingMouse() { // True if the person is waving the mouse around try
|
|||
var NON_LINEAR_DIVISOR = 2;
|
||||
var MINIMUM_SEEK_DISTANCE = 0.1;
|
||||
function updateSeeking(doNotStartSeeking) {
|
||||
// if the lasers are on, then we never do seeking
|
||||
if (systemLaserOn) {
|
||||
isSeeking = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!doNotStartSeeking && (!Reticle.visible || isShakingMouse())) {
|
||||
if (!isSeeking) {
|
||||
print('Start seeking mouse.');
|
||||
isSeeking = true;
|
||||
}
|
||||
} // e.g., if we're about to turn it on with first movement.
|
||||
|
@ -287,7 +307,6 @@ function updateSeeking(doNotStartSeeking) {
|
|||
averageMouseVelocity = lastIntegration = 0;
|
||||
var lookAt2D = HMD.getHUDLookAtPosition2D();
|
||||
if (!lookAt2D) { // If this happens, something has gone terribly wrong.
|
||||
print('Cannot seek without lookAt position');
|
||||
isSeeking = false;
|
||||
return; // E.g., if parallel to location in HUD
|
||||
}
|
||||
|
@ -303,7 +322,6 @@ function updateSeeking(doNotStartSeeking) {
|
|||
}
|
||||
var okX = !updateDimension('x'), okY = !updateDimension('y'); // Evaluate both. Don't short-circuit.
|
||||
if (okX && okY) {
|
||||
print('Finished seeking mouse');
|
||||
isSeeking = false;
|
||||
} else {
|
||||
Reticle.setPosition(copy); // Not setReticlePosition
|
||||
|
@ -322,7 +340,7 @@ function updateMouseActivity(isClick) {
|
|||
return;
|
||||
} // Bug: mouse clicks should keep going. Just not hand controller clicks
|
||||
handControllerLockOut.update(now);
|
||||
Reticle.visible = true;
|
||||
Reticle.visible = !systemLaserOn;
|
||||
}
|
||||
function expireMouseCursor(now) {
|
||||
if (!isPointingAtOverlay() && mouseCursorActivity.expired(now)) {
|
||||
|
@ -476,7 +494,6 @@ var LASER_ALPHA = 0.5;
|
|||
var LASER_SEARCH_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: LASER_ALPHA};
|
||||
var LASER_TRIGGER_COLOR_XYZW = {x: 250 / 255, y: 10 / 255, z: 10 / 255, w: LASER_ALPHA};
|
||||
var SYSTEM_LASER_DIRECTION = {x: 0, y: 0, z: -1};
|
||||
var systemLaserOn = false;
|
||||
function clearSystemLaser() {
|
||||
if (!systemLaserOn) {
|
||||
return;
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
<input type="button" id="visible" class="glyph" value="" />
|
||||
</div>
|
||||
<input type="button" id="teleport" value="Jump To Selection" />
|
||||
<input type="button" id="pal" class="glyph" value="" />
|
||||
<input type="button" class="red" id="delete" value="Delete" />
|
||||
</div>
|
||||
<div id="entity-list">
|
||||
|
@ -94,4 +95,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -39,6 +39,7 @@ function loaded() {
|
|||
elInView = document.getElementById("in-view")
|
||||
elRadius = document.getElementById("radius");
|
||||
elTeleport = document.getElementById("teleport");
|
||||
elPal = document.getElementById("pal");
|
||||
elEntityTable = document.getElementById("entity-table");
|
||||
elInfoToggle = document.getElementById("info-toggle");
|
||||
elInfoToggleGlyph = elInfoToggle.firstChild;
|
||||
|
@ -274,6 +275,9 @@ function loaded() {
|
|||
elTeleport.onclick = function () {
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'teleport' }));
|
||||
}
|
||||
elPal.onclick = function () {
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'pal' }));
|
||||
}
|
||||
elDelete.onclick = function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
|
||||
refreshEntities();
|
||||
|
|
|
@ -98,7 +98,6 @@ EntityListTool = function(opts) {
|
|||
webView.emitScriptEvent(JSON.stringify(data));
|
||||
}
|
||||
|
||||
|
||||
webView.webEventReceived.connect(function(data) {
|
||||
data = JSON.parse(data);
|
||||
if (data.type == "selectionUpdate") {
|
||||
|
@ -120,6 +119,23 @@ EntityListTool = function(opts) {
|
|||
if (selectionManager.hasSelection()) {
|
||||
MyAvatar.position = selectionManager.worldPosition;
|
||||
}
|
||||
} else if (data.type == "pal") {
|
||||
var sessionIds = {}; // Collect the sessionsIds of all selected entitities, w/o duplicates.
|
||||
selectionManager.selections.forEach(function (id) {
|
||||
var lastEditedBy = Entities.getEntityProperties(id, 'lastEditedBy').lastEditedBy;
|
||||
if (lastEditedBy) {
|
||||
sessionIds[lastEditedBy] = true;
|
||||
}
|
||||
});
|
||||
var dedupped = Object.keys(sessionIds);
|
||||
if (!selectionManager.selections.length) {
|
||||
Window.alert('No objects selected.');
|
||||
} else if (!dedupped.length) {
|
||||
Window.alert('There were no recent users of the ' + selectionManager.selections.length + ' selected objects.');
|
||||
} else {
|
||||
// No need to subscribe if we're just sending.
|
||||
Messages.sendMessage('com.highfidelity.pal', JSON.stringify({method: 'select', params: [dedupped, true]}), 'local');
|
||||
}
|
||||
} else if (data.type == "delete") {
|
||||
deleteSelectedEntities();
|
||||
} else if (data.type == "toggleLocked") {
|
||||
|
@ -140,4 +156,4 @@ EntityListTool = function(opts) {
|
|||
});
|
||||
|
||||
return that;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -11,7 +11,19 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
// FIXME when we make this a defaultScript: (function() { // BEGIN LOCAL_SCOPE
|
||||
// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed
|
||||
// something, will revisit as this is sorta horrible.
|
||||
const UNSELECTED_TEXTURES = {"idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png"),
|
||||
"idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png")
|
||||
};
|
||||
const SELECTED_TEXTURES = { "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png"),
|
||||
"idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png")
|
||||
};
|
||||
|
||||
const UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6};
|
||||
const SELECTED_COLOR = {red: 0xf3, green: 0x91, blue: 0x29};
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
Script.include("/~/system/libraries/controllers.js");
|
||||
|
||||
|
@ -19,8 +31,19 @@ Script.include("/~/system/libraries/controllers.js");
|
|||
// Overlays.
|
||||
//
|
||||
var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier.
|
||||
function ExtendedOverlay(key, type, properties, selected) { // A wrapper around overlays to store the key it is associated with.
|
||||
|
||||
function ExtendedOverlay(key, type, properties, selected, hasModel) { // A wrapper around overlays to store the key it is associated with.
|
||||
overlays[key] = this;
|
||||
if (hasModel) {
|
||||
var modelKey = key + "-m";
|
||||
this.model = new ExtendedOverlay(modelKey, "model", {
|
||||
url: Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx"),
|
||||
textures: textures(selected),
|
||||
ignoreRayIntersection: true
|
||||
}, false, false);
|
||||
} else {
|
||||
this.model = undefined;
|
||||
}
|
||||
this.key = key;
|
||||
this.selected = selected || false; // not undefined
|
||||
this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected...
|
||||
|
@ -34,14 +57,24 @@ ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and da
|
|||
ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay
|
||||
Overlays.editOverlay(this.activeOverlay, properties);
|
||||
};
|
||||
const UNSELECTED_COLOR = {red: 20, green: 250, blue: 20};
|
||||
const SELECTED_COLOR = {red: 250, green: 20, blue: 20};
|
||||
function color(selected) { return selected ? SELECTED_COLOR : UNSELECTED_COLOR; }
|
||||
|
||||
function color(selected) {
|
||||
return selected ? SELECTED_COLOR : UNSELECTED_COLOR;
|
||||
}
|
||||
|
||||
function textures(selected) {
|
||||
return selected ? SELECTED_TEXTURES : UNSELECTED_TEXTURES;
|
||||
}
|
||||
|
||||
ExtendedOverlay.prototype.select = function (selected) {
|
||||
if (this.selected === selected) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.editOverlay({color: color(selected)});
|
||||
if (this.model) {
|
||||
this.model.editOverlay({textures: textures(selected)});
|
||||
}
|
||||
this.selected = selected;
|
||||
};
|
||||
// Class methods:
|
||||
|
@ -91,7 +124,7 @@ function HighlightedEntity(id, entityProperties) {
|
|||
},
|
||||
lineWidth: 1.0,
|
||||
ignoreRayIntersection: true,
|
||||
drawInFront: true
|
||||
drawInFront: false // Arguable. For now, let's not distract with mysterious wires around the scene.
|
||||
});
|
||||
HighlightedEntity.overlays.push(this);
|
||||
}
|
||||
|
@ -167,12 +200,12 @@ pal.fromQml.connect(function (message) { // messages are {method, params}, like
|
|||
//
|
||||
function addAvatarNode(id) {
|
||||
var selected = ExtendedOverlay.isSelected(id);
|
||||
return new ExtendedOverlay(id, "sphere", { // 3d so we don't go cross-eyed looking at it, but on top of everything
|
||||
solid: true,
|
||||
alpha: 0.8,
|
||||
color: color(selected),
|
||||
drawInFront: true
|
||||
}, selected);
|
||||
return new ExtendedOverlay(id, "sphere", {
|
||||
drawInFront: true,
|
||||
solid: true,
|
||||
alpha: 0.8,
|
||||
color: color(selected),
|
||||
ignoreRayIntersection: false}, selected, true);
|
||||
}
|
||||
function populateUserList() {
|
||||
var data = [];
|
||||
|
@ -227,6 +260,7 @@ function updateOverlays() {
|
|||
if (!id) {
|
||||
return; // don't update ourself
|
||||
}
|
||||
|
||||
var overlay = ExtendedOverlay.get(id);
|
||||
if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back.
|
||||
print('Adding non-PAL avatar node', id);
|
||||
|
@ -235,11 +269,36 @@ function updateOverlays() {
|
|||
var avatar = AvatarList.getAvatar(id);
|
||||
var target = avatar.position;
|
||||
var distance = Vec3.distance(target, eye);
|
||||
var offset = 0.2;
|
||||
|
||||
// base offset on 1/2 distance from hips to head if we can
|
||||
var headIndex = avatar.getJointIndex("Head");
|
||||
if (headIndex > 0) {
|
||||
offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2;
|
||||
}
|
||||
|
||||
// get diff between target and eye (a vector pointing to the eye from avatar position)
|
||||
var diff = Vec3.subtract(target, eye);
|
||||
|
||||
// move a bit in front, towards the camera
|
||||
target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset));
|
||||
|
||||
// now bump it up a bit
|
||||
target.y = target.y + offset;
|
||||
|
||||
overlay.ping = pingPong;
|
||||
overlay.editOverlay({
|
||||
position: target,
|
||||
dimensions: 0.05 * distance // constant apparent size
|
||||
dimensions: 0.032 * distance
|
||||
});
|
||||
if (overlay.model) {
|
||||
overlay.model.ping = pingPong;
|
||||
overlay.model.editOverlay({
|
||||
position: target,
|
||||
scale: 0.2 * distance, // constant apparent size
|
||||
rotation: Camera.orientation
|
||||
});
|
||||
}
|
||||
});
|
||||
pingPong = !pingPong;
|
||||
ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.)
|
||||
|
@ -262,7 +321,7 @@ function removeOverlays() {
|
|||
function handleClick(pickRay) {
|
||||
ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
|
||||
// Don't select directly. Tell qml, who will give us back a list of ids.
|
||||
var message = {method: 'select', params: [overlay.key, !overlay.selected]};
|
||||
var message = {method: 'select', params: [[overlay.key], !overlay.selected]};
|
||||
pal.sendToQml(message);
|
||||
return true;
|
||||
});
|
||||
|
@ -333,6 +392,31 @@ function onClicked() {
|
|||
pal.setVisible(!pal.visible);
|
||||
}
|
||||
|
||||
//
|
||||
// Message from other scripts, such as edit.js
|
||||
//
|
||||
var CHANNEL = 'com.highfidelity.pal';
|
||||
function receiveMessage(channel, messageString, senderID) {
|
||||
if ((channel !== CHANNEL) ||
|
||||
(senderID !== MyAvatar.sessionUUID)) {
|
||||
return;
|
||||
}
|
||||
var message = JSON.parse(messageString);
|
||||
switch (message.method) {
|
||||
case 'select':
|
||||
if (!pal.visible) {
|
||||
onClicked();
|
||||
}
|
||||
pal.sendToQml(message); // Accepts objects, not just strings.
|
||||
break;
|
||||
default:
|
||||
print('Unrecognized PAL message', messageString);
|
||||
}
|
||||
}
|
||||
Messages.subscribe(CHANNEL);
|
||||
Messages.messageReceived.connect(receiveMessage);
|
||||
|
||||
|
||||
var AVERAGING_RATIO = 0.05;
|
||||
var LOUDNESS_FLOOR = 11.0;
|
||||
var LOUDNESS_SCALE = 2.8 / 5.0;
|
||||
|
@ -412,8 +496,10 @@ Script.scriptEnding.connect(function () {
|
|||
Users.usernameFromIDReply.disconnect(usernameFromIDReply);
|
||||
Window.domainChanged.disconnect(clearIgnoredInQMLAndClosePAL);
|
||||
Window.domainConnectionRefused.disconnect(clearIgnoredInQMLAndClosePAL);
|
||||
Messages.unsubscribe(CHANNEL);
|
||||
Messages.messageReceived.disconnect(receiveMessage);
|
||||
off();
|
||||
});
|
||||
|
||||
|
||||
// FIXME: }()); // END LOCAL_SCOPE
|
||||
}()); // END LOCAL_SCOPE
|
||||
|
|
169
scripts/tutorials/entity_scripts/chair.js
Normal file
169
scripts/tutorials/entity_scripts/chair.js
Normal file
|
@ -0,0 +1,169 @@
|
|||
// chair.js
|
||||
//
|
||||
// Restrictions right now:
|
||||
// Chair objects need to be set as not colliding with avatars, so that they pull avatar
|
||||
// avatar into collision with them. Also they need to be at or above standing height
|
||||
// (like a stool).
|
||||
//
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function(){
|
||||
|
||||
var CHECK_INTERVAL_MSECS = 250; // When sitting, check for need to stand up
|
||||
var SETTINGS_INTERVAL_MSECS = 1000; // Periodically check user data for updates
|
||||
var DEFAULT_SIT_DISTANCE = 1.0; // How far away from the chair can you sit?
|
||||
var HYSTERESIS = 1.1;
|
||||
|
||||
var sitTarget = { x: 0, y: 0, z: 0 }; // Offset where your butt should go relative
|
||||
// to the object's center.
|
||||
var SITTING = 0;
|
||||
var STANDING = 1;
|
||||
|
||||
var state = STANDING;
|
||||
var sitDistance = DEFAULT_SIT_DISTANCE;
|
||||
|
||||
var entity;
|
||||
var props;
|
||||
var checkTimer = false;
|
||||
var settingsTimer = false;
|
||||
var sitting = false;
|
||||
|
||||
var _this;
|
||||
|
||||
var WANT_DEBUG = false;
|
||||
function debugPrint(string) {
|
||||
if (WANT_DEBUG) {
|
||||
print(string);
|
||||
}
|
||||
}
|
||||
|
||||
function howFarAway(position) {
|
||||
return Vec3.distance(MyAvatar.position, position);
|
||||
}
|
||||
|
||||
function isSeatOpen(position, distance) {
|
||||
closest = true;
|
||||
AvatarList.getAvatarIdentifiers().forEach(function(avatarSessionUUID) {
|
||||
var avatar = AvatarList.getAvatar(avatarSessionUUID);
|
||||
if (avatarSessionUUID && Vec3.distance(avatar.position, position) < distance) {
|
||||
debugPrint("Seat Occupied!");
|
||||
closest = false;
|
||||
}
|
||||
});
|
||||
return closest;
|
||||
}
|
||||
|
||||
function enterSitPose() {
|
||||
var rot;
|
||||
var UPPER_LEG_ANGLE = 240;
|
||||
var LOWER_LEG_ANGLE = -80;
|
||||
rot = Quat.safeEulerAngles(MyAvatar.getJointRotation("RightUpLeg"));
|
||||
MyAvatar.setJointData("RightUpLeg", Quat.fromPitchYawRollDegrees(UPPER_LEG_ANGLE, rot.y, rot.z), MyAvatar.getJointTranslation("RightUpLeg"));
|
||||
rot = Quat.safeEulerAngles(MyAvatar.getJointRotation("RightLeg"));
|
||||
MyAvatar.setJointData("RightLeg", Quat.fromPitchYawRollDegrees(LOWER_LEG_ANGLE, rot.y, rot.z), MyAvatar.getJointTranslation("RightLeg"));
|
||||
rot = Quat.safeEulerAngles(MyAvatar.getJointRotation("LeftUpLeg"));
|
||||
MyAvatar.setJointData("LeftUpLeg", Quat.fromPitchYawRollDegrees(UPPER_LEG_ANGLE, rot.y, rot.z), MyAvatar.getJointTranslation("LeftUpLeg"));
|
||||
rot = Quat.safeEulerAngles(MyAvatar.getJointRotation("LeftLeg"));
|
||||
MyAvatar.setJointData("LeftLeg", Quat.fromPitchYawRollDegrees(LOWER_LEG_ANGLE, rot.y, rot.z), MyAvatar.getJointTranslation("LeftLeg"));
|
||||
}
|
||||
|
||||
function leaveSitPose() {
|
||||
MyAvatar.clearJointData("RightUpLeg");
|
||||
MyAvatar.clearJointData("LeftUpLeg");
|
||||
MyAvatar.clearJointData("RightLeg");
|
||||
MyAvatar.clearJointData("LeftLeg");
|
||||
}
|
||||
|
||||
function sitDown(position, rotation) {
|
||||
var eulers = Quat.safeEulerAngles(MyAvatar.orientation);
|
||||
eulers.y = Quat.safeEulerAngles(rotation).y;
|
||||
MyAvatar.position = Vec3.sum(position, Vec3.multiplyQbyV(props.rotation, sitTarget));
|
||||
MyAvatar.orientation = Quat.fromPitchYawRollDegrees(eulers.x, eulers.y, eulers.z);
|
||||
|
||||
enterSitPose();
|
||||
state = SITTING;
|
||||
}
|
||||
|
||||
this.preload = function(entityID) {
|
||||
// Load the sound and range from the entity userData fields, and note the position of the entity.
|
||||
debugPrint("chair preload");
|
||||
entity = entityID;
|
||||
_this = this;
|
||||
settingsTimer = Script.setInterval(this.checkSettings, SETTINGS_INTERVAL_MSECS);
|
||||
};
|
||||
|
||||
this.maybeStand = function() {
|
||||
props = Entities.getEntityProperties(entity, [ "position", "rotation" ]);
|
||||
// First, check if the entity is far enough away to not need to do anything with it
|
||||
var howFar = howFarAway(props.position);
|
||||
if ((state === SITTING) && (howFar > sitDistance * HYSTERESIS)) {
|
||||
leaveSitPose();
|
||||
Script.clearInterval(checkTimer);
|
||||
checkTimer = null;
|
||||
state = STANDING;
|
||||
debugPrint("Standing");
|
||||
}
|
||||
}
|
||||
|
||||
this.clickDownOnEntity = function(entityID, mouseEvent) {
|
||||
// If entity is clicked, sit
|
||||
props = Entities.getEntityProperties(entity, [ "position", "rotation" ]);
|
||||
if ((state === STANDING) && isSeatOpen(props.position, sitDistance)) {
|
||||
sitDown(props.position, props.rotation);
|
||||
checkTimer = Script.setInterval(this.maybeStand, CHECK_INTERVAL_MSECS);
|
||||
debugPrint("Sitting from mouse click");
|
||||
}
|
||||
}
|
||||
|
||||
this.startFarTrigger = function() {
|
||||
// If entity is far clicked, sit
|
||||
props = Entities.getEntityProperties(entity, [ "position", "rotation" ]);
|
||||
if ((state === STANDING) && isSeatOpen(props.position, sitDistance)) {
|
||||
sitDown(props.position, props.rotation);
|
||||
checkTimer = Script.setInterval(this.maybeStand, CHECK_INTERVAL_MSECS);
|
||||
debugPrint("Sitting from far trigger");
|
||||
}
|
||||
}
|
||||
|
||||
this.checkSettings = function() {
|
||||
var dataProps = Entities.getEntityProperties(entity, [ "userData" ]);
|
||||
if (dataProps.userData) {
|
||||
var data = JSON.parse(dataProps.userData);
|
||||
if (data.sitDistance) {
|
||||
if (!(sitDistance === data.sitDistance)) {
|
||||
debugPrint("Read new sit distance: " + data.sitDistance);
|
||||
}
|
||||
sitDistance = data.sitDistance;
|
||||
}
|
||||
if (data.sitTarget) {
|
||||
if (data.sitTarget.y && (data.sitTarget.y != sitTarget.y)) {
|
||||
debugPrint("Read new sitTarget.y: " + data.sitTarget.y);
|
||||
sitTarget.y = data.sitTarget.y;
|
||||
}
|
||||
if (data.sitTarget.x && (data.sitTarget.x != sitTarget.x)) {
|
||||
debugPrint("Read new sitTarget.x: " + data.sitTarget.x);
|
||||
sitTarget.x = data.sitTarget.x;
|
||||
}
|
||||
if (data.sitTarget.z && (data.sitTarget.z != sitTarget.z)) {
|
||||
debugPrint("Read new sitTarget.z: " + data.sitTarget.z);
|
||||
sitTarget.z = data.sitTarget.z;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.unload = function(entityID) {
|
||||
debugPrint("chair unload");
|
||||
if (checkTimer) {
|
||||
Script.clearInterval(checkTimer);
|
||||
}
|
||||
if (settingsTimer) {
|
||||
Script.clearInterval(settingsTimer);
|
||||
}
|
||||
};
|
||||
|
||||
})
|
Loading…
Reference in a new issue