Merge branch 'master' of github.com:highfidelity/hifi into groups

This commit is contained in:
Seth Alves 2016-07-25 13:19:13 -07:00
commit e3b4612283
37 changed files with 947 additions and 537 deletions

View 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();
});
}
}

View file

@ -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();
});
}
}

View 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));
});
}
}

View 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
}
}
}

View 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();
}
}
}

View file

@ -0,0 +1,6 @@
import QtQuick 2.5
Item {
width: 100
height: 20
}

View file

@ -0,0 +1,4 @@
import QtQuick 2.5
Item {
}

View 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();
});
}
}

View 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

View file

@ -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

View file

@ -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) {

View file

@ -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();
}

View file

@ -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 };

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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
}

View file

@ -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
};

View file

@ -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());
}

View file

@ -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) {

View file

@ -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 };
};

View file

@ -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()) {

View file

@ -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;

View file

@ -1192,6 +1192,8 @@ void Model::deleteGeometry() {
_meshStates.clear();
_rig->destroyAnimGraph();
_blendedBlendshapeCoefficients.clear();
_renderGeometry.reset();
_collisionGeometry.reset();
}
AABox Model::getRenderableMeshBound() const {

View file

@ -20,7 +20,7 @@ struct Grid {
};
uniform gridBuffer { Grid grid; };
Grid getGrid() { return grid; };
Grid getGrid() { return grid; }
in vec2 varTexCoord0;
in vec4 varColor;

View file

@ -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;
}

View file

@ -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);
};

View file

@ -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;

View file

@ -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);
}
};

View file

@ -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;
};

View file

@ -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

View file

@ -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 \

View file

@ -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();
}