From 040346ecb5a260ad64a77f8aad5e56796d41a546 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Mon, 13 Apr 2020 19:26:13 +0300 Subject: [PATCH 1/3] XR input sources interact with Elements --- src/framework/application.js | 3 + src/framework/components/button/component.js | 68 +++++ src/input/element-input.js | 291 +++++++++++++++---- src/input/input.js | 22 ++ src/xr/xr-input-source.js | 25 ++ src/xr/xr-manager.js | 2 + 6 files changed, 360 insertions(+), 51 deletions(-) diff --git a/src/framework/application.js b/src/framework/application.js index 735b6fb78c4..291570ab469 100644 --- a/src/framework/application.js +++ b/src/framework/application.js @@ -566,6 +566,9 @@ Object.assign(pc, function () { this.vr = null; this.xr = new pc.XrManager(this); + if (this.elementInput) + this.elementInput.attachSelectEvents(); + this._inTools = false; this._skyboxLast = 0; diff --git a/src/framework/components/button/component.js b/src/framework/components/button/component.js index b2d8eb39f74..cb57db8e905 100644 --- a/src/framework/components/button/component.js +++ b/src/framework/components/button/component.js @@ -53,6 +53,7 @@ Object.assign(pc, function () { this._visualState = VisualState.DEFAULT; this._isHovering = false; + this._hoveringCounter = 0; this._isPressed = false; this._defaultTint = new pc.Color(1, 1, 1, 1); @@ -150,6 +151,10 @@ Object.assign(pc, function () { this.entity.element[onOrOff]('touchend', this._onTouchEnd, this); this.entity.element[onOrOff]('touchleave', this._onTouchLeave, this); this.entity.element[onOrOff]('touchcancel', this._onTouchCancel, this); + this.entity.element[onOrOff]('selectstart', this._onSelectStart, this); + this.entity.element[onOrOff]('selectend', this._onSelectEnd, this); + this.entity.element[onOrOff]('selectenter', this._onSelectEnter, this); + this.entity.element[onOrOff]('selectleave', this._onSelectLeave, this); this.entity.element[onOrOff]('click', this._onClick, this); this._hasHitElementListeners = isAdding; @@ -278,6 +283,41 @@ Object.assign(pc, function () { this._fireIfActive('touchcancel', event); }, + _onSelectStart: function (event) { + this._isPressed = true; + this._updateVisualState(); + this._fireIfActive('selectstart', event); + }, + + _onSelectEnd: function (event) { + this._isPressed = false; + this._updateVisualState(); + this._fireIfActive('selectend', event); + }, + + _onSelectEnter: function (event) { + this._hoveringCounter++; + + if (this._hoveringCounter === 1) { + this._isHovering = true; + this._updateVisualState(); + } + + this._fireIfActive('selectenter', event); + }, + + _onSelectLeave: function (event) { + this._hoveringCounter--; + + if (this._hoveringCounter === 0) { + this._isHovering = false; + this._isPressed = false; + this._updateVisualState(); + } + + this._fireIfActive('selectleave', event); + }, + _onClick: function (event) { this._fireIfActive('click', event); }, @@ -524,6 +564,34 @@ Object.assign(pc, function () { * @param {pc.ElementTouchEvent} event - The event. */ +/** + * @event + * @name pc.ButtonComponent#selectstart + * @description Fired when a xr select starts on the component. + * @param {pc.ElementSelectEvent} event - The event. + */ + +/** + * @event + * @name pc.ButtonComponent#selectend + * @description Fired when a xr select ends on the component. + * @param {pc.ElementSelectEvent} event - The event. + */ + +/** + * @event + * @name pc.ButtonComponent#selectenter + * @description Fired when a xr select now hovering over the component. + * @param {pc.ElementSelectEvent} event - The event. + */ + +/** + * @event + * @name pc.ButtonComponent#selectleave + * @description Fired when a xr select not hovering over the component. + * @param {pc.ElementSelectEvent} event - The event. + */ + /** * @event * @name pc.ButtonComponent#hoverstart diff --git a/src/input/element-input.js b/src/input/element-input.js index 66150670bf1..98a0ba34ff3 100644 --- a/src/input/element-input.js +++ b/src/input/element-input.js @@ -3,6 +3,12 @@ Object.assign(pc, function () { var vecA = new pc.Vec3(); var vecB = new pc.Vec3(); + var rayA = new pc.Ray(); + rayA.end = new pc.Vec3(); + + var rayB = new pc.Ray(); + rayB.end = new pc.Vec3(); + var _pq = new pc.Vec3(); var _pa = new pc.Vec3(); var _pb = new pc.Vec3(); @@ -91,8 +97,10 @@ Object.assign(pc, function () { */ stopPropagation: function () { this._stopPropagation = true; - this.event.stopImmediatePropagation(); - this.event.stopPropagation(); + if (this.event) { + this.event.stopImmediatePropagation(); + this.event.stopPropagation(); + } } }); @@ -181,6 +189,25 @@ Object.assign(pc, function () { ElementTouchEvent.prototype = Object.create(ElementInputEvent.prototype); ElementTouchEvent.prototype.constructor = ElementTouchEvent; + /** + * @class + * @name pc.ElementSelectEvent + * @augments pc.ElementInputEvent + * @classdesc Represents a XRInputSourceEvent fired on a {@link pc.ElementComponent}. + * @description Create an instance of a pc.ElementSelectEvent. + * @param {object} event - The XRInputSourceEvent that was originally raised. + * @param {pc.ElementComponent} element - The ElementComponent that this event was originally raised on. + * @param {pc.CameraComponent} camera - The CameraComponent that this event was originally raised via. + * @param {pc.XrInputSource} inputSource - The XR input source that this event was originally raised from. + * @property {pc.XrInputSource} inputSource The XR input source that this event was originally raised from. + */ + var ElementSelectEvent = function (event, element, camera, inputSource) { + ElementInputEvent.call(this, event, element, camera); + this.inputSource = inputSource; + }; + ElementSelectEvent.prototype = Object.create(ElementInputEvent.prototype); + ElementSelectEvent.prototype.constructor = ElementSelectEvent; + /** * @class * @name pc.ElementInput @@ -191,6 +218,7 @@ Object.assign(pc, function () { * @param {object} [options] - Optional arguments. * @param {boolean} [options.useMouse] - Whether to allow mouse input. Defaults to true. * @param {boolean} [options.useTouch] - Whether to allow touch input. Defaults to true. + * @param {boolean} [options.useXr] - Whether to allow XR input sources. Defaults to true. */ var ElementInput = function (domElement, options) { this._app = null; @@ -218,15 +246,18 @@ Object.assign(pc, function () { this._pressedElement = null; this._touchedElements = {}; this._touchesForWhichTouchLeaveHasFired = {}; + this._selectedElements = {}; + this._selectedPressedElements = {}; this._useMouse = !options || options.useMouse !== false; this._useTouch = !options || options.useTouch !== false; + this._useXr = !options || options.useXr !== false; + this._selectEventsAttached = false; - if (pc.platform.touch) { + if (pc.platform.touch) this._clickedEntities = {}; - } - this.attach(domElement, options); + this.attach(domElement); }; Object.assign(ElementInput.prototype, { @@ -261,6 +292,18 @@ Object.assign(pc, function () { this._target.addEventListener('touchmove', this._touchmoveHandler, false); this._target.addEventListener('touchcancel', this._touchcancelHandler, false); } + + this.attachSelectEvents(); + }, + + attachSelectEvents: function () { + if (! this._selectEventsAttached && this._useXr && this.app && this.app.xr && this.app.xr.supported) { + if (! this._clickedEntities) + this._clickedEntities = {}; + + this._selectEventsAttached = true; + this.app.xr.on('start', this._onXrStart, this); + } }, /** @@ -287,6 +330,16 @@ Object.assign(pc, function () { this._target.removeEventListener('touchcancel', this._touchcancelHandler, false); } + if (this._selectEventsAttached) { + this._selectEventsAttached = false; + this.app.xr.off('start', this._onXrStart, this); + this.app.xr.off('end', this._onXrEnd, this); + this.app.xr.off('update', this._onXrUpdate, this); + this.app.xr.input.off('selectstart', this._onSelectStart, this); + this.app.xr.input.off('selectend', this._onSelectEnd, this); + this.app.xr.input.off('remove', this._onXrInputRemove, this); + } + this._target = null; }, @@ -323,7 +376,7 @@ Object.assign(pc, function () { if (targetX === null) return; - this._onElementMouseEvent(pc.EVENT_MOUSEUP, event); + this._onElementMouseEvent('mouseup', event); }, _handleDown: function (event) { @@ -336,7 +389,7 @@ Object.assign(pc, function () { if (targetX === null) return; - this._onElementMouseEvent(pc.EVENT_MOUSEDOWN, event); + this._onElementMouseEvent('mousedown', event); }, _handleMove: function (event) { @@ -346,7 +399,7 @@ Object.assign(pc, function () { if (targetX === null) return; - this._onElementMouseEvent(pc.EVENT_MOUSEMOVE, event); + this._onElementMouseEvent('mousemove', event); this._lastX = targetX; this._lastY = targetY; @@ -359,7 +412,7 @@ Object.assign(pc, function () { if (targetX === null) return; - this._onElementMouseEvent(pc.EVENT_MOUSEWHEEL, event); + this._onElementMouseEvent('mousewheel', event); }, _determineTouchedElements: function (event) { @@ -533,13 +586,12 @@ Object.assign(pc, function () { this._hoveredElement = element; - if (eventType === pc.EVENT_MOUSEDOWN) { + if (eventType === 'mousedown') { this._pressedElement = element; } } if (hovered !== this._hoveredElement) { - // mouseleave event if (hovered) { this._fireEvent('mouseleave', new ElementMouseEvent(event, hovered, camera, targetX, targetY, this._lastX, this._lastY)); @@ -551,7 +603,7 @@ Object.assign(pc, function () { } } - if (eventType === pc.EVENT_MOUSEUP && this._pressedElement) { + if (eventType === 'mouseup' && this._pressedElement) { // click event if (this._pressedElement === this._hoveredElement) { this._pressedElement = null; @@ -566,6 +618,101 @@ Object.assign(pc, function () { } }, + _onXrStart: function () { + this.app.xr.on('end', this._onXrEnd, this); + this.app.xr.on('update', this._onXrUpdate, this); + this.app.xr.input.on('selectstart', this._onSelectStart, this); + this.app.xr.input.on('selectend', this._onSelectEnd, this); + this.app.xr.input.on('remove', this._onXrInputRemove, this); + }, + + _onXrEnd: function () { + this.app.xr.off('update', this._onXrUpdate, this); + this.app.xr.input.off('selectstart', this._onSelectStart, this); + this.app.xr.input.off('selectend', this._onSelectEnd, this); + this.app.xr.input.off('remove', this._onXrInputRemove, this); + }, + + _onXrUpdate: function () { + if (!this._enabled) return; + + var inputSources = this.app.xr.input.inputSources; + for (var i = 0; i < inputSources.length; i++) { + this._onElementSelectEvent('selectmove', inputSources[i], null); + } + }, + + _onXrInputRemove: function (inputSource) { + var hovered = this._selectedElements[inputSource.id]; + if (hovered) this._fireEvent('selectleave', new ElementSelectEvent(null, hovered, null, inputSource)); + + delete this._selectedElements[inputSource.id]; + delete this._selectedPressedElements[inputSource.id]; + }, + + _onSelectStart: function (inputSource, event) { + if (! this._enabled) return; + this._onElementSelectEvent('selectstart', inputSource, event); + }, + + _onSelectEnd: function (inputSource, event) { + if (! this._enabled) return; + this._onElementSelectEvent('selectend', inputSource, event); + }, + + _onElementSelectEvent: function (eventType, inputSource, event) { + var element; + + var hoveredBefore = this._selectedElements[inputSource.id]; + var hoveredNow; + + var cameras = this.app.systems.camera.cameras; + var camera; + + if (inputSource.elementInput) { + for (var i = cameras.length - 1; i >= 0; i--) { + camera = cameras[i]; + + element = this._getTargetElementByRay(inputSource.ray, camera); + if (element) + break; + } + } + + if (element) { + this._selectedElements[inputSource.id] = element; + hoveredNow = element; + } else { + delete this._selectedElements[inputSource.id]; + } + + if (hoveredBefore !== hoveredNow) { + if (hoveredBefore) this._fireEvent('selectleave', new ElementSelectEvent(event, hoveredBefore, camera, inputSource)); + if (hoveredNow) this._fireEvent('selectenter', new ElementSelectEvent(event, hoveredNow, camera, inputSource)); + } + + if (eventType === 'selectstart') { + this._selectedPressedElements[inputSource.id] = hoveredNow; + if (hoveredNow) this._fireEvent('selectstart', new ElementSelectEvent(event, hoveredNow, camera, inputSource)); + } + + var pressed = this._selectedPressedElements[inputSource.id]; + if (! inputSource.elementInput && pressed) { + delete this._selectedPressedElements[inputSource.id]; + if (hoveredBefore) this._fireEvent('selectend', new ElementSelectEvent(event, hoveredBefore, camera, inputSource)); + } + + if (eventType === 'selectend' && inputSource.elementInput) { + delete this._selectedPressedElements[inputSource.id]; + + if (hoveredBefore) this._fireEvent('selectend', new ElementSelectEvent(event, hoveredBefore, camera, inputSource)); + + if (pressed && pressed === hoveredBefore) { + this._fireEvent('click', new ElementSelectEvent(event, pressed, camera, inputSource)); + } + } + }, + _fireEvent: function (name, evt) { var element = evt.element; while (true) { @@ -649,20 +796,59 @@ Object.assign(pc, function () { // sort elements this._elements.sort(this._sortHandler); + var rayScreen, ray3d; + for (var i = 0, len = this._elements.length; i < len; i++) { var element = this._elements[i]; + var screen = false; + var ray; - // scale x, y based on the camera's rect - + // cache rays if (element.screen && element.screen.screen.screenSpace) { // 2D screen - if (this._checkElement2d(x, y, element, camera)) { - result = element; - break; + if (rayScreen === undefined) { + rayScreen = rayA; + if (this._calculateRayScreen(x, y, camera, rayScreen) === false) { + rayScreen = null; + } } + ray = rayScreen; + screen = true; } else { // 3d - if (this._checkElement3d(x, y, element, camera)) { + if (ray3d === undefined) { + ray3d = rayB; + if (this._calculateRay3d(x, y, camera, ray3d) === false) { + ray3d = null; + } + } + ray = ray3d; + } + + if (ray && this._checkElement(ray, element, screen)) { + result = element; + break; + } + } + + return result; + }, + + _getTargetElementByRay: function (ray, camera) { + var result = null; + + rayA.origin.copy(ray.origin); + rayA.direction.copy(ray.direction); + rayA.end.copy(rayA.direction).scale(camera.farClip * 2).add(rayA.origin); + + // sort elements + this._elements.sort(this._sortHandler); + + for (var i = 0, len = this._elements.length; i < len; i++) { + var element = this._elements[i]; + + if (! element.screen || ! element.screen.screen.screenSpace) { + if (this._checkElement(rayA, element, false)) { result = element; break; } @@ -718,13 +904,7 @@ Object.assign(pc, function () { return _accumulatedScale; }, - _checkElement2d: function (x, y, element, camera) { - // ensure click is contained by any mask first - if (element.maskedBy) { - var result = this._checkElement2d(x, y, element.maskedBy.element, camera); - if (!result) return false; - } - + _calculateRayScreen: function (x, y, camera, ray) { var sw = this.app.graphicsDevice.width; var sh = this.app.graphicsDevice.height; @@ -739,7 +919,6 @@ Object.assign(pc, function () { var _x = x * sw / this._target.clientWidth; var _y = y * sh / this._target.clientHeight; - // check window coords are within camera rect if (_x >= cameraLeft && _x <= cameraRight && _y <= cameraBottom && _y >= cameraTop) { @@ -750,26 +929,16 @@ Object.assign(pc, function () { // reverse _y _y = sh - _y; - var scale = this._calculateScaleToScreen(element); - var hitCorners = this._buildHitCorners(element, element.screenCorners, scale.x, scale.y); - vecA.set(_x, _y, 1); - vecB.set(_x, _y, -1); + ray.origin.set(_x, _y, 1); + ray.direction.set(0, 0, -1); + ray.end.copy(ray.direction).scale(2).add(ray.origin); - if (intersectLineQuad(vecA, vecB, hitCorners)) { - return true; - } + return true; } - return false; }, - _checkElement3d: function (x, y, element, camera) { - // ensure click is contained by any mask first - if (element.maskedBy) { - var result = this._checkElement3d(x, y, element.maskedBy.element, camera); - if (!result) return false; - } - + _calculateRay3d: function (x, y, camera, ray) { var sw = this._target.clientWidth; var sh = this._target.clientHeight; @@ -793,18 +962,38 @@ Object.assign(pc, function () { _y = sh * (_y - (cameraTop)) / cameraHeight; // 3D screen - var scale = element.entity.getWorldTransform().getScale(); - var worldCorners = this._buildHitCorners(element, element.worldCorners, scale.x, scale.y); - var start = vecA; - var end = vecB; - camera.screenToWorld(_x, _y, camera.nearClip, start); - camera.screenToWorld(_x, _y, camera.farClip, end); - - if (intersectLineQuad(start, end, worldCorners)) { - return true; - } + camera.screenToWorld(_x, _y, camera.nearClip, vecA); + camera.screenToWorld(_x, _y, camera.farClip, vecB); + + ray.origin.copy(vecA); + ray.direction.set(0, 0, -1); + ray.end.copy(vecB); + + return true; + } + return false; + }, + + _checkElement: function (ray, element, screen) { + // ensure click is contained by any mask first + if (element.maskedBy) { + var result = this._checkElement(ray, element.maskedBy.element, screen); + if (!result) return false; } + var scale; + + if (screen) { + scale = this._calculateScaleToScreen(element); + } else { + scale = element.entity.getWorldTransform().getScale(); + } + + var corners = this._buildHitCorners(element, screen ? element.screenCorners : element.worldCorners, scale.x, scale.y); + + if (intersectLineQuad(ray.origin, ray.end, corners)) + return true; + return false; } }); diff --git a/src/input/input.js b/src/input/input.js index 72cfe2e12f0..4b2fa494e08 100644 --- a/src/input/input.js +++ b/src/input/input.js @@ -89,6 +89,28 @@ */ EVENT_TOUCHCANCEL: 'touchcancel', + /** + * @constant + * @type {string} + * @name pc.EVENT_SELECT + * @description Name of event fired when a new xr select occurs. For example, primary trigger was pressed. + */ + EVENT_SELECT: 'select', + /** + * @constant + * @type {string} + * @name pc.EVENT_SELECTSTART + * @description Name of event fired when a new xr select starts. For example, primary trigger is now pressed. + */ + EVENT_SELECTSTART: 'selectstart', + /** + * @constant + * @type {string} + * @name pc.EVENT_SELECTEND + * @description Name of event fired when xr select ends. For example, a primary trigger is now released. + */ + EVENT_SELECTEND: 'selectend', + /** * @constant * @type {number} diff --git a/src/xr/xr-input-source.js b/src/xr/xr-input-source.js index 28a9da924e3..8efb52cfae6 100644 --- a/src/xr/xr-input-source.js +++ b/src/xr/xr-input-source.js @@ -1,5 +1,6 @@ Object.assign(pc, function () { var quat = new pc.Quat(); + var ids = 0; var targetRayModes = { @@ -62,6 +63,7 @@ Object.assign(pc, function () { * @description Represents XR input source, which is any input mechanism which allows the user to perform targeted actions in the same virtual space as the viewer. Example XR input sources include, but are not limited to, handheld controllers, optically tracked hands, and gaze-based input methods that operate on the viewer's pose. * @param {pc.XrManager} manager - WebXR Manager. * @param {object} xrInputSource - XRInputSource object that is created by WebXR API. + * @property {number} id Unique number associated with instance of input source. Same physical devices when reconnected will not share this ID. * @property {object} inputSource XRInputSource object that is associated with this input source. * @property {string} targetRayMode Type of ray Input Device is based on. Can be one of the following: * @@ -82,11 +84,14 @@ Object.assign(pc, function () { * @property {pc.Quat|null} rotation If {@link pc.XrInputSource#grip} is true, then rotation will represent rotation of handheld input source in local space of XR session. * @property {Gamepad|null} gamepad If input source has buttons, triggers, thumbstick or touchpad, then this object provides access to its states. * @property {boolean} selecting True if input source is in active primary action between selectstart and selectend events. + * @property {boolean} elementInput Set to true to allow input source to interact with Element components. Defaults to true. * @property {pc.XrHitTestSource[]} hitTestSources list of active {@link pc.XrHitTestSource} created by this input source. */ var XrInputSource = function (manager, xrInputSource) { pc.EventHandler.call(this); + this._id = ++ids; + this._manager = manager; this._xrInputSource = xrInputSource; @@ -96,6 +101,8 @@ Object.assign(pc, function () { this._rotation = null; this._selecting = false; + this._elementInput = true; + this._hitTestSources = []; }; XrInputSource.prototype = Object.create(pc.EventHandler.prototype); @@ -269,6 +276,12 @@ Object.assign(pc, function () { if (ind !== -1) this._hitTestSources.splice(ind, 1); }; + Object.defineProperty(XrInputSource.prototype, 'id', { + get: function () { + return this._id; + } + }); + Object.defineProperty(XrInputSource.prototype, 'inputSource', { get: function () { return this._xrInputSource; @@ -329,6 +342,18 @@ Object.assign(pc, function () { } }); + Object.defineProperty(XrInputSource.prototype, 'elementInput', { + get: function () { + return this._elementInput; + }, + set: function (value) { + if (this._elementInput === value) + return; + + this._elementInput = value; + } + }); + Object.defineProperty(XrInputSource.prototype, 'hitTestSources', { get: function () { return this._hitTestSources; diff --git a/src/xr/xr-manager.js b/src/xr/xr-manager.js index 1f8578274aa..fad136b4560 100644 --- a/src/xr/xr-manager.js +++ b/src/xr/xr-manager.js @@ -511,6 +511,8 @@ Object.assign(pc, function () { if (this._type === pc.XRTYPE_AR && this.hitTest.supported) this.hitTest.update(frame); + + this.fire('update'); }; Object.defineProperty(XrManager.prototype, 'supported', { From 4cbfd86cddf204c34b6bc68c8807dffeae175980 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Thu, 16 Apr 2020 16:44:42 +0300 Subject: [PATCH 2/3] update input source ray --- src/input/element-input.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/input/element-input.js b/src/input/element-input.js index 98a0ba34ff3..d4b56c367a8 100644 --- a/src/input/element-input.js +++ b/src/input/element-input.js @@ -4,10 +4,12 @@ Object.assign(pc, function () { var vecB = new pc.Vec3(); var rayA = new pc.Ray(); - rayA.end = new pc.Vec3(); - var rayB = new pc.Ray(); + var rayC = new pc.Ray(); + + rayA.end = new pc.Vec3(); rayB.end = new pc.Vec3(); + rayC.end = new pc.Vec3(); var _pq = new pc.Vec3(); var _pa = new pc.Vec3(); @@ -670,10 +672,12 @@ Object.assign(pc, function () { var camera; if (inputSource.elementInput) { + rayC.set(inputSource.getOrigin(), inputSource.getDirection()); + for (var i = cameras.length - 1; i >= 0; i--) { camera = cameras[i]; - element = this._getTargetElementByRay(inputSource.ray, camera); + element = this._getTargetElementByRay(rayC, camera); if (element) break; } From 1c4c0b35041b06f63fa75e240e7cf81f76589f63 Mon Sep 17 00:00:00 2001 From: Maksims Mihejevs Date: Thu, 16 Apr 2020 17:08:52 +0300 Subject: [PATCH 3/3] add new input source property - which element it hovering --- src/input/element-input.js | 7 ++++++- src/xr/xr-input-source.js | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/input/element-input.js b/src/input/element-input.js index d4b56c367a8..e2c76d0c6c0 100644 --- a/src/input/element-input.js +++ b/src/input/element-input.js @@ -646,7 +646,10 @@ Object.assign(pc, function () { _onXrInputRemove: function (inputSource) { var hovered = this._selectedElements[inputSource.id]; - if (hovered) this._fireEvent('selectleave', new ElementSelectEvent(null, hovered, null, inputSource)); + if (hovered) { + inputSource._elementEntity = null; + this._fireEvent('selectleave', new ElementSelectEvent(null, hovered, null, inputSource)); + } delete this._selectedElements[inputSource.id]; delete this._selectedPressedElements[inputSource.id]; @@ -683,6 +686,8 @@ Object.assign(pc, function () { } } + inputSource._elementEntity = element || null; + if (element) { this._selectedElements[inputSource.id] = element; hoveredNow = element; diff --git a/src/xr/xr-input-source.js b/src/xr/xr-input-source.js index ee84d670a47..f413b9d860d 100644 --- a/src/xr/xr-input-source.js +++ b/src/xr/xr-input-source.js @@ -82,6 +82,7 @@ Object.assign(pc, function () { * @property {Gamepad|null} gamepad If input source has buttons, triggers, thumbstick or touchpad, then this object provides access to its states. * @property {boolean} selecting True if input source is in active primary action between selectstart and selectend events. * @property {boolean} elementInput Set to true to allow input source to interact with Element components. Defaults to true. + * @property {pc.Entity} elementEntity If {@link pc.XrInputSource#elementInput} is true, this property will hold entity with Element component at which this input source is hovering, or null if not hovering over any element. * @property {pc.XrHitTestSource[]} hitTestSources list of active {@link pc.XrHitTestSource} created by this input source. */ var XrInputSource = function (manager, xrInputSource) { @@ -107,6 +108,7 @@ Object.assign(pc, function () { this._selecting = false; this._elementInput = true; + this._elementEntity = null; this._hitTestSources = []; }; @@ -467,6 +469,15 @@ Object.assign(pc, function () { return; this._elementInput = value; + + if (! this._elementInput) + this._elementEntity = null; + } + }); + + Object.defineProperty(XrInputSource.prototype, 'elementEntity', { + get: function () { + return this._elementEntity; } });