diff --git a/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml b/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml index 73819839a1..e2172d8eda 100644 --- a/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml @@ -34,7 +34,7 @@ Preference { left: parent.left right: parent.right } - height: isFirstCheckBox ? hifi.dimensions.controlInterlineHeight : 0 + height: isFirstCheckBox && !preference.indented ? 16 : 2 } CheckBox { @@ -54,6 +54,7 @@ Preference { left: parent.left right: parent.right bottom: parent.bottom + leftMargin: preference.indented ? 20 : 0 } text: root.label colorScheme: hifi.colorSchemes.dark diff --git a/interface/resources/qml/dialogs/preferences/RadioButtonsPreference.qml b/interface/resources/qml/dialogs/preferences/RadioButtonsPreference.qml index 77c94c2ae5..103904a666 100644 --- a/interface/resources/qml/dialogs/preferences/RadioButtonsPreference.qml +++ b/interface/resources/qml/dialogs/preferences/RadioButtonsPreference.qml @@ -11,14 +11,27 @@ import QtQuick 2.5 import "../../controls-uit" +import "../../styles-uit" Preference { id: root - + height: control.height + hifi.dimensions.controlInterlineHeight + property int value: 0 + Component.onCompleted: { - repeater.itemAt(preference.value).checked = true + value = preference.value; + repeater.itemAt(preference.value).checked = true; + } + + function updateValue() { + for (var i = 0; i < repeater.count; i++) { + if (repeater.itemAt(i).checked) { + value = i; + break; + } + } } function save() { @@ -33,24 +46,36 @@ Preference { preference.save(); } - Row { + Column { id: control anchors { left: parent.left right: parent.right bottom: parent.bottom } - spacing: 5 + spacing: 3 + + RalewaySemiBold { + id: heading + size: hifi.fontSizes.inputLabel + text: preference.heading + color: hifi.colors.lightGrayText + visible: text !== "" + bottomPadding: 3 + } Repeater { id: repeater model: preference.items.length delegate: RadioButton { text: preference.items[index] + letterSpacing: 0 anchors { - verticalCenter: parent.verticalCenter + left: parent.left } + leftPadding: 0 colorScheme: hifi.colorSchemes.dark + onClicked: updateValue(); } } } diff --git a/interface/resources/qml/dialogs/preferences/Section.qml b/interface/resources/qml/dialogs/preferences/Section.qml index 0284af9d9c..c2c6583b7e 100644 --- a/interface/resources/qml/dialogs/preferences/Section.qml +++ b/interface/resources/qml/dialogs/preferences/Section.qml @@ -138,11 +138,12 @@ Preference { break; case Preference.PrimaryHand: - checkBoxCount++; + checkBoxCount = 0; builder = primaryHandBuilder; break; + case Preference.RadioButtons: - checkBoxCount++; + checkBoxCount = 0; builder = radioButtonsBuilder; break; }; diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml index ec2c003383..3708f75114 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml @@ -122,6 +122,22 @@ Item { } } + // Runtime customization of preferences. + var locomotionPreference = findPreference("VR Movement", "Teleporting only / Walking and teleporting"); + var flyingPreference = findPreference("VR Movement", "Jumping and flying"); + if (locomotionPreference && flyingPreference) { + flyingPreference.visible = (locomotionPreference.value === 1); + locomotionPreference.valueChanged.connect(function () { + flyingPreference.visible = (locomotionPreference.value === 1); + }); + } + if (HMD.isHeadControllerAvailable("Oculus")) { + var boundariesPreference = findPreference("VR Movement", "Show room boundaries while teleporting"); + if (boundariesPreference) { + boundariesPreference.label = "Show room boundaries and sensors while teleporting"; + } + } + if (sections.length) { // Default sections to expanded/collapsed as appropriate for dialog. if (sections.length === 1) { @@ -234,4 +250,32 @@ Item { } } } + + function findPreference(category, name) { + var section = null; + var preference = null; + var i; + + // Find category section. + i = 0; + while (!section && i < sections.length) { + if (sections[i].name === category) { + section = sections[i]; + } + i++; + } + + // Find named preference. + if (section) { + i = 0; + while (!preference && i < section.preferences.length) { + if (section.preferences[i].preference && section.preferences[i].preference.name === name) { + preference = section.preferences[i]; + } + i++; + } + } + + return preference; + } } diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml index 833175f311..6ac3f706e4 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml @@ -153,11 +153,12 @@ Preference { break; case Preference.PrimaryHand: - checkBoxCount++; + checkBoxCount = 0; builder = primaryHandBuilder; break; + case Preference.RadioButtons: - checkBoxCount++; + checkBoxCount = 0; builder = radioButtonsBuilder; break; }; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 497ea351a0..086cbb3425 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -106,6 +106,7 @@ MyAvatar::MyAvatar(QThread* thread) : _realWorldFieldOfView("realWorldFieldOfView", DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES), _useAdvancedMovementControls("advancedMovementForHandControllersIsChecked", true), + _showPlayArea("showPlayArea", true), _smoothOrientationTimer(std::numeric_limits::max()), _smoothOrientationInitial(), _smoothOrientationTarget(), @@ -3307,7 +3308,7 @@ float MyAvatar::getRawDriveKey(DriveKeys key) const { } void MyAvatar::relayDriveKeysToCharacterController() { - if (getDriveKey(TRANSLATE_Y) > 0.0f) { + if (getDriveKey(TRANSLATE_Y) > 0.0f && (!qApp->isHMDMode() || (useAdvancedMovementControls() && getFlyingHMDPref()))) { _characterController.jump(); } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index c861ee48a4..16b765711a 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -122,8 +122,10 @@ class MyAvatar : public Avatar { * zone may disallow collisionless avatars. * @property {boolean} characterControllerEnabled - Synonym of collisionsEnabled. * Deprecated: Use collisionsEnabled instead. - * @property {boolean} useAdvancedMovementControls - Returns the value of the Interface setting, Settings > Advanced - * Movement for Hand Controller. Note: Setting the value has no effect unless Interface is restarted. + * @property {boolean} useAdvancedMovementControls - Returns and sets the value of the Interface setting, Settings > + * Walking and teleporting. Note: Setting the value has no effect unless Interface is restarted. + * @property {boolean} showPlayArea - Returns and sets the value of the Interface setting, Settings > Show room boundaries + * while teleporting. Note: Setting the value has no effect unless Interface is restarted. * @property {number} yawSpeed=75 * @property {number} pitchSpeed=50 * @property {boolean} hmdRollControlEnabled=true - If true, the roll angle of your HMD turns your avatar @@ -223,6 +225,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(bool collisionsEnabled READ getCollisionsEnabled WRITE setCollisionsEnabled) Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled) Q_PROPERTY(bool useAdvancedMovementControls READ useAdvancedMovementControls WRITE setUseAdvancedMovementControls) + Q_PROPERTY(bool showPlayArea READ getShowPlayArea WRITE setShowPlayArea) Q_PROPERTY(float yawSpeed MEMBER _yawSpeed) Q_PROPERTY(float pitchSpeed MEMBER _pitchSpeed) @@ -542,6 +545,9 @@ public: void setUseAdvancedMovementControls(bool useAdvancedMovementControls) { _useAdvancedMovementControls.set(useAdvancedMovementControls); } + bool getShowPlayArea() const { return _showPlayArea.get(); } + void setShowPlayArea(bool showPlayArea) { _showPlayArea.set(showPlayArea); } + void setHMDRollControlEnabled(bool value) { _hmdRollControlEnabled = value; } bool getHMDRollControlEnabled() const { return _hmdRollControlEnabled; } void setHMDRollControlDeadZone(float value) { _hmdRollControlDeadZone = value; } @@ -1631,6 +1637,7 @@ private: Setting::Handle _realWorldFieldOfView; Setting::Handle _useAdvancedMovementControls; + Setting::Handle _showPlayArea; // Smoothing. const float SMOOTH_TIME_ORIENTATION = 0.5f; diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index 33fa8738d9..9c33cb4637 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -18,6 +18,7 @@ const glm::vec4 ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR { 1.0f }; const float ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH { 0.01f }; const bool ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA { false }; +const bool ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_DRAWINFRONT { false }; gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::_parabolaPipeline { nullptr }; gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::_transparentParabolaPipeline { nullptr }; @@ -46,6 +47,7 @@ void ParabolaPointer::editRenderStatePath(const std::string& state, const QVaria float alpha = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR.a; float width = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH; bool isVisibleInSecondaryCamera = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA; + bool drawInFront = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_DRAWINFRONT; bool enabled = false; if (!pathMap.isEmpty()) { enabled = true; @@ -63,8 +65,11 @@ void ParabolaPointer::editRenderStatePath(const std::string& state, const QVaria if (pathMap["isVisibleInSecondaryCamera"].isValid()) { isVisibleInSecondaryCamera = pathMap["isVisibleInSecondaryCamera"].toBool(); } + if (pathMap["drawInFront"].isValid()) { + drawInFront = pathMap["drawInFront"].toBool(); + } } - renderState->editParabola(color, alpha, width, isVisibleInSecondaryCamera, enabled); + renderState->editParabola(color, alpha, width, isVisibleInSecondaryCamera, drawInFront, enabled); } } @@ -146,7 +151,7 @@ void ParabolaPointer::setVisualPickResultInternal(PickResultPointer pickResult, } ParabolaPointer::RenderState::RenderState(const OverlayID& startID, const OverlayID& endID, const glm::vec3& pathColor, float pathAlpha, float pathWidth, - bool isVisibleInSecondaryCamera, bool pathEnabled) : + bool isVisibleInSecondaryCamera, bool drawInFront, bool pathEnabled) : StartEndRenderState(startID, endID) { render::Transaction transaction; @@ -154,7 +159,7 @@ ParabolaPointer::RenderState::RenderState(const OverlayID& startID, const Overla _pathID = scene->allocateID(); _pathWidth = pathWidth; if (render::Item::isValidID(_pathID)) { - auto renderItem = std::make_shared(pathColor, pathAlpha, pathWidth, isVisibleInSecondaryCamera, pathEnabled); + auto renderItem = std::make_shared(pathColor, pathAlpha, pathWidth, isVisibleInSecondaryCamera, drawInFront, pathEnabled); transaction.resetItem(_pathID, std::make_shared(renderItem)); scene->enqueueTransaction(transaction); } @@ -182,15 +187,16 @@ void ParabolaPointer::RenderState::disable() { } } -void ParabolaPointer::RenderState::editParabola(const glm::vec3& color, float alpha, float width, bool isVisibleInSecondaryCamera, bool enabled) { +void ParabolaPointer::RenderState::editParabola(const glm::vec3& color, float alpha, float width, bool isVisibleInSecondaryCamera, bool drawInFront, bool enabled) { if (render::Item::isValidID(_pathID)) { render::Transaction transaction; auto scene = qApp->getMain3DScene(); - transaction.updateItem(_pathID, [color, alpha, width, isVisibleInSecondaryCamera, enabled](ParabolaRenderItem& item) { + transaction.updateItem(_pathID, [color, alpha, width, isVisibleInSecondaryCamera, drawInFront, enabled](ParabolaRenderItem& item) { item.setColor(color); item.setAlpha(alpha); item.setWidth(width); item.setIsVisibleInSecondaryCamera(isVisibleInSecondaryCamera); + item.setDrawInFront(drawInFront); item.setEnabled(enabled); item.updateKey(); }); @@ -238,6 +244,7 @@ std::shared_ptr ParabolaPointer::buildRenderState(const QVa float alpha = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR.a; float width = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH; bool isVisibleInSecondaryCamera = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA; + bool drawInFront = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_DRAWINFRONT; bool enabled = false; if (propMap["path"].isValid()) { enabled = true; @@ -258,6 +265,10 @@ std::shared_ptr ParabolaPointer::buildRenderState(const QVa if (pathMap["isVisibleInSecondaryCamera"].isValid()) { isVisibleInSecondaryCamera = pathMap["isVisibleInSecondaryCamera"].toBool(); } + + if (pathMap["drawInFront"].isValid()) { + drawInFront = pathMap["drawInFront"].toBool(); + } } QUuid endID; @@ -269,7 +280,7 @@ std::shared_ptr ParabolaPointer::buildRenderState(const QVa } } - return std::make_shared(startID, endID, color, alpha, width, isVisibleInSecondaryCamera, enabled); + return std::make_shared(startID, endID, color, alpha, width, isVisibleInSecondaryCamera, drawInFront, enabled); } PointerEvent ParabolaPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) { @@ -321,8 +332,8 @@ glm::vec3 ParabolaPointer::findIntersection(const PickedObject& pickedObject, co } ParabolaPointer::RenderState::ParabolaRenderItem::ParabolaRenderItem(const glm::vec3& color, float alpha, float width, - bool isVisibleInSecondaryCamera, bool enabled) : - _isVisibleInSecondaryCamera(isVisibleInSecondaryCamera), _enabled(enabled) + bool isVisibleInSecondaryCamera, bool drawInFront, bool enabled) : + _isVisibleInSecondaryCamera(isVisibleInSecondaryCamera), _drawInFront(drawInFront), _enabled(enabled) { _uniformBuffer->resize(sizeof(ParabolaData)); setColor(color); @@ -358,6 +369,10 @@ void ParabolaPointer::RenderState::ParabolaRenderItem::updateKey() { builder.withTagBits(render::hifi::TAG_MAIN_VIEW); } + if (_drawInFront) { + builder.withLayer(render::hifi::LAYER_3D_FRONT); + } + _key = builder.build(); } diff --git a/interface/src/raypick/ParabolaPointer.h b/interface/src/raypick/ParabolaPointer.h index 8fb864c07b..ae0bd086e2 100644 --- a/interface/src/raypick/ParabolaPointer.h +++ b/interface/src/raypick/ParabolaPointer.h @@ -21,7 +21,7 @@ public: using Pointer = Payload::DataPointer; ParabolaRenderItem(const glm::vec3& color, float alpha, float width, - bool isVisibleInSecondaryCamera, bool enabled); + bool isVisibleInSecondaryCamera, bool drawInFront, bool enabled); ~ParabolaRenderItem() {} static gpu::PipelinePointer _parabolaPipeline; @@ -46,11 +46,13 @@ public: void setAcceleration(const glm::vec3& acceleration) { _parabolaData.acceleration = acceleration; } void setOrigin(const glm::vec3& origin) { _origin = origin; } void setIsVisibleInSecondaryCamera(const bool& isVisibleInSecondaryCamera) { _isVisibleInSecondaryCamera = isVisibleInSecondaryCamera; } + void setDrawInFront(const bool& drawInFront) { _drawInFront = drawInFront; } void setEnabled(const bool& enabled) { _enabled = enabled; } static const glm::vec4 DEFAULT_PARABOLA_COLOR; static const float DEFAULT_PARABOLA_WIDTH; static const bool DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA; + static const bool DEFAULT_PARABOLA_DRAWINFRONT; private: render::Item::Bound _bound; @@ -58,6 +60,7 @@ public: glm::vec3 _origin { 0.0f }; bool _isVisibleInSecondaryCamera { DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA }; + bool _drawInFront { DEFAULT_PARABOLA_DRAWINFRONT }; bool _visible { false }; bool _enabled { false }; @@ -77,7 +80,7 @@ public: RenderState() {} RenderState(const OverlayID& startID, const OverlayID& endID, const glm::vec3& pathColor, float pathAlpha, float pathWidth, - bool isVisibleInSecondaryCamera, bool pathEnabled); + bool isVisibleInSecondaryCamera, bool drawInFront, bool pathEnabled); void setPathWidth(float width) { _pathWidth = width; } float getPathWidth() const { return _pathWidth; } @@ -87,7 +90,7 @@ public: void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) override; - void editParabola(const glm::vec3& color, float alpha, float width, bool isVisibleInSecondaryCamera, bool enabled); + void editParabola(const glm::vec3& color, float alpha, float width, bool isVisibleInSecondaryCamera, bool drawInFront, bool enabled); private: int _pathID; diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index 7209e402a1..81ab023d20 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -218,6 +218,7 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope * @property {number} alpha=1.0 The alpha of the parabola. * @property {number} width=0.01 The width of the parabola, in meters. * @property {boolean} isVisibleInSecondaryCamera=false The width of the parabola, in meters. +* @property {boolean} drawInFront=false If true, the parabola is rendered in front of other items in the scene. */ /**jsdoc * A set of properties used to define the visual aspect of a Parabola Pointer in the case that the Pointer is not intersecting something. Same as a {@link Pointers.ParabolaPointerRenderState}, @@ -393,4 +394,4 @@ QVariantMap PointerScriptingInterface::getPrevPickResult(unsigned int uid) const QVariantMap PointerScriptingInterface::getPointerProperties(unsigned int uid) const { return DependencyManager::get()->getPointerProperties(uid); -} \ No newline at end of file +} diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 94f1d62552..2677f37fae 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -205,7 +205,7 @@ public: /**jsdoc * Returns information about an existing Pointer - * @function Pointers.getPointerState + * @function Pointers.getPointerProperties * @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}. * @returns {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties|Pointers.ParabolaPointerProperties} The information about the Pointer. * Currently only includes renderStates and defaultRenderStates with associated overlay IDs. diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index b648e125bf..4ba3813c4a 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -241,4 +241,4 @@ glm::vec2 StylusPointer::findPos2D(const PickedObject& pickedObject, const glm:: default: return glm::vec2(NAN); } -} \ No newline at end of file +} diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index ad8e265a01..ea24d6c793 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -201,3 +201,12 @@ bool HMDScriptingInterface::isKeyboardVisible() { void HMDScriptingInterface::centerUI() { QMetaObject::invokeMethod(qApp, "centerUI", Qt::QueuedConnection); } + +QVariant HMDScriptingInterface::getPlayAreaRect() { + auto rect = qApp->getActiveDisplayPlugin()->getPlayAreaRect(); + return qRectFToVariant(rect); +} + +QVector HMDScriptingInterface::getSensorPositions() { + return qApp->getActiveDisplayPlugin()->getSensorPositions(); +} diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index 78744b320b..2c0a3fe45f 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -61,6 +61,8 @@ class QScriptEngine; * @property {Uuid} miniTabletScreenID - The UUID of the mini tablet's screen overlay. null if not in HMD mode. * @property {number} miniTabletHand - The hand that the mini tablet is displayed on: 0 for left hand, * 1 for right hand, -1 if not in HMD mode. + * @property {Rect} playArea=0,0,0,0 - The size and position of the HMD play area in sensor coordinates. Read-only. + * @property {Vec3[]} sensorPositions=[]] - The positions of the VR system sensors in sensor coordinates. Read-only. */ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Dependency { Q_OBJECT @@ -75,6 +77,8 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen Q_PROPERTY(QUuid miniTabletID READ getCurrentMiniTabletID WRITE setCurrentMiniTabletID) Q_PROPERTY(QUuid miniTabletScreenID READ getCurrentMiniTabletScreenID WRITE setCurrentMiniTabletScreenID) Q_PROPERTY(int miniTabletHand READ getCurrentMiniTabletHand WRITE setCurrentMiniTabletHand) + Q_PROPERTY(QVariant playArea READ getPlayAreaRect); + Q_PROPERTY(QVector sensorPositions READ getSensorPositions); public: @@ -384,6 +388,9 @@ public: void setCurrentMiniTabletHand(int miniTabletHand) { _miniTabletHand = miniTabletHand; } int getCurrentMiniTabletHand() const { return _miniTabletHand; } + QVariant getPlayAreaRect(); + QVector getSensorPositions(); + private: bool _showTablet { false }; bool _tabletContextualMode { false }; diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 951925214c..5eccef5e9d 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -226,18 +226,22 @@ void setupPreferences() { static const QString VR_MOVEMENT{ "VR Movement" }; { - - static const QString movementsControlChannel = QStringLiteral("Hifi-Advanced-Movement-Disabler"); - auto getter = [myAvatar]()->bool { return myAvatar->useAdvancedMovementControls(); }; - auto setter = [myAvatar](bool value) { myAvatar->setUseAdvancedMovementControls(value); }; - preferences->addPreference(new CheckPreference(VR_MOVEMENT, - QStringLiteral("Advanced movement in VR (Teleport movement when unchecked)"), - getter, setter)); + auto getter = [myAvatar]()->int { return myAvatar->useAdvancedMovementControls() ? 1 : 0; }; + auto setter = [myAvatar](int value) { myAvatar->setUseAdvancedMovementControls(value == 1); }; + auto preference = + new RadioButtonsPreference(VR_MOVEMENT, "Teleporting only / Walking and teleporting", getter, setter); + QStringList items; + items << "Teleporting only" << "Walking and teleporting"; + preference->setHeading("Movement mode"); + preference->setItems(items); + preferences->addPreference(preference); } { auto getter = [myAvatar]()->bool { return myAvatar->getFlyingHMDPref(); }; auto setter = [myAvatar](bool value) { myAvatar->setFlyingHMDPref(value); }; - preferences->addPreference(new CheckPreference(VR_MOVEMENT, "Flying & jumping (HMD)", getter, setter)); + auto preference = new CheckPreference(VR_MOVEMENT, "Jumping and flying", getter, setter); + preference->setIndented(true); + preferences->addPreference(preference); } { auto getter = [myAvatar]()->int { return myAvatar->getSnapTurn() ? 0 : 1; }; @@ -245,9 +249,16 @@ void setupPreferences() { auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Snap turn / Smooth turn", getter, setter); QStringList items; items << "Snap turn" << "Smooth turn"; + preference->setHeading("Rotation mode"); preference->setItems(items); preferences->addPreference(preference); } + { + auto getter = [myAvatar]()->bool { return myAvatar->getShowPlayArea(); }; + auto setter = [myAvatar](bool value) { myAvatar->setShowPlayArea(value); }; + auto preference = new CheckPreference(VR_MOVEMENT, "Show room boundaries while teleporting", getter, setter); + preferences->addPreference(preference); + } { auto getter = [=]()->float { return myAvatar->getUserHeight(); }; auto setter = [=](float value) { myAvatar->setUserHeight(value); }; @@ -258,12 +269,6 @@ void setupPreferences() { preference->setStep(0.001f); preferences->addPreference(preference); } - { - auto preference = new ButtonPreference(VR_MOVEMENT, "RESET SENSORS", [] { - qApp->resetSensors(); - }); - preferences->addPreference(preference); - } static const QString AVATAR_CAMERA{ "Mouse Sensitivity" }; { diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index e330f6d7ec..ad49ceafe6 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -112,6 +112,9 @@ public: virtual bool suppressKeyboard() { return false; } virtual void unsuppressKeyboard() {}; virtual bool isKeyboardVisible() { return false; } + + virtual QRectF getPlayAreaRect() { return QRectF(); } + virtual QVector getSensorPositions() { return QVector(); } }; class DisplayPlugin : public Plugin, public HmdDisplay { diff --git a/libraries/pointers/src/PointerManager.cpp b/libraries/pointers/src/PointerManager.cpp index 922f0bb5bc..72e4b417cd 100644 --- a/libraries/pointers/src/PointerManager.cpp +++ b/libraries/pointers/src/PointerManager.cpp @@ -153,4 +153,4 @@ bool PointerManager::isMouse(unsigned int uid) { return pointer->isMouse(); } return false; -} \ No newline at end of file +} diff --git a/libraries/render-utils/src/Highlight.slh b/libraries/render-utils/src/Highlight.slh index b26337676f..885df34d26 100644 --- a/libraries/render-utils/src/Highlight.slh +++ b/libraries/render-utils/src/Highlight.slh @@ -26,7 +26,6 @@ layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; const float FAR_Z = 1.0; -const float LINEAR_DEPTH_BIAS = 5e-3; const float OPACITY_EPSILON = 5e-3; <@func main(IS_FILLED)@> @@ -46,7 +45,7 @@ void main(void) { highlightedDepth = -evalZeyeFromZdb(highlightedDepth); sceneDepth = -evalZeyeFromZdb(sceneDepth); - if (sceneDepth < (highlightedDepth-LINEAR_DEPTH_BIAS)) { + if (sceneDepth < highlightedDepth) { outFragColor = vec4(params._fillOccludedColor, params._fillOccludedAlpha); } else { outFragColor = vec4(params._fillUnoccludedColor, params._fillUnoccludedAlpha); @@ -107,7 +106,7 @@ void main(void) { sceneDepth = -evalZeyeFromZdb(sceneDepth); // Are we occluded? - if (sceneDepth < (outlinedDepth/*-LINEAR_DEPTH_BIAS*/)) { + if (sceneDepth < outlinedDepth) { outFragColor = vec4(params._outlineOccludedColor, intensity * params._outlineOccludedAlpha); } else { outFragColor = vec4(params._outlineUnoccludedColor, intensity * params._outlineUnoccludedAlpha); diff --git a/libraries/shared/src/Preferences.h b/libraries/shared/src/Preferences.h index 27bcf7a71b..4a58d71c54 100644 --- a/libraries/shared/src/Preferences.h +++ b/libraries/shared/src/Preferences.h @@ -340,10 +340,16 @@ public: class CheckPreference : public BoolPreference { Q_OBJECT + Q_PROPERTY(bool indented READ getIndented CONSTANT) public: CheckPreference(const QString& category, const QString& name, Getter getter, Setter setter) : BoolPreference(category, name, getter, setter) { } Type getType() override { return Checkbox; } + + bool getIndented() { return _isIndented; } + void setIndented(const bool indented) { _isIndented = indented; } +protected: + bool _isIndented { false }; }; class PrimaryHandPreference : public StringPreference { @@ -356,16 +362,20 @@ public: class RadioButtonsPreference : public IntPreference { Q_OBJECT + Q_PROPERTY(QString heading READ getHeading CONSTANT) Q_PROPERTY(QStringList items READ getItems CONSTANT) public: RadioButtonsPreference(const QString& category, const QString& name, Getter getter, Setter setter) : IntPreference(category, name, getter, setter) { } Type getType() override { return RadioButtons; } + const QString& getHeading() { return _heading; } const QStringList& getItems() { return _items; } + void setHeading(const QString& heading) { _heading = heading; } void setItems(const QStringList& items) { _items = items; } protected: + QString _heading; QStringList _items; }; #endif diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index a9dbe83b06..0c8f4f0466 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -552,6 +552,14 @@ glm::vec2 vec2FromVariant(const QVariant &object) { return vec2FromVariant(object, valid); } +/**jsdoc + * Defines a rectangular portion of an image or screen, or similar. + * @typedef {object} Rect + * @property {number} x - Left, x-coordinate value. + * @property {number} y - Top, y-coordinate value. + * @property {number} width - Width of the rectangle. + * @property {number} height - Height of the rectangle. + */ QScriptValue qRectToScriptValue(QScriptEngine* engine, const QRect& rect) { QScriptValue obj = engine->newObject(); obj.setProperty("x", rect.x()); @@ -568,22 +576,6 @@ void qRectFromScriptValue(const QScriptValue &object, QRect& rect) { rect.setHeight(object.property("height").toVariant().toInt()); } -QScriptValue xColorToScriptValue(QScriptEngine *engine, const xColor& color) { - QScriptValue obj = engine->newObject(); - obj.setProperty("red", color.red); - obj.setProperty("green", color.green); - obj.setProperty("blue", color.blue); - return obj; -} - -/**jsdoc - * Defines a rectangular portion of an image or screen. - * @typedef {object} Rect - * @property {number} x - Integer left, x-coordinate value. - * @property {number} y - Integer top, y-coordinate value. - * @property {number} width - Integer width of the rectangle. - * @property {number} height - Integer height of the rectangle. - */ QVariant qRectToVariant(const QRect& rect) { QVariantMap obj; obj["x"] = rect.x(); @@ -615,6 +607,61 @@ QRect qRectFromVariant(const QVariant& object) { return qRectFromVariant(object, valid); } +QScriptValue qRectFToScriptValue(QScriptEngine* engine, const QRectF& rect) { + QScriptValue obj = engine->newObject(); + obj.setProperty("x", rect.x()); + obj.setProperty("y", rect.y()); + obj.setProperty("width", rect.width()); + obj.setProperty("height", rect.height()); + return obj; +} + +void qRectFFromScriptValue(const QScriptValue &object, QRectF& rect) { + rect.setX(object.property("x").toVariant().toFloat()); + rect.setY(object.property("y").toVariant().toFloat()); + rect.setWidth(object.property("width").toVariant().toFloat()); + rect.setHeight(object.property("height").toVariant().toFloat()); +} + +QVariant qRectFToVariant(const QRectF& rect) { + QVariantMap obj; + obj["x"] = rect.x(); + obj["y"] = rect.y(); + obj["width"] = rect.width(); + obj["height"] = rect.height(); + return obj; +} + +QRectF qRectFFromVariant(const QVariant& objectVar, bool& valid) { + QVariantMap object = objectVar.toMap(); + QRectF rect; + valid = false; + rect.setX(object["x"].toFloat(&valid)); + if (valid) { + rect.setY(object["y"].toFloat(&valid)); + } + if (valid) { + rect.setWidth(object["width"].toFloat(&valid)); + } + if (valid) { + rect.setHeight(object["height"].toFloat(&valid)); + } + return rect; +} + +QRectF qRectFFromVariant(const QVariant& object) { + bool valid; + return qRectFFromVariant(object, valid); +} + + +QScriptValue xColorToScriptValue(QScriptEngine *engine, const xColor& color) { + QScriptValue obj = engine->newObject(); + obj.setProperty("red", color.red); + obj.setProperty("green", color.green); + obj.setProperty("blue", color.blue); + return obj; +} void xColorFromScriptValue(const QScriptValue &object, xColor& color) { if (!object.isValid()) { diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 6ecf9faca7..ea576b6e33 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -100,7 +100,11 @@ void qRectFromScriptValue(const QScriptValue& object, QRect& rect); QRect qRectFromVariant(const QVariant& object, bool& isValid); QRect qRectFromVariant(const QVariant& object); QVariant qRectToVariant(const QRect& rect); - +QScriptValue qRectFToScriptValue(QScriptEngine* engine, const QRectF& rect); +void qRectFFromScriptValue(const QScriptValue& object, QRectF& rect); +QRectF qRectFFromVariant(const QVariant& object, bool& isValid); +QRectF qRectFFromVariant(const QVariant& object); +QVariant qRectFToVariant(const QRectF& rect); // xColor QScriptValue xColorToScriptValue(QScriptEngine* engine, const xColor& color); diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index 36790d1b50..0e4dac796d 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -171,3 +171,53 @@ void OculusBaseDisplayPlugin::updatePresentPose() { _currentPresentFrameInfo.presentPose = ovr::toGlm(trackingState.HeadPose.ThePose); _currentPresentFrameInfo.renderPose = _currentPresentFrameInfo.presentPose; } + +QRectF OculusBaseDisplayPlugin::getPlayAreaRect() { + if (!_session) { + return QRectF(); + } + + int floorPointsCount = 0; + auto result = ovr_GetBoundaryGeometry(_session, ovrBoundary_PlayArea, nullptr, &floorPointsCount); + if (!OVR_SUCCESS(result) || floorPointsCount != 4) { + return QRectF(); + } + + auto floorPoints = new ovrVector3f[floorPointsCount]; + result = ovr_GetBoundaryGeometry(_session, ovrBoundary_PlayArea, floorPoints, nullptr); + if (!OVR_SUCCESS(result)) { + return QRectF(); + } + + auto minXZ = ovr::toGlm(floorPoints[0]); + auto maxXZ = minXZ; + for (int i = 1; i < floorPointsCount; i++) { + auto point = ovr::toGlm(floorPoints[i]); + minXZ.x = std::min(minXZ.x, point.x); + minXZ.z = std::min(minXZ.z, point.z); + maxXZ.x = std::max(maxXZ.x, point.x); + maxXZ.z = std::max(maxXZ.z, point.z); + } + + glm::vec2 center = glm::vec2((minXZ.x + maxXZ.x) / 2, (minXZ.z + maxXZ.z) / 2); + glm::vec2 dimensions = glm::vec2(maxXZ.x - minXZ.x, maxXZ.z - minXZ.z); + + return QRectF(center.x, center.y, dimensions.x, dimensions.y); +} + +QVector OculusBaseDisplayPlugin::getSensorPositions() { + if (!_session) { + return QVector(); + } + + QVector result; + auto numTrackers = ovr_GetTrackerCount(_session); + for (uint i = 0; i < numTrackers; i++) { + auto trackerPose = ovr_GetTrackerPose(_session, i); + if (trackerPose.TrackerFlags & ovrTracker_PoseTracked) { + result.append(ovr::toGlm(trackerPose.Pose.Position)); + } + } + + return result; +} diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.h b/plugins/oculus/src/OculusBaseDisplayPlugin.h index 244c06ecf5..f71fb8ece8 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.h +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.h @@ -27,6 +27,9 @@ public: void resetSensors() override final; bool beginFrameRender(uint32_t frameIndex) override; float getTargetFrameRate() const override { return _hmdDesc.DisplayRefreshRate; } + + QRectF getPlayAreaRect() override; + QVector getSensorPositions() override; protected: void customizeContext() override; diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index fae2144caf..ef0ac65c2a 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -750,3 +750,37 @@ QString OpenVrDisplayPlugin::getPreferredAudioOutDevice() const { } return device; } + +QRectF OpenVrDisplayPlugin::getPlayAreaRect() { + auto chaperone = vr::VRChaperone(); + if (!chaperone) { + qWarning() << "No chaperone"; + return QRectF(); + } + + if (chaperone->GetCalibrationState() >= vr::ChaperoneCalibrationState_Error) { + qWarning() << "Chaperone status =" << chaperone->GetCalibrationState(); + return QRectF(); + } + + vr::HmdQuad_t rect; + if (!chaperone->GetPlayAreaRect(&rect)) { + qWarning() << "Chaperone rect not obtained"; + return QRectF(); + } + + auto minXZ = transformPoint(_sensorResetMat, toGlm(rect.vCorners[0])); + auto maxXZ = minXZ; + for (int i = 1; i < 4; i++) { + auto point = transformPoint(_sensorResetMat, toGlm(rect.vCorners[i])); + minXZ.x = std::min(minXZ.x, point.x); + minXZ.z = std::min(minXZ.z, point.z); + maxXZ.x = std::max(maxXZ.x, point.x); + maxXZ.z = std::max(maxXZ.z, point.z); + } + + glm::vec2 center = glm::vec2((minXZ.x + maxXZ.x) / 2, (minXZ.z + maxXZ.z) / 2); + glm::vec2 dimensions = glm::vec2(maxXZ.x - minXZ.x, maxXZ.z - minXZ.z); + + return QRectF(center.x, center.y, dimensions.x, dimensions.y); +} diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index add35d6383..5585957031 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -64,6 +64,8 @@ public: QString getPreferredAudioInDevice() const override; QString getPreferredAudioOutDevice() const override; + QRectF getPlayAreaRect() override; + protected: bool internalActivate() override; void internalDeactivate() override; diff --git a/scripts/system/assets/models/oculusSensorv11.fbx b/scripts/system/assets/models/oculusSensorv11.fbx new file mode 100644 index 0000000000..52fadc77dc Binary files /dev/null and b/scripts/system/assets/models/oculusSensorv11.fbx differ diff --git a/scripts/system/assets/models/teleportationSpotBasev8.fbx b/scripts/system/assets/models/teleportationSpotBasev8.fbx new file mode 100644 index 0000000000..d651575dea Binary files /dev/null and b/scripts/system/assets/models/teleportationSpotBasev8.fbx differ diff --git a/scripts/system/assets/models/trackingSpacev18.fbx b/scripts/system/assets/models/trackingSpacev18.fbx new file mode 100644 index 0000000000..16597eb285 Binary files /dev/null and b/scripts/system/assets/models/trackingSpacev18.fbx differ diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index deaa934f99..2381c2fe46 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -21,14 +21,10 @@ Script.include("/~/system/libraries/controllers.js"); (function() { // BEGIN LOCAL_SCOPE - var TARGET_MODEL_URL = Script.resolvePath("../../assets/models/teleport-destination.fbx"); + var TARGET_MODEL_URL = Script.resolvePath("../../assets/models/teleportationSpotBasev8.fbx"); var SEAT_MODEL_URL = Script.resolvePath("../../assets/models/teleport-seat.fbx"); - var TARGET_MODEL_DIMENSIONS = { - x: 1.15, - y: 0.5, - z: 1.15 - }; + var TARGET_MODEL_DIMENSIONS = { x: 0.6552, y: 0.3063, z: 0.6552 }; var COLORS_TELEPORT_SEAT = { red: 255, @@ -59,20 +55,23 @@ Script.include("/~/system/libraries/controllers.js"); var cancelPath = { color: COLORS_TELEPORT_CANCEL, - alpha: 1, - width: 0.025 + alpha: 0.3, + width: 0.025, + drawInFront: true }; var teleportPath = { color: COLORS_TELEPORT_CAN_TELEPORT, - alpha: 1, - width: 0.025 + alpha: 0.7, + width: 0.025, + drawInFront: true }; var seatPath = { color: COLORS_TELEPORT_SEAT, - alpha: 1, - width: 0.025 + alpha: 0.7, + width: 0.025, + drawInFront: true }; var teleportEnd = { @@ -150,19 +149,149 @@ Script.include("/~/system/libraries/controllers.js"); this.teleportParabolaHeadVisuals; this.teleportParabolaHeadCollisions; + + this.PLAY_AREA_OVERLAY_MODEL = Script.resolvePath("../../assets/models/trackingSpacev18.fbx"); + this.PLAY_AREA_OVERLAY_MODEL_DIMENSIONS = { x: 1.969, y: 0.001, z: 1.969 }; + this.PLAY_AREA_FLOAT_ABOVE_FLOOR = 0.005; + this.PLAY_AREA_OVERLAY_OFFSET = // Offset from floor. + { x: 0, y: this.PLAY_AREA_OVERLAY_MODEL_DIMENSIONS.y / 2 + this.PLAY_AREA_FLOAT_ABOVE_FLOOR, z: 0 }; + this.PLAY_AREA_SENSOR_OVERLAY_MODEL = Script.resolvePath("../../assets/models/oculusSensorv11.fbx"); + this.PLAY_AREA_SENSOR_OVERLAY_DIMENSIONS = { x: 0.1198, y: 0.2981, z: 0.1198 }; + this.PLAY_AREA_SENSOR_OVERLAY_ROTATION = Quat.fromVec3Degrees({ x: 0, y: -90, z: 0 }); + this.PLAY_AREA_BOX_ALPHA = 1.0; + this.PLAY_AREA_SENSOR_ALPHA = 0.8; + this.playAreaSensorPositions = []; + this.playArea = { x: 0, y: 0 }; + this.playAreaCenterOffset = this.PLAY_AREA_OVERLAY_OFFSET; + this.isPlayAreaVisible = false; + this.wasPlayAreaVisible = false; + this.isPlayAreaAvailable = false; + this.targetOverlayID = null; + this.playAreaOverlay = null; + this.playAreaSensorPositionOverlays = []; + + this.TELEPORT_SCALE_DURATION = 130; + this.TELEPORT_SCALE_TIMEOUT = 25; + this.isTeleportVisible = false; + this.teleportScaleTimer = null; + this.teleportScaleStart = 0; + this.teleportScaleFactor = 0; + this.teleportScaleMode = "head"; + + this.TELEPORTED_FADE_DELAY_DURATION = 900; + this.TELEPORTED_FADE_DURATION = 200; + this.TELEPORTED_FADE_INTERVAL = 25; + this.TELEPORTED_FADE_DELAY_DELTA = this.TELEPORTED_FADE_INTERVAL / this.TELEPORTED_FADE_DELAY_DURATION; + this.TELEPORTED_FADE_DELTA = this.TELEPORTED_FADE_INTERVAL / this.TELEPORTED_FADE_DURATION; + this.teleportedFadeTimer = null; + this.teleportedFadeDelayFactor = 0; + this.teleportedFadeFactor = 0; + this.teleportedPosition = Vec3.ZERO; + this.TELEPORTED_TARGET_ALPHA = 1.0; + this.TELEPORTED_TARGET_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }); + this.teleportedTargetOverlay = null; + + this.setPlayAreaDimensions = function () { + var avatarScale = MyAvatar.sensorToWorldScale; + + var playAreaOverlayProperties = { + dimensions: + Vec3.multiply(this.teleportScaleFactor * avatarScale, { + x: this.playArea.width, + y: this.PLAY_AREA_OVERLAY_MODEL_DIMENSIONS.y, + z: this.playArea.height + }) + }; + + if (this.teleportScaleFactor < 1) { + // Adjust position of playAreOverlay so that its base is at correct height. + // Always parenting to teleport target is good enough for this. + var sensorToWorldMatrix = MyAvatar.sensorToWorldMatrix; + var sensorToWorldRotation = Mat4.extractRotation(MyAvatar.sensorToWorldMatrix); + var worldToSensorMatrix = Mat4.inverse(sensorToWorldMatrix); + var avatarSensorPosition = Mat4.transformPoint(worldToSensorMatrix, MyAvatar.position); + avatarSensorPosition.y = 0; + + var targetRotation = Overlays.getProperty(this.targetOverlayID, "rotation"); + var relativePlayAreaCenterOffset = + Vec3.sum(this.playAreaCenterOffset, { x: 0, y: -TARGET_MODEL_DIMENSIONS.y / 2, z: 0 }); + var localPosition = Vec3.multiplyQbyV(Quat.inverse(targetRotation), + Vec3.multiplyQbyV(sensorToWorldRotation, + Vec3.multiply(avatarScale, Vec3.subtract(relativePlayAreaCenterOffset, avatarSensorPosition)))); + localPosition.y = this.teleportScaleFactor * localPosition.y; + + playAreaOverlayProperties.parentID = this.targetOverlayID; + playAreaOverlayProperties.localPosition = localPosition; + } + + Overlays.editOverlay(this.playAreaOverlay, playAreaOverlayProperties); + + for (var i = 0; i < this.playAreaSensorPositionOverlays.length; i++) { + localPosition = this.playAreaSensorPositions[i]; + localPosition = Vec3.multiply(avatarScale, localPosition); + // Position relative to the play area. + localPosition.y = avatarScale * (this.PLAY_AREA_SENSOR_OVERLAY_DIMENSIONS.y / 2 + - this.PLAY_AREA_OVERLAY_MODEL_DIMENSIONS.y / 2); + Overlays.editOverlay(this.playAreaSensorPositionOverlays[i], { + dimensions: Vec3.multiply(this.teleportScaleFactor * avatarScale, this.PLAY_AREA_SENSOR_OVERLAY_DIMENSIONS), + parentID: this.playAreaOverlay, + localPosition: localPosition + }); + } + }; + + this.updatePlayAreaScale = function () { + if (this.isPlayAreaAvailable) { + this.setPlayAreaDimensions(); + } + }; + + + this.teleporterSelectionName = "teleporterSelection" + hand.toString(); + this.TELEPORTER_SELECTION_STYLE = { + outlineUnoccludedColor: { red: 0, green: 0, blue: 0 }, + outlineUnoccludedAlpha: 0, + outlineOccludedColor: { red: 0, green: 0, blue: 0 }, + outlineOccludedAlpha: 0, + fillUnoccludedColor: { red: 0, green: 0, blue: 0 }, + fillUnoccludedAlpha: 0, + fillOccludedColor: { red: 0, green: 0, blue: 255 }, + fillOccludedAlpha: 0.84, + outlineWidth: 0, + isOutlineSmooth: false + }; + + this.addToSelectedItemsList = function (properties) { + for (var i = 0, length = teleportRenderStates.length; i < length; i++) { + var state = properties.renderStates[teleportRenderStates[i].name]; + if (state && state.end) { + Selection.addToSelectedItemsList(this.teleporterSelectionName, "overlay", state.end); + } + } + }; + + this.cleanup = function() { + Selection.removeListFromMap(_this.teleporterSelectionName); Pointers.removePointer(_this.teleportParabolaHandVisuals); Pointers.removePointer(_this.teleportParabolaHandCollisions); Pointers.removePointer(_this.teleportParabolaHeadVisuals); Pointers.removePointer(_this.teleportParabolaHeadCollisions); Picks.removePick(_this.teleportHandCollisionPick); Picks.removePick(_this.teleportHeadCollisionPick); + Overlays.deleteOverlay(_this.teleportedTargetOverlay); + Overlays.deleteOverlay(_this.playAreaOverlay); + for (var i = 0; i < _this.playAreaSensorPositionOverlays.length; i++) { + Overlays.deleteOverlay(_this.playAreaSensorPositionOverlays[i]); + } + _this.playAreaSensorPositionOverlays = []; }; - this.initPointers = function () { + this.initPointers = function() { if (_this.init) { _this.cleanup(); } + _this.teleportParabolaHandVisuals = Pointers.createPointer(PickType.Parabola, { joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", dirOffset: { x: 0, y: 1, z: 0.1 }, @@ -221,6 +350,9 @@ Script.include("/~/system/libraries/controllers.js"); maxDistance: 8.0 }); + _this.addToSelectedItemsList(Pointers.getPointerProperties(_this.teleportParabolaHandVisuals)); + _this.addToSelectedItemsList(Pointers.getPointerProperties(_this.teleportParabolaHeadVisuals)); + var capsuleData = MyAvatar.getCollisionCapsule(); @@ -262,11 +394,264 @@ Script.include("/~/system/libraries/controllers.js"); position: { x: 0, y: offset + height * 0.5, z: 0 }, threshold: _this.capsuleThreshold }); + + + _this.playAreaOverlay = Overlays.addOverlay("model", { + url: _this.PLAY_AREA_OVERLAY_MODEL, + drawInFront: false, + visible: false + }); + + _this.teleportedTargetOverlay = Overlays.addOverlay("model", { + url: TARGET_MODEL_URL, + alpha: _this.TELEPORTED_TARGET_ALPHA, + visible: false + }); + + Selection.addToSelectedItemsList(_this.teleporterSelectionName, "overlay", _this.playAreaOverlay); + Selection.addToSelectedItemsList(_this.teleporterSelectionName, "overlay", _this.teleportedTargetOverlay); + + + _this.playArea = HMD.playArea; + _this.isPlayAreaAvailable = HMD.active && _this.playArea.width !== 0 && _this.playArea.height !== 0; + if (_this.isPlayAreaAvailable) { + _this.playAreaCenterOffset = Vec3.sum({ x: _this.playArea.x, y: 0, z: _this.playArea.y }, + _this.PLAY_AREA_OVERLAY_OFFSET); + _this.playAreaSensorPositions = HMD.sensorPositions; + + for (var i = 0; i < _this.playAreaSensorPositions.length; i++) { + if (i > _this.playAreaSensorPositionOverlays.length - 1) { + var overlay = Overlays.addOverlay("model", { + url: _this.PLAY_AREA_SENSOR_OVERLAY_MODEL, + dimensions: _this.PLAY_AREA_SENSOR_OVERLAY_DIMENSIONS, + parentID: _this.playAreaOverlay, + localRotation: _this.PLAY_AREA_SENSOR_OVERLAY_ROTATION, + drawInFront: false, + visible: false + }); + _this.playAreaSensorPositionOverlays.push(overlay); + Selection.addToSelectedItemsList(_this.teleporterSelectionName, "overlay", overlay); + } + } + + _this.setPlayAreaDimensions(); + } + _this.init = true; - } + }; _this.initPointers(); + + this.translateXAction = Controller.findAction("TranslateX"); + this.translateYAction = Controller.findAction("TranslateY"); + this.translateZAction = Controller.findAction("TranslateZ"); + + this.setPlayAreaVisible = function (visible, targetOverlayID, fade) { + if (!this.isPlayAreaAvailable || this.isPlayAreaVisible === visible) { + return; + } + + this.wasPlayAreaVisible = this.isPlayAreaVisible; + this.isPlayAreaVisible = visible; + this.targetOverlayID = targetOverlayID; + + if (this.teleportedFadeTimer !== null) { + Script.clearTimeout(this.teleportedFadeTimer); + this.teleportedFadeTimer = null; + } + if (visible || !fade) { + // Immediately make visible or invisible. + this.isPlayAreaVisible = visible; + Overlays.editOverlay(this.playAreaOverlay, { + dimensions: Vec3.ZERO, + alpha: this.PLAY_AREA_BOX_ALPHA, + visible: visible + }); + for (var i = 0; i < this.playAreaSensorPositionOverlays.length; i++) { + Overlays.editOverlay(this.playAreaSensorPositionOverlays[i], { + dimensions: Vec3.ZERO, + alpha: this.PLAY_AREA_SENSOR_ALPHA, + visible: visible + }); + } + Overlays.editOverlay(this.teleportedTargetOverlay, { visible: false }); + } else { + // Fading out of overlays is initiated in setTeleportVisible(). + } + }; + + this.updatePlayArea = function (position) { + var sensorToWorldMatrix = MyAvatar.sensorToWorldMatrix; + var sensorToWorldRotation = Mat4.extractRotation(MyAvatar.sensorToWorldMatrix); + var worldToSensorMatrix = Mat4.inverse(sensorToWorldMatrix); + var avatarSensorPosition = Mat4.transformPoint(worldToSensorMatrix, MyAvatar.position); + avatarSensorPosition.y = 0; + + var targetXZPosition = { x: position.x, y: 0, z: position.z }; + var avatarXZPosition = MyAvatar.position; + avatarXZPosition.y = 0; + var MIN_PARENTING_DISTANCE = 0.2; // Parenting under this distance results in the play area's rotation jittering. + if (Vec3.distance(targetXZPosition, avatarXZPosition) < MIN_PARENTING_DISTANCE) { + // Set play area position and rotation in world coordinates with no parenting. + Overlays.editOverlay(this.playAreaOverlay, { + parentID: Uuid.NULL, + position: Vec3.sum(position, + Vec3.multiplyQbyV(sensorToWorldRotation, + Vec3.multiply(MyAvatar.sensorToWorldScale, + Vec3.subtract(this.playAreaCenterOffset, avatarSensorPosition)))), + rotation: sensorToWorldRotation + }); + } else { + // Set play area position and rotation in local coordinates with parenting. + var targetRotation = Overlays.getProperty(this.targetOverlayID, "rotation"); + var sensorToTargetRotation = Quat.multiply(Quat.inverse(targetRotation), sensorToWorldRotation); + var relativePlayAreaCenterOffset = + Vec3.sum(this.playAreaCenterOffset, { x: 0, y: -TARGET_MODEL_DIMENSIONS.y / 2, z: 0 }); + Overlays.editOverlay(this.playAreaOverlay, { + parentID: this.targetOverlayID, + localPosition: Vec3.multiplyQbyV(Quat.inverse(targetRotation), + Vec3.multiplyQbyV(sensorToWorldRotation, + Vec3.multiply(MyAvatar.sensorToWorldScale, + Vec3.subtract(relativePlayAreaCenterOffset, avatarSensorPosition)))), + localRotation: sensorToTargetRotation + }); + } + }; + + + this.scaleInTeleport = function () { + _this.teleportScaleFactor = Math.min((Date.now() - _this.teleportScaleStart) / _this.TELEPORT_SCALE_DURATION, 1); + Pointers.editRenderState( + _this.teleportScaleMode === "head" ? _this.teleportParabolaHeadVisuals : _this.teleportParabolaHandVisuals, + "teleport", + { + path: teleportPath, // Teleport beam disappears if not included. + end: { dimensions: Vec3.multiply(_this.teleportScaleFactor, TARGET_MODEL_DIMENSIONS) } + } + ); + if (_this.isPlayAreaVisible) { + _this.setPlayAreaDimensions(); + } + if (_this.teleportScaleFactor < 1) { + _this.teleportScaleTimer = Script.setTimeout(_this.scaleInTeleport, _this.TELEPORT_SCALE_TIMEOUT); + } else { + _this.teleportScaleTimer = null; + } + }; + + this.fadeOutTeleport = function () { + var isAvatarMoving, + i, length; + + isAvatarMoving = Controller.getActionValue(_this.translateXAction) !== 0 + || Controller.getActionValue(_this.translateYAction) !== 0 + || Controller.getActionValue(_this.translateZAction) !== 0; + + if (_this.teleportedFadeDelayFactor > 0 && !_this.isTeleportVisible && !isAvatarMoving) { + // Delay fade. + _this.teleportedFadeDelayFactor = _this.teleportedFadeDelayFactor - _this.TELEPORTED_FADE_DELAY_DELTA; + _this.teleportedFadeTimer = Script.setTimeout(_this.fadeOutTeleport, _this.TELEPORTED_FADE_INTERVAL); + } else if (_this.teleportedFadeFactor > 0 && !_this.isTeleportVisible && !isAvatarMoving) { + // Fade. + _this.teleportedFadeFactor = _this.teleportedFadeFactor - _this.TELEPORTED_FADE_DELTA; + Overlays.editOverlay(_this.teleportedTargetOverlay, { + alpha: _this.teleportedFadeFactor * _this.TELEPORTED_TARGET_ALPHA + }); + if (_this.wasPlayAreaVisible) { + Overlays.editOverlay(_this.playAreaOverlay, { + alpha: _this.teleportedFadeFactor * _this.PLAY_AREA_BOX_ALPHA + }); + var sensorAlpha = _this.teleportedFadeFactor * _this.PLAY_AREA_SENSOR_ALPHA; + for (i = 0, length = _this.playAreaSensorPositionOverlays.length; i < length; i++) { + Overlays.editOverlay(_this.playAreaSensorPositionOverlays[i], { alpha: sensorAlpha }); + } + } + _this.teleportedFadeTimer = Script.setTimeout(_this.fadeOutTeleport, _this.TELEPORTED_FADE_INTERVAL); + } else { + // Make invisible. + Overlays.editOverlay(_this.teleportedTargetOverlay, { visible: false }); + if (_this.wasPlayAreaVisible) { + Overlays.editOverlay(_this.playAreaOverlay, { visible: false }); + for (i = 0, length = _this.playAreaSensorPositionOverlays.length; i < length; i++) { + Overlays.editOverlay(_this.playAreaSensorPositionOverlays[i], { visible: false }); + } + } + _this.teleportedFadeTimer = null; + Selection.disableListHighlight(this.teleporterSelectionName); + } + }; + + this.cancelFade = function () { + // Other hand may call this to immediately hide fading overlays. + var i, length; + if (this.teleportedFadeTimer) { + Overlays.editOverlay(this.teleportedTargetOverlay, { visible: false }); + if (this.wasPlayAreaVisible) { + Overlays.editOverlay(this.playAreaOverlay, { visible: false }); + for (i = 0, length = this.playAreaSensorPositionOverlays.length; i < length; i++) { + Overlays.editOverlay(this.playAreaSensorPositionOverlays[i], { visible: false }); + } + } + this.teleportedFadeTimer = null; + } + }; + + this.setTeleportVisible = function (visible, mode, fade) { + // Scales in teleport target and play area when start displaying them. + if (visible === this.isTeleportVisible) { + return; + } + + if (visible) { + this.teleportScaleMode = mode; + Pointers.editRenderState( + mode === "head" ? _this.teleportParabolaHeadVisuals : _this.teleportParabolaHandVisuals, + "teleport", + { + path: teleportPath, // Teleport beam disappears if not included. + end: { dimensions: Vec3.ZERO } + } + ); + this.getOtherModule().cancelFade(); + this.teleportScaleStart = Date.now(); + this.teleportScaleFactor = 0; + this.scaleInTeleport(); + Selection.enableListHighlight(this.teleporterSelectionName, this.TELEPORTER_SELECTION_STYLE); + } else { + if (this.teleportScaleTimer !== null) { + Script.clearTimeout(this.teleportScaleTimer); + this.teleportScaleTimer = null; + } + + if (fade) { + // Copy of target at teleported position for fading. + var avatarScale = MyAvatar.sensorToWorldScale; + Overlays.editOverlay(this.teleportedTargetOverlay, { + position: Vec3.sum(this.teleportedPosition, { + x: 0, + y: -getAvatarFootOffset() + avatarScale * TARGET_MODEL_DIMENSIONS.y / 2, + z: 0 + }), + rotation: Quat.multiply(this.TELEPORTED_TARGET_ROTATION, MyAvatar.orientation), + dimensions: Vec3.multiply(avatarScale, TARGET_MODEL_DIMENSIONS), + alpha: this.TELEPORTED_TARGET_ALPHA, + visible: true + }); + + // Fade out over time. + this.teleportedFadeDelayFactor = 1.0; + this.teleportedFadeFactor = 1.0; + this.teleportedFadeTimer = Script.setTimeout(this.fadeOutTeleport, this.TELEPORTED_FADE_DELAY); + } else { + Selection.disableListHighlight(this.teleporterSelectionName); + } + } + + this.isTeleportVisible = visible; + }; + + this.axisButtonStateX = 0; // Left/right axis button pressed. this.axisButtonStateY = 0; // Up/down axis button pressed. this.BUTTON_TRANSITION_DELAY = 100; // Allow time for transition from direction buttons to touch-pad. @@ -379,6 +764,7 @@ Script.include("/~/system/libraries/controllers.js"); this.setTeleportState(mode, "cancel", "collision"); } else if (teleportLocationType === TARGET.SURFACE || teleportLocationType === TARGET.DISCREPANCY) { this.setTeleportState(mode, "teleport", "collision"); + this.updatePlayArea(result.intersection); } else if (teleportLocationType === TARGET.SEAT) { this.setTeleportState(mode, "collision", "seat"); } @@ -387,6 +773,7 @@ Script.include("/~/system/libraries/controllers.js"); this.teleport = function(newResult, target) { var result = newResult; + this.teleportedPosition = newResult.intersection; if (_this.buttonValue !== 0) { return makeRunningValues(true, [], []); } @@ -410,6 +797,8 @@ Script.include("/~/system/libraries/controllers.js"); }; this.disableLasers = function() { + this.setPlayAreaVisible(false, null, true); + this.setTeleportVisible(false, null, true); Pointers.disablePointer(_this.teleportParabolaHandVisuals); Pointers.disablePointer(_this.teleportParabolaHandCollisions); Pointers.disablePointer(_this.teleportParabolaHeadVisuals); @@ -418,14 +807,29 @@ Script.include("/~/system/libraries/controllers.js"); Picks.disablePick(_this.teleportHandCollisionPick); }; - this.setTeleportState = function(mode, visibleState, invisibleState) { + this.teleportState = ""; + + this.setTeleportState = function (mode, visibleState, invisibleState) { + var teleportState = mode + visibleState + invisibleState; + if (teleportState === this.teleportState) { + return; + } + this.teleportState = teleportState; + + var pointerID; if (mode === 'head') { Pointers.setRenderState(_this.teleportParabolaHeadVisuals, visibleState); Pointers.setRenderState(_this.teleportParabolaHeadCollisions, invisibleState); + pointerID = _this.teleportParabolaHeadVisuals; } else { Pointers.setRenderState(_this.teleportParabolaHandVisuals, visibleState); Pointers.setRenderState(_this.teleportParabolaHandCollisions, invisibleState); + pointerID = _this.teleportParabolaHandVisuals; } + var visible = visibleState === "teleport"; + this.setPlayAreaVisible(visible && MyAvatar.showPlayArea, + Pointers.getPointerProperties(pointerID).renderStates.teleport.end, false); + this.setTeleportVisible(visible, mode, false); }; this.setIgnoreEntities = function(entitiesToIgnore) { @@ -642,4 +1046,9 @@ Script.include("/~/system/libraries/controllers.js"); Messages.subscribe('Hifi-Teleport-Ignore-Remove'); Messages.messageReceived.connect(handleTeleportMessages); + MyAvatar.sensorToWorldScaleChanged.connect(function () { + leftTeleporter.updatePlayAreaScale(); + rightTeleporter.updatePlayAreaScale(); + }); + }()); // END LOCAL_SCOPE