3
0
Fork 0
mirror of https://github.com/lubosz/overte.git synced 2025-04-26 22:56:02 +02:00

Merge branch 'hifi-master' into kpi-v1-modkit-rc

This commit is contained in:
humbletim 2019-07-26 01:36:52 -04:00
commit 058c81aa1a
172 changed files with 7742 additions and 1903 deletions
BUILD_WIN.mdCMakeLists.txtREADME.md
assignment-client/src
cmake/macros
interface
launchers
libraries

View file

@ -66,12 +66,6 @@ Open `%HIFI_DIR%\build\hifi.sln` using Visual Studio.
Change the Solution Configuration (menu ribbon under the menu bar, next to the green play button) from "Debug" to "Release" for best performance.
Create another environment variable (see Step #3)
* Set "Variable name": `PreferredToolArchitecture`
* Set "Variable value": `x64`
Restart Visual Studio for the new variable to take effect.
Run from the menu bar `Build > Build Solution`.
### Step 6. Testing Interface

View file

@ -10,6 +10,11 @@ endif()
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/macros/TargetPython.cmake")
target_python()
if (WIN32 AND NOT HIFI_ANDROID)
# Force x64 toolset
set(CMAKE_GENERATOR_TOOLSET "host=x64" CACHE STRING "64-bit toolset" FORCE)
endif()
# set our OS X deployment target
# (needs to be set before first project() call and before prebuild.py)
# Will affect VCPKG dependencies

View file

@ -17,7 +17,7 @@ Documentation
=========
Documentation is available at [docs.highfidelity.com](https://docs.highfidelity.com), if something is missing, please suggest it via a new job on Worklist (add to the hifi-docs project).
There is also detailed [documentation on our coding standards](https://wiki.highfidelity.com/wiki/Coding_Standards).
There is also detailed [documentation on our coding standards](CODING_STANDARD.md).
Contributor License Agreement (CLA)
=========

View file

@ -204,10 +204,8 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
if (traitType == AvatarTraits::SkeletonModelURL) {
// special handling for skeleton model URL, since we need to make sure it is in the whitelist
checkSkeletonURLAgainstWhitelist(slaveSharedData, sendingNode, packetTraitVersion);
#ifdef AVATAR_POP_CHECK
// Deferred for UX work. With no PoP check, no need to get the .fst.
_avatar->fetchAvatarFST();
#endif
}
anyTraitsChanged = true;

View file

@ -1292,6 +1292,7 @@ void OctreeServer::aboutToFinish() {
for (auto& it : _sendThreads) {
auto& sendThread = *it.second;
sendThread.setIsShuttingDown();
sendThread.terminate();
}
// Clear will destruct all the unique_ptr to OctreeSendThreads which will call the GenericThread's dtor

View file

@ -131,8 +131,11 @@ macro(SET_PACKAGING_PARAMETERS)
endif ()
if (DEPLOY_PACKAGE)
# for deployed packages always grab the serverless content
set(DOWNLOAD_SERVERLESS_CONTENT ON)
# For deployed packages we do not grab the serverless content any longer.
# Instead, we deploy just the serverless content that is in the interface/resources/serverless
# directory. If we ever move back to delivering serverless via a hosted .zip file,
# we can re-enable this.
set(DOWNLOAD_SERVERLESS_CONTENT OFF)
endif ()
if (APPLE)

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -33,33 +33,6 @@ var EventBridge;
// replace the TempEventBridge with the real one.
var tempEventBridge = EventBridge;
EventBridge = channel.objects.eventBridge;
EventBridge.audioOutputDeviceChanged.connect(function(deviceName) {
navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(function(mediaStream) {
navigator.mediaDevices.enumerateDevices().then(function(devices) {
devices.forEach(function(device) {
if (device.kind == "audiooutput") {
if (device.label == deviceName){
console.log("Changing HTML audio output to device " + device.label);
var deviceId = device.deviceId;
var videos = document.getElementsByTagName("video");
for (var i = 0; i < videos.length; i++){
videos[i].setSinkId(deviceId);
}
var audios = document.getElementsByTagName("audio");
for (var i = 0; i < audios.length; i++){
audios[i].setSinkId(deviceId);
}
}
}
});
}).catch(function(err) {
console.log("Error getting media devices"+ err.name + ": " + err.message);
});
}).catch(function(err) {
console.log("Error getting user media"+ err.name + ": " + err.message);
});
});
// To be able to update the state of the output device selection for every element added to the DOM
// we need to listen to events that might precede the addition of this elements.

View file

@ -30,7 +30,6 @@ Windows.Window {
signal selfDestruct();
property var flags: 0;
property var additionalFlags: 0;
property var overrideFlags: 0;
@ -158,8 +157,7 @@ Windows.Window {
if (Qt.platform.os !== "windows" && (root.additionalFlags & Desktop.ALWAYS_ON_TOP)) {
nativeWindowFlags |= Qt.WindowStaysOnTopHint;
}
root.flags = root.overrideFlags || nativeWindowFlags;
nativeWindow.flags = root.flags;
nativeWindow.flags = root.overrideFlags || nativeWindowFlags;
nativeWindow.x = interactiveWindowPosition.x;
nativeWindow.y = interactiveWindowPosition.y;
@ -317,7 +315,7 @@ Windows.Window {
// set invisible on close, to make it not re-appear unintended after switching PresentationMode
interactiveWindowVisible = false;
if ((root.flags & Desktop.CLOSE_BUTTON_HIDES) !== Desktop.CLOSE_BUTTON_HIDES) {
if ((root.additionalFlags & Desktop.CLOSE_BUTTON_HIDES) !== Desktop.CLOSE_BUTTON_HIDES) {
selfDestruct();
}
}

View file

@ -5,4 +5,6 @@ Text {
style: Text.Outline;
styleColor: "black";
font.pixelSize: 12;
font.bold: true;
font.family: "monospace";
}

View file

@ -22,6 +22,7 @@ FocusScope {
property alias editable: comboBox.editable
property alias comboBox: comboBox
readonly property alias currentText: comboBox.currentText;
property alias displayText: comboBox.displayText;
property alias currentIndex: comboBox.currentIndex;
property int currentHighLightedIndex: comboBox.currentIndex;

View file

@ -39,8 +39,8 @@ Rectangle {
}
Component.onCompleted: {
AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; });
AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; });
AudioScriptingInterface.noiseGateOpened.connect(function() { root.gated = false; });
AudioScriptingInterface.noiseGateClosed.connect(function() { root.gated = true; });
HMD.displayModeChanged.connect(function() {
muted = AudioScriptingInterface.muted;
pushToTalk = AudioScriptingInterface.pushToTalk;
@ -151,7 +151,7 @@ Rectangle {
readonly property string yellow: "#C0C000";
readonly property string fill: "#55000000";
readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF";
readonly property string icon: (muted || clipping) ? mutedColor : gated ? gatedColor : unmutedColor;
readonly property string icon: (muted || clipping) ? mutedColor : root.gated ? gatedColor : unmutedColor;
}
Item {
@ -169,7 +169,7 @@ Rectangle {
Image {
id: image;
source: (pushToTalk) ? pushToTalkIcon : muted ? mutedIcon :
clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon;
clipping ? clippingIcon : root.gated ? gatedIcon : unmutedIcon;
width: 29;
height: 32;
anchors {

View file

@ -0,0 +1,379 @@
//
// GraphicsSettings.qml
// qml\hifi\dialogs\graphics
//
// Created by Zach Fox on 2019-07-10
// Copyright 2019 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 Hifi 1.0 as Hifi
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.12
import stylesUit 1.0 as HifiStylesUit
import controlsUit 1.0 as HifiControlsUit
import "qrc:////qml//controls" as HifiControls
import PerformanceEnums 1.0
Item {
HifiStylesUit.HifiConstants { id: hifi; }
id: root;
anchors.fill: parent
ColumnLayout {
id: graphicsSettingsColumnLayout
anchors.left: parent.left
anchors.leftMargin: 26
anchors.right: parent.right
anchors.rightMargin: 26
anchors.top: parent.top
anchors.topMargin: HMD.active ? 80 : 0
spacing: 8
ColumnLayout {
Layout.preferredWidth: parent.width
Layout.topMargin: 18
spacing: 0
HifiStylesUit.RalewayRegular {
text: "GRAPHICS SETTINGS"
Layout.maximumWidth: parent.width
height: 30
size: 16
color: "#FFFFFF"
}
ColumnLayout {
Layout.topMargin: 10
Layout.preferredWidth: parent.width
spacing: 0
HifiControlsUit.RadioButton {
id: performanceLow
colorScheme: hifi.colorSchemes.dark
height: 18
fontSize: 16
leftPadding: 0
text: "Low"
checked: Performance.getPerformancePreset() === PerformanceEnums.LOW
onClicked: {
Performance.setPerformancePreset(PerformanceEnums.LOW);
root.refreshAllDropdowns();
}
}
HifiControlsUit.RadioButton {
id: performanceMedium
colorScheme: hifi.colorSchemes.dark
height: 18
fontSize: 16
leftPadding: 0
text: "Medium"
checked: Performance.getPerformancePreset() === PerformanceEnums.MID
onClicked: {
Performance.setPerformancePreset(PerformanceEnums.MID);
root.refreshAllDropdowns();
}
}
HifiControlsUit.RadioButton {
id: performanceHigh
colorScheme: hifi.colorSchemes.dark
height: 18
fontSize: 16
leftPadding: 0
text: "High"
checked: Performance.getPerformancePreset() === PerformanceEnums.HIGH
onClicked: {
Performance.setPerformancePreset(PerformanceEnums.HIGH);
root.refreshAllDropdowns();
}
}
HifiControlsUit.RadioButton {
id: performanceCustom
colorScheme: hifi.colorSchemes.dark
height: 18
fontSize: 16
leftPadding: 0
text: "Custom"
checked: Performance.getPerformancePreset() === PerformanceEnums.UNKNOWN
onClicked: {
Performance.setPerformancePreset(PerformanceEnums.UNKNOWN);
}
}
}
ColumnLayout {
Layout.topMargin: 10
Layout.preferredWidth: parent.width
spacing: 0
Item {
Layout.preferredWidth: parent.width
Layout.preferredHeight: 35
HifiStylesUit.RalewayRegular {
id: worldDetailHeader
text: "World Detail"
anchors.left: parent.left
anchors.top: parent.top
width: 130
height: parent.height
size: 16
color: "#FFFFFF"
}
ListModel {
id: worldDetailModel
ListElement {
text: "Low World Detail"
worldDetailQualityValue: 0.25
}
ListElement {
text: "Medium World Detail"
worldDetailQualityValue: 0.5
}
ListElement {
text: "Full World Detail"
worldDetailQualityValue: 0.75
}
}
HifiControlsUit.ComboBox {
id: worldDetailDropdown
enabled: performanceCustom.checked
anchors.left: worldDetailHeader.right
anchors.leftMargin: 20
anchors.top: parent.top
width: 280
height: parent.height
colorScheme: hifi.colorSchemes.dark
model: worldDetailModel
currentIndex: -1
function refreshWorldDetailDropdown() {
var currentWorldDetailQuality = LODManager.worldDetailQuality;
if (currentWorldDetailQuality <= 0.25) {
worldDetailDropdown.currentIndex = 0;
} else if (currentWorldDetailQuality <= 0.5) {
worldDetailDropdown.currentIndex = 1;
} else {
worldDetailDropdown.currentIndex = 2;
}
}
Component.onCompleted: {
worldDetailDropdown.refreshWorldDetailDropdown();
}
onCurrentIndexChanged: {
LODManager.worldDetailQuality = model.get(currentIndex).worldDetailQualityValue;
worldDetailDropdown.displayText = model.get(currentIndex).text;
}
}
}
Item {
Layout.preferredWidth: parent.width
Layout.preferredHeight: 35
Layout.topMargin: 20
HifiStylesUit.RalewayRegular {
id: renderingEffectsHeader
text: "Rendering Effects"
anchors.left: parent.left
anchors.top: parent.top
width: 130
height: parent.height
size: 16
color: "#FFFFFF"
}
ListModel {
id: renderingEffectsModel
ListElement {
text: "No Rendering Effects"
preferredRenderMethod: 1 // "FORWARD"
shadowsEnabled: false
}
ListElement {
text: "Local Lights, Fog, Bloom"
preferredRenderMethod: 0 // "DEFERRED"
shadowsEnabled: false
}
ListElement {
text: "Local Lights, Fog, Bloom, Shadows"
preferredRenderMethod: 0 // "DEFERRED"
shadowsEnabled: true
}
}
HifiControlsUit.ComboBox {
id: renderingEffectsDropdown
enabled: performanceCustom.checked
anchors.left: renderingEffectsHeader.right
anchors.leftMargin: 20
anchors.top: parent.top
width: 280
height: parent.height
colorScheme: hifi.colorSchemes.dark
model: renderingEffectsModel
currentIndex: -1
function refreshRenderingEffectsDropdownDisplay() {
if (Render.shadowsEnabled) {
renderingEffectsDropdown.currentIndex = 2;
} else if (Render.renderMethod === 0) {
renderingEffectsDropdown.currentIndex = 1;
} else {
renderingEffectsDropdown.currentIndex = 0;
}
}
Component.onCompleted: {
renderingEffectsDropdown.refreshRenderingEffectsDropdownDisplay();
}
onCurrentIndexChanged: {
var renderMethodToSet = 1;
if (model.get(currentIndex).preferredRenderMethod === 0 &&
PlatformInfo.isRenderMethodDeferredCapable()) {
renderMethodToSet = 0;
}
Render.renderMethod = renderMethodToSet;
Render.shadowsEnabled = model.get(currentIndex).shadowsEnabled;
renderingEffectsDropdown.displayText = model.get(currentIndex).text;
}
}
}
Item {
Layout.preferredWidth: parent.width
Layout.preferredHeight: 35
Layout.topMargin: 20
HifiStylesUit.RalewayRegular {
id: refreshRateHeader
text: "Refresh Rate"
anchors.left: parent.left
anchors.top: parent.top
width: 130
height: parent.height
size: 16
color: "#FFFFFF"
}
ListModel {
id: refreshRateModel
ListElement {
text: "Economical"
refreshRatePreset: 0 // RefreshRateProfile::ECO
}
ListElement {
text: "Interactive"
refreshRatePreset: 1 // RefreshRateProfile::INTERACTIVE
}
ListElement {
text: "Real-Time"
refreshRatePreset: 2 // RefreshRateProfile::REALTIME
}
}
HifiControlsUit.ComboBox {
id: refreshRateDropdown
enabled: performanceCustom.checked
anchors.left: refreshRateHeader.right
anchors.leftMargin: 20
anchors.top: parent.top
width: 280
height: parent.height
colorScheme: hifi.colorSchemes.dark
model: refreshRateModel
currentIndex: -1
function refreshRefreshRateDropdownDisplay() {
if (Performance.getRefreshRateProfile() === 0) {
refreshRateDropdown.currentIndex = 0;
} else if (Performance.getRefreshRateProfile() === 1) {
refreshRateDropdown.currentIndex = 1;
} else {
refreshRateDropdown.currentIndex = 2;
}
}
Component.onCompleted: {
refreshRateDropdown.refreshRefreshRateDropdownDisplay();
}
onCurrentIndexChanged: {
Performance.setRefreshRateProfile(model.get(currentIndex).refreshRatePreset);
refreshRateDropdown.displayText = model.get(currentIndex).text;
}
}
}
Item {
Layout.preferredWidth: parent.width
Layout.preferredHeight: 35
Layout.topMargin: 16
HifiStylesUit.RalewayRegular {
id: resolutionHeader
text: "Resolution Scale (" + Number.parseFloat(Render.viewportResolutionScale).toPrecision(3) + ")"
anchors.left: parent.left
anchors.top: parent.top
width: 130
height: parent.height
size: 16
color: "#FFFFFF"
}
HifiControlsUit.Slider {
id: resolutionScaleSlider
enabled: performanceCustom.checked
anchors.left: resolutionHeader.right
anchors.leftMargin: 57
anchors.top: parent.top
width: 150
height: parent.height
colorScheme: hifi.colorSchemes.dark
minimumValue: 0.25
maximumValue: 1.0
stepSize: 0.02
value: Render.viewportResolutionScale
live: true
function updateResolutionScale(sliderValue) {
if (Render.viewportResolutionScale !== sliderValue) {
Render.viewportResolutionScale = sliderValue;
}
}
onValueChanged: {
updateResolutionScale(value);
}
onPressedChanged: {
if (!pressed) {
updateResolutionScale(value);
}
}
}
}
}
}
}
function refreshAllDropdowns() {
worldDetailDropdown.refreshWorldDetailDropdown();
renderingEffectsDropdown.refreshRenderingEffectsDropdownDisplay();
refreshRateDropdown.refreshRefreshRateDropdownDisplay();
}
}

View file

@ -91,7 +91,7 @@ Item {
SimplifiedControls.TextField {
id: myDisplayNameText
text: MyAvatar.sessionDisplayName === "" ? MyAvatar.displayName : MyAvatar.sessionDisplayName
text: MyAvatar.displayName
maximumLength: 256
clip: true
selectByMouse: true

View file

@ -362,8 +362,8 @@ Rectangle {
id: displayModeImage
source: HMD.active ? "./images/desktopMode.svg" : "./images/vrMode.svg"
anchors.centerIn: parent
width: HMD.active ? 25 : 43
height: 22
width: HMD.active ? 25 : 26
height: HMD.active ? 22 : 14
visible: false
}

File diff suppressed because it is too large Load diff

View file

@ -257,6 +257,10 @@ extern "C" {
}
#endif
#ifdef Q_OS_MAC
#include "MacHelper.h"
#endif
#if defined(Q_OS_ANDROID)
#include <android/log.h>
#include "AndroidHelper.h"
@ -960,6 +964,9 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<KeyboardScriptingInterface>();
DependencyManager::set<GrabManager>();
DependencyManager::set<AvatarPackager>();
#ifdef Q_OS_MAC
DependencyManager::set<MacHelper>();
#endif
QString setBookmarkValue = getCmdOption(argc, constArgv, "--setBookmark");
if (!setBookmarkValue.isEmpty()) {
@ -2810,13 +2817,14 @@ void Application::cleanupBeforeQuit() {
DependencyManager::destroy<TabletScriptingInterface>();
DependencyManager::destroy<ToolbarScriptingInterface>();
DependencyManager::destroy<OffscreenUi>();
DependencyManager::destroy<OffscreenQmlSurfaceCache>();
_snapshotSoundInjector = nullptr;
// destroy Audio so it and its threads have a chance to go down safely
// this must happen after QML, as there are unexplained audio crashes originating in qtwebengine
AudioInjector::setLocalAudioInterface(nullptr);
DependencyManager::destroy<AudioClient>();
DependencyManager::destroy<AudioScriptingInterface>();
@ -2856,6 +2864,9 @@ Application::~Application() {
_gameWorkload.shutdown();
DependencyManager::destroy<Preferences>();
#ifdef Q_OS_MAC
DependencyManager::destroy<MacHelper>();
#endif
_entityClipboard->eraseAllOctreeElements();
_entityClipboard.reset();
@ -5976,6 +5987,7 @@ void Application::resetPhysicsReadyInformation() {
_gpuTextureMemSizeStabilityCount = 0;
_gpuTextureMemSizeAtLastCheck = 0;
_physicsEnabled = false;
_octreeProcessor.stopSafeLanding();
}
void Application::reloadResourceCaches() {
@ -6235,6 +6247,7 @@ void Application::tryToEnablePhysics() {
// We keep physics disabled until we've received a full scene and everything near the avatar in that
// scene is ready to compute its collision shape.
if (getMyAvatar()->isReadyForPhysics()) {
_octreeProcessor.resetSafeLanding();
_physicsEnabled = true;
setIsInterstitialMode(false);
getMyAvatar()->updateMotionBehaviorFromMenu();
@ -6758,6 +6771,12 @@ void Application::update(float deltaTime) {
if (!getActiveDisplayPlugin()->isActive()) {
getMain3DScene()->processTransactionQueue();
}
// decide if the sensorToWorldMatrix is changing in a way that warrents squeezing the edges of the view down
if (getActiveDisplayPlugin()->isHmd()) {
PerformanceTimer perfTimer("squeezeVision");
_visionSqueeze.updateVisionSqueeze(myAvatar->getSensorToWorldMatrix(), deltaTime);
}
}
void Application::updateRenderArgs(float deltaTime) {
@ -6964,13 +6983,16 @@ int Application::sendNackPackets() {
}
void Application::queryOctree(NodeType_t serverType, PacketType packetType) {
if (!_settingsLoaded) {
return; // bail early if settings are not loaded
}
const bool isModifiedQuery = !_physicsEnabled;
if (isModifiedQuery) {
if (!_octreeProcessor.safeLandingIsActive()) {
// don't send the octreeQuery until SafeLanding knows it has started
return;
}
// Create modified view that is a simple sphere.
bool interstitialModeEnabled = DependencyManager::get<NodeList>()->getDomainHandler().getInterstitialModeEnabled();
@ -7193,7 +7215,6 @@ void Application::clearDomainOctreeDetails(bool clearAll) {
void Application::domainURLChanged(QUrl domainURL) {
// disable physics until we have enough information about our new location to not cause craziness.
resetPhysicsReadyInformation();
setIsServerlessMode(domainURL.scheme() != URL_SCHEME_HIFI);
if (isServerlessMode()) {
loadServerlessDomain(domainURL);
@ -7203,7 +7224,6 @@ void Application::domainURLChanged(QUrl domainURL) {
void Application::goToErrorDomainURL(QUrl errorDomainURL) {
// disable physics until we have enough information about our new location to not cause craziness.
resetPhysicsReadyInformation();
setIsServerlessMode(errorDomainURL.scheme() != URL_SCHEME_HIFI);
if (isServerlessMode()) {
loadErrorDomain(errorDomainURL);
@ -7220,12 +7240,12 @@ void Application::resettingDomain() {
void Application::nodeAdded(SharedNodePointer node) {
if (node->getType() == NodeType::EntityServer) {
if (_failedToConnectToEntityServer && !_entityServerConnectionTimer.isActive()) {
_failedToConnectToEntityServer = false;
_octreeProcessor.stopSafeLanding();
_octreeProcessor.startSafeLanding();
_failedToConnectToEntityServer = false;
} else if (_entityServerConnectionTimer.isActive()) {
_entityServerConnectionTimer.stop();
}
_octreeProcessor.startSafeLanding();
_entityServerConnectionTimer.setInterval(ENTITY_SERVER_CONNECTION_TIMEOUT);
_entityServerConnectionTimer.start();
}
@ -7300,7 +7320,7 @@ void Application::nodeKilled(SharedNodePointer node) {
_octreeProcessor.nodeKilled(node);
_entityEditSender.nodeKilled(node);
if (node->getType() == NodeType::AudioMixer) {
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "audioMixerKilled");
} else if (node->getType() == NodeType::EntityServer) {

View file

@ -78,6 +78,7 @@
#include <ModelScriptingInterface.h>
#include "Sound.h"
#include "VisionSqueeze.h"
class GLCanvas;
class FaceTracker;
@ -364,6 +365,9 @@ public:
void forceLoginWithTokens(const QString& tokens);
void setConfigFileURL(const QString& fileUrl);
// used by preferences and HMDScriptingInterface...
VisionSqueeze& getVisionSqueeze() { return _visionSqueeze; }
signals:
void svoImportRequested(const QString& url);
@ -731,6 +735,7 @@ private:
bool _loginDialogPoppedUp{ false };
bool _desktopRootItemCreated{ false };
bool _developerMenuVisible{ false };
QString _previousAvatarSkeletonModel;
float _previousAvatarTargetScale;
@ -837,5 +842,7 @@ private:
bool _resumeAfterLoginDialogActionTaken_SafeToRun { false };
bool _startUpFinished { false };
bool _overrideEntry { false };
VisionSqueeze _visionSqueeze;
};
#endif // hifi_Application_h

View file

@ -179,6 +179,18 @@ void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) {
}
}
/**jsdoc
* Details of an avatar bookmark.
* @typedef {object} AvatarBookmarks.BookmarkData
* @property {number} version - The version of the bookmark data format.
* @property {string} avatarUrl - The URL of the avatar model.
* @property {number} avatarScale - The target scale of the avatar.
* @property {Array<Object<"properties",Entities.EntityProperties>>} [avatarEntites] - The avatar entities included with the
* bookmark.
* @property {MyAvatar.AttachmentData[]} [attachments] - The attachments included with the bookmark.
* <p class="important">Deprecated: Use avatar entities instead.
*/
void AvatarBookmarks::loadBookmark(const QString& bookmarkName) {
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "loadBookmark", Q_ARG(QString, bookmarkName));

View file

@ -16,7 +16,9 @@
#include "Bookmarks.h"
/**jsdoc
* This API helps manage adding and deleting avatar bookmarks.
* The <code>AvatarBookmarks</code> API provides facilities for working with avatar bookmarks ("favorites" in the Avatar app).
* An avatar bookmark associates a name with an avatar model, scale, and avatar entities (wearables).
*
* @namespace AvatarBookmarks
*
* @hifi-interface
@ -32,41 +34,100 @@ class AvatarBookmarks: public Bookmarks, public Dependency {
public:
AvatarBookmarks();
void setupMenus(Menu* menubar, MenuWrapper* menu) override {};
/**jsdoc
* Gets the details of an avatar bookmark.
* @function AvatarBookmarks.getBookmark
* @param {string} bookmarkName - The name of the avatar bookmark (case sensitive).
* @returns {AvatarBookmarks.BookmarkData|{}} The bookmark data if the bookmark exists, <code>{}</code> if it doesn't.
*/
Q_INVOKABLE QVariantMap getBookmark(const QString& bookmarkName);
public slots:
/**jsdoc
* Add the current Avatar to your avatar bookmarks.
* @function AvatarBookmarks.addBookMark
* Adds a new (or updates an existing) avatar bookmark with your current avatar model, scale, and avatar entities.
* @function AvatarBookmarks.addBookmark
* @param {string} bookmarkName - The name of the avatar bookmark (case sensitive).
* @example <caption>Add a new avatar bookmark and report the bookmark data.</caption>
* var bookmarkName = "New Bookmark";
* AvatarBookmarks.addBookmark(bookmarkName);
* var bookmarkData = AvatarBookmarks.getBookmark(bookmarkName);
* print("Bookmark data: " + JSON.stringify(bookmarkData));
*/
void addBookmark(const QString& bookmarkName);
/**jsdoc
* Updates an existing bookmark with your current avatar model, scale, and wearables. No action is taken if the bookmark
* doesn't exist.
* @function AvatarBookmarks.saveBookmark
* @param {string} bookmarkName - The name of the avatar bookmark (case sensitive).
*/
void saveBookmark(const QString& bookmarkName);
/**jsdoc
* Loads an avatar bookmark, setting your avatar model, scale, and avatar entities (or attachments if an old bookmark) to
* those in the bookmark.
* @function AvatarBookmarks.loadBookmark
* @param {string} bookmarkName - The name of the avatar bookmark to load (case sensitive).
*/
void loadBookmark(const QString& bookmarkName);
/**jsdoc
* Deletes an avatar bookmark.
* @function AvatarBookmarks.removeBookmark
* @param {string} bookmarkName - The name of the avatar bookmark to delete (case sensitive).
*/
void removeBookmark(const QString& bookmarkName);
/**jsdoc
* Updates the avatar entities and their properties. Current avatar entities not included in the list provided are deleted.
* @function AvatarBookmarks.updateAvatarEntities
* @param {MyAvatar.AvatarEntityData[]} avatarEntities - The avatar entity IDs and properties.
* @deprecated This function is deprecated and will be removed. Use the {@link MyAvatar} API instead.
*/
void updateAvatarEntities(const QVariantList& avatarEntities);
/**jsdoc
* Gets the details of all avatar bookmarks.
* @function AvatarBookmarks.getBookmarks
* @returns {Object<string,AvatarBookmarks.BookmarkData>} The current avatar bookmarks in an object where the keys are the
* bookmark names and the values are the bookmark details.
* @example <caption>List the names and URLs of all the avatar bookmarks.</caption>
* var bookmarks = AvatarBookmarks.getBookmarks();
* print("Avatar bookmarks:");
* for (var key in bookmarks) {
* print("- " + key + " " + bookmarks[key].avatarUrl);
* };
*/
QVariantMap getBookmarks() { return _bookmarks; }
signals:
/**jsdoc
* This function gets triggered after avatar loaded from bookmark
* Triggered when an avatar bookmark is loaded, setting your avatar model, scale, and avatar entities (or attachments if an
* old bookmark) to those in the bookmark.
* @function AvatarBookmarks.bookmarkLoaded
* @param {string} bookmarkName
* @param {string} bookmarkName - The name of the avatar bookmark loaded.
* @returns {Signal}
*/
void bookmarkLoaded(const QString& bookmarkName);
/**jsdoc
* This function gets triggered after avatar bookmark deleted
* Triggered when an avatar bookmark is deleted.
* @function AvatarBookmarks.bookmarkDeleted
* @param {string} bookmarkName
* @param {string} bookmarkName - The name of the avatar bookmark deleted.
* @returns {Signal}
* @example <caption>Report when a bookmark is deleted.</caption>
* AvatarBookmarks.bookmarkDeleted.connect(function (bookmarkName) {
* print("Bookmark deleted: " + bookmarkName);
* });
*/
void bookmarkDeleted(const QString& bookmarkName);
/**jsdoc
* This function gets triggered after avatar bookmark added
* Triggered when a new avatar bookmark is added or an existing avatar bookmark is updated, using
* {@link AvatarBookmarks.addBookmark|addBookmark}.
* @function AvatarBookmarks.bookmarkAdded
* @param {string} bookmarkName
* @param {string} bookmarkName - The name of the avatar bookmark added or updated.
* @returns {Signal}
*/
void bookmarkAdded(const QString& bookmarkName);
@ -77,6 +138,11 @@ protected:
QVariantMap getAvatarDataToBookmark();
protected slots:
/**jsdoc
* Performs no action.
* @function AvatarBookmarks.deleteBookmark
* @deprecated This function is deprecated and will be removed.
*/
void deleteBookmark() override;
private:

View file

@ -52,6 +52,7 @@ protected:
protected slots:
/**jsdoc
* Prompts the user to delete a bookmark. The user can select the bookmark to delete in the dialog that is opened.
* @function LocationBookmarks.deleteBookmark
*/
virtual void deleteBookmark();

View file

@ -17,6 +17,9 @@
#include "Bookmarks.h"
/**jsdoc
* The <code>LocationBookmarks</code> API provides facilities for working with location bookmarks. A location bookmark
* associates a name with a metaverse address.
*
* @namespace LocationBookmarks
*
* @hifi-client-entity
@ -35,28 +38,35 @@ public:
static const QString HOME_BOOKMARK;
/**jsdoc
* Gets the metaverse address associated with a bookmark.
* @function LocationBookmarks.getAddress
* @param {string} bookmarkName Name of the bookmark to get the address for.
* @returns {string} The url for the specified bookmark. If the bookmark does not exist, the empty string will be returned.
* @param {string} bookmarkName - Name of the bookmark to get the metaverse address for (case sensitive).
* @returns {string} The metaverse address for the bookmark. If the bookmark does not exist, <code>""</code> is returned.
* @example <caption>Report the "Home" bookmark's metaverse address.</caption>
* print("Home bookmark's address: " + LocationBookmarks.getAddress("Home"));
*/
Q_INVOKABLE QString getAddress(const QString& bookmarkName);
public slots:
/**jsdoc
* Prompts the user to bookmark their current location. The user can specify the name of the bookmark in the dialog that is
* opened.
* @function LocationBookmarks.addBookmark
*/
void addBookmark();
/**jsdoc
* Sets the metaverse address associated with the "Home" bookmark.
* @function LocationBookmarks.setHomeLocationToAddress
* @param {string} address
* @param {string} address - The metaverse address to set the "Home" bookmark to.
*/
void setHomeLocationToAddress(const QVariant& address);
/**jsdoc
* Gets the metaverse address associated with the "Home" bookmark.
* @function LocationBookmarks.getHomeLocationAddress
* @returns {string} The url for the home location bookmark
* @returns {string} The metaverse address for the "Home" bookmark.
*/
QString getHomeLocationAddress();

58
interface/src/MacHelper.cpp Executable file
View file

@ -0,0 +1,58 @@
//
// MacHelper.h
// interface/src
//
// Created by Howard Stearns
// Copyright 2019 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 "InterfaceLogging.h"
#include "MacHelper.h"
#include <NodeList.h>
#ifdef Q_OS_MAC
#include <IOKit/IOMessage.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
// The type definitions in these variables come from IOKit, which includes a definition of Duration that conflicts with ours.
// So... we include these definitions here rather than in the .h, as the .h is included in Application.cpp which
// uses Duration.
static io_connect_t root_port;
static IONotificationPortRef notifyPortRef;
static io_object_t notifierObject;
static void* refCon;
static void sleepHandler(void* refCon, io_service_t service, natural_t messageType, void* messageArgument) {
if (messageType == kIOMessageSystemHasPoweredOn) {
qCInfo(interfaceapp) << "Waking up from sleep or hybernation.";
QMetaObject::invokeMethod(DependencyManager::get<NodeList>().data(), "noteAwakening", Qt::QueuedConnection);
}
}
#endif
MacHelper::MacHelper() {
#ifdef Q_OS_MAC
root_port = IORegisterForSystemPower(refCon, &notifyPortRef, sleepHandler, &notifierObject);
if (root_port == 0) {
qCWarning(interfaceapp) << "IORegisterForSystemPower failed";
return;
}
CFRunLoopAddSource(CFRunLoopGetCurrent(),
IONotificationPortGetRunLoopSource(notifyPortRef),
kCFRunLoopCommonModes);
#endif
}
MacHelper::~MacHelper() {
#ifdef Q_OS_MAC
CFRunLoopRemoveSource(CFRunLoopGetCurrent(),
IONotificationPortGetRunLoopSource(notifyPortRef),
kCFRunLoopCommonModes);
IODeregisterForSystemPower(&notifierObject);
IOServiceClose(root_port);
IONotificationPortDestroy(notifyPortRef);
#endif
}

21
interface/src/MacHelper.h Executable file
View file

@ -0,0 +1,21 @@
//
// MacHelper.h
// interface/src
//
// Created by Howard Stearns
// Copyright 2019 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
//
#pragma once
#include "DependencyManager.h"
class MacHelper : public Dependency {
public:
MacHelper();
~MacHelper();
};

View file

@ -266,8 +266,13 @@ Menu::Menu() {
// Settings > Graphics...
action = addActionToQMenuAndActionHash(settingsMenu, "Graphics...");
connect(action, &QAction::triggered, [] {
qApp->showDialog(QString("hifi/dialogs/GraphicsPreferencesDialog.qml"),
QString("hifi/tablet/TabletGraphicsPreferences.qml"), "GraphicsPreferencesDialog");
auto tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system");
auto hmd = DependencyManager::get<HMDScriptingInterface>();
tablet->pushOntoStack("hifi/dialogs/graphics/GraphicsSettings.qml");
if (!hmd->getShouldShowTablet()) {
hmd->toggleShouldShowTablet();
}
});
// Settings > Security...

View file

@ -92,7 +92,7 @@ void PerformanceManager::applyPerformancePreset(PerformanceManager::PerformanceP
RenderScriptingInterface::getInstance()->setShadowsEnabled(true);
qApp->getRefreshRateManager().setRefreshRateProfile(RefreshRateManager::RefreshRateProfile::REALTIME);
DependencyManager::get<LODManager>()->setWorldDetailQuality(0.5f);
DependencyManager::get<LODManager>()->setWorldDetailQuality(0.75f);
break;
case PerformancePreset::MID:
@ -114,7 +114,7 @@ void PerformanceManager::applyPerformancePreset(PerformanceManager::PerformanceP
RenderScriptingInterface::getInstance()->setViewportResolutionScale(recommandedPpiScale);
DependencyManager::get<LODManager>()->setWorldDetailQuality(0.75f);
DependencyManager::get<LODManager>()->setWorldDetailQuality(0.25f);
break;
case PerformancePreset::UNKNOWN:

View file

@ -0,0 +1,213 @@
//
// VisionSqueeze.cpp
// interface/src
//
// Created by Seth Alves on 2019-3-13.
// Copyright 2019 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 "VisionSqueeze.h"
#include <glm/gtx/matrix_decompose.hpp>
#include <glm/gtc/epsilon.hpp>
#include <display-plugins/hmd/HmdDisplayPlugin.h>
#include "Application.h"
VisionSqueeze::VisionSqueeze() :
_visionSqueezeEnabled(_visionSqueezeEnabledSetting.get()),
_visionSqueezeRatioX(_visionSqueezeRatioXSetting.get()),
_visionSqueezeRatioY(_visionSqueezeRatioYSetting.get()),
_visionSqueezeUnSqueezeDelay(_visionSqueezeUnSqueezeDelaySetting.get()),
_visionSqueezeUnSqueezeSpeed(_visionSqueezeUnSqueezeSpeedSetting.get()),
_visionSqueezeTransition(_visionSqueezeTransitionSetting.get()),
_visionSqueezePerEye(_visionSqueezePerEyeSetting.get()),
_visionSqueezeGroundPlaneY(_visionSqueezeGroundPlaneYSetting.get()),
_visionSqueezeSpotlightSize(_visionSqueezeSpotlightSizeSetting.get()),
_visionSqueezeTurningXFactor(_visionSqueezeTurningXFactorSetting.get()),
_visionSqueezeTurningYFactor(_visionSqueezeTurningYFactorSetting.get()) {
}
void VisionSqueeze::setVisionSqueezeEnabled(bool value) {
if (value != _visionSqueezeEnabled) {
_visionSqueezeEnabled = value;
_visionSqueezeEnabledSetting.set(_visionSqueezeEnabled);
}
}
void VisionSqueeze::setVisionSqueezeRatioX(float value) {
if (value != _visionSqueezeRatioX) {
_visionSqueezeRatioX = value;
_visionSqueezeRatioXSetting.set(_visionSqueezeRatioX);
}
}
void VisionSqueeze::setVisionSqueezeRatioY(float value) {
if (value != _visionSqueezeRatioY) {
_visionSqueezeRatioY = value;
_visionSqueezeRatioYSetting.set(_visionSqueezeRatioY);
}
}
void VisionSqueeze::setVisionSqueezeUnSqueezeDelay(float value) {
if (value != _visionSqueezeUnSqueezeDelay) {
_visionSqueezeUnSqueezeDelay = value;
_visionSqueezeUnSqueezeDelaySetting.set(_visionSqueezeUnSqueezeDelay);
}
}
void VisionSqueeze::setVisionSqueezeUnSqueezeSpeed(float value) {
if (value != _visionSqueezeUnSqueezeSpeed) {
_visionSqueezeUnSqueezeSpeed = value;
_visionSqueezeUnSqueezeSpeedSetting.set(_visionSqueezeUnSqueezeSpeed);
}
}
void VisionSqueeze::setVisionSqueezeTransition(float value) {
if (value != _visionSqueezeTransition) {
_visionSqueezeTransition = value;
_visionSqueezeTransitionSetting.set(_visionSqueezeTransition);
}
}
void VisionSqueeze::setVisionSqueezePerEye(int value) {
if (value != _visionSqueezePerEye) {
_visionSqueezePerEye = value;
_visionSqueezePerEyeSetting.set(_visionSqueezePerEye);
}
}
void VisionSqueeze::setVisionSqueezeGroundPlaneY(float value) {
if (value != _visionSqueezeGroundPlaneY) {
_visionSqueezeGroundPlaneY = value;
_visionSqueezeGroundPlaneYSetting.set(_visionSqueezeGroundPlaneY);
}
}
void VisionSqueeze::setVisionSqueezeSpotlightSize(float value) {
if (value != _visionSqueezeSpotlightSize) {
_visionSqueezeSpotlightSize = value;
_visionSqueezeSpotlightSizeSetting.set(_visionSqueezeSpotlightSize);
}
}
void VisionSqueeze::setVisionSqueezeTurningXFactor(float value) {
if (value != _visionSqueezeTurningXFactor) {
_visionSqueezeTurningXFactor = value;
_visionSqueezeTurningXFactorSetting.set(_visionSqueezeTurningXFactor);
}
}
void VisionSqueeze::setVisionSqueezeTurningYFactor(float value) {
if (value != _visionSqueezeTurningYFactor) {
_visionSqueezeTurningYFactor = value;
_visionSqueezeTurningYFactorSetting.set(_visionSqueezeTurningYFactor);
}
}
void VisionSqueeze::updateVisionSqueeze(const glm::mat4& sensorToWorldMatrix, float deltaTime) {
const float SENSOR_TO_WORLD_TRANS_EPSILON = 0.0001f;
const float SENSOR_TO_WORLD_TRANS_Y_EPSILON = 0.01f;
const float SENSOR_TO_WORLD_TRANS_ITS_A_TELEPORT_SQUARED = 2.0f;
const float SENSOR_TO_WORLD_ROT_EPSILON = 0.000005f;
const float SENSOR_TO_WORLD_ROT_ITS_A_SNAP_TURN = 0.99f;
const float VISION_SQUEEZE_TP_LOCKOUT = 0.1f; // seconds
glm::vec3 scale;
glm::quat rotation;
glm::vec3 translation;
glm::vec3 skew;
glm::vec4 perspective;
glm::decompose(sensorToWorldMatrix, scale, rotation, translation, skew, perspective);
if (!_visionSqueezeEnabled) {
_squeezeVision = false;
_squeezeVisionTurning = false;
} else if (_visionSqueezeLockout > 0.0f) {
_visionSqueezeLockout -= deltaTime;
} else {
_squeezeVision = false;
_squeezeVisionTurning = false;
glm::vec3 absTransDelta = glm::abs(translation - _prevTranslation);
float rotDot = fabsf(glm::dot(rotation, _prevRotation));
// if the avatar has just teleported or snap-turned, briefly disable triggering of vision-squeeze
if (glm::length2(translation - _prevTranslation) > SENSOR_TO_WORLD_TRANS_ITS_A_TELEPORT_SQUARED ||
rotDot < SENSOR_TO_WORLD_ROT_ITS_A_SNAP_TURN) {
_visionSqueezeLockout = VISION_SQUEEZE_TP_LOCKOUT;
_squeezeVision = true;
_squeezeVisionTurning = true;
} else if (rotDot < 1.0f - SENSOR_TO_WORLD_ROT_EPSILON) {
_squeezeVision = true;
_squeezeVisionTurning = true;
} else if (absTransDelta.x > SENSOR_TO_WORLD_TRANS_EPSILON ||
absTransDelta.y > SENSOR_TO_WORLD_TRANS_Y_EPSILON ||
absTransDelta.z > SENSOR_TO_WORLD_TRANS_EPSILON) {
_squeezeVision = true;
_squeezeVisionTurning = false;
}
}
_prevTranslation = translation;
_prevRotation = rotation;
static quint64 lastSqueezeTime = 0;
quint64 now = usecTimestampNow();
static float visionSqueezeX = 0.0f; // 0.0 -- unobstructed, 1.0 -- fully blocked
static float visionSqueezeY = 0.0f; // 0.0 -- unobstructed, 1.0 -- fully blocked
if (_squeezeVision) {
float ratioX = getVisionSqueezeRatioX();
float ratioY = getVisionSqueezeRatioY();
if (ratioX >= 0.0f) {
if (_squeezeVisionTurning) {
ratioX += (1.0f - ratioX) * getVisionSqueezeTurningXFactor();
}
float newVisionSqueezeX = ratioX;
if (newVisionSqueezeX >= visionSqueezeX) {
lastSqueezeTime = now;
visionSqueezeX = newVisionSqueezeX;
}
} else {
visionSqueezeX = -1.0f;
}
if (ratioY >= 0.0f) {
float newVisionSqueezeY = ratioY;
if (newVisionSqueezeY >= visionSqueezeY) {
lastSqueezeTime = now;
visionSqueezeY = newVisionSqueezeY;
}
} else {
visionSqueezeY = -1.0f;
}
}
float unsqueezeAmount = deltaTime * getVisionSqueezeUnSqueezeSpeed();
if (now - lastSqueezeTime > getVisionSqueezeUnSqueezeDelay() * USECS_PER_SECOND) {
visionSqueezeX -= unsqueezeAmount;
if (visionSqueezeX < 0.0f) {
visionSqueezeX = -1.0f;
}
visionSqueezeY -= unsqueezeAmount;
if (visionSqueezeY < 0.0f) {
visionSqueezeY = -1.0f;
}
}
std::shared_ptr<HmdDisplayPlugin> hmdDisplayPlugin =
std::dynamic_pointer_cast<HmdDisplayPlugin>(qApp->getActiveDisplayPlugin());
if (hmdDisplayPlugin) {
hmdDisplayPlugin->updateVisionSqueezeParameters(visionSqueezeX, visionSqueezeY,
getVisionSqueezeTransition(),
getVisionSqueezePerEye(),
getVisionSqueezeGroundPlaneY(),
getVisionSqueezeSpotlightSize());
}
}

View file

@ -0,0 +1,108 @@
//
// VisionSqueeze.h
// interface/src
//
// Created by Seth Alves on 2019-3-13.
// Copyright 2019 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_VisionSqueeze_h
#define hifi_VisionSqueeze_h
#include <memory>
#include <glm/glm.hpp>
#include <SettingHandle.h>
static const float DEFAULT_VISION_SQUEEZE_TURNING_X_FACTOR = 0.51f;
static const float DEFAULT_VISION_SQUEEZE_TURNING_Y_FACTOR = 0.36f;
static const float DEFAULT_VISION_SQUEEZE_UNSQUEEZE_DELAY = 0.2f; // seconds
static const float DEFAULT_VISION_SQUEEZE_UNSQUEEZE_SPEED = 3.0f;
static const float DEFAULT_VISION_SQUEEZE_TRANSITION = 0.25f;
static const int DEFAULT_VISION_SQUEEZE_PER_EYE = 1;
static const float DEFAULT_VISION_SQUEEZE_GROUND_PLANE_Y = 0.0f;
static const float DEFAULT_VISION_SQUEEZE_SPOTLIGHT_SIZE = 6.0f;
class VisionSqueeze {
public:
VisionSqueeze();
bool getVisionSqueezeEnabled() const { return _visionSqueezeEnabled; }
void setVisionSqueezeEnabled(bool value);
float getVisionSqueezeRatioX() const { return _visionSqueezeRatioX; }
float getVisionSqueezeRatioY() const { return _visionSqueezeRatioY; }
void setVisionSqueezeRatioX(float value);
void setVisionSqueezeRatioY(float value);
float getVisionSqueezeUnSqueezeDelay() const { return _visionSqueezeUnSqueezeDelay; }
void setVisionSqueezeUnSqueezeDelay(float value);
float getVisionSqueezeUnSqueezeSpeed() const { return _visionSqueezeUnSqueezeSpeed; }
void setVisionSqueezeUnSqueezeSpeed(float value);
float getVisionSqueezeTransition() const { return _visionSqueezeTransition; }
void setVisionSqueezeTransition(float value);
int getVisionSqueezePerEye() const { return _visionSqueezePerEye; }
void setVisionSqueezePerEye(int value);
float getVisionSqueezeGroundPlaneY() const { return _visionSqueezeGroundPlaneY; }
void setVisionSqueezeGroundPlaneY(float value);
float getVisionSqueezeSpotlightSize() const { return _visionSqueezeSpotlightSize; }
void setVisionSqueezeSpotlightSize(float value);
float getVisionSqueezeTurningXFactor() const { return _visionSqueezeTurningXFactor; }
void setVisionSqueezeTurningXFactor(float value);
float getVisionSqueezeTurningYFactor() const { return _visionSqueezeTurningYFactor; }
void setVisionSqueezeTurningYFactor(float value);
void updateVisionSqueeze(const glm::mat4& sensorToWorldMatrix, float deltaTime);
// state variable accessors used by Application.cpp...
bool getSqueezeVision() const { return _squeezeVision; }
void setSqueezeVision(bool value) { _squeezeVision = value; }
bool getSqueezeVisionTurning() const { return _squeezeVisionTurning; }
void setSqueezeVisionTurning(bool value) { _squeezeVisionTurning = value; }
private:
Setting::Handle<bool> _visionSqueezeEnabledSetting {"visionSqueezeEnabled", false};
Setting::Handle<float> _visionSqueezeRatioXSetting {"visionSqueezeRatioX", 0.0f};
Setting::Handle<float> _visionSqueezeRatioYSetting {"visionSqueezeRatioY", 0.0f};
Setting::Handle<float> _visionSqueezeUnSqueezeDelaySetting {"visionSqueezeUnSqueezeDelay",
DEFAULT_VISION_SQUEEZE_UNSQUEEZE_DELAY};
Setting::Handle<float> _visionSqueezeUnSqueezeSpeedSetting {"visionSqueezeUnSqueezeSpeed",
DEFAULT_VISION_SQUEEZE_UNSQUEEZE_SPEED};
Setting::Handle<float> _visionSqueezeTransitionSetting {"visionSqueezeTransition", DEFAULT_VISION_SQUEEZE_TRANSITION};
Setting::Handle<float> _visionSqueezePerEyeSetting {"visionSqueezePerEye", DEFAULT_VISION_SQUEEZE_PER_EYE};
Setting::Handle<float> _visionSqueezeGroundPlaneYSetting {"visionSqueezeGroundPlaneY",
DEFAULT_VISION_SQUEEZE_GROUND_PLANE_Y};
Setting::Handle<float> _visionSqueezeSpotlightSizeSetting {"visionSqueezeSpotlightSize",
DEFAULT_VISION_SQUEEZE_SPOTLIGHT_SIZE};
Setting::Handle<float> _visionSqueezeTurningXFactorSetting {"visionSqueezeTurningXFactor",
DEFAULT_VISION_SQUEEZE_TURNING_X_FACTOR};
Setting::Handle<float> _visionSqueezeTurningYFactorSetting {"visionSqueezeTurningYFactor",
DEFAULT_VISION_SQUEEZE_TURNING_Y_FACTOR};
// these are readable and writable from the scripting interface (on a different thread), so make them atomic
std::atomic<bool> _visionSqueezeEnabled { false };
std::atomic<float> _visionSqueezeRatioX { 0.0f };
std::atomic<float> _visionSqueezeRatioY { 0.0f };
std::atomic<float> _visionSqueezeUnSqueezeDelay { DEFAULT_VISION_SQUEEZE_UNSQUEEZE_DELAY }; // seconds
std::atomic<float> _visionSqueezeUnSqueezeSpeed { DEFAULT_VISION_SQUEEZE_UNSQUEEZE_SPEED };
std::atomic<float> _visionSqueezeTransition { DEFAULT_VISION_SQUEEZE_TRANSITION };
std::atomic<int> _visionSqueezePerEye { DEFAULT_VISION_SQUEEZE_PER_EYE };
std::atomic<float> _visionSqueezeGroundPlaneY { DEFAULT_VISION_SQUEEZE_GROUND_PLANE_Y };
std::atomic<float> _visionSqueezeSpotlightSize { DEFAULT_VISION_SQUEEZE_SPOTLIGHT_SIZE };
std::atomic<float> _visionSqueezeTurningXFactor { DEFAULT_VISION_SQUEEZE_TURNING_X_FACTOR };
std::atomic<float> _visionSqueezeTurningYFactor { DEFAULT_VISION_SQUEEZE_TURNING_Y_FACTOR };
bool _squeezeVision { false };
bool _squeezeVisionTurning { false };
float _visionSqueezeLockout { 0.0 };
glm::vec3 _prevTranslation;
glm::quat _prevRotation;
};
#endif // hifi_VisionSqueeze_h

View file

@ -574,6 +574,7 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
avatar->die();
queuePhysicsChange(avatar);
avatar->removeOrb();
// remove this avatar's entities from the tree now, if we wait (as we did previously) for this Avatar's destructor
// it might not fire until after we create a new instance for the same remote avatar, which creates a race

View file

@ -238,7 +238,7 @@ public:
* @function AvatarManager.getPalData
* @param {string[]} [avatarIDs=[]] - The IDs of the avatars to get the PAL data for. If empty, then PAL data is obtained
* for all avatars.
* @returns {object<"data", AvatarManager.PalData[]>} An array of objects, each object being the PAL data for an avatar.
* @returns {Object<"data", AvatarManager.PalData[]>} An array of objects, each object being the PAL data for an avatar.
* @example <caption>Report the PAL data for an avatar nearby.</caption>
* var palData = AvatarManager.getPalData();
* print("PAL data for one avatar: " + JSON.stringify(palData.data[0]));

View file

@ -125,6 +125,18 @@ QString userRecenterModelToString(MyAvatar::SitStandModelType model) {
}
}
static const QStringList REACTION_NAMES = {
QString("positive"),
QString("negative"),
QString("raiseHand"),
QString("applaud"),
QString("point")
};
static int reactionNameToIndex(const QString& reactionName) {
return REACTION_NAMES.indexOf(reactionName);
}
MyAvatar::MyAvatar(QThread* thread) :
Avatar(thread),
_yawSpeed(YAW_SPEED_DEFAULT),
@ -2751,19 +2763,23 @@ QString MyAvatar::getScriptedMotorMode() const {
}
void MyAvatar::setScriptedMotorVelocity(const glm::vec3& velocity) {
float MAX_SCRIPTED_MOTOR_SPEED = 500.0f;
_scriptedMotorVelocity = velocity;
float speed = glm::length(_scriptedMotorVelocity);
if (speed > MAX_SCRIPTED_MOTOR_SPEED) {
_scriptedMotorVelocity *= MAX_SCRIPTED_MOTOR_SPEED / speed;
float newSpeed = glm::length(velocity);
if (!glm::isnan(newSpeed)) {
_scriptedMotorVelocity = velocity;
constexpr float MAX_SCRIPTED_MOTOR_SPEED = 500.0f;
if (newSpeed > MAX_SCRIPTED_MOTOR_SPEED) {
_scriptedMotorVelocity *= MAX_SCRIPTED_MOTOR_SPEED / newSpeed;
}
}
}
void MyAvatar::setScriptedMotorTimescale(float timescale) {
// we clamp the timescale on the large side (instead of just the low side) to prevent
// obnoxiously large values from introducing NaN into avatar's velocity
_scriptedMotorTimescale = glm::clamp(timescale, MIN_SCRIPTED_MOTOR_TIMESCALE,
DEFAULT_SCRIPTED_MOTOR_TIMESCALE);
if (!glm::isnan(timescale)) {
// we clamp the timescale on the large side (instead of just the low side) to prevent
// obnoxiously large values from introducing NaN into avatar's velocity
_scriptedMotorTimescale = glm::clamp(timescale, MIN_SCRIPTED_MOTOR_TIMESCALE,
DEFAULT_SCRIPTED_MOTOR_TIMESCALE);
}
}
void MyAvatar::setScriptedMotorFrame(QString frame) {
@ -5808,6 +5824,53 @@ void MyAvatar::setModelScale(float scale) {
}
}
QStringList MyAvatar::getReactions() const {
return REACTION_NAMES;
}
bool MyAvatar::triggerReaction(QString reactionName) {
int reactionIndex = reactionNameToIndex(reactionName);
if (reactionIndex >= 0 && reactionIndex < (int)NUM_AVATAR_REACTIONS) {
std::lock_guard<std::mutex> guard(_reactionLock);
_reactionTriggers[reactionIndex] = true;
return true;
}
return false;
}
bool MyAvatar::beginReaction(QString reactionName) {
int reactionIndex = reactionNameToIndex(reactionName);
if (reactionIndex >= 0 && reactionIndex < (int)NUM_AVATAR_REACTIONS) {
std::lock_guard<std::mutex> guard(_reactionLock);
_reactionEnabledRefCounts[reactionIndex]++;
return true;
}
return false;
}
bool MyAvatar::endReaction(QString reactionName) {
int reactionIndex = reactionNameToIndex(reactionName);
if (reactionIndex >= 0 && reactionIndex < (int)NUM_AVATAR_REACTIONS) {
std::lock_guard<std::mutex> guard(_reactionLock);
_reactionEnabledRefCounts[reactionIndex]--;
return true;
}
return false;
}
void MyAvatar::updateRigControllerParameters(Rig::ControllerParameters& params) {
std::lock_guard<std::mutex> guard(_reactionLock);
for (int i = 0; i < NUM_AVATAR_REACTIONS; i++) {
// copy current state into params.
params.reactionEnabledFlags[i] = _reactionEnabledRefCounts[i] > 0;
params.reactionTriggers[i] = _reactionTriggers[i];
// clear reaction triggers here as well
_reactionTriggers[i] = false;
}
}
SpatialParentTree* MyAvatar::getParentTree() const {
auto entityTreeRenderer = qApp->getEntities();
EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr;
@ -6144,4 +6207,3 @@ void MyAvatar::sendPacket(const QUuid& entityID) const {
});
}
}

View file

@ -1369,9 +1369,9 @@ public:
bool hasDriveInput() const;
/**jsdoc
* Gets the list of avatar entities and their properties.
* Gets the current avatar entity IDs and their properties.
* @function MyAvatar.getAvatarEntitiesVariant
* @returns {MyAvatar.AvatarEntityData[]} The list of avatar entities and their properties.
* @returns {MyAvatar.AvatarEntityData[]} The current avatar entity IDs and their properties.
*/
Q_INVOKABLE QVariantList getAvatarEntitiesVariant();
@ -1839,6 +1839,10 @@ public:
bool getFlowActive() const;
bool getNetworkGraphActive() const;
// sets the reaction enabled and triggered parameters of the passed in params
// also clears internal reaction triggers
void updateRigControllerParameters(Rig::ControllerParameters& params);
public slots:
/**jsdoc
@ -2192,6 +2196,33 @@ public slots:
*/
virtual void setModelScale(float scale) override;
/**jsdoc
* MyAvatar.getReactions
* @returns {string[]} Array of reaction names.
*/
QStringList getReactions() const;
/**jsdoc
* MyAvatar.triggerReaction
* @param {string} reactionName - reaction name
* @returns {bool} false if the given reaction is not supported.
*/
bool triggerReaction(QString reactionName);
/**jsdoc
* MyAvatar.beginReaction
* @param {string} reactionName - reaction name
* @returns {bool} false if the given reaction is not supported.
*/
bool beginReaction(QString reactionName);
/**jsdoc
* MyAvatar.endReaction
* @param {string} reactionName - reaction name
* @returns {bool} false if the given reaction is not supported.
*/
bool endReaction(QString reactionName);
signals:
/**jsdoc
@ -2823,6 +2854,10 @@ private:
mutable std::mutex _scriptEngineLock;
QScriptEngine* _scriptEngine { nullptr };
bool _needToSaveAvatarEntitySettings { false };
int _reactionEnabledRefCounts[NUM_AVATAR_REACTIONS] { 0, 0, 0, 0, 0 };
bool _reactionTriggers[NUM_AVATAR_REACTIONS] { false, false, false, false, false };
mutable std::mutex _reactionLock;
};
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);

View file

@ -294,8 +294,6 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
_prevIsEstimatingHips = false;
}
params.isTalking = head->getTimeWithoutTalking() <= 1.5f;
// pass detailed torso k-dops to rig.
int hipsJoint = _rig.indexOfJoint("Hips");
if (hipsJoint >= 0) {
@ -314,6 +312,10 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
params.spine2ShapeInfo = hfmModel.joints[spine2Joint].shapeInfo;
}
params.isTalking = head->getTimeWithoutTalking() <= 1.5f;
myAvatar->updateRigControllerParameters(params);
_rig.updateFromControllerParameters(params, deltaTime);
Rig::CharacterControllerState ccState = convertCharacterControllerState(myAvatar->getCharacterController()->getState());

View file

@ -75,6 +75,7 @@ void OtherAvatar::createOrb() {
properties.setType(EntityTypes::Sphere);
properties.setAlpha(1.0f);
properties.setColor(getLoadingOrbColor(_loadingStatus));
properties.setName("Loading Avatar " + getID().toString());
properties.setPrimitiveMode(PrimitiveMode::LINES);
properties.getPulse().setMin(0.5f);
properties.getPulse().setMax(1.0f);
@ -201,6 +202,7 @@ void OtherAvatar::computeShapeLOD() {
break;
case workload::Region::UNKNOWN:
case workload::Region::INVALID:
case workload::Region::R4:
case workload::Region::R3:
default:
newLOD = BodyLOD::Sphere;

View file

@ -114,7 +114,11 @@ void OctreePacketProcessor::processPacket(QSharedPointer<ReceivedMessage> messag
if (renderer) {
renderer->processDatagram(*message, sendingNode);
if (_safeLanding && _safeLanding->isTracking()) {
_safeLanding->addToSequence(renderer->getLastOctreeMessageSequence());
OCTREE_PACKET_SEQUENCE thisSequence = renderer->getLastOctreeMessageSequence();
_safeLanding->addToSequence(thisSequence);
if (_safeLandingSequenceStart == SafeLanding::INVALID_SEQUENCE) {
_safeLandingSequenceStart = thisSequence;
}
}
}
}
@ -124,8 +128,8 @@ void OctreePacketProcessor::processPacket(QSharedPointer<ReceivedMessage> messag
// Read sequence #
OCTREE_PACKET_SEQUENCE completionNumber;
message->readPrimitive(&completionNumber);
if (_safeLanding) {
_safeLanding->finishSequence(0, completionNumber);
if (_safeLanding && _safeLanding->isTracking()) {
_safeLanding->finishSequence(_safeLandingSequenceStart, completionNumber);
}
} break;
@ -153,6 +157,13 @@ void OctreePacketProcessor::stopSafeLanding() {
}
}
void OctreePacketProcessor::resetSafeLanding() {
if (_safeLanding) {
_safeLanding->reset();
}
_safeLandingSequenceStart = SafeLanding::INVALID_SEQUENCE;
}
bool OctreePacketProcessor::safeLandingIsActive() const {
return _safeLanding && _safeLanding->isTracking();
}

View file

@ -28,6 +28,7 @@ public:
void startSafeLanding();
void updateSafeLanding();
void stopSafeLanding();
void resetSafeLanding();
bool safeLandingIsActive() const;
bool safeLandingIsComplete() const;
@ -43,6 +44,7 @@ private slots:
void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
private:
OCTREE_PACKET_SEQUENCE _safeLandingSequenceStart { SafeLanding::INVALID_SEQUENCE };
std::unique_ptr<SafeLanding> _safeLanding;
};
#endif // hifi_OctreePacketProcessor_h

View file

@ -17,7 +17,6 @@
#include "InterfaceLogging.h"
#include "Application.h"
const int SafeLanding::SEQUENCE_MODULO = std::numeric_limits<OCTREE_PACKET_SEQUENCE>::max() + 1;
CalculateEntityLoadingPriority SafeLanding::entityLoadingOperatorElevateCollidables = [](const EntityItem& entityItem) {
const int COLLIDABLE_ENTITY_PRIORITY = 10.0f;
@ -25,8 +24,8 @@ CalculateEntityLoadingPriority SafeLanding::entityLoadingOperatorElevateCollidab
};
namespace {
template<typename T> bool lessThanWraparound(int a, int b) {
constexpr int MAX_T_VALUE = std::numeric_limits<T>::max();
template<typename T> bool lessThanWraparound(int32_t a, int32_t b) {
constexpr int32_t MAX_T_VALUE = std::numeric_limits<T>::max();
if (b <= a) {
b += MAX_T_VALUE;
}
@ -34,7 +33,7 @@ namespace {
}
}
bool SafeLanding::SequenceLessThan::operator()(const int& a, const int& b) const {
bool SafeLanding::SequenceLessThan::operator()(const OCTREE_PACKET_SEQUENCE& a, const OCTREE_PACKET_SEQUENCE& b) const {
return lessThanWraparound<OCTREE_PACKET_SEQUENCE>(a, b);
}
@ -46,8 +45,8 @@ void SafeLanding::startTracking(QSharedPointer<EntityTreeRenderer> entityTreeRen
_entityTreeRenderer = entityTreeRenderer;
_trackedEntities.clear();
_maxTrackedEntityCount = 0;
_initialStart = INVALID_SEQUENCE;
_initialEnd = INVALID_SEQUENCE;
_sequenceStart = SafeLanding::INVALID_SEQUENCE;
_sequenceEnd = SafeLanding::INVALID_SEQUENCE;
_sequenceNumbers.clear();
_trackingEntities = true;
_startTime = usecTimestampNow();
@ -72,7 +71,7 @@ void SafeLanding::addTrackedEntity(const EntityItemID& entityID) {
if (entity && !entity->isLocalEntity() && entity->getCreated() < _startTime) {
_trackedEntities.emplace(entityID, entity);
int trackedEntityCount = (int)_trackedEntities.size();
int32_t trackedEntityCount = (int32_t)_trackedEntities.size();
if (trackedEntityCount > _maxTrackedEntityCount) {
_maxTrackedEntityCount = trackedEntityCount;
_trackedEntityStabilityCount = 0;
@ -87,15 +86,15 @@ void SafeLanding::deleteTrackedEntity(const EntityItemID& entityID) {
_trackedEntities.erase(entityID);
}
void SafeLanding::finishSequence(int first, int last) {
void SafeLanding::finishSequence(OCTREE_PACKET_SEQUENCE first, OCTREE_PACKET_SEQUENCE last) {
Locker lock(_lock);
if (_trackingEntities) {
_initialStart = first;
_initialEnd = last;
_sequenceStart = first;
_sequenceEnd = last;
}
}
void SafeLanding::addToSequence(int sequenceNumber) {
void SafeLanding::addToSequence(OCTREE_PACKET_SEQUENCE sequenceNumber) {
Locker lock(_lock);
_sequenceNumbers.insert(sequenceNumber);
}
@ -135,14 +134,13 @@ void SafeLanding::updateTracking() {
if (_trackedEntities.empty()) {
// no more tracked entities --> check sequenceNumbers
if (_initialStart != INVALID_SEQUENCE) {
if (_sequenceStart != SafeLanding::INVALID_SEQUENCE) {
bool shouldStop = false;
{
Locker lock(_lock);
int sequenceSize = _initialStart <= _initialEnd ? _initialEnd - _initialStart:
_initialEnd + SEQUENCE_MODULO - _initialStart;
auto startIter = _sequenceNumbers.find(_initialStart);
auto endIter = _sequenceNumbers.find(_initialEnd - 1);
auto sequenceSize = _sequenceEnd - _sequenceStart; // this works even in rollover case
auto startIter = _sequenceNumbers.find(_sequenceStart);
auto endIter = _sequenceNumbers.find(_sequenceEnd - 1);
bool missingSequenceNumbers = qApp->isMissingSequenceNumbers();
shouldStop = (sequenceSize == 0 ||
@ -159,31 +157,41 @@ void SafeLanding::updateTracking() {
void SafeLanding::stopTracking() {
Locker lock(_lock);
_trackingEntities = false;
if (_entityTreeRenderer) {
auto entityTree = _entityTreeRenderer->getTree();
disconnect(std::const_pointer_cast<EntityTree>(entityTree).get(),
&EntityTree::addingEntity, this, &SafeLanding::addTrackedEntity);
disconnect(std::const_pointer_cast<EntityTree>(entityTree).get(),
&EntityTree::deletingEntity, this, &SafeLanding::deleteTrackedEntity);
_entityTreeRenderer.reset();
if (_trackingEntities) {
_trackingEntities = false;
if (_entityTreeRenderer) {
auto entityTree = _entityTreeRenderer->getTree();
disconnect(std::const_pointer_cast<EntityTree>(entityTree).get(),
&EntityTree::addingEntity, this, &SafeLanding::addTrackedEntity);
disconnect(std::const_pointer_cast<EntityTree>(entityTree).get(),
&EntityTree::deletingEntity, this, &SafeLanding::deleteTrackedEntity);
_entityTreeRenderer.reset();
}
EntityTreeRenderer::setEntityLoadingPriorityFunction(_prevEntityLoadingPriorityOperator);
}
EntityTreeRenderer::setEntityLoadingPriorityFunction(_prevEntityLoadingPriorityOperator);
}
void SafeLanding::reset() {
_trackingEntities = false;
_trackedEntities.clear();
_maxTrackedEntityCount = 0;
_sequenceStart = SafeLanding::INVALID_SEQUENCE;
_sequenceEnd = SafeLanding::INVALID_SEQUENCE;
}
bool SafeLanding::trackingIsComplete() const {
return !_trackingEntities && (_initialStart != INVALID_SEQUENCE);
return !_trackingEntities && (_sequenceStart != SafeLanding::INVALID_SEQUENCE);
}
float SafeLanding::loadingProgressPercentage() {
Locker lock(_lock);
static const int MINIMUM_TRACKED_ENTITY_STABILITY_COUNT = 15;
float entityReadyPercentage = 0.0f;
if (_maxTrackedEntityCount > 0) {
entityReadyPercentage = ((_maxTrackedEntityCount - _trackedEntities.size()) / (float)_maxTrackedEntityCount);
}
constexpr int32_t MINIMUM_TRACKED_ENTITY_STABILITY_COUNT = 15;
if (_trackedEntityStabilityCount < MINIMUM_TRACKED_ENTITY_STABILITY_COUNT) {
entityReadyPercentage *= 0.20f;
}
@ -203,8 +211,22 @@ bool SafeLanding::isEntityPhysicsReady(const EntityItemPointer& entity) {
if (hasAABox && downloadedCollisionTypes.count(modelEntity->getShapeType()) != 0) {
auto space = _entityTreeRenderer->getWorkloadSpace();
uint8_t region = space ? space->getRegion(entity->getSpaceIndex()) : (uint8_t)workload::Region::INVALID;
bool shouldBePhysical = region < workload::Region::R3 && entity->shouldBePhysical();
return (!shouldBePhysical || entity->isInPhysicsSimulation() || modelEntity->computeShapeFailedToLoad());
// Note: the meanings of the workload regions are:
// R1 = in physics simulation and willing to own simulation
// R2 = in physics simulation but does NOT want to own simulation
// R3 = not in physics simulation but kinematically animated when velocities are non-zero
// R4 = sorted by workload and found to be outside R3
// UNKNOWN = known to workload but not yet sorted
// INVALID = not known to workload
// So any entity sorted into R3 or R4 is definitelyNotPhysical
bool definitelyNotPhysical = region == workload::Region::R3 ||
region == workload::Region::R4 ||
!entity->shouldBePhysical() ||
modelEntity->unableToLoadCollisionShape();
bool definitelyPhysical = entity->isInPhysicsSimulation();
return definitelyNotPhysical || definitelyPhysical;
}
}
}
@ -212,20 +234,24 @@ bool SafeLanding::isEntityPhysicsReady(const EntityItemPointer& entity) {
}
void SafeLanding::debugDumpSequenceIDs() const {
int p = -1;
qCDebug(interfaceapp) << "Sequence set size:" << _sequenceNumbers.size();
for (auto s: _sequenceNumbers) {
if (p == -1) {
p = s;
qCDebug(interfaceapp) << "First:" << s;
} else {
auto itr = _sequenceNumbers.begin();
OCTREE_PACKET_SEQUENCE p = SafeLanding::INVALID_SEQUENCE;
if (itr != _sequenceNumbers.end()) {
p = (*itr);
qCDebug(interfaceapp) << "First:" << (int32_t)p;
++itr;
while (itr != _sequenceNumbers.end()) {
OCTREE_PACKET_SEQUENCE s = *itr;
if (s != p + 1) {
qCDebug(interfaceapp) << "Gap from" << p << "to" << s << "(exclusive)";
qCDebug(interfaceapp) << "Gap from" << (int32_t)p << "to" << (int32_t)s << "(exclusive)";
p = s;
}
++itr;
}
if (p != SafeLanding::INVALID_SEQUENCE) {
qCDebug(interfaceapp) << "Last:" << p;
}
}
if (p != -1) {
qCDebug(interfaceapp) << "Last:" << p;
}
}

View file

@ -17,6 +17,8 @@
#include <QtCore/QObject>
#include <QtCore/QSharedPointer>
#include <set>
#include "EntityItem.h"
#include "EntityDynamicInterface.h"
@ -27,14 +29,18 @@ class EntityItemID;
class SafeLanding : public QObject {
public:
static constexpr OCTREE_PACKET_SEQUENCE MAX_SEQUENCE = std::numeric_limits<OCTREE_PACKET_SEQUENCE>::max();
static constexpr OCTREE_PACKET_SEQUENCE INVALID_SEQUENCE = MAX_SEQUENCE; // not technically invalid, but close enough
void startTracking(QSharedPointer<EntityTreeRenderer> entityTreeRenderer);
void updateTracking();
void stopTracking();
void reset();
bool isTracking() const { return _trackingEntities; }
bool trackingIsComplete() const;
void finishSequence(int first, int last); // 'last' exclusive.
void addToSequence(int sequenceNumber);
void finishSequence(OCTREE_PACKET_SEQUENCE first, OCTREE_PACKET_SEQUENCE last); // 'last' exclusive.
void addToSequence(OCTREE_PACKET_SEQUENCE sequenceNumber);
float loadingProgressPercentage();
private slots:
@ -52,24 +58,22 @@ private:
using EntityMap = std::map<EntityItemID, EntityItemPointer>;
EntityMap _trackedEntities;
static constexpr int INVALID_SEQUENCE = -1;
int _initialStart { INVALID_SEQUENCE };
int _initialEnd { INVALID_SEQUENCE };
int _maxTrackedEntityCount { 0 };
int _trackedEntityStabilityCount { 0 };
OCTREE_PACKET_SEQUENCE _sequenceStart { INVALID_SEQUENCE };
OCTREE_PACKET_SEQUENCE _sequenceEnd { INVALID_SEQUENCE };
int32_t _maxTrackedEntityCount { 0 };
int32_t _trackedEntityStabilityCount { 0 };
quint64 _startTime { 0 };
struct SequenceLessThan {
bool operator()(const int& a, const int& b) const;
bool operator()(const OCTREE_PACKET_SEQUENCE& a, const OCTREE_PACKET_SEQUENCE& b) const;
};
std::set<int, SequenceLessThan> _sequenceNumbers;
using SequenceSet = std::set<OCTREE_PACKET_SEQUENCE, SequenceLessThan>;
SequenceSet _sequenceNumbers;
static CalculateEntityLoadingPriority entityLoadingOperatorElevateCollidables;
CalculateEntityLoadingPriority _prevEntityLoadingPriorityOperator { nullptr };
static const int SEQUENCE_MODULO;
};
#endif // hifi_SafeLanding_h

View file

@ -29,7 +29,7 @@
* @property {FilterFlags} PICK_AVATAR_ENTITIES - Include avatar entities when intersecting. <em>Read-only.</em>
* @property {FilterFlags} PICK_LOCAL_ENTITIES - Include local entities when intersecting. <em>Read-only.</em>
* @property {FilterFlags} PICK_AVATARS - Include avatars when intersecting. <em>Read-only.</em>
* @property {FilterFlags} PICK_HUD - Include the HUD sphere when intersecting in HMD mode. <em>Read-only.</em>
* @property {FilterFlags} PICK_HUD - Include the HUD surface when intersecting in HMD mode. <em>Read-only.</em>
*
* @property {FilterFlags} PICK_ENTITIES - Include domain and avatar entities when intersecting. <em>Read-only.</em>
* <p class="important">Deprecated: This property is deprecated and will be removed. Use <code>PICK_DOMAIN_ENTITIES |
@ -61,7 +61,7 @@
* <p class="important">Deprecated: This property is deprecated and will be removed. Use
* <code>INTERSECTED_LOCAL_ENTITY</code> instead.</p>
* @property {IntersectionType} INTERSECTED_AVATAR - Intersected an avatar. <em>Read-only.</em>
* @property {IntersectionType} INTERSECTED_HUD - Intersected the HUD sphere. <em>Read-only.</em>
* @property {IntersectionType} INTERSECTED_HUD - Intersected the HUD surface. <em>Read-only.</em>
*
* @property {number} perFrameTimeBudget - The maximum time, in microseconds, to spend per frame updating pick results.
*/

View file

@ -33,7 +33,7 @@
* <em>Read-only.</em>
* @property {FilterFlags} PICK_OVERLAYS - Include local entities when intersecting. <em>Read-only.</em>
* @property {FilterFlags} PICK_AVATARS - Include avatars when intersecting. <em>Read-only.</em>
* @property {FilterFlags} PICK_HUD - Include the HUD sphere when intersecting in HMD mode. <em>Read-only.</em>
* @property {FilterFlags} PICK_HUD - Include the HUD surface when intersecting in HMD mode. <em>Read-only.</em>
* @property {FilterFlags} PICK_PRECISE - Pick against exact meshes. <em>Read-only.</em>
* @property {FilterFlags} PICK_INCLUDE_INVISIBLE - Include invisible objects when intersecting. <em>Read-only.</em>
* @property {FilterFlags} PICK_INCLUDE_NONCOLLIDABLE - Include non-collidable objects when intersecting. <em>Read-only.</em>
@ -43,7 +43,7 @@
* @property {IntersectionType} INTERSECTED_LOCAL_ENTITY - Intersected a local entity. <em>Read-only.</em>
* @property {IntersectionType} INTERSECTED_OVERLAY - Intersected an entity (3D Overlays no longer exist). <em>Read-only.</em>
* @property {IntersectionType} INTERSECTED_AVATAR - Intersected an avatar. <em>Read-only.</em>
* @property {IntersectionType} INTERSECTED_HUD - Intersected the HUD sphere. <em>Read-only.</em>
* @property {IntersectionType} INTERSECTED_HUD - Intersected the HUD surface. <em>Read-only.</em>
*/
class RayPickScriptingInterface : public QObject, public Dependency {
Q_OBJECT

View file

@ -459,7 +459,7 @@ signals:
/**jsdoc
* Triggered when the server injector gain changes.
* @function Audio.serverInjectorGainChanged
* @param {float} gain - The new server injector gain value.
* @param {number} gain - The new server injector gain value.
* @returns {Signal}
*/
void serverInjectorGainChanged(float gain);

View file

@ -111,10 +111,11 @@ void DesktopScriptingInterface::show(const QString& path, const QString& title)
InteractiveWindowPointer DesktopScriptingInterface::createWindow(const QString& sourceUrl, const QVariantMap& properties) {
if (QThread::currentThread() != thread()) {
InteractiveWindowPointer interactiveWindow = nullptr;
BLOCKING_INVOKE_METHOD(this, "createWindow",
BLOCKING_INVOKE_METHOD(this, "createWindowOnThread",
Q_RETURN_ARG(InteractiveWindowPointer, interactiveWindow),
Q_ARG(QString, sourceUrl),
Q_ARG(QVariantMap, properties));
Q_ARG(QVariantMap, properties),
Q_ARG(QThread*, QThread::currentThread()));
return interactiveWindow;
}
@ -129,3 +130,16 @@ InteractiveWindowPointer DesktopScriptingInterface::createWindow(const QString&
return new InteractiveWindow(sourceUrl, properties);
}
InteractiveWindowPointer DesktopScriptingInterface::createWindowOnThread(const QString& sourceUrl, const QVariantMap& properties, QThread* targetThread) {
// The offscreen surface already validates against non-local QML sources, but we also need to ensure that
// if we create top level QML, like dock widgets or other types of QQuickView containing desktop windows
// that the source URL is permitted
const auto& urlValidator = OffscreenQmlSurface::getUrlValidator();
if (!urlValidator(sourceUrl)) {
return nullptr;
}
InteractiveWindowPointer window = new InteractiveWindow(sourceUrl, properties);
window->moveToThread(targetThread);
return window;
}

View file

@ -101,6 +101,8 @@ private:
static int flagAlwaysOnTop() { return AlwaysOnTop; }
static int flagCloseButtonHides() { return CloseButtonHides; }
Q_INVOKABLE InteractiveWindowPointer createWindowOnThread(const QString& sourceUrl, const QVariantMap& properties, QThread* targetThread);
static QVariantMap getDockArea();
Q_INVOKABLE static QVariantMap getPresentationMode();

View file

@ -236,3 +236,83 @@ QVariant HMDScriptingInterface::getPlayAreaRect() {
QVector<glm::vec3> HMDScriptingInterface::getSensorPositions() {
return qApp->getActiveDisplayPlugin()->getSensorPositions();
}
float HMDScriptingInterface::getVisionSqueezeRatioX() const {
return qApp->getVisionSqueeze().getVisionSqueezeRatioX();
}
float HMDScriptingInterface::getVisionSqueezeRatioY() const {
return qApp->getVisionSqueeze().getVisionSqueezeRatioY();
}
void HMDScriptingInterface::setVisionSqueezeRatioX(float value) {
qApp->getVisionSqueeze().setVisionSqueezeRatioX(value);
}
void HMDScriptingInterface::setVisionSqueezeRatioY(float value) {
qApp->getVisionSqueeze().setVisionSqueezeRatioY(value);
}
float HMDScriptingInterface::getVisionSqueezeUnSqueezeDelay() const {
return qApp->getVisionSqueeze().getVisionSqueezeUnSqueezeDelay();
}
void HMDScriptingInterface::setVisionSqueezeUnSqueezeDelay(float value) {
qApp->getVisionSqueeze().setVisionSqueezeUnSqueezeDelay(value);
}
float HMDScriptingInterface::getVisionSqueezeUnSqueezeSpeed() const {
return qApp->getVisionSqueeze().getVisionSqueezeUnSqueezeSpeed();
}
void HMDScriptingInterface::setVisionSqueezeUnSqueezeSpeed(float value) {
qApp->getVisionSqueeze().setVisionSqueezeUnSqueezeSpeed(value);
}
float HMDScriptingInterface::getVisionSqueezeTransition() const {
return qApp->getVisionSqueeze().getVisionSqueezeTransition();
}
void HMDScriptingInterface::setVisionSqueezeTransition(float value) {
qApp->getVisionSqueeze().setVisionSqueezeTransition(value);
}
int HMDScriptingInterface::getVisionSqueezePerEye() const {
return qApp->getVisionSqueeze().getVisionSqueezePerEye();
}
void HMDScriptingInterface::setVisionSqueezePerEye(int value) {
qApp->getVisionSqueeze().setVisionSqueezePerEye(value);
}
float HMDScriptingInterface::getVisionSqueezeGroundPlaneY() const {
return qApp->getVisionSqueeze().getVisionSqueezeGroundPlaneY();
}
void HMDScriptingInterface::setVisionSqueezeGroundPlaneY(float value) {
qApp->getVisionSqueeze().setVisionSqueezeGroundPlaneY(value);
}
float HMDScriptingInterface::getVisionSqueezeSpotlightSize() const {
return qApp->getVisionSqueeze().getVisionSqueezeSpotlightSize();
}
void HMDScriptingInterface::setVisionSqueezeSpotlightSize(float value) {
qApp->getVisionSqueeze().setVisionSqueezeSpotlightSize(value);
}
float HMDScriptingInterface::getVisionSqueezeTurningXFactor() const {
return qApp->getVisionSqueeze().getVisionSqueezeTurningXFactor();
}
void HMDScriptingInterface::setVisionSqueezeTurningXFactor(float value) {
qApp->getVisionSqueeze().setVisionSqueezeTurningXFactor(value);
}
float HMDScriptingInterface::getVisionSqueezeTurningYFactor() const {
return qApp->getVisionSqueeze().getVisionSqueezeTurningYFactor();
}
void HMDScriptingInterface::setVisionSqueezeTurningYFactor(float value) {
qApp->getVisionSqueeze().setVisionSqueezeTurningYFactor(value);
}

View file

@ -62,10 +62,29 @@ class QScriptEngine;
* @property {Uuid} miniTabletScreenID - The UUID of the mini tablet's screen entity. <code>null</code> if not in HMD mode.
* @property {number} miniTabletHand - The hand that the mini tablet is displayed on: <code>0</code> for left hand,
* <code>1</code> for right hand, <code>-1</code> if not in HMD mode.
* @property {bool} miniTabletEnabled=true - <code>true</code> if the mini tablet is enabled to be displayed, otherwise
* @property {boolean} miniTabletEnabled=true - <code>true</code> if the mini tablet is enabled to be displayed, otherwise
* <code>false</code>.
* @property {Rect} playArea=0,0,0,0 - The size and position of the HMD play area in sensor coordinates. <em>Read-only.</em>
* @property {Vec3[]} sensorPositions=[]] - The positions of the VR system sensors in sensor coordinates. <em>Read-only.</em>
*
* @property {number} visionSqueezeRatioX=0.0 - The amount of vision squeeze for the x-axis when moving, range <code>0.0</code>
* &ndash; <code>1.0</code>.
* @property {number} visionSqueezeRatioY=0.0 - The amount of vision squeeze for the y-axis when moving, range <code>0.0</code>
* &ndash; <code>1.0</code>.
* @property {number} visionSqueezeTurningXFactor=0.51 - The additional amount of vision squeeze for the x-axis when turning,
* range <code>0.0</code> &ndash; <code>1.0</code>.
* @property {number} visionSqueezeTurningYFactor=0.36 - <em>Currently unused.</em>
* @property {number} visionSqueezeUnSqueezeDelay=0.2 - The delay in undoing the vision squeeze effect after motion stops, in
* seconds.
* @property {number} visionSqueezeUnSqueezeSpeed=3.0 - How quickly the vision squeeze effect fades, once
* <code>visionSqueezeUnSqueezeDelay</code> has passed.
* @property {number} visionSqueezeTransition=0.25 - How tightly vision is squeezed, range <code>0.01</code> &ndash;
* <code>0.7</code>.
* @property {number} visionSqueezePerEye=1 - <code>1</code> if each eye gets a tube to see through, <code>0</code> if the face
* gets a tube.
* @property {number} visionSqueezeGroundPlaneY=0.0 - Adjusts how far below the camera the vision squeeze grid is displayed at.
* @property {number} visionSqueezeSpotlightSize=6.0 - The diameter of the circle of vision squeeze grid that is illuminated
* around the camera.
*/
class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Dependency {
Q_OBJECT
@ -84,6 +103,17 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen
Q_PROPERTY(QVariant playArea READ getPlayAreaRect);
Q_PROPERTY(QVector<glm::vec3> sensorPositions READ getSensorPositions);
Q_PROPERTY(float visionSqueezeRatioX READ getVisionSqueezeRatioX WRITE setVisionSqueezeRatioX);
Q_PROPERTY(float visionSqueezeRatioY READ getVisionSqueezeRatioY WRITE setVisionSqueezeRatioY);
Q_PROPERTY(float visionSqueezeUnSqueezeDelay READ getVisionSqueezeUnSqueezeDelay WRITE setVisionSqueezeUnSqueezeDelay);
Q_PROPERTY(float visionSqueezeUnSqueezeSpeed READ getVisionSqueezeUnSqueezeSpeed WRITE setVisionSqueezeUnSqueezeSpeed);
Q_PROPERTY(float visionSqueezeTransition READ getVisionSqueezeTransition WRITE setVisionSqueezeTransition);
Q_PROPERTY(int visionSqueezePerEye READ getVisionSqueezePerEye WRITE setVisionSqueezePerEye);
Q_PROPERTY(float visionSqueezeGroundPlaneY READ getVisionSqueezeGroundPlaneY WRITE setVisionSqueezeGroundPlaneY);
Q_PROPERTY(float visionSqueezeSpotlightSize READ getVisionSqueezeSpotlightSize WRITE setVisionSqueezeSpotlightSize);
Q_PROPERTY(float visionSqueezeTurningXFactor READ getVisionSqueezeTurningXFactor WRITE setVisionSqueezeTurningXFactor);
Q_PROPERTY(float visionSqueezeTurningYFactor READ getVisionSqueezeTurningYFactor WRITE setVisionSqueezeTurningYFactor);
public:
/**jsdoc
@ -339,6 +369,27 @@ public:
*/
Q_INVOKABLE void openTablet(bool contextualMode = false);
float getVisionSqueezeRatioX() const;
float getVisionSqueezeRatioY() const;
void setVisionSqueezeRatioX(float value);
void setVisionSqueezeRatioY(float value);
float getVisionSqueezeUnSqueezeDelay() const;
void setVisionSqueezeUnSqueezeDelay(float value);
float getVisionSqueezeUnSqueezeSpeed() const;
void setVisionSqueezeUnSqueezeSpeed(float value);
float getVisionSqueezeTransition() const;
void setVisionSqueezeTransition(float value);
int getVisionSqueezePerEye() const;
void setVisionSqueezePerEye(int value);
float getVisionSqueezeGroundPlaneY() const;
void setVisionSqueezeGroundPlaneY(float value);
float getVisionSqueezeSpotlightSize() const;
void setVisionSqueezeSpotlightSize(float value);
float getVisionSqueezeTurningXFactor() const;
void setVisionSqueezeTurningXFactor(float value);
float getVisionSqueezeTurningYFactor() const;
void setVisionSqueezeTurningYFactor(float value);
signals:
/**jsdoc
* Triggered when a request to show or hide models of the HMD hand controllers is made using

View file

@ -221,4 +221,6 @@ QStringList PlatformInfoScriptingInterface::getPlatformTierNames() {
return platformTierNames;
}
bool PlatformInfoScriptingInterface::isRenderMethodDeferredCapable() {
return platform::Profiler::isRenderMethodDeferredCapable();
}

View file

@ -245,7 +245,12 @@ public slots:
*/
QStringList getPlatformTierNames();
/**jsdoc
* Gets whether the current hardware can render using the Deferred method.
* @function PlatformInfo.isRenderMethodDeferredCapable
* @returns {bool} <code>true</code> if the current hardware can render using the Deferred method; <code>false</code> otherwise.
*/
bool isRenderMethodDeferredCapable();
};
#endif // hifi_PlatformInfoScriptingInterface_h

View file

@ -658,7 +658,8 @@ signals:
/**jsdoc
* Triggered when you try to visit a domain but are redirected into the error state.
* @function Window.redirectErrorStateChanged
* @param {boolean} isInErrorState - If <code>true</code>, the user has been redirected to the error URL.
* @param {boolean} isInErrorState - <code>true</code> if the user has been redirected to the error URL, <code>false</code>
* if they haven't.
* @returns {Signal}
*/
void redirectErrorStateChanged(bool isInErrorState);
@ -666,8 +667,8 @@ signals:
/**jsdoc
* Triggered when the interstitial mode changes.
* @function Window.interstitialModeChanged
* @param {bool} interstitialMode - The new interstitial mode value. If <code>true</code>, the interstitial graphics are
* displayed when the domain is loading.
* @param {boolean} interstitialMode - <code>true</code> if the interstitial graphics are displayed when the domain is
* loading, <code>false</code> if they are not.
* @returns {Signal}
*/
void interstitialModeChanged(bool interstitialMode);

View file

@ -94,7 +94,7 @@ void AvatarInputs::setShowBubbleTools(bool showBubbleTools) {
return;
_showBubbleTools = showBubbleTools;
showBubbleToolsSetting.set(_showAudioTools);
showBubbleToolsSetting.set(_showBubbleTools);
emit showBubbleToolsChanged(_showBubbleTools);
}

View file

@ -24,18 +24,31 @@ class AvatarInputs : public QObject {
HIFI_QML_DECL
/**jsdoc
* API to help manage your Avatar's input
* The <code>AvatarInputs</code> API provides facilities to manage user inputs.
*
* @namespace AvatarInputs
*
* @hifi-interface
* @hifi-client-entity
* @hifi-avatar
*
* @property {boolean} cameraEnabled <em>Read-only.</em>
* @property {boolean} cameraMuted <em>Read-only.</em>
* @property {boolean} isHMD <em>Read-only.</em>
* @property {boolean} showAudioTools
* @property {boolean} showBubbleTools
* @property {boolean} cameraEnabled - <code>true</code> if webcam face tracking is enabled, <code>false</code> if it is
* disabled.
* <em>Read-only.</em>
* <p class="important">Deprecated: This property is deprecated and will be removed.</p>
* @property {boolean} cameraMuted - <code>true</code> if webcam face tracking is muted (temporarily disabled),
* <code>false</code> it if isn't.
* <em>Read-only.</em>
* <p class="important">Deprecated: This property is deprecated and will be removed.</p>
* @property {boolean} ignoreRadiusEnabled - <code>true</code> if the privacy shield is enabled, <code>false</code> if it
* is disabled.
* <em>Read-only.</em>
* @property {boolean} isHMD - <code>true</code> if the display mode is HMD, <code>false</code> if it isn't.
* <em>Read-only.</em>
* @property {boolean} showAudioTools - <code>true</code> if the microphone mute button and audio level meter are shown,
* <code>false</code> if they are hidden.
* @property {boolean} showBubbleTools - <code>true</code> if the privacy shield UI button is shown, <code>false</code> if
* it is hidden.
*/
AI_PROPERTY(bool, cameraEnabled, false)
@ -51,9 +64,10 @@ public:
static AvatarInputs* getInstance();
/**jsdoc
* Converts non-linear audio loudness to a linear audio level.
* @function AvatarInputs.loudnessToAudioLevel
* @param {number} loudness
* @returns {number}
* @param {number} loudness - The non-linear audio loudness.
* @returns {number} The linear audio level.
*/
Q_INVOKABLE float loudnessToAudioLevel(float loudness);
@ -67,76 +81,96 @@ public:
public slots:
/**jsdoc
* Sets whether or not the microphone mute button and audio level meter is shown.
* @function AvatarInputs.setShowAudioTools
* @param {boolean} showAudioTools
* @param {boolean} showAudioTools - <code>true</code> to show the microphone mute button and audio level meter,
* <code>false</code> to hide it.
*/
void setShowAudioTools(bool showAudioTools);
/**jsdoc
* Sets whether or not the privacy shield button is shown.
* @function AvatarInputs.setShowBubbleTools
* @param {boolean} showBubbleTools
* @param {boolean} showBubbleTools - <code>true</code> to show the privacy shield button, <code>false</code> to hide it.
*/
void setShowBubbleTools(bool showBubbleTools);
signals:
/**jsdoc
* Triggered when webcam face tracking is enabled or disabled.
* @deprecated This signal is deprecated and will be removed.
* @function AvatarInputs.cameraEnabledChanged
* @returns {Signal}
*/
void cameraEnabledChanged();
/**jsdoc
* Triggered when webcam face tracking is muted (temporarily disabled) or unmuted.
* @deprecated This signal is deprecated and will be removed.
* @function AvatarInputs.cameraMutedChanged
* @returns {Signal}
*/
void cameraMutedChanged();
/**jsdoc
* Triggered when the display mode changes between desktop and HMD.
* @function AvatarInputs.isHMDChanged
* @returns {Signal}
*/
void isHMDChanged();
/**jsdoc
* Triggered when the visibility of the microphone mute button and audio level meter changes.
* @function AvatarInputs.showAudioToolsChanged
* @param {boolean} show
* @param {boolean} show - <code>true</code> if the microphone mute button and audio level meter are shown,
* <code>false</code> if they are is hidden.
* @returns {Signal}
*/
void showAudioToolsChanged(bool show);
/**jsdoc
* Triggered when the visibility of the privacy shield button changes.
* @function AvatarInputs.showBubbleToolsChanged
* @param {boolean} show
* @param {boolean} show - <code>true</code> if the privacy shield UI button is shown, <code>false</code> if
* it is hidden.
* @returns {Signal}
*/
void showBubbleToolsChanged(bool show);
/**jsdoc
* Triggered when another user enters the privacy shield.
* @function AvatarInputs.avatarEnteredIgnoreRadius
* @param {QUuid} avatarID
* @param {QUuid} avatarID - The session ID of the user that entered the privacy shield.
* @returns {Signal}
*/
* @example <caption>Report when a user enters the privacy shield.</caption>
* AvatarInputs.avatarEnteredIgnoreRadius.connect(function(avatarID) {
* print("User entered the privacy shield: " + avatarID);
* };
*/
void avatarEnteredIgnoreRadius(QUuid avatarID);
/**jsdoc
* Triggered when another user leaves the privacy shield.
* <p><strong>Note:</strong> Currently doesn't work.</p>
* @function AvatarInputs.avatarLeftIgnoreRadius
* @param {QUuid} avatarID
* @param {QUuid} avatarID - The session ID of the user that exited the privacy shield.
* @returns {Signal}
*/
* @deprecated This signal is deprecated and will be removed.
*/
void avatarLeftIgnoreRadius(QUuid avatarID);
/**jsdoc
* Triggered when the privacy shield is enabled or disabled.
* @function AvatarInputs.ignoreRadiusEnabledChanged
* @param {boolean} enabled
* @param {boolean} enabled - <code>true</code> if the privacy shield is enabled, <code>false</code> if it is disabled.
* @returns {Signal}
*/
void ignoreRadiusEnabledChanged(bool enabled);
/**jsdoc
* Triggered when another user enters the privacy shield.
* @function AvatarInputs.enteredIgnoreRadiusChanged
* @param {boolean} enabled
* @returns {Signal}
*/
void enteredIgnoreRadiusChanged();
@ -144,11 +178,14 @@ signals:
protected:
/**jsdoc
* Resets sensors, audio, avatar animations, and the avatar rig.
* @function AvatarInputs.resetSensors
*/
Q_INVOKABLE void resetSensors();
/**jsdoc
* Toggles the muting (temporary disablement) of webcam face tracking on/off.
* <p class="important">Deprecated: This function is deprecated and will be removed.</p>
* @function AvatarInputs.toggleCameraMute
*/
Q_INVOKABLE void toggleCameraMute();

View file

@ -51,6 +51,36 @@ static const QStringList KNOWN_SCHEMES = QStringList() << "http" << "https" << "
static const int DEFAULT_HEIGHT = 60;
QmlWindowProxy::QmlWindowProxy(QObject* qmlObject, QObject* parent) : QmlWrapper(qmlObject, parent) {
_qmlWindow = qmlObject;
}
void QmlWindowProxy::parentNativeWindowToMainWindow() {
#ifdef Q_OS_WIN
if (!_qmlWindow) {
return;
}
const auto nativeWindowProperty = _qmlWindow->property("nativeWindow");
if (nativeWindowProperty.isNull() || !nativeWindowProperty.isValid()) {
return;
}
const auto nativeWindow = qvariant_cast<QQuickWindow*>(nativeWindowProperty);
SetWindowLongPtr((HWND)nativeWindow->winId(), GWLP_HWNDPARENT, (LONG)MainWindow::findMainWindow()->winId());
#endif
}
void InteractiveWindowProxy::emitScriptEvent(const QVariant& scriptMessage){
emit scriptEventReceived(scriptMessage);
}
void InteractiveWindowProxy::emitWebEvent(const QVariant& webMessage) {
emit webEventReceived(webMessage);
}
static void qmlWindowProxyDeleter(QmlWindowProxy* qmlWindowProxy) {
qmlWindowProxy->deleteLater();
}
static void dockWidgetDeleter(DockWidget* dockWidget) {
dockWidget->deleteLater();
}
@ -85,13 +115,13 @@ void InteractiveWindow::forwardKeyReleaseEvent(int key, int modifiers) {
* @property {string} [title="InteractiveWindow] - The title of the window.
* @property {Vec2} [position] - The initial position of the window, in pixels.
* @property {Vec2} [size] - The initial size of the window, in pixels
* @property {boolean} [visible=true] - <code>true</code> to make the window visible when created, <code>false</code> to make
* @property {boolean} [visible=true] - <code>true</code> to make the window visible when created, <code>false</code> to make
* it invisible.
* @property {InteractiveWindow.PresentationMode} [presentationMode=Desktop.PresentationMode.VIRTUAL] -
* <code>Desktop.PresentationMode.VIRTUAL</code> to display the window inside Interface, <code>.NATIVE</code> to display it
* @property {InteractiveWindow.PresentationMode} [presentationMode=Desktop.PresentationMode.VIRTUAL] -
* <code>Desktop.PresentationMode.VIRTUAL</code> to display the window inside Interface, <code>.NATIVE</code> to display it
* as its own separate window.
* @property {InteractiveWindow.PresentationWindowInfo} [presentationWindowInfo] - Controls how a <code>NATIVE</code> window is
* displayed. If used, the window is docked to the specified edge of the Interface window, otherwise the window is
* @property {InteractiveWindow.PresentationWindowInfo} [presentationWindowInfo] - Controls how a <code>NATIVE</code> window is
* displayed. If used, the window is docked to the specified edge of the Interface window, otherwise the window is
* displayed as its own separate window.
* @property {InteractiveWindow.AdditionalFlags} [additionalFlags=0] - Window behavior flags in addition to "native window flags" (minimize/maximize/close),
* set at window creation. Possible flag values are provided as {@link Desktop|Desktop.ALWAYS_ON_TOP} and {@link Desktop|Desktop.CLOSE_BUTTON_HIDES}.
@ -107,6 +137,12 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
presentationMode = (InteractiveWindowPresentationMode) properties[PRESENTATION_MODE_PROPERTY].toInt();
}
if (!_interactiveWindowProxy) {
_interactiveWindowProxy = new InteractiveWindowProxy();
QObject::connect(_interactiveWindowProxy, &InteractiveWindowProxy::webEventReceived, this, &InteractiveWindow::emitWebEvent, Qt::QueuedConnection);
QObject::connect(this, &InteractiveWindow::scriptEventReceived, _interactiveWindowProxy, &InteractiveWindowProxy::emitScriptEvent, Qt::QueuedConnection);
}
if (properties.contains(DOCKED_PROPERTY) && presentationMode == InteractiveWindowPresentationMode::Native) {
QVariantMap nativeWindowInfo = properties[DOCKED_PROPERTY].toMap();
Qt::DockWidgetArea dockArea = Qt::TopDockWidgetArea;
@ -124,7 +160,7 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
auto mainWindow = qApp->getWindow();
_dockWidget = std::shared_ptr<DockWidget>(new DockWidget(title, mainWindow), dockWidgetDeleter);
auto quickView = _dockWidget->getQuickView();
Application::setupQmlSurface(quickView->rootContext() , true);
//add any whitelisted callbacks
@ -160,13 +196,17 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
break;
}
}
QObject::connect(quickView.get(), &QQuickView::statusChanged, [&, this] (QQuickView::Status status) {
if (status == QQuickView::Ready) {
QQuickItem* rootItem = _dockWidget->getRootItem();
_dockWidget->getQuickView()->rootContext()->setContextProperty(EVENT_BRIDGE_PROPERTY, this);
_dockWidget->getQuickView()->rootContext()->setContextProperty(EVENT_BRIDGE_PROPERTY, _interactiveWindowProxy);
// The qmlToScript method handles the thread-safety of this call. Because the QVariant argument
// passed to the sendToScript signal may wrap an externally managed and thread-unsafe QJSValue,
// qmlToScript needs to be called directly, so the QJSValue can be immediately converted to a plain QVariant.
QObject::connect(rootItem, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)),
Qt::QueuedConnection);
Qt::DirectConnection);
QObject::connect(rootItem, SIGNAL(keyPressEvent(int, int)), this, SLOT(forwardKeyPressEvent(int, int)),
Qt::QueuedConnection);
QObject::connect(rootItem, SIGNAL(keyReleaseEvent(int, int)), this, SLOT(forwardKeyReleaseEvent(int, int)),
@ -176,14 +216,13 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
});
_dockWidget->setSource(QUrl(sourceUrl));
mainWindow->addDockWidget(dockArea, _dockWidget.get());
} else {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
// Build the event bridge and wrapper on the main thread
offscreenUi->loadInNewContext(CONTENT_WINDOW_QML, [&](QQmlContext* context, QObject* object) {
_qmlWindow = object;
context->setContextProperty(EVENT_BRIDGE_PROPERTY, this);
_qmlWindowProxy = std::shared_ptr<QmlWindowProxy>(new QmlWindowProxy(object, nullptr), qmlWindowProxyDeleter);
context->setContextProperty(EVENT_BRIDGE_PROPERTY, _interactiveWindowProxy);
if (properties.contains(ADDITIONAL_FLAGS_PROPERTY)) {
object->setProperty(ADDITIONAL_FLAGS_PROPERTY, properties[ADDITIONAL_FLAGS_PROPERTY].toUInt());
}
@ -208,7 +247,10 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
object->setProperty(VISIBLE_PROPERTY, properties[INTERACTIVE_WINDOW_VISIBLE_PROPERTY].toBool());
}
connect(object, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection);
// The qmlToScript method handles the thread-safety of this call. Because the QVariant argument
// passed to the sendToScript signal may wrap an externally managed and thread-unsafe QJSValue,
// qmlToScript needs to be called directly, so the QJSValue can be immediately converted to a plain QVariant.
connect(object, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::DirectConnection);
QObject::connect(object, SIGNAL(keyPressEvent(int, int)), this, SLOT(forwardKeyPressEvent(int, int)),
Qt::QueuedConnection);
QObject::connect(object, SIGNAL(keyReleaseEvent(int, int)), this, SLOT(forwardKeyReleaseEvent(int, int)),
@ -242,6 +284,7 @@ InteractiveWindow::~InteractiveWindow() {
}
void InteractiveWindow::sendToQml(const QVariant& message) {
// Forward messages received from the script on to QML
if (_dockWidget) {
QQuickItem* rootItem = _dockWidget->getRootItem();
@ -249,212 +292,153 @@ void InteractiveWindow::sendToQml(const QVariant& message) {
QMetaObject::invokeMethod(rootItem, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));
}
} else {
QMetaObject::invokeMethod(_qmlWindow, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));
QMetaObject::invokeMethod(_qmlWindowProxy->getQmlWindow(), "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message));
}
}
void InteractiveWindow::emitScriptEvent(const QVariant& scriptMessage) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, scriptMessage));
} else {
emit scriptEventReceived(scriptMessage);
}
emit scriptEventReceived(scriptMessage);
}
void InteractiveWindow::emitWebEvent(const QVariant& webMessage) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, webMessage));
} else {
emit webEventReceived(webMessage);
}
emit webEventReceived(webMessage);
}
void InteractiveWindow::close() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "close");
return;
if (_qmlWindowProxy) {
QObject* qmlWindow = _qmlWindowProxy->getQmlWindow();
if (qmlWindow) {
qmlWindow->deleteLater();
}
_qmlWindowProxy->deleteLater();
}
if (_qmlWindow) {
_qmlWindow->deleteLater();
if (_interactiveWindowProxy) {
_interactiveWindowProxy->deleteLater();
}
qApp->getWindow()->removeDockWidget(_dockWidget.get());
if (_dockWidget) {
auto window = qApp->getWindow();
if (QThread::currentThread() != window->thread()) {
BLOCKING_INVOKE_METHOD(window, "removeDockWidget", Q_ARG(QDockWidget*, _dockWidget.get()));
} else {
window->removeDockWidget(_dockWidget.get());
}
}
_dockWidget = nullptr;
_qmlWindow = nullptr;
_qmlWindowProxy = nullptr;
_interactiveWindowProxy = nullptr;
}
void InteractiveWindow::show() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "show");
return;
}
if (_qmlWindow) {
QMetaObject::invokeMethod(_qmlWindow, "show", Qt::DirectConnection);
if (_qmlWindowProxy) {
QMetaObject::invokeMethod(_qmlWindowProxy->getQmlWindow(), "show");
}
}
void InteractiveWindow::raise() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "raise");
return;
}
if (_qmlWindow) {
QMetaObject::invokeMethod(_qmlWindow, "raiseWindow", Qt::DirectConnection);
if (_qmlWindowProxy) {
QMetaObject::invokeMethod(_qmlWindowProxy->getQmlWindow(), "raiseWindow");
}
}
void InteractiveWindow::qmlToScript(const QVariant& message) {
void InteractiveWindow::qmlToScript(const QVariant& originalMessage) {
QVariant message = originalMessage;
if (message.canConvert<QJSValue>()) {
emit fromQml(qvariant_cast<QJSValue>(message).toVariant());
message = qvariant_cast<QJSValue>(message).toVariant();
} else if (message.canConvert<QString>()) {
emit fromQml(message.toString());
message = message.toString();
} else {
qWarning() << "Unsupported message type " << message;
return;
}
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this, "fromQml", Q_ARG(const QVariant&, message));
} else {
emit fromQml(message);
}
}
void InteractiveWindow::setVisible(bool visible) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setVisible", Q_ARG(bool, visible));
return;
}
if (!_qmlWindow.isNull()) {
_qmlWindow->setProperty(INTERACTIVE_WINDOW_VISIBLE_PROPERTY, visible);
if (_qmlWindowProxy) {
QMetaObject::invokeMethod(_qmlWindowProxy.get(), "writeProperty", Q_ARG(QString, INTERACTIVE_WINDOW_VISIBLE_PROPERTY),
Q_ARG(QVariant, visible));
}
}
bool InteractiveWindow::isVisible() const {
if (QThread::currentThread() != thread()) {
bool result = false;
BLOCKING_INVOKE_METHOD(const_cast<InteractiveWindow*>(this), "isVisible", Q_RETURN_ARG(bool, result));
return result;
}
if (_qmlWindow.isNull()) {
if (!_qmlWindowProxy) {
return false;
}
return _qmlWindow->property(INTERACTIVE_WINDOW_VISIBLE_PROPERTY).toBool();
return _qmlWindowProxy->readProperty(INTERACTIVE_WINDOW_VISIBLE_PROPERTY).toBool();
}
glm::vec2 InteractiveWindow::getPosition() const {
if (QThread::currentThread() != thread()) {
glm::vec2 result;
BLOCKING_INVOKE_METHOD(const_cast<InteractiveWindow*>(this), "getPosition", Q_RETURN_ARG(glm::vec2, result));
return result;
}
if (_qmlWindow.isNull()) {
if (!_qmlWindowProxy) {
return {};
}
return toGlm(_qmlWindow->property(INTERACTIVE_WINDOW_POSITION_PROPERTY).toPointF());
return toGlm(_qmlWindowProxy->readProperty(INTERACTIVE_WINDOW_POSITION_PROPERTY).toPointF());
}
void InteractiveWindow::setPosition(const glm::vec2& position) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setPosition", Q_ARG(const glm::vec2&, position));
return;
}
if (!_qmlWindow.isNull()) {
_qmlWindow->setProperty(INTERACTIVE_WINDOW_POSITION_PROPERTY, QPointF(position.x, position.y));
QMetaObject::invokeMethod(_qmlWindow, "updateInteractiveWindowPositionForMode", Qt::DirectConnection);
if (_qmlWindowProxy) {
QMetaObject::invokeMethod(_qmlWindowProxy.get(), "writeProperty", Q_ARG(QString, INTERACTIVE_WINDOW_POSITION_PROPERTY),
Q_ARG(QVariant, QPointF(position.x, position.y)));
QMetaObject::invokeMethod(_qmlWindowProxy->getQmlWindow(), "updateInteractiveWindowPositionForMode");
}
}
glm::vec2 InteractiveWindow::getSize() const {
if (QThread::currentThread() != thread()) {
glm::vec2 result;
BLOCKING_INVOKE_METHOD(const_cast<InteractiveWindow*>(this), "getSize", Q_RETURN_ARG(glm::vec2, result));
return result;
}
if (_qmlWindow.isNull()) {
if (!_qmlWindowProxy) {
return {};
}
return toGlm(_qmlWindow->property(INTERACTIVE_WINDOW_SIZE_PROPERTY).toSize());
return toGlm(_qmlWindowProxy->readProperty(INTERACTIVE_WINDOW_SIZE_PROPERTY).toSize());
}
void InteractiveWindow::setSize(const glm::vec2& size) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setSize", Q_ARG(const glm::vec2&, size));
return;
}
if (!_qmlWindow.isNull()) {
_qmlWindow->setProperty(INTERACTIVE_WINDOW_SIZE_PROPERTY, QSize(size.x, size.y));
QMetaObject::invokeMethod(_qmlWindow, "updateInteractiveWindowSizeForMode", Qt::DirectConnection);
if (_qmlWindowProxy) {
QMetaObject::invokeMethod(_qmlWindowProxy.get(), "writeProperty", Q_ARG(QString, INTERACTIVE_WINDOW_SIZE_PROPERTY),
Q_ARG(QVariant, QSize(size.x, size.y)));
QMetaObject::invokeMethod(_qmlWindowProxy->getQmlWindow(), "updateInteractiveWindowSizeForMode");
}
}
QString InteractiveWindow::getTitle() const {
if (QThread::currentThread() != thread()) {
QString result;
BLOCKING_INVOKE_METHOD(const_cast<InteractiveWindow*>(this), "getTitle", Q_RETURN_ARG(QString, result));
return result;
}
if (_qmlWindow.isNull()) {
if (!_qmlWindowProxy) {
return QString();
}
return _qmlWindow->property(TITLE_PROPERTY).toString();
return _qmlWindowProxy->readProperty(TITLE_PROPERTY).toString();
}
void InteractiveWindow::setTitle(const QString& title) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setTitle", Q_ARG(const QString&, title));
return;
}
if (!_qmlWindow.isNull()) {
_qmlWindow->setProperty(TITLE_PROPERTY, title);
if (_qmlWindowProxy) {
QMetaObject::invokeMethod(_qmlWindowProxy.get(), "writeProperty", Q_ARG(QString, TITLE_PROPERTY),
Q_ARG(QVariant, title));
}
}
int InteractiveWindow::getPresentationMode() const {
if (QThread::currentThread() != thread()) {
int result;
BLOCKING_INVOKE_METHOD(const_cast<InteractiveWindow*>(this), "getPresentationMode",
Q_RETURN_ARG(int, result));
return result;
}
if (_qmlWindow.isNull()) {
if (!_qmlWindowProxy) {
return Virtual;
}
return _qmlWindow->property(PRESENTATION_MODE_PROPERTY).toInt();
return _qmlWindowProxy->readProperty(PRESENTATION_MODE_PROPERTY).toInt();
}
void InteractiveWindow::parentNativeWindowToMainWindow() {
#ifdef Q_OS_WIN
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "parentNativeWindowToMainWindow");
return;
if (_qmlWindowProxy) {
QMetaObject::invokeMethod(_qmlWindowProxy.get(), "parentNativeWindowToMainWindow");
}
if (_qmlWindow.isNull()) {
return;
}
const auto nativeWindowProperty = _qmlWindow->property("nativeWindow");
if (nativeWindowProperty.isNull() || !nativeWindowProperty.isValid()) {
return;
}
const auto nativeWindow = qvariant_cast<QQuickWindow*>(nativeWindowProperty);
SetWindowLongPtr((HWND)nativeWindow->winId(), GWLP_HWNDPARENT, (LONG)MainWindow::findMainWindow()->winId());
#endif
}
void InteractiveWindow::setPresentationMode(int presentationMode) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setPresentationMode", Q_ARG(int, presentationMode));
return;
}
if (!_qmlWindow.isNull()) {
_qmlWindow->setProperty(PRESENTATION_MODE_PROPERTY, presentationMode);
if (_qmlWindowProxy) {
QMetaObject::invokeMethod(_qmlWindowProxy.get(), "writeProperty", Q_ARG(QString, PRESENTATION_MODE_PROPERTY),
Q_ARG(QVariant, presentationMode));
}
}

View file

@ -18,10 +18,42 @@
#include <QtCore/QPointer>
#include <QtScript/QScriptValue>
#include <QQmlEngine>
#include <ui/QmlWrapper.h>
#include <glm/glm.hpp>
#include <GLMHelpers.h>
class QmlWindowProxy : public QmlWrapper {
Q_OBJECT
public:
QmlWindowProxy(QObject* qmlObject, QObject* parent = nullptr);
Q_INVOKABLE void parentNativeWindowToMainWindow();
QObject* getQmlWindow() const { return _qmlWindow; }
private:
QObject* _qmlWindow;
};
class InteractiveWindowProxy : public QObject {
Q_OBJECT
public:
InteractiveWindowProxy(){}
public slots:
void emitScriptEvent(const QVariant& scriptMessage);
void emitWebEvent(const QVariant& webMessage);
signals:
void scriptEventReceived(const QVariant& message);
void webEventReceived(const QVariant& message);
};
namespace InteractiveWindowEnums {
Q_NAMESPACE
@ -291,8 +323,9 @@ protected slots:
void forwardKeyReleaseEvent(int key, int modifiers);
private:
QPointer<QObject> _qmlWindow;
std::shared_ptr<QmlWindowProxy> _qmlWindowProxy;
std::shared_ptr<DockWidget> _dockWidget { nullptr };
InteractiveWindowProxy *_interactiveWindowProxy{ nullptr };
};
typedef InteractiveWindow* InteractiveWindowPointer;

View file

@ -16,6 +16,7 @@
#include <Preferences.h>
#include <plugins/PluginUtils.h>
#include <display-plugins/CompositorHelper.h>
#include <display-plugins/hmd/HmdDisplayPlugin.h>
#include "scripting/RenderScriptingInterface.h"
#include "Application.h"
#include "DialogsManager.h"
@ -377,6 +378,33 @@ void setupPreferences() {
preference->setDecimals(2);
preferences->addPreference(preference);
}
{
auto getter = []()->bool {
return qApp->getVisionSqueeze().getVisionSqueezeEnabled();
};
auto setter = [](bool value) {
qApp->getVisionSqueeze().setVisionSqueezeEnabled(value);
};
auto preference = new CheckPreference(VR_MOVEMENT, "Enable HMD Comfort Mode", getter, setter);
preferences->addPreference(preference);
}
{
const float sliderPositions = 5.0f;
auto getter = [sliderPositions]()->float {
return roundf(sliderPositions * qApp->getVisionSqueeze().getVisionSqueezeRatioX());
};
auto setter = [sliderPositions](float value) {
float ratio = value / sliderPositions;
qApp->getVisionSqueeze().setVisionSqueezeRatioX(ratio);
qApp->getVisionSqueeze().setVisionSqueezeRatioY(ratio);
};
auto preference = new SpinnerSliderPreference(VR_MOVEMENT, "Comfort Mode", getter, setter);
preference->setMin(0.0f);
preference->setMax(sliderPositions);
preference->setStep(1.0f);
preference->setDecimals(0);
preferences->addPreference(preference);
}
{
auto getter = [myAvatar]()->bool { return myAvatar->getShowPlayArea(); };
auto setter = [myAvatar](bool value) { myAvatar->setShowPlayArea(value); };

View file

@ -1333,127 +1333,170 @@ QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
* <p>An overlay may be one of the following types:</p>
* <table>
* <thead>
* <tr><th>Value</th><th>2D/3D</th><th>Description</th></tr>
* <tr><th>Value</th><th>2D/3D</th><th>Description</th><th>Properties</th></tr>
* </thead>
* <tbody>
* <tr><td><code>image</code></td><td>2D</td><td>An image. Synonym: <code>billboard</code>.</td></tr>
* <tr><td><code>rectangle</code></td><td>2D</td><td>A rectangle.</td></tr>
* <tr><td><code>text</code></td><td>2D</td><td>Text.</td></tr>
* <tr><td><code>cube</code></td><td>3D</td><td>A cube. Can also use a <code>shape</code> overlay to create a cube.</td></tr>
* <tr><td><code>sphere</code></td><td>3D</td><td>A sphere. Can also use a <code>shape</code> overlay to create a sphere.</td></tr>
* <tr><td><code>rectangle3d</code></td><td>3D</td><td>A rectangle.</td></tr>
* <tr><td><code>shape</code></td><td>3D</td><td>A geometric shape, such as a cube, sphere, or cylinder.</td></tr>
* <tr><td><code>model</code></td><td>3D</td><td>A model.</td></tr>
* <tr><td><code>text3d</code></td><td>3D</td><td>Text.</td></tr>
* <tr><td><code>image3d</code></td><td>3D</td><td>An image.</td></tr>
* <tr><td><code>web3d</code></td><td>3D</td><td>Web content.</td></tr>
* <tr><td><code>line3d</code></td><td>3D</td><td>A line.</td></tr>
* <tr><td><code>grid</code></td><td>3D</td><td>A grid of lines in a plane.</td></tr>
* <tr><td><code>circle3d</code></td><td>3D</td><td>A circle.</td></tr>
* <tr><td><code>"rectangle"</code></td><td>2D</td>
* <td>A rectangle.</td>
* <td>{@link Overlays.OverlayProperties-Rectangle|OverlayProperties-Rectangle}</td></tr>
* <tr><td><code>"image"</code></td><td>2D</td>
* <td>An image.</td>
* <td>{@link Overlays.OverlayProperties-Image|OverlayProperties-Image}</td></tr>
* <tr><td><code>"text"</code></td><td>2D</td>
* <td>Some text.</td>
* <td>{@link Overlays.OverlayProperties-Text|OverlayProperties-Text}</td></tr>
* <tr><td><code>"cube"</code></td><td>3D</td>
* <td>A cube. A <code>"shape"</code> overlay can also be used to create a cube.<br/>
* <span class="important">Deprecated.</span>
* </td>
* <td>{@link Overlays.OverlayProperties-Cube|OverlayProperties-Cube}</td></tr>
* <tr><td><code>"sphere"</code></td><td>3D</td>
* <td>A sphere. A <code>"shape"</code> overlay can also be used to create a sphere.<br/>
* <span class="important">Deprecated.</span></td>
* <td>{@link Overlays.OverlayProperties-Sphere|OverlayProperties-Sphere}</td></tr>
* <tr><td><code>"shape"</code></td><td>3D</td>
* <td>A geometric shape, such as a cube, sphere, or cylinder.<br/>
* <span class="important">Deprecated.</span></td>
* <td>{@link Overlays.OverlayProperties-Shape|OverlayProperties-Shape}</td></tr>
* <tr><td><code>"model"</code></td><td>3D</td>
* <td>A model.<br/>
* <span class="important">Deprecated.</span></td>
* <td>{@link Overlays.OverlayProperties-Model|OverlayProperties-Model}</td></tr>
* <tr><td><code>"image3d"</code></td><td>3D</td>
* <td>An image. Synonym: <code>"billboard"</code>.<br/>
* <span class="important">Deprecated.</span></td>
* <td>{@link Overlays.OverlayProperties-Image3D|OverlayProperties-Image3D}</td></tr>
* <tr><td><code>"rectangle3d"</code></td><td>3D</td>
* <td>A rectangle.<br/>
* <span class="important">Deprecated.</span></td>
* <td>{@link Overlays.OverlayProperties-Rectangle3D|OverlayProperties-Rectangle3D}</td></tr>
* <tr><td><code>"text3d"</code></td><td>3D</td>
* <td>Some text.<br/>
* <span class="important">Deprecated.</span></td>
* <td>{@link Overlays.OverlayProperties-Text3D|OverlayProperties-Text3D}</td></tr>
* <tr><td><code>"web3d"</code></td><td>3D</td>
* <td>Web content.<br/>
* <span class="important">Deprecated.</span></td>
* <td>{@link Overlays.OverlayProperties-Web3D|OverlayProperties-Web3D}</td></tr>
* <tr><td><code>"line3d"</code></td><td>3D</td>
* <td>A line.<br/>
* <span class="important">Deprecated.</span></td>
* <td>{@link Overlays.OverlayProperties-Line3D|OverlayProperties-Line3D}</td></tr>
* <tr><td><code>"grid"</code></td><td>3D</td>
* <td>A grid of lines in a plane.<br/>
* <span class="important">Deprecated.</span></td>
* <td>{@link Overlays.OverlayProperties-Grid|OverlayProperties-Grid}</td></tr>
* <tr><td><code>"circle3d"</code></td><td>3D</td>
* <td>A circle.<br/>
* <span class="important">Deprecated.</span></td>
* <td>{@link Overlays.OverlayProperties-Circle3D|OverlayProperties-Circle3D}</td></tr>
* </tbody>
* </table>
* <p>2D overlays are rendered on the display surface in desktop mode and on the HUD surface in HMD mode. 3D overlays are
* rendered at a position and orientation in-world, but are deprecated (use local entities instead).<p>
* <p>Each overlay type has different {@link Overlays.OverlayProperties|OverlayProperties}.</p>
* rendered at a position and orientation in-world.</p>
* <p class="important">3D overlays are deprecated. Use local {@link Entities} instead.</p>
* @typedef {string} Overlays.OverlayType
*/
/**jsdoc
* Different overlay types have different properties: some common to all overlays (listed below) and some specific to each
* {@link Overlays.OverlayType|OverlayType} (linked to below). The properties are accessed as an object of property names and
* values. 3D Overlays are local entities, internally, so they also support the corresponding entity properties.
*
* Different overlay types have different properties: some common to all overlays (listed in the table) and some specific to
* each {@link Overlays.OverlayType|OverlayType} (linked to below).
* <p>3D overlays are local entities, internally, so they also support the relevant entity's properties.</p>
* @typedef {object} Overlays.OverlayProperties
* @property {Uuid} id - The ID of the overlay. <em>Read-only.</em>
* @property {Overlays.OverlayType} type - The overlay type. <em>Read-only.</em>
* @property {boolean} visible=true - If <code>true</code>, the overlay is rendered, otherwise it is not rendered.
* @property {Overlays.OverlayType} type - The overlay's type. <em>Read-only.</em>
* @property {boolean} visible=true - <code>true</code> if the overlay is rendered, <code>false</code> if it isn't.
*
* @see The different entity types have additional properties as follows:
* @see {@link Overlays.OverlayProperties-Rectangle|OverlayProperties-Rectangle}
* @see {@link Overlays.OverlayProperties-Image|OverlayProperties-Image}
* @see {@link Overlays.OverlayProperties-Text|OverlayProperties-Text}
* @see {@link Overlays.OverlayProperties-Rectangle|OverlayProperties-Rectangle}
* @see {@link Overlays.OverlayProperties-Cube|OverlayProperties-Cube}
* @see {@link Overlays.OverlayProperties-Sphere|OverlayProperties-Sphere}
* @see {@link Overlays.OverlayProperties-Rectangle3D|OverlayProperties-Rectangle3D}
* @see {@link Overlays.OverlayProperties-Shape|OverlayProperties-Shape}
* @see {@link Overlays.OverlayProperties-Model|OverlayProperties-Model}
* @see {@link Overlays.OverlayProperties-Text3D|OverlayProperties-Text3D}
* @see {@link Overlays.OverlayProperties-Image3D|OverlayProperties-Image3D}
* @see {@link Overlays.OverlayProperties-Web|OverlayProperties-Web}
* @see {@link Overlays.OverlayProperties-Line|OverlayProperties-Line}
* @see {@link Overlays.OverlayProperties-Grid|OverlayProperties-Grid}
* @see {@link Overlays.OverlayProperties-Circle|OverlayProperties-Circle}
* @see {@link Overlays.OverlayProperties-Cube|OverlayProperties-Cube} <span class="important">Deprecated.</span>
* @see {@link Overlays.OverlayProperties-Sphere|OverlayProperties-Sphere} <span class="important">Deprecated.</span>
* @see {@link Overlays.OverlayProperties-Shape|OverlayProperties-Shape} <span class="important">Deprecated.</span>
* @see {@link Overlays.OverlayProperties-Model|OverlayProperties-Model} <span class="important">Deprecated.</span>
* @see {@link Overlays.OverlayProperties-Rectangle3D|OverlayProperties-Rectangle3D} <span class="important">Deprecated.</span>
* @see {@link Overlays.OverlayProperties-Image3D|OverlayProperties-Image3D} <span class="important">Deprecated.</span>
* @see {@link Overlays.OverlayProperties-Text3D|OverlayProperties-Text3D} <span class="important">Deprecated.</span>
* @see {@link Overlays.OverlayProperties-Web3D|OverlayProperties-Web3D} <span class="important">Deprecated.</span>
* @see {@link Overlays.OverlayProperties-Line3D|OverlayProperties-Line3D} <span class="important">Deprecated.</span>
* @see {@link Overlays.OverlayProperties-Grid|OverlayProperties-Grid} <span class="important">Deprecated.</span>
* @see {@link Overlays.OverlayProperties-Circle3D|OverlayProperties-Circle3D} <span class="important">Deprecated.</span>
*/
/**jsdoc
* The <code>"Image"</code> {@link Overlays.OverlayType|OverlayType} is a 2D image.
* The <code>"image"</code> {@link Overlays.OverlayType|OverlayType} is for 2D images.
* It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}.
* @typedef {object} Overlays.OverlayProperties-Image
* @property {Rect} bounds - The position and size of the image display area, in pixels. <em>Write-only.</em>
* @property {number} x - Integer left, x-coordinate value of the image display area = <code>bounds.x</code>.
* <em>Write-only.</em>
* @property {number} y - Integer top, y-coordinate value of the image display area = <code>bounds.y</code>.
* @property {number} y - Integer top, y-coordinate value of the image display area = <code>bounds.y</code>.
* <em>Write-only.</em>
* @property {number} width - Integer width of the image display area = <code>bounds.width</code>. <em>Write-only.</em>
* @property {number} height - Integer height of the image display area = <code>bounds.height</code>. <em>Write-only.</em>
* @property {number} height - Integer height of the image display area = <code>bounds.height</code>. <em>Write-only.</em>
* @property {string} imageURL - The URL of the image file to display. The image is scaled to fit to the <code>bounds</code>.
* <em>Write-only.</em>
* @property {Vec2} subImage=0,0 - Integer coordinates of the top left pixel to start using image content from.
* @property {Rect} subImage - The portion of the image to use. If not specified, the whole image is used.
* <em>Write-only.</em>
* @property {Color} color=0,0,0 - The color to apply over the top of the image to colorize it. <em>Write-only.</em>
* @property {number} alpha=0.0 - The opacity of the color applied over the top of the image, <code>0.0</code> -
* @property {number} alpha=0.0 - The opacity of the color applied over the top of the image, <code>0.0</code> &ndash;
* <code>1.0</code>. <em>Write-only.</em>
*/
/**jsdoc
* The <code>"Text"</code> {@link Overlays.OverlayType|OverlayType} is for 2D text.
* The <code>"text"</code> {@link Overlays.OverlayType|OverlayType} is for 2D text.
* It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}.
* @typedef {object} Overlays.OverlayProperties-Text
* @property {Rect} bounds - The position and size of the rectangle, in pixels. <em>Write-only.</em>
* @property {number} x - Integer left, x-coordinate value = <code>bounds.x</code>. <em>Write-only.</em>
* @property {number} y - Integer top, y-coordinate value = <code>bounds.y</code>. <em>Write-only.</em>
* @property {number} width - Integer width of the rectangle = <code>bounds.width</code>. <em>Write-only.</em>
* @property {number} height - Integer height of the rectangle = <code>bounds.height</code>. <em>Write-only.</em>
*
* @property {number} margin=0 - Sets the <code>leftMargin</code> and <code>topMargin</code> values, in pixels.
* @property {number} margin=0 - The <code>leftMargin</code> and <code>topMargin</code> values, in pixels.
* <em>Write-only.</em>
* @property {number} leftMargin=0 - The left margin's size, in pixels. This value is also used for the right margin.
* <em>Write-only.</em>
* @property {number} topMargin=0 - The top margin's size, in pixels. This value is also used for the bottom margin.
* <em>Write-only.</em>
* @property {string} text="" - The text to display. Text does not automatically wrap; use <code>\n</code> for a line break. Text
* is clipped to the <code>bounds</code>. <em>Write-only.</em>
* @property {string} text="" - The text to display. Text does not automatically wrap; use <code>"\n"</code> for a line break.
* Text is clipped to the <code>bounds</code>. <em>Write-only.</em>
* @property {number} font.size=18 - The size of the text, in pixels. <em>Write-only.</em>
* @property {number} lineHeight=18 - The height of a line of text, in pixels. <em>Write-only.</em>
* @property {Color} color=255,255,255 - The color of the text. Synonym: <code>textColor</code>. <em>Write-only.</em>
* @property {number} alpha=1.0 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>. <em>Write-only.</em>
* @property {number} alpha=1.0 - The opacity of the overlay, <code>0.0</code> &ndash; <code>1.0</code>. <em>Write-only.</em>
* @property {Color} backgroundColor=0,0,0 - The color of the background rectangle. <em>Write-only.</em>
* @property {number} backgroundAlpha=0.7 - The opacity of the background rectangle. <em>Write-only.</em>
* @property {number} backgroundAlpha=0.7 - The opacity of the background rectangle, <code>0.0</code> &ndash; <code>1.0</code>.
* <em>Write-only.</em>
*/
/**jsdoc
* The <code>"Rectangle"</code> {@link Overlays.OverlayType|OverlayType} is for 2D rectangles.
* The <code>"rectangle"</code> {@link Overlays.OverlayType|OverlayType} is for 2D rectangles.
* It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}.
* @typedef {object} Overlays.OverlayProperties-Rectangle
* @property {Rect} bounds - The position and size of the rectangle, in pixels. <em>Write-only.</em>
* @property {number} x - Integer left, x-coordinate value = <code>bounds.x</code>. <em>Write-only.</em>
* @property {number} y - Integer top, y-coordinate value = <code>bounds.y</code>. <em>Write-only.</em>
* @property {number} width - Integer width of the rectangle = <code>bounds.width</code>. <em>Write-only.</em>
* @property {number} height - Integer height of the rectangle = <code>bounds.height</code>. <em>Write-only.</em>
*
* @property {number} radius=0 - Integer corner radius, in pixels. <em>Write-only.</em>
* @property {Color} color=0,0,0 - The color of the overlay. <em>Write-only.</em>
* @property {number} alpha=1.0 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>. <em>Write-only.</em>
* @property {number} alpha=1.0 - The opacity of the overlay, <code>0.0</code> &ndash; <code>1.0</code>. <em>Write-only.</em>
* @property {number} borderWidth=1 - Integer width of the border, in pixels. The border is drawn within the rectangle's bounds.
* It is not drawn unless either <code>borderColor</code> or <code>borderAlpha</code> are specified. <em>Write-only.</em>
* @property {number} radius=0 - Integer corner radius, in pixels. <em>Write-only.</em>
* @property {Color} borderColor=0,0,0 - The color of the border. <em>Write-only.</em>
* @property {number} borderAlpha=1.0 - The opacity of the border, <code>0.0</code> - <code>1.0</code>.
* @property {number} borderAlpha=1.0 - The opacity of the border, <code>0.0</code> &ndash; <code>1.0</code>.
* <em>Write-only.</em>
*/
/**jsdoc
* The <code>"Cube"</code> {@link Overlays.OverlayType|OverlayType} is for 3D cubes.
* The <code>"cube"</code> {@link Overlays.OverlayType|OverlayType} is for 3D cubes.
* It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}.
* It additionally has properties per the {@link Entities.EntityProperties-Box|Box} entity.
* <p class="important">Deprecated: Use local {@link Entities} instead.</p>
* @typedef {object} Overlays.OverlayProperties-Cube
* @property {string} name - The name of the overlay.
* @property {Color} color=255,255,255 - The color of the overlay.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> &ndash; <code>1.0</code>.
* @property {number} pulseMax=0 - The maximum value of the pulse multiplier.
* @property {number} pulseMin=0 - The minimum value of the pulse multiplier.
* @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from
@ -1475,24 +1518,33 @@ QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>. Synonym: <code>localOrientation</code>.
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>, and <code>filled</code>.
* @property {boolean} isSolid=false - <code>true</code> if the overlay is rendered as a solid, <code>false</code> if it is
* rendered as a wire frame.
* Synonyms: <ode>solid</code>, <code>isFilled</code>, and <code>filled</code>.
* Antonyms: <code>isWire</code> and <code>wire</code>.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of objects in the world, but behind the HUD.
* @property {boolean} drawHUDLayer=false - If <code>true</code>, the overlay is rendered in front of everything, including the HUD.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {boolean} ignorePickIntersection=false - <code>true</code> if {@link Picks} ignore the overlay, <code>false</code>
* if they don't.
* Synonym: <code>ignoreRayIntersection</code>.
* @property {boolean} drawInFront=false - <code>true</code> if the overlay is rendered on top of the world layer but behind
* the HUD surface.
* @property {boolean} drawHUDLayer=false - <code>true</code> if the overlay is rendered in front of everything, including the
* HUD surface.
* @property {boolean} grabbable=false - <code>true</code> if the overlay can be grabbed, <code>false</code> if it can't be.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
*
* @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is
* parented to if <code>parentID</code> is set. Use 65535 or -1 to parent to the parent's position and orientation rather
* than a joint.
*/
/**jsdoc
* The <code>"Sphere"</code> {@link Overlays.OverlayType|OverlayType} is for 3D spheres.
* The <code>"sphere"</code> {@link Overlays.OverlayType|OverlayType} is for 3D spheres.
* It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}.
* It additionally has properties per the {@link Entities.EntityProperties-Sphere|Sphere} entity.
* <p class="important">Deprecated: Use local {@link Entities} instead.</p>
* @typedef {object} Overlays.OverlayProperties-Sphere
* @property {string} name - The name of the overlay.
* @property {Color} color=255,255,255 - The color of the overlay.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> &ndash; <code>1.0</code>.
* @property {number} pulseMax=0 - The maximum value of the pulse multiplier.
* @property {number} pulseMin=0 - The minimum value of the pulse multiplier.
* @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from
@ -1514,24 +1566,34 @@ QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>. Synonym: <code>localOrientation</code>.
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>, and <code>filled</code>.
* @property {boolean} isSolid=false - <code>true</code> if the overlay is rendered as a solid, <code>false</code> if it is
* rendered as a wire frame.
* Synonyms: <ode>solid</code>, <code>isFilled</code>, and <code>filled</code>.
* Antonyms: <code>isWire</code> and <code>wire</code>.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of objects in the world, but behind the HUD.
* @property {boolean} drawHUDLayer=false - If <code>true</code>, the overlay is rendered in front of everything, including the HUD.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {boolean} ignorePickIntersection=false - <code>true</code> if {@link Picks} ignore the overlay, <code>false</code>
* if they don't.
* Synonym: <code>ignoreRayIntersection</code>.
* @property {boolean} drawInFront=false - <code>true</code> if the overlay is rendered on top of the world layer but behind
* the HUD surface.
* @property {boolean} drawHUDLayer=false - <code>true</code> if the overlay is rendered in front of everything, including the
* HUD surface.
* @property {boolean} grabbable=false - <code>true</code> if the overlay can be grabbed, <code>false</code> if it can't be.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
*
* @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is
* parented to if <code>parentID</code> is set. Use 65535 or -1 to parent to the parent's position and orientation rather
* than a joint.
*/
/**jsdoc
* The <code>"Rectangle3D"</code> {@link Overlays.OverlayType|OverlayType} is for 3D rectangles.
* The <code>"rectangle3D"</code> {@link Overlays.OverlayType|OverlayType} is for 3D rectangles.
* It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}.
* It additionally has properties per the {@link Entities.EntityProperties-Shape|Shape} entity, with the <code>shape</code>
* property value being <code>"Quad"</code>.
* <p class="important">Deprecated: Use local {@link Entities} instead.</p>
* @typedef {object} Overlays.OverlayProperties-Rectangle3D
* @property {string} name - The name of the overlay.
* @property {Color} color=255,255,255 - The color of the overlay.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> &ndash; <code>1.0</code>.
* @property {number} pulseMax=0 - The maximum value of the pulse multiplier.
* @property {number} pulseMin=0 - The minimum value of the pulse multiplier.
* @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from
@ -1553,20 +1615,27 @@ QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>. Synonym: <code>localOrientation</code>.
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>, and <code>filled</code>.
* @property {boolean} isSolid=false - <code>true</code> if the overlay is rendered as a solid, <code>false</code> if it is
* rendered as a wire frame.
* Synonyms: <ode>solid</code>, <code>isFilled</code>, and <code>filled</code>.
* Antonyms: <code>isWire</code> and <code>wire</code>.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of objects in the world, but behind the HUD.
* @property {boolean} drawHUDLayer=false - If <code>true</code>, the overlay is rendered in front of everything, including the HUD.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {boolean} ignorePickIntersection=false - <code>true</code> if {@link Picks} ignore the overlay, <code>false</code>
* if they don't.
* Synonym: <code>ignoreRayIntersection</code>.
* @property {boolean} drawInFront=false - <code>true</code> if the overlay is rendered on top of the world layer but behind
* the HUD surface.
* @property {boolean} drawHUDLayer=false - <code>true</code> if the overlay is rendered in front of everything, including the
* HUD surface.
* @property {boolean} grabbable=false - <code>true</code> if the overlay can be grabbed, <code>false</code> if it can't be.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
*
* @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is
* parented to if <code>parentID</code> is set. Use 65535 or -1 to parent to the parent's position and orientation rather
* than a joint.
*/
/**jsdoc
* <p>A <code>shape</code> {@link Overlays.OverlayType|OverlayType} may display as one of the following geometrical shapes:</p>
* <p>A <code>"shape"</code> {@link Overlays.OverlayType|OverlayType} may display as one of the following geometrical
* shapes:</p>
* <table>
* <thead>
* <tr><th>Value</th><th>Dimensions</th><th>Description</th></tr>
@ -1579,7 +1648,6 @@ QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
* <tr><td><code>"Dodecahedron"</code></td><td>3D</td><td></td></tr>
* <tr><td><code>"Hexagon"</code></td><td>3D</td><td>A hexagonal prism.</td></tr>
* <tr><td><code>"Icosahedron"</code></td><td>3D</td><td></td></tr>
* <tr><td><code>"Line"</code></td><td>1D</td><td>A line oriented in 3D.</td></tr>
* <tr><td><code>"Octagon"</code></td><td>3D</td><td>An octagonal prism.</td></tr>
* <tr><td><code>"Octahedron"</code></td><td>3D</td><td></td></tr>
* <tr><td><code>"Quad"</code></td><td>2D</td><td>A square oriented in 3D.</tr>
@ -1593,11 +1661,14 @@ QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
*/
/**jsdoc
* The <code>"Shape"</code> {@link Overlays.OverlayType|OverlayType} is for 3D shapes.
* The <code>"shape"</code> {@link Overlays.OverlayType|OverlayType} is for 3D shapes.
* It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}.
* It additionally has properties per the {@link Entities.EntityProperties-Shape|Shape} entity.
* <p class="important">Deprecated: Use local {@link Entities} instead.</p>
* @typedef {object} Overlays.OverlayProperties-Shape
* @property {string} name - The name of the overlay.
* @property {Color} color=255,255,255 - The color of the overlay.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> &ndash; <code>1.0</code>.
* @property {number} pulseMax=0 - The maximum value of the pulse multiplier.
* @property {number} pulseMin=0 - The minimum value of the pulse multiplier.
* @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from
@ -1619,21 +1690,31 @@ QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>. Synonym: <code>localOrientation</code>.
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>, and <code>filled</code>.
* @property {boolean} isSolid=false - <code>true</code> if the overlay is rendered as a solid, <code>false</code> if it is
* rendered as a wire frame.
* Synonyms: <ode>solid</code>, <code>isFilled</code>, and <code>filled</code>.
* Antonyms: <code>isWire</code> and <code>wire</code>.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of objects in the world, but behind the HUD.
* @property {boolean} drawHUDLayer=false - If <code>true</code>, the overlay is rendered in front of everything, including the HUD.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {boolean} ignorePickIntersection=false - <code>true</code> if {@link Picks} ignore the overlay, <code>false</code>
* if they don't.
* Synonym: <code>ignoreRayIntersection</code>.
* @property {boolean} drawInFront=false - <code>true</code> if the overlay is rendered on top of the world layer but behind
* the HUD surface.
* @property {boolean} drawHUDLayer=false - <code>true</code> if the overlay is rendered in front of everything, including the
* HUD surface.
* @property {boolean} grabbable=false - <code>true</code> if the overlay can be grabbed, <code>false</code> if it can't be.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
* @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is
* parented to if <code>parentID</code> is set. Use 65535 or -1 to parent to the parent's position and orientation rather
* than a joint.
*
* @property {Overlays.Shape} shape=Hexagon - The geometrical shape of the overlay.
*/
/**jsdoc
* The <code>"Model"</code> {@link Overlays.OverlayType|OverlayType} is for 3D models.
* The <code>"model"</code> {@link Overlays.OverlayType|OverlayType} is for 3D models.
* It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}.
* It additionally has properties per the {@link Entities.EntityProperties-Model|Model} entity.
* <p class="important">Deprecated: Use local {@link Entities} instead.</p>
* @typedef {object} Overlays.OverlayProperties-Model
* @property {string} name - The name of the overlay.
*
@ -1646,42 +1727,39 @@ QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>. Synonym: <code>localOrientation</code>.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of objects in the world, but behind the HUD.
* @property {boolean} drawHUDLayer=false - If <code>true</code>, the overlay is rendered in front of everything, including the HUD.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {boolean} ignorePickIntersection=false - <code>true</code> if {@link Picks} ignore the overlay, <code>false</code>
* if they don't.
* Synonym: <code>ignoreRayIntersection</code>.
* @property {boolean} drawInFront=false - <code>true</code> if the overlay is rendered on top of the world layer but behind
* the HUD surface.
* @property {boolean} drawHUDLayer=false - <code>true</code> if the overlay is rendered in front of everything, including the
* HUD surface.
* @property {boolean} grabbable=false - <code>true</code> if the overlay can be grabbed, <code>false</code> if it can't be.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
* @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is
* parented to if <code>parentID</code> is set. Use 65535 or -1 to parent to the parent's position and orientation rather
* than a joint.
*
* @property {string} url - The URL of the FBX or OBJ model used for the overlay.
* @property {number} loadPriority=0.0 - The priority for loading and displaying the overlay. Overlays with higher values load
* first. <CURRENTLY NOT USED>
* @property {object.<name, url>} textures - Maps the named textures in the model to the JPG or PNG images in the urls.
* @property {string[]} jointNames - The names of the joints - if any - in the model. <em>Read-only.</em>
* @property {Quat[]} jointRotations - The relative rotations of the model's joints.
* @property {Vec3[]} jointTranslations - The relative translations of the model's joints.
* @property {Quat[]} jointOrientations - The absolute orientations of the model's joints, in world coordinates. <em>Read-only.</em>
* @property {Vec3[]} jointPositions - The absolute positions of the model's joints, in world coordinates. <em>Read-only.</em>
* @property {string} animationSettings.url="" - The URL of an FBX file containing an animation to play.
* @property {number} animationSettings.fps=0 - The frame rate (frames/sec) to play the animation at.
* @property {number} animationSettings.firstFrame=0 - The frame to start playing at.
* @property {number} animationSettings.lastFrame=0 - The frame to finish playing at.
* @property {number} animationSettings.currentFrame=0 - The current frame being played.
* @property {boolean} animationSettings.running=false - Whether or not the animation is playing.
* @property {boolean} animationSettings.loop=false - Whether or not the animation should repeat in a loop.
* @property {boolean} animationSettings.hold=false - Whether or not when the animation finishes, the rotations and
* translations of the last frame played should be maintained.
* @property {boolean} animationSettings.allowTranslation=false - Whether or not translations contained in the animation should
* be played.
* first. <em>Currently not used.</em>
* @property {Object.<string, string>|string} textures - Texture name, URL pairs used when rendering the model in place of the
* model's original textures, per the {@link Entities.EntityProperties-Model} property of the same name.
* <p>The value can be an object or a JSON string when setting the value; it is a JSON string when getting the value.</p>
* @property {Entities.AnimationProperties} animationSettings - An animation to play on the model.
*/
/**jsdoc
* The <code>"Text3D"</code> {@link Overlays.OverlayType|OverlayType} is for 3D text.
* The <code>"text3D"</code> {@link Overlays.OverlayType|OverlayType} is for 3D text.
* It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}.
* It additionally has properties per the {@link Entities.EntityProperties-Text|Text} entity.
* <p class="important">Deprecated: Use local {@link Entities} instead.</p>
* @typedef {object} Overlays.OverlayProperties-Text3D
* @property {string} name - The name of the overlay.
* @property {Color} color=255,255,255 - The color of the overlay.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>.
* @property {Color} color=255,255,255 - The color of the overlay text. Synonym: <code>textColor</code>.
* @property {number} alpha=0.7 - The opacity of the overlay text, <code>0.0</code> &ndash; <code>1.0</code>.
* <p><em>Currently not used; use <code>textAlpha</code> instead.</em></p>
* <CURRENTLY BROKEN>
* @property {number} pulseMax=0 - The maximum value of the pulse multiplier.
* @property {number} pulseMin=0 - The minimum value of the pulse multiplier.
* @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from
@ -1703,33 +1781,29 @@ QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>. Synonym: <code>localOrientation</code>.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of objects in the world, but behind the HUD.
* @property {boolean} drawHUDLayer=false - If <code>true</code>, the overlay is rendered in front of everything, including the HUD.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {boolean} ignorePickIntersection=false - <code>true</code> if {@link Picks} ignore the overlay, <code>false</code>
* if they don't.
* Synonym: <code>ignoreRayIntersection</code>.
* @property {boolean} drawInFront=false - <code>true</code> if the overlay is rendered on top of the world layer but behind
* the HUD surface.
* @property {boolean} drawHUDLayer=false - <code>true</code> if the overlay is rendered in front of everything, including the
* HUD surface.
* @property {boolean} grabbable=false - <code>true</code> if the overlay can be grabbed, <code>false</code> if it can't be.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
*
* @property {boolean} isFacingAvatar - If <code>true</code>, the overlay is rotated to face the user's camera about an axis
* parallel to the user's avatar's "up" direction.
* @property {string} text="" - The text to display.Text does not automatically wrap; use <code>\n</code> for a line break.
* @property {number} textAlpha=1 - The text alpha value.
* @property {Color} backgroundColor=0,0,0 - The background color.
* @property {number} backgroundAlpha=0.7 - The background alpha value.
* @property {number} lineHeight=1 - The height of a line of text in meters.
* @property {number} leftMargin=0.1 - The left margin, in meters.
* @property {number} topMargin=0.1 - The top margin, in meters.
* @property {number} rightMargin=0.1 - The right margin, in meters.
* @property {number} bottomMargin=0.1 - The bottom margin, in meters.
* @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is
* parented to if <code>parentID</code> is set. Use 65535 or -1 to parent to the parent's position and orientation rather
* than a joint.
*/
/**jsdoc
* The <code>"Image3D"</code> {@link Overlays.OverlayType|OverlayType} is for 3D images.
* The <code>"image3D"</code> {@link Overlays.OverlayType|OverlayType} is for 3D images.
* It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}.
* It additionally has properties per the {@link Entities.EntityProperties-Image|Image} entity.
* <p class="important">Deprecated: Use local {@link Entities} instead.</p>
* @typedef {object} Overlays.OverlayProperties-Image3D
* @property {string} name - The name of the overlay.
* @property {Color} color=255,255,255 - The color of the overlay.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> &ndash; <code>1.0</code>.
* @property {number} pulseMax=0 - The maximum value of the pulse multiplier.
* @property {number} pulseMin=0 - The minimum value of the pulse multiplier.
* @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from
@ -1751,29 +1825,31 @@ QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>. Synonym: <code>localOrientation</code>.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of objects in the world, but behind the HUD.
* @property {boolean} drawHUDLayer=false - If <code>true</code>, the overlay is rendered in front of everything, including the HUD.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {boolean} ignorePickIntersection=false - <code>true</code> if {@link Picks} ignore the overlay, <code>false</code>
* if they don't.
* Synonym: <code>ignoreRayIntersection</code>.
* @property {boolean} drawInFront=false - <code>true</code> if the overlay is rendered on top of the world layer but behind
* the HUD surface.
* @property {boolean} drawHUDLayer=false - <code>true</code> if the overlay is rendered in front of everything, including the
* HUD surface.
* @property {boolean} grabbable=false - <code>true</code> if the overlay can be grabbed, <code>false</code> if it can't be.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
* @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is
* parented to if <code>parentID</code> is set. Use 65535 or -1 to parent to the parent's position and orientation rather
* than a joint.
*
* @property {boolean} isFacingAvatar - If <code>true</code>, the overlay is rotated to face the user's camera about an axis
* parallel to the user's avatar's "up" direction.
* @property {string} url - The URL of the PNG or JPG image to display.
* @property {Rect} subImage - The portion of the image to display. Defaults to the full image.
* @property {boolean} emissive - If <code>true</code>, the overlay is displayed at full brightness, otherwise it is rendered
* with scene lighting.
* @property {bool} keepAspectRatio=true - overlays will maintain the aspect ratio when the subImage is applied.
* @property {string} url - The URL of the image to display.
*/
/**jsdoc
* The <code>"Web"</code> {@link Overlays.OverlayType|OverlayType} is for 3D web surfaces.
* @typedef {object} Overlays.OverlayProperties-Web
* The <code>"web3d"</code> {@link Overlays.OverlayType|OverlayType} is for 3D web surfaces.
* It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}.
* It additionally has properties per the {@link Entities.EntityProperties-Web|Web} entity.
* <p class="important">Deprecated: Use local {@link Entities} instead.</p>
* @typedef {object} Overlays.OverlayProperties-Web3D
* @property {string} name - The name of the overlay.
* @property {Color} color=255,255,255 - The color of the overlay.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> &ndash; <code>1.0</code>.
* @property {number} pulseMax=0 - The maximum value of the pulse multiplier.
* @property {number} pulseMin=0 - The minimum value of the pulse multiplier.
* @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from
@ -1795,29 +1871,31 @@ QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>. Synonym: <code>localOrientation</code>.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of objects in the world, but behind the HUD.
* @property {boolean} drawHUDLayer=false - If <code>true</code>, the overlay is rendered in front of everything, including the HUD.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {boolean} ignorePickIntersection=false - <code>true</code> if {@link Picks} ignore the overlay, <code>false</code>
* if they don't.
* Synonym: <code>ignoreRayIntersection</code>.
* @property {boolean} drawInFront=false - <code>true</code> if the overlay is rendered on top of the world layer but behind
* the HUD surface.
* @property {boolean} drawHUDLayer=false - <code>true</code> if the overlay is rendered in front of everything, including the
* HUD surface.
* @property {boolean} grabbable=false - <code>true</code> if the overlay can be grabbed, <code>false</code> if it can't be.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
* @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is
* parented to if <code>parentID</code> is set. Use 65535 or -1 to parent to the parent's position and orientation rather
* than a joint.
*
* @property {boolean} isFacingAvatar - If <code>true</code>, the overlay is rotated to face the user's camera about an axis
* parallel to the user's avatar's "up" direction.
* @property {string} url - The URL of the Web page to display.
* @property {string} scriptURL="" - The URL of a JavaScript file to inject into the Web page.
* @property {number} dpi=30 - The dots per inch to display the Web page at, on the overlay.
* @property {number} maxFPS=10 - The maximum update rate for the Web overlay content, in frames/second.
* @property {string} inputMode=Touch - The user input mode to use - either <code>"Touch"</code> or <code>"Mouse"</code>.
* @property {string} url - The URL of the web page to display.
*/
/**jsdoc
* The <code>"Line"</code> {@link Overlays.OverlayType|OverlayType} is for 3D lines.
* @typedef {object} Overlays.OverlayProperties-Line
* The <code>"line3d"</code> {@link Overlays.OverlayType|OverlayType} is for 3D lines.
* It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}.
* It additionally has properties per the {@link Entities.EntityProperties-PolyLine|PolyLine} entity.
* <p class="important">Deprecated: Use local {@link Entities} instead.</p>
* @typedef {object} Overlays.OverlayProperties-Line3D
* @property {string} name - The name of the overlay.
* @property {Color} color=255,255,255 - The color of the overlay.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> &ndash; <code>1.0</code>.
*
* @property {Vec3} position - The position of the overlay center. Synonyms: <code>p1</code>, <code>point</code>, and
* <code>start</code>.
@ -1827,35 +1905,61 @@ QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>. Synonym: <code>localOrientation</code>.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of objects in the world, but behind the HUD.
* @property {boolean} drawHUDLayer=false - If <code>true</code>, the overlay is rendered in front of everything, including the HUD.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {boolean} ignorePickIntersection=false - <code>true</code> if {@link Picks} ignore the overlay, <code>false</code>
* if they don't.
* Synonym: <code>ignoreRayIntersection</code>.
* @property {boolean} drawInFront=false - <code>true</code> if the overlay is rendered on top of the world layer but behind
* the HUD surface.
* @property {boolean} drawHUDLayer=false - <code>true</code> if the overlay is rendered in front of everything, including the
* HUD surface.
* @property {boolean} grabbable=false - <code>true</code> if the overlay can be grabbed, <code>false</code> if it can't be.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
* @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is
* parented to if <code>parentID</code> is set. Use 65535 or -1 to parent to the parent's position and orientation rather
* than a joint.
*
* @property {Uuid} endParentID=null - The avatar, entity, or overlay that the end point of the line is parented to.
* <p><em>Currently doesn't work.</em></p>
* <CURRENTLY BROKEN>
* @property {number} endParentJointIndex=65535 - Integer value specifying the skeleton joint that the end point of the line is
* attached to if <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint". <CURRENTLY BROKEN>
* attached to if <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
* <p><em>Currently doesn't work.</em></p>
* <CURRENTLY BROKEN>
* @property {Vec3} start - The start point of the line. Synonyms: <code>startPoint</code> and <code>p1</code>.
* <p><em>If <code>parentID<code> is set, use <code>localStart</code> to set the local position of the start point.</em></p>
* <CURRENTLY BROKEN>
* @property {Vec3} end - The end point of the line. Synonyms: <code>endPoint</code> and <code>p2</code>.
* <p><em>If <code>parentID<code> is set, use <code>localEnd</code> to set the local position of the end point.</em></p>
* <CURRENTLY BROKEN>
* @property {Vec3} localStart - The local position of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>start</code>. Synonym: <code>localPosition</code>.
* <code>parentID</code> set, otherwise the same value as <code>start</code>.
* <CURRENTLY BROKEN>
* @property {Vec3} localEnd - The local position of the overlay relative to its parent if the overlay has a
* <code>endParentID</code> set, otherwise the same value as <code>end</code>. <CURRENTLY BROKEN>
* <code>endParentID</code> set, otherwise the same value as <code>end</code>.
* <CURRENTLY BROKEN>
* @property {number} length - The length of the line, in meters. This can be set after creating a line with start and end
* points. <CURRENTLY BROKEN>
* points.
* <p><em>Currently doesn't work.</em></p>
* <CURRENTLY BROKEN>
* @property {number} glow=0 - If <code>glow > 0</code>, the line is rendered with a glow.
* @property {number} lineWidth=0.02 - Width of the line, in meters.
* <p><em>You can set this property's value but currently cannot retrieve its value. Use the <code>strokeWidths</code>
* property to retrieve its value instead.</p>
*/
/**jsdoc
* The <code>"Grid"</code> {@link Overlays.OverlayType|OverlayType} is for 3D grid.
* The <code>"grid"</code> {@link Overlays.OverlayType|OverlayType} is for 3D grids.
* It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}.
* It additionally has properties per the {@link Entities.EntityProperties-Grid|Grid} entity.
* <p class="important">Deprecated: Use local {@link Entities} instead.</p>
* @typedef {object} Overlays.OverlayProperties-Grid
* @property {string} name - The name of the overlay.
* @property {Color} color=255,255,255 - The color of the overlay.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> &ndash; <code>1.0</code>.
* @property {number} pulseMax=0 - The maximum value of the pulse multiplier.
* @property {number} pulseMin=0 - The minimum value of the pulse multiplier.
* @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from
@ -1877,25 +1981,28 @@ QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>. Synonym: <code>localOrientation</code>.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of objects in the world, but behind the HUD.
* @property {boolean} drawHUDLayer=false - If <code>true</code>, the overlay is rendered in front of everything, including the HUD.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {boolean} ignorePickIntersection=false - <code>true</code> if {@link Picks} ignore the overlay, <code>false</code>
* if they don't.
* Synonym: <code>ignoreRayIntersection</code>.
* @property {boolean} drawInFront=false - <code>true</code> if the overlay is rendered on top of the world layer but behind
* the HUD surface.
* @property {boolean} drawHUDLayer=false - <code>true</code> if the overlay is rendered in front of everything, including the
* HUD surface.
* @property {boolean} grabbable=false - <code>true</code> if the overlay can be grabbed, <code>false</code> if it can't be.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
*
* @property {boolean} followCamera=true - If <code>true</code>, the grid is always visible even as the camera moves to another position.
* @property {number} majorGridEvery=5 - Integer number of <code>minorGridEvery</code> intervals at which to draw a thick grid line. Minimum value = <code>1</code>.
* @property {number} minorGridEvery=1 - Real number of meters at which to draw thin grid lines. Minimum value = <code>0.001</code>.
* @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is
* parented to if <code>parentID</code> is set. Use 65535 or -1 to parent to the parent's position and orientation rather
* than a joint.
*/
/**jsdoc
* The <code>"Circle"</code> {@link Overlays.OverlayType|OverlayType} is for 3D circle.
* @typedef {object} Overlays.OverlayProperties-Circle
* The <code>"circle3d"</code> {@link Overlays.OverlayType|OverlayType} is for 3D circles.
* It has properties in addition to the common {@link Overlays.OverlayProperties|OverlayProperties}.
* It additionally has properties per the {@link Entities.EntityProperties-Gizmo|Gizmo} entity, with the
* <code>gizmoType</code> property value being <code>"ring"</code>.
* <p class="important">Deprecated: Use local {@link Entities} instead.</p>
* @typedef {object} Overlays.OverlayProperties-Circle3D
* @property {string} name - The name of the overlay.
* @property {Color} color=255,255,255 - The color of the overlay.
* @property {number} alpha=0.7 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>.
* @property {number} pulseMax=0 - The maximum value of the pulse multiplier.
* @property {number} pulseMin=0 - The minimum value of the pulse multiplier.
* @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from
@ -1912,62 +2019,75 @@ QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
* @property {Vec3} position - The position of the overlay center. Synonyms: <code>p1</code>, <code>point</code>, and
* <code>start</code>.
* @property {Vec3} dimensions - The dimensions of the overlay. Synonyms: <code>scale</code>, <code>size</code>.
* <em>Read-only.</em>
* @property {Quat} rotation - The orientation of the overlay. Synonym: <code>orientation</code>.
* @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>position</code>.
* @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a
* <code>parentID</code> set, otherwise the same value as <code>rotation</code>. Synonym: <code>localOrientation</code>.
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>, and <code>filled</code>.
* @property {boolean} isSolid=false - <code>true</code> if the overlay is rendered as a solid, <code>false</code> if it is
* rendered as a wire frame.
* Synonyms: <ode>solid</code>, <code>isFilled</code>, and <code>filled</code>.
* Antonyms: <code>isWire</code> and <code>wire</code>.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of objects in the world, but behind the HUD.
* @property {boolean} drawHUDLayer=false - If <code>true</code>, the overlay is rendered in front of everything, including the HUD.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
* @property {boolean} ignorePickIntersection=false - <code>true</code> if {@link Picks} ignore the overlay, <code>false</code>
* if they don't.
* Synonym: <code>ignoreRayIntersection</code>.
* @property {boolean} drawInFront=false - <code>true</code> if the overlay is rendered on top of the world layer but behind
* the HUD surface.
* @property {boolean} drawHUDLayer=false - <code>true</code> if the overlay is rendered in front of everything, including the
* HUD surface.
* @property {boolean} grabbable=false - <code>true</code> if the overlay can be grabbed, <code>false</code> if it can't be.
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
* @property {number} parentJointIndex=65535 - Integer value specifying the joint of the entity or avatar that the entity is
* parented to if <code>parentID</code> is set. Use 65535 or -1 to parent to the parent's position and orientation rather
* than a joint.
*
* @property {number} startAt = 0 - The counter - clockwise angle from the overlay's x-axis that drawing starts at in degrees.
* @property {number} endAt = 360 - The counter - clockwise angle from the overlay's x-axis that drawing ends at in degrees.
* @property {number} outerRadius = 1 - The outer radius of the overlay in meters. Synonym: <code>radius</code>.
* @property {number} innerRadius = 0 - The inner radius of the overlay in meters.
* @property {Color} color = 255, 255, 255 - The color of the overlay. Setting this value also sets the values of
* <code>innerStartColor</code>, <code>innerEndColor</code>, <code>outerStartColor</code>, and <code>outerEndColor</code>.
* @property {number} startAt=0 - The counter-clockwise angle from the overlay's x-axis that drawing starts at, in degrees.
* @property {number} endAt=360 - The counter-clockwise angle from the overlay's x-axis that drawing ends at, in degrees.
* @property {number} outerRadius=1 - The outer radius of the overlay in meters. Synonym: <code>radius</code>.
* @property {number} innerRadius=0 - The inner radius of the overlay in meters.
* @property {Color} color - Sets the color of the overlay. Setting this value sets the values of <code>innerStartColor</code>,
* <code>innerEndColor</code>, <code>outerStartColor</code>, and <code>outerEndColor</code>.
* <em>Write-only.</em>
* @property {Color} startColor - Sets the values of <code>innerStartColor</code> and <code>outerStartColor</code>.
* <em>Write - only.</em>
* <em>Write-only.</em>
* @property {Color} endColor - Sets the values of <code>innerEndColor</code> and <code>outerEndColor</code>.
* <em>Write - only.</em>
* <em>Write-only.</em>
* @property {Color} innerColor - Sets the values of <code>innerStartColor</code> and <code>innerEndColor</code>.
* <em>Write - only.</em>
* <em>Write-only.</em>
* @property {Color} outerColor - Sets the values of <code>outerStartColor</code> and <code>outerEndColor</code>.
* <em>Write - only.</em>
* @property {Color} innerStartcolor - The color at the inner start point of the overlay.
* @property {Color} innerEndColor - The color at the inner end point of the overlay.
* @property {Color} outerStartColor - The color at the outer start point of the overlay.
* @property {Color} outerEndColor - The color at the outer end point of the overlay.
* @property {number} alpha = 0.5 - The opacity of the overlay, <code>0.0</code> -<code>1.0</code>. Setting this value also sets
* the values of <code>innerStartAlpha</code>, <code>innerEndAlpha</code>, <code>outerStartAlpha</code>, and
* <code>outerEndAlpha</code>. Synonym: <code>Alpha</code>; <em>write - only</em>.
* <em>Write-only.</em>
* @property {Color} innerStartcolor=255,255,255 - The color at the inner start point of the overlay.
* @property {Color} innerEndColor=255,255,255 - The color at the inner end point of the overlay.
* @property {Color} outerStartColor=255,255,255 - The color at the outer start point of the overlay.
* @property {Color} outerEndColor=255,255,255 - The color at the outer end point of the overlay.
* @property {number} alpha - Sets the opacity of the overlay, <code>0.0</code> &ndash; <code>1.0</code>. Setting this value
* sets the values of <code>innerStartAlpha</code>, <code>innerEndAlpha</code>, <code>outerStartAlpha</code>, and
* <code>outerEndAlpha</code>. Synonym: <code>Alpha</code>.
* @property {number} startAlpha - Sets the values of <code>innerStartAlpha</code> and <code>outerStartAlpha</code>.
* <em>Write - only.</em>
* <em>Write-only.</em>
* @property {number} endAlpha - Sets the values of <code>innerEndAlpha</code> and <code>outerEndAlpha</code>.
* <em>Write - only.</em>
* <em>Write-only.</em>
* @property {number} innerAlpha - Sets the values of <code>innerStartAlpha</code> and <code>innerEndAlpha</code>.
* <em>Write - only.</em>
* <em>Write-only.</em>
* @property {number} outerAlpha - Sets the values of <code>outerStartAlpha</code> and <code>outerEndAlpha</code>.
* <em>Write - only.</em>
* @property {number} innerStartAlpha = 0 - The alpha at the inner start point of the overlay.
* @property {number} innerEndAlpha = 0 - The alpha at the inner end point of the overlay.
* @property {number} outerStartAlpha = 0 - The alpha at the outer start point of the overlay.
* @property {number} outerEndAlpha = 0 - The alpha at the outer end point of the overlay.
* <em>Write-only.</em>
* @property {number} innerStartAlpha=0.7 - The opacity at the inner start point of the overlay, <code>0.0</code> &ndash;
* <code>1.0</code>.
* @property {number} innerEndAlpha=0.7 - The opacity at the inner end point of the overlay, <code>0.0</code> &ndash;
* <code>1.0</code>.
* @property {number} outerStartAlpha=0.7 - The opacity at the outer start point of the overlay, <code>0.0</code> &ndash;
* <code>1.0</code>.
* @property {number} outerEndAlpha=0.7 - The opacity at the outer end point of the overlay, <code>0.0</code> &ndash;
* <code>1.0</code>.
*
* @property {boolean} hasTickMarks = false - If <code>true</code>, tick marks are drawn.
* @property {number} majorTickMarksAngle = 0 - The angle between major tick marks, in degrees.
* @property {number} minorTickMarksAngle = 0 - The angle between minor tick marks, in degrees.
* @property {number} majorTickMarksLength = 0 - The length of the major tick marks, in meters. A positive value draws tick marks
* @property {boolean} hasTickMarks=false - <code>true</code> if tick marks are drawn, <code>false</code> if they aren't.
* @property {number} majorTickMarksAngle=0 - The angle between major tick marks, in degrees.
* @property {number} minorTickMarksAngle=0 - The angle between minor tick marks, in degrees.
* @property {number} majorTickMarksLength=0 - The length of the major tick marks, in meters. A positive value draws tick marks
* outwards from the inner radius; a negative value draws tick marks inwards from the outer radius.
* @property {number} minorTickMarksLength = 0 - The length of the minor tick marks, in meters. A positive value draws tick marks
* @property {number} minorTickMarksLength=0 - The length of the minor tick marks, in meters. A positive value draws tick marks
* outwards from the inner radius; a negative value draws tick marks inwards from the outer radius.
* @property {Color} majorTickMarksColor = 0, 0, 0 - The color of the major tick marks.
* @property {Color} minorTickMarksColor = 0, 0, 0 - The color of the minor tick marks.
* @property {Color} majorTickMarksColor=0,0,0 - The color of the major tick marks.
* @property {Color} minorTickMarksColor=0,0,0 - The color of the minor tick marks.
*/

View file

@ -68,17 +68,23 @@ public:
};
/**jsdoc
* (Note: 3D Overlays are deprecated. Use local entities instead.) The Overlays API provides facilities to create and interact with overlays. Overlays are 2D and 3D objects visible only to
* yourself and that aren't persisted to the domain. They are used for UI.
* The <code>Overlays</code> API provides facilities to create and interact with overlays. These are 2D and 3D objects visible
* only to yourself and that aren't persisted to the domain. They are used for UI.
*
* <p><strong>Note:</strong> 3D overlays are local {@link Entities}, internally, so many of the methods also work with
* entities.</p>
*
* <p class="important">3D overlays are deprecated: Use local {@link Entities} for these instead.</p>
*
* @namespace Overlays
*
* @hifi-interface
* @hifi-client-entity
* @hifi-avatar
*
* @property {Uuid} keyboardFocusOverlay - Get or set the {@link Entities.EntityTypes|Web} entity that has keyboard focus.
* If no entity has keyboard focus, returns <code>null</code>; set to <code>null</code> or {@link Uuid(0)|Uuid.NULL} to
* clear keyboard focus.
* @property {Uuid} keyboardFocusOverlay - The <code>{@link Overlays.OverlayProperties-Web3D|"web3d"}</code> overlay
* ({@link Entities.EntityProperties-Web|Web} entity) that has keyboard focus. If no overlay (entity) has keyboard focus,
* returns <code>null</code>; set to <code>null</code> or {@link Uuid(0)|Uuid.NULL} to clear keyboard focus.
*/
class Overlays : public QObject {
@ -118,7 +124,7 @@ public:
public slots:
/**jsdoc
* Add an overlay to the scene.
* Adds an overlay to the scene.
* @function Overlays.addOverlay
* @param {Overlays.OverlayType} type - The type of the overlay to add.
* @param {Overlays.OverlayProperties} properties - The properties of the overlay to add.
@ -134,19 +140,21 @@ public slots:
QUuid addOverlay(const QString& type, const QVariant& properties);
/**jsdoc
* Create a clone of an existing entity (or 2D overlay).
* Creates a clone of an existing overlay (or entity).
* <p>Note: For cloning behavior of 3D overlays and entities, see {@link Entities.cloneEntity}.</p>
* @function Overlays.cloneOverlay
* @param {Uuid} id - The ID of the entity/2D overlay to clone.
* @returns {Uuid} The ID of the new object if successful, otherwise {@link Uuid(0)|Uuid.NULL}.
* @param {Uuid} id - The ID of the overlay (or entity) to clone.
* @returns {Uuid} The ID of the new overlay (or entity) if successful, otherwise {@link Uuid(0)|Uuid.NULL}.
*/
QUuid cloneOverlay(const QUuid& id);
/**jsdoc
* Edit an overlay's properties.
* Edits an overlay's (or entity's) properties.
* @function Overlays.editOverlay
* @param {Uuid} id - The ID of the overlay to edit.
* @param {Uuid} id - The ID of the overlay (or entity) to edit.
* @param {Overlays.OverlayProperties} properties - The properties changes to make.
* @returns {boolean} <code>true</code> if the overlay was found and edited, otherwise <code>false</code>.
* @returns {boolean} <code>false</code> if Interface is exiting. Otherwise, if a 2D overlay then <code>true</code> always,
* and if a 3D overlay then <code>true</code> if the overlay was found and edited, otherwise <code>false</code>.
* @example <caption>Add an overlay in front of your avatar then change its color.</caption>
* var overlay = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })),
@ -163,12 +171,11 @@ public slots:
bool editOverlay(const QUuid& id, const QVariant& properties);
/**jsdoc
* Edit multiple overlays' properties.
* Edits the properties of multiple overlays (or entities).
* @function Overlays.editOverlays
* @param propertiesById {object.<Uuid, Overlays.OverlayProperties>} - An object with overlay IDs as keys and
* @param propertiesById {object.<Uuid, Overlays.OverlayProperties>} - An object with overlay (or entity) IDs as keys and
* {@link Overlays.OverlayProperties|OverlayProperties} edits to make as values.
* @returns {boolean} <code>true</code> if all overlays were found and edited, otherwise <code>false</code> (some may have
* been found and edited).
* @returns {boolean} <code>false</code> if Interface is exiting, otherwise <code>true</code>.
* @example <caption>Create two overlays in front of your avatar then change their colors.</caption>
* var overlayA = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: -0.3, y: 0, z: -3 })),
@ -192,18 +199,18 @@ public slots:
bool editOverlays(const QVariant& propertiesById);
/**jsdoc
* Delete an entity or 2D overlay.
* Deletes an overlay (or entity).
* @function Overlays.deleteOverlay
* @param {Uuid} id - The ID of the object to delete.
* @param {Uuid} id - The ID of the overlay (or entity) to delete.
*/
void deleteOverlay(const QUuid& id);
/**jsdoc
* Get the type of an entity or 2D overlay.
* Gets the type of an overlay.
* @function Overlays.getOverlayType
* @param {Uuid} id - The ID of the object to get the type of.
* @returns {string} The type of the object if found, otherwise an empty string.
* @example <caption>Create an object in front of your avatar then get and report its type.</caption>
* @param {Uuid} id - The ID of the overlay to get the type of.
* @returns {Overlays.OverlayType} The type of the overlay if found, otherwise <code>"unknown"</code>.
* @example <caption>Create an overlay in front of your avatar then get and report its type.</caption>
* var overlay = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })),
* rotation: MyAvatar.orientation,
@ -211,56 +218,82 @@ public slots:
* solid: true
* });
* var type = Overlays.getOverlayType(overlay);
* print("Type: " + type);
* print("Type: " + type); // cube
*/
QString getOverlayType(const QUuid& id);
/**jsdoc
* Get the overlay script object. In particular, this is useful for accessing the event bridge for a <code>web3d</code>
* overlay.
* Gets an overlay's (or entity's) script object. In particular, this is useful for accessing a
* <code>{@link Overlays.OverlayProperties-Web3D|"web3d"}</code> overlay's <code>EventBridge</code> script object to
* exchange messages with the web page script.
* <p>To send a message from an Interface script to a <code>"web3d"</code> overlay over its event bridge:</p>
* <pre class="prettyprint"><code>var overlayObject = Overlays.getOverlayObject(overlayID);
* overlayObject.emitScriptEvent(message);</code></pre>
* <p>To receive a message from a <code>"web3d"</code> overlay over its event bridge in an Interface script:</p>
* <pre class="prettyprint"><code>var overlayObject = Overlays.getOverlayObject(overlayID);
* overlayObject.webEventReceived.connect(function(message) {
* ...
* };</code></pre>
* @function Overlays.getOverlayObject
* @param {Uuid} overlayID - The ID of the overlay to get the script object of.
* @returns {object} The script object for the overlay if found.
* @example <caption>Receive "hello" messages from a <code>web3d</code> overlay.</caption>
* // HTML file: name "web3d.html".
* @example <caption>Exchange messages with a <code>"web3d"</code> overlay.</caption>
* // HTML file, name: "web3d.html".
* <!DOCTYPE html>
* <html>
* <head>
* <title>HELLO</title>
* </head>
* <body>
* <h1>HELLO</h1></h1>
* <h1>HELLO</h1>
* <script>
* function onScriptEventReceived(message) {
* // Message received from the script.
* console.log("Message received: " + message);
* }
*
* EventBridge.scriptEventReceived.connect(onScriptEventReceived);
*
* setInterval(function () {
* // Send a message to the script.
* EventBridge.emitWebEvent("hello");
* }, 2000);
* </script>
* </body>
* </html>
*
* // Script file.
* var web3dOverlay = Overlays.addOverlay("web3d", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.5, z: -3 })),
* rotation: MyAvatar.orientation,
* url: Script.resolvePath("web3d.html"),
* alpha: 1.0
* });
*
* function onWebEventReceived(event) {
* print("onWebEventReceived() : " + JSON.stringify(event));
*
* // Interface script file.
* var web3DOverlay = Overlays.addOverlay("web3d", {
* type: "Web",
* position : Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y : 0.5, z : -3 })),
* rotation : MyAvatar.orientation,
* sourceUrl : Script.resolvePath("web3d.html"),
* alpha : 1.0
* });
*
* var overlayObject;
*
* function onWebEventReceived(message) {
* // Message received.
* print("Message received: " + message);
*
* // Send a message back.
* overlayObject.emitScriptEvent(message + " back");
* }
*
* overlayObject = Overlays.getOverlayObject(web3dOverlay);
* overlayObject.webEventReceived.connect(onWebEventReceived);
*
* Script.scriptEnding.connect(function () {
* Overlays.deleteOverlay(web3dOverlay);
*
* Script.setTimeout(function() {
* overlayObject = Overlays.getOverlayObject(web3DOverlay);
* overlayObject.webEventReceived.connect(onWebEventReceived);
* }, 500);
*
* Script.scriptEnding.connect(function() {
* Overlays.deleteOverlay(web3DOverlay);
* });
*/
QObject* getOverlayObject(const QUuid& id);
/**jsdoc
* Get the ID of the 2D overlay at a particular point on the screen or HUD.
* Gets the ID of the 2D overlay at a particular point on the desktop screen or HUD surface.
* @function Overlays.getOverlayAtPoint
* @param {Vec2} point - The point to check for an overlay.
* @returns {Uuid} The ID of the 2D overlay at the specified point if found, otherwise <code>null</code>.
@ -279,10 +312,11 @@ public slots:
QUuid getOverlayAtPoint(const glm::vec2& point);
/**jsdoc
* Get the value of a 3D overlay's property.
* Gets a specified property value of a 3D overlay (or entity).
* <p><strong>Note:</strong> 2D overlays' property values cannot be retrieved.</p>
* @function Overlays.getProperty
* @param {Uuid} id - The ID of the overlay. <em>Must be for a 3D {@link Overlays.OverlayType|OverlayType}.</em>
* @param {string} property - The name of the property value to get.
* @param {Uuid} id - The ID of the 3D overlay (or entity).
* @param {string} property - The name of the property to get the value of.
* @returns {object} The value of the property if the 3D overlay and property can be found, otherwise
* <code>undefined</code>.
* @example <caption>Create an overlay in front of your avatar then report its alpha property value.</caption>
@ -298,12 +332,13 @@ public slots:
QVariant getProperty(const QUuid& id, const QString& property);
/**jsdoc
* Get the values of an overlay's properties.
* Gets specified property values of a 3D overlay (or entity).
* <p><strong>Note:</strong> 2D overlays' property values cannot be retrieved.</p>
* @function Overlays.getProperties
* @param {Uuid} id - The ID of the overlay.
* @param {Array.<string>} properties - An array of names of properties to get the values of.
* @returns {Overlays.OverlayProperties} The values of valid properties if the overlay can be found, otherwise
* <code>undefined</code>.
* @param {Uuid} id - The ID of the overlay (or entity).
* @param {Array.<string>} properties - The names of the properties to get the values of.
* @returns {Overlays.OverlayProperties} The values of valid properties if the overlay can be found, otherwise an empty
* object.
* @example <caption>Create an overlay in front of your avatar then report some of its properties.</caption>
* var overlay = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })),
@ -317,11 +352,11 @@ public slots:
QVariantMap getProperties(const QUuid& id, const QStringList& properties);
/**jsdoc
* Get the values of multiple overlays' properties.
* Gets the values of multiple overlays' (or entities') properties.
* @function Overlays.getOverlaysProperties
* @param propertiesById {object.<Uuid, Array.<string>>} - An object with overlay IDs as keys and arrays of the names of
* properties to get for each as values.
* @returns {object.<Uuid, Overlays.OverlayProperties>} An object with overlay IDs as keys and
* @param propertiesById {object.<Uuid, Array.<string>>} - An object with overlay (or entity) IDs as keys and arrays of the
* names of properties to get for each as values.
* @returns {object.<Uuid, Overlays.OverlayProperties>} An object with overlay (or entity) IDs as keys and
* {@link Overlays.OverlayProperties|OverlayProperties} as values.
* @example <caption>Create two cube overlays in front of your avatar then get some of their properties.</caption>
* var overlayA = Overlays.addOverlay("cube", {
@ -345,22 +380,20 @@ public slots:
QVariantMap getOverlaysProperties(const QVariant& overlaysProperties);
/**jsdoc
* Find the closest 3D overlay intersected by a {@link PickRay}. Overlays with their <code>drawInFront</code> property set
* to <code>true</code> have priority over overlays that don't, except that tablet overlays have priority over any
* <code>drawInFront</code> overlays behind them. I.e., if a <code>drawInFront</code> overlay is behind one that isn't
* <code>drawInFront</code>, the <code>drawInFront</code> overlay is returned, but if a tablet overlay is in front of a
* <code>drawInFront</code> overlay, the tablet overlay is returned.
* Finds the closest 3D overlay (or local entity) intersected by a {@link PickRay}.
* @function Overlays.findRayIntersection
* @param {PickRay} pickRay - The PickRay to use for finding overlays.
* @param {boolean} [precisionPicking=false] - <em>Unused</em>; exists to match Entity API.
* @param {Array.<Uuid>} [include=[]] - If not empty then the search is restricted to these overlays.
* @param {Array.<Uuid>} [discard=[]] - Overlays to ignore during the search.
* @param {boolean} [visibleOnly=false] - If <code>true</code> then only entities that are
* <code>{@link Entities.EntityProperties|visible}<code> are searched.
* @param {boolean} [collideableOnly=false] - If <code>true</code> then only entities that are not
* <code>{@link Entities.EntityProperties|collisionless}</code> are searched.
* @returns {Overlays.RayToOverlayIntersectionResult} The closest 3D overlay intersected by <code>pickRay</code>, taking
* into account <code>overlayIDsToInclude</code> and <code>overlayIDsToExclude</code> if they're not empty.
* @param {boolean} [precisionPicking=false] - <code>true</code> to pick against precise meshes, <code>false</code> to pick
* against coarse meshes. If <code>true</code> and the intersected entity is a model, the result's
* <code>extraInfo</code> property includes more information than it otherwise would.
* @param {Array.<Uuid>} [include=[]] - If not empty, then the search is restricted to these overlays (and local entities).
* @param {Array.<Uuid>} [discard=[]] - Overlays (and local entities) to ignore during the search.
* @param {boolean} [visibleOnly=false] - <code>true</code> if only overlays (and local entities) that are
* <code>{@link Overlays.OverlayProperties|visible}</code> should be searched.
* @param {boolean} [collideableOnly=false] - <code>true</code> if only local entities that are not
* <code>{@link Entities.EntityProperties|collisionless}</code> should be searched.
* @returns {Overlays.RayToOverlayIntersectionResult} The result of the search for the first intersected overlay (or local
* entity.
* @example <caption>Create a cube overlay in front of your avatar. Report 3D overlay intersection details for mouse
* clicks.</caption>
* var overlay = Overlays.addOverlay("cube", {
@ -384,12 +417,13 @@ public slots:
bool collidableOnly = false);
/**jsdoc
* Return a list of local entities with bounding boxes that touch a search sphere.
* Gets a list of visible 3D overlays (local entities) with bounding boxes that touch a search sphere.
* @function Overlays.findOverlays
* @param {Vec3} center - The center of the search sphere.
* @param {number} radius - The radius of the search sphere.
* @returns {Uuid[]} An array of entity IDs with bounding boxes that touch a search sphere.
* @example <caption>Create two cube entities in front of your avatar then search for entities near your avatar.</caption>
* @returns {Uuid[]} The IDs of the overlays (local entities) that are visible and have bounding boxes that touch a search
* sphere.
* @example <caption>Create two overlays in front of your avatar then search for overlays near your avatar.</caption>
* var overlayA = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: -0.3, y: 0, z: -3 })),
* rotation: MyAvatar.orientation,
@ -411,11 +445,13 @@ public slots:
QVector<QUuid> findOverlays(const glm::vec3& center, float radius);
/**jsdoc
* Check whether an overlay's assets have been loaded. For example, for an <code>image</code> overlay the result indicates
* whether its image has been loaded.
* Checks whether an overlay's (or entity's) assets have been loaded. For example, for an
* <code>{@link Overlays.OverlayProperties-Image|"image"}</code> overlay, the result indicates whether its image has been
* loaded.
* @function Overlays.isLoaded
* @param {Uuid} id - The ID of the overlay to check.
* @returns {boolean} <code>true</code> if the overlay's assets have been loaded, otherwise <code>false</code>.
* @param {Uuid} id - The ID of the overlay (or entity) to check.
* @returns {boolean} <code>true</code> if the overlay's (or entity's) assets have been loaded, otherwise
* <code>false</code>.
* @example <caption>Create an image overlay and report whether its image is loaded after 1s.</caption>
* var overlay = Overlays.addOverlay("image", {
* bounds: { x: 100, y: 100, width: 200, height: 200 },
@ -429,55 +465,60 @@ public slots:
bool isLoaded(const QUuid& id);
/**jsdoc
* Calculates the size of the given text in the specified object if it is a text entity or overlay.
* Calculates the size of some text in a text overlay (or entity). The overlay (or entity) need not be set visible.
* <p><strong>Note:</strong> The size of text in a 3D overlay (or entity) cannot be calculated immediately after the
* overlay (or entity) is created; a short delay is required while the overlay (or entity) finishes being created.</p>
* @function Overlays.textSize
* @param {Uuid} id - The ID of the object to use for calculation.
* @param {Uuid} id - The ID of the overlay (or entity) to use for calculation.
* @param {string} text - The string to calculate the size of.
* @returns {Size} The size of the <code>text</code> if the object is a text entity or overlay, otherwise
* <code>{ height: 0, width : 0 }</code>. If the object is a 2D overlay, the size is in pixels; if the object is an entity,
* the size is in meters.
* @returns {Size} The size of the <code>text</code> if the object is a text overlay (or entity), otherwise
* <code>{ height: 0, width : 0 }</code>. If the object is a 2D overlay, the size is in pixels; if the object is a 3D
* overlay (or entity), the size is in meters.
* @example <caption>Calculate the size of "hello" in a 3D text entity.</caption>
* var overlay = Overlays.addOverlay("text3d", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -2 })),
* rotation: MyAvatar.orientation,
* text: "hello",
* lineHeight: 0.2
* lineHeight: 0.2,
* visible: false
* });
* var textSize = Overlays.textSize(overlay, "hello");
* print("Size of \"hello\": " + JSON.stringify(textSize));
*
* Script.setTimeout(function() {
* var textSize = Overlays.textSize(overlay, "hello");
* print("Size of \"hello\": " + JSON.stringify(textSize));
* }, 500);
*/
QSizeF textSize(const QUuid& id, const QString& text);
/**jsdoc
* Get the width of the window or HUD.
* Gets the width of the Interface window or HUD surface.
* @function Overlays.width
* @returns {number} The width, in pixels, of the Interface window if in desktop mode or the HUD if in HMD mode.
* @returns {number} The width, in pixels, of the Interface window if in desktop mode or the HUD surface if in HMD mode.
*/
float width();
/**jsdoc
* Get the height of the window or HUD.
* Gets the height of the Interface window or HUD surface.
* @function Overlays.height
* @returns {number} The height, in pixels, of the Interface window if in desktop mode or the HUD if in HMD mode.
* @returns {number} The height, in pixels, of the Interface window if in desktop mode or the HUD surface if in HMD mode.
*/
float height();
/**jsdoc
* Check if there is an object of a given ID.
* Checks if an overlay (or entity) exists.
* @function Overlays.isAddedOverlay
* @param {Uuid} id - The ID to check.
* @returns {boolean} <code>true</code> if an object with the given ID exists, <code>false</code> otherwise.
* @param {Uuid} id - The ID of the overlay (or entity) to check.
* @returns {boolean} <code>true</code> if an overlay (or entity) with the given ID exists, <code>false</code> if it doesn't.
*/
bool isAddedOverlay(const QUuid& id);
/**jsdoc
* Generate a mouse press event on an overlay.
* Generates a mouse press event on an overlay (or local entity).
* @function Overlays.sendMousePressOnOverlay
* @param {Uuid} id - The ID of the overlay to generate a mouse press event on.
* @param {Uuid} id - The ID of the overlay (or local entity) to generate a mouse press event on.
* @param {PointerEvent} event - The mouse press event details.
* @example <caption>Create a 2D rectangle overlay plus a 3D cube overlay and generate mousePressOnOverlay events for the 2D
* overlay.</caption>
* var overlay = Overlays.addOverlay("cube", {
* @example <caption>Create a 2D rectangle overlay plus a 3D cube overlay and generate mousePressOnOverlay events for the
* 2D overlay.</caption>
* var overlay3D = Overlays.addOverlay("cube", {
* position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -3 })),
* rotation: MyAvatar.orientation,
* dimensions: { x: 0.3, y: 0.3, z: 0.3 },
@ -485,7 +526,7 @@ public slots:
* });
* print("3D overlay: " + overlay);
*
* var overlay = Overlays.addOverlay("rectangle", {
* var overlay2D = Overlays.addOverlay("rectangle", {
* bounds: { x: 100, y: 100, width: 200, height: 100 },
* color: { red: 255, green: 255, blue: 255 }
* });
@ -511,66 +552,69 @@ public slots:
void sendMousePressOnOverlay(const QUuid& id, const PointerEvent& event);
/**jsdoc
* Generate a mouse release event on an overlay.
* Generates a mouse release event on an overlay (or local entity).
* @function Overlays.sendMouseReleaseOnOverlay
* @param {Uuid} id - The ID of the overlay to generate a mouse release event on.
* @param {Uuid} id - The ID of the overlay (or local entity) to generate a mouse release event on.
* @param {PointerEvent} event - The mouse release event details.
*/
void sendMouseReleaseOnOverlay(const QUuid& id, const PointerEvent& event);
/**jsdoc
* Generate a mouse move event on an overlay.
* Generates a mouse move event on an overlay (or local entity).
* @function Overlays.sendMouseMoveOnOverlay
* @param {Uuid} id - The ID of the overlay to generate a mouse move event on.
* @param {Uuid} id - The ID of the overlay (or local entity) to generate a mouse move event on.
* @param {PointerEvent} event - The mouse move event details.
*/
void sendMouseMoveOnOverlay(const QUuid& id, const PointerEvent& event);
/**jsdoc
* Generate a hover enter event on an overlay.
* Generates a hover enter event on an overlay (or local entity).
* @function Overlays.sendHoverEnterOverlay
* @param {Uuid} id - The ID of the overlay to generate a hover enter event on.
* @param {Uuid} id - The ID of the overlay (or local entity) to generate a hover enter event on.
* @param {PointerEvent} event - The hover enter event details.
*/
void sendHoverEnterOverlay(const QUuid& id, const PointerEvent& event);
/**jsdoc
* Generate a hover over event on an overlay.
* Generates a hover over event on an overlay (or entity).
* @function Overlays.sendHoverOverOverlay
* @param {Uuid} id - The ID of the overlay to generate a hover over event on.
* @param {Uuid} id - The ID of the overlay (or local entity) to generate a hover over event on.
* @param {PointerEvent} event - The hover over event details.
*/
void sendHoverOverOverlay(const QUuid& id, const PointerEvent& event);
/**jsdoc
* Generate a hover leave event on an overlay.
* Generates a hover leave event on an overlay (or local entity).
* @function Overlays.sendHoverLeaveOverlay
* @param {Uuid} id - The ID of the overlay to generate a hover leave event on.
* @param {Uuid} id - The ID of the overlay (or local entity) to generate a hover leave event on.
* @param {PointerEvent} event - The hover leave event details.
*/
void sendHoverLeaveOverlay(const QUuid& id, const PointerEvent& event);
/**jsdoc
* Get the ID of the Web3D entity that has keyboard focus.
* Gets the ID of the <code>{@link Overlays.OverlayProperties-Web3D|"web3d"}</code> overlay
* ({@link Entities.EntityProperties-Web|Web} entity) that has keyboard focus.
* @function Overlays.getKeyboardFocusOverlay
* @returns {Uuid} The ID of the {@link Entities.EntityTypes|Web} overlay that has focus, if any, otherwise
* <code>null</code>.
* @returns {Uuid} The ID of the <code>{@link Overlays.OverlayProperties-Web3D|"web3d"}</code> overlay
* ({@link Entities.EntityProperties-Web|Web} entity) that has focus, if any, otherwise <code>null</code>.
*/
QUuid getKeyboardFocusOverlay() { return DependencyManager::get<EntityScriptingInterface>()->getKeyboardFocusEntity(); }
/**jsdoc
* Set the Web3D entity that has keyboard focus.
* Sets the <code>{@link Overlays.OverlayProperties-Web3D|"web3d"}</code> overlay
* ({@link Entities.EntityProperties-Web|Web} entity) that has keyboard focus.
* @function Overlays.setKeyboardFocusOverlay
* @param {Uuid} id - The ID of the {@link Entities.EntityTypes|Web} entity to set keyboard focus to. Use
* <code>null</code> or {@link Uuid(0)|Uuid.NULL} to unset keyboard focus from an overlay.
* @param {Uuid} id - The ID of the <code>{@link Overlays.OverlayProperties-Web3D|"web3d"}</code> overlay
* ({@link Entities.EntityProperties-Web|Web} entity) to set keyboard focus to. Use <code>null</code> or
* {@link Uuid(0)|Uuid.NULL} to unset keyboard focus from an overlay (entity).
*/
void setKeyboardFocusOverlay(const QUuid& id) { DependencyManager::get<EntityScriptingInterface>()->setKeyboardFocusEntity(id); }
signals:
/**jsdoc
* Triggered when an overlay is deleted.
* Triggered when an overlay (or entity) is deleted.
* @function Overlays.overlayDeleted
* @param {Uuid} id - The ID of the overlay that was deleted.
* @param {Uuid} id - The ID of the overlay (or entity) that was deleted.
* @returns {Signal}
* @example <caption>Create an overlay then delete it after 1s.</caption>
* var overlay = Overlays.addOverlay("cube", {

View file

@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.0)
set(ENV{MACOSX_DEPLOYMENT_TARGET} 10.9)
project(HQLauncher)
set (CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/")
include("cmake/macros/SetPackagingParameters.cmake")
set(src_files
src/Launcher.h
src/Launcher.m
@ -59,15 +60,17 @@ set_target_properties(${this_target} PROPERTIES
set(MACOSX_BUNDLE_ICON_FILE "interface.icns")
function(set_from_env _RESULT_NAME _ENV_VAR_NAME _DEFAULT_VALUE)
if (NOT DEFINED ${_RESULT_NAME})
if ("$ENV{${_ENV_VAR_NAME}}" STREQUAL "")
set (${_RESULT_NAME} ${_DEFAULT_VALUE} PARENT_SCOPE)
else()
set (${_RESULT_NAME} $ENV{${_ENV_VAR_NAME}} PARENT_SCOPE)
endif()
if (NOT DEFINED ${_RESULT_NAME})
if ("$ENV{${_ENV_VAR_NAME}}" STREQUAL "")
set (${_RESULT_NAME} ${_DEFAULT_VALUE} PARENT_SCOPE)
else()
set (${_RESULT_NAME} $ENV{${_ENV_VAR_NAME}} PARENT_SCOPE)
endif()
endif()
endfunction()
set_packaging_parameters()
add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${src_files})
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${APP_NAME}
MACOSX_BUNDLE_BUNDLE_NAME ${APP_NAME})
@ -77,6 +80,7 @@ if ("${LAUNCHER_HMAC_SECRET}" STREQUAL "")
endif()
target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_HMAC_SECRET="${LAUNCHER_HMAC_SECRET}")
target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_BUILD_VERSION="${BUILD_VERSION}")
file(GLOB NIB_FILES "nib/*.xib")

View file

@ -0,0 +1,45 @@
#
# SetPackagingParameters.cmake
# cmake/macros
#
# Created by Leonardo Murillo on 07/14/2015.
# Copyright 2015 High Fidelity, Inc.
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
# This macro checks some Jenkins defined environment variables to determine the origin of this build
# and decides how targets should be packaged.
macro(SET_PACKAGING_PARAMETERS)
set(PR_BUILD 0)
set(PRODUCTION_BUILD 0)
set(DEV_BUILD 0)
set(BUILD_NUMBER 0)
set_from_env(RELEASE_TYPE RELEASE_TYPE "DEV")
set_from_env(RELEASE_NUMBER RELEASE_NUMBER "")
set_from_env(STABLE_BUILD STABLE_BUILD 0)
message(STATUS "The RELEASE_TYPE variable is: ${RELEASE_TYPE}")
set(BUILD_NUMBER ${RELEASE_NUMBER})
if (RELEASE_TYPE STREQUAL "PRODUCTION")
set(PRODUCTION_BUILD 1)
set(BUILD_VERSION ${RELEASE_NUMBER})
# add definition for this release type
add_definitions(-DPRODUCTION_BUILD)
elseif (RELEASE_TYPE STREQUAL "PR")
set(PR_BUILD 1)
set(BUILD_VERSION "PR${RELEASE_NUMBER}")
# add definition for this release type
add_definitions(-DPR_BUILD)
else ()
set(DEV_BUILD 1)
set(BUILD_VERSION "dev")
endif ()
endmacro(SET_PACKAGING_PARAMETERS)

View file

@ -102,12 +102,22 @@
<action selector="termsOfService:" target="YVh-OH-vU8" id="bgc-08-8Lj"/>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="oJs-u5-OOJ">
<rect key="frame" x="380" y="0.0" width="130" height="14"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Label" id="H6o-Xs-wK1">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="systemGrayColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<point key="canvasLocation" x="138.5" y="154"/>
</customView>
<customObject id="YVh-OH-vU8" customClass="DisplayNameScreen">
<connections>
<outlet property="backgroundImage" destination="aus-lo-eVi" id="SRc-pV-lXG"/>
<outlet property="buildVersion" destination="oJs-u5-OOJ" id="avj-j2-5P6"/>
<outlet property="displayName" destination="Vhg-rq-xUH" id="Fb5-im-2hx"/>
<outlet property="smallLogo" destination="j8K-TD-h7e" id="OVd-p3-nu6"/>
</connections>

View file

@ -69,12 +69,22 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="CBD-Vk-Xd4"/>
</imageView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Omk-5j-C6r">
<rect key="frame" x="381" y="0.0" width="130" height="14"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Label" id="xpG-oP-agI">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="systemGrayColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<point key="canvasLocation" x="138.5" y="152"/>
</customView>
<customObject id="nWn-x7-LxT" customClass="ErrorViewController">
<connections>
<outlet property="backgroundImage" destination="eih-a8-Pqa" id="2xh-8r-1Qu"/>
<outlet property="buildVersion" destination="Omk-5j-C6r" id="aTp-c3-RVy"/>
<outlet property="smallLogo" destination="ugn-Hk-gWL" id="EVI-d3-mf5"/>
<outlet property="voxelImage" destination="jmW-5q-Mf3" id="NiI-cY-tAf"/>
</connections>

View file

@ -119,12 +119,22 @@
<action selector="havingTrouble:" target="NkF-nk-81S" id="tsf-tC-aqq"/>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="EsT-hn-six">
<rect key="frame" x="380" y="0.0" width="130" height="14"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Label" id="AGt-jH-mX2">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="systemGrayColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<point key="canvasLocation" x="138.5" y="154"/>
</customView>
<customObject id="NkF-nk-81S" customClass="LoginScreen">
<connections>
<outlet property="backgroundImage" destination="L56-Jv-0N8" id="INT-rB-YtG"/>
<outlet property="buildVersion" destination="EsT-hn-six" id="Z4k-Gv-U5g"/>
<outlet property="button" destination="jKE-fV-Tjv" id="or6-tG-r6R"/>
<outlet property="header" destination="hIC-qf-Abj" id="sVQ-rl-cvR"/>
<outlet property="orginization" destination="L7W-3n-OKy" id="TiL-wn-Z2b"/>

View file

@ -53,6 +53,15 @@
<rect key="frame" x="68" y="78" width="394" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</progressIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="U3I-TA-xRz">
<rect key="frame" x="380" y="0.0" width="130" height="14"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Label" id="8YH-Td-daK">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="systemGrayColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<point key="canvasLocation" x="138.5" y="154"/>
</customView>
@ -60,6 +69,7 @@
<connections>
<outlet property="background" destination="kuY-e2-Hqb" id="CBc-bD-ux7"/>
<outlet property="boldStatus" destination="EMF-E4-qLL" id="udm-8B-7lt"/>
<outlet property="buildVersion" destination="U3I-TA-xRz" id="HDI-tW-1cC"/>
<outlet property="progressView" destination="aEr-fi-fkV" id="OUy-Qp-tiP"/>
<outlet property="smallLogo" destination="uh2-4K-n56" id="pYg-hP-nr5"/>
<outlet property="smallStatus" destination="BSg-lp-njL" id="ziz-ek-Lq4"/>

View file

@ -26,11 +26,21 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="qC6-tI-Uwf"/>
</imageView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="EgF-VK-Hfo">
<rect key="frame" x="380" y="0.0" width="130" height="14"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Label" id="gWS-UL-cjB">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="systemGrayColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="systemGrayColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<point key="canvasLocation" x="119.5" y="134"/>
</customView>
<customObject id="iJ0-FI-XIf" customClass="SplashScreen">
<connections>
<outlet property="buildVersion" destination="EgF-VK-Hfo" id="lVp-Ua-9Mt"/>
<outlet property="imageView" destination="qtD-mb-qqq" id="rCt-Gd-Uux"/>
<outlet property="logoImage" destination="2i5-Zw-nH7" id="7tM-sX-cvR"/>
</connections>

View file

@ -6,6 +6,7 @@
@property (nonatomic, assign) IBOutlet NSImageView* backgroundImage;
@property (nonatomic, assign) IBOutlet NSImageView* smallLogo;
@property (nonatomic, assign) IBOutlet NSTextField* displayName;
@property (nonatomic, assign) IBOutlet NSTextField* buildVersion;
@end
@implementation DisplayNameScreen
@ -13,10 +14,9 @@
[self.backgroundImage setImage: [NSImage imageNamed:hifiBackgroundFilename]];
[self.smallLogo setImage: [NSImage imageNamed:hifiSmallLogoFilename]];
NSMutableAttributedString* displayNameString = [[NSMutableAttributedString alloc] initWithString:@"Display Name"];
[self.buildVersion setStringValue: [@"V." stringByAppendingString:@LAUNCHER_BUILD_VERSION]];
[displayNameString addAttribute:NSForegroundColorAttributeName value:[NSColor grayColor] range:NSMakeRange(0, displayNameString.length)];
[displayNameString addAttribute:NSFontAttributeName value:[NSFont systemFontOfSize:18] range:NSMakeRange(0,displayNameString.length)];
[self.displayName setPlaceholderAttributedString:displayNameString];
[self.displayName setTarget:self];
[self.displayName setAction:@selector(login:)];

View file

@ -6,6 +6,7 @@
@property (nonatomic, assign) IBOutlet NSImageView* backgroundImage;
@property (nonatomic, assign) IBOutlet NSImageView* smallLogo;
@property (nonatomic, assign) IBOutlet NSImageView* voxelImage;
@property (nonatomic, assign) IBOutlet NSTextField* buildVersion;
@end
@ -16,6 +17,7 @@
[self.backgroundImage setImage:[NSImage imageNamed:hifiBackgroundFilename]];
[self.smallLogo setImage:[NSImage imageNamed:hifiSmallLogoFilename]];
[self.voxelImage setImage:[NSImage imageNamed:hifiVoxelFilename]];
[self.buildVersion setStringValue: [@"V." stringByAppendingString:@LAUNCHER_BUILD_VERSION]];
}
-(IBAction)resartLauncher:(id)sender

View file

@ -12,6 +12,7 @@
@property (nonatomic, assign) IBOutlet NSTextField* smallHeader;
@property (nonatomic, assign) IBOutlet NSTextField* trouble;
@property (nonatomic, assign) IBOutlet NSButton* button;
@property (nonatomic, assign) IBOutlet NSTextField* buildVersion;
@end
@implementation LoginScreen
@ -36,6 +37,7 @@
[self.button setTitle:@"TRY AGAIN"];
}
[self.buildVersion setStringValue: [@"V." stringByAppendingString:@LAUNCHER_BUILD_VERSION]];
[self.backgroundImage setImage:[NSImage imageNamed:hifiBackgroundFilename]];
[self.smallLogo setImage:[NSImage imageNamed:hifiSmallLogoFilename]];

View file

@ -9,6 +9,7 @@
@property (nonatomic, assign) IBOutlet NSTextField* boldStatus;
@property (nonatomic, assign) IBOutlet NSTextField* smallStatus;
@property (nonatomic, assign) IBOutlet NSProgressIndicator* progressView;
@property (nonatomic, assign) IBOutlet NSTextField* buildVersion;
@end
@implementation ProcessScreen
@ -37,6 +38,7 @@
default:
break;
}
[self.buildVersion setStringValue: [@"V." stringByAppendingString:@LAUNCHER_BUILD_VERSION]];
[self.background setImage: [NSImage imageNamed:hifiBackgroundFilename]];
[self.smallLogo setImage: [NSImage imageNamed:hifiSmallLogoFilename]];
[self.voxelImage setImage: [NSImage imageNamed:hifiVoxelFilename]];

View file

@ -6,6 +6,7 @@
@property (nonatomic, assign) IBOutlet NSImageView* imageView;
@property (nonatomic, assign) IBOutlet NSImageView* logoImage;
@property (nonatomic, assign) IBOutlet NSButton* button;
@property (nonatomic, assign) IBOutlet NSTextField* buildVersion;
@end
@implementation SplashScreen
@ -15,5 +16,6 @@
-(void)awakeFromNib {
[self.imageView setImage:[NSImage imageNamed:hifiBackgroundFilename]];
[self.logoImage setImage:[NSImage imageNamed:hifiLargeLogoFilename]];
[self.buildVersion setStringValue: [@"V." stringByAppendingString:@LAUNCHER_BUILD_VERSION]];
}
@end

View file

@ -10,6 +10,7 @@ set(CMAKE_MFC_FLAG 1)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
include("cmake/macros/SetPackagingParameters.cmake")
add_executable(HQLauncher
WIN32
@ -49,6 +50,8 @@ function(set_from_env _RESULT_NAME _ENV_VAR_NAME _DEFAULT_VALUE)
endif()
endfunction()
set_packaging_parameters()
set_from_env(LAUNCHER_HMAC_SECRET LAUNCHER_HMAC_SECRET "")
if (LAUNCHER_HMAC_SECRET STREQUAL "")
@ -56,7 +59,7 @@ if (LAUNCHER_HMAC_SECRET STREQUAL "")
endif()
target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_HMAC_SECRET="${LAUNCHER_HMAC_SECRET}")
target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_BUILD_VERSION="${BUILD_VERSION}")
# Preprocessor definitions
target_compile_definitions(HQLauncher PRIVATE

View file

@ -92,8 +92,8 @@ STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION
EXSTYLE WS_EX_APPWINDOW
FONT 10, "MS Shell Dlg", 400, 0, 0x0
BEGIN
CONTROL "",IDC_VOXEL,"Static",SS_BLACKRECT,65,3,174,123, NOT WS_VISIBLE
CONTROL "", IDC_PROGRESS, "Static", SS_BLACKRECT, 35, 170, 239, 4, NOT WS_VISIBLE
CONTROL "",IDC_VOXEL,"Static",SS_BLACKRECT,65,3,174,123
CONTROL "",IDC_PROGRESS,"Static",SS_BLACKRECT,35,170,239,4
EDITTEXT IDC_ORGNAME,44,68,219,12,ES_AUTOHSCROLL | NOT WS_VISIBLE | NOT WS_BORDER
EDITTEXT IDC_USERNAME,44,95,219,12,ES_AUTOHSCROLL | NOT WS_VISIBLE | NOT WS_BORDER
EDITTEXT IDC_PASSWORD,44,122,219,12,ES_PASSWORD | ES_AUTOHSCROLL | NOT WS_VISIBLE | NOT WS_BORDER
@ -107,6 +107,7 @@ BEGIN
RTEXT "",IDC_TERMS,15,172,180,15,NOT WS_VISIBLE
CONTROL "",IDC_TERMS_LINK,"Button",BS_OWNERDRAW | BS_FLAT | NOT WS_VISIBLE | WS_TABSTOP,197,172,80,15
CTEXT "",IDC_TROUBLE,65,203,174,15,NOT WS_VISIBLE
RTEXT "",IDC_VERSION,100,205,205,10
CONTROL "NEXT",IDC_BUTTON_NEXT,"Button",BS_OWNERDRAW | BS_FLAT | NOT WS_VISIBLE | WS_TABSTOP,107,158,94,16
CONTROL "Having Trouble?",IDC_TROUBLE_LINK,"Button",BS_OWNERDRAW | BS_FLAT | NOT WS_VISIBLE | WS_TABSTOP,126,203,56,11
END

View file

@ -39,16 +39,28 @@ BOOL CLauncherApp::InitInstance() {
}
int iNumOfArgs;
LPWSTR* pArgs = CommandLineToArgvW(GetCommandLine(), &iNumOfArgs);
bool isUninstalling = false;
bool isRestarting = false;
bool uninstalling = false;
bool restarting = false;
bool noUpdate = false;
bool continueUpdating = false;
bool skipSplash = false;
if (iNumOfArgs > 1) {
if (CString(pArgs[1]).Compare(_T("--uninstall")) == 0) {
isUninstalling = true;
} else if (CString(pArgs[1]).Compare(_T("--restart")) == 0) {
isRestarting = true;
for (int i = 1; i < iNumOfArgs; i++) {
CString curArg = CString(pArgs[i]);
if (curArg.Compare(_T("--uninstall")) == 0) {
uninstalling = true;
} else if (curArg.Compare(_T("--restart")) == 0) {
restarting = true;
} else if (curArg.Compare(_T("--noUpdate")) == 0) {
noUpdate = true;
} else if (curArg.Compare(_T("--continueUpdating")) == 0) {
continueUpdating = true;
} else if (curArg.Compare(_T("--skipSplash")) == 0) {
skipSplash = true;
}
}
}
if (!isRestarting) {
if (!restarting) {
// don't launch if already running
CreateMutex(NULL, TRUE, _T("HQ_Launcher_Mutex"));
if (GetLastError() == ERROR_ALREADY_EXISTS) {
@ -56,10 +68,10 @@ BOOL CLauncherApp::InitInstance() {
}
}
if (isUninstalling) {
if (uninstalling) {
_manager.uninstall();
} else {
_manager.init();
_manager.init(!noUpdate, continueUpdating, skipSplash);
}
if (!_manager.hasFailed() && !_manager.installLauncher()) {
return FALSE;

View file

@ -40,6 +40,8 @@ static CString GRAPHIK_SEMIBOLD = _T("Graphik-Semibold");
static CString TROUBLE_URL = _T("https://www.highfidelity.com/hq-support");
static CString TERMS_URL = _T("https://www.highfidelity.com/termsofservice");
static int SPLASH_DURATION = 100;
CLauncherDlg::CLauncherDlg(CWnd* pParent)
: CDialog(IDD_LAUNCHER_DIALOG, pParent)
@ -112,6 +114,11 @@ BOOL CLauncherDlg::OnInitDialog() {
m_voxel = (CStatic *)GetDlgItem(IDC_VOXEL);
m_progress = (CStatic *)GetDlgItem(IDC_PROGRESS);
m_version = (CStatic *)GetDlgItem(IDC_VERSION);
CString version;
version.Format(_T("V.%s"), theApp._manager.getLauncherVersion());
m_version->SetWindowTextW(version);
m_voxel->EnableD2DSupport();
m_progress->EnableD2DSupport();
@ -230,7 +237,6 @@ void CLauncherDlg::startProcess() {
theApp._manager.setFailed(true);
}
});
}
BOOL CLauncherDlg::getHQInfo(const CString& orgname) {
@ -322,11 +328,12 @@ void CLauncherDlg::drawLogo(CHwndRenderTarget* pRenderTarget) {
void CLauncherDlg::drawSmallLogo(CHwndRenderTarget* pRenderTarget) {
CD2DBitmap m_pBitmamLogo(pRenderTarget, IDB_PNG5, _T("PNG"));
auto size = pRenderTarget->GetSize();
int padding = 6;
int xPadding = 6;
int yPadding = 22;
int logoWidth = 100;
int logoHeight = 18;
float logoPosX = size.width - logoWidth - padding;
float logoPosY = size.height - logoHeight - padding;
float logoPosX = size.width - logoWidth - xPadding;
float logoPosY = size.height - logoHeight - yPadding;
CD2DRectF logoRec(logoPosX, logoPosY, logoPosX + logoWidth, logoPosY + logoHeight);
pRenderTarget->DrawBitmap(&m_pBitmamLogo, logoRec);
}
@ -521,6 +528,7 @@ BOOL CLauncherDlg::getTextFormat(int resID, TextFormat& formatOut) {
formatOut.size = FIELDS_FONT_SIZE;
formatOut.color = COLOR_GREY;
break;
case IDC_VERSION:
case IDC_TERMS:
formatOut.size = TERMS_FONT_SIZE;
break;
@ -663,16 +671,46 @@ void CLauncherDlg::OnTimer(UINT_PTR nIDEvent) {
// Refresh
setDrawDialog(_drawStep, true);
}
if (theApp._manager.needsSelfUpdate()) {
if (theApp._manager.needsSelfDownload()) {
theApp._manager.downloadNewLauncher();
} else {
if (_splashStep > SPLASH_DURATION && _splashStep < 2 * SPLASH_DURATION) {
float progress = (float)(_splashStep - SPLASH_DURATION) / SPLASH_DURATION;
if (theApp._manager.willContinueUpdating()) {
progress = CONTINUE_UPDATING_GLOBAL_OFFSET * progress;
progress = min(progress, CONTINUE_UPDATING_GLOBAL_OFFSET);
}
theApp._manager.updateProgress(LauncherManager::ProcessType::Uninstall, progress);
_splashStep++;
}
if (theApp._manager.needsRestartNewLauncher()) {
if (_splashStep >= 2 * SPLASH_DURATION) {
theApp._manager.restartNewLauncher();
exit(0);
}
}
}
}
if (_showSplash) {
if (_splashStep == 0) {
if (theApp._manager.needsUninstall()) {
theApp._manager.addToLog(_T("Waiting to uninstall"));
setDrawDialog(DrawStep::DrawProcessUninstall);
} else if (theApp._manager.shouldContinueUpdating()) {
_splashStep = SPLASH_DURATION;
setDrawDialog(DrawStep::DrawProcessUpdate);
theApp._manager.updateProgress(LauncherManager::ProcessType::Uninstall, 0.0f);
} else {
theApp._manager.addToLog(_T("Start splash screen"));
setDrawDialog(DrawStep::DrawLogo);
if (theApp._manager.shouldSkipSplashScreen()) {
_splashStep = SPLASH_DURATION;
} else {
theApp._manager.addToLog(_T("Start splash screen"));
setDrawDialog(DrawStep::DrawLogo);
}
}
} else if (_splashStep > 100) {
} else if (_splashStep > SPLASH_DURATION && !theApp._manager.needsToWait()) {
_showSplash = false;
if (theApp._manager.shouldShutDown()) {
if (_applicationWND != NULL) {
@ -692,12 +730,14 @@ void CLauncherDlg::OnTimer(UINT_PTR nIDEvent) {
theApp._manager.addToLog(_T("HQ failed to uninstall."));
theApp._manager.setFailed(true);
}
} else if (theApp._manager.needsSelfUpdate()) {
setDrawDialog(DrawStep::DrawProcessUpdate);
} else {
theApp._manager.addToLog(_T("Starting login"));
setDrawDialog(DrawStep::DrawLoginLogin);
}
} else if (theApp._manager.needsUninstall()) {
theApp._manager.updateProgress(LauncherManager::ProcessType::Uninstall, (float)_splashStep/100);
theApp._manager.updateProgress(LauncherManager::ProcessType::Uninstall, (float)_splashStep / SPLASH_DURATION);
}
_splashStep++;
} else if (theApp._manager.shouldShutDown()) {
@ -741,12 +781,17 @@ void CLauncherDlg::setDrawDialog(DrawStep step, BOOL isUpdate) {
auto m_voxelRenderTarget = m_voxel->GetRenderTarget();
auto m_progressRenderTarget = m_progress->GetRenderTarget();
switch (_drawStep) {
case DrawStep::DrawLogo:
case DrawStep::DrawLogo: {
m_pRenderTarget->BeginDraw();
drawBackground(m_pRenderTarget);
drawLogo(m_pRenderTarget);
m_pRenderTarget->EndDraw();
CRect redrawRec;
GetClientRect(redrawRec);
redrawRec.top = redrawRec.bottom - 30;
RedrawWindow(redrawRec);
break;
}
case DrawStep::DrawLoginLogin:
case DrawStep::DrawLoginErrorOrg:
case DrawStep::DrawLoginErrorCred:

View file

@ -94,6 +94,8 @@ protected:
CStatic* m_username_banner;
CStatic* m_password_banner;
CStatic* m_version;
HWND _applicationWND { 0 };
void drawBackground(CHwndRenderTarget* pRenderTarget);

View file

@ -21,38 +21,20 @@ LauncherManager::LauncherManager() {
LauncherManager::~LauncherManager() {
}
void LauncherManager::init() {
void LauncherManager::init(BOOL allowUpdate, BOOL continueUpdating, BOOL skipSplashScreen) {
initLog();
addToLog(_T("Getting most recent build"));
CString response;
LauncherUtils::ResponseError error = getMostRecentBuild(_latestApplicationURL, _latestVersion, response);
if (error == LauncherUtils::ResponseError::NoError) {
addToLog(_T("Latest version: ") + _latestVersion);
CString currentVersion;
if (isApplicationInstalled(currentVersion, _domainURL, _contentURL, _loggedIn) && _loggedIn) {
addToLog(_T("Installed version: ") + currentVersion);
if (_latestVersion.Compare(currentVersion) == 0) {
addToLog(_T("Already running most recent build. Launching interface.exe"));
_shouldLaunch = TRUE;
_shouldShutdown = TRUE;
} else {
addToLog(_T("New build found. Updating"));
_shouldUpdate = TRUE;
}
} else if (_loggedIn) {
addToLog(_T("Interface not found but logged in. Reinstalling"));
_shouldUpdate = TRUE;
} else {
_shouldInstall = TRUE;
}
} else {
_hasFailed = true;
CString msg;
msg.Format(_T("Getting most recent build has failed with error: %d"), error);
addToLog(msg);
msg.Format(_T("Response: %s"), response);
addToLog(msg);
int tokenPos = 0;
_updateLauncherAllowed = allowUpdate;
_continueUpdating = continueUpdating;
_skipSplashScreen = skipSplashScreen;
_shouldWait = !skipSplashScreen;
if (_continueUpdating) {
_progressOffset = CONTINUE_UPDATING_GLOBAL_OFFSET;
}
_launcherVersion = CString(LAUNCHER_BUILD_VERSION).Tokenize(_T("-"), tokenPos);
addToLog(_T("Launcher is running version: " + _launcherVersion));
addToLog(_T("Getting most recent builds"));
getMostRecentBuilds(_latestLauncherURL, _latestLauncherVersion, _latestApplicationURL, _latestVersion);
}
BOOL LauncherManager::initLog() {
@ -152,6 +134,8 @@ BOOL LauncherManager::restartLauncher() {
void LauncherManager::updateProgress(ProcessType processType, float progress) {
switch (processType) {
case ProcessType::DownloadLauncher:
break;
case ProcessType::Uninstall:
_progress = progress;
break;
@ -181,6 +165,7 @@ void LauncherManager::updateProgress(ProcessType processType, float progress) {
default:
break;
}
_progress = _progressOffset + (1.0f - _progressOffset) * _progress;
TRACE("progress = %f\n", _progress);
}
@ -228,11 +213,11 @@ BOOL LauncherManager::isApplicationInstalled(CString& version, CString& domain,
CString applicationDir;
getAndCreatePaths(PathType::Launcher_Directory, applicationDir);
CString applicationPath = applicationDir + "interface\\interface.exe";
BOOL isApplicationInstalled = PathFileExistsW(applicationPath);
BOOL isInstalled = PathFileExistsW(applicationPath);
BOOL configFileExist = PathFileExistsW(applicationDir + _T("interface\\config.json"));
if (configFileExist) {
LauncherUtils::ResponseError status = readConfigJSON(version, domain, content, loggedIn);
return isApplicationInstalled && status == LauncherUtils::ResponseError::NoError;
return isInstalled && status == LauncherUtils::ResponseError::NoError;
}
return FALSE;
}
@ -387,39 +372,100 @@ LauncherUtils::ResponseError LauncherManager::readOrganizationJSON(const CString
return LauncherUtils::ResponseError::ParsingJSON;
}
LauncherUtils::ResponseError LauncherManager::getMostRecentBuild(CString& urlOut, CString& versionOut,
CString& response) {
void LauncherManager::getMostRecentBuilds(CString& launcherUrlOut, CString& launcherVersionOut,
CString& interfaceUrlOut, CString& interfaceVersionOut) {
CString contentTypeJson = L"content-type:application/json";
LauncherUtils::ResponseError error = LauncherUtils::makeHTTPCall(L"HQ Launcher",
L"thunder.highfidelity.com",
L"/builds/api/tags/latest?format=json",
contentTypeJson, CStringA(),
response, false);
if (error != LauncherUtils::ResponseError::NoError) {
return error;
}
Json::Value json;
if (LauncherUtils::parseJSON(response, json)) {
int count = json["count"].isInt() ? json["count"].asInt() : 0;
if (count > 0 && json["results"].isArray()) {
for (int i = 0; i < count; i++) {
if (json["results"][i].isObject()) {
Json::Value result = json["results"][i];
if (result["latest_version"].isInt()) {
std::string version = std::to_string(result["latest_version"].asInt());
versionOut = CString(version.c_str());
std::function<void(CString, int)> httpCallback = [&](CString response, int err) {
LauncherUtils::ResponseError error = LauncherUtils::ResponseError(err);
if (error == LauncherUtils::ResponseError::NoError) {
Json::Value json;
if (LauncherUtils::parseJSON(response, json)) {
if (json["launcher"].isObject()) {
if (json["launcher"]["windows"].isObject() && json["launcher"]["windows"]["url"].isString()) {
launcherUrlOut = json["launcher"]["windows"]["url"].asCString();
}
if (result["installers"].isObject() &&
result["installers"]["windows"].isObject() &&
result["installers"]["windows"]["zip_url"].isString()) {
urlOut = result["installers"]["windows"]["zip_url"].asCString();
return LauncherUtils::ResponseError::NoError;
if (json["launcher"]["version"].isInt()) {
std::string version = std::to_string(json["launcher"]["version"].asInt());
launcherVersionOut = CString(version.c_str());
}
}
int count = json["count"].isInt() ? json["count"].asInt() : 0;
if (count > 0 && json["results"].isArray()) {
for (int i = 0; i < count; i++) {
if (json["results"][i].isObject()) {
Json::Value result = json["results"][i];
if (result["latest_version"].isInt()) {
std::string version = std::to_string(result["latest_version"].asInt());
interfaceVersionOut = CString(version.c_str());
}
if (result["installers"].isObject() &&
result["installers"]["windows"].isObject() &&
result["installers"]["windows"]["zip_url"].isString()) {
interfaceUrlOut = result["installers"]["windows"]["zip_url"].asCString();
}
}
}
}
if (launcherUrlOut.IsEmpty() || launcherVersionOut.IsEmpty() || interfaceUrlOut.IsEmpty() || interfaceVersionOut.IsEmpty()) {
error = LauncherUtils::ResponseError::ParsingJSON;
}
}
onMostRecentBuildsReceived(response, error);
}
};
LauncherUtils::httpCallOnThread(L"HQ Launcher",
L"thunder.highfidelity.com",
L"/builds/api/tags/latest?format=json",
contentTypeJson, CStringA(), false, httpCallback);
}
void LauncherManager::onMostRecentBuildsReceived(const CString& response, LauncherUtils::ResponseError error) {
if (error == LauncherUtils::ResponseError::NoError) {
addToLog(_T("Latest launcher version: ") + _latestLauncherVersion);
CString currentVersion;
BOOL isInstalled = (isApplicationInstalled(currentVersion, _domainURL, _contentURL, _loggedIn) && _loggedIn);
bool newInterfaceVersion = _latestVersion.Compare(currentVersion) != 0;
bool newLauncherVersion = _latestLauncherVersion.Compare(_launcherVersion) != 0 && _updateLauncherAllowed;
if (newLauncherVersion) {
CString updatingMsg;
updatingMsg.Format(_T("Updating Launcher from version: %s to version: %s"), _launcherVersion, _latestLauncherVersion);
addToLog(updatingMsg);
_shouldUpdateLauncher = TRUE;
_shouldDownloadLauncher = TRUE;
_willContinueUpdating = isInstalled && newInterfaceVersion;
} else {
if (_updateLauncherAllowed) {
addToLog(_T("Already running most recent build. Launching interface.exe"));
} else {
addToLog(_T("Updating the launcher was not allowed --noUpdate"));
}
if (isInstalled) {
addToLog(_T("Installed version: ") + currentVersion);
if (!newInterfaceVersion) {
addToLog(_T("Already running most recent build. Launching interface.exe"));
_shouldLaunch = TRUE;
_shouldShutdown = TRUE;
} else {
addToLog(_T("New build found. Updating"));
_shouldUpdate = TRUE;
}
} else if (_loggedIn) {
addToLog(_T("Interface not found but logged in. Reinstalling"));
_shouldUpdate = TRUE;
} else {
_shouldInstall = TRUE;
}
}
_shouldWait = FALSE;
} else {
_hasFailed = true;
CString msg;
msg.Format(_T("Getting most recent builds has failed with error: %d"), error);
addToLog(msg);
msg.Format(_T("Response: %s"), response);
addToLog(msg);
}
return LauncherUtils::ResponseError::ParsingJSON;
}
LauncherUtils::ResponseError LauncherManager::getAccessTokenForCredentials(const CString& username,
@ -517,7 +563,7 @@ BOOL LauncherManager::extractApplication() {
}
};
std::function<void(float)> onProgress = [&](float progress) {
updateProgress(ProcessType::UnzipApplication, progress);
updateProgress(ProcessType::UnzipApplication, max(progress, 0.0f));
};
_currentProcess = ProcessType::UnzipApplication;
BOOL success = LauncherUtils::unzipFileOnThread(ProcessType::UnzipApplication,
@ -559,9 +605,21 @@ void LauncherManager::onFileDownloaded(ProcessType type) {
setFailed(true);
}
});
} else if (type == ProcessType::DownloadLauncher) {
_shouldRestartNewLauncher = true;
}
}
void LauncherManager::restartNewLauncher() {
closeLog();
if (_willContinueUpdating) {
LauncherUtils::launchApplication(_tempLauncherPath, _T(" --restart --noUpdate --continueUpdating"));
} else {
LauncherUtils::launchApplication(_tempLauncherPath, _T(" --restart --noUpdate --skipSplash"));
}
Sleep(500);
}
BOOL LauncherManager::installContent() {
std::string contentZipFile = LauncherUtils::cStringToStd(_contentZipPath);
@ -578,7 +636,7 @@ BOOL LauncherManager::installContent() {
}
};
std::function<void(float)> onProgress = [&](float progress) {
updateProgress(ProcessType::UnzipContent, progress);
updateProgress(ProcessType::UnzipContent, max(progress, 0.0f));
};
_currentProcess = ProcessType::UnzipContent;
BOOL success = LauncherUtils::unzipFileOnThread(ProcessType::UnzipContent, contentZipFile,
@ -593,33 +651,34 @@ BOOL LauncherManager::installContent() {
BOOL LauncherManager::downloadFile(ProcessType type, const CString& url, CString& outPath) {
CString fileName = url.Mid(url.ReverseFind('/') + 1);
CString downloadDirectory;
BOOL success = getAndCreatePaths(LauncherManager::PathType::Download_Directory, downloadDirectory);
outPath = downloadDirectory + fileName;
BOOL success = TRUE;
if (outPath.IsEmpty()) {
CString fileName = url.Mid(url.ReverseFind('/') + 1);
CString downloadDirectory;
BOOL success = getAndCreatePaths(LauncherManager::PathType::Download_Directory, downloadDirectory);
outPath = downloadDirectory + fileName;
}
_currentProcess = type;
if (success) {
addToLog(_T("Downloading: ") + url);
std::function<void(int, bool)> onDownloadFinished = [&](int type, bool error) {
if (!error) {
onFileDownloaded((ProcessType)type);
}
else {
} else {
if (type == ProcessType::DownloadApplication) {
addToLog(_T("Error downloading content."));
}
else {
} else if (type == ProcessType::DownloadLauncher) {
addToLog(_T("Error downloading launcher."));
} else {
addToLog(_T("Error downloading application."));
}
_hasFailed = true;
}
};
std::function<void(float)> onProgress = [&](float progress) {
updateProgress(_currentProcess, progress);
std::function<void(float)> onProgress = [&, type](float progress) {
updateProgress(_currentProcess, max(progress, 0.0f));
};
if (!LauncherUtils::downloadFileOnThread(type, url, outPath, onDownloadFinished, onProgress)) {
success = FALSE;
}
success = LauncherUtils::downloadFileOnThread(type, url, outPath, onDownloadFinished, onProgress);
}
return success;
}
@ -635,6 +694,14 @@ BOOL LauncherManager::downloadApplication() {
return downloadFile(ProcessType::DownloadApplication, applicationURL, _applicationZipPath);
}
BOOL LauncherManager::downloadNewLauncher() {
_shouldDownloadLauncher = FALSE;
getAndCreatePaths(PathType::Temp_Directory, _tempLauncherPath);
CString tempName = _T("HQLauncher") + _launcherVersion + _T(".exe");
_tempLauncherPath += tempName;
return downloadFile(ProcessType::DownloadLauncher, _latestLauncherURL, _tempLauncherPath);
}
void LauncherManager::onCancel() {
if (_currentProcess == ProcessType::UnzipApplication) {
_latestVersion = _T("");

View file

@ -25,6 +25,7 @@ const float DOWNLOAD_APPLICATION_INSTALL_WEIGHT = 0.5f;
const float EXTRACT_APPLICATION_INSTALL_WEIGHT = 0.2f;
const float DOWNLOAD_APPLICATION_UPDATE_WEIGHT = 0.75f;
const float EXTRACT_APPLICATION_UPDATE_WEIGHT = 0.25f;
const float CONTINUE_UPDATING_GLOBAL_OFFSET = 0.2f;
class LauncherManager
{
@ -49,6 +50,7 @@ public:
ErrorIOFiles
};
enum ProcessType {
DownloadLauncher = 0,
DownloadContent,
DownloadApplication,
UnzipContent,
@ -57,7 +59,7 @@ public:
};
LauncherManager();
~LauncherManager();
void init();
void init(BOOL allowUpdate, BOOL continueUpdating, BOOL skipSplashScreen);
BOOL initLog();
BOOL addToLog(const CString& line);
void closeLog();
@ -67,7 +69,8 @@ public:
BOOL isApplicationInstalled(CString& version, CString& domain,
CString& content, bool& loggedIn);
LauncherUtils::ResponseError getAccessTokenForCredentials(const CString& username, const CString& password);
LauncherUtils::ResponseError getMostRecentBuild(CString& urlOut, CString& versionOut, CString& response);
void getMostRecentBuilds(CString& launcherUrlOut, CString& launcherVersionOut,
CString& interfaceUrlOut, CString& interfaceVersionOut);
LauncherUtils::ResponseError readOrganizationJSON(const CString& hash);
LauncherUtils::ResponseError readConfigJSON(CString& version, CString& domain,
CString& content, bool& loggedIn);
@ -87,31 +90,44 @@ public:
const CString& getVersion() const { return _version; }
BOOL shouldShutDown() const { return _shouldShutdown; }
BOOL shouldLaunch() const { return _shouldLaunch; }
BOOL needsUpdate() { return _shouldUpdate; }
BOOL needsUninstall() { return _shouldUninstall; }
BOOL needsInstall() { return _shouldInstall; }
BOOL shouldSkipSplashScreen() const { return _skipSplashScreen; }
BOOL needsUpdate() const { return _shouldUpdate; }
BOOL needsSelfUpdate() const { return _shouldUpdateLauncher; }
BOOL needsSelfDownload() const { return _shouldDownloadLauncher; }
BOOL needsUninstall() const { return _shouldUninstall; }
BOOL needsInstall() const { return _shouldInstall; }
BOOL needsToWait() const { return _shouldWait; }
BOOL needsRestartNewLauncher() const { return _shouldRestartNewLauncher; }
BOOL shouldContinueUpdating() const { return _continueUpdating; }
BOOL willContinueUpdating() const { return _willContinueUpdating; }
void setDisplayName(const CString& displayName) { _displayName = displayName; }
bool isLoggedIn() { return _loggedIn; }
bool hasFailed() { return _hasFailed; }
bool isLoggedIn() const { return _loggedIn; }
bool hasFailed() const { return _hasFailed; }
void setFailed(bool hasFailed) { _hasFailed = hasFailed; }
const CString& getLatestInterfaceURL() const { return _latestApplicationURL; }
void uninstall() { _shouldUninstall = true; };
void uninstall() { _shouldUninstall = true; _shouldWait = false; };
BOOL downloadFile(ProcessType type, const CString& url, CString& localPath);
BOOL downloadContent();
BOOL downloadApplication();
BOOL downloadNewLauncher();
BOOL installContent();
BOOL extractApplication();
void restartNewLauncher();
void onZipExtracted(ProcessType type, int size);
void onFileDownloaded(ProcessType type);
float getProgress() { return _progress; }
float getProgress() const { return _progress; }
void updateProgress(ProcessType processType, float progress);
void onCancel();
const CString& getLauncherVersion() const { return _launcherVersion; }
private:
ProcessType _currentProcess { ProcessType::DownloadApplication };
void onMostRecentBuildsReceived(const CString& response, LauncherUtils::ResponseError error);
CString _latestApplicationURL;
CString _latestVersion;
CString _latestLauncherURL;
CString _latestLauncherVersion;
CString _contentURL;
CString _domainURL;
CString _version;
@ -119,6 +135,8 @@ private:
CString _tokensJSON;
CString _applicationZipPath;
CString _contentZipPath;
CString _launcherVersion;
CString _tempLauncherPath;
bool _loggedIn { false };
bool _hasFailed { false };
BOOL _shouldUpdate { FALSE };
@ -126,6 +144,15 @@ private:
BOOL _shouldInstall { FALSE };
BOOL _shouldShutdown { FALSE };
BOOL _shouldLaunch { FALSE };
BOOL _shouldWait { TRUE };
BOOL _shouldUpdateLauncher { FALSE };
BOOL _shouldDownloadLauncher { FALSE };
BOOL _updateLauncherAllowed { TRUE };
BOOL _shouldRestartNewLauncher { FALSE };
BOOL _continueUpdating { FALSE };
BOOL _willContinueUpdating { FALSE };
BOOL _skipSplashScreen { FALSE };
float _progressOffset { 0.0f };
float _progress { 0.0f };
CStdioFile _logFile;
};

View file

@ -470,9 +470,9 @@ BOOL LauncherUtils::hMac256(const CString& cmessage, const char* keystr, CString
DWORD WINAPI LauncherUtils::unzipThread(LPVOID lpParameter) {
UnzipThreadData& data = *((UnzipThreadData*)lpParameter);
uint64_t size = LauncherUtils::extractZip(data._zipFile, data._path, std::vector<std::string>(), data.progressCallback);
uint64_t size = LauncherUtils::extractZip(data._zipFile, data._path, std::vector<std::string>(), data._progressCallback);
int mb_size = (int)(size * 0.001f);
data.callback(data._type, mb_size);
data._callback(data._type, mb_size);
delete &data;
return 0;
}
@ -480,17 +480,26 @@ DWORD WINAPI LauncherUtils::unzipThread(LPVOID lpParameter) {
DWORD WINAPI LauncherUtils::downloadThread(LPVOID lpParameter) {
DownloadThreadData& data = *((DownloadThreadData*)lpParameter);
ProgressCallback progressCallback;
progressCallback.setProgressCallback(data.progressCallback);
progressCallback.setProgressCallback(data._progressCallback);
auto hr = URLDownloadToFile(0, data._url, data._file, 0,
static_cast<LPBINDSTATUSCALLBACK>(&progressCallback));
data.callback(data._type, hr != S_OK);
data._callback(data._type, hr != S_OK);
return 0;
}
DWORD WINAPI LauncherUtils::deleteDirectoryThread(LPVOID lpParameter) {
DeleteThreadData& data = *((DeleteThreadData*)lpParameter);
BOOL success = LauncherUtils::deleteFileOrDirectory(data._dirPath);
data.callback(!success);
data._callback(!success);
return 0;
}
DWORD WINAPI LauncherUtils::httpThread(LPVOID lpParameter) {
HttpThreadData& data = *((HttpThreadData*)lpParameter);
CString response;
auto error = LauncherUtils::makeHTTPCall(data._callerName, data._mainUrl, data._dirUrl,
data._contentType, data._postData, response, data._isPost);
data._callback(response, error);
return 0;
}
@ -543,6 +552,26 @@ BOOL LauncherUtils::deleteDirectoryOnThread(const CString& dirPath, std::functio
return FALSE;
}
BOOL LauncherUtils::httpCallOnThread(const CString& callerName, const CString& mainUrl, const CString& dirUrl,
const CString& contentType, CStringA& postData, bool isPost,
std::function<void(CString, int)> callback) {
DWORD myThreadID;
HttpThreadData* httpThreadData = new HttpThreadData();
httpThreadData->_callerName = callerName;
httpThreadData->_mainUrl = mainUrl;
httpThreadData->_dirUrl = dirUrl;
httpThreadData->_contentType = contentType;
httpThreadData->_postData = postData;
httpThreadData->_isPost = isPost;
httpThreadData->setCallback(callback);
HANDLE myHandle = CreateThread(0, 0, httpThread, httpThreadData, 0, &myThreadID);
if (myHandle) {
CloseHandle(myHandle);
return TRUE;
}
return FALSE;
}
HWND LauncherUtils::executeOnForeground(const CString& path, const CString& params) {
SHELLEXECUTEINFO info;
info.cbSize = sizeof(SHELLEXECUTEINFO);

View file

@ -55,15 +55,15 @@ public:
ULONG ulStatusCode, LPCWSTR szStatusText) {
float progress = (float)ulProgress / ulProgressMax;
if (!isnan(progress)) {
onProgressCallback(progress);
_onProgressCallback(progress);
}
return S_OK;
}
void setProgressCallback(std::function<void(float)> fn) {
onProgressCallback = std::bind(fn, std::placeholders::_1);
_onProgressCallback = std::bind(fn, std::placeholders::_1);
}
private:
std::function<void(float)> onProgressCallback;
std::function<void(float)> _onProgressCallback;
};
enum ResponseError {
@ -82,14 +82,14 @@ public:
int _type;
CString _url;
CString _file;
std::function<void(int, bool)> callback;
std::function<void(float)> progressCallback;
std::function<void(int, bool)> _callback;
std::function<void(float)> _progressCallback;
// function(type, errorType)
void setCallback(std::function<void(int, bool)> fn) {
callback = std::bind(fn, std::placeholders::_1, std::placeholders::_2);
_callback = std::bind(fn, std::placeholders::_1, std::placeholders::_2);
}
void setProgressCallback(std::function<void(float)> fn) {
progressCallback = std::bind(fn, std::placeholders::_1);
_progressCallback = std::bind(fn, std::placeholders::_1);
}
};
@ -98,23 +98,36 @@ public:
std::string _zipFile;
std::string _path;
// function(type, size)
std::function<void(int, int)> callback;
std::function<void(float)> progressCallback;
std::function<void(int, int)> _callback;
std::function<void(float)> _progressCallback;
void setCallback(std::function<void(int, int)> fn) {
callback = std::bind(fn, std::placeholders::_1, std::placeholders::_2);
_callback = std::bind(fn, std::placeholders::_1, std::placeholders::_2);
}
void setProgressCallback(std::function<void(float)> fn) {
progressCallback = std::bind(fn, std::placeholders::_1);
_progressCallback = std::bind(fn, std::placeholders::_1);
}
};
struct DeleteThreadData {
CString _dirPath;
std::function<void(bool)> callback;
std::function<void(float)> progressCallback;
void setCallback(std::function<void(bool)> fn) { callback = std::bind(fn, std::placeholders::_1); }
std::function<void(bool)> _callback;
std::function<void(float)> _progressCallback;
void setCallback(std::function<void(bool)> fn) { _callback = std::bind(fn, std::placeholders::_1); }
void setProgressCallback(std::function<void(float)> fn) {
progressCallback = std::bind(fn, std::placeholders::_1);
_progressCallback = std::bind(fn, std::placeholders::_1);
}
};
struct HttpThreadData {
CString _callerName;
CString _mainUrl;
CString _dirUrl;
CString _contentType;
CStringA _postData;
bool _isPost { false };
std::function<void(CString, int)> _callback;
void setCallback(std::function<void(CString, int)> fn) {
_callback = std::bind(fn, std::placeholders::_1, std::placeholders::_2);
}
};
@ -150,6 +163,9 @@ public:
std::function<void(int, bool)> callback,
std::function<void(float)> progressCallback);
static BOOL deleteDirectoryOnThread(const CString& dirPath, std::function<void(bool)> callback);
static BOOL httpCallOnThread(const CString& callerName, const CString& mainUrl, const CString& dirUrl,
const CString& contentType, CStringA& postData, bool isPost,
std::function<void(CString, int)> callback);
static CString urlEncodeString(const CString& url);
static HWND executeOnForeground(const CString& path, const CString& params);
@ -159,4 +175,5 @@ private:
static DWORD WINAPI unzipThread(LPVOID lpParameter);
static DWORD WINAPI downloadThread(LPVOID lpParameter);
static DWORD WINAPI deleteDirectoryThread(LPVOID lpParameter);
};
static DWORD WINAPI httpThread(LPVOID lpParameter);
};

View file

@ -0,0 +1,45 @@
#
# SetPackagingParameters.cmake
# cmake/macros
#
# Created by Leonardo Murillo on 07/14/2015.
# Copyright 2015 High Fidelity, Inc.
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
# This macro checks some Jenkins defined environment variables to determine the origin of this build
# and decides how targets should be packaged.
macro(SET_PACKAGING_PARAMETERS)
set(PR_BUILD 0)
set(PRODUCTION_BUILD 0)
set(DEV_BUILD 0)
set(BUILD_NUMBER 0)
set_from_env(RELEASE_TYPE RELEASE_TYPE "DEV")
set_from_env(RELEASE_NUMBER RELEASE_NUMBER "")
set_from_env(STABLE_BUILD STABLE_BUILD 0)
message(STATUS "The RELEASE_TYPE variable is: ${RELEASE_TYPE}")
set(BUILD_NUMBER ${RELEASE_NUMBER})
if (RELEASE_TYPE STREQUAL "PRODUCTION")
set(PRODUCTION_BUILD 1)
set(BUILD_VERSION ${RELEASE_NUMBER})
# add definition for this release type
add_definitions(-DPRODUCTION_BUILD)
elseif (RELEASE_TYPE STREQUAL "PR")
set(PR_BUILD 1)
set(BUILD_VERSION "PR${RELEASE_NUMBER}")
# add definition for this release type
add_definitions(-DPR_BUILD)
else ()
set(DEV_BUILD 1)
set(BUILD_VERSION "dev")
endif ()
endmacro(SET_PACKAGING_PARAMETERS)

View file

@ -27,6 +27,7 @@
#define IDC_TROUBLE 1023
#define IDC_VOXEL 1024
#define IDC_PROGRESS 1025
#define IDC_VERSION 1026
#define IDC_TROUBLE_LINK 1027
// Next default values for new objects

View file

@ -431,7 +431,6 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
constrained = constraint->apply(tipRelativeRotation);
if (constrained) {
tipOrientation = tipParentOrientation * tipRelativeRotation;
tipRelativeRotation = tipRelativeRotation;
}
}

View file

@ -1873,6 +1873,61 @@ void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, bool headEnabl
}
}
void Rig::updateReactions(const ControllerParameters& params) {
// enable/disable animVars
bool enabled = params.reactionEnabledFlags[AVATAR_REACTION_POSITIVE];
_animVars.set("reactionPositiveEnabled", enabled);
_animVars.set("reactionPositiveDisabled", !enabled);
enabled = params.reactionEnabledFlags[AVATAR_REACTION_NEGATIVE];
_animVars.set("reactionNegativeEnabled", enabled);
_animVars.set("reactionNegativeDisabled", !enabled);
enabled = params.reactionEnabledFlags[AVATAR_REACTION_RAISE_HAND];
_animVars.set("reactionRaiseHandEnabled", enabled);
_animVars.set("reactionRaiseHandDisabled", !enabled);
enabled = params.reactionEnabledFlags[AVATAR_REACTION_APPLAUD];
_animVars.set("reactionApplaudEnabled", enabled);
_animVars.set("reactionApplaudDisabled", !enabled);
enabled = params.reactionEnabledFlags[AVATAR_REACTION_POINT];
_animVars.set("reactionPointEnabled", enabled);
_animVars.set("reactionPointDisabled", !enabled);
// trigger animVars
if (params.reactionTriggers[AVATAR_REACTION_POSITIVE]) {
_animVars.set("reactionPositiveTrigger", true);
} else {
_animVars.set("reactionPositiveTrigger", false);
}
if (params.reactionTriggers[AVATAR_REACTION_NEGATIVE]) {
_animVars.set("reactionNegativeTrigger", true);
} else {
_animVars.set("reactionNegativeTrigger", false);
}
if (params.reactionTriggers[AVATAR_REACTION_RAISE_HAND]) {
_animVars.set("reactionRaiseHandTrigger", true);
} else {
_animVars.set("reactionRaiseHandTrigger", false);
}
if (params.reactionTriggers[AVATAR_REACTION_APPLAUD]) {
_animVars.set("reactionApplaudTrigger", true);
} else {
_animVars.set("reactionApplaudTrigger", false);
}
if (params.reactionTriggers[AVATAR_REACTION_POINT]) {
_animVars.set("reactionPointTrigger", true);
} else {
_animVars.set("reactionPointTrigger", false);
}
}
void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::vec3& lookAtSpot, const glm::vec3& saccade) {
// TODO: does not properly handle avatar scale.
@ -2152,6 +2207,8 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo
}
}
updateReactions(params);
_previousControllerParameters = params;
}

View file

@ -88,6 +88,8 @@ public:
AnimPose secondaryControllerPoses[NumSecondaryControllerTypes]; // rig space
uint8_t secondaryControllerFlags[NumSecondaryControllerTypes];
bool isTalking;
bool reactionEnabledFlags[NUM_AVATAR_REACTIONS];
bool reactionTriggers[NUM_AVATAR_REACTIONS];
HFMJointShapeInfo hipsShapeInfo;
HFMJointShapeInfo spineShapeInfo;
HFMJointShapeInfo spine1ShapeInfo;
@ -268,6 +270,7 @@ protected:
void updateFeet(bool leftFootEnabled, bool rightFootEnabled, bool headEnabled,
const AnimPose& leftFootPose, const AnimPose& rightFootPose,
const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix);
void updateReactions(const ControllerParameters& params);
void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::vec3& lookAt, const glm::vec3& saccade);
void calcAnimAlpha(float speed, const std::vector<float>& referenceSpeeds, float* alphaOut) const;

View file

@ -2861,7 +2861,7 @@ glm::vec3 AvatarData::getAbsoluteJointTranslationInObjectFrame(int index) const
* Information on an attachment worn by the avatar.
* @typedef {object} AttachmentData
* @property {string} modelUrl - The URL of the model file. Models can be FBX or OBJ format.
* @property {string} jointName - The offset to apply to the model relative to the joint position.
* @property {string} jointName - The name of the joint that the attachment is parented to.
* @property {Vec3} translation - The offset from the joint that the attachment is positioned at.
* @property {Vec3} rotation - The rotation applied to the model relative to the joint orientation.
* @property {number} scale - The scale applied to the attachment model.

View file

@ -1292,7 +1292,7 @@ public:
* jointName: "Head",
* translation: {"x": 0, "y": 0.25, "z": 0},
* rotation: {"x": 0, "y": 0, "z": 0, "w": 1},
* scale: 1,
* scale: 0.01,
* isSoft: false
* };
*

View file

@ -47,5 +47,9 @@ void AbstractHMDScriptingInterface::setIPDScale(float IPDScale) {
}
bool AbstractHMDScriptingInterface::isHMDMode() const {
return PluginContainer::getInstance().getActiveDisplayPlugin()->isHmd();
}
auto displayPlugin = PluginContainer::getInstance().getActiveDisplayPlugin();
if (displayPlugin) {
return displayPlugin->isHmd();
}
return false;
}

View file

@ -174,20 +174,35 @@ private:
};
/**jsdoc
* The <code>Reticle</code> API provides access to the mouse cursor. The cursor may be an arrow or a reticle circle, depending
* on Interface settings. The mouse cursor is visible in HMD mode if controllers aren't being used.
*
* @namespace Reticle
*
* @hifi-interface
* @hifi-client-entity
* @hifi-avatar
*
* @property {boolean} allowMouseCapture
* @property {number} depth
* @property {Vec2} maximumPosition
* @property {boolean} mouseCaptured
* @property {boolean} pointingAtSystemOverlay
* @property {Vec2} position
* @property {number} scale
* @property {boolean} visible
* @property {boolean} allowMouseCapture=true - <code>true</code> if the mouse cursor will be captured when in HMD mode and the
* Interface window content (excluding menus) has focus, <code>false</code> if the mouse cursor will not be captured.
* @property {number} depth - The depth (distance) that the reticle is displayed at relative to the HMD view, in HMD mode.
* @property {Vec2} maximumPosition - The maximum reticle coordinates on the display device in desktop mode or the HUD surface
* in HMD mode. (The minimum reticle coordinates on the desktop display device or HUD surface are <code>0</code>,
* <code>0</code>.) <em>Read-only.</em>
* @property {boolean} mouseCaptured - <code>true</code> if the mouse cursor is captured, displaying only in Interface and
* not on the rest of the desktop. The mouse cursor may be captured when in HMD mode and the Interface window content
* (excluding menu items) has focus, if capturing is enabled (<code>allowMouseCapture</code> is <code>true</code>).
* <em>Read-only.</em>
* @property {boolean} pointingAtSystemOverlay - <code>true</code> if the mouse cursor is pointing at UI in the Interface
* window in desktop mode or on the HUD surface in HMD mode, <code>false</code> if it isn't. <em>Read-only.</em>
* @property {Vec2} position - The position of the cursor. This is the position relative to the Interface window in desktop
* mode, and the HUD surface in HMD mode.
* <p><strong>Note:</strong> The position values may be negative.</p>
* @property {number} scale=1 - The scale of the reticle circle in desktop mode, and the arrow and reticle circle in HMD mode.
* (Does not affect the size of the arrow in desktop mode.)
* @property {boolean} visible=true - <code>true</code> if the reticle circle is visible in desktop mode, and the arrow or
* reticle circle are visible in HMD mode; <code>false</code> otherwise. (Does not affect the visibility of the mouse
* pointer in desktop mode.)
*/
// Scripting interface available to control the Reticle
class ReticleInterface : public QObject {
@ -205,80 +220,113 @@ public:
ReticleInterface(CompositorHelper* outer) : QObject(outer), _compositor(outer) {}
/**jsdoc
* Checks whether the mouse cursor is captured, displaying only in Interface and not on the rest of the desktop. The mouse
* cursor is captured when in HMD mode and the Interface window content (excluding menu items) has focus, if capturing is
* enabled (<code>allowMouseCapture</code> property value is <code>true</code>).
* @function Reticle.isMouseCaptured
* @returns {boolean}
* @returns {boolean} <code>true</code> if the mouse cursor is captured, displaying only in Interface and not on the
* desktop.
*/
Q_INVOKABLE bool isMouseCaptured() { return _compositor->shouldCaptureMouse(); }
/**jsdoc
* Gets whether the mouse cursor will be captured when in HMD mode and the Interface window content (excluding menu items)
* has focus. When captured, the mouse cursor displays only in Interface, not on the rest of the desktop.
* @function Reticle.getAllowMouseCapture
* @returns {boolean}
* @returns {boolean} <code>true</code> if the mouse cursor will be captured when in HMD mode and the Interface window
* content has focus, <code>false</code> if the mouse cursor will not be captured.
*/
Q_INVOKABLE bool getAllowMouseCapture() { return _compositor->getAllowMouseCapture(); }
/**jsdoc
* Sets whether the mouse cursor will be captured when in HMD mode and the Interface window content (excluding menu items)
* has focus. When captured, the mouse cursor displays only in Interface, not on the rest of desktop.
* @function Reticle.setAllowMouseCapture
* @param {boolean} allowMouseCaptured
* @param {boolean} allowMouseCaptured - <code>true</code> if the mouse cursor will be captured when in HMD mode and the
* Interface window content has focus, <code>false</code> if the mouse cursor will not be captured.
*/
Q_INVOKABLE void setAllowMouseCapture(bool value) { return _compositor->setAllowMouseCapture(value); }
/**jsdoc
* Gets whether the mouse cursor is pointing at UI in the Interface window in desktop mode or on the HUD surface in HMD
* mode.
* @function Reticle.isPointingAtSystemOverlay
* @returns {boolean}
* @returns {boolean} <code>true</code> if the mouse cursor is pointing at UI in the Interface window in desktop mode or on
* the HUD surface in HMD mode, <code>false</code> if it isn't.
*/
Q_INVOKABLE bool isPointingAtSystemOverlay() { return !_compositor->getReticleOverDesktop(); }
/**jsdoc
* Gets whether the reticle circle is visible in desktop mode, or the arrow or reticle circle are visible in HMD mode.
* @function Reticle.getVisible
* @returns {boolean}
* @returns {boolean} <code>true</code> if the reticle circle is visible in desktop mode, and the arrow or
* reticle circle are visible in HMD mode; <code>false</code> otherwise. (The mouse pointer is always visible in
* desktop mode.)
*/
Q_INVOKABLE bool getVisible() { return _compositor->getReticleVisible(); }
/**jsdoc
* Sets whether the reticle circle is visible in desktop mode, or the arrow or reticle circle are visible in HMD mode.
* @function Reticle.setVisible
* @param {boolean} visible
* @param {boolean} visible - <code>true</code> if the reticle circle is visible in desktop mode, and the arrow or reticle
* circle are visible in HMD mode; <code>false</code> otherwise. (Does not affect the visibility of the mouse pointer
* in desktop mode.)
*/
Q_INVOKABLE void setVisible(bool visible) { _compositor->setReticleVisible(visible); }
/**jsdoc
* Gets the depth (distance) that the reticle is displayed at relative to the HMD view, in HMD mode.
* @function Reticle.getDepth
* @returns {number}
* @returns {number} The depth (distance) that the reticle is displayed at relative to the HMD view, in HMD mode.
*/
Q_INVOKABLE float getDepth() { return _compositor->getReticleDepth(); }
/**jsdoc
* Sets the depth (distance) that the reticle is displayed at relative to the HMD view, in HMD mode.
* @function Reticle.setDepth
* @param {number} depth
* @param {number} depth - The depth (distance) that the reticle is displayed at relative to the HMD view, in HMD mode.
*/
Q_INVOKABLE void setDepth(float depth) { _compositor->setReticleDepth(depth); }
/**jsdoc
* Gets the scale of the reticle circle in desktop mode, and the arrow and reticle circle in HMD mode. (Does not affect the
* size of the arrow in desktop mode.) The default scale is <code>1.0</code>.
* @function Reticle.getScale
* @returns {number}
* @returns {number} The scale of the reticle.
*/
Q_INVOKABLE float getScale() const;
/**jsdoc
* Sets the scale of the reticle circle in desktop mode, and the arrow and reticle circle in HMD mode. (Does not affect the
* size of the arrow in desktop mode.) The default scale is <code>1.0</code>.
* @function Reticle.setScale
* @param {number} scale
* @param {number} scale - The scale of the reticle.
*/
Q_INVOKABLE void setScale(float scale);
/**jsdoc
* Gets the position of the cursor. This is the position relative to the Interface window in desktop mode, and the HUD
* surface in HMD mode.
* <p><strong>Note:</strong> The position values may be negative.</p>
* @function Reticle.getPosition
* @returns {Vec2}
* @returns {Vec2} The position of the cursor.
*/
Q_INVOKABLE QVariant getPosition() const;
/**jsdoc
* Sets the position of the cursor. This is the position relative to the Interface window in desktop mode, and the HUD
* surface in HMD mode.
* <p><strong>Note:</strong> The position values may be negative.</p>
* @function Reticle.setPosition
* @param {Vec2} position
* @param {Vec2} position - The position of the cursor.
*/
Q_INVOKABLE void setPosition(QVariant position);
/**jsdoc
* Gets the maximum reticle coordinates on the display device in desktop mode or the HUD surface in HMD mode. (The minimum
* reticle coordinates on the desktop display device or HUD surface are <code>0</code>, <code>0</code>.)
* @function Reticle.getMaximumPosition
* @returns {Vec2}
* @returns {Vec2} The maximum reticle coordinates on the display device in desktop mode or the HUD surface in HMD mode.
*/
Q_INVOKABLE glm::vec2 getMaximumPosition() { return _compositor->getReticleMaximumPosition(); }

View file

@ -0,0 +1,104 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
//
// DrawTextureWithVisionSqueeze.frag
//
// Draw texture 0 fetched at texcoord.xy
//
// Created by Seth Alves on 2019-2-15
// Copyright 2019 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 display-plugins/VisionSqueeze.slh@>
struct DrawTextureWithVisionSqueezeParams {
float _visionSqueezeX;
float _visionSqueezeY;
float _spareA;
float _spareB;
mat4 _leftProjection;
mat4 _rightProjection;
mat4 _hmdSensorMatrix;
float _visionSqueezeTransition;
int _visionSqueezePerEye;
float _visionSqueezeGroundPlaneY;
float _visionSqueezeSpotlightSize;
};
LAYOUT(binding=0) uniform sampler2D colorMap;
// binding=1 must match drawTextureWithSqueezeParamsSlot in OpenGLDisplayPlugin.h
LAYOUT(binding=1) uniform drawTextureWithSqueezeMappingParamsBuffer {
DrawTextureWithVisionSqueezeParams params;
};
layout(location=0) in vec2 varTexCoord0;
layout(location=0) out vec4 outFragColor;
float getVisionSqueezeX() {
return params._visionSqueezeX;
}
float getVisionSqueezeY() {
return params._visionSqueezeY;
}
float getVisionSqueezeTransitionRatio() {
return params._visionSqueezeTransition;
}
int getVisionSqueezePerEye() {
return params._visionSqueezePerEye;
}
float getVisionSqueezeGroundPlaneY() {
return params._visionSqueezeGroundPlaneY;
}
float getVisionSqueezeSpotlightSize() {
return params._visionSqueezeSpotlightSize;
}
mat4 getProjectionMatrix(float eye) {
if (eye == 0.0) {
return params._leftProjection;
} else {
return params._rightProjection;
}
}
mat4 getHMDSensorMatrix() {
return params._hmdSensorMatrix;
}
void main(void) {
float side = float(varTexCoord0.x > 0.5);
mat4 leftProjectionMatrix = getProjectionMatrix(0.0);
vec4 fovTan = extractFovTan(leftProjectionMatrix);
vec2 focalPointNDC = extractFocalPoint(fovTan);
focalPointNDC.x -= 2.0 * focalPointNDC.x * (1.0 - side);
vec2 focalPointUV = 0.5 * (focalPointNDC + vec2(1.0));
// block edges of vision to avoid sickness
vec2 visionSqueezeRatios = vec2(getVisionSqueezeX(), getVisionSqueezeY());
bool perEye = getVisionSqueezePerEye() > 0;
float frac = squeezeVision(perEye, varTexCoord0, visionSqueezeRatios, getVisionSqueezeTransitionRatio(), focalPointUV);
if (frac == 0.0) {
// world only
outFragColor = texture(colorMap, varTexCoord0);
} else {
// grid on the floor only or mixed
mat4 hmdSensorMatrix = getHMDSensorMatrix();
mat4 projectionMatrix = getProjectionMatrix(side);
mat4 projectionInverse = inverse(projectionMatrix);
float groundPlaneY = getVisionSqueezeGroundPlaneY();
float spotLightSize = getVisionSqueezeSpotlightSize();
vec4 gridColor = vec4(gridFloor(varTexCoord0, hmdSensorMatrix, projectionInverse, groundPlaneY, spotLightSize), 1.0);
vec4 preSqueezeColor = texture(colorMap, varTexCoord0);
// mix between grid and world
outFragColor = mix(preSqueezeColor, gridColor, frac);
}
}

View file

@ -0,0 +1 @@
VERTEX gpu::vertex::DrawUnitQuadTexcoord

View file

@ -392,6 +392,9 @@ void OpenGLDisplayPlugin::customizeContext() {
_drawTexturePipeline = gpu::Pipeline::create(gpu::Shader::createProgram(DrawTexture), scissorState);
_drawTextureSqueezePipeline =
gpu::Pipeline::create(gpu::Shader::createProgram(shader::display_plugins::program::DrawTextureWithVisionSqueeze), scissorState);
_linearToSRGBPipeline = gpu::Pipeline::create(gpu::Shader::createProgram(DrawTextureLinearToSRGB), scissorState);
_SRGBToLinearPipeline = gpu::Pipeline::create(gpu::Shader::createProgram(DrawTextureSRGBToLinear), scissorState);
@ -407,6 +410,7 @@ void OpenGLDisplayPlugin::customizeContext() {
void OpenGLDisplayPlugin::uncustomizeContext() {
_drawTexturePipeline.reset();
_drawTextureSqueezePipeline.reset();
_linearToSRGBPipeline.reset();
_SRGBToLinearPipeline.reset();
_cursorPipeline.reset();
@ -629,6 +633,10 @@ void OpenGLDisplayPlugin::compositePointer() {
});
}
void OpenGLDisplayPlugin::setupCompositeScenePipeline(gpu::Batch& batch) {
batch.setPipeline(_drawTexturePipeline);
}
void OpenGLDisplayPlugin::compositeScene() {
render([&](gpu::Batch& batch) {
batch.enableStereo(false);
@ -637,8 +645,8 @@ void OpenGLDisplayPlugin::compositeScene() {
batch.setStateScissorRect(ivec4(uvec2(), _compositeFramebuffer->getSize()));
batch.resetViewTransform();
batch.setProjectionTransform(mat4());
batch.setPipeline(_drawTexturePipeline);
batch.setResourceTexture(0, _currentFrame->framebuffer->getRenderBuffer(0));
setupCompositeScenePipeline(batch);
batch.draw(gpu::TRIANGLE_STRIP, 4);
});
}
@ -958,4 +966,3 @@ void OpenGLDisplayPlugin::copyTextureToQuickFramebuffer(NetworkTexturePointer ne
gpu::PipelinePointer OpenGLDisplayPlugin::getRenderTexturePipeline() {
return _drawTexturePipeline;
}

View file

@ -100,6 +100,7 @@ protected:
virtual QThread::Priority getPresentPriority() { return QThread::HighPriority; }
virtual void compositeLayers();
virtual void setupCompositeScenePipeline(gpu::Batch& batch);
virtual void compositeScene();
virtual void compositePointer();
virtual void compositeExtra(){};
@ -155,6 +156,7 @@ protected:
gpu::PipelinePointer _mirrorHUDPipeline;
gpu::ShaderPointer _mirrorHUDPS;
gpu::PipelinePointer _drawTexturePipeline;
gpu::PipelinePointer _drawTextureSqueezePipeline;
gpu::PipelinePointer _linearToSRGBPipeline;
gpu::PipelinePointer _SRGBToLinearPipeline;
gpu::PipelinePointer _cursorPipeline;

View file

@ -0,0 +1,106 @@
// Generated on <$_SCRIBE_DATE$>
//
// Created by Seth Alves on 2019-2-13.
// Copyright 2019 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
//
<@if not VISION_SQUEEZE_SLH@>
<@def VISION_SQUEEZE_SLH@>
float ellipse(vec2 coord, vec2 centerUV, vec2 semiAxis) {
return pow(coord.x - centerUV.x, 2.0) / semiAxis.x + pow(coord.y - centerUV.y, 2.0) / semiAxis.y;
}
vec4 extractFovTan(mat4 m) {
mat4 mt = transpose(m);
vec4 v;
vec4 result;
// x -- Left
v = mt * vec4(1.0, 0.0, 0.0, 1.0);
result.x = -(v.z / v.x);
// y -- Right
v = mt * vec4(-1.0, 0.0, 0.0, 1.0);
result.y = (v.z / v.x);
// z -- Down
v = mt * vec4(0.0, 1.0, 0.0, 1.0);
result.z = -(v.z / v.y);
// w -- Up
v = mt * vec4(0.0, -1.0, 0.0, 1.0);
result.w = v.z / v.y;
return result;
}
// takes left-side projection matrix, returns NDC for right eye. to get left, invert sign on x coord of result.
vec2 extractFocalPoint(vec4 fovTan) {
float fovwidth = fovTan.x + fovTan.y;
float fovheight = fovTan.z + fovTan.w;
vec2 focalPoint = vec2(fovTan.y / fovwidth, (fovTan.z / fovheight) - 0.5f);
return focalPoint;
}
float squeezeVision(bool perEye, vec2 varTexCoord0, vec2 visionSqueezeRatios, float transitionRatio, vec2 focalPointUV) {
if (visionSqueezeRatios.x == 0.0 && visionSqueezeRatios.y == 0.0) {
return 0.0;
}
vec2 centerUV;
vec2 semiAxis;
if (perEye) {
// tubes in front of each eye
centerUV = focalPointUV;
semiAxis = vec2(0.25) - visionSqueezeRatios * 0.25;
} else {
// one tube in front of face
centerUV = vec2(0.5, focalPointUV.y);
semiAxis = vec2(0.5) - visionSqueezeRatios * 0.5;
}
float ellipseValue = ellipse(varTexCoord0, centerUV, semiAxis);
float frac = clamp((ellipseValue - 1.0) / clamp(transitionRatio, 0.01, 0.7), 0.0, 1.0);
return frac;
}
vec3 gridFloor(vec2 varTexCoord0, mat4 hmdSensorMatrix, mat4 projectionInverse, float groundPlaneY, float spotLightSize) {
vec4 ndc = vec4(varTexCoord0.x * 4.0 - 1.0 - 2.0 * float(varTexCoord0.x > 0.5), varTexCoord0.y * 2.0 - 1.0, -1.0, 1.0);
vec4 fragmentEyeCoords = hmdSensorMatrix * projectionInverse * ndc;
vec4 near4 = hmdSensorMatrix * vec4(0.0, 0.0, 0.0, 1.0);
vec3 near = (near4 / near4.w).xyz;
vec3 far = fragmentEyeCoords.xyz / fragmentEyeCoords.w;
// intersect a line from near to far with the plane y = groundPlaneY
float t = -(near.y - groundPlaneY) / (far.y - near.y);
vec2 R = (near + t * (far - near)).xz;
float lineThickness = 1.5 / length(R);
vec4 gridColor = vec4(0.35);
vec4 baseColor = vec4(0.1);
vec4 skyColor = vec4(0.0, 0.0, 0.0, 0.0);
vec2 wrapped = fract(R) - 0.5f;
vec2 range = abs(wrapped);
vec2 speeds = fwidth(R);
vec2 pixelRange = range/speeds;
float lineWeight = clamp(min(pixelRange.x, pixelRange.y) - lineThickness, 0.0, 1.0);
float horizonFuzz = 0.02;
if (t < 0.0) {
return mix(gridColor, skyColor, clamp(0.0, 1.0, -t)).xyz;
} else if (t < horizonFuzz) {
lineWeight = lineWeight * max(0.0, t);
}
vec4 c = mix(gridColor, baseColor, lineWeight);
// fade out grid to avoid shimmer
float fadeVal = 0.7;
return mix(c, baseColor * fadeVal + gridColor * (1.0 - fadeVal),
0.1 * clamp((length(R) - spotLightSize), 0.0, 1.0)).xyz;
}
<@endif@>

View file

@ -1,4 +1,4 @@
//
//
// Created by Bradley Austin Davis on 2016/02/15
// Copyright 2016 High Fidelity, Inc.
//
@ -113,6 +113,11 @@ void HmdDisplayPlugin::internalDeactivate() {
}
void HmdDisplayPlugin::customizeContext() {
VisionSqueezeParameters parameters;
_visionSqueezeParametersBuffer =
gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(VisionSqueezeParameters), (const gpu::Byte*) &parameters));
Parent::customizeContext();
_hudRenderer.build();
}
@ -478,3 +483,48 @@ HmdDisplayPlugin::~HmdDisplayPlugin() {
float HmdDisplayPlugin::stutterRate() const {
return _stutterRate.rate();
}
float adjustVisionSqueezeRatioForDevice(float visionSqueezeRatio, float visionSqueezeDeviceLow, float visionSqueezeDeviceHigh) {
if (visionSqueezeRatio <= 0.0f) {
return 0.0f;
}
float deviceRange = visionSqueezeDeviceHigh - visionSqueezeDeviceLow;
const float SQUEEZE_ADJUSTMENT = 0.75f; // magic number picked through experimentation
return deviceRange * (SQUEEZE_ADJUSTMENT * visionSqueezeRatio) + visionSqueezeDeviceLow;
}
void HmdDisplayPlugin::updateVisionSqueezeParameters(float visionSqueezeX, float visionSqueezeY,
float visionSqueezeTransition,
int visionSqueezePerEye, float visionSqueezeGroundPlaneY,
float visionSqueezeSpotlightSize) {
visionSqueezeX = adjustVisionSqueezeRatioForDevice(visionSqueezeX, _visionSqueezeDeviceLowX, _visionSqueezeDeviceHighX);
visionSqueezeY = adjustVisionSqueezeRatioForDevice(visionSqueezeY, _visionSqueezeDeviceLowY, _visionSqueezeDeviceHighY);
auto& params = _visionSqueezeParametersBuffer.get<VisionSqueezeParameters>();
if (params._visionSqueezeX != visionSqueezeX) {
_visionSqueezeParametersBuffer.edit<VisionSqueezeParameters>()._visionSqueezeX = visionSqueezeX;
}
if (params._visionSqueezeY != visionSqueezeY) {
_visionSqueezeParametersBuffer.edit<VisionSqueezeParameters>()._visionSqueezeY = visionSqueezeY;
}
if (params._visionSqueezeTransition != visionSqueezeTransition) {
_visionSqueezeParametersBuffer.edit<VisionSqueezeParameters>()._visionSqueezeTransition = visionSqueezeTransition;
}
if (params._visionSqueezePerEye != visionSqueezePerEye) {
_visionSqueezeParametersBuffer.edit<VisionSqueezeParameters>()._visionSqueezePerEye = visionSqueezePerEye;
}
if (params._visionSqueezeGroundPlaneY != visionSqueezeGroundPlaneY) {
_visionSqueezeParametersBuffer.edit<VisionSqueezeParameters>()._visionSqueezeGroundPlaneY = visionSqueezeGroundPlaneY;
}
if (params._visionSqueezeSpotlightSize != visionSqueezeSpotlightSize) {
_visionSqueezeParametersBuffer.edit<VisionSqueezeParameters>()._visionSqueezeSpotlightSize = visionSqueezeSpotlightSize;
}
}
void HmdDisplayPlugin::setupCompositeScenePipeline(gpu::Batch& batch) {
batch.setPipeline(_drawTextureSqueezePipeline);
_visionSqueezeParametersBuffer.edit<VisionSqueezeParameters>()._hmdSensorMatrix = _currentPresentFrameInfo.presentPose;
batch.setUniformBuffer(drawTextureWithVisionSqueezeParamsSlot, _visionSqueezeParametersBuffer);
}

View file

@ -50,6 +50,9 @@ public:
std::function<void(gpu::Batch&, const gpu::TexturePointer&)> getHUDOperator() override;
virtual StencilMaskMode getStencilMaskMode() const override { return StencilMaskMode::PAINT; }
void updateVisionSqueezeParameters(float visionSqueezeX, float visionSqueezeY, float visionSqueezeTransition,
int visionSqueezePerEye, float visionSqueezeGroundPlaneY,
float visionSqueezeSpotlightSize);
signals:
void hmdMountedChanged();
@ -91,6 +94,33 @@ protected:
RateCounter<> _stutterRate;
bool _disablePreview { true };
class VisionSqueezeParameters {
public:
float _visionSqueezeX { 0.0f };
float _visionSqueezeY { 0.0f };
float _spareA { 0.0f };
float _spareB { 0.0f };
glm::mat4 _leftProjection;
glm::mat4 _rightProjection;
glm::mat4 _hmdSensorMatrix;
float _visionSqueezeTransition { 0.15f };
int _visionSqueezePerEye { 0 };
float _visionSqueezeGroundPlaneY { 0.0f };
float _visionSqueezeSpotlightSize { 0.0f };
VisionSqueezeParameters() {}
};
typedef gpu::BufferView UniformBufferView;
gpu::BufferView _visionSqueezeParametersBuffer;
virtual void setupCompositeScenePipeline(gpu::Batch& batch) override;
float _visionSqueezeDeviceLowX { 0.0f };
float _visionSqueezeDeviceHighX { 1.0f };
float _visionSqueezeDeviceLowY { 0.0f };
float _visionSqueezeDeviceHighY { 1.0f };
private:
ivec4 getViewportForSourceSize(const uvec2& size) const;
float getLeftCenterPixel() const;
@ -112,7 +142,7 @@ private:
struct Uniforms {
float alpha { 1.0f };
} uniforms;
struct Vertex {
vec3 pos;
vec2 uv;
@ -126,3 +156,5 @@ private:
std::function<void(gpu::Batch&, const gpu::TexturePointer&)> render();
} _hudRenderer;
};
const int drawTextureWithVisionSqueezeParamsSlot = 1; // must match binding in DrawTextureWithVisionSqueeze.slf

View file

@ -282,27 +282,26 @@ bool RenderableModelEntityItem::findDetailedParabolaIntersection(const glm::vec3
}
void RenderableModelEntityItem::fetchCollisionGeometryResource() {
_compoundShapeResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(getCollisionShapeURL());
_collisionGeometryResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(getCollisionShapeURL());
}
bool RenderableModelEntityItem::computeShapeFailedToLoad() {
if (!_compoundShapeResource) {
bool RenderableModelEntityItem::unableToLoadCollisionShape() {
if (!_collisionGeometryResource) {
fetchCollisionGeometryResource();
}
return (_compoundShapeResource && _compoundShapeResource->isFailed());
return (_collisionGeometryResource && _collisionGeometryResource->isFailed());
}
void RenderableModelEntityItem::setShapeType(ShapeType type) {
ModelEntityItem::setShapeType(type);
auto shapeType = getShapeType();
if (shapeType == SHAPE_TYPE_COMPOUND || shapeType == SHAPE_TYPE_SIMPLE_COMPOUND) {
if (!_compoundShapeResource && !getCollisionShapeURL().isEmpty()) {
if (!_collisionGeometryResource && !getCollisionShapeURL().isEmpty()) {
fetchCollisionGeometryResource();
}
} else if (_compoundShapeResource && !getCompoundShapeURL().isEmpty()) {
} else if (_collisionGeometryResource && !getCompoundShapeURL().isEmpty()) {
// the compoundURL has been set but the shapeType does not agree
_compoundShapeResource.reset();
_collisionGeometryResource.reset();
}
}
@ -333,11 +332,11 @@ bool RenderableModelEntityItem::isReadyToComputeShape() const {
}
if (model->isLoaded()) {
if (!shapeURL.isEmpty() && !_compoundShapeResource) {
if (!shapeURL.isEmpty() && !_collisionGeometryResource) {
const_cast<RenderableModelEntityItem*>(this)->fetchCollisionGeometryResource();
}
if (_compoundShapeResource && _compoundShapeResource->isLoaded()) {
if (_collisionGeometryResource && _collisionGeometryResource->isLoaded()) {
// we have both URLs AND both geometries AND they are both fully loaded.
if (_needsInitialSimulation) {
// the _model's offset will be wrong until _needsInitialSimulation is false
@ -368,7 +367,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
}
if (type == SHAPE_TYPE_COMPOUND) {
if (!_compoundShapeResource || !_compoundShapeResource->isLoaded()) {
if (!_collisionGeometryResource || !_collisionGeometryResource->isLoaded()) {
return;
}
@ -376,8 +375,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
// should never fall in here when collision model not fully loaded
// TODO: assert that all geometries exist and are loaded
//assert(_model && _model->isLoaded() && _compoundShapeResource && _compoundShapeResource->isLoaded());
const HFMModel& collisionGeometry = _compoundShapeResource->getHFMModel();
//assert(_model && _model->isLoaded() && _collisionGeometryResource && _collisionGeometryResource->isLoaded());
const HFMModel& collisionGeometry = _collisionGeometryResource->getHFMModel();
ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection();
pointCollection.clear();
@ -499,7 +498,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
std::vector<std::shared_ptr<const graphics::Mesh>> meshes;
if (type == SHAPE_TYPE_SIMPLE_COMPOUND) {
auto& hfmMeshes = _compoundShapeResource->getHFMModel().meshes;
auto& hfmMeshes = _collisionGeometryResource->getHFMModel().meshes;
meshes.reserve(hfmMeshes.size());
for (auto& hfmMesh : hfmMeshes) {
meshes.push_back(hfmMesh._mesh);
@ -727,10 +726,10 @@ int RenderableModelEntityItem::avatarJointIndex(int modelJointIndex) {
bool RenderableModelEntityItem::contains(const glm::vec3& point) const {
auto model = getModel();
if (EntityItem::contains(point) && model && _compoundShapeResource && _compoundShapeResource->isLoaded()) {
if (EntityItem::contains(point) && model && _collisionGeometryResource && _collisionGeometryResource->isLoaded()) {
glm::mat4 worldToHFMMatrix = model->getWorldToHFMMatrix();
glm::vec3 hfmPoint = worldToHFMMatrix * glm::vec4(point, 1.0f);
return _compoundShapeResource->getHFMModel().convexHullContains(hfmPoint);
return _collisionGeometryResource->getHFMModel().convexHullContains(hfmPoint);
}
return false;

View file

@ -79,7 +79,7 @@ public:
virtual bool isReadyToComputeShape() const override;
virtual void computeShapeInfo(ShapeInfo& shapeInfo) override;
bool computeShapeFailedToLoad();
bool unableToLoadCollisionShape();
virtual bool contains(const glm::vec3& point) const override;
void stopModelOverrideIfNoParent();
@ -120,7 +120,7 @@ private:
bool readyToAnimate() const;
void fetchCollisionGeometryResource();
GeometryResource::Pointer _compoundShapeResource;
GeometryResource::Pointer _collisionGeometryResource;
std::vector<int> _jointMap;
QVariantMap _originalTextures;
bool _jointMapCompleted { false };

Some files were not shown because too many files have changed in this diff Show more