mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 11:45:36 +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
|
||||
|
@ -157,4 +239,4 @@ ScrollingWindow {
|
|||
break;
|
||||
}
|
||||
}
|
||||
} // dialog
|
||||
} // dialog
|
|
@ -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
|
||||
_hipsParentIndex = _skeleton->getParentIndex(_hipsIndex);
|
||||
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();
|
||||
stream << static_cast<quint32>(length);
|
||||
stream << data;
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -232,7 +239,7 @@ int64_t AudioInjector::injectNextFrame() {
|
|||
static int positionOptionOffset = -1;
|
||||
static int volumeOptionOffset = -1;
|
||||
static int audioDataOffset = -1;
|
||||
|
||||
|
||||
if (!_currentPacket) {
|
||||
if (_currentSendOffset < 0 ||
|
||||
_currentSendOffset >= _audioData.size()) {
|
||||
|
@ -270,7 +277,7 @@ int64_t AudioInjector::injectNextFrame() {
|
|||
|
||||
// current injectors don't use codecs, so pack in the unknown codec name
|
||||
QString noCodecForInjectors("");
|
||||
writeStringToStream(noCodecForInjectors, audioPacketStream);
|
||||
writeStringToStream(noCodecForInjectors, audioPacketStream);
|
||||
|
||||
// pack stream identifier (a generated UUID)
|
||||
audioPacketStream << QUuid::createUuid();
|
||||
|
@ -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";
|
||||
// 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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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,38 +1054,41 @@ void AvatarData::detachAll(const QString& modelURL, const QString& jointName) {
|
|||
void AvatarData::setJointMappingsFromNetworkReply() {
|
||||
QNetworkReply* networkReply = static_cast<QNetworkReply*>(sender());
|
||||
|
||||
QByteArray line;
|
||||
while (!(line = networkReply->readLine()).isEmpty()) {
|
||||
line = line.trimmed();
|
||||
if (line.startsWith("filename")) {
|
||||
int filenameIndex = line.indexOf('=') + 1;
|
||||
if (filenameIndex > 0) {
|
||||
_skeletonFBXURL = _skeletonModelURL.resolved(QString(line.mid(filenameIndex).trimmed()));
|
||||
{
|
||||
QWriteLocker writeLock(&_jointDataLock);
|
||||
QByteArray line;
|
||||
while (!(line = networkReply->readLine()).isEmpty()) {
|
||||
line = line.trimmed();
|
||||
if (line.startsWith("filename")) {
|
||||
int filenameIndex = line.indexOf('=') + 1;
|
||||
if (filenameIndex > 0) {
|
||||
_skeletonFBXURL = _skeletonModelURL.resolved(QString(line.mid(filenameIndex).trimmed()));
|
||||
}
|
||||
}
|
||||
if (!line.startsWith("jointIndex")) {
|
||||
continue;
|
||||
}
|
||||
if (!line.startsWith("jointIndex")) {
|
||||
continue;
|
||||
}
|
||||
int jointNameIndex = line.indexOf('=') + 1;
|
||||
if (jointNameIndex == 0) {
|
||||
continue;
|
||||
}
|
||||
int secondSeparatorIndex = line.indexOf('=', jointNameIndex);
|
||||
if (secondSeparatorIndex == -1) {
|
||||
continue;
|
||||
}
|
||||
QString jointName = line.mid(jointNameIndex, secondSeparatorIndex - jointNameIndex).trimmed();
|
||||
bool ok;
|
||||
int jointIndex = line.mid(secondSeparatorIndex + 1).trimmed().toInt(&ok);
|
||||
if (ok) {
|
||||
while (_jointNames.size() < jointIndex + 1) {
|
||||
_jointNames.append(QString());
|
||||
int jointNameIndex = line.indexOf('=') + 1;
|
||||
if (jointNameIndex == 0) {
|
||||
continue;
|
||||
}
|
||||
int secondSeparatorIndex = line.indexOf('=', jointNameIndex);
|
||||
if (secondSeparatorIndex == -1) {
|
||||
continue;
|
||||
}
|
||||
QString jointName = line.mid(jointNameIndex, secondSeparatorIndex - jointNameIndex).trimmed();
|
||||
bool ok;
|
||||
int jointIndex = line.mid(secondSeparatorIndex + 1).trimmed().toInt(&ok);
|
||||
if (ok) {
|
||||
while (_jointNames.size() < jointIndex + 1) {
|
||||
_jointNames.append(QString());
|
||||
}
|
||||
_jointNames[jointIndex] = jointName;
|
||||
}
|
||||
_jointNames[jointIndex] = jointName;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < _jointNames.size(); i++) {
|
||||
_jointIndices.insert(_jointNames.at(i), i + 1);
|
||||
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() {
|
||||
_jointIndices.clear();
|
||||
_jointNames.clear();
|
||||
_jointData.clear();
|
||||
{
|
||||
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);
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -622,4 +618,4 @@ function cleanup() {
|
|||
if (teleporter.updateConnected !== null) {
|
||||
Script.update.disconnect(teleporter.update);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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