mirror of
https://github.com/overte-org/overte.git
synced 2025-07-22 21:28:54 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into groups
This commit is contained in:
commit
e3b4612283
37 changed files with 947 additions and 537 deletions
25
interface/resources/QtWebEngine/UIDelegates/AlertDialog.qml
Normal file
25
interface/resources/QtWebEngine/UIDelegates/AlertDialog.qml
Normal file
|
@ -0,0 +1,25 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Layouts 1.0
|
||||
|
||||
import "../../qml/dialogs"
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
signal accepted;
|
||||
property var text;
|
||||
|
||||
property var messageDialogBuilder: Component { MessageDialog { } }
|
||||
|
||||
function open() {
|
||||
console.log("prompt text " + text)
|
||||
var dialog = messageDialogBuilder.createObject(desktop, {
|
||||
text: root.text
|
||||
});
|
||||
|
||||
dialog.selected.connect(function(button){
|
||||
accepted();
|
||||
dialog.destroy();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import QtQuick 2.4
|
||||
|
||||
import QtQuick.Dialogs 1.1 as OriginalDialogs
|
||||
|
||||
import "../../qml/dialogs"
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
signal accepted;
|
||||
signal rejected;
|
||||
property var text;
|
||||
|
||||
property var messageDialogBuilder: Component { MessageDialog { } }
|
||||
|
||||
function open() {
|
||||
var dialog = messageDialogBuilder.createObject(desktop, {
|
||||
text: root.text,
|
||||
icon: OriginalDialogs.StandardIcon.Question,
|
||||
buttons: OriginalDialogs.StandardButton.Ok | OriginalDialogs.StandardButton.Cancel
|
||||
});
|
||||
|
||||
dialog.selected.connect(function(button){
|
||||
if (button === OriginalDialogs.StandardButton.Ok) {
|
||||
accepted()
|
||||
} else {
|
||||
rejected();
|
||||
}
|
||||
dialog.destroy();
|
||||
});
|
||||
}
|
||||
}
|
39
interface/resources/QtWebEngine/UIDelegates/FilePicker.qml
Normal file
39
interface/resources/QtWebEngine/UIDelegates/FilePicker.qml
Normal file
|
@ -0,0 +1,39 @@
|
|||
import QtQuick 2.4
|
||||
import QtQuick.Dialogs 1.1
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
import "../../qml/dialogs"
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
signal filesSelected(var fileList);
|
||||
signal rejected();
|
||||
property var text;
|
||||
property url fileUrl;
|
||||
property var fileUrls;
|
||||
property url folder;
|
||||
property var nameFilters;
|
||||
property bool selectExisting;
|
||||
property bool selectFolder;
|
||||
property bool selectMultiple;
|
||||
property string selectedNameFilter;
|
||||
property string title;
|
||||
|
||||
property var fileDialogBuilder: Component { FileDialog { } }
|
||||
|
||||
function open() {
|
||||
var foo = root;
|
||||
var dialog = fileDialogBuilder.createObject(desktop, {
|
||||
});
|
||||
|
||||
dialog.canceled.connect(function(){
|
||||
root.filesSelected([]);
|
||||
dialog.destroy();
|
||||
});
|
||||
|
||||
dialog.selectedFile.connect(function(file){
|
||||
root.filesSelected(fileDialogHelper.urlToList(file));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
68
interface/resources/QtWebEngine/UIDelegates/Menu.qml
Normal file
68
interface/resources/QtWebEngine/UIDelegates/Menu.qml
Normal file
|
@ -0,0 +1,68 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4 as Controls
|
||||
|
||||
import "../../qml/menus"
|
||||
import "../../qml/controls-uit"
|
||||
import "../../qml/styles-uit"
|
||||
|
||||
Item {
|
||||
id: menu
|
||||
HifiConstants { id: hifi }
|
||||
signal done()
|
||||
implicitHeight: column.height
|
||||
implicitWidth: column.width
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: -16
|
||||
}
|
||||
radius: hifi.dimensions.borderRadius
|
||||
border.width: hifi.dimensions.borderWidth
|
||||
border.color: hifi.colors.lightGrayText80
|
||||
color: hifi.colors.faintGray80
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: closer
|
||||
width: 8192
|
||||
height: 8192
|
||||
x: -4096
|
||||
y: -4096
|
||||
propagateComposedEvents: true
|
||||
acceptedButtons: "AllButtons"
|
||||
onClicked: {
|
||||
menu.done();
|
||||
mouse.accepted = false;
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: column
|
||||
}
|
||||
|
||||
function popup() {
|
||||
var position = Reticle.position;
|
||||
var localPosition = menu.parent.mapFromItem(desktop, position.x, position.y);
|
||||
x = localPosition.x
|
||||
y = localPosition.y
|
||||
console.log("Popup at " + x + " x " + y)
|
||||
var moveChildren = [];
|
||||
for (var i = 0; i < children.length; ++i) {
|
||||
var child = children[i];
|
||||
if (child.objectName !== "MenuItem") {
|
||||
continue;
|
||||
}
|
||||
moveChildren.push(child);
|
||||
}
|
||||
|
||||
for (i = 0; i < moveChildren.length; ++i) {
|
||||
child = moveChildren[i];
|
||||
child.parent = column;
|
||||
child.menu = menu
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
39
interface/resources/QtWebEngine/UIDelegates/MenuItem.qml
Normal file
39
interface/resources/QtWebEngine/UIDelegates/MenuItem.qml
Normal file
|
@ -0,0 +1,39 @@
|
|||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4 as Controls
|
||||
|
||||
import "../../qml/controls-uit"
|
||||
import "../../qml/styles-uit"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
objectName: "MenuItem"
|
||||
|
||||
property alias text: label.text
|
||||
property var menu;
|
||||
property var shortcut;
|
||||
signal triggered();
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
implicitHeight: 2 * label.implicitHeight
|
||||
implicitWidth: 2 * hifi.dimensions.menuPadding.x + label.width
|
||||
|
||||
RalewaySemiBold {
|
||||
id: label
|
||||
size: hifi.fontSizes.rootMenu
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: hifi.dimensions.menuPadding.x
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: enabled ? hifi.colors.baseGrayShadow : hifi.colors.baseGrayShadow50
|
||||
enabled: root.enabled
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
root.triggered();
|
||||
menu.done();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import QtQuick 2.5
|
||||
|
||||
Item {
|
||||
width: 100
|
||||
height: 20
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
import QtQuick 2.5
|
||||
|
||||
Item {
|
||||
}
|
42
interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml
Normal file
42
interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml
Normal file
|
@ -0,0 +1,42 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Layouts 1.0
|
||||
|
||||
import "../../qml/controls-uit"
|
||||
import "../../qml/styles-uit"
|
||||
import "../../qml/dialogs"
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
signal input(string text);
|
||||
signal accepted;
|
||||
signal rejected;
|
||||
signal closing(var close)
|
||||
|
||||
property var titleWidth;
|
||||
property var text;
|
||||
property var prompt;
|
||||
|
||||
property var inputDialogBuilder: Component { QueryDialog { } }
|
||||
|
||||
function open() {
|
||||
console.log("prompt text " + text)
|
||||
console.log("prompt prompt " + prompt)
|
||||
|
||||
var dialog = inputDialogBuilder.createObject(desktop, {
|
||||
label: root.text,
|
||||
current: root.prompt
|
||||
});
|
||||
|
||||
dialog.selected.connect(function(result){
|
||||
root.input(dialog.result)
|
||||
root.accepted();
|
||||
dialog.destroy();
|
||||
});
|
||||
|
||||
dialog.canceled.connect(function(){
|
||||
root.rejected();
|
||||
dialog.destroy();
|
||||
});
|
||||
}
|
||||
}
|
8
interface/resources/QtWebEngine/UIDelegates/qmldir
Normal file
8
interface/resources/QtWebEngine/UIDelegates/qmldir
Normal file
|
@ -0,0 +1,8 @@
|
|||
module QtWebEngine.UIDelegates
|
||||
AlertDialog 1.0 AlertDialog.qml
|
||||
ConfirmDialog 1.0 ConfirmDialog.qml
|
||||
FilePicker 1.0 FilePicker.qml
|
||||
PromptDialog 1.0 PromptDialog.qml
|
||||
Menu 1.0 Menu.qml
|
||||
MenuItem 1.0 MenuItem.qml
|
||||
MenuSeparator 1.0 MenuSeparator.qml
|
|
@ -1,4 +1,4 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.2
|
||||
import QtWebEngine 1.1
|
||||
|
||||
|
@ -16,6 +16,8 @@ ScrollingWindow {
|
|||
destroyOnHidden: true
|
||||
width: 800
|
||||
height: 600
|
||||
property variant permissionsBar: {'securityOrigin':'none','feature':'none'}
|
||||
property alias url: webview.url
|
||||
property alias webView: webview
|
||||
x: 100
|
||||
y: 100
|
||||
|
@ -32,6 +34,19 @@ ScrollingWindow {
|
|||
}
|
||||
}
|
||||
|
||||
function showPermissionsBar(){
|
||||
permissionsContainer.visible=true;
|
||||
}
|
||||
|
||||
function hidePermissionsBar(){
|
||||
permissionsContainer.visible=false;
|
||||
}
|
||||
|
||||
function allowPermissions(){
|
||||
webview.grantFeaturePermission(permissionsBar.securityOrigin, permissionsBar.feature, true);
|
||||
hidePermissionsBar();
|
||||
}
|
||||
|
||||
Item {
|
||||
id:item
|
||||
width: pane.contentWidth
|
||||
|
@ -70,6 +85,7 @@ ScrollingWindow {
|
|||
size: 48
|
||||
MouseArea { anchors.fill: parent; onClicked: webview.goForward() }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
|
@ -116,6 +132,7 @@ ScrollingWindow {
|
|||
if (text.indexOf("http") != 0) {
|
||||
text = "http://" + text
|
||||
}
|
||||
root.hidePermissionsBar();
|
||||
webview.url = text
|
||||
break;
|
||||
}
|
||||
|
@ -123,14 +140,76 @@ ScrollingWindow {
|
|||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id:permissionsContainer
|
||||
visible:false
|
||||
color: "#000000"
|
||||
width: parent.width
|
||||
anchors.top: buttons.bottom
|
||||
height:40
|
||||
z:100
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: "black" }
|
||||
GradientStop { position: 1.0; color: "grey" }
|
||||
}
|
||||
|
||||
RalewayLight {
|
||||
id: permissionsInfo
|
||||
anchors.right:permissionsRow.left
|
||||
anchors.rightMargin: 32
|
||||
anchors.topMargin:8
|
||||
anchors.top:parent.top
|
||||
text: "This site wants to use your microphone/camera"
|
||||
size: 18
|
||||
color: hifi.colors.white
|
||||
}
|
||||
|
||||
Row {
|
||||
id: permissionsRow
|
||||
spacing: 4
|
||||
anchors.top:parent.top
|
||||
anchors.topMargin: 8
|
||||
anchors.right: parent.right
|
||||
visible: true
|
||||
z:101
|
||||
|
||||
Button {
|
||||
id:allow
|
||||
text: "Allow"
|
||||
color: hifi.buttons.blue
|
||||
colorScheme: root.colorScheme
|
||||
width: 120
|
||||
enabled: true
|
||||
onClicked: root.allowPermissions();
|
||||
z:101
|
||||
}
|
||||
|
||||
Button {
|
||||
id:block
|
||||
text: "Block"
|
||||
color: hifi.buttons.red
|
||||
colorScheme: root.colorScheme
|
||||
width: 120
|
||||
enabled: true
|
||||
onClicked: root.hidePermissionsBar();
|
||||
z:101
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WebEngineView {
|
||||
id: webview
|
||||
url: "http://highfidelity.com"
|
||||
url: "https://highfidelity.com"
|
||||
anchors.top: buttons.bottom
|
||||
anchors.topMargin: 8
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
onFeaturePermissionRequested: {
|
||||
permissionsBar.securityOrigin = securityOrigin;
|
||||
permissionsBar.feature = feature;
|
||||
root.showPermissionsBar();
|
||||
}
|
||||
onLoadingChanged: {
|
||||
if (loadRequest.status === WebEngineView.LoadSucceededStatus) {
|
||||
addressBar.text = loadRequest.url
|
||||
|
@ -139,9 +218,12 @@ ScrollingWindow {
|
|||
onIconChanged: {
|
||||
console.log("New icon: " + icon)
|
||||
}
|
||||
|
||||
onNewViewRequested:{
|
||||
var component = Qt.createComponent("Browser.qml");
|
||||
var newWindow = component.createObject(desktop);
|
||||
request.openIn(newWindow.webView)
|
||||
}
|
||||
//profile: desktop.browserProfile
|
||||
|
||||
}
|
||||
|
||||
} // item
|
||||
|
|
|
@ -15,7 +15,7 @@ WebEngineView {
|
|||
id: root
|
||||
property var newUrl;
|
||||
|
||||
profile.httpUserAgent: "Mozilla/5.0 Chrome (HighFidelityInterface)"
|
||||
profile.httpUserAgent: "Mozilla/5.0 Chrome/38.0 (HighFidelityInterface)"
|
||||
|
||||
Component.onCompleted: {
|
||||
console.log("Connecting JS messaging to Hifi Logging")
|
||||
|
@ -48,10 +48,6 @@ WebEngineView {
|
|||
}
|
||||
}
|
||||
|
||||
onFeaturePermissionRequested: {
|
||||
grantFeaturePermission(securityOrigin, feature, true);
|
||||
}
|
||||
|
||||
onLoadingChanged: {
|
||||
// Required to support clicking on "hifi://" links
|
||||
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
|
||||
|
|
|
@ -720,7 +720,9 @@ void MyAvatar::saveData() {
|
|||
_fullAvatarURLFromPreferences.toString());
|
||||
|
||||
settings.setValue("fullAvatarModelName", _fullAvatarModelName);
|
||||
settings.setValue("animGraphURL", _animGraphUrl);
|
||||
|
||||
QUrl animGraphUrl = _prefOverrideAnimGraphUrl.get();
|
||||
settings.setValue("animGraphURL", animGraphUrl);
|
||||
|
||||
settings.beginWriteArray("attachmentData");
|
||||
for (int i = 0; i < _attachmentData.size(); i++) {
|
||||
|
@ -833,7 +835,7 @@ void MyAvatar::loadData() {
|
|||
_targetScale = loadSetting(settings, "scale", 1.0f);
|
||||
setScale(glm::vec3(_targetScale));
|
||||
|
||||
_animGraphUrl = settings.value("animGraphURL", "").toString();
|
||||
_prefOverrideAnimGraphUrl.set(QUrl(settings.value("animGraphURL", "").toString()));
|
||||
_fullAvatarURLFromPreferences = settings.value("fullAvatarURL", AvatarData::defaultFullAvatarModelUrl()).toUrl();
|
||||
_fullAvatarModelName = settings.value("fullAvatarModelName", DEFAULT_FULL_AVATAR_MODEL_NAME).toString();
|
||||
|
||||
|
@ -1412,21 +1414,55 @@ void MyAvatar::initHeadBones() {
|
|||
}
|
||||
}
|
||||
|
||||
QUrl MyAvatar::getAnimGraphOverrideUrl() const {
|
||||
return _prefOverrideAnimGraphUrl.get();
|
||||
}
|
||||
|
||||
void MyAvatar::setAnimGraphOverrideUrl(QUrl value) {
|
||||
_prefOverrideAnimGraphUrl.set(value);
|
||||
if (!value.isEmpty()) {
|
||||
setAnimGraphUrl(value);
|
||||
} else {
|
||||
initAnimGraph();
|
||||
}
|
||||
}
|
||||
|
||||
QUrl MyAvatar::getAnimGraphUrl() const {
|
||||
return _currentAnimGraphUrl.get();
|
||||
}
|
||||
|
||||
void MyAvatar::setAnimGraphUrl(const QUrl& url) {
|
||||
if (_animGraphUrl == url) {
|
||||
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setAnimGraphUrl", Q_ARG(QUrl, url));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentAnimGraphUrl.get() == url) {
|
||||
return;
|
||||
}
|
||||
destroyAnimGraph();
|
||||
_skeletonModel->reset(); // Why is this necessary? Without this, we crash in the next render.
|
||||
_animGraphUrl = url;
|
||||
initAnimGraph();
|
||||
|
||||
_currentAnimGraphUrl.set(url);
|
||||
_rig->initAnimGraph(url);
|
||||
|
||||
_bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation..
|
||||
updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes
|
||||
}
|
||||
|
||||
void MyAvatar::initAnimGraph() {
|
||||
auto graphUrl =_animGraphUrl.isEmpty() ?
|
||||
QUrl::fromLocalFile(PathUtils::resourcesPath() + "avatar/avatar-animation.json") :
|
||||
QUrl(_animGraphUrl);
|
||||
QUrl graphUrl;
|
||||
if (!_prefOverrideAnimGraphUrl.get().isEmpty()) {
|
||||
graphUrl = _prefOverrideAnimGraphUrl.get();
|
||||
} else if (!_fstAnimGraphOverrideUrl.isEmpty()) {
|
||||
graphUrl = _fstAnimGraphOverrideUrl;
|
||||
} else {
|
||||
graphUrl = QUrl::fromLocalFile(PathUtils::resourcesPath() + "avatar/avatar-animation.json");
|
||||
}
|
||||
|
||||
_rig->initAnimGraph(graphUrl);
|
||||
_currentAnimGraphUrl.set(graphUrl);
|
||||
|
||||
_bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation..
|
||||
updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes
|
||||
|
@ -1444,6 +1480,7 @@ void MyAvatar::postUpdate(float deltaTime) {
|
|||
if (_skeletonModel->initWhenReady(scene)) {
|
||||
initHeadBones();
|
||||
_skeletonModel->setCauterizeBoneSet(_headBoneSet);
|
||||
_fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl();
|
||||
initAnimGraph();
|
||||
}
|
||||
|
||||
|
|
|
@ -292,8 +292,6 @@ public slots:
|
|||
|
||||
Q_INVOKABLE void updateMotionBehaviorFromMenu();
|
||||
|
||||
Q_INVOKABLE QUrl getAnimGraphUrl() const { return _animGraphUrl; }
|
||||
|
||||
void setEnableDebugDrawDefaultPose(bool isEnabled);
|
||||
void setEnableDebugDrawAnimPose(bool isEnabled);
|
||||
void setEnableDebugDrawPosition(bool isEnabled);
|
||||
|
@ -303,7 +301,11 @@ public slots:
|
|||
void setEnableMeshVisible(bool isEnabled);
|
||||
void setUseAnimPreAndPostRotations(bool isEnabled);
|
||||
void setEnableInverseKinematics(bool isEnabled);
|
||||
Q_INVOKABLE void setAnimGraphUrl(const QUrl& url);
|
||||
|
||||
QUrl getAnimGraphOverrideUrl() const; // thread-safe
|
||||
void setAnimGraphOverrideUrl(QUrl value); // thread-safe
|
||||
QUrl getAnimGraphUrl() const; // thread-safe
|
||||
void setAnimGraphUrl(const QUrl& url); // thread-safe
|
||||
|
||||
glm::vec3 getPositionForAudio();
|
||||
glm::quat getOrientationForAudio();
|
||||
|
@ -407,7 +409,9 @@ private:
|
|||
// Avatar Preferences
|
||||
QUrl _fullAvatarURLFromPreferences;
|
||||
QString _fullAvatarModelName;
|
||||
QUrl _animGraphUrl {""};
|
||||
ThreadSafeValueCache<QUrl> _currentAnimGraphUrl;
|
||||
ThreadSafeValueCache<QUrl> _prefOverrideAnimGraphUrl;
|
||||
QUrl _fstAnimGraphOverrideUrl;
|
||||
bool _useSnapTurn { true };
|
||||
bool _clearOverlayWhenMoving { true };
|
||||
|
||||
|
|
|
@ -161,8 +161,8 @@ void setupPreferences() {
|
|||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = [=]()->QString { return myAvatar->getAnimGraphUrl().toString(); };
|
||||
auto setter = [=](const QString& value) { myAvatar->setAnimGraphUrl(value); };
|
||||
auto getter = [=]()->QString { return myAvatar->getAnimGraphOverrideUrl().toString(); };
|
||||
auto setter = [=](const QString& value) { myAvatar->setAnimGraphOverrideUrl(QUrl(value)); };
|
||||
auto preference = new EditPreference(AVATAR_TUNING, "Avatar animation JSON", getter, setter);
|
||||
preference->setPlaceholderText("default");
|
||||
preferences->addPreference(preference);
|
||||
|
|
|
@ -234,6 +234,29 @@ bool Overlays::editOverlay(unsigned int id, const QVariant& properties) {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool Overlays::editOverlays(const QVariant& propertiesById) {
|
||||
QVariantMap map = propertiesById.toMap();
|
||||
bool success = true;
|
||||
QWriteLocker lock(&_lock);
|
||||
for (const auto& key : map.keys()) {
|
||||
bool convertSuccess;
|
||||
unsigned int id = key.toUInt(&convertSuccess);
|
||||
if (!convertSuccess) {
|
||||
success = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
Overlay::Pointer thisOverlay = getOverlay(id);
|
||||
if (!thisOverlay) {
|
||||
success = false;
|
||||
continue;
|
||||
}
|
||||
QVariant properties = map[key];
|
||||
thisOverlay->setProperties(properties.toMap());
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
void Overlays::deleteOverlay(unsigned int id) {
|
||||
Overlay::Pointer overlayToDelete;
|
||||
|
||||
|
|
|
@ -89,6 +89,10 @@ public slots:
|
|||
/// successful edit, if the input id is for an unknown overlay this function will have no effect
|
||||
bool editOverlay(unsigned int id, const QVariant& properties);
|
||||
|
||||
/// edits an overlay updating only the included properties, will return the identified OverlayID in case of
|
||||
/// successful edit, if the input id is for an unknown overlay this function will have no effect
|
||||
bool editOverlays(const QVariant& propertiesById);
|
||||
|
||||
/// deletes a particle
|
||||
void deleteOverlay(unsigned int id);
|
||||
|
||||
|
|
|
@ -428,7 +428,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
|||
// shift hips according to the _hipsOffset from the previous frame
|
||||
float offsetLength = glm::length(_hipsOffset);
|
||||
const float MIN_HIPS_OFFSET_LENGTH = 0.03f;
|
||||
if (offsetLength > MIN_HIPS_OFFSET_LENGTH) {
|
||||
if (offsetLength > MIN_HIPS_OFFSET_LENGTH && _hipsIndex >= 0) {
|
||||
// but only if offset is long enough
|
||||
float scaleFactor = ((offsetLength - MIN_HIPS_OFFSET_LENGTH) / offsetLength);
|
||||
if (_hipsParentIndex == -1) {
|
||||
|
@ -861,7 +861,11 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele
|
|||
_hipsIndex = _skeleton->nameToJointIndex("Hips");
|
||||
|
||||
// also cache the _hipsParentIndex for later
|
||||
if (_hipsIndex >= 0) {
|
||||
_hipsParentIndex = _skeleton->getParentIndex(_hipsIndex);
|
||||
} else {
|
||||
_hipsParentIndex = -1;
|
||||
}
|
||||
} else {
|
||||
clearConstraints();
|
||||
_headIndex = -1;
|
||||
|
|
|
@ -203,8 +203,8 @@ bool AudioInjector::injectLocally() {
|
|||
}
|
||||
|
||||
if (!success) {
|
||||
// we never started so we are finished, call our stop method
|
||||
stop();
|
||||
// we never started so we are finished with local injection
|
||||
finishLocalInjection();
|
||||
}
|
||||
|
||||
return success;
|
||||
|
@ -217,8 +217,15 @@ static const int64_t NEXT_FRAME_DELTA_IMMEDIATELY = 0;
|
|||
qint64 writeStringToStream(const QString& string, QDataStream& stream) {
|
||||
QByteArray data = string.toUtf8();
|
||||
uint32_t length = data.length();
|
||||
if (length == 0) {
|
||||
stream << static_cast<quint32>(length);
|
||||
} else {
|
||||
// http://doc.qt.io/qt-5/datastreamformat.html
|
||||
// QDataStream << QByteArray -
|
||||
// If the byte array is null : 0xFFFFFFFF (quint32)
|
||||
// Otherwise : the array size(quint32) followed by the array bytes, i.e.size bytes
|
||||
stream << data;
|
||||
}
|
||||
return length + sizeof(uint32_t);
|
||||
}
|
||||
|
||||
|
@ -301,7 +308,6 @@ int64_t AudioInjector::injectNextFrame() {
|
|||
volumeOptionOffset = _currentPacket->pos();
|
||||
quint8 volume = MAX_INJECTOR_VOLUME;
|
||||
audioPacketStream << volume;
|
||||
|
||||
audioPacketStream << _options.ignorePenumbra;
|
||||
|
||||
audioDataOffset = _currentPacket->pos();
|
||||
|
@ -312,7 +318,6 @@ int64_t AudioInjector::injectNextFrame() {
|
|||
return NEXT_FRAME_DELTA_ERROR_OR_FINISHED;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_frameTimer->isValid()) {
|
||||
// in the case where we have been restarted, the frame timer will be invalid and we need to start it back over here
|
||||
_frameTimer->restart();
|
||||
|
@ -418,7 +423,7 @@ void AudioInjector::triggerDeleteAfterFinish() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (_state == AudioInjectorState::Finished) {
|
||||
if (stateHas(AudioInjectorState::Finished)) {
|
||||
stopAndDeleteLater();
|
||||
} else {
|
||||
_state |= AudioInjectorState::PendingDelete;
|
||||
|
@ -484,23 +489,17 @@ AudioInjector* AudioInjector::playSound(const QByteArray& buffer, const AudioInj
|
|||
// setup parameters required for injection
|
||||
injector->setupInjection();
|
||||
|
||||
// we always inject locally
|
||||
//
|
||||
if (!injector->injectLocally()) {
|
||||
// failed, so don't bother sending to server
|
||||
qDebug() << "AudioInjector::playSound failed to inject locally";
|
||||
return nullptr;
|
||||
}
|
||||
// we always inject locally, except when there is no localInterface
|
||||
injector->injectLocally();
|
||||
|
||||
// if localOnly, we are done, just return injector.
|
||||
if (options.localOnly) {
|
||||
return injector;
|
||||
}
|
||||
if (!options.localOnly) {
|
||||
|
||||
// send off to server for everyone else
|
||||
if (!injectorManager->threadInjector(injector)) {
|
||||
// we failed to thread the new injector (we are at the max number of injector threads)
|
||||
qDebug() << "AudioInjector::playSound failed to thread injector";
|
||||
}
|
||||
return injector;
|
||||
|
||||
}
|
||||
return injector;
|
||||
}
|
||||
|
|
|
@ -256,6 +256,8 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) {
|
|||
destinationBuffer += _headData->_blendshapeCoefficients.size() * sizeof(float);
|
||||
}
|
||||
|
||||
QReadLocker readLock(&_jointDataLock);
|
||||
|
||||
// joint rotation data
|
||||
*destinationBuffer++ = _jointData.size();
|
||||
unsigned char* validityPosition = destinationBuffer;
|
||||
|
@ -378,6 +380,7 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) {
|
|||
|
||||
void AvatarData::doneEncoding(bool cullSmallChanges) {
|
||||
// The server has finished sending this version of the joint-data to other nodes. Update _lastSentJointData.
|
||||
QReadLocker readLock(&_jointDataLock);
|
||||
_lastSentJointData.resize(_jointData.size());
|
||||
for (int i = 0; i < _jointData.size(); i ++) {
|
||||
const JointData& data = _jointData[ i ];
|
||||
|
@ -551,8 +554,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
PACKET_READ_CHECK(NumJoints, sizeof(uint8_t));
|
||||
int numJoints = *sourceBuffer++;
|
||||
|
||||
_jointData.resize(numJoints);
|
||||
|
||||
const int bytesOfValidity = (int)ceil((float)numJoints / (float)BITS_IN_BYTE);
|
||||
PACKET_READ_CHECK(JointRotationValidityBits, bytesOfValidity);
|
||||
|
||||
|
@ -576,6 +577,9 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
}
|
||||
|
||||
// each joint rotation is stored in 6 bytes.
|
||||
QWriteLocker writeLock(&_jointDataLock);
|
||||
_jointData.resize(numJoints);
|
||||
|
||||
const int COMPRESSED_QUATERNION_SIZE = 6;
|
||||
PACKET_READ_CHECK(JointRotations, numValidJointRotations * COMPRESSED_QUATERNION_SIZE);
|
||||
for (int i = 0; i < numJoints; i++) {
|
||||
|
@ -653,6 +657,7 @@ void AvatarData::setRawJointData(QVector<JointData> data) {
|
|||
QMetaObject::invokeMethod(this, "setRawJointData", Q_ARG(QVector<JointData>, data));
|
||||
return;
|
||||
}
|
||||
QWriteLocker writeLock(&_jointDataLock);
|
||||
_jointData = data;
|
||||
}
|
||||
|
||||
|
@ -664,6 +669,7 @@ void AvatarData::setJointData(int index, const glm::quat& rotation, const glm::v
|
|||
QMetaObject::invokeMethod(this, "setJointData", Q_ARG(int, index), Q_ARG(const glm::quat&, rotation));
|
||||
return;
|
||||
}
|
||||
QWriteLocker writeLock(&_jointDataLock);
|
||||
if (_jointData.size() <= index) {
|
||||
_jointData.resize(index + 1);
|
||||
}
|
||||
|
@ -682,6 +688,8 @@ void AvatarData::clearJointData(int index) {
|
|||
QMetaObject::invokeMethod(this, "clearJointData", Q_ARG(int, index));
|
||||
return;
|
||||
}
|
||||
QWriteLocker writeLock(&_jointDataLock);
|
||||
// FIXME: I don't understand how this "clears" the joint data at index
|
||||
if (_jointData.size() <= index) {
|
||||
_jointData.resize(index + 1);
|
||||
}
|
||||
|
@ -710,6 +718,7 @@ glm::quat AvatarData::getJointRotation(int index) const {
|
|||
Q_RETURN_ARG(glm::quat, result), Q_ARG(int, index));
|
||||
return result;
|
||||
}
|
||||
QReadLocker readLock(&_jointDataLock);
|
||||
return index < _jointData.size() ? _jointData.at(index).rotation : glm::quat();
|
||||
}
|
||||
|
||||
|
@ -724,6 +733,7 @@ glm::vec3 AvatarData::getJointTranslation(int index) const {
|
|||
Q_RETURN_ARG(glm::vec3, result), Q_ARG(int, index));
|
||||
return result;
|
||||
}
|
||||
QReadLocker readLock(&_jointDataLock);
|
||||
return index < _jointData.size() ? _jointData.at(index).translation : glm::vec3();
|
||||
}
|
||||
|
||||
|
@ -771,6 +781,7 @@ void AvatarData::setJointRotation(int index, const glm::quat& rotation) {
|
|||
QMetaObject::invokeMethod(this, "setJointRotation", Q_ARG(int, index), Q_ARG(const glm::quat&, rotation));
|
||||
return;
|
||||
}
|
||||
QWriteLocker writeLock(&_jointDataLock);
|
||||
if (_jointData.size() <= index) {
|
||||
_jointData.resize(index + 1);
|
||||
}
|
||||
|
@ -787,6 +798,7 @@ void AvatarData::setJointTranslation(int index, const glm::vec3& translation) {
|
|||
QMetaObject::invokeMethod(this, "setJointTranslation", Q_ARG(int, index), Q_ARG(const glm::vec3&, translation));
|
||||
return;
|
||||
}
|
||||
QWriteLocker writeLock(&_jointDataLock);
|
||||
if (_jointData.size() <= index) {
|
||||
_jointData.resize(index + 1);
|
||||
}
|
||||
|
@ -831,6 +843,7 @@ QVector<glm::quat> AvatarData::getJointRotations() const {
|
|||
Q_RETURN_ARG(QVector<glm::quat>, result));
|
||||
return result;
|
||||
}
|
||||
QReadLocker readLock(&_jointDataLock);
|
||||
QVector<glm::quat> jointRotations(_jointData.size());
|
||||
for (int i = 0; i < _jointData.size(); ++i) {
|
||||
jointRotations[i] = _jointData[i].rotation;
|
||||
|
@ -845,6 +858,7 @@ void AvatarData::setJointRotations(QVector<glm::quat> jointRotations) {
|
|||
"setJointRotations", Qt::BlockingQueuedConnection,
|
||||
Q_ARG(QVector<glm::quat>, jointRotations));
|
||||
}
|
||||
QWriteLocker writeLock(&_jointDataLock);
|
||||
if (_jointData.size() < jointRotations.size()) {
|
||||
_jointData.resize(jointRotations.size());
|
||||
}
|
||||
|
@ -862,6 +876,7 @@ void AvatarData::setJointTranslations(QVector<glm::vec3> jointTranslations) {
|
|||
"setJointTranslations", Qt::BlockingQueuedConnection,
|
||||
Q_ARG(QVector<glm::vec3>, jointTranslations));
|
||||
}
|
||||
QWriteLocker writeLock(&_jointDataLock);
|
||||
if (_jointData.size() < jointTranslations.size()) {
|
||||
_jointData.resize(jointTranslations.size());
|
||||
}
|
||||
|
@ -873,11 +888,23 @@ void AvatarData::setJointTranslations(QVector<glm::vec3> jointTranslations) {
|
|||
}
|
||||
|
||||
void AvatarData::clearJointsData() {
|
||||
// FIXME: this method is terribly inefficient and probably doesn't even work
|
||||
// (see implementation of clearJointData(index))
|
||||
for (int i = 0; i < _jointData.size(); ++i) {
|
||||
clearJointData(i);
|
||||
}
|
||||
}
|
||||
|
||||
int AvatarData::getJointIndex(const QString& name) const {
|
||||
QReadLocker readLock(&_jointDataLock);
|
||||
return _jointIndices.value(name) - 1;
|
||||
}
|
||||
|
||||
QStringList AvatarData::getJointNames() const {
|
||||
QReadLocker readLock(&_jointDataLock);
|
||||
return _jointNames;
|
||||
}
|
||||
|
||||
void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut) {
|
||||
QDataStream packetStream(data);
|
||||
|
||||
|
@ -1027,6 +1054,8 @@ void AvatarData::detachAll(const QString& modelURL, const QString& jointName) {
|
|||
void AvatarData::setJointMappingsFromNetworkReply() {
|
||||
QNetworkReply* networkReply = static_cast<QNetworkReply*>(sender());
|
||||
|
||||
{
|
||||
QWriteLocker writeLock(&_jointDataLock);
|
||||
QByteArray line;
|
||||
while (!(line = networkReply->readLine()).isEmpty()) {
|
||||
line = line.trimmed();
|
||||
|
@ -1060,6 +1089,7 @@ void AvatarData::setJointMappingsFromNetworkReply() {
|
|||
for (int i = 0; i < _jointNames.size(); i++) {
|
||||
_jointIndices.insert(_jointNames.at(i), i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
networkReply->deleteLater();
|
||||
}
|
||||
|
@ -1101,16 +1131,19 @@ void AvatarData::sendIdentityPacket() {
|
|||
}
|
||||
|
||||
void AvatarData::updateJointMappings() {
|
||||
{
|
||||
QWriteLocker writeLock(&_jointDataLock);
|
||||
_jointIndices.clear();
|
||||
_jointNames.clear();
|
||||
_jointData.clear();
|
||||
}
|
||||
|
||||
if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) {
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkRequest networkRequest = QNetworkRequest(_skeletonModelURL);
|
||||
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
QNetworkReply* networkReply = networkAccessManager.get(networkRequest);
|
||||
connect(networkReply, SIGNAL(finished()), this, SLOT(setJointMappingsFromNetworkReply()));
|
||||
connect(networkReply, &QNetworkReply::finished, this, &AvatarData::setJointMappingsFromNetworkReply);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -271,9 +271,9 @@ public:
|
|||
Q_INVOKABLE virtual void clearJointsData();
|
||||
|
||||
/// Returns the index of the joint with the specified name, or -1 if not found/unknown.
|
||||
Q_INVOKABLE virtual int getJointIndex(const QString& name) const { return _jointIndices.value(name) - 1; }
|
||||
Q_INVOKABLE virtual int getJointIndex(const QString& name) const;
|
||||
|
||||
Q_INVOKABLE virtual QStringList getJointNames() const { return _jointNames; }
|
||||
Q_INVOKABLE virtual QStringList getJointNames() const;
|
||||
|
||||
Q_INVOKABLE void setBlendshape(QString name, float val) { _headData->setBlendshape(name, val); }
|
||||
|
||||
|
@ -374,6 +374,7 @@ protected:
|
|||
|
||||
QVector<JointData> _jointData; ///< the state of the skeleton joints
|
||||
QVector<JointData> _lastSentJointData; ///< the state of the skeleton joints last time we transmitted
|
||||
mutable QReadWriteLock _jointDataLock;
|
||||
|
||||
// key state
|
||||
KeyState _keyState;
|
||||
|
|
|
@ -113,10 +113,12 @@ void HmdDisplayPlugin::customizeContext() {
|
|||
|
||||
updateReprojectionProgram();
|
||||
updateOverlayProgram();
|
||||
#ifdef HMD_HAND_LASER_SUPPORT
|
||||
updateLaserProgram();
|
||||
|
||||
_laserGeometry = loadLaser(_laserProgram);
|
||||
#endif
|
||||
}
|
||||
|
||||
//#define LIVE_SHADER_RELOAD 1
|
||||
|
||||
static QString readFile(const QString& filename) {
|
||||
|
@ -162,6 +164,7 @@ void HmdDisplayPlugin::updateReprojectionProgram() {
|
|||
|
||||
}
|
||||
|
||||
#ifdef HMD_HAND_LASER_SUPPORT
|
||||
void HmdDisplayPlugin::updateLaserProgram() {
|
||||
static const QString vsFile = PathUtils::resourcesPath() + "/shaders/hmd_hand_lasers.vert";
|
||||
static const QString gsFile = PathUtils::resourcesPath() + "/shaders/hmd_hand_lasers.geom";
|
||||
|
@ -202,6 +205,7 @@ void HmdDisplayPlugin::updateLaserProgram() {
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void HmdDisplayPlugin::updateOverlayProgram() {
|
||||
static const QString vsFile = PathUtils::resourcesPath() + "/shaders/hmd_ui_glow.vert";
|
||||
|
@ -249,8 +253,10 @@ void HmdDisplayPlugin::uncustomizeContext() {
|
|||
_compositeFramebuffer.reset();
|
||||
_previewProgram.reset();
|
||||
_reprojectionProgram.reset();
|
||||
#ifdef HMD_HAND_LASER_SUPPORT
|
||||
_laserProgram.reset();
|
||||
_laserGeometry.reset();
|
||||
#endif
|
||||
Parent::uncustomizeContext();
|
||||
}
|
||||
|
||||
|
@ -516,6 +522,7 @@ bool HmdDisplayPlugin::setHandLaser(uint32_t hands, HandLaserMode mode, const ve
|
|||
}
|
||||
|
||||
void HmdDisplayPlugin::compositeExtra() {
|
||||
#ifdef HMD_HAND_LASER_SUPPORT
|
||||
// If neither hand laser is activated, exit
|
||||
if (!_presentHandLasers[0].valid() && !_presentHandLasers[1].valid()) {
|
||||
return;
|
||||
|
@ -584,4 +591,5 @@ void HmdDisplayPlugin::compositeExtra() {
|
|||
}
|
||||
});
|
||||
glDisable(GL_BLEND);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -14,6 +14,10 @@
|
|||
|
||||
#include "../OpenGLDisplayPlugin.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#define HMD_HAND_LASER_SUPPORT
|
||||
#endif
|
||||
|
||||
class HmdDisplayPlugin : public OpenGLDisplayPlugin {
|
||||
using Parent = OpenGLDisplayPlugin;
|
||||
public:
|
||||
|
@ -93,7 +97,9 @@ protected:
|
|||
|
||||
private:
|
||||
void updateOverlayProgram();
|
||||
#ifdef HMD_HAND_LASER_SUPPORT
|
||||
void updateLaserProgram();
|
||||
#endif
|
||||
void updateReprojectionProgram();
|
||||
|
||||
bool _enablePreview { false };
|
||||
|
@ -130,11 +136,13 @@ private:
|
|||
|
||||
ShapeWrapperPtr _sphereSection;
|
||||
|
||||
#ifdef HMD_HAND_LASER_SUPPORT
|
||||
ProgramPtr _laserProgram;
|
||||
struct LaserUniforms {
|
||||
int32_t mvp { -1 };
|
||||
int32_t color { -1 };
|
||||
} _laserUniforms;
|
||||
ShapeWrapperPtr _laserGeometry;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <DependencyManager.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <Finally.h>
|
||||
#include <PathUtils.h>
|
||||
|
||||
#include "OffscreenGLCanvas.h"
|
||||
#include "GLEscrow.h"
|
||||
|
@ -400,6 +401,10 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
|
|||
|
||||
// Create a QML engine.
|
||||
_qmlEngine = new QQmlEngine;
|
||||
|
||||
auto importList = _qmlEngine->importPathList();
|
||||
importList.insert(importList.begin(), PathUtils::resourcesPath());
|
||||
_qmlEngine->setImportPathList(importList);
|
||||
if (!_qmlEngine->incubationController()) {
|
||||
_qmlEngine->setIncubationController(_renderer->_quickWindow->incubationController());
|
||||
}
|
||||
|
|
|
@ -67,6 +67,18 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) {
|
|||
_textureBaseUrl = resolveTextureBaseUrl(url, _url.resolved(texdir));
|
||||
}
|
||||
|
||||
auto animGraphVariant = mapping.value("animGraphUrl");
|
||||
if (animGraphVariant.isValid()) {
|
||||
QUrl fstUrl(animGraphVariant.toString());
|
||||
if (fstUrl.isValid()) {
|
||||
_animGraphOverrideUrl = _url.resolved(fstUrl);
|
||||
} else {
|
||||
_animGraphOverrideUrl = QUrl();
|
||||
}
|
||||
} else {
|
||||
_animGraphOverrideUrl = QUrl();
|
||||
}
|
||||
|
||||
auto modelCache = DependencyManager::get<ModelCache>();
|
||||
GeometryExtra extra{ mapping, _textureBaseUrl };
|
||||
|
||||
|
@ -284,6 +296,8 @@ Geometry::Geometry(const Geometry& geometry) {
|
|||
for (const auto& material : geometry._materials) {
|
||||
_materials.push_back(std::make_shared<NetworkMaterial>(*material));
|
||||
}
|
||||
|
||||
_animGraphOverrideUrl = geometry._animGraphOverrideUrl;
|
||||
}
|
||||
|
||||
void Geometry::setTextures(const QVariantMap& textureMap) {
|
||||
|
|
|
@ -52,6 +52,7 @@ public:
|
|||
void setTextures(const QVariantMap& textureMap);
|
||||
|
||||
virtual bool areTexturesLoaded() const;
|
||||
const QUrl& getAnimGraphOverrideUrl() const { return _animGraphOverrideUrl; }
|
||||
|
||||
protected:
|
||||
friend class GeometryMappingResource;
|
||||
|
@ -64,6 +65,8 @@ protected:
|
|||
// Copied to each geometry, mutable throughout lifetime via setTextures
|
||||
NetworkMaterials _materials;
|
||||
|
||||
QUrl _animGraphOverrideUrl;
|
||||
|
||||
private:
|
||||
mutable bool _areTexturesLoaded { false };
|
||||
};
|
||||
|
|
|
@ -53,7 +53,6 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall
|
|||
detailsPart.setBody(QJsonDocument(details).toJson(QJsonDocument::Compact));
|
||||
multipart->append(detailsPart);
|
||||
}
|
||||
qCDebug(networking) << "Logging activity" << action;
|
||||
|
||||
// if no callbacks specified, call our owns
|
||||
if (params.isEmpty()) {
|
||||
|
|
|
@ -1552,6 +1552,12 @@ void GeometryCache::renderLine(gpu::Batch& batch, const glm::vec2& p1, const glm
|
|||
|
||||
void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const glm::vec3& p2,
|
||||
const glm::vec4& color, float glowIntensity, float glowWidth, int id) {
|
||||
|
||||
// Disable glow lines on OSX
|
||||
#ifndef Q_OS_WIN
|
||||
glowIntensity = 0.0f;
|
||||
#endif
|
||||
|
||||
if (glowIntensity <= 0) {
|
||||
renderLine(batch, p1, p2, color, id);
|
||||
return;
|
||||
|
|
|
@ -1192,6 +1192,8 @@ void Model::deleteGeometry() {
|
|||
_meshStates.clear();
|
||||
_rig->destroyAnimGraph();
|
||||
_blendedBlendshapeCoefficients.clear();
|
||||
_renderGeometry.reset();
|
||||
_collisionGeometry.reset();
|
||||
}
|
||||
|
||||
AABox Model::getRenderableMeshBound() const {
|
||||
|
|
|
@ -20,7 +20,7 @@ struct Grid {
|
|||
};
|
||||
|
||||
uniform gridBuffer { Grid grid; };
|
||||
Grid getGrid() { return grid; };
|
||||
Grid getGrid() { return grid; }
|
||||
|
||||
in vec2 varTexCoord0;
|
||||
in vec4 varColor;
|
||||
|
|
|
@ -108,3 +108,10 @@ QStringList FileDialogHelper::drives() {
|
|||
void FileDialogHelper::openDirectory(const QString& path) {
|
||||
QDesktopServices::openUrl(path);
|
||||
}
|
||||
|
||||
QList<QUrl> FileDialogHelper::urlToList(const QUrl& url) {
|
||||
QList<QUrl> results;
|
||||
results.push_back(url);
|
||||
return results;
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@ public:
|
|||
Q_INVOKABLE bool validFolder(const QString& path);
|
||||
Q_INVOKABLE QUrl pathToUrl(const QString& path);
|
||||
Q_INVOKABLE QUrl saveHelper(const QString& saveText, const QUrl& currentFolder, const QStringList& selectionFilters);
|
||||
Q_INVOKABLE QList<QUrl> urlToList(const QUrl& url);
|
||||
|
||||
Q_INVOKABLE void openDirectory(const QString& path);
|
||||
};
|
||||
|
|
|
@ -42,7 +42,11 @@ var HAND_HEAD_MIX_RATIO = 0.0; // 0 = only use hands for search/move. 1 = only
|
|||
|
||||
var PICK_WITH_HAND_RAY = true;
|
||||
|
||||
var EQUIP_SPHERE_COLOR = { red: 179, green: 120, blue: 211 };
|
||||
var EQUIP_SPHERE_COLOR = {
|
||||
red: 116,
|
||||
green: 90,
|
||||
blue: 238
|
||||
};
|
||||
var EQUIP_SPHERE_ALPHA = 0.15;
|
||||
var EQUIP_SPHERE_SCALE_FACTOR = 0.65;
|
||||
|
||||
|
@ -56,22 +60,25 @@ var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance hold
|
|||
var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified
|
||||
var MOVE_WITH_HEAD = true; // experimental head-control of distantly held objects
|
||||
|
||||
var NO_INTERSECT_COLOR = {
|
||||
red: 10,
|
||||
green: 10,
|
||||
blue: 255
|
||||
}; // line color when pick misses
|
||||
var INTERSECT_COLOR = {
|
||||
red: 250,
|
||||
green: 10,
|
||||
blue: 10
|
||||
}; // line color when pick hits
|
||||
var LINE_ENTITY_DIMENSIONS = {
|
||||
x: 1000,
|
||||
y: 1000,
|
||||
z: 1000
|
||||
var COLORS_GRAB_SEARCHING_HALF_SQUEEZE = {
|
||||
red: 255,
|
||||
green: 97,
|
||||
blue: 129
|
||||
};
|
||||
|
||||
var COLORS_GRAB_SEARCHING_FULL_SQUEEZE = {
|
||||
red: 255,
|
||||
green: 97,
|
||||
blue: 129
|
||||
};
|
||||
|
||||
var COLORS_GRAB_DISTANCE_HOLD = {
|
||||
red: 238,
|
||||
green: 75,
|
||||
blue: 214
|
||||
};
|
||||
|
||||
|
||||
var LINE_LENGTH = 500;
|
||||
var PICK_MAX_DISTANCE = 500; // max length of pick-ray
|
||||
|
||||
|
@ -143,17 +150,6 @@ var DEFAULT_GRABBABLE_DATA = {
|
|||
var USE_BLACKLIST = true;
|
||||
var blacklist = [];
|
||||
|
||||
// we've created various ways of visualizing looking for and moving distant objects
|
||||
var USE_ENTITY_LINES_FOR_SEARCHING = false;
|
||||
var USE_OVERLAY_LINES_FOR_SEARCHING = true;
|
||||
|
||||
var USE_ENTITY_LINES_FOR_MOVING = false;
|
||||
var USE_OVERLAY_LINES_FOR_MOVING = false;
|
||||
var USE_PARTICLE_BEAM_FOR_MOVING = true;
|
||||
|
||||
var USE_SPOTLIGHT = false;
|
||||
var USE_POINTLIGHT = false;
|
||||
|
||||
var FORBIDDEN_GRAB_NAMES = ["Grab Debug Entity", "grab pointer"];
|
||||
var FORBIDDEN_GRAB_TYPES = ['Unknown', 'Light', 'PolyLine', 'Zone'];
|
||||
|
||||
|
@ -243,6 +239,7 @@ function findRayIntersection(pickRay, precise, include, exclude) {
|
|||
}
|
||||
return overlays;
|
||||
}
|
||||
|
||||
function entityIsGrabbedByOther(entityID) {
|
||||
// by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*.
|
||||
var actionIDs = Entities.getActionIDs(entityID);
|
||||
|
@ -273,6 +270,7 @@ function propsArePhysical(props) {
|
|||
var USE_ATTACH_POINT_SETTINGS = true;
|
||||
|
||||
var ATTACH_POINT_SETTINGS = "io.highfidelity.attachPoints";
|
||||
|
||||
function getAttachPointSettings() {
|
||||
try {
|
||||
var str = Settings.getValue(ATTACH_POINT_SETTINGS);
|
||||
|
@ -286,10 +284,12 @@ function getAttachPointSettings() {
|
|||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function setAttachPointSettings(attachPointSettings) {
|
||||
var str = JSON.stringify(attachPointSettings);
|
||||
Settings.setValue(ATTACH_POINT_SETTINGS, str);
|
||||
}
|
||||
|
||||
function getAttachPointForHotspotFromSettings(hotspot, hand) {
|
||||
var attachPointSettings = getAttachPointSettings();
|
||||
var jointName = (hand === RIGHT_HAND) ? "RightHand" : "LeftHand";
|
||||
|
@ -300,6 +300,7 @@ function getAttachPointForHotspotFromSettings(hotspot, hand) {
|
|||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function storeAttachPointForHotspotInSettings(hotspot, hand, offsetPosition, offsetRotation) {
|
||||
var attachPointSettings = getAttachPointSettings();
|
||||
var jointName = (hand === RIGHT_HAND) ? "RightHand" : "LeftHand";
|
||||
|
@ -327,10 +328,12 @@ function removeMyAvatarFromCollidesWith(origCollidesWith) {
|
|||
// and we should not be showing lasers when someone else is using the Reticle to indicate a 2D minor mode.
|
||||
var EXTERNALLY_MANAGED_2D_MINOR_MODE = true;
|
||||
var EDIT_SETTING = "io.highfidelity.isEditting";
|
||||
|
||||
function isEditing() {
|
||||
var actualSettingValue = Settings.getValue(EDIT_SETTING) === "false" ? false : !!Settings.getValue(EDIT_SETTING);
|
||||
return EXTERNALLY_MANAGED_2D_MINOR_MODE && actualSettingValue;
|
||||
}
|
||||
|
||||
function isIn2DMode() {
|
||||
// In this version, we make our own determination of whether we're aimed a HUD element,
|
||||
// because other scripts (such as handControllerPointer) might be using some other visualization
|
||||
|
@ -338,6 +341,7 @@ function isIn2DMode() {
|
|||
return (EXTERNALLY_MANAGED_2D_MINOR_MODE &&
|
||||
(Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(Reticle.position)));
|
||||
}
|
||||
|
||||
function restore2DMode() {
|
||||
if (!EXTERNALLY_MANAGED_2D_MINOR_MODE) {
|
||||
Reticle.setVisible(true);
|
||||
|
@ -490,7 +494,12 @@ EquipHotspotBuddy.prototype.updateHotspot = function (hotspot, timestamp) {
|
|||
overlayInfoSet.overlays.push(Overlays.addOverlay("model", {
|
||||
url: hotspot.modelURL,
|
||||
position: hotspot.worldPosition,
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1},
|
||||
rotation: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
w: 1
|
||||
},
|
||||
dimensions: diameter * EQUIP_SPHERE_SCALE_FACTOR,
|
||||
scale: hotspot.modelScale,
|
||||
ignoreRayIntersection: true
|
||||
|
@ -500,7 +509,12 @@ EquipHotspotBuddy.prototype.updateHotspot = function (hotspot, timestamp) {
|
|||
// default sphere overlay
|
||||
overlayInfoSet.overlays.push(Overlays.addOverlay("sphere", {
|
||||
position: hotspot.worldPosition,
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1},
|
||||
rotation: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
w: 1
|
||||
},
|
||||
dimensions: diameter * EQUIP_SPHERE_SCALE_FACTOR,
|
||||
color: EQUIP_SPHERE_COLOR,
|
||||
alpha: EQUIP_SPHERE_ALPHA,
|
||||
|
@ -617,11 +631,8 @@ function MyController(hand) {
|
|||
|
||||
// for visualizations
|
||||
this.overlayLine = null;
|
||||
this.particleBeamObject = null;
|
||||
|
||||
// for lights
|
||||
this.spotlight = null;
|
||||
this.pointlight = null;
|
||||
this.overlayLine = null;
|
||||
this.searchSphere = null;
|
||||
|
||||
|
@ -710,56 +721,7 @@ function MyController(hand) {
|
|||
}
|
||||
};
|
||||
|
||||
this.debugLine = function (closePoint, farPoint, color) {
|
||||
Entities.addEntity({
|
||||
type: "Line",
|
||||
name: "Grab Debug Entity",
|
||||
dimensions: LINE_ENTITY_DIMENSIONS,
|
||||
visible: true,
|
||||
position: closePoint,
|
||||
linePoints: [ZERO_VEC, farPoint],
|
||||
color: color,
|
||||
lifetime: 0.1,
|
||||
dynamic: false,
|
||||
ignoreForCollisions: true,
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
grabbable: false
|
||||
}
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
this.lineOn = function (closePoint, farPoint, color) {
|
||||
// draw a line
|
||||
if (this.pointer === null) {
|
||||
this.pointer = Entities.addEntity({
|
||||
type: "Line",
|
||||
name: "grab pointer",
|
||||
dimensions: LINE_ENTITY_DIMENSIONS,
|
||||
visible: true,
|
||||
position: closePoint,
|
||||
linePoints: [ZERO_VEC, farPoint],
|
||||
color: color,
|
||||
lifetime: LIFETIME,
|
||||
dynamic: false,
|
||||
ignoreForCollisions: true,
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
grabbable: false
|
||||
}
|
||||
})
|
||||
});
|
||||
} else {
|
||||
var age = Entities.getEntityProperties(this.pointer, "age").age;
|
||||
this.pointer = Entities.editEntity(this.pointer, {
|
||||
position: closePoint,
|
||||
linePoints: [ZERO_VEC, farPoint],
|
||||
color: color,
|
||||
lifetime: age + LIFETIME
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.searchSphereOn = function(location, size, color) {
|
||||
|
||||
|
@ -836,95 +798,13 @@ function MyController(hand) {
|
|||
var searchSphereLocation = Vec3.sum(distantPickRay.origin,
|
||||
Vec3.multiply(distantPickRay.direction, this.searchSphereDistance));
|
||||
this.searchSphereOn(searchSphereLocation, SEARCH_SPHERE_SIZE * this.searchSphereDistance,
|
||||
(this.triggerSmoothedGrab() || this.secondarySqueezed()) ? INTERSECT_COLOR : NO_INTERSECT_COLOR);
|
||||
if ((USE_OVERLAY_LINES_FOR_SEARCHING === true) && PICK_WITH_HAND_RAY) {
|
||||
(this.triggerSmoothedGrab() || this.secondarySqueezed()) ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE : COLORS_GRAB_SEARCHING_HALF_SQUEEZE);
|
||||
if (PICK_WITH_HAND_RAY) {
|
||||
this.overlayLineOn(handPosition, searchSphereLocation,
|
||||
(this.triggerSmoothedGrab() || this.secondarySqueezed()) ? INTERSECT_COLOR : NO_INTERSECT_COLOR);
|
||||
(this.triggerSmoothedGrab() || this.secondarySqueezed()) ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE : COLORS_GRAB_SEARCHING_HALF_SQUEEZE);
|
||||
}
|
||||
};
|
||||
|
||||
this.handleDistantParticleBeam = function (handPosition, objectPosition, color) {
|
||||
|
||||
var handToObject = Vec3.subtract(objectPosition, handPosition);
|
||||
var finalRotationObject = Quat.rotationBetween(Vec3.multiply(-1, Vec3.UP), handToObject);
|
||||
var distance = Vec3.distance(handPosition, objectPosition);
|
||||
var speed = distance * 3;
|
||||
var spread = 0;
|
||||
var lifespan = distance / speed;
|
||||
|
||||
if (this.particleBeamObject === null) {
|
||||
this.createParticleBeam(objectPosition, finalRotationObject, color, speed, spread, lifespan);
|
||||
} else {
|
||||
this.updateParticleBeam(objectPosition, finalRotationObject, color, speed, spread, lifespan);
|
||||
}
|
||||
};
|
||||
|
||||
this.createParticleBeam = function (positionObject, orientationObject, color, speed, spread, lifespan) {
|
||||
|
||||
var particleBeamPropertiesObject = {
|
||||
type: "ParticleEffect",
|
||||
isEmitting: true,
|
||||
position: positionObject,
|
||||
visible: false,
|
||||
lifetime: 60,
|
||||
"name": "Particle Beam",
|
||||
"color": color,
|
||||
"maxParticles": 2000,
|
||||
"lifespan": lifespan,
|
||||
"emitRate": 1000,
|
||||
"emitSpeed": speed,
|
||||
"speedSpread": spread,
|
||||
"emitOrientation": {
|
||||
"x": -1,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"emitDimensions": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"emitRadiusStart": 0.5,
|
||||
"polarStart": 0,
|
||||
"polarFinish": 0,
|
||||
"azimuthStart": -3.1415927410125732,
|
||||
"azimuthFinish": 3.1415927410125732,
|
||||
"emitAcceleration": {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
"accelerationSpread": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"particleRadius": 0.015,
|
||||
"radiusSpread": 0.005,
|
||||
"alpha": 1,
|
||||
"alphaSpread": 0,
|
||||
"alphaStart": 1,
|
||||
"alphaFinish": 1,
|
||||
"additiveBlending": 0,
|
||||
"textures": "https://hifi-content.s3.amazonaws.com/alan/dev/textures/grabsprite-3.png"
|
||||
};
|
||||
|
||||
this.particleBeamObject = Entities.addEntity(particleBeamPropertiesObject);
|
||||
};
|
||||
|
||||
this.updateParticleBeam = function (positionObject, orientationObject, color, speed, spread, lifespan) {
|
||||
Entities.editEntity(this.particleBeamObject, {
|
||||
rotation: orientationObject,
|
||||
position: positionObject,
|
||||
visible: true,
|
||||
color: color,
|
||||
emitSpeed: speed,
|
||||
speedSpread: spread,
|
||||
lifespan: lifespan
|
||||
});
|
||||
};
|
||||
|
||||
this.evalLightWorldTransform = function(modelPos, modelRot) {
|
||||
|
||||
var MODEL_LIGHT_POSITION = {
|
||||
|
@ -945,75 +825,6 @@ function MyController(hand) {
|
|||
};
|
||||
};
|
||||
|
||||
this.handleSpotlight = function (parentID) {
|
||||
var LIFETIME = 100;
|
||||
|
||||
var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']);
|
||||
|
||||
var lightTransform = this.evalLightWorldTransform(modelProperties.position, modelProperties.rotation);
|
||||
var lightProperties = {
|
||||
type: "Light",
|
||||
isSpotlight: true,
|
||||
dimensions: {
|
||||
x: 2,
|
||||
y: 2,
|
||||
z: 20
|
||||
},
|
||||
parentID: parentID,
|
||||
color: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
intensity: 2,
|
||||
exponent: 0.3,
|
||||
cutoff: 20,
|
||||
lifetime: LIFETIME,
|
||||
position: lightTransform.p
|
||||
};
|
||||
|
||||
if (this.spotlight === null) {
|
||||
this.spotlight = Entities.addEntity(lightProperties);
|
||||
} else {
|
||||
Entities.editEntity(this.spotlight, {
|
||||
// without this, this light would maintain rotation with its parent
|
||||
rotation: Quat.fromPitchYawRollDegrees(-90, 0, 0)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.handlePointLight = function (parentID) {
|
||||
var LIFETIME = 100;
|
||||
|
||||
var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']);
|
||||
var lightTransform = this.evalLightWorldTransform(modelProperties.position, modelProperties.rotation);
|
||||
|
||||
var lightProperties = {
|
||||
type: "Light",
|
||||
isSpotlight: false,
|
||||
dimensions: {
|
||||
x: 2,
|
||||
y: 2,
|
||||
z: 20
|
||||
},
|
||||
parentID: parentID,
|
||||
color: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
intensity: 2,
|
||||
exponent: 0.3,
|
||||
cutoff: 20,
|
||||
lifetime: LIFETIME,
|
||||
position: lightTransform.p
|
||||
};
|
||||
|
||||
if (this.pointlight === null) {
|
||||
this.pointlight = Entities.addEntity(lightProperties);
|
||||
}
|
||||
};
|
||||
|
||||
this.lineOff = function() {
|
||||
if (this.pointer !== null) {
|
||||
Entities.deleteEntity(this.pointer);
|
||||
|
@ -1037,37 +848,10 @@ function MyController(hand) {
|
|||
}
|
||||
};
|
||||
|
||||
this.particleBeamOff = function () {
|
||||
if (this.particleBeamObject !== null) {
|
||||
Entities.deleteEntity(this.particleBeamObject);
|
||||
this.particleBeamObject = null;
|
||||
}
|
||||
};
|
||||
|
||||
this.turnLightsOff = function () {
|
||||
if (this.spotlight !== null) {
|
||||
Entities.deleteEntity(this.spotlight);
|
||||
this.spotlight = null;
|
||||
}
|
||||
|
||||
if (this.pointlight !== null) {
|
||||
Entities.deleteEntity(this.pointlight);
|
||||
this.pointlight = null;
|
||||
}
|
||||
};
|
||||
|
||||
this.turnOffVisualizations = function() {
|
||||
if (USE_ENTITY_LINES_FOR_SEARCHING === true || USE_ENTITY_LINES_FOR_MOVING === true) {
|
||||
this.lineOff();
|
||||
}
|
||||
|
||||
if (USE_OVERLAY_LINES_FOR_SEARCHING === true || USE_OVERLAY_LINES_FOR_MOVING === true) {
|
||||
this.overlayLineOff();
|
||||
}
|
||||
|
||||
if (USE_PARTICLE_BEAM_FOR_MOVING === true) {
|
||||
this.particleBeamOff();
|
||||
}
|
||||
this.searchSphereOff();
|
||||
restore2DMode();
|
||||
|
||||
|
@ -1274,7 +1058,11 @@ function MyController(hand) {
|
|||
result.push({
|
||||
key: entityID.toString() + "0",
|
||||
entityID: entityID,
|
||||
localPosition: {x: 0, y: 0, z: 0},
|
||||
localPosition: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
worldPosition: entityXform.pos,
|
||||
radius: EQUIP_RADIUS,
|
||||
joints: wearableProps.joints,
|
||||
|
@ -1557,12 +1345,6 @@ function MyController(hand) {
|
|||
equipHotspotBuddy.highlightHotspot(potentialEquipHotspot);
|
||||
}
|
||||
|
||||
// search line visualizations
|
||||
if (USE_ENTITY_LINES_FOR_SEARCHING === true) {
|
||||
this.lineOn(rayPickInfo.searchRay.origin,
|
||||
Vec3.multiply(rayPickInfo.searchRay.direction, LINE_LENGTH),
|
||||
NO_INTERSECT_COLOR);
|
||||
}
|
||||
|
||||
this.searchIndicatorOn(rayPickInfo.searchRay);
|
||||
Reticle.setVisible(false);
|
||||
|
@ -1735,21 +1517,8 @@ function MyController(hand) {
|
|||
var handPosition = this.getHandPosition();
|
||||
|
||||
// visualizations
|
||||
if (USE_ENTITY_LINES_FOR_MOVING === true) {
|
||||
this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR);
|
||||
}
|
||||
if (USE_OVERLAY_LINES_FOR_MOVING === true) {
|
||||
this.overlayLineOn(handPosition, grabbedProperties.position, INTERSECT_COLOR);
|
||||
}
|
||||
if (USE_PARTICLE_BEAM_FOR_MOVING === true) {
|
||||
this.handleDistantParticleBeam(handPosition, grabbedProperties.position, INTERSECT_COLOR);
|
||||
}
|
||||
if (USE_POINTLIGHT === true) {
|
||||
this.handlePointLight(this.grabbedEntity);
|
||||
}
|
||||
if (USE_SPOTLIGHT === true) {
|
||||
this.handleSpotlight(this.grabbedEntity);
|
||||
}
|
||||
|
||||
this.overlayLineOn(handPosition, grabbedProperties.position, COLORS_GRAB_DISTANCE_HOLD);
|
||||
|
||||
var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition));
|
||||
var success = Entities.updateAction(this.grabbedEntity, this.actionID, {
|
||||
|
@ -1813,9 +1582,21 @@ function MyController(hand) {
|
|||
var pose = Controller.getPoseValue(standardControllerValue);
|
||||
var worldHandRotation = Quat.multiply(MyAvatar.orientation, pose.rotation);
|
||||
|
||||
var localHandUpAxis = this.hand === RIGHT_HAND ? {x: 1, y: 0, z: 0} : {x: -1, y: 0, z: 0};
|
||||
var localHandUpAxis = this.hand === RIGHT_HAND ? {
|
||||
x: 1,
|
||||
y: 0,
|
||||
z: 0
|
||||
} : {
|
||||
x: -1,
|
||||
y: 0,
|
||||
z: 0
|
||||
};
|
||||
var worldHandUpAxis = Vec3.multiplyQbyV(worldHandRotation, localHandUpAxis);
|
||||
var DOWN = {x: 0, y: -1, z: 0};
|
||||
var DOWN = {
|
||||
x: 0,
|
||||
y: -1,
|
||||
z: 0
|
||||
};
|
||||
|
||||
var DROP_ANGLE = Math.PI / 7;
|
||||
var HYSTERESIS_FACTOR = 1.1;
|
||||
|
@ -1932,8 +1713,16 @@ function MyController(hand) {
|
|||
}
|
||||
|
||||
Entities.editEntity(this.grabbedEntity, {
|
||||
velocity: {x: 0, y: 0, z: 0},
|
||||
angularVelocity: {x: 0, y: 0, z: 0},
|
||||
velocity: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
angularVelocity: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
dynamic: false
|
||||
});
|
||||
|
||||
|
@ -2156,7 +1945,6 @@ function MyController(hand) {
|
|||
};
|
||||
|
||||
this.release = function() {
|
||||
this.turnLightsOff();
|
||||
this.turnOffVisualizations();
|
||||
|
||||
var noVelocity = false;
|
||||
|
@ -2209,9 +1997,6 @@ function MyController(hand) {
|
|||
|
||||
this.cleanup = function() {
|
||||
this.release();
|
||||
Entities.deleteEntity(this.particleBeamObject);
|
||||
Entities.deleteEntity(this.spotLight);
|
||||
Entities.deleteEntity(this.pointLight);
|
||||
};
|
||||
|
||||
this.heartBeat = function(entityID) {
|
||||
|
@ -2298,7 +2083,9 @@ function MyController(hand) {
|
|||
// people are holding something and one of them will be able (if the other releases at the right time) to
|
||||
// bootstrap themselves with the held object. This happens because the meaning of "otherAvatar" in
|
||||
// the collision mask hinges on who the physics simulation owner is.
|
||||
Entities.editEntity(entityID, {"collidesWith": COLLIDES_WITH_WHILE_MULTI_GRABBED});
|
||||
Entities.editEntity(entityID, {
|
||||
"collidesWith": COLLIDES_WITH_WHILE_MULTI_GRABBED
|
||||
});
|
||||
}
|
||||
}
|
||||
setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data);
|
||||
|
@ -2312,7 +2099,9 @@ function MyController(hand) {
|
|||
var children = Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, handJointIndex);
|
||||
children.forEach(function(childID) {
|
||||
print("disconnecting stray child of hand: (" + _this.hand + ") " + childID);
|
||||
Entities.editEntity(childID, {parentID: NULL_UUID});
|
||||
Entities.editEntity(childID, {
|
||||
parentID: NULL_UUID
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -2322,7 +2111,9 @@ function MyController(hand) {
|
|||
// are delayed a bit. This keeps thrown things from colliding with the avatar's capsule so often.
|
||||
// The refcount is handled in this delayed fashion so things don't get confused if someone else
|
||||
// grabs the entity before the timeout fires.
|
||||
Entities.editEntity(entityID, { collidesWith: collidesWith });
|
||||
Entities.editEntity(entityID, {
|
||||
collidesWith: collidesWith
|
||||
});
|
||||
var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {});
|
||||
if (data && data["refCount"]) {
|
||||
data["refCount"] = data["refCount"] - 1;
|
||||
|
@ -2397,12 +2188,24 @@ function MyController(hand) {
|
|||
data["dynamic"] &&
|
||||
data["parentID"] == NULL_UUID &&
|
||||
!data["collisionless"]) {
|
||||
deactiveProps["velocity"] = {x: 0.0, y: 0.1, z: 0.0};
|
||||
deactiveProps["velocity"] = {
|
||||
x: 0.0,
|
||||
y: 0.1,
|
||||
z: 0.0
|
||||
};
|
||||
doSetVelocity = false;
|
||||
}
|
||||
if (noVelocity) {
|
||||
deactiveProps["velocity"] = {x: 0.0, y: 0.0, z: 0.0};
|
||||
deactiveProps["angularVelocity"] = {x: 0.0, y: 0.0, z: 0.0};
|
||||
deactiveProps["velocity"] = {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0
|
||||
};
|
||||
deactiveProps["angularVelocity"] = {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0
|
||||
};
|
||||
doSetVelocity = false;
|
||||
}
|
||||
|
||||
|
@ -2424,14 +2227,32 @@ function MyController(hand) {
|
|||
deactiveProps = {
|
||||
parentID: this.previousParentID,
|
||||
parentJointIndex: this.previousParentJointIndex,
|
||||
velocity: {x: 0.0, y: 0.0, z: 0.0},
|
||||
angularVelocity: {x: 0.0, y: 0.0, z: 0.0}
|
||||
velocity: {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0
|
||||
},
|
||||
angularVelocity: {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0
|
||||
}
|
||||
};
|
||||
Entities.editEntity(entityID, deactiveProps);
|
||||
} else if (noVelocity) {
|
||||
Entities.editEntity(entityID, {velocity: {x: 0.0, y: 0.0, z: 0.0},
|
||||
angularVelocity: {x: 0.0, y: 0.0, z: 0.0},
|
||||
dynamic: data["dynamic"]});
|
||||
Entities.editEntity(entityID, {
|
||||
velocity: {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0
|
||||
},
|
||||
angularVelocity: {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0
|
||||
},
|
||||
dynamic: data["dynamic"]
|
||||
});
|
||||
}
|
||||
} else {
|
||||
data = null;
|
||||
|
|
|
@ -45,6 +45,19 @@ var TARGET_MODEL_DIMENSIONS = {
|
|||
|
||||
};
|
||||
|
||||
|
||||
var COLORS_TELEPORT_CAN_TELEPORT = {
|
||||
red: 97,
|
||||
green: 247,
|
||||
blue: 255
|
||||
}
|
||||
|
||||
var COLORS_TELEPORT_CANNOT_TELEPORT = {
|
||||
red: 0,
|
||||
green: 121,
|
||||
blue: 141
|
||||
};
|
||||
|
||||
function ThumbPad(hand) {
|
||||
this.hand = hand;
|
||||
var _thisPad = this;
|
||||
|
@ -269,17 +282,13 @@ function Teleporter() {
|
|||
|
||||
this.rightPickRay = rightPickRay;
|
||||
|
||||
var location = Vec3.sum(rightPickRay.origin, Vec3.multiply(rightPickRay.direction, 500));
|
||||
var location = Vec3.sum(rightPickRay.origin, Vec3.multiply(rightPickRay.direction, 50));
|
||||
|
||||
|
||||
var rightIntersection = Entities.findRayIntersection(teleporter.rightPickRay, true, [], [this.targetEntity]);
|
||||
|
||||
if (rightIntersection.intersects) {
|
||||
this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, {
|
||||
red: 7,
|
||||
green: 36,
|
||||
blue: 44
|
||||
});
|
||||
this.rightLineOn(rightPickRay.origin, rightIntersection.intersection, COLORS_TELEPORT_CAN_TELEPORT);
|
||||
if (this.targetOverlay !== null) {
|
||||
this.updateTargetOverlay(rightIntersection);
|
||||
} else {
|
||||
|
@ -289,11 +298,7 @@ function Teleporter() {
|
|||
} else {
|
||||
|
||||
this.deleteTargetOverlay();
|
||||
this.rightLineOn(rightPickRay.origin, location, {
|
||||
red: 7,
|
||||
green: 36,
|
||||
blue: 44
|
||||
});
|
||||
this.rightLineOn(rightPickRay.origin, location, COLORS_TELEPORT_CANNOT_TELEPORT);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -318,18 +323,14 @@ function Teleporter() {
|
|||
|
||||
this.leftPickRay = leftPickRay;
|
||||
|
||||
var location = Vec3.sum(MyAvatar.position, Vec3.multiply(leftPickRay.direction, 500));
|
||||
var location = Vec3.sum(MyAvatar.position, Vec3.multiply(leftPickRay.direction, 50));
|
||||
|
||||
|
||||
var leftIntersection = Entities.findRayIntersection(teleporter.leftPickRay, true, [], [this.targetEntity]);
|
||||
|
||||
if (leftIntersection.intersects) {
|
||||
|
||||
this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, {
|
||||
red: 7,
|
||||
green: 36,
|
||||
blue: 44
|
||||
});
|
||||
this.leftLineOn(leftPickRay.origin, leftIntersection.intersection, COLORS_TELEPORT_CAN_TELEPORT);
|
||||
if (this.targetOverlay !== null) {
|
||||
this.updateTargetOverlay(leftIntersection);
|
||||
} else {
|
||||
|
@ -339,13 +340,8 @@ function Teleporter() {
|
|||
|
||||
} else {
|
||||
|
||||
|
||||
this.deleteTargetOverlay();
|
||||
this.leftLineOn(leftPickRay.origin, location, {
|
||||
red: 7,
|
||||
green: 36,
|
||||
blue: 44
|
||||
});
|
||||
this.leftLineOn(leftPickRay.origin, location, COLORS_TELEPORT_CANNOT_TELEPORT);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#include <gpu/gl/GLFramebuffer.h>
|
||||
#include <gpu/gl/GLTexture.h>
|
||||
|
||||
#include <WebEntityItem.h>
|
||||
#include <OctreeUtils.h>
|
||||
#include <render/Engine.h>
|
||||
#include <Model.h>
|
||||
|
@ -143,6 +144,19 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
static QString toHumanSize(size_t size, size_t maxUnit = std::numeric_limits<size_t>::max()) {
|
||||
static const std::vector<QString> SUFFIXES{ { "B", "KB", "MB", "GB", "TB", "PB" } };
|
||||
const size_t maxIndex = std::min(maxUnit, SUFFIXES.size() - 1);
|
||||
size_t suffixIndex = 0;
|
||||
|
||||
while (suffixIndex < maxIndex && size > 1024) {
|
||||
size >>= 10;
|
||||
++suffixIndex;
|
||||
}
|
||||
|
||||
return QString("%1 %2").arg(size).arg(SUFFIXES[suffixIndex]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Create a simple OpenGL window that renders text in various ways
|
||||
|
@ -211,6 +225,9 @@ public:
|
|||
AbstractViewStateInterface::setInstance(this);
|
||||
_octree = DependencyManager::set<EntityTreeRenderer>(false, this, nullptr);
|
||||
_octree->init();
|
||||
// Prevent web entities from rendering
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Web, WebEntityItem::factory)
|
||||
|
||||
DependencyManager::set<ParentFinder>(_octree->getTree());
|
||||
getEntities()->setViewFrustum(_viewFrustum);
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
@ -296,6 +313,10 @@ protected:
|
|||
reloadScene();
|
||||
return;
|
||||
|
||||
case Qt::Key_F4:
|
||||
toggleStereo();
|
||||
return;
|
||||
|
||||
case Qt::Key_F5:
|
||||
goTo();
|
||||
return;
|
||||
|
@ -365,6 +386,17 @@ private:
|
|||
|
||||
renderArgs.setViewFrustum(_viewFrustum);
|
||||
|
||||
renderArgs._context->enableStereo(_stereoEnabled);
|
||||
if (_stereoEnabled) {
|
||||
mat4 eyeOffsets[2];
|
||||
mat4 eyeProjections[2];
|
||||
for (size_t i = 0; i < 2; ++i) {
|
||||
eyeProjections[i] = _viewFrustum.getProjection();
|
||||
}
|
||||
renderArgs._context->setStereoProjections(eyeProjections);
|
||||
renderArgs._context->setStereoViews(eyeOffsets);
|
||||
}
|
||||
|
||||
// Final framebuffer that will be handled to the display-plugin
|
||||
{
|
||||
auto finalFramebuffer = framebufferCache->getFramebuffer();
|
||||
|
@ -388,7 +420,7 @@ private:
|
|||
}
|
||||
|
||||
{
|
||||
_textOverlay->render();
|
||||
//_textOverlay->render();
|
||||
}
|
||||
|
||||
_context.swapBuffers(this);
|
||||
|
@ -429,6 +461,9 @@ private:
|
|||
const qint64& now;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
void updateText() {
|
||||
//qDebug() << "FPS " << fps.rate();
|
||||
{
|
||||
|
@ -438,6 +473,11 @@ private:
|
|||
infoTextBlock.push_back({ vec2(100, 10), std::to_string((uint32_t)_fps), TextOverlay::alignLeft });
|
||||
infoTextBlock.push_back({ vec2(98, 30), "Culling: ", TextOverlay::alignRight });
|
||||
infoTextBlock.push_back({ vec2(100, 30), _cullingEnabled ? "Enabled" : "Disabled", TextOverlay::alignLeft });
|
||||
|
||||
setTitle(QString("FPS %1 Culling %2 TextureMemory GPU %3 CPU %4")
|
||||
.arg(_fps).arg(_cullingEnabled)
|
||||
.arg(toHumanSize(gpu::Context::getTextureGPUMemoryUsage(), 2))
|
||||
.arg(toHumanSize(gpu::Texture::getTextureCPUMemoryUsage(), 2)));
|
||||
}
|
||||
|
||||
_textOverlay->beginTextUpdate();
|
||||
|
@ -561,14 +601,29 @@ private:
|
|||
void importScene(const QString& fileName) {
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
QFileInfo fileInfo(fileName);
|
||||
//assetClient->loadLocalMappings(fileInfo.absolutePath() + "/" + fileInfo.baseName() + ".atp");
|
||||
QString atpPath = fileInfo.absolutePath() + "/" + fileInfo.baseName() + ".atp";
|
||||
qDebug() << atpPath;
|
||||
QFileInfo atpPathInfo(atpPath);
|
||||
if (atpPathInfo.exists()) {
|
||||
QString atpUrl = QUrl::fromLocalFile(atpPath).toString();
|
||||
ResourceManager::setUrlPrefixOverride("atp:/", atpUrl + "/");
|
||||
}
|
||||
_settings.setValue(LAST_SCENE_KEY, fileName);
|
||||
_octree->clear();
|
||||
_octree->getTree()->readFromURL(fileName);
|
||||
}
|
||||
|
||||
void importScene() {
|
||||
QString fileName = QFileDialog::getOpenFileName(nullptr, tr("Open File"), "/home", tr("Hifi Exports (*.json *.svo)"));
|
||||
auto lastScene = _settings.value(LAST_SCENE_KEY);
|
||||
QString openDir;
|
||||
if (lastScene.isValid()) {
|
||||
QFileInfo lastSceneInfo(lastScene.toString());
|
||||
if (lastSceneInfo.absoluteDir().exists()) {
|
||||
openDir = lastSceneInfo.absolutePath();
|
||||
}
|
||||
}
|
||||
|
||||
QString fileName = QFileDialog::getOpenFileName(nullptr, tr("Open File"), openDir, tr("Hifi Exports (*.json *.svo)"));
|
||||
if (fileName.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
@ -617,6 +672,10 @@ private:
|
|||
_cullingEnabled = !_cullingEnabled;
|
||||
}
|
||||
|
||||
void toggleStereo() {
|
||||
_stereoEnabled = !_stereoEnabled;
|
||||
}
|
||||
|
||||
QSharedPointer<EntityTreeRenderer> getEntities() {
|
||||
return _octree;
|
||||
}
|
||||
|
@ -665,6 +724,7 @@ private:
|
|||
float _fps { 0 };
|
||||
TextOverlay* _textOverlay;
|
||||
bool _cullingEnabled { true };
|
||||
bool _stereoEnabled { false };
|
||||
QSharedPointer<EntityTreeRenderer> _octree;
|
||||
};
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import "../../../interface/resources/qml/styles-uit"
|
|||
|
||||
ApplicationWindow {
|
||||
id: appWindow
|
||||
objectName: "MainWindow"
|
||||
visible: true
|
||||
width: 1280
|
||||
height: 800
|
||||
|
@ -93,9 +94,6 @@ ApplicationWindow {
|
|||
onClicked: testButtons.lastButton.visible = !testButtons.lastButton.visible
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Error alerts
|
||||
/*
|
||||
Button {
|
||||
|
@ -350,6 +348,11 @@ ApplicationWindow {
|
|||
}
|
||||
*/
|
||||
|
||||
Browser {
|
||||
url: "http://s3.amazonaws.com/DreamingContent/testUiDelegates.html"
|
||||
}
|
||||
|
||||
|
||||
Window {
|
||||
id: blue
|
||||
closable: true
|
||||
|
|
|
@ -16,6 +16,8 @@ QML_IMPORT_PATH = ../../interface/resources/qml
|
|||
|
||||
DISTFILES += \
|
||||
qml/*.qml \
|
||||
../../interface/resources/QtWebEngine/UIDelegates/original/*.qml \
|
||||
../../interface/resources/QtWebEngine/UIDelegates/*.qml \
|
||||
../../interface/resources/qml/*.qml \
|
||||
../../interface/resources/qml/controls/*.qml \
|
||||
../../interface/resources/qml/controls-uit/*.qml \
|
||||
|
|
|
@ -33,6 +33,28 @@ protected:
|
|||
const QString _name;
|
||||
};
|
||||
|
||||
class Reticle : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QPoint position READ getPosition CONSTANT)
|
||||
public:
|
||||
|
||||
Reticle(QObject* parent) : QObject(parent) {
|
||||
}
|
||||
|
||||
QPoint getPosition() {
|
||||
if (!_window) {
|
||||
return QPoint(0, 0);
|
||||
}
|
||||
return _window->mapFromGlobal(QCursor::pos());
|
||||
}
|
||||
|
||||
void setWindow(QWindow* window) {
|
||||
_window = window;
|
||||
}
|
||||
|
||||
private:
|
||||
QWindow* _window{nullptr};
|
||||
};
|
||||
|
||||
QString getRelativeDir(const QString& relativePath = ".") {
|
||||
QDir path(__FILE__); path.cdUp();
|
||||
|
@ -61,10 +83,8 @@ void setChild(QQmlApplicationEngine& engine, const char* name) {
|
|||
qWarning() << "Could not find object named " << name;
|
||||
}
|
||||
|
||||
void addImportPath(QQmlApplicationEngine& engine, const QString& relativePath) {
|
||||
QString resolvedPath = getRelativeDir("../qml");
|
||||
QUrl resolvedUrl = QUrl::fromLocalFile(resolvedPath);
|
||||
resolvedPath = resolvedUrl.toString();
|
||||
void addImportPath(QQmlApplicationEngine& engine, const QString& relativePath, bool insert = false) {
|
||||
QString resolvedPath = getRelativeDir(relativePath);
|
||||
engine.addImportPath(resolvedPath);
|
||||
}
|
||||
|
||||
|
@ -79,8 +99,9 @@ int main(int argc, char *argv[]) {
|
|||
qmlRegisterType<Preference>("Hifi", 1, 0, "Preference");
|
||||
|
||||
QQmlApplicationEngine engine;
|
||||
addImportPath(engine, "../qml");
|
||||
addImportPath(engine, "../../../interface/resources/qml");
|
||||
addImportPath(engine, "qml");
|
||||
addImportPath(engine, "../../interface/resources/qml");
|
||||
addImportPath(engine, "../../interface/resources");
|
||||
engine.load(QUrl(QStringLiteral("qml/Stubs.qml")));
|
||||
|
||||
setChild(engine, "offscreenFlags");
|
||||
|
@ -99,6 +120,15 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
//engine.load(QUrl(QStringLiteral("qrc:/qml/gallery/main.qml")));
|
||||
engine.load(QUrl(QStringLiteral("qml/main.qml")));
|
||||
for (QObject* rootObject : engine.rootObjects()) {
|
||||
if (rootObject->objectName() == "MainWindow") {
|
||||
Reticle* reticle = new Reticle(rootObject);
|
||||
reticle->setWindow((QWindow*)rootObject);
|
||||
engine.rootContext()->setContextProperty("Reticle", reticle);
|
||||
engine.rootContext()->setContextProperty("Window", rootObject);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue