diff --git a/package-lock.json b/package-lock.json index 35c52ccb91e8c416cf7897d217d553390281fda4..0bbad51e844badf4f715528348442ed89792212a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -550,10 +550,6 @@ "resolved": "https://registry.yarnpkg.com/aframe-billboard-component/-/aframe-billboard-component-1.0.0.tgz", "integrity": "sha1-EM4kgnKe73OGxYRNZZF1gaYtOtw=" }, - "aframe-input-mapping-component": { - "version": "github:mozillareality/aframe-input-mapping-component#03932457c5318db243e811d2767fe0c5a8c7e9e0", - "from": "github:mozillareality/aframe-input-mapping-component#hubs/master" - }, "aframe-inspector": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/aframe-inspector/-/aframe-inspector-0.8.3.tgz", diff --git a/package.json b/package.json index bee3e1b3d3c7b14a9d5f939f369ade4f0b49c437..d9e3d59c08ecdbf2adc92df5046aa149331bae1f 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "@fortawesome/react-fontawesome": "^0.1.0", "aframe": "github:mozillareality/aframe#bugfix/oculus-go-controller-reconnect-pre-e0c8ff7", "aframe-billboard-component": "^1.0.0", - "aframe-input-mapping-component": "github:mozillareality/aframe-input-mapping-component#hubs/master", "aframe-inspector": "^0.8.3", "aframe-motion-capture-components": "github:mozillareality/aframe-motion-capture-components#1ca616fa67b627e447b23b35a09da175d8387668", "aframe-physics-extras": "github:mozillareality/aframe-physics-extras#bugfix/physics-collider-world", diff --git a/src/activators/pressedmove.js b/src/activators/pressedmove.js deleted file mode 100644 index fb7d7470723246b687049eafeb83d51e84f0e8bd..0000000000000000000000000000000000000000 --- a/src/activators/pressedmove.js +++ /dev/null @@ -1,35 +0,0 @@ -function PressedMove(el, button, onActivate) { - this.down = button + "down"; - this.up = button + "up"; - this.pressed = false; - this.onActivate = onActivate; - this.el = el; - this.onButtonDown = this.onButtonDown.bind(this); - this.onButtonUp = this.onButtonUp.bind(this); - this.onAxisMove = this.onAxisMove.bind(this); - el.addEventListener(this.down, this.onButtonDown); - el.addEventListener(this.up, this.onButtonUp); - el.addEventListener("axismove", this.onAxisMove); -} - -PressedMove.prototype = { - onAxisMove: function(event) { - if (this.pressed) { - this.onActivate(event); - } - }, - onButtonDown: function() { - this.pressed = true; - }, - onButtonUp: function() { - this.pressed = false; - }, - - removeListeners: function() { - this.el.addEventListener(this.down, this.onButtonDown); - this.el.addEventListener(this.up, this.onButtonUp); - this.el.addEventListener("axismove", this.onAxisMove); - } -}; - -export { PressedMove }; diff --git a/src/activators/reversey.js b/src/activators/reversey.js deleted file mode 100644 index d3f3967f2da72d954b9a47d81bfaa0f1782f95f2..0000000000000000000000000000000000000000 --- a/src/activators/reversey.js +++ /dev/null @@ -1,20 +0,0 @@ -function ReverseY(el, button, onActivate) { - this.el = el; - this.emitReverseY = this.emitReverseY.bind(this); - this.onActivate = onActivate; - this.button = button; - this.removeListeners = this.removeListeners.bind(this); - el.addEventListener(button, this.emitReverseY); -} - -ReverseY.prototype = { - emitReverseY: function(event) { - event.detail.axis[1] *= -1; - this.onActivate(event); - }, - removeListeners: function() { - this.el.removeEventListener(this.button, this.emitReverseY); - } -}; - -export { ReverseY }; diff --git a/src/activators/shortpress.js b/src/activators/shortpress.js deleted file mode 100644 index fd9b8da0a212dc6acfba55509072d9c46c079305..0000000000000000000000000000000000000000 --- a/src/activators/shortpress.js +++ /dev/null @@ -1,34 +0,0 @@ -function ShortPress(el, button, onActivate) { - this.lastTime = 0; - this.timeOut = 500; - this.eventNameDown = button + "down"; - this.eventNameUp = button + "up"; - - this.el = el; - this.onActivate = onActivate; - - this.onButtonDown = this.onButtonDown.bind(this); - this.onButtonUp = this.onButtonUp.bind(this); - - el.addEventListener(this.eventNameDown, this.onButtonDown); - el.addEventListener(this.eventNameUp, this.onButtonUp); -} - -ShortPress.prototype = { - onButtonDown(event) { - this.pressTimer = window.setTimeout(() => { - this.onActivate(event); - }, this.timeOut); - }, - - onButtonUp() { - clearTimeout(this.pressTimer); - }, - - removeListeners() { - this.el.removeEventListener(this.eventNameDown, this.onButtonDown); - this.el.removeEventListener(this.eventNameUp, this.onButtonUp); - } -}; - -AFRAME.registerInputActivator("shortpress", ShortPress); diff --git a/src/behaviours/joystick-dpad4.js b/src/behaviours/joystick-dpad4.js deleted file mode 100644 index 0eae7e1d194dfb6f207da53a7499caaf8e483c02..0000000000000000000000000000000000000000 --- a/src/behaviours/joystick-dpad4.js +++ /dev/null @@ -1,34 +0,0 @@ -import { angleTo4Direction } from "../utils/dpad"; - -// @TODO specify 4 or 8 direction -function joystick_dpad4(el, outputPrefix) { - this.angleToDirection = angleTo4Direction; - this.outputPrefix = outputPrefix; - this.centerRadius = 0.6; - this.previous = "none"; - this.hapticIntensity = "low"; - this.emitDPad4 = this.emitDPad4.bind(this); - this.el = el; -} - -joystick_dpad4.prototype = { - addEventListeners: function() { - this.el.addEventListener("axismove", this.emitDPad4); - }, - removeEventListeners: function() { - this.el.removeEventListener("axismove", this.emitDPad4); - }, - emitDPad4: function(event) { - const x = event.detail.axis[0]; - const y = event.detail.axis[1]; - const inCenter = Math.abs(x) < this.centerRadius && Math.abs(y) < this.centerRadius; - const current = inCenter ? "center" : this.angleToDirection(Math.atan2(x, -y)); - if (current !== this.previous) { - this.previous = current; - event.target.emit(`${this.outputPrefix}_dpad4_${current}`); - event.target.emit("haptic_pulse", { intensity: this.hapticIntensity }); - } - } -}; - -export default joystick_dpad4; diff --git a/src/behaviours/msft-mr-axis-with-deadzone.js b/src/behaviours/msft-mr-axis-with-deadzone.js deleted file mode 100644 index c2b86fe8b5a0b645572a6bf27a2dfceb0449187b..0000000000000000000000000000000000000000 --- a/src/behaviours/msft-mr-axis-with-deadzone.js +++ /dev/null @@ -1,26 +0,0 @@ -function msft_mr_axis_with_deadzone(el, outputPrefix) { - this.el = el; - this.outputPrefix = outputPrefix; - this.deadzone = 0.1; - this.emitAxisMoveWithDeadzone = this.emitAxisMoveWithDeadzone.bind(this); -} - -msft_mr_axis_with_deadzone.prototype = { - addEventListeners: function() { - this.el.addEventListener("axismove", this.emitAxisMoveWithDeadzone); - }, - removeEventListeners: function() { - this.el.removeEventListener("axismove", this.emitAxisMoveWithDeadzone); - }, - emitAxisMoveWithDeadzone: function(event) { - const axis = event.detail.axis; - if (Math.abs(axis[0]) < this.deadzone && Math.abs(axis[1]) < this.deadzone) { - return; - } - // Reverse y - axis[1] = -axis[1]; - this.el.emit("axisMoveWithDeadzone", event.detail); - } -}; - -export default msft_mr_axis_with_deadzone; diff --git a/src/behaviours/trackpad-dpad4.js b/src/behaviours/trackpad-dpad4.js deleted file mode 100644 index f15908d34d5854d60ae158af7026007745293dfb..0000000000000000000000000000000000000000 --- a/src/behaviours/trackpad-dpad4.js +++ /dev/null @@ -1,62 +0,0 @@ -import { angleTo4Direction } from "../utils/dpad"; - -function trackpad_dpad4(el, outputPrefix) { - this.outputPrefix = outputPrefix; - this.lastDirection = ""; - this.previous = ""; - this.pressed = false; - this.emitDPad4 = this.emitDPad4.bind(this); - this.press = this.press.bind(this); - this.unpress = this.unpress.bind(this); - this.hapticIntensity = "low"; - this.centerRadius = 0.6; - this.el = el; -} - -trackpad_dpad4.prototype = { - addEventListeners: function() { - this.el.addEventListener("axismove", this.emitDPad4); - this.el.addEventListener("trackpaddown", this.press); - this.el.addEventListener("trackpadup", this.unpress); - }, - removeEventListeners: function() { - this.el.removeEventListener("axismove", this.emitDPad4); - this.el.removeEventListener("trackpaddown", this.press); - this.el.removeEventListener("trackpadup", this.unpress); - }, - press: function() { - this.pressed = true; - }, - unpress: function() { - this.pressed = false; - }, - emitDPad4: function(event) { - const x = event.detail.axis[0]; - const y = event.detail.axis[1]; - const inCenter = Math.abs(x) < this.centerRadius && Math.abs(y) < this.centerRadius; - const direction = inCenter ? "center" : angleTo4Direction(Math.atan2(x, -y)); - const pressed = this.pressed ? "pressed_" : ""; - const current = `${pressed + direction}`; // e.g. "pressed_north" - - // Real axismove events are not perfectly [0,0]... - // This is a touchend event. - if (x === 0 && y === 0) { - event.target.emit(`${this.outputPrefix}_dpad4_${this.previous}_up`); - this.previous = ""; // Clear this because the user has lifted their finger. - return; - } - - if (current === this.previous) { - return; - } - - if (this.previous !== "") { - event.target.emit(`${this.outputPrefix}_dpad4_${this.previous}_up`); - } - - event.target.emit(`${this.outputPrefix}_dpad4_${current}_down`); - this.previous = current; - } -}; - -export default trackpad_dpad4; diff --git a/src/behaviours/trackpad-scrolling.js b/src/behaviours/trackpad-scrolling.js deleted file mode 100644 index 8cb5baf9502b697141b434499525b5c40e63e555..0000000000000000000000000000000000000000 --- a/src/behaviours/trackpad-scrolling.js +++ /dev/null @@ -1,56 +0,0 @@ -function trackpad_scrolling(el) { - this.el = el; - this.start = "trackpadtouchstart"; - this.move = "axismove"; - this.end = "trackpadtouchend"; - this.isScrolling = false; - this.x = -10; - this.y = -10; - this.axis = [0, 0]; - this.emittedEventDetail = { detail: { axis: this.axis } }; - - this.onStart = this.onStart.bind(this); - this.onMove = this.onMove.bind(this); - this.onEnd = this.onEnd.bind(this); -} - -trackpad_scrolling.prototype = { - addEventListeners: function() { - this.el.addEventListener(this.start, this.onStart); - this.el.addEventListener(this.move, this.onMove); - this.el.addEventListener(this.end, this.onEnd); - }, - removeEventListeners: function() { - this.el.removeEventListener(this.start, this.onStart); - this.el.removeEventListener(this.move, this.onMove); - this.el.removeEventListener(this.end, this.onEnd); - }, - onStart: function() { - this.isScrolling = true; - }, - onMove: function(e) { - if (!this.isScrolling) return; - const x = e.detail.axis[0]; - const y = e.detail.axis[1]; - if (this.x === -10) { - this.x = x; - this.y = y; - return; - } - - const scrollSpeed = 8; - this.axis[0] = (x - this.x) * scrollSpeed; - this.axis[1] = (y - this.y) * scrollSpeed; - this.emittedEventDetail.axis = this.axis; - e.target.emit("scroll", this.emittedEventDetail); - this.x = x; - this.y = y; - }, - onEnd: function() { - this.isScrolling = false; - this.x = -10; - this.y = -10; - } -}; - -export default trackpad_scrolling; diff --git a/src/components/action-to-event.js b/src/components/action-to-event.js new file mode 100644 index 0000000000000000000000000000000000000000..ee6f397445fea8d556f268ca81ea978f366df6d0 --- /dev/null +++ b/src/components/action-to-event.js @@ -0,0 +1,15 @@ +AFRAME.registerComponent("action-to-event", { + multiple: true, + + schema: { + path: { type: "string" }, + event: { type: "string" } + }, + + tick() { + const userinput = AFRAME.scenes[0].systems.userinput; + if (userinput.readFrameValueAtPath(this.data.path)) { + this.el.emit(this.data.event); + } + } +}); diff --git a/src/components/camera-tool.js b/src/components/camera-tool.js index cc6bbefb233cecc6d28980fd2d1630c5d1730952..2ead31c99657728c3128ea63c6489e1bcf9b240b 100644 --- a/src/components/camera-tool.js +++ b/src/components/camera-tool.js @@ -1,10 +1,23 @@ import { addMedia } from "../utils/media-utils"; import { ObjectTypes } from "../object-types"; +import { paths } from "../systems/userinput/paths"; import cameraModelSrc from "../assets/camera_tool.glb"; const cameraModelPromise = new Promise(resolve => new THREE.GLTFLoader().load(cameraModelSrc, resolve)); +const pathsMap = { + "player-right-controller": { + takeSnapshot: paths.actions.rightHand.takeSnapshot + }, + "player-left-controller": { + takeSnapshot: paths.actions.leftHand.takeSnapshot + }, + cursor: { + takeSnapshot: paths.actions.cursor.takeSnapshot + } +}; + const snapCanvas = document.createElement("canvas"); async function pixelsToPNG(pixels, width, height) { snapCanvas.width = width; @@ -93,6 +106,16 @@ AFRAME.registerComponent("camera-tool", { } }, + tick() { + const grabber = this.el.components.grabbable.grabbers[0]; + if (grabber && !!pathsMap[grabber.id]) { + const paths = pathsMap[grabber.id]; + if (AFRAME.scenes[0].systems.userinput.readFrameValueAtPath(paths.takeSnapshot)) { + this.takeSnapshotNextTick = true; + } + } + }, + tock: (function() { const tempScale = new THREE.Vector3(); return function tock() { diff --git a/src/components/character-controller.js b/src/components/character-controller.js index b0dd84323e3d6eb7027df2d938fbb66a37705789..6aeb0797bd212d01b0fce8628205fc46f55291e0 100644 --- a/src/components/character-controller.js +++ b/src/components/character-controller.js @@ -1,3 +1,4 @@ +import { paths } from "../systems/userinput/paths"; const CLAMP_VELOCITY = 0.01; const MAX_DELTA = 0.2; const EPS = 10e-6; @@ -102,6 +103,7 @@ AFRAME.registerComponent("character-controller", { const pivotRotationInvMatrix = new THREE.Matrix4(); const startPos = new THREE.Vector3(); const startScale = new THREE.Vector3(); + const jump = new THREE.Vector3(); return function(t, dt) { const deltaSeconds = dt / 1000; @@ -116,6 +118,25 @@ AFRAME.registerComponent("character-controller", { // Other aframe components like teleport-controls set position/rotation/scale, not the matrix, so we need to make sure to compose them back into the matrix root.updateMatrix(); + const userinput = AFRAME.scenes[0].systems.userinput; + if (userinput.readFrameValueAtPath(paths.actions.snapRotateLeft)) { + this.snapRotateLeft(); + } + if (userinput.readFrameValueAtPath(paths.actions.snapRotateRight)) { + this.snapRotateRight(); + } + jump.set(0, 0, 0); + if (userinput.readFrameValueAtPath(paths.actions.translate.up)) { + jump.y += userinput.readFrameValueAtPath(paths.actions.translate.up); + } + if (userinput.readFrameValueAtPath(paths.actions.translate.down)) { + jump.y -= userinput.readFrameValueAtPath(paths.actions.translate.down); + } + const acc = userinput.readFrameValueAtPath(paths.actions.characterAcceleration); + if (acc) { + this.accelerationInput.set(acc[0], 0, acc[1]); + } + pivotPos.copy(pivot.position); pivotPos.applyMatrix4(root.matrix); trans.setPosition(pivotPos); @@ -125,7 +146,13 @@ AFRAME.registerComponent("character-controller", { pivotRotationMatrix.makeRotationAxis(rotationAxis, pivot.rotation.y); pivotRotationInvMatrix.makeRotationAxis(rotationAxis, -pivot.rotation.y); this.updateVelocity(deltaSeconds); - move.makeTranslation(this.velocity.x * distance, this.velocity.y * distance, this.velocity.z * distance); + + const boost = userinput.readFrameValueAtPath(paths.actions.boost) ? 2 : 1; + move.makeTranslation( + jump.x + this.velocity.x * distance * boost, + jump.y + this.velocity.y * distance * boost, + jump.z + this.velocity.z * distance * boost + ); yawMatrix.makeRotationAxis(rotationAxis, rotationDelta); // Translate to middle of playspace (player rig) diff --git a/src/components/cursor-controller.js b/src/components/cursor-controller.js index 421b084c309710e9442dd3f44019fdcb0cad94d5..e42d1ba6a703d62e480033fdd669a630ecc824fc 100644 --- a/src/components/cursor-controller.js +++ b/src/components/cursor-controller.js @@ -1,8 +1,4 @@ -const TARGET_TYPE_NONE = 1; -const TARGET_TYPE_INTERACTABLE = 2; -const TARGET_TYPE_UI = 4; -const TARGET_TYPE_INTERACTABLE_OR_UI = TARGET_TYPE_INTERACTABLE | TARGET_TYPE_UI; - +import { paths } from "../systems/userinput/paths"; /** * Manages targeting and physical cursor location. Has the following responsibilities: * @@ -21,28 +17,27 @@ AFRAME.registerComponent("cursor-controller", { cursorColorHovered: { default: "#2F80ED" }, cursorColorUnhovered: { default: "#FFFFFF" }, rayObject: { type: "selector" }, - drawLine: { default: false }, + drawLine: { default: true }, objects: { default: "" } }, init: function() { this.enabled = true; - this.currentTargetType = TARGET_TYPE_NONE; - this.currentDistance = this.data.far; - this.currentDistanceMod = 0; - this.mousePos = new THREE.Vector2(); - this.wasCursorHovered = false; - this.data.cursor.setAttribute("material", { color: this.data.cursorColorUnhovered }); - this._handleCursorLoaded = this._handleCursorLoaded.bind(this); - this.data.cursor.addEventListener("loaded", this._handleCursorLoaded); + this.data.cursor.addEventListener( + "loaded", + () => { + this.data.cursor.object3DMap.mesh.renderOrder = window.APP.RENDER_ORDER.CURSOR; + }, + { once: true } + ); // raycaster state + this.setDirty = this.setDirty.bind(this); this.targets = []; - this.intersection = null; this.raycaster = new THREE.Raycaster(); - this.setDirty = this.setDirty.bind(this); this.dirty = true; + this.distance = this.data.far; }, update: function() { @@ -89,27 +84,6 @@ AFRAME.registerComponent("cursor-controller", { } }, - performRaycast: (function() { - const rayObjectRotation = new THREE.Quaternion(); - const rawIntersections = []; - return function performRaycast(targets) { - if (this.data.rayObject) { - const rayObject = this.data.rayObject.object3D; - rayObject.updateMatrixWorld(); - rayObjectRotation.setFromRotationMatrix(rayObject.matrixWorld); - this.raycaster.ray.origin.setFromMatrixPosition(rayObject.matrixWorld); - this.raycaster.ray.direction.set(0, 0, -1).applyQuaternion(rayObjectRotation); - } else { - this.raycaster.setFromCamera(this.mousePos, this.data.camera.components.camera.camera); // camera - } - const prevIntersection = this.intersection; - rawIntersections.length = 0; - this.raycaster.intersectObjects(targets, true, rawIntersections); - this.intersection = rawIntersections.find(x => x.object.el); - this.emitIntersectionEvents(prevIntersection, this.intersection); - }; - })(), - enable: function() { this.enabled = true; }, @@ -120,130 +94,68 @@ AFRAME.registerComponent("cursor-controller", { }, tick: (() => { + const rawIntersections = []; const cameraPos = new THREE.Vector3(); - return function() { - if (!this.enabled) { - return; - } - if (this.dirty) { + // app aware devices cares about this.targets so we must update it even if cursor is not enabled this.populateEntities(this.data.objects, this.targets); this.dirty = false; } - this.performRaycast(this.targets); - - if (this.isInteracting()) { - const distance = Math.min( - this.data.far, - Math.max(this.data.near, this.currentDistance - this.currentDistanceMod) - ); - this.data.cursor.object3D.position.copy(this.raycaster.ray.origin); - this.data.cursor.object3D.position.addScaledVector(this.raycaster.ray.direction, distance); - } else { - this.currentDistanceMod = 0; - this.updateDistanceAndTargetType(); - - const isTarget = this._isTargetOfType(TARGET_TYPE_INTERACTABLE_OR_UI); - if (isTarget && !this.wasCursorHovered) { - this.wasCursorHovered = true; - this.data.cursor.setAttribute("material", { color: this.data.cursorColorHovered }); - } else if (!isTarget && this.wasCursorHovered) { - this.wasCursorHovered = false; - this.data.cursor.setAttribute("material", { color: this.data.cursorColorUnhovered }); - } + const userinput = AFRAME.scenes[0].systems.userinput; + const cursorPose = userinput.readFrameValueAtPath(paths.actions.cursor.pose); + + this.setCursorVisibility(this.enabled && !!cursorPose); + + if (!this.enabled || !cursorPose) { + return; } - if (this.data.drawLine) { - this.el.setAttribute("line", { - start: this.raycaster.ray.origin.clone(), - end: this.data.cursor.object3D.position.clone() - }); + let intersection; + const isGrabbing = this.data.cursor.components["super-hands"].state.has("grab-start"); + if (!isGrabbing) { + rawIntersections.length = 0; + this.raycaster.ray.origin = cursorPose.position; + this.raycaster.ray.direction = cursorPose.direction; + this.raycaster.intersectObjects(this.targets, true, rawIntersections); + intersection = rawIntersections.find(x => x.object.el); + this.emitIntersectionEvents(this.prevIntersection, intersection); + this.prevIntersection = intersection; + this.distance = intersection ? intersection.distance : this.data.far; } + const { cursor, near, far, drawLine, camera, cursorColorHovered, cursorColorUnhovered } = this.data; + + const cursorModDelta = userinput.readFrameValueAtPath(paths.actions.cursor.modDelta); + if (isGrabbing && cursorModDelta) { + this.distance = THREE.Math.clamp(this.distance - cursorModDelta, near, far); + } + cursor.object3D.position.copy(cursorPose.position).addScaledVector(cursorPose.direction, this.distance); // The cursor will always be oriented towards the player about its Y axis, so objects held by the cursor will rotate towards the player. - this.data.camera.object3D.getWorldPosition(cameraPos); - cameraPos.y = this.data.cursor.object3D.position.y; - this.data.cursor.object3D.lookAt(cameraPos); + camera.object3D.getWorldPosition(cameraPos); + cameraPos.y = cursor.object3D.position.y; + cursor.object3D.lookAt(cameraPos); + + this.data.cursor.setAttribute("material", { + color: intersection || isGrabbing ? cursorColorHovered : cursorColorUnhovered + }); + if (drawLine) { + this.el.setAttribute("line", { + start: cursorPose.position.clone(), + end: cursor.object3D.position.clone() + }); + } }; })(), - updateDistanceAndTargetType: function() { - const intersection = this.intersection; - if (intersection && intersection.distance <= this.data.far) { - this.data.cursor.object3D.position.copy(intersection.point); - this.currentDistance = intersection.distance; - } else { - this.currentDistance = this.data.far; - this.data.cursor.object3D.position.copy(this.raycaster.ray.origin); - this.data.cursor.object3D.position.addScaledVector(this.raycaster.ray.direction, this.currentDistance); - } - - if (!intersection) { - this.currentTargetType = TARGET_TYPE_NONE; - } else if (intersection.object.el.matches(".interactable, .interactable *")) { - this.currentTargetType = TARGET_TYPE_INTERACTABLE; - } else if (intersection.object.el.matches(".ui, .ui *")) { - this.currentTargetType = TARGET_TYPE_UI; - } - }, - - _isTargetOfType: function(mask) { - return (this.currentTargetType & mask) === this.currentTargetType; - }, - setCursorVisibility: function(visible) { this.data.cursor.setAttribute("visible", visible); this.el.setAttribute("line", { visible: visible && this.data.drawLine }); }, - forceCursorUpdate: function() { - this.performRaycast(this.targets); - this.updateDistanceAndTargetType(); - this.data.cursor.components["static-body"].syncToPhysics(); - }, - - isInteracting: function() { - return this.data.cursor.components["super-hands"].state.has("grab-start"); - }, - - startInteraction: function() { - if (this._isTargetOfType(TARGET_TYPE_INTERACTABLE_OR_UI)) { - this.data.cursor.emit("cursor-grab", {}); - return true; - } - return false; - }, - - endInteraction: function() { - this.data.cursor.emit("cursor-release", {}); - }, - - moveCursor: function(x, y) { - this.mousePos.set(x, y); - }, - - changeDistanceMod: function(delta) { - const { near, far } = this.data; - const targetDistanceMod = this.currentDistanceMod + delta; - const moddedDistance = this.currentDistance - targetDistanceMod; - if (moddedDistance > far || moddedDistance < near) { - return false; - } - - this.currentDistanceMod = targetDistanceMod; - return true; - }, - - _handleCursorLoaded: function() { - this.data.cursor.object3DMap.mesh.renderOrder = window.APP.RENDER_ORDER.CURSOR; - this.data.cursor.removeEventListener("loaded", this._handleCursorLoaded); - }, - remove: function() { - this.emitIntersectionEvents(this.intersection, null); - this.intersection = null; - this.data.cursor.removeEventListener("loaded", this._handleCursorLoaded); + this.emitIntersectionEvents(this.prevIntersection, null); + delete this.prevIntersection; } }); diff --git a/src/components/grabbable-toggle.js b/src/components/grabbable-toggle.js deleted file mode 100644 index 5a11d5698d5ba53cbafeab63286bf142b1699bfb..0000000000000000000000000000000000000000 --- a/src/components/grabbable-toggle.js +++ /dev/null @@ -1,185 +0,0 @@ -/* global AFRAME, THREE */ -const inherit = AFRAME.utils.extendDeep; -const physicsCore = require("super-hands/reaction_components/prototypes/physics-grab-proto.js"); -const buttonsCore = require("super-hands/reaction_components/prototypes/buttons-proto.js"); -// new object with all core modules -const base = inherit({}, physicsCore, buttonsCore); -AFRAME.registerComponent( - "grabbable-toggle", - inherit(base, { - schema: { - maxGrabbers: { type: "int", default: NaN }, - invert: { default: false }, - suppressY: { default: false }, - primaryReleaseEvents: { default: ["primary_hand_release"] }, - secondaryReleaseEvents: { default: ["secondary_hand_release"] } - }, - init: function() { - this.GRABBED_STATE = "grabbed"; - this.GRAB_EVENT = "grab-start"; - this.UNGRAB_EVENT = "grab-end"; - this.grabbed = false; - this.grabbers = []; - this.constraints = new Map(); - this.deltaPositionIsValid = false; - this.grabDistance = undefined; - this.grabDirection = { x: 0, y: 0, z: -1 }; - this.grabOffset = { x: 0, y: 0, z: 0 }; - // persistent object speeds up repeat setAttribute calls - this.destPosition = { x: 0, y: 0, z: 0 }; - this.deltaPosition = new THREE.Vector3(); - this.targetPosition = new THREE.Vector3(); - this.physicsInit(); - - this.el.addEventListener(this.GRAB_EVENT, e => this.start(e)); - this.el.addEventListener(this.UNGRAB_EVENT, e => this.end(e)); - this.el.addEventListener("mouseout", e => this.lostGrabber(e)); - - this.toggle = false; - this.lastGrabber = null; - }, - update: function() { - this.physicsUpdate(); - this.xFactor = this.data.invert ? -1 : 1; - this.zFactor = this.data.invert ? -1 : 1; - this.yFactor = (this.data.invert ? -1 : 1) * !this.data.suppressY; - }, - tick: (function() { - const q = new THREE.Quaternion(); - const v = new THREE.Vector3(); - - return function() { - let entityPosition; - if (this.grabber) { - // reflect on z-axis to point in same direction as the laser - this.targetPosition.copy(this.grabDirection); - this.targetPosition - .applyQuaternion(this.grabber.object3D.getWorldQuaternion(q)) - .setLength(this.grabDistance) - .add(this.grabber.object3D.getWorldPosition(v)) - .add(this.grabOffset); - if (this.deltaPositionIsValid) { - // relative position changes work better with nested entities - this.deltaPosition.sub(this.targetPosition); - entityPosition = this.el.getAttribute("position"); - this.destPosition.x = entityPosition.x - this.deltaPosition.x * this.xFactor; - this.destPosition.y = entityPosition.y - this.deltaPosition.y * this.yFactor; - this.destPosition.z = entityPosition.z - this.deltaPosition.z * this.zFactor; - this.el.setAttribute("position", this.destPosition); - } else { - this.deltaPositionIsValid = true; - } - this.deltaPosition.copy(this.targetPosition); - } - }; - })(), - remove: function() { - this.el.removeEventListener(this.GRAB_EVENT, this.start); - this.el.removeEventListener(this.UNGRAB_EVENT, this.end); - this.physicsRemove(); - }, - start: function(evt) { - if (evt.defaultPrevented || !this.startButtonOk(evt)) { - return; - } - // room for more grabbers? - let grabAvailable = !Number.isFinite(this.data.maxGrabbers) || this.grabbers.length < this.data.maxGrabbers; - if (Number.isFinite(this.data.maxGrabbers) && !grabAvailable && this.grabbed) { - this.grabbers[0].components["super-hands"].onGrabEndButton(); - grabAvailable = true; - } - if (this.grabbers.indexOf(evt.detail.hand) === -1 && grabAvailable) { - if (!evt.detail.hand.object3D) { - console.warn("grabbable entities must have an object3D"); - return; - } - this.grabbers.push(evt.detail.hand); - // initiate physics if available, otherwise manual - if (!this.physicsStart(evt) && !this.grabber) { - this.grabber = evt.detail.hand; - this.resetGrabber(); - } - // notify super-hands that the gesture was accepted - if (evt.preventDefault) { - evt.preventDefault(); - } - this.grabbed = true; - this.el.addState(this.GRABBED_STATE); - } - }, - end: function(evt) { - const handIndex = this.grabbers.indexOf(evt.detail.hand); - if (evt.defaultPrevented || !this.endButtonOk(evt)) { - return; - } - - const type = evt.detail && evt.detail.buttonEvent ? evt.detail.buttonEvent.type : null; - - if (this.toggle && this.lastGrabber !== this.grabbers[0]) { - this.toggle = false; - this.lastGrabber = null; - } - - if (handIndex !== -1) { - this.grabber = this.grabbers[0]; - } - - if ((this.isPrimaryRelease(type) && !this.toggle) || this.isSecondaryRelease(type)) { - this.toggle = true; - this.lastGrabber = this.grabbers[0]; - return; - } else if (this.toggle && this.isPrimaryRelease(type)) { - this.toggle = false; - this.lastGrabber = null; - } - - if (handIndex !== -1) { - this.grabbers.splice(handIndex, 1); - this.grabber = this.grabbers[0]; - } - - this.physicsEnd(evt); - if (!this.resetGrabber()) { - this.grabbed = false; - this.el.removeState(this.GRABBED_STATE); - } - if (evt.preventDefault) { - evt.preventDefault(); - } - }, - resetGrabber: (() => { - const objPos = new THREE.Vector3(); - const grabPos = new THREE.Vector3(); - return function() { - if (!this.grabber) { - return false; - } - const raycaster = this.grabber.getAttribute("raycaster"); - this.deltaPositionIsValid = false; - this.grabDistance = this.el.object3D - .getWorldPosition(objPos) - .distanceTo(this.grabber.object3D.getWorldPosition(grabPos)); - if (raycaster) { - this.grabDirection = raycaster.direction; - this.grabOffset = raycaster.origin; - } - return true; - }; - })(), - lostGrabber: function(evt) { - const i = this.grabbers.indexOf(evt.relatedTarget); - // if a queued, non-physics grabber leaves the collision zone, forget it - if (i !== -1 && evt.relatedTarget !== this.grabber && !this.physicsIsConstrained(evt.relatedTarget)) { - this.grabbers.splice(i, 1); - } - }, - - isPrimaryRelease(type) { - return this.data.primaryReleaseEvents.indexOf(type) !== -1; - }, - - isSecondaryRelease(type) { - return this.data.secondaryReleaseEvents.indexOf(type) !== -1; - } - }) -); diff --git a/src/components/hand-controls2.js b/src/components/hand-controls2.js index 961790fead81073fcf066ad907079c2497461cd8..cdafd23c4379a6e8c1a87c5a1156a2d738e09c90 100644 --- a/src/components/hand-controls2.js +++ b/src/components/hand-controls2.js @@ -115,17 +115,11 @@ AFRAME.registerComponent("hand-controls2", { const controlConfiguration = { hand: hand, - model: false, orientationOffset: { x: 0, y: 0, z: 0 } }; if (hand !== prevData) { - el.setAttribute("vive-controls", controlConfiguration); - el.setAttribute("oculus-touch-controls", controlConfiguration); - el.setAttribute("oculus-go-controls", controlConfiguration); - el.setAttribute("windows-motion-controls", controlConfiguration); - el.setAttribute("daydream-controls", controlConfiguration); - el.setAttribute("gearvr-controls", controlConfiguration); + el.setAttribute("tracked-controls", controlConfiguration); } }, diff --git a/src/components/in-world-hud.js b/src/components/in-world-hud.js index aa0d8f83f1bf2e57a6a884b16c154b41d22a0bb2..293344113b055ef5b6ff603b3b6050e8d6089280 100644 --- a/src/components/in-world-hud.js +++ b/src/components/in-world-hud.js @@ -11,7 +11,7 @@ AFRAME.registerComponent("in-world-hud", { init() { this.mic = this.el.querySelector(".mic"); this.freeze = this.el.querySelector(".freeze"); - this.pen = this.el.querySelector(".pen"); + this.pen = this.el.querySelector(".penhud"); this.cameraBtn = this.el.querySelector(".cameraBtn"); this.background = this.el.querySelector(".bg"); const renderOrder = window.APP.RENDER_ORDER; @@ -54,19 +54,19 @@ AFRAME.registerComponent("in-world-hud", { this.el.sceneEl.addEventListener("stateadded", this.onStateChange); this.el.sceneEl.addEventListener("stateremoved", this.onStateChange); - this.mic.addEventListener("click", this.onMicClick); - this.freeze.addEventListener("click", this.onFreezeClick); + this.mic.addEventListener("mousedown", this.onMicClick); + this.freeze.addEventListener("mousedown", this.onFreezeClick); this.pen.addEventListener("mousedown", this.onPenClick); - this.cameraBtn.addEventListener("click", this.onCameraClick); + this.cameraBtn.addEventListener("mousedown", this.onCameraClick); }, pause() { this.el.sceneEl.removeEventListener("stateadded", this.onStateChange); this.el.sceneEl.removeEventListener("stateremoved", this.onStateChange); - this.mic.removeEventListener("click", this.onMicClick); - this.freeze.removeEventListener("click", this.onFreezeClick); + this.mic.removeEventListener("mousedown", this.onMicClick); + this.freeze.removeEventListener("mousedown", this.onFreezeClick); this.pen.removeEventListener("mousedown", this.onPenClick); - this.cameraBtn.removeEventListener("click", this.onCameraClick); + this.cameraBtn.removeEventListener("mousedown", this.onCameraClick); } }); diff --git a/src/components/input-configurator.js b/src/components/input-configurator.js deleted file mode 100644 index 356213fbf319e5037bcb21166aa11e794ccc5f94..0000000000000000000000000000000000000000 --- a/src/components/input-configurator.js +++ /dev/null @@ -1,171 +0,0 @@ -import TouchEventsHandler from "../utils/touch-events-handler.js"; -import MouseEventsHandler from "../utils/mouse-events-handler.js"; -import GearVRMouseEventsHandler from "../utils/gearvr-mouse-events-handler.js"; -import ActionEventHandler from "../utils/action-event-handler.js"; - -AFRAME.registerComponent("input-configurator", { - schema: { - cursorController: { type: "selector" }, - gazeTeleporter: { type: "selector" }, - camera: { type: "selector" }, - playerRig: { type: "selector" }, - leftController: { type: "selector" }, - rightController: { type: "selector" }, - leftControllerRayObject: { type: "string" }, - rightControllerRayObject: { type: "string" }, - gazeCursorRayObject: { type: "string" } - }, - - init() { - this.inVR = this.el.sceneEl.is("vr-mode"); - this.isMobile = AFRAME.utils.device.isMobile(); - this.eventHandlers = []; - this.controllerQueue = []; - this.cursor = this.data.cursorController.components["cursor-controller"]; - this.gazeTeleporter = this.data.gazeTeleporter.components["teleport-controls"]; - this.cameraController = this.data.camera.components["pitch-yaw-rotator"]; - this.playerRig = this.data.playerRig; - this.handedness = "right"; - - this.onEnterVR = this.onEnterVR.bind(this); - this.onExitVR = this.onExitVR.bind(this); - this.handleControllerConnected = this.handleControllerConnected.bind(this); - this.handleControllerDisconnected = this.handleControllerDisconnected.bind(this); - - this.configureInput(); - }, - - play() { - this.el.sceneEl.addEventListener("controllerconnected", this.handleControllerConnected); - this.el.sceneEl.addEventListener("controllerdisconnected", this.handleControllerDisconnected); - this.el.sceneEl.addEventListener("enter-vr", this.onEnterVR); - this.el.sceneEl.addEventListener("exit-vr", this.onExitVR); - }, - - pause() { - this.el.sceneEl.removeEventListener("controllerconnected", this.handleControllerConnected); - this.el.sceneEl.removeEventListener("controllerdisconnected", this.handleControllerDisconnected); - this.el.sceneEl.removeEventListener("enter-vr", this.onEnterVR); - this.el.sceneEl.removeEventListener("exit-vr", this.onExitVR); - }, - - onEnterVR() { - this.inVR = true; - this.tearDown(); - this.configureInput(); - this.updateController(); - }, - - onExitVR() { - this.inVR = false; - this.tearDown(); - this.configureInput(); - this.updateController(); - }, - - tearDown() { - this.eventHandlers.forEach(h => h.tearDown()); - this.eventHandlers = []; - this.actionEventHandler = null; - if (this.lookOnMobile) { - this.lookOnMobile.el.removeAttribute("look-on-mobile"); - this.lookOnMobile = null; - } - this.cursorRequiresManagement = false; - }, - - addLookOnMobile() { - const onAdded = e => { - if (e.detail.name !== "look-on-mobile") return; - this.lookOnMobile = this.el.sceneEl.components["look-on-mobile"]; - }; - this.el.sceneEl.addEventListener("componentinitialized", onAdded); - // This adds look-on-mobile to the scene - this.el.sceneEl.setAttribute("look-on-mobile", "camera", this.data.camera); - }, - - configureInput() { - this.actionEventHandler = new ActionEventHandler(this.el.sceneEl, this.cursor); - this.eventHandlers.push(this.actionEventHandler); - - if (this.inVR) { - this.cameraController.pause(); - this.cursorRequiresManagement = true; - this.cursor.el.setAttribute("cursor-controller", "near", 0); - if (this.isMobile) { - this.eventHandlers.push(new GearVRMouseEventsHandler(this.cursor, this.gazeTeleporter)); - } else { - this.eventHandlers.push(new MouseEventsHandler(this.cursor, this.cameraController)); - } - } else { - this.cameraController.play(); - if (this.isMobile) { - this.cursor.setCursorVisibility(false); - this.eventHandlers.push(new TouchEventsHandler(this.cursor, this.cameraController, this.cursor.el)); - this.addLookOnMobile(); - } else { - this.eventHandlers.push(new MouseEventsHandler(this.cursor, this.cameraController)); - this.cursor.el.setAttribute("cursor-controller", "near", 0.3); - } - } - }, - - tick() { - if (this.cursorRequiresManagement && this.controller) { - this.actionEventHandler.manageCursorEnabled(); - } - }, - - handleControllerConnected: function(e) { - const data = { - controller: e.target, - handedness: e.detail.component.data.hand - }; - - if (data.handedness === this.handedness) { - this.controllerQueue.unshift(data); - } else { - this.controllerQueue.push(data); - } - - this.updateController(); - }, - - handleControllerDisconnected: function(e) { - for (let i = 0; i < this.controllerQueue.length; i++) { - if (e.target === this.controllerQueue[i].controller) { - this.controllerQueue.splice(i, 1); - this.updateController(); - return; - } - } - }, - - updateController: function() { - this.cursor.setCursorVisibility(true); - const controllerData = this.controllerQueue.length ? this.controllerQueue[0] : null; - - if (controllerData) { - this.controller = controllerData.controller; - this.actionEventHandler.setHandThatAlsoDrivesCursor(this.controller); - } else { - this.controller = null; - this.actionEventHandler.setHandThatAlsoDrivesCursor(null); - } - - let rayObject; - let drawLine; - if (controllerData && this.inVR) { - rayObject = - controllerData.handedness === "left" ? this.data.leftControllerRayObject : this.data.rightControllerRayObject; - drawLine = true; - } else if (this.inVR) { - rayObject = this.data.gazeCursorRayObject; - drawLine = false; - } else { - rayObject = null; - drawLine = false; - } - this.cursor.el.setAttribute("cursor-controller", { rayObject, drawLine }); - } -}); diff --git a/src/components/look-on-mobile.js b/src/components/look-on-mobile.js deleted file mode 100644 index 7cde5ba136933c7d658693f00e3a3ea3a3316307..0000000000000000000000000000000000000000 --- a/src/components/look-on-mobile.js +++ /dev/null @@ -1,103 +0,0 @@ -const TWOPI = Math.PI * 2; - -class CircularBuffer { - constructor(length) { - this.items = new Array(length).fill(0); - this.writePtr = 0; - } - - push(item) { - this.items[this.writePtr] = item; - this.writePtr = (this.writePtr + 1) % this.items.length; - } -} - -const abs = Math.abs; -// Input: two numbers between [-Math.PI, Math.PI] -// Output: difference between them, where -Math.PI === Math.PI -const difference = (curr, prev) => { - const a = curr - prev; - const b = curr + TWOPI - prev; - const c = curr - (prev + TWOPI); - if (abs(a) < abs(b)) { - if (abs(a) < abs(c)) { - return a; - } - } - if (abs(b) < abs(c)) { - return b; - } - - return c; -}; - -const average = a => { - let sum = 0; - for (let i = 0; i < a.length; i++) { - const n = a[i]; - sum += n; - } - return sum / a.length; -}; - -AFRAME.registerComponent("look-on-mobile", { - schema: { - horizontalLookSpeedRatio: { default: 1.0 }, // motion applied to camera / motion of polyfill object - verticalLookSpeedRatio: { default: 1.0 }, // motion applied to camera / motion of polyfill object - camera: { type: "selector" } - }, - - init() { - this.hmdEuler = new THREE.Euler(); - this.hmdQuaternion = new THREE.Quaternion(); - this.prevX = this.hmdEuler.x; - this.prevY = this.hmdEuler.y; - this.pendingLookX = 0; - this.onRotateX = this.onRotateX.bind(this); - this.dXBuffer = new CircularBuffer(6); - this.dYBuffer = new CircularBuffer(6); - this.vrDisplay = window.webvrpolyfill.getPolyfillDisplays()[0]; - this.frameData = new window.webvrpolyfill.constructor.VRFrameData(); - }, - - play() { - this.el.addEventListener("rotateX", this.onRotateX); - }, - - pause() { - this.el.removeEventListener("rotateX", this.onRotateX); - }, - - update() { - this.cameraController = this.data.camera.components["pitch-yaw-rotator"]; - }, - - onRotateX(e) { - this.pendingLookX = e.detail.value; - }, - - tick() { - const hmdEuler = this.hmdEuler; - const { horizontalLookSpeedRatio, verticalLookSpeedRatio } = this.data; - this.vrDisplay.getFrameData(this.frameData); - if (this.frameData.pose.orientation !== null) { - this.hmdQuaternion.fromArray(this.frameData.pose.orientation); - hmdEuler.setFromQuaternion(this.hmdQuaternion, "YXZ"); - } - - const dX = THREE.Math.RAD2DEG * difference(hmdEuler.x, this.prevX); - const dY = THREE.Math.RAD2DEG * difference(hmdEuler.y, this.prevY); - - this.dXBuffer.push(Math.abs(dX) < 0.001 ? 0 : dX); - this.dYBuffer.push(Math.abs(dY) < 0.001 ? 0 : dY); - - const deltaYaw = average(this.dYBuffer.items) * horizontalLookSpeedRatio; - const deltaPitch = average(this.dXBuffer.items) * verticalLookSpeedRatio + this.pendingLookX; - - this.cameraController.look(deltaPitch, deltaYaw); - - this.prevX = hmdEuler.x; - this.prevY = hmdEuler.y; - this.pendingLookX = 0; - } -}); diff --git a/src/components/pinch-to-move.js b/src/components/pinch-to-move.js index 79e51f0dab313dc36facf3a37ac2a326053ac0e7..28dc43cb5d2584532bf549fc3b08aaf01c1ce953 100644 --- a/src/components/pinch-to-move.js +++ b/src/components/pinch-to-move.js @@ -1,36 +1,18 @@ +import { paths } from "../systems/userinput/paths"; + AFRAME.registerComponent("pinch-to-move", { schema: { speed: { default: 0.25 } }, init() { - this.onPinch = this.onPinch.bind(this); this.axis = [0, 0]; - this.pinch = 0; - this.prevPinch = 0; - this.needsMove = false; - }, - play() { - this.el.addEventListener("pinch", this.onPinch); - }, - pause() { - this.el.removeEventListener("pinch", this.onPinch); }, tick() { - if (this.needsMove) { - const diff = this.pinch - this.prevPinch; - this.axis[1] = diff * this.data.speed; + const userinput = AFRAME.scenes[0].systems.userinput; + const pinch = userinput.readFrameValueAtPath(paths.device.touchscreen.pinchDelta); + if (pinch) { + this.axis[1] = pinch * this.data.speed; this.el.emit("move", { axis: this.axis }); - this.prevPinch = this.pinch; - } - this.needsMove = false; - }, - onPinch(e) { - const { isNewPinch, distance } = e.detail; - if (isNewPinch) { - this.prevPinch = distance; - return; } - this.pinch = distance; - this.needsMove = true; } }); diff --git a/src/components/pitch-yaw-rotator.js b/src/components/pitch-yaw-rotator.js index bb7eb4c56d2ec9a21fb58d72deda5be1402c35a3..f4c5f684ef5bd42cdb4d006464b1160f104de244 100644 --- a/src/components/pitch-yaw-rotator.js +++ b/src/components/pitch-yaw-rotator.js @@ -1,3 +1,5 @@ +import { paths } from "../systems/userinput/paths"; + const degToRad = THREE.Math.degToRad; const radToDeg = THREE.Math.radToDeg; @@ -26,7 +28,12 @@ AFRAME.registerComponent("pitch-yaw-rotator", { }, tick() { - this.el.object3D.rotation.set(degToRad(this.pitch), degToRad(this.yaw), 0); - this.el.object3D.rotation.order = "YXZ"; + const userinput = AFRAME.scenes[0].systems.userinput; + const cameraDelta = userinput.readFrameValueAtPath(paths.actions.cameraDelta); + if (cameraDelta) { + this.look(cameraDelta[1], cameraDelta[0]); + this.el.object3D.rotation.set(degToRad(this.pitch), degToRad(this.yaw), 0); + this.el.object3D.rotation.order = "YXZ"; + } } }); diff --git a/src/components/super-networked-interactable.js b/src/components/super-networked-interactable.js index d2e04e23911cb499c1ce2c9eb7a1a62ab1ae2e53..86a49e71b2cea0639f3ef8de81120356177893fd 100644 --- a/src/components/super-networked-interactable.js +++ b/src/components/super-networked-interactable.js @@ -1,3 +1,5 @@ +import { paths } from "../systems/userinput/paths"; + /** * Manages ownership and haptics on an interatable * @namespace network @@ -82,6 +84,29 @@ AFRAME.registerComponent("super-networked-interactable", { } }, + tick: function() { + const userinput = AFRAME.scenes[0].systems.userinput; + const grabbable = this.el.components.grabbable; + let delta = 0; + if (this.el.is("grabbed") && this.el.components.hasOwnProperty("stretchable")) { + const isLeftHand = grabbable.grabbers[0] === document.querySelector("[super-hands], #player-left-controller"); + if (isLeftHand) { + delta = userinput.readFrameValueAtPath(paths.actions.leftHand.scaleGrabbedGrabbable); + } + const isRightHand = grabbable.grabbers[0] === document.querySelector("[super-hands], #player-right-controller"); + if (isRightHand) { + delta = userinput.readFrameValueAtPath(paths.actions.rightHand.scaleGrabbedGrabbable); + } + const isCursor = document.querySelector("[cursor-controller]").components["cursor-controller"].data.cursor; + if (isCursor) { + delta = userinput.readFrameValueAtPath(paths.actions.cursor.scaleGrabbedGrabbable); + } + } + if (delta) { + this._changeScale(delta); + } + }, + _stateAdded(evt) { switch (evt.detail) { case "scaleUp": diff --git a/src/components/super-spawner.js b/src/components/super-spawner.js index 77e18f67a737f5bf5fdfddf30b41e624d65ef1a4..bd0992e4bf7850897154bdd89f7db5cfed267247 100644 --- a/src/components/super-spawner.js +++ b/src/components/super-spawner.js @@ -114,7 +114,8 @@ AFRAME.registerComponent("super-spawner", { }, async onSpawnEvent() { - const controllerCount = this.el.sceneEl.components["input-configurator"].controllerQueue.length; + // TODO: I remove input-configurato, so this is wrong: + const controllerCount = 0; //this.el.sceneEl.components["input-configurator"].controllerQueue.length; const using6DOF = controllerCount > 1 && this.el.sceneEl.is("vr-mode"); const hand = using6DOF ? this.data.superHand : this.data.cursorSuperHand; @@ -165,8 +166,6 @@ AFRAME.registerComponent("super-spawner", { ); entity.object3D.scale.copy(this.data.useCustomSpawnScale ? this.data.spawnScale : this.el.object3D.scale); - this.activateCooldown(); - await waitForEvent("body-loaded", entity); // If we are still holding the spawner with the hand that grabbed to create this entity, release the spawner and grab the entity @@ -179,6 +178,8 @@ AFRAME.registerComponent("super-spawner", { hand.emit(this.data.grabEvents[i], { targetEntity: entity }); } } + + this.activateCooldown(); }, onGrabEnd(e) { diff --git a/src/components/tools/pen.js b/src/components/tools/pen.js index 855c94e0a1d11f5905dd20f55acdda27ebad227d..7e4b097b0d36ea65f4a39b1040b16828bd566b6d 100644 --- a/src/components/tools/pen.js +++ b/src/components/tools/pen.js @@ -1,3 +1,28 @@ +import { paths } from "../../systems/userinput/paths"; + +const pathsMap = { + "player-right-controller": { + startDrawing: paths.actions.rightHand.startDrawing, + stopDrawing: paths.actions.rightHand.stopDrawing, + penNextColor: paths.actions.rightHand.penNextColor, + penPrevColor: paths.actions.rightHand.penPrevColor, + scalePenTip: paths.actions.rightHand.scalePenTip + }, + "player-left-controller": { + startDrawing: paths.actions.leftHand.startDrawing, + stopDrawing: paths.actions.leftHand.stopDrawing, + penNextColor: paths.actions.leftHand.penNextColor, + penPrevColor: paths.actions.leftHand.penPrevColor, + scalePenTip: paths.actions.leftHand.scalePenTip + }, + cursor: { + startDrawing: paths.actions.cursor.startDrawing, + stopDrawing: paths.actions.cursor.stopDrawing, + penNextColor: paths.actions.cursor.penNextColor, + penPrevColor: paths.actions.cursor.penPrevColor, + scalePenTip: paths.actions.cursor.scalePenTip + } +}; /** * Pen tool * A tool that allows drawing on networked-drawing components. @@ -41,9 +66,6 @@ AFRAME.registerComponent("pen", { }, init() { - this._stateAdded = this._stateAdded.bind(this); - this._stateRemoved = this._stateRemoved.bind(this); - this.timeSinceLastDraw = 0; this.lastPosition = new THREE.Vector3(); @@ -65,14 +87,6 @@ AFRAME.registerComponent("pen", { play() { this.drawingManager = document.querySelector(this.data.drawingManager).components["drawing-manager"]; this.drawingManager.createDrawing(); - - this.el.parentNode.addEventListener("stateadded", this._stateAdded); - this.el.parentNode.addEventListener("stateremoved", this._stateRemoved); - }, - - pause() { - this.el.parentNode.removeEventListener("stateadded", this._stateAdded); - this.el.parentNode.removeEventListener("stateremoved", this._stateRemoved); }, update(prevData) { @@ -85,6 +99,28 @@ AFRAME.registerComponent("pen", { }, tick(t, dt) { + const grabber = this.el.parentNode.components.grabbable.grabbers[0]; + const userinput = AFRAME.scenes[0].systems.userinput; + if (grabber && pathsMap[grabber.id]) { + const paths = pathsMap[grabber.id]; + if (userinput.readFrameValueAtPath(paths.startDrawing)) { + this._startDraw(); + } + if (userinput.readFrameValueAtPath(paths.stopDrawing)) { + this._endDraw(); + } + const penScaleMod = userinput.readFrameValueAtPath(paths.scalePenTip); + if (penScaleMod) { + this._changeRadius(penScaleMod); + } + if (userinput.readFrameValueAtPath(paths.penNextColor)) { + this._changeColor(1); + } + if (userinput.readFrameValueAtPath(paths.penPrevColor)) { + this._changeColor(-1); + } + } + this.el.object3D.getWorldPosition(this.worldPosition); if (!almostEquals(0.005, this.worldPosition, this.lastPosition)) { @@ -145,44 +181,5 @@ AFRAME.registerComponent("pen", { _changeRadius(mod) { this.data.radius = Math.max(this.data.minRadius, Math.min(this.data.radius + mod, this.data.maxRadius)); this.el.setAttribute("radius", this.data.radius); - }, - - _stateAdded(evt) { - switch (evt.detail) { - case "activated": - this._startDraw(); - break; - case "colorNext": - this._changeColor(1); - break; - case "colorPrev": - this._changeColor(-1); - break; - case "radiusUp": - this._changeRadius(this.data.minRadius); - break; - case "radiusDown": - this._changeRadius(-this.data.minRadius); - break; - case "grabbed": - this.grabbed = true; - break; - default: - break; - } - }, - - _stateRemoved(evt) { - switch (evt.detail) { - case "activated": - this._endDraw(); - break; - case "grabbed": - this.grabbed = false; - this._endDraw(); - break; - default: - break; - } } }); diff --git a/src/hub.html b/src/hub.html index cda3976f09403f830a6c9c4e65e05874d6507070..74a8d6c6abe272070c2ebb258021fd0649caa1c9 100644 --- a/src/hub.html +++ b/src/hub.html @@ -32,16 +32,6 @@ vr-mode-ui="enabled: false" pinch-to-move stats-plus="false" - input-configurator=" - gazeCursorRayObject: #player-camera; - cursorController: #cursor-controller; - gazeTeleporter: #gaze-teleport; - camera: #player-camera; - playerRig: #player-rig; - leftController: #player-left-controller; - leftControllerRayObject: #player-left-controller; - rightController: #player-right-controller; - rightControllerRayObject: #player-right-controller;" > <a-assets> @@ -160,8 +150,6 @@ position-at-box-shape-border="target:.action-buttons" destroy-at-extreme-distances set-yxz-order - activatable__increase-scale="buttonStartEvents: scroll_right; buttonEndEvents: horizontal_scroll_release; activatedState: scaleUp;" - activatable__decrease-scale="buttonStartEvents: scroll_left; buttonEndEvents: horizontal_scroll_release; activatedState: scaleDown;" > <a-entity class="action-buttons" visible-while-frozen> <a-entity mixin="rounded-text-button" pin-networked-object-button position="0 0.125 0.01"> </a-entity> @@ -174,18 +162,12 @@ <template id="pen-interactable"> <a-entity - class="interactable toggle" + class="pen interactable" super-networked-interactable="counter: #pen-counter;" body="type: dynamic; shape: none; mass: 1;" - grabbable-toggle="maxGrabbers: 1;" + grabbable="maxGrabbers: 1" sticky-object="autoLockOnRelease: true; autoLockOnLoad: true;" hoverable - activatable__draw-hand="buttonStartEvents: secondary_hand_grab; buttonEndEvents: secondary_hand_release;" - activatable__draw-cursor="buttonStartEvents: secondary-cursor-grab; buttonEndEvents: secondary-cursor-release;" - activatable__color-next="buttonStartEvents: next_color, scroll_right; buttonEndEvents: thumb_up, secondary_hand_release, horizontal_scroll_release; activatedState: colorNext;" - activatable__color-prev="buttonStartEvents: previous_color, scroll_left; buttonEndEvents: thumb_up, secondary_hand_release, horizontal_scroll_release; activatedState: colorPrev;" - activatable__increase-radius="buttonStartEvents: increase_radius, scroll_up; buttonEndEvents: thumb_up, secondary_hand_release, vertical_scroll_release; activatedState: radiusUp;" - activatable__decrease-radius="buttonStartEvents: decrease_radius, scroll_down; buttonEndEvents: thumb_up, secondary_hand_release, vertical_scroll_release; activatedState: radiusDown;" scale="0.5 0.5 0.5" > <a-sphere @@ -207,11 +189,10 @@ <template id="interactable-camera"> <a-entity - class="interactable toggle" - grabbable-toggle="maxGrabbers: 1;" + class="interactable icamera" + grabbable hoverable - activatable__snap-hand="buttonStartEvents: secondary_hand_grab; buttonEndEvents: secondary_hand_release;" - activatable__snap-cursor="buttonStartEvents: secondary-cursor-grab; buttonEndEvents: secondary-cursor-release;" + stretchable camera-tool body="type: dynamic; shape: none; mass: 1;" shape="shape: box; halfExtents: 0.22 0.145 0.1; offset: 0 0.02 0" @@ -321,6 +302,8 @@ dragDropEndButtons: cursor-release, primary_hand_release, secondary_hand_release; activateStartButtons: secondary-cursor-grab, secondary_hand_grab, next_color, previous_color, increase_radius, decrease_radius, scroll_up, scroll_down, scroll_left, scroll_right; activateEndButtons: secondary-cursor-release, secondary_hand_release, vertical_scroll_release, horizontal_scroll_release, thumb_up;" + action-to-event__grab="path: /actions/cursorGrab; event: cursor-grab" + action-to-event__drop="path: /actions/cursorDrop; event: cursor-release" ></a-sphere> <!-- Player Rig --> @@ -343,10 +326,10 @@ <a-rounded height="0.08" width="0.5" color="#000000" position="-0.20 0.125 0" radius="0.040" opacity="0.35" class="hud bg"></a-rounded> <a-entity id="hud-hub-entry-link" text=" value:; width:1.1; align:center;" position="0.05 0.165 0"></a-entity> <a-rounded height="0.13" width="0.59" color="#000000" position="-0.24 -0.065 0" radius="0.065" opacity="0.35" class="hud bg"></a-rounded> - <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Mute Mic; activeTooltipText: Unmute Mic; image: #mute-off; hoverImage: #mute-off-hover; activeImage: #mute-on; activeHoverImage: #mute-on-hover" scale="0.1 0.1 0.1" position="-0.17 0 0.001" class="ui hud mic" material="alphaTest:0.1;"></a-image> - <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Pause; activeTooltipText: Resume; image: #freeze-off; hoverImage: #freeze-off-hover; activeImage: #freeze-on; activeHoverImage: #freeze-on-hover" scale="0.2 0.2 0.2" position="0 0 0.005" class="ui hud freeze"></a-image> - <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Pen; activeTooltipText: Pen; image: #spawn-pen; hoverImage: #spawn-pen-hover; activeImage: #spawn-pen; activeHoverImage: #spawn-pen-hover" scale="0.1 0.1 0.1" position="0.17 0 0.001" class="ui hud pen" material="alphaTest:0.1;"></a-image> - <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Camera; activeTooltipText: Camera; image: #spawn-camera; hoverImage: #spawn-camera-hover; activeImage: #spawn-camera; activeHoverImage: #spawn-camera-hover" scale="0.1 0.1 0.1" position="0.28 0 0.001" class="ui hud cameraBtn" material="alphaTest:0.1;"></a-image> + <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Mute Mic; activeTooltipText: Unmute Mic; image: #mute-off; hoverImage: #mute-off-hover; activeImage: #mute-on; activeHoverImage: #mute-on-hover" scale="0.1 0.1 0.1" position="-0.17 0 0.001" class="ui hud mic" material="alphaTest:0.1;" hoverable></a-image> + <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Pause; activeTooltipText: Resume; image: #freeze-off; hoverImage: #freeze-off-hover; activeImage: #freeze-on; activeHoverImage: #freeze-on-hover" scale="0.2 0.2 0.2" position="0 0 0.005" class="ui hud freeze" hoverable></a-image> + <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Pen; activeTooltipText: Pen; image: #spawn-pen; hoverImage: #spawn-pen-hover; activeImage: #spawn-pen; activeHoverImage: #spawn-pen-hover" scale="0.1 0.1 0.1" position="0.17 0 0.001" class="ui hud penhud" material="alphaTest:0.1;" hoverable></a-image> + <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Camera; activeTooltipText: Camera; image: #spawn-camera; hoverImage: #spawn-camera-hover; activeImage: #spawn-camera; activeHoverImage: #spawn-camera-hover" scale="0.1 0.1 0.1" position="0.28 0 0.001" class="ui hud cameraBtn" material="alphaTest:0.1;" hoverable></a-image> <a-rounded visible="false" id="hud-tooltip" height="0.08" width="0.3" color="#000000" position="-0.15 -0.2 0" rotation="-20 0 0" radius="0.025" opacity="0.35" class="hud bg"> <a-entity text="value: Mute Mic; align:center;" position="0.15 0.04 0.001" ></a-entity> </a-rounded> @@ -369,10 +352,12 @@ button: gaze-teleport_; collisionEntities: [nav-mesh]; drawIncrementally: true; - incrementalDrawMs: 600; + incrementalDrawMs: 300; hitOpacity: 0.3; missOpacity: 0.1; curveShootingSpeed: 12;" + action-to-event__start-teleport="path: /actions/startTeleport; event: gaze-teleport_down" + action-to-event__stop-teleport="path: /actions/stopTeleport; event: gaze-teleport_up" ></a-entity> </a-entity> @@ -384,10 +369,10 @@ teleport-controls=" cameraRig: #player-rig; teleportOrigin: #player-camera; - button: cursor-teleport_; + button: left-teleport_; collisionEntities: [nav-mesh]; drawIncrementally: true; - incrementalDrawMs: 600; + incrementalDrawMs: 300; hitOpacity: 0.3; missOpacity: 0.1; curveShootingSpeed: 12;" @@ -395,6 +380,10 @@ body="type: static; shape: none;" mixin="controller-super-hands" controls-shape-offset + action-to-event__a="path: /actions/leftHandStartTeleport; event: left-teleport_down;" + action-to-event__b="path: /actions/leftHandStopTeleport; event: left-teleport_up;" + action-to-event__c="path: /actions/leftHandGrab; event: primary_hand_grab;" + action-to-event__d="path: /actions/leftHandDrop; event: primary_hand_release;" > </a-entity> @@ -406,18 +395,21 @@ teleport-controls=" cameraRig: #player-rig; teleportOrigin: #player-camera; - button: cursor-teleport_; + button: right-teleport_; collisionEntities: [nav-mesh]; drawIncrementally: true; - incrementalDrawMs: 600; + incrementalDrawMs: 300; hitOpacity: 0.3; missOpacity: 0.1; curveShootingSpeed: 12;" haptic-feedback - event-repeater="events: haptic_pulse; eventSource: #cursor" body="type: static; shape: none;" mixin="controller-super-hands" controls-shape-offset + action-to-event__a="path: /actions/rightHandStartTeleport; event: right-teleport_down;" + action-to-event__b="path: /actions/rightHandStopTeleport; event: right-teleport_up;" + action-to-event__c="path: /actions/rightHandGrab; event: primary_hand_grab;" + action-to-event__d="path: /actions/rightHandDrop; event: primary_hand_release;" > </a-entity> @@ -469,12 +461,13 @@ </a-entity> <a-entity - super-spawner=" - template: #pen-interactable; - src: https://asset-bundles-prod.reticulum.io/interactables/DrawingPen/DrawingPen-34fb4aee27.gltf; - spawnEvent: spawn_pen; - superHand: #player-right-controller; - cursorSuperHand: #cursor;" + action-to-event="path: /actions/spawnPen; event: spawn_pen" + super-spawner=" + template: #pen-interactable; + src: https://asset-bundles-prod.reticulum.io/interactables/DrawingPen/DrawingPen-34fb4aee27.gltf; + spawnEvent: spawn_pen; + superHand: #player-right-controller; + cursorSuperHand: #cursor;" ></a-entity> </a-scene> diff --git a/src/hub.js b/src/hub.js index 64ab033dbfa30a965fbdf2a05ca7702c9f96a5e1..fcd1c6bdb19e9860b3d109931e5d1984c6f43823 100644 --- a/src/hub.js +++ b/src/hub.js @@ -12,7 +12,6 @@ import "three/examples/js/loaders/GLTFLoader"; import "networked-aframe/src/index"; import "naf-janus-adapter"; import "aframe-teleport-controls"; -import "aframe-input-mapping-component"; import "aframe-billboard-component"; import "aframe-rounded"; import "webrtc-adapter"; @@ -23,17 +22,8 @@ import { getReticulumFetchUrl } from "./utils/phoenix-utils"; import nextTick from "./utils/next-tick"; import { addAnimationComponents } from "./utils/animation"; - -import trackpad_dpad4 from "./behaviours/trackpad-dpad4"; -import trackpad_scrolling from "./behaviours/trackpad-scrolling"; -import joystick_dpad4 from "./behaviours/joystick-dpad4"; -import msft_mr_axis_with_deadzone from "./behaviours/msft-mr-axis-with-deadzone"; -import { PressedMove } from "./activators/pressedmove"; -import { ReverseY } from "./activators/reversey"; import { Presence } from "phoenix"; -import "./activators/shortpress"; - import "./components/scene-components"; import "./components/wasd-to-analog2d"; //Might be a behaviour or activator in the future import "./components/mute-mic"; @@ -62,9 +52,7 @@ import "./components/networked-avatar"; import "./components/avatar-replay"; import "./components/media-views"; import "./components/pinch-to-move"; -import "./components/look-on-mobile"; import "./components/pitch-yaw-rotator"; -import "./components/input-configurator"; import "./components/auto-scale-cannon-physics-body"; import "./components/position-at-box-shape-border"; import "./components/pinned"; @@ -74,6 +62,7 @@ import "./components/destroy-at-extreme-distances"; import "./components/gamma-factor"; import "./components/visible-to-owner"; import "./components/camera-tool"; +import "./components/action-to-event"; import ReactDOM from "react-dom"; import React from "react"; @@ -89,6 +78,7 @@ import "./systems/nav"; import "./systems/personal-space-bubble"; import "./systems/app-mode"; import "./systems/exit-on-blur"; +import "./systems/userinput/userinput"; import "./gltf-component-mappings"; @@ -114,7 +104,6 @@ import "./components/super-networked-interactable"; import "./components/networked-counter"; import "./components/event-repeater"; import "./components/controls-shape-offset"; -import "./components/grabbable-toggle"; import "./components/set-yxz-order"; import "./components/cardboard-controls"; @@ -129,7 +118,6 @@ import "./components/tools/networked-drawing"; import "./components/tools/drawing-manager"; import registerNetworkSchemas from "./network-schemas"; -import { config as inputConfig } from "./input-mappings"; import registerTelemetry from "./telemetry"; import { getAvailableVREntryTypes } from "./utils/vr-caps-detect.js"; @@ -147,14 +135,6 @@ if (!isBotMode && !isTelemetryDisabled) { disableiOSZoom(); -AFRAME.registerInputBehaviour("trackpad_dpad4", trackpad_dpad4); -AFRAME.registerInputBehaviour("trackpad_scrolling", trackpad_scrolling); -AFRAME.registerInputBehaviour("joystick_dpad4", joystick_dpad4); -AFRAME.registerInputBehaviour("msft_mr_axis_with_deadzone", msft_mr_axis_with_deadzone); -AFRAME.registerInputActivator("pressedmove", PressedMove); -AFRAME.registerInputActivator("reverseY", ReverseY); -AFRAME.registerInputMappings(inputConfig, true); - const concurrentLoadDetector = new ConcurrentLoadDetector(); concurrentLoadDetector.start(); diff --git a/src/input-mappings.js b/src/input-mappings.js deleted file mode 100644 index 6b1b4e955b95acfd13c469804c2f1478fd1435a1..0000000000000000000000000000000000000000 --- a/src/input-mappings.js +++ /dev/null @@ -1,173 +0,0 @@ -const inGameActions = { - // Define action sets here. - // An action set separates "driving" controls from "menu" controls. - // Only one action set is active at a time. - default: { - move: { label: "Move" }, - snap_rotate_left: { label: "Snap Rotate Left" }, - snap_rotate_right: { label: "Snap Rotate Right" }, - action_mute: { label: "Mute" }, - action_teleport_down: { label: "Teleport Aim" }, - action_teleport_up: { label: "Teleport" }, - action_share_screen: { label: "Share Screen" } - }, - hud: { - action_ui_select_down: { label: "Select UI item" }, - action_ui_select_up: { label: "Select UI item" } - } -}; - -const config = { - behaviours: { - default: { - "oculus-touch-controls": { - joystick: "joystick_dpad4" - }, - "vive-controls": { - trackpad: "trackpad_dpad4", - trackpad_scrolling: "trackpad_scrolling" - }, - "windows-motion-controls": { - joystick: "joystick_dpad4", - axisMoveWithDeadzone: "msft_mr_axis_with_deadzone" - }, - "daydream-controls": { - trackpad: "trackpad_dpad4", - axisMoveWithDeadzone: "msft_mr_axis_with_deadzone" - }, - "gearvr-controls": { - trackpad: "trackpad_dpad4", - trackpad_scrolling: "trackpad_scrolling" - }, - "oculus-go-controls": { - trackpad: "trackpad_dpad4", - trackpad_scrolling: "trackpad_scrolling" - } - } - }, - mappings: { - default: { - "vive-controls": { - "trackpad.pressedmove": { left: "move" }, - trackpad_dpad4_pressed_west_down: { right: "snap_rotate_left" }, - trackpad_dpad4_pressed_east_down: { right: "snap_rotate_right" }, - trackpad_dpad4_pressed_center_down: { right: "action_primary_down" }, - trackpad_dpad4_pressed_north_down: { right: "action_primary_down" }, - trackpad_dpad4_pressed_south_down: { right: "action_primary_down" }, - trackpadup: { left: "action_primary_up", right: "action_primary_up" }, - menudown: "thumb_down", - menuup: "thumb_up", - gripdown: ["primary_action_grab", "middle_ring_pinky_down"], - gripup: ["primary_action_release", "middle_ring_pinky_up"], - trackpadtouchstart: "thumb_down", - trackpadtouchend: "thumb_up", - triggerdown: ["secondary_action_grab", "index_down"], - triggerup: ["secondary_action_release", "index_up"], - scroll: { left: "scroll_move", right: "scroll_move" } - }, - "oculus-touch-controls": { - joystick_dpad4_west: { right: "snap_rotate_left" }, - joystick_dpad4_east: { right: "snap_rotate_right" }, - gripdown: ["primary_action_grab", "middle_ring_pinky_down"], - gripup: ["primary_action_release", "middle_ring_pinky_up"], - abuttontouchstart: ["thumb_down", "increase_radius"], - abuttontouchend: "thumb_up", - bbuttontouchstart: ["thumb_down", "decrease_radius"], - bbuttontouchend: "thumb_up", - xbuttontouchstart: ["thumb_down", "increase_radius"], - xbuttontouchend: "thumb_up", - ybuttontouchstart: ["thumb_down", "decrease_radius"], - ybuttontouchend: "thumb_up", - surfacetouchstart: ["thumb_down", "next_color"], - surfacetouchend: "thumb_up", - thumbsticktouchstart: "thumb_down", - thumbsticktouchend: "thumb_up", - triggerdown: ["secondary_action_grab", "index_down"], - triggerup: ["secondary_action_release", "index_up"], - "axismove.reverseY": { left: "move", right: "scroll_move" }, - abuttondown: "action_primary_down", - abuttonup: "action_primary_up" - }, - "windows-motion-controls": { - joystick_dpad4_west: { - right: "snap_rotate_left" - }, - joystick_dpad4_east: { - right: "snap_rotate_right" - }, - "trackpad.pressedmove": { left: "move" }, - joystick_dpad4_pressed_west_down: { right: "snap_rotate_left" }, - joystick_dpad4_pressed_east_down: { right: "snap_rotate_right" }, - trackpaddown: { right: "action_primary_down" }, - trackpadup: { left: "action_primary_up", right: "action_primary_up" }, - menudown: "thumb_down", - menuup: "thumb_up", - gripdown: ["primary_action_grab", "middle_ring_pinky_down"], - gripup: ["primary_action_release", "middle_ring_pinky_up"], - trackpadtouchstart: "thumb_down", - trackpadtouchend: "thumb_up", - triggerdown: ["secondary_action_grab", "index_down"], - triggerup: ["secondary_action_release", "index_up"], - axisMoveWithDeadzone: { left: "move", right: "scroll_move" } - }, - "daydream-controls": { - trackpad_dpad4_pressed_west_down: "snap_rotate_left", - trackpad_dpad4_pressed_east_down: "snap_rotate_right", - trackpad_dpad4_pressed_center_down: ["action_primary_down"], - trackpad_dpad4_pressed_north_down: ["action_primary_down"], - trackpad_dpad4_pressed_south_down: ["action_primary_down"], - trackpadup: ["action_primary_up"], - axisMoveWithDeadzone: "scroll_move" - }, - "gearvr-controls": { - trackpad_dpad4_pressed_west_down: "snap_rotate_left", - trackpad_dpad4_pressed_east_down: "snap_rotate_right", - trackpad_dpad4_pressed_center_down: ["action_primary_down"], - trackpad_dpad4_pressed_north_down: ["action_primary_down"], - trackpad_dpad4_pressed_south_down: ["action_primary_down"], - trackpadup: ["action_primary_up"], - triggerdown: ["action_secondary_down"], - triggerup: ["action_secondary_up"], - scroll: "scroll_move" - }, - "oculus-go-controls": { - trackpad_dpad4_pressed_west_down: "snap_rotate_left", - trackpad_dpad4_pressed_east_down: "snap_rotate_right", - trackpad_dpad4_pressed_center_down: ["action_primary_down"], - trackpad_dpad4_pressed_north_down: ["action_primary_down"], - trackpad_dpad4_pressed_south_down: ["action_primary_down"], - trackpadup: ["action_primary_up"], - triggerdown: ["action_secondary_down"], - triggerup: ["action_secondary_up"], - scroll: "scroll_move" - }, - keyboard: { - m_press: "action_mute", - q_press: "snap_rotate_left", - e_press: "snap_rotate_right", - b_press: "action_share_screen", - - // We can't create a keyboard behaviour with AFIM yet, - // so these will get captured by wasd-to-analog2d - w_down: "w_down", - w_up: "w_up", - a_down: "a_down", - a_up: "a_up", - s_down: "s_down", - s_up: "s_up", - d_down: "d_down", - d_up: "d_up", - arrowup_down: "w_down", - arrowup_up: "w_up", - arrowleft_down: "a_down", - arrowleft_up: "a_up", - arrowdown_down: "s_down", - arrowdown_up: "s_up", - arrowright_down: "d_down", - arrowright_up: "d_up" - } - } - } -}; - -export { inGameActions, config }; diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js index a72231c2fba05b7e7d777367a5482808c4a3bde3..bc249694ad94a002fd141c005f98d483a1c4df93 100644 --- a/src/react-components/ui-root.js +++ b/src/react-components/ui-root.js @@ -172,7 +172,7 @@ class UIRoot extends Component { }; spawnPen = () => { - this.props.scene.emit("spawn_pen"); + this.props.scene.emit("penButtonPressed"); }; handleStartEntry = () => { diff --git a/src/scene-entry-manager.js b/src/scene-entry-manager.js index 70a1ee73415215f4eea6ea9b690d0b2dcb5e766d..284cc30f1ba8551971720de4b4318aaaecd2a3ba 100644 --- a/src/scene-entry-manager.js +++ b/src/scene-entry-manager.js @@ -1,6 +1,5 @@ import qsTruthy from "./utils/qs_truthy"; import screenfull from "screenfull"; -import { inGameActions } from "./input-mappings"; import nextTick from "./utils/next-tick"; const playerHeight = 1.6; @@ -56,8 +55,6 @@ export default class SceneEntryManager { document.body.addEventListener("touchend", requestFullscreen); } - AFRAME.registerInputActions(inGameActions, "default"); - if (isMobile || qsTruthy("mobile")) { this.playerRig.setAttribute("virtual-gamepad-controls", {}); } diff --git a/src/systems/userinput/bindings/daydream-user.js b/src/systems/userinput/bindings/daydream-user.js new file mode 100644 index 0000000000000000000000000000000000000000..c61a32b961ec7aba23f3184842203a899f4e9987 --- /dev/null +++ b/src/systems/userinput/bindings/daydream-user.js @@ -0,0 +1,265 @@ +import { paths } from "../paths"; +import { sets } from "../sets"; +import { xforms } from "./xforms"; + +// vars +const v = s => `/vars/daydream/${s}`; +const touchpad = v("touchpad"); +const touchpadPressed = v("touchpadPressed"); +const touchpadReleased = v("touchpadReleased"); +const dpadNorth = v("dpad/north"); +const dpadSouth = v("dpad/south"); +const dpadEast = v("dpad/east"); +const dpadWest = v("dpad/west"); +const dpadCenter = v("dpad/center"); +const vec2zero = "/vars/vec2zero"; +const brushSizeDelta = v("brushSizeDelta"); +const cursorModDelta = v("cursorModDelta"); +const dpadSouthDrop = v("dpad/southDrop"); +const dpadCenterDrop = v("dpad/centerDrop"); + +// roots +const dpadEastRoot = "daydreamDpadEast"; +const dpadWestRoot = "daydreamDpadWest"; +const dpadCenterRoot = "daydreamDpadCenter"; +const touchpadFallingRoot = "daydreamTouchpadFalling"; +const cursorModDeltaRoot = "daydreamCursorModDeltaRoot"; + +const grabBinding = [ + { + src: { value: dpadCenter, bool: touchpadPressed }, + dest: { value: paths.actions.cursor.grab }, + xform: xforms.copyIfTrue, + root: dpadCenterRoot, + priority: 200 + } +]; + +const dropOnCenterOrSouth = [ + { + src: { value: dpadCenter, bool: touchpadPressed }, + dest: { value: dpadCenterDrop }, + xform: xforms.copyIfTrue + }, + { + src: { value: dpadSouth, bool: touchpadPressed }, + dest: { value: dpadSouthDrop }, + xform: xforms.copyIfTrue + }, + { + src: [dpadCenterDrop, dpadSouthDrop], + dest: { value: paths.actions.cursor.drop }, + xform: xforms.any + } +]; + +export const daydreamUserBindings = { + [sets.global]: [ + { + src: { + x: paths.device.daydream.axis("touchpadX"), + y: paths.device.daydream.axis("touchpadY") + }, + dest: { value: touchpad }, + xform: xforms.compose_vec2 + }, + { + src: { + value: paths.device.daydream.button("touchpad").pressed + }, + dest: { value: touchpadPressed }, + xform: xforms.rising + }, + { + src: { + value: paths.device.daydream.button("touchpad").pressed + }, + dest: { value: touchpadReleased }, + xform: xforms.falling + }, + { + src: { + value: touchpad + }, + dest: { + north: dpadNorth, + south: dpadSouth, + east: dpadEast, + west: dpadWest, + center: dpadCenter + }, + xform: xforms.vec2dpad(0.5) + }, + { + src: { + value: dpadEast, + bool: touchpadPressed + }, + dest: { + value: paths.actions.snapRotateRight + }, + xform: xforms.copyIfTrue, + root: dpadEastRoot, + priority: 100 + }, + { + src: { + value: dpadWest, + bool: touchpadPressed + }, + dest: { + value: paths.actions.snapRotateLeft + }, + xform: xforms.copyIfTrue, + root: dpadWestRoot, + priority: 100 + }, + { + src: { + value: dpadCenter, + bool: touchpadPressed + }, + dest: { value: paths.actions.rightHand.startTeleport }, + xform: xforms.copyIfTrue, + root: dpadCenterRoot, + priority: 100 + }, + { + dest: { value: vec2zero }, + xform: xforms.always([0, -0.2]) + }, + { + src: { value: vec2zero }, + dest: { value: paths.actions.cursor.pose }, + xform: xforms.poseFromCameraProjection() + }, + { + src: { value: paths.device.daydream.pose }, + dest: { value: paths.actions.cursor.pose }, + xform: xforms.copy + } + ], + + [sets.cursorHoveringOnInteractable]: grabBinding, + [sets.cursorHoveringOnUI]: grabBinding, + + [sets.cursorHoldingInteractable]: [ + { + src: { value: paths.device.daydream.button("touchpad").pressed }, + dest: { value: paths.actions.cursor.drop }, + xform: xforms.falling, + root: touchpadFallingRoot, + priority: 100 + }, + { + src: { + value: paths.device.daydream.axis("touchpadY"), + touching: paths.device.daydream.button("touchpad").touched + }, + dest: { value: cursorModDelta }, + xform: xforms.touch_axis_scroll() + }, + { + src: { value: cursorModDelta }, + dest: { value: paths.actions.cursor.modDelta }, + xform: xforms.copy, + root: cursorModDeltaRoot, + priority: 100 + } + ], + + [sets.rightHandTeleporting]: [ + { + src: { value: paths.device.daydream.button("touchpad").pressed }, + dest: { value: paths.actions.rightHand.stopTeleport }, + xform: xforms.falling, + root: touchpadFallingRoot, + priority: 100 + } + ], + + [sets.cursorHoldingPen]: [ + { + src: { value: dpadNorth, bool: touchpadPressed }, + dest: { value: paths.actions.cursor.startDrawing }, + xform: xforms.copyIfTrue, + root: dpadCenterRoot, + priority: 300 + }, + { + src: { value: touchpadReleased }, + dest: { value: paths.actions.cursor.stopDrawing }, + xform: xforms.copy, + root: touchpadFallingRoot, + priority: 300 + }, + { + src: { + value: paths.device.daydream.axis("touchpadX"), + touching: paths.device.daydream.button("touchpad").touched + }, + dest: { value: brushSizeDelta }, + xform: xforms.touch_axis_scroll(-0.1) + }, + { + src: { + value: brushSizeDelta, + bool: paths.device.daydream.button("touchpad").pressed + }, + dest: { value: paths.actions.cursor.scalePenTip }, + xform: xforms.copyIfFalse + }, + { + src: { value: dpadEast, bool: touchpadPressed }, + dest: { + value: paths.actions.cursor.penPrevColor + }, + xform: xforms.copyIfTrue, + root: dpadEastRoot, + priority: 200 + }, + { + src: { value: dpadWest, bool: touchpadPressed }, + dest: { + value: paths.actions.cursor.penNextColor + }, + xform: xforms.copyIfTrue, + root: dpadWestRoot, + priority: 200 + }, + { + src: { + value: cursorModDelta, + bool: paths.device.daydream.button("touchpad").pressed + }, + dest: { value: paths.actions.cursor.modDelta }, + xform: xforms.copyIfFalse, + root: cursorModDeltaRoot, + priority: 200 + }, + ...dropOnCenterOrSouth + ], + + [sets.cursorHoldingCamera]: [ + // Don't drop on touchpad release + { + src: { + value: paths.device.daydream.button("touchpad").pressed + }, + xform: xforms.noop, + root: touchpadFallingRoot, + priority: 300 + }, + { + src: { + value: dpadNorth, + bool: touchpadPressed + }, + dest: { value: paths.actions.cursor.takeSnapshot }, + xform: xforms.copyIfTrue, + root: dpadCenterRoot, + priority: 300 + }, + ...dropOnCenterOrSouth + ] +}; diff --git a/src/systems/userinput/bindings/generic-gamepad.js b/src/systems/userinput/bindings/generic-gamepad.js new file mode 100644 index 0000000000000000000000000000000000000000..a62acaae658dbf852bd1d121b87cc67b79a26f78 --- /dev/null +++ b/src/systems/userinput/bindings/generic-gamepad.js @@ -0,0 +1 @@ +export const gamepadBindings = {}; diff --git a/src/systems/userinput/bindings/keyboard-debugging.js b/src/systems/userinput/bindings/keyboard-debugging.js new file mode 100644 index 0000000000000000000000000000000000000000..d1142ee335e348c2f8ba2db71e763cd616528b35 --- /dev/null +++ b/src/systems/userinput/bindings/keyboard-debugging.js @@ -0,0 +1,17 @@ +import { paths } from "../paths"; +import { sets } from "../sets"; +import { xforms } from "./xforms"; + +export const keyboardDebuggingBindings = { + [sets.global]: [ + { + src: { + value: paths.device.keyboard.key("l") + }, + dest: { + value: paths.actions.logDebugFrame + }, + xform: xforms.rising + } + ] +}; diff --git a/src/systems/userinput/bindings/keyboard-mouse-user.js b/src/systems/userinput/bindings/keyboard-mouse-user.js new file mode 100644 index 0000000000000000000000000000000000000000..cba87f13f4c53e19c27b3cfea696b8b36e5a2c73 --- /dev/null +++ b/src/systems/userinput/bindings/keyboard-mouse-user.js @@ -0,0 +1,249 @@ +import { paths } from "../paths"; +import { sets } from "../sets"; +import { xforms } from "./xforms"; + +const wasd_vec2 = "/var/mouse-and-keyboard/wasd_vec2"; +export const keyboardMouseUserBindings = { + [sets.global]: [ + { + src: { + value: paths.device.keyboard.key("b") + }, + dest: { + value: paths.actions.toggleScreenShare + }, + xform: xforms.rising + }, + { + src: { + w: paths.device.keyboard.key("w"), + a: paths.device.keyboard.key("a"), + s: paths.device.keyboard.key("s"), + d: paths.device.keyboard.key("d") + }, + dest: { vec2: wasd_vec2 }, + xform: xforms.wasd_to_vec2 + }, + { + src: { value: wasd_vec2 }, + dest: { value: paths.actions.characterAcceleration }, + xform: xforms.copy + }, + { + src: { value: paths.device.keyboard.key("shift") }, + dest: { value: paths.actions.boost }, + xform: xforms.copy + }, + { + src: { value: paths.device.keyboard.key("q") }, + dest: { value: paths.actions.snapRotateLeft }, + xform: xforms.rising, + root: "q", + priority: 100 + }, + { + src: { value: paths.device.keyboard.key("e") }, + dest: { value: paths.actions.snapRotateRight }, + xform: xforms.rising, + root: "e", + priority: 100 + }, + { + src: { value: paths.device.hud.penButton }, + dest: { value: paths.actions.spawnPen }, + xform: xforms.rising + }, + { + src: { value: paths.device.smartMouse.cursorPose }, + dest: { value: paths.actions.cursor.pose }, + xform: xforms.copy + }, + { + src: { value: paths.device.smartMouse.cameraDelta }, + dest: { x: "/var/smartMouseCamDeltaX", y: "/var/smartMouseCamDeltaY" }, + xform: xforms.split_vec2 + }, + { + src: { value: "/var/smartMouseCamDeltaX" }, + dest: { value: "/var/smartMouseCamDeltaXScaled" }, + xform: xforms.scale(-0.06) + }, + { + src: { value: "/var/smartMouseCamDeltaY" }, + dest: { value: "/var/smartMouseCamDeltaYScaled" }, + xform: xforms.scale(-0.1) + }, + { + src: { x: "/var/smartMouseCamDeltaXScaled", y: "/var/smartMouseCamDeltaYScaled" }, + dest: { value: paths.actions.cameraDelta }, + xform: xforms.compose_vec2 + }, + { + src: { + value: paths.device.keyboard.key("l") + }, + dest: { + value: paths.actions.logDebugFrame + }, + xform: xforms.rising + } + ], + + [sets.cursorHoldingPen]: [ + { + src: { + bool: paths.device.keyboard.key("shift"), + value: paths.device.keyboard.key("q") + }, + dest: { value: "/var/shift+q" }, + xform: xforms.copyIfTrue + }, + { + src: { value: "/var/shift+q" }, + dest: { value: paths.actions.cursor.penPrevColor }, + xform: xforms.rising + }, + { + src: { + bool: paths.device.keyboard.key("shift"), + value: paths.device.keyboard.key("e") + }, + dest: { value: "/var/shift+e" }, + xform: xforms.copyIfTrue + }, + { + src: { value: "/var/shift+e" }, + dest: { value: paths.actions.cursor.penNextColor }, + xform: xforms.rising + }, + { + src: { + bool: paths.device.keyboard.key("shift"), + value: paths.device.keyboard.key("q") + }, + dest: { value: "/var/notshift+q" }, + xform: xforms.copyIfFalse + }, + { + src: { value: "/var/notshift+q" }, + dest: { value: paths.actions.snapRotateLeft }, + xform: xforms.rising, + root: "q", + priority: 200 + }, + { + src: { + bool: paths.device.keyboard.key("shift"), + value: paths.device.keyboard.key("e") + }, + dest: { value: "/var/notshift+e" }, + xform: xforms.copyIfFalse + }, + { + src: { value: "/var/notshift+e" }, + dest: { value: paths.actions.snapRotateRight }, + xform: xforms.rising, + root: "e", + priority: 200 + }, + { + src: { value: paths.device.mouse.buttonLeft }, + dest: { value: paths.actions.cursor.startDrawing }, + xform: xforms.rising, + priority: 200 + }, + { + src: { value: paths.device.mouse.buttonLeft }, + dest: { value: paths.actions.cursor.stopDrawing }, + xform: xforms.falling, + priority: 200, + root: "lmb" + }, + { + src: { value: paths.device.mouse.buttonRight }, + dest: { value: paths.actions.cursor.drop }, + xform: xforms.falling + }, + { + src: { + bool: paths.device.keyboard.key("shift"), + value: paths.device.mouse.wheel + }, + dest: { value: "/var/cursorScalePenTipWheel" }, + xform: xforms.copyIfTrue, + priority: 200, + root: "wheel" + }, + { + src: { value: "/var/cursorScalePenTipWheel" }, + dest: { value: paths.actions.cursor.scalePenTip }, + xform: xforms.scale(0.12) + } + ], + + [sets.cursorHoldingCamera]: [ + { + src: { value: paths.device.mouse.buttonLeft }, + dest: { value: paths.actions.cursor.takeSnapshot }, + xform: xforms.rising + }, + { + src: { value: paths.device.mouse.buttonLeft }, + xform: xforms.noop, + priority: 200, + root: "lmb" + }, + { + src: { value: paths.device.mouse.buttonRight }, + dest: { value: paths.actions.cursor.drop }, + xform: xforms.falling + } + ], + + [sets.cursorHoldingInteractable]: [ + { + src: { + value: paths.device.mouse.wheel + }, + dest: { + value: paths.actions.cursor.modDelta + }, + xform: xforms.copy, + root: "wheel", + priority: 100 + }, + { + src: { + bool: paths.device.keyboard.key("shift"), + value: paths.device.mouse.wheel + }, + dest: { value: paths.actions.cursor.modDelta }, + xform: xforms.copyIfFalse + }, + { + src: { + bool: paths.device.keyboard.key("shift"), + value: paths.device.mouse.wheel + }, + dest: { value: paths.actions.cursor.scaleGrabbedGrabbable }, + xform: xforms.copyIfTrue, + priority: 150, + root: "wheel" + }, + { + src: { value: paths.device.mouse.buttonLeft }, + dest: { value: paths.actions.cursor.drop }, + xform: xforms.falling, + priority: 100, + root: "lmb" + } + ], + + [sets.cursorHoveringOnInteractable]: [ + { + src: { value: paths.device.mouse.buttonLeft }, + dest: { value: paths.actions.cursor.grab }, + xform: xforms.rising + } + ] +}; diff --git a/src/systems/userinput/bindings/oculus-go-user.js b/src/systems/userinput/bindings/oculus-go-user.js new file mode 100644 index 0000000000000000000000000000000000000000..f18e670776e17d606f9a1dd941b3fddeeb39a140 --- /dev/null +++ b/src/systems/userinput/bindings/oculus-go-user.js @@ -0,0 +1,231 @@ +import { paths } from "../paths"; +import { sets } from "../sets"; +import { xforms } from "./xforms"; + +const touchpad = "/vars/oculusgo/touchpad"; +const touchpadPressed = "/vars/oculusgo/touchpadPressed"; +const dpadNorth = "/vars/oculusgo/dpad/north"; +const dpadSouth = "/vars/oculusgo/dpad/south"; +const dpadEast = "/vars/oculusgo/dpad/east"; +const dpadWest = "/vars/oculusgo/dpad/west"; +const dpadCenter = "/vars/oculusgo/dpad/center"; +const vec2zero = "/vars/vec2zero"; + +const triggerRisingRoot = "oculusGoTriggerRising"; +const triggerFallingRoot = "oculusGoTriggerFalling"; +const dpadEastRoot = "oculusGoDpadEast"; +const dpadWestRoot = "oculusGoDpadWest"; + +const grabBinding = { + src: { + value: paths.device.oculusgo.button("trigger").pressed + }, + dest: { value: paths.actions.cursor.grab }, + xform: xforms.rising, + root: triggerRisingRoot, + priority: 200 +}; + +export const oculusGoUserBindings = { + [sets.global]: [ + { + src: { + x: paths.device.oculusgo.axis("touchpadX"), + y: paths.device.oculusgo.axis("touchpadY") + }, + dest: { value: touchpad }, + xform: xforms.compose_vec2 + }, + { + src: { + value: paths.device.oculusgo.button("touchpad").pressed + }, + dest: { value: touchpadPressed }, + xform: xforms.rising + }, + { + src: { + value: touchpad + }, + dest: { + north: dpadNorth, + south: dpadSouth, + east: dpadEast, + west: dpadWest, + center: dpadCenter + }, + xform: xforms.vec2dpad(0.8) + }, + { + src: { + value: dpadEast, + bool: touchpadPressed + }, + dest: { + value: paths.actions.snapRotateRight + }, + xform: xforms.copyIfTrue, + root: dpadEastRoot, + priority: 100 + }, + { + src: { + value: dpadWest, + bool: touchpadPressed + }, + dest: { + value: paths.actions.snapRotateLeft + }, + xform: xforms.copyIfTrue, + root: dpadWestRoot, + priority: 100 + }, + { + src: { + value: paths.device.oculusgo.button("trigger").pressed + }, + dest: { value: paths.actions.rightHand.startTeleport }, + xform: xforms.rising, + root: triggerRisingRoot, + priority: 100 + }, + { + dest: { value: vec2zero }, + xform: xforms.always([0, -0.2]) + }, + { + src: { value: vec2zero }, + dest: { value: paths.actions.cursor.pose }, + xform: xforms.poseFromCameraProjection() + }, + { + src: { value: paths.device.oculusgo.pose }, + dest: { value: paths.actions.cursor.pose }, + xform: xforms.copy + } + ], + + [sets.cursorHoveringOnInteractable]: [grabBinding], + [sets.cursorHoveringOnUI]: [grabBinding], + + [sets.cursorHoldingInteractable]: [ + { + src: { + value: paths.device.oculusgo.button("trigger").pressed + }, + dest: { value: paths.actions.cursor.drop }, + xform: xforms.falling, + root: triggerFallingRoot, + priority: 200 + }, + { + src: { + value: paths.device.oculusgo.axis("touchpadY"), + touching: paths.device.oculusgo.button("touchpad").touched + }, + dest: { value: paths.actions.cursor.modDelta }, + xform: xforms.touch_axis_scroll() + } + ], + + [sets.rightHandTeleporting]: [ + { + src: { + value: paths.device.oculusgo.button("trigger").pressed + }, + dest: { value: paths.actions.rightHand.stopTeleport }, + xform: xforms.falling, + root: triggerFallingRoot, + priority: 100 + } + ], + + [sets.cursorHoldingPen]: [ + { + src: { + value: paths.device.oculusgo.button("trigger").pressed + }, + dest: { value: paths.actions.cursor.startDrawing }, + xform: xforms.rising, + root: triggerRisingRoot, + priority: 300 + }, + { + src: { + value: paths.device.oculusgo.button("trigger").pressed + }, + dest: { value: paths.actions.cursor.stopDrawing }, + xform: xforms.falling, + root: triggerFallingRoot, + priority: 300 + }, + { + src: { + value: paths.device.oculusgo.axis("touchpadX"), + touching: paths.device.oculusgo.button("touchpad").touched + }, + dest: { value: paths.actions.cursor.scalePenTip }, + xform: xforms.touch_axis_scroll(-0.1) + }, + { + src: { + value: dpadCenter, + bool: touchpadPressed + }, + dest: { value: paths.actions.cursor.drop }, + xform: xforms.copyIfTrue + }, + { + src: { + value: dpadEast, + bool: touchpadPressed + }, + dest: { + value: paths.actions.cursor.penPrevColor + }, + xform: xforms.copyIfTrue, + root: dpadEastRoot, + priority: 200 + }, + { + src: { + value: dpadWest, + bool: touchpadPressed + }, + dest: { + value: paths.actions.cursor.penNextColor + }, + xform: xforms.copyIfTrue, + root: dpadWestRoot, + priority: 200 + } + ], + + [sets.cursorHoldingCamera]: [ + { + src: { + value: paths.device.oculusgo.button("trigger").pressed + }, + dest: { value: paths.actions.cursor.takeSnapshot }, + xform: xforms.rising, + root: triggerRisingRoot, + priority: 300 + }, + { + src: { + value: paths.device.oculusgo.button("trigger").pressed + }, + xform: xforms.noop, + root: triggerFallingRoot, + priority: 300 + }, + { + src: { + value: dpadCenter, + bool: touchpadPressed + }, + dest: { value: paths.actions.cursor.drop }, + xform: xforms.copyIfTrue + } + ] +}; diff --git a/src/systems/userinput/bindings/oculus-touch-user.js b/src/systems/userinput/bindings/oculus-touch-user.js new file mode 100644 index 0000000000000000000000000000000000000000..9f475b833472577d1a2447c3ab74a39072ff1797 --- /dev/null +++ b/src/systems/userinput/bindings/oculus-touch-user.js @@ -0,0 +1,596 @@ +import { paths } from "../paths"; +import { sets } from "../sets"; +import { xforms } from "./xforms"; + +const name = "/touch/var/"; + +const leftButton = paths.device.leftOculusTouch.button; +const leftAxis = paths.device.leftOculusTouch.axis; +const leftPose = paths.device.leftOculusTouch.pose; +const rightButton = paths.device.rightOculusTouch.button; +const rightAxis = paths.device.rightOculusTouch.axis; +const rightPose = paths.device.rightOculusTouch.pose; + +const scaledLeftJoyX = `${name}left/scaledJoyX`; +const scaledLeftJoyY = `${name}left/scaledJoyY`; +const rightGripFalling = "${name}right/GripFalling"; +const rightTriggerFalling = `${name}right/TriggerFalling`; +const cursorDrop2 = `${name}right/cursorDrop2`; +const cursorDrop1 = `${name}right/cursorDrop1`; +const rightHandDrop2 = `${name}right/rightHandDrop2`; +const rightHandDrop1 = `${name}right/rightHandDrop1`; +const rightGripRising = `${name}right/GripRising`; +const rightTriggerRising = `${name}right/TriggerRising`; +const rightGripRisingGrab = `${name}right/grip/RisingGrab`; +const rightTriggerRisingGrab = `${name}right/trigger/RisingGrab`; +const leftGripRisingGrab = `${name}left/grip/RisingGrab`; +const leftTriggerRisingGrab = `${name}left/trigger/RisingGrab`; +const leftGripFalling = `${name}left/GripFalling`; +const leftGripRising = `${name}left/GripRising`; +const leftTriggerRising = `${name}left/TriggerRising`; +const leftTriggerFalling = `${name}left/TriggerFalling`; +const rightDpadNorth = `${name}rightDpad/north`; +const rightDpadSouth = `${name}rightDpad/south`; +const rightDpadEast = `${name}rightDpad/east`; +const rightDpadWest = `${name}rightDpad/west`; +const rightDpadCenter = `${name}rightDpad/center`; +const rightJoy = `${name}right/joy`; +const rightJoyY = `${name}right/joyY`; +const rightJoyYCursorMod = `${name}right/joyYCursorMod`; +const leftDpadNorth = `${name}leftDpad/north`; +const leftDpadSouth = `${name}leftDpad/south`; +const leftDpadEast = `${name}leftDpad/east`; +const leftDpadWest = `${name}leftDpad/west`; +const leftDpadCenter = `${name}leftDpad/center`; +const leftJoy = `${name}left/joy`; +const leftJoyY = `${name}left/joyY`; +const leftJoyYCursorMod = `${name}left/joyYCursorMod`; +const oculusTouchCharacterAcceleration = `${name}characterAcceleration`; +const keyboardCharacterAcceleration = "/var/keyboard/characterAcceleration"; +const keyboardBoost = "/var/keyboard-oculus/boost"; +const rightBoost = "/var/right-oculus/boost"; +const leftBoost = "/var/left-oculus/boost"; +const rightTouchSnapRight = `${name}/right/snap-right`; +const rightTouchSnapLeft = `${name}/right/snap-left`; +const keyboardSnapRight = `${name}/keyboard/snap-right`; +const keyboardSnapLeft = `${name}/keyboard/snap-left`; + +export const oculusTouchUserBindings = { + [sets.global]: [ + { + src: { + value: paths.device.keyboard.key("b") + }, + dest: { + value: paths.actions.toggleScreenShare + }, + xform: xforms.rising + }, + { + src: { + x: leftAxis("joyX"), + y: leftAxis("joyY") + }, + dest: { + value: leftJoy + }, + xform: xforms.compose_vec2 + }, + { + src: { + value: leftJoy + }, + dest: { + north: leftDpadNorth, + south: leftDpadSouth, + east: leftDpadEast, + west: leftDpadWest, + center: leftDpadCenter + }, + xform: xforms.vec2dpad(0.2, false, true) + }, + { + src: { + x: rightAxis("joyX"), + y: rightAxis("joyY") + }, + dest: { + value: rightJoy + }, + xform: xforms.compose_vec2 + }, + { + src: { + value: rightJoy + }, + dest: { + north: rightDpadNorth, + south: rightDpadSouth, + east: rightDpadEast, + west: rightDpadWest, + center: rightDpadCenter + }, + xform: xforms.vec2dpad(0.2, false, true) + }, + { + src: { + value: rightDpadEast + }, + dest: { + value: rightTouchSnapRight + }, + xform: xforms.rising, + root: rightDpadEast, + priority: 100 + }, + { + src: { value: paths.device.keyboard.key("e") }, + dest: { value: keyboardSnapRight }, + xform: xforms.rising + }, + { + src: [rightTouchSnapRight, keyboardSnapRight], + dest: { value: paths.actions.snapRotateRight }, + xform: xforms.any + }, + { + src: { + value: rightDpadWest + }, + dest: { + value: rightTouchSnapLeft + }, + xform: xforms.rising, + root: rightDpadWest, + priority: 100 + }, + { + src: { value: paths.device.keyboard.key("q") }, + dest: { value: keyboardSnapLeft }, + xform: xforms.rising + }, + { + src: [rightTouchSnapLeft, keyboardSnapLeft], + dest: { value: paths.actions.snapRotateLeft }, + xform: xforms.any + }, + { + src: { + value: leftAxis("joyX") + }, + dest: { + value: scaledLeftJoyX + }, + xform: xforms.scale(1.5) // horizontal character speed modifier + }, + { + src: { + value: leftAxis("joyY") + }, + dest: { value: scaledLeftJoyY }, + xform: xforms.scale(-1.5) // vertical character speed modifier + }, + { + src: { + x: scaledLeftJoyX, + y: scaledLeftJoyY + }, + dest: { value: oculusTouchCharacterAcceleration }, + xform: xforms.compose_vec2 + }, + { + src: { + w: paths.device.keyboard.key("w"), + a: paths.device.keyboard.key("a"), + s: paths.device.keyboard.key("s"), + d: paths.device.keyboard.key("d") + }, + dest: { vec2: keyboardCharacterAcceleration }, + xform: xforms.wasd_to_vec2 + }, + { + src: { + first: oculusTouchCharacterAcceleration, + second: keyboardCharacterAcceleration + }, + dest: { + value: paths.actions.characterAcceleration + }, + xform: xforms.add_vec2 + }, + { + src: { value: paths.device.keyboard.key("shift") }, + dest: { value: keyboardBoost }, + xform: xforms.copy + }, + { + src: { + value: leftButton("x").pressed + }, + dest: { + value: leftBoost + }, + xform: xforms.copy + }, + { + src: { + value: rightButton("a").pressed + }, + dest: { + value: rightBoost + }, + xform: xforms.copy + }, + { + src: [keyboardBoost, leftBoost, rightBoost], + dest: { value: paths.actions.boost }, + xform: xforms.any + }, + { + src: { value: rightPose }, + dest: { value: paths.actions.cursor.pose }, + xform: xforms.copy + }, + { + src: { value: rightPose }, + dest: { value: paths.actions.rightHand.pose }, + xform: xforms.copy + }, + { + src: { value: leftPose }, + dest: { value: paths.actions.leftHand.pose }, + xform: xforms.copy + }, + { + src: { value: rightButton("trigger").pressed }, + dest: { value: paths.actions.rightHand.stopTeleport }, + xform: xforms.falling, + root: rightTriggerFalling, + priority: 100 + }, + { + src: { value: leftButton("trigger").pressed }, + dest: { value: paths.actions.leftHand.stopTeleport }, + xform: xforms.falling, + root: leftTriggerFalling, + priority: 100 + } + ], + + [sets.leftHandHoveringOnNothing]: [ + { + src: { value: leftButton("trigger").pressed }, + dest: { value: paths.actions.leftHand.startTeleport }, + xform: xforms.rising, + root: leftTriggerRising, + priority: 100 + } + ], + + [sets.cursorHoveringOnUI]: [ + { + src: { value: rightButton("trigger").pressed }, + dest: { value: paths.actions.cursor.grab }, + xform: xforms.rising, + root: rightTriggerRising, + priority: 100 + } + ], + + [sets.cursorHoveringOnNothing]: [ + { + src: { value: rightButton("trigger").pressed }, + dest: { value: paths.actions.rightHand.startTeleport }, + xform: xforms.rising, + root: rightTriggerRising, + priority: 100 + } + ], + + [sets.leftHandHoveringOnInteractable]: [ + { + src: { value: leftButton("grip").pressed }, + dest: { value: leftGripRisingGrab }, + xform: xforms.rising, + root: leftGripRising, + priority: 200 + }, + { + src: { value: leftButton("trigger").pressed }, + dest: { value: leftTriggerRisingGrab }, + xform: xforms.rising, + root: leftTriggerRising, + priority: 200 + }, + { + src: [leftGripRisingGrab, leftTriggerRisingGrab], + dest: { value: paths.actions.cursor.grab }, + xform: xforms.any + } + ], + + [sets.leftHandHoldingInteractable]: [ + { + src: { value: leftButton("grip").pressed }, + dest: { value: paths.actions.leftHand.drop }, + xform: xforms.falling, + root: leftGripFalling, + priority: 200 + } + ], + + [sets.leftHandHoveringOnPen]: [], + [sets.leftHandHoldingPen]: [ + { + src: { value: leftButton("trigger").pressed }, + dest: { value: paths.actions.leftHand.startDrawing }, + xform: xforms.rising + }, + { + src: { value: leftButton("trigger").pressed }, + dest: { value: paths.actions.leftHand.stopDrawing }, + xform: xforms.falling + }, + { + src: { + value: leftDpadEast + }, + dest: { + value: paths.actions.leftHand.penNextColor + }, + xform: xforms.rising, + root: leftDpadEast, + priority: 200 + }, + { + src: { + value: leftDpadWest + }, + dest: { + value: paths.actions.leftHand.penPrevColor + }, + xform: xforms.rising, + root: leftDpadWest, + priority: 200 + }, + { + src: { + bool: leftButton("grip").pressed, + value: leftAxis("joyY") + }, + dest: { value: leftJoyY }, + xform: xforms.copyIfTrue + }, + { + src: { value: leftJoyY }, + dest: { value: paths.actions.leftHand.scalePenTip }, + xform: xforms.scale(-0.01) + }, + { + src: { + boo: leftButton("grip").pressed, + value: leftAxis("joyY") + }, + dest: { value: leftJoyYCursorMod }, + xform: xforms.copyIfFalse, + root: leftJoyY, + priority: 100 + } + ], + + [sets.cursorHoveringOnInteractable]: [ + { + src: { value: rightButton("grip").pressed }, + dest: { value: rightGripRisingGrab }, + xform: xforms.rising, + root: rightGripRising, + priority: 200 + }, + { + src: { value: rightButton("trigger").pressed }, + dest: { value: rightTriggerRisingGrab }, + xform: xforms.rising, + root: rightTriggerRising, + priority: 200 + }, + { + src: [rightGripRisingGrab, rightTriggerRisingGrab], + dest: { value: paths.actions.cursor.grab }, + xform: xforms.any + } + ], + + [sets.cursorHoldingInteractable]: [ + { + src: { value: rightAxis("joyY") }, + dest: { value: paths.actions.cursor.modDelta }, + xform: xforms.scale(0.1) + }, + { + src: { value: rightButton("grip").pressed }, + dest: { value: cursorDrop1 }, + xform: xforms.falling, + root: rightGripFalling, + priority: 200 + }, + { + src: { value: rightButton("trigger").pressed }, + dest: { + value: cursorDrop2 + }, + xform: xforms.falling, + root: rightTriggerFalling, + priority: 200 + }, + { + src: [cursorDrop1, cursorDrop2], + dest: { value: paths.actions.cursor.drop }, + xform: xforms.any + } + ], + + [sets.cursorHoveringOnPen]: [], + + [sets.cursorHoldingPen]: [ + { + src: { value: rightButton("trigger").pressed }, + dest: { value: paths.actions.cursor.startDrawing }, + xform: xforms.rising + }, + { + src: { value: rightButton("trigger").pressed }, + dest: { value: paths.actions.cursor.stopDrawing }, + xform: xforms.falling, + root: rightTriggerFalling, + priority: 300 + } + ], + + [sets.rightHandHoveringOnInteractable]: [ + { + src: { value: rightButton("grip").pressed }, + dest: { value: rightGripRisingGrab }, + xform: xforms.rising, + root: rightGripRising, + priority: 200 + }, + { + src: { value: rightButton("trigger").pressed }, + dest: { value: rightTriggerRisingGrab }, + xform: xforms.rising, + root: rightTriggerRising, + priority: 200 + }, + { + src: [rightGripRisingGrab, rightTriggerRisingGrab], + dest: { value: paths.actions.cursor.grab }, + xform: xforms.any + } + ], + + [sets.rightHandHoldingInteractable]: [ + { + src: { value: rightButton("grip").pressed }, + dest: { value: rightHandDrop1 }, + xform: xforms.falling, + root: rightGripFalling, + priority: 200 + }, + { + src: { value: rightButton("trigger").pressed }, + dest: { + value: rightHandDrop2 + }, + xform: xforms.falling, + root: rightTriggerFalling, + priority: 200 + }, + { + src: [rightHandDrop1, rightHandDrop2], + dest: { value: paths.actions.rightHand.drop }, + xform: xforms.any + } + ], + [sets.rightHandHoveringOnPen]: [], + [sets.rightHandHoldingPen]: [ + { + src: { value: rightButton("trigger").pressed }, + dest: { value: paths.actions.rightHand.startDrawing }, + xform: xforms.rising + }, + { + src: { value: rightButton("trigger").pressed }, + dest: { value: paths.actions.rightHand.stopDrawing }, + xform: xforms.falling, + root: rightTriggerFalling, + priority: 300 + }, + { + src: { + value: rightDpadEast + }, + dest: { + value: paths.actions.rightHand.penNextColor + }, + xform: xforms.rising, + root: rightDpadEast, + priority: 200 + }, + { + src: { + value: rightDpadWest + }, + dest: { + value: paths.actions.rightHand.penPrevColor + }, + xform: xforms.rising, + root: rightDpadWest, + priority: 200 + }, + { + src: { + bool: rightButton("grip").pressed, + value: rightAxis("joyY") + }, + dest: { value: rightJoyY }, + xform: xforms.copyIfTrue + }, + { + src: { value: rightJoyY }, + dest: { value: paths.actions.rightHand.scalePenTip }, + xform: xforms.scale(-0.01) + }, + { + src: { + boo: rightButton("grip").pressed, + value: rightAxis("joyY") + }, + dest: { value: rightJoyYCursorMod }, + xform: xforms.copyIfFalse, + root: rightJoyY, + priority: 100 + } + ], + + [sets.cursorHoveringOnCamera]: [], + [sets.rightHandHoveringOnCamera]: [], + [sets.leftHandHoveringOnCamera]: [], + + [sets.rightHandHoldingCamera]: [ + { + src: { value: rightButton("trigger").pressed }, + dest: { value: paths.actions.rightHand.takeSnapshot }, + xform: xforms.rising + }, + { + src: { value: rightButton("trigger").pressed }, + dest: { value: paths.noop }, + xform: xforms.falling, + root: rightTriggerFalling, + priority: 400 + } + ], + [sets.leftHandHoldingCamera]: [ + { + src: { value: leftButton("trigger").pressed }, + dest: { value: paths.actions.leftHand.takeSnapshot }, + xform: xforms.rising + } + ], + [sets.cursorHoldingCamera]: [ + { + src: { value: rightButton("trigger").pressed }, + dest: { value: paths.actions.cursor.takeSnapshot }, + xform: xforms.rising + }, + { + src: { value: rightButton("trigger").pressed }, + dest: { value: paths.noop }, + xform: xforms.falling, + root: rightTriggerFalling, + priority: 400 + } + ], + + [sets.rightHandHoveringOnNothing]: [] +}; diff --git a/src/systems/userinput/bindings/touchscreen-user.js b/src/systems/userinput/bindings/touchscreen-user.js new file mode 100644 index 0000000000000000000000000000000000000000..770fe3f38d9547f401c18aa4f3390b1bcfbb2a5c --- /dev/null +++ b/src/systems/userinput/bindings/touchscreen-user.js @@ -0,0 +1,84 @@ +import { paths } from "../paths"; +import { sets } from "../sets"; +import { xforms } from "./xforms"; + +export const touchscreenUserBindings = { + [sets.global]: [ + { + src: { value: paths.device.touchscreen.cursorPose }, + dest: { value: paths.actions.cursor.pose }, + xform: xforms.copy + }, + { + src: { value: paths.device.touchscreen.cameraDelta }, + dest: { x: "/var/touchscreenCamDeltaX", y: "/var/touchscreenCamDeltaY" }, + xform: xforms.split_vec2 + }, + { + src: { value: "/var/touchscreenCamDeltaX" }, + dest: { value: "/var/touchscreenCamDeltaXScaled" }, + xform: xforms.scale(0.18) + }, + { + src: { value: "/var/touchscreenCamDeltaY" }, + dest: { value: "/var/touchscreenCamDeltaYScaled" }, + xform: xforms.scale(0.35) + }, + { + src: { x: "/var/touchscreenCamDeltaXScaled", y: "/var/touchscreenCamDeltaYScaled" }, + dest: { value: paths.actions.cameraDelta }, + xform: xforms.compose_vec2 + }, + { + src: { value: paths.device.touchscreen.isTouchingGrabbable }, + dest: { value: paths.actions.cursor.grab }, + xform: xforms.copy, + root: "touchscreen.isTouchingGrabbable", + priority: 100 + }, + { + src: { value: paths.device.hud.penButton }, + dest: { value: paths.actions.spawnPen }, + xform: xforms.rising, + root: "hud.penButton", + priority: 100 + } + ], + [sets.cursorHoldingInteractable]: [ + { + src: { value: paths.device.touchscreen.isTouchingGrabbable }, + dest: { value: paths.actions.cursor.drop }, + xform: xforms.falling, + root: "touchscreen.cursorDrop", + priority: 100 + } + ], + + [sets.cursorHoveringOnPen]: [], + [sets.cursorHoldingPen]: [ + { + src: { value: paths.device.touchscreen.isTouchingGrabbable }, + dest: { value: paths.noop }, + xform: xforms.noop, + root: "touchscreen.cursorDrop", + priority: 200 + }, + { + src: { value: paths.device.touchscreen.isTouchingGrabbable }, + dest: { value: paths.actions.cursor.startDrawing }, + xform: xforms.risingWithFrameDelay(5) + }, + { + src: { value: paths.device.touchscreen.isTouchingGrabbable }, + dest: { value: paths.actions.cursor.stopDrawing }, + xform: xforms.falling + }, + { + src: { value: paths.device.hud.penButton }, + dest: { value: paths.actions.cursor.drop }, + xform: xforms.rising, + root: "hud.penButton", + priority: 200 + } + ] +}; diff --git a/src/systems/userinput/bindings/vive-user.js b/src/systems/userinput/bindings/vive-user.js new file mode 100644 index 0000000000000000000000000000000000000000..d276867e3c22d6ab4d3d5a9d303ec7bd815c6ae5 --- /dev/null +++ b/src/systems/userinput/bindings/vive-user.js @@ -0,0 +1,737 @@ +import { paths } from "../paths"; +import { sets } from "../sets"; +import { xforms } from "./xforms"; + +const v = name => { + return `/vive-user/vive-var/${name}`; +}; + +const lButton = paths.device.vive.left.button; +const lAxis = paths.device.vive.left.axis; +const lPose = paths.device.vive.left.pose; +const lJoy = v("left/joy"); +const lJoyScaled = v("left/joy/scaled"); +const lJoyXScaled = v("left/joyX/scaled"); +const lJoyYScaled = v("left/joyY/scaled"); +const lDpadNorth = v("left/dpad/north"); +const lDpadSouth = v("left/dpad/south"); +const lDpadEast = v("left/dpad/east"); +const lDpadWest = v("left/dpad/west"); +const lDpadCenter = v("left/dpad/center"); +const lTriggerFalling = v("left/trigger/falling"); +const lTriggerFallingStopDrawing = v("left/trigger/falling/stopDrawing"); +const lGripFallingStopDrawing = v("left/grip/falling/stopDrawing"); +const lTriggerRising = v("left/trigger/rising"); +const lTriggerRisingGrab = v("right/trigger/rising/grab"); +const lGripRisingGrab = v("right/grab/rising/grab"); +const lTouchpadRising = v("left/touchpad/rising"); +const lCharacterAcceleration = v("left/characterAcceleration"); +const lGripFalling = v("left/grip/falling"); +const lGripRising = v("left/grip/rising"); +const leftBoost = v("left/boost"); +const lTriggerStartTeleport = v("left/trigger/startTeleport"); +const lDpadCenterStartTeleport = v("left/dpadCenter/startTeleport"); +const lTriggerStopTeleport = v("left/trigger/stopTeleport"); +const lTouchpadStopTeleport = v("left/touchpad/stopTeleport"); + +const rButton = paths.device.vive.right.button; +const rAxis = paths.device.vive.right.axis; +const rPose = paths.device.vive.right.pose; +const rJoy = v("right/joy"); +const rDpadNorth = v("right/dpad/north"); +const rDpadSouth = v("right/dpad/south"); +const rDpadEast = v("right/dpad/east"); +const rDpadWest = v("right/dpad/west"); +const rDpadCenter = v("right/dpad/center"); +const rTriggerFalling = v("right/trigger/falling"); +const rTriggerRising = v("right/trigger/rising"); +const rTouchpadRising = v("right/touchpad/rising"); +const rightBoost = v("right/boost"); +const rGripRising = v("right/grip/rising"); +const rTriggerRisingGrab = v("right/trigger/rising/grab"); +const rGripRisingGrab = v("right/grab/rising/grab"); +const rGripFalling = v("right/grip/rising"); +const cursorDrop1 = v("right/cursorDrop1"); +const cursorDrop2 = v("right/cursorDrop2"); +const rHandDrop1 = v("right/drop1"); +const rHandDrop2 = v("right/drop2"); +const rTriggerStartTeleport = v("right/trigger/startTeleport"); +const rDpadCenterStartTeleport = v("right/dpadCenter/startTeleport"); +const rTriggerStopTeleport = v("right/trigger/stopTeleport"); +const rTouchpadStopTeleport = v("right/touchpad/stopTeleport"); + +const rSnapRight = v("right/snap-right"); +const rSnapLeft = v("right/snap-left"); + +const k = name => { + return `/vive-user/keyboard-var/${name}`; +}; +const keyboardSnapRight = k("snap-right"); +const keyboardSnapLeft = k("snap-left"); +const keyboardCharacterAcceleration = k("characterAcceleration"); +const keyboardBoost = k("boost"); + +const teleportLeft = [ + { + src: { value: lButton("trigger").pressed }, + dest: { value: lTriggerStartTeleport }, + xform: xforms.rising, + root: lTriggerRising, + priority: 100 + }, + { + src: { + bool: lTouchpadRising, + value: lDpadCenter + }, + dest: { value: lDpadCenterStartTeleport }, + xform: xforms.copyIfTrue + }, + { + src: [lTriggerStartTeleport, lDpadCenterStartTeleport], + dest: { value: paths.actions.leftHand.startTeleport }, + xform: xforms.any + } +]; +const teleportRight = [ + { + src: { value: rButton("trigger").pressed }, + dest: { value: rTriggerStartTeleport }, + xform: xforms.rising, + root: rTriggerRising, + priority: 100 + }, + { + src: { + bool: rTouchpadRising, + value: rDpadCenter + }, + dest: { value: rDpadCenterStartTeleport }, + xform: xforms.copyIfTrue + }, + { + src: [rTriggerStartTeleport, rDpadCenterStartTeleport], + dest: { value: paths.actions.rightHand.startTeleport }, + xform: xforms.any + } +]; + +export const viveUserBindings = { + [sets.global]: [ + { + src: { + value: paths.device.keyboard.key("b") + }, + dest: { + value: paths.actions.toggleScreenShare + }, + xform: xforms.rising + }, + { + src: { + x: lAxis("joyX"), + y: lAxis("joyY") + }, + dest: { + value: lJoy + }, + xform: xforms.compose_vec2 + }, + { + src: { + value: lJoy + }, + dest: { + north: lDpadNorth, + south: lDpadSouth, + east: lDpadEast, + west: lDpadWest, + center: lDpadCenter + }, + xform: xforms.vec2dpad(0.35) + }, + { + src: { + value: lButton("touchpad").pressed + }, + dest: { + value: lTouchpadRising + }, + xform: xforms.rising + }, + { + src: { + x: rAxis("joyX"), + y: rAxis("joyY") + }, + dest: { + value: rJoy + }, + xform: xforms.compose_vec2 + }, + { + src: { + value: rJoy + }, + dest: { + north: rDpadNorth, + south: rDpadSouth, + east: rDpadEast, + west: rDpadWest, + center: rDpadCenter + }, + xform: xforms.vec2dpad(0.35) + }, + { + src: { + value: rButton("touchpad").pressed + }, + dest: { + value: rTouchpadRising + }, + xform: xforms.rising + }, + { + src: { + bool: rTouchpadRising, + value: rDpadEast + }, + dest: { + value: rSnapRight + }, + xform: xforms.copyIfTrue, + root: rDpadEast, + priority: 100 + }, + { + src: { value: paths.device.keyboard.key("e") }, + dest: { value: keyboardSnapRight }, + xform: xforms.rising + }, + { + src: [rSnapRight, keyboardSnapRight], + dest: { value: paths.actions.snapRotateRight }, + xform: xforms.any + }, + { + src: { + bool: rTouchpadRising, + value: rDpadWest + }, + dest: { + value: rSnapLeft + }, + xform: xforms.copyIfTrue, + root: rDpadWest, + priority: 100 + }, + { + src: { value: paths.device.keyboard.key("q") }, + dest: { value: keyboardSnapLeft }, + xform: xforms.rising + }, + { + src: [rSnapLeft, keyboardSnapLeft], + dest: { value: paths.actions.snapRotateLeft }, + xform: xforms.any + }, + + { + src: { + value: lAxis("joyX") + }, + dest: { + value: lJoyXScaled + }, + xform: xforms.scale(1.5) // horizontal character speed modifier + }, + { + src: { + value: lAxis("joyY") + }, + dest: { value: lJoyYScaled }, + xform: xforms.scale(1.5) // vertical character speed modifier + }, + { + src: { + x: lJoyXScaled, + y: lJoyYScaled + }, + dest: { value: lJoyScaled }, + xform: xforms.compose_vec2 + }, + { + src: { + bool: lButton("touchpad").pressed, + value: lJoyScaled + }, + dest: { value: lCharacterAcceleration }, + xform: xforms.copyIfTrue + }, + { + src: { + w: paths.device.keyboard.key("w"), + a: paths.device.keyboard.key("a"), + s: paths.device.keyboard.key("s"), + d: paths.device.keyboard.key("d") + }, + dest: { vec2: keyboardCharacterAcceleration }, + xform: xforms.wasd_to_vec2 + }, + { + src: { + first: lCharacterAcceleration, + second: keyboardCharacterAcceleration + }, + dest: { + value: paths.actions.characterAcceleration + }, + xform: xforms.add_vec2 + }, + { + src: { value: paths.device.keyboard.key("shift") }, + dest: { value: keyboardBoost }, + xform: xforms.copy + }, + { + src: { + value: lButton("top").pressed + }, + dest: { + value: leftBoost + }, + xform: xforms.copy + }, + { + src: { + value: rButton("top").pressed + }, + dest: { + value: rightBoost + }, + xform: xforms.copy + }, + { + src: [keyboardBoost, leftBoost, rightBoost], + dest: { value: paths.actions.boost }, + xform: xforms.any + }, + { + src: { value: rPose }, + dest: { value: paths.actions.cursor.pose }, + xform: xforms.copy + }, + { + src: { value: rPose }, + dest: { value: paths.actions.rightHand.pose }, + xform: xforms.copy + }, + { + src: { value: lPose }, + dest: { value: paths.actions.leftHand.pose }, + xform: xforms.copy + } + ], + [sets.rightHandTeleporting]: [ + { + src: { value: rButton("trigger").pressed }, + dest: { value: rTriggerStopTeleport }, + xform: xforms.falling, + root: rTriggerFalling, + priority: 100 + }, + { + src: { value: rButton("touchpad").pressed }, + dest: { value: rTouchpadStopTeleport }, + xform: xforms.falling + }, + { + src: [rTriggerStopTeleport, rTouchpadStopTeleport], + dest: { value: paths.actions.rightHand.stopTeleport }, + xform: xforms.any + } + ], + [sets.leftHandHoveringOnNothing]: [...teleportLeft], + + [sets.leftHandTeleporting]: [ + { + src: { value: lButton("trigger").pressed }, + dest: { value: lTriggerStopTeleport }, + xform: xforms.falling, + root: lTriggerFalling, + priority: 100 + }, + { + src: { value: lButton("touchpad").pressed }, + dest: { value: lTouchpadStopTeleport }, + xform: xforms.falling + }, + { + src: [lTriggerStopTeleport, lTouchpadStopTeleport], + dest: { value: paths.actions.leftHand.stopTeleport }, + xform: xforms.any + } + ], + + [sets.rightHandHoveringOnNothing]: [...teleportRight], + + [sets.cursorHoveringOnNothing]: [], + + [sets.cursorHoveringOnUI]: [ + { + src: { value: rButton("trigger").pressed }, + dest: { value: paths.actions.cursor.grab }, + xform: xforms.rising, + root: rTriggerRising, + priority: 100 + } + ], + + [sets.leftHandHoveringOnInteractable]: [ + { + src: { value: lButton("grip").pressed }, + dest: { value: lGripRisingGrab }, + xform: xforms.rising, + root: lGripRising, + priority: 200 + }, + { + src: { value: lButton("trigger").pressed }, + dest: { value: lTriggerRisingGrab }, + xform: xforms.rising, + root: lTriggerRising, + priority: 200 + }, + { + src: [lGripRisingGrab, lTriggerRisingGrab], + dest: { value: paths.actions.leftHand.grab }, + xform: xforms.any + } + ], + + [sets.leftHandHoldingInteractable]: [ + { + src: { value: lButton("grip").pressed }, + dest: { value: paths.actions.leftHand.drop }, + xform: xforms.falling, + root: lGripFalling, + priority: 200 + } + ], + + [sets.leftHandHoveringOnPen]: [], + [sets.leftHandHoldingPen]: [ + { + src: { + bool: lTouchpadRising, + value: lDpadCenter + }, + dest: { value: paths.actions.leftHand.startTeleport }, + xform: xforms.copyIfTrue + }, + { + src: { value: lButton("trigger").pressed }, + dest: { value: paths.actions.leftHand.startDrawing }, + xform: xforms.rising + }, + { + src: { value: lButton("trigger").pressed }, + dest: { value: lTriggerFallingStopDrawing }, + xform: xforms.falling + }, + { + src: { value: lButton("grip").pressed }, + dest: { value: lGripFallingStopDrawing }, + xform: xforms.falling + }, + { + src: [lTriggerFallingStopDrawing, lGripFallingStopDrawing], + dest: { value: paths.actions.leftHand.stopDrawing }, + xform: xforms.any + }, + { + src: { + bool: lTouchpadRising, + value: lDpadNorth + }, + dest: { + value: paths.actions.leftHand.penNextColor + }, + xform: xforms.copyIfTrue, + root: lDpadNorth, + priority: 200 + }, + { + src: { + bool: lTouchpadRising, + value: lDpadSouth + }, + dest: { + value: paths.actions.leftHand.penPrevColor + }, + xform: xforms.copyIfTrue, + root: lDpadSouth, + priority: 200 + }, + { + src: { + value: lAxis("joyX"), + touching: lButton("touchpad").touched + }, + dest: { value: paths.actions.leftHand.scalePenTip }, + xform: xforms.touch_axis_scroll(0.1) + } + ], + + [sets.cursorHoveringOnInteractable]: [ + { + src: { value: rButton("grip").pressed }, + dest: { value: rGripRisingGrab }, + xform: xforms.rising, + root: rGripRising, + priority: 200 + }, + { + src: { value: rButton("trigger").pressed }, + dest: { value: rTriggerRisingGrab }, + xform: xforms.rising, + root: rTriggerRising, + priority: 200 + }, + { + src: [rGripRisingGrab, rTriggerRisingGrab], + dest: { value: paths.actions.cursor.grab }, + xform: xforms.any + } + ], + + [sets.cursorHoldingInteractable]: [ + { + src: { + value: rAxis("joyY"), + touching: rButton("touchpad").touched + }, + dest: { value: paths.actions.cursor.modDelta }, + xform: xforms.touch_axis_scroll(-1) + }, + { + src: { value: rButton("grip").pressed }, + dest: { value: cursorDrop1 }, + xform: xforms.falling, + root: rGripFalling, + priority: 200 + }, + { + src: { value: rButton("trigger").pressed }, + dest: { + value: cursorDrop2 + }, + xform: xforms.falling, + root: rTriggerFalling, + priority: 200 + }, + { + src: [cursorDrop1, cursorDrop2], + dest: { value: paths.actions.cursor.drop }, + xform: xforms.any + } + ], + + [sets.cursorHoveringOnPen]: [], + + [sets.cursorHoldingPen]: [ + { + src: { + bool: rTouchpadRising, + value: rDpadCenter + }, + dest: { value: paths.actions.rightHand.startTeleport }, + xform: xforms.copyIfTrue + }, + { + src: { value: rButton("trigger").pressed }, + dest: { value: paths.actions.cursor.startDrawing }, + xform: xforms.rising + }, + { + src: { value: rButton("trigger").pressed }, + dest: { value: paths.actions.cursor.stopDrawing }, + xform: xforms.falling, + root: rTriggerFalling, + priority: 300 + }, + { + src: { + value: rAxis("joyX"), + touching: rButton("touchpad").touched + }, + dest: { value: paths.actions.cursor.scalePenTip }, + xform: xforms.touch_axis_scroll(0.1) + }, + { + src: { + bool: rTouchpadRising, + value: rDpadNorth + }, + dest: { + value: paths.actions.cursor.penNextColor + }, + xform: xforms.copyIfTrue, + root: rDpadNorth, + priority: 200 + }, + { + src: { + bool: rTouchpadRising, + value: rDpadSouth + }, + dest: { + value: paths.actions.cursor.penPrevColor + }, + xform: xforms.copyIfTrue, + root: rDpadSouth, + priority: 200 + } + ], + + [sets.rightHandHoveringOnInteractable]: [ + { + src: { value: rButton("grip").pressed }, + dest: { value: rGripRisingGrab }, + xform: xforms.rising, + root: rGripRising, + priority: 200 + }, + { + src: { value: rButton("trigger").pressed }, + dest: { value: rTriggerRisingGrab }, + xform: xforms.rising, + root: rTriggerRising, + priority: 200 + }, + { + src: [rGripRisingGrab, rTriggerRisingGrab], + dest: { value: paths.actions.rightHand.grab }, + xform: xforms.any + } + ], + + [sets.rightHandHoldingInteractable]: [ + { + src: { value: rButton("grip").pressed }, + dest: { value: rHandDrop1 }, + xform: xforms.falling, + root: rGripFalling, + priority: 200 + }, + { + src: { value: rButton("trigger").pressed }, + dest: { + value: rHandDrop2 + }, + xform: xforms.falling, + root: rTriggerFalling, + priority: 200 + }, + { + src: [rHandDrop1, rHandDrop2], + dest: { value: paths.actions.rightHand.drop }, + xform: xforms.any + } + ], + [sets.rightHandHoveringOnPen]: [], + [sets.rightHandHoldingPen]: [ + { + src: { + bool: rTouchpadRising, + value: rDpadCenter + }, + dest: { value: paths.actions.rightHand.startTeleport }, + xform: xforms.copyIfTrue + }, + { + src: { value: rButton("trigger").pressed }, + dest: { value: paths.actions.rightHand.startDrawing }, + xform: xforms.rising + }, + { + src: { value: rButton("trigger").pressed }, + dest: { value: paths.actions.rightHand.stopDrawing }, + xform: xforms.falling, + root: rTriggerFalling, + priority: 300 + }, + { + src: { + bool: rTouchpadRising, + value: rDpadNorth + }, + dest: { + value: paths.actions.rightHand.penNextColor + }, + xform: xforms.copyIfTrue, + root: rDpadNorth, + priority: 200 + }, + { + src: { + bool: rTouchpadRising, + value: rDpadSouth + }, + dest: { + value: paths.actions.rightHand.penPrevColor + }, + xform: xforms.copyIfTrue, + root: rDpadSouth, + priority: 200 + }, + { + src: { + value: rAxis("joyX"), + touching: rButton("touchpad").touched + }, + dest: { value: paths.actions.rightHand.scalePenTip }, + xform: xforms.touch_axis_scroll(0.1) + } + ], + + [sets.cursorHoveringOnCamera]: [], + [sets.rightHandHoveringOnCamera]: [], + [sets.leftHandHoveringOnCamera]: [], + + [sets.rightHandHoldingCamera]: [ + { + src: { value: rButton("trigger").pressed }, + dest: { value: paths.actions.rightHand.takeSnapshot }, + xform: xforms.rising + }, + { + src: { value: rButton("trigger").pressed }, + dest: { value: paths.noop }, + xform: xforms.falling, + root: rTriggerFalling, + priority: 400 + } + ], + [sets.leftHandHoldingCamera]: [ + { + src: { value: lButton("trigger").pressed }, + dest: { value: paths.actions.leftHand.takeSnapshot }, + xform: xforms.rising + } + ], + [sets.cursorHoldingCamera]: [ + { + src: { value: rButton("trigger").pressed }, + dest: { value: paths.actions.cursor.takeSnapshot }, + xform: xforms.rising + }, + { + src: { value: rButton("trigger").pressed }, + dest: { value: paths.noop }, + xform: xforms.falling, + root: rTriggerFalling, + priority: 400 + } + ] +}; diff --git a/src/systems/userinput/bindings/xbox-controller-user.js b/src/systems/userinput/bindings/xbox-controller-user.js new file mode 100644 index 0000000000000000000000000000000000000000..460f3b1f50cc9876680fe111e86da950a3741466 --- /dev/null +++ b/src/systems/userinput/bindings/xbox-controller-user.js @@ -0,0 +1,200 @@ +import { paths } from "../paths"; +import { sets } from "../sets"; +import { xforms } from "./xforms"; + +const xboxUnscaledCursorScalePenTip = "foobarbazbotbooch"; + +const button = paths.device.xbox.button; +const axis = paths.device.xbox.axis; +const rightTriggerFalling = "/vars/xbox/rightTriggerFalling"; + +export const xboxControllerUserBindings = { + [sets.cursorHoldingInteractable]: [ + { + src: { value: button("rightTrigger").pressed }, + dest: { value: paths.actions.cursor.drop }, + xform: xforms.falling, + root: rightTriggerFalling, + priority: 100 + }, + { + src: { + bool: button("leftTrigger").pressed, + value: axis("leftJoystickVertical") + }, + dest: { value: "/vars/xbox/cursorModDelta" }, + xform: xforms.copyIfTrue + }, + { + src: { + value: "/vars/xbox/cursorModDelta" + }, + dest: { value: paths.actions.cursor.modDelta }, + xform: xforms.copy + }, + { + src: { + bool: button("leftTrigger").pressed, + value: axis("leftJoystickVertical") + }, + dest: { value: "/var/xbox/leftJoystickVertical" }, + xform: xforms.copyIfFalse, + root: "xbox/leftJoystick", + priority: 200 + } + ], + [sets.cursorHoldingPen]: [ + { + src: { value: button("rightTrigger").pressed }, + dest: { value: paths.actions.cursor.startDrawing }, + xform: xforms.rising, + root: "xboxRightTriggerRising", + priority: 200 + }, + { + src: { value: button("rightTrigger").pressed }, + dest: { value: paths.actions.cursor.stopDrawing }, + xform: xforms.falling, + root: rightTriggerFalling, + priority: 200 + }, + { + src: { value: button("b").pressed }, + dest: { value: paths.actions.cursor.drop }, + xform: xforms.rising + }, + { + src: { value: button("y").pressed }, + dest: { value: paths.noop }, + xform: xforms.noop, + root: "xbox/y", + priority: 200 + }, + { + src: { value: button("a").pressed }, + dest: { value: paths.actions.cursor.penNextColor }, + xform: xforms.rising + }, + { + src: { value: button("x").pressed }, + dest: { value: paths.actions.cursor.penPrevColor }, + xform: xforms.rising + }, + { + src: { + bool: button("leftTrigger").pressed, + value: axis("rightJoystickVertical") + }, + dest: { value: xboxUnscaledCursorScalePenTip }, + xform: xforms.copyIfTrue + }, + { + dest: { + value: paths.actions.cursorScalePenTip + }, + src: { value: xboxUnscaledCursorScalePenTip }, + xform: xforms.scale(0.01) + } + ], + [sets.global]: [ + { + src: { + value: axis("rightJoystickHorizontal") + }, + dest: { value: "/var/xbox/scaledRightJoystickHorizontal" }, + xform: xforms.scale(-1.5) // horizontal look speed modifier + }, + { + src: { + value: axis("rightJoystickVertical") + }, + dest: { value: "/var/xbox/scaledRightJoystickVertical" }, + xform: xforms.scale(-1.25) // vertical look speed modifier + }, + { + src: { + x: "/var/xbox/scaledRightJoystickHorizontal", + y: "/var/xbox/scaledRightJoystickVertical" + }, + dest: { value: paths.actions.cameraDelta }, + xform: xforms.compose_vec2 + }, + { + src: { + value: axis("leftJoystickHorizontal") + }, + dest: { value: "/var/xbox/scaledLeftJoystickHorizontal" }, + xform: xforms.scale(1.5) // horizontal move speed modifier + }, + { + src: { value: axis("leftJoystickVertical") }, + dest: { value: "/var/xbox/leftJoystickVertical" }, + xform: xforms.copy, + root: "xbox/leftJoystick", + priority: 100 + }, + { + src: { value: "/var/xbox/leftJoystickVertical" }, + dest: { value: "/var/xbox/scaledLeftJoystickVertical" }, + xform: xforms.scale(-1.25) // vertical move speed modifier + }, + { + src: { + x: "/var/xbox/scaledLeftJoystickHorizontal", + y: "/var/xbox/scaledLeftJoystickVertical" + }, + dest: { value: paths.actions.characterAcceleration }, + xform: xforms.compose_vec2 + }, + { + src: { value: button("leftTrigger").pressed }, + dest: { value: paths.actions.boost }, + xform: xforms.copy + }, + { + src: { value: button("leftBumper").pressed }, + dest: { value: paths.actions.snapRotateLeft }, + xform: xforms.rising + }, + { + src: { value: button("rightBumper").pressed }, + dest: { value: paths.actions.snapRotateRight }, + xform: xforms.rising + }, + { + src: { value: button("dpadUp").pressed }, + dest: { value: paths.actions.translate.up }, + xform: xforms.scale(0.1) + }, + { + src: { value: button("dpadDown").pressed }, + dest: { value: paths.actions.translate.down }, + xform: xforms.scale(0.1) + }, + { + dest: { value: "var/vec2/zero" }, + xform: xforms.vec2Zero + }, + { + src: { value: "var/vec2/zero" }, + dest: { value: paths.actions.cursor.pose }, + xform: xforms.poseFromCameraProjection() + }, + { + src: { value: button("y").pressed }, + dest: { value: paths.actions.spawnPen }, + xform: xforms.rising, + root: "xbox/y", + priority: 100 + } + ], + [sets.cursorHoveringOnInteractable]: [ + { + src: { value: button("rightTrigger").pressed }, + dest: { value: paths.actions.cursor.grab }, + xform: xforms.rising, + root: "xboxRightTriggerRising", + priority: 100 + } + ] +}; diff --git a/src/systems/userinput/bindings/xforms.js b/src/systems/userinput/bindings/xforms.js new file mode 100644 index 0000000000000000000000000000000000000000..8fe0d49d22ed58513bd50cf65fb8e7a104905b41 --- /dev/null +++ b/src/systems/userinput/bindings/xforms.js @@ -0,0 +1,121 @@ +import { Pose } from "../pose"; +import { angleTo4Direction } from "../../../utils/dpad"; + +const zeroVec2 = [0, 0]; +export const xforms = { + noop: function() {}, + copy: function(frame, src, dest) { + frame[dest.value] = frame[src.value]; + }, + scale: function(scalar) { + return function scale(frame, src, dest) { + if (frame[src.value] !== undefined) { + frame[dest.value] = frame[src.value] * scalar; + } + }; + }, + split_vec2: function(frame, src, dest) { + if (frame[src.value] !== undefined) { + frame[dest.x] = frame[src.value][0]; + frame[dest.y] = frame[src.value][1]; + } + }, + compose_vec2: function(frame, src, dest) { + if (frame[src.x] !== undefined && frame[src.y] !== undefined) { + frame[dest.value] = [frame[src.x], frame[src.y]]; + } + }, + negate: function(frame, src, dest) { + frame[dest.value] = -frame[src.value]; + }, + copyIfFalse: function(frame, src, dest) { + frame[dest.value] = frame[src.bool] ? undefined : frame[src.value]; + }, + copyIfTrue: function(frame, src, dest) { + frame[dest.value] = frame[src.bool] ? frame[src.value] : undefined; + }, + zeroIfDefined: function(frame, src, dest) { + frame[dest.value] = frame[src.bool] !== undefined ? 0 : frame[src.value]; + }, + true: function(frame, src, dest) { + frame[dest.value] = true; + }, + rising: function rising(frame, src, dest, prevState) { + frame[dest.value] = frame[src.value] && prevState === false; + return !!frame[src.value]; + }, + risingWithFrameDelay: function(n) { + return function risingWithFrameDelay(frame, src, dest, state = { values: new Array(n) }) { + frame[dest.value] = state.values.shift(); + state.values.push(frame[src.value] && !state.prev); + state.prev = frame[src.value]; + return state; + }; + }, + falling: function falling(frame, src, dest, prevState) { + frame[dest.value] = !frame[src.value] && prevState; + return !!frame[src.value]; + }, + vec2Zero: function(frame, _, dest) { + frame[dest.value] = zeroVec2; + }, + poseFromCameraProjection: function() { + let camera; + const pose = new Pose(); + return function poseFromCameraProjection(frame, src, dest) { + if (!camera) { + camera = document.querySelector("#player-camera").components.camera.camera; + } + frame[dest.value] = pose.fromCameraProjection(camera, frame[src.value][0], frame[src.value][1]); + }; + }, + vec2dpad: function(deadzoneRadius, invertX = false, invertY = false) { + const deadzoneRadiusSquared = deadzoneRadius * deadzoneRadius; + return function vec2dpad(frame, src, dest) { + if (!frame[src.value]) return; + const [x, y] = frame[src.value]; + const inCenter = x * x + y * y < deadzoneRadiusSquared; + const direction = inCenter ? "center" : angleTo4Direction(Math.atan2(invertX ? -x : x, invertY ? -y : y)); + frame[dest[direction]] = true; + }; + }, + always: function(constValue) { + return function always(frame, _, dest) { + frame[dest.value] = constValue; + }; + }, + wasd_to_vec2: function(frame, { w, a, s, d }, { vec2 }) { + let x = 0; + let y = 0; + if (frame[a]) x -= 1; + if (frame[d]) x += 1; + if (frame[w]) y += 1; + if (frame[s]) y -= 1; + frame[vec2] = [x, y]; + }, + add_vec2: function(frame, src, dest) { + const first = frame[src.first]; + const second = frame[src.second]; + if (first && second) { + frame[dest.value] = [first[0] + second[0], first[1] + second[1]]; + } + }, + any: function(frame, src, dest) { + for (const path in src) { + if (frame[src[path]]) { + frame[dest.value] = true; + return; + } + } + frame[dest.value] = false; + }, + touch_axis_scroll(scale = 1) { + return function(frame, src, dest, state = { value: 0, touching: false }) { + frame[dest.value] = + !state.touching || !frame[src.touching] ? 0 : scale * (frame[src.value] + 1 - (state.value + 1)); + state.value = frame[src.value]; + state.touching = frame[src.touching]; + return state; + }; + } +}; diff --git a/src/systems/userinput/devices/app-aware-mouse.js b/src/systems/userinput/devices/app-aware-mouse.js new file mode 100644 index 0000000000000000000000000000000000000000..02a032ac4e296e07e3a68b77752472c50a3897ca --- /dev/null +++ b/src/systems/userinput/devices/app-aware-mouse.js @@ -0,0 +1,61 @@ +import { paths } from "../paths"; +import { Pose } from "../pose"; + +const calculateCursorPose = function(camera, coords) { + const cursorPose = new Pose(); + const origin = new THREE.Vector3(); + const direction = new THREE.Vector3(); + origin.setFromMatrixPosition(camera.matrixWorld); + direction + .set(coords[0], coords[1], 0.5) + .unproject(camera) + .sub(origin) + .normalize(); + cursorPose.fromOriginAndDirection(origin, direction); + return cursorPose; +}; + +export class AppAwareMouseDevice { + constructor() { + this.prevButtonLeft = false; + this.clickedOnAnything = false; + } + + write(frame) { + const cursorController = document.querySelector("[cursor-controller]").components["cursor-controller"]; + if (!cursorController) { + console.error("initialization order error? get aframe'd"); + return; + } + + const coords = frame[paths.device.mouse.coords]; + const isCursorGrabbing = cursorController.data.cursor.components["super-hands"].state.has("grab-start"); + if (isCursorGrabbing) { + const camera = document.querySelector("#player-camera").components.camera.camera; + frame[paths.device.smartMouse.cursorPose] = calculateCursorPose(camera, coords); + return; + } + + const buttonLeft = frame[paths.device.mouse.buttonLeft]; + if (buttonLeft && !this.prevButtonLeft) { + const rawIntersections = []; + cursorController.raycaster.intersectObjects(cursorController.targets, true, rawIntersections); + const intersection = rawIntersections.find(x => x.object.el); + this.clickedOnAnything = + intersection && + intersection.object.el.matches(".pen, .pen *, .video, .video *, .interactable, .interactable *"); + } + this.prevButtonLeft = buttonLeft; + + if (!buttonLeft) { + this.clickedOnAnything = false; + } + + if (!this.clickedOnAnything && buttonLeft) { + frame[paths.device.smartMouse.cameraDelta] = frame[paths.device.mouse.movementXY]; + } else { + const camera = document.querySelector("#player-camera").components.camera.camera; + frame[paths.device.smartMouse.cursorPose] = calculateCursorPose(camera, coords); + } + } +} diff --git a/src/systems/userinput/devices/app-aware-touchscreen.js b/src/systems/userinput/devices/app-aware-touchscreen.js new file mode 100644 index 0000000000000000000000000000000000000000..31bf0fb7b9c625a5c060a31174f973eee79f39a9 --- /dev/null +++ b/src/systems/userinput/devices/app-aware-touchscreen.js @@ -0,0 +1,244 @@ +import { paths } from "../paths"; +import { Pose } from "../pose"; +import { touchIsAssigned, jobIsAssigned, assign, unassign, findByJob, findByTouch } from "./touchscreen/assignments"; + +const MOVE_CURSOR_JOB = "MOVE CURSOR"; +const MOVE_CAMERA_JOB = "MOVE CAMERA"; +const FIRST_PINCHER_JOB = "FIRST PINCHER"; +const SECOND_PINCHER_JOB = "SECOND PINCHER"; + +function distance(x1, y1, x2, y2) { + const dx = x1 - x2; + const dy = y1 - y2; + return Math.sqrt(dx * dx + dy * dy); +} + +function shouldMoveCursor(touch, raycaster) { + const cursorController = document.querySelector("[cursor-controller]").components["cursor-controller"]; + const isCursorGrabbing = cursorController.data.cursor.components["super-hands"].state.has("grab-start"); + if (isCursorGrabbing) { + return true; + } + const rawIntersections = []; + raycaster.setFromCamera( + { + x: (touch.clientX / window.innerWidth) * 2 - 1, + y: -(touch.clientY / window.innerHeight) * 2 + 1 + }, + document.querySelector("#player-camera").components.camera.camera + ); + raycaster.intersectObjects(cursorController.targets, true, rawIntersections); + const intersection = rawIntersections.find(x => x.object.el); + return intersection && intersection.object.el.matches(".interactable, .interactable *"); +} + +export class AppAwareTouchscreenDevice { + constructor() { + this.raycaster = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3(), 0, 3); + this.assignments = []; + this.pinch = {}; + this.events = []; + ["touchstart", "touchend", "touchmove", "touchcancel"].map(x => + document.querySelector("canvas").addEventListener(x, this.events.push.bind(this.events)) + ); + } + + end(touch) { + if (!touchIsAssigned(touch, this.assignments)) { + console.warn("touch does not have a job", touch); + return; + } + + const assignment = findByTouch(touch, this.assignments); + switch (assignment.job) { + case MOVE_CURSOR_JOB: + case MOVE_CAMERA_JOB: + unassign(assignment.touch, assignment.job, this.assignments); + break; + case FIRST_PINCHER_JOB: + unassign(assignment.touch, assignment.job, this.assignments); + this.pinch = undefined; + + if (jobIsAssigned(SECOND_PINCHER_JOB, this.assignments)) { + const second = findByJob(SECOND_PINCHER_JOB, this.assignments); + unassign(second.touch, second.job, this.assignments); + if (jobIsAssigned(MOVE_CAMERA_JOB, this.assignments)) { + // reassign secondPincher to firstPincher + const first = assign(second.touch, FIRST_PINCHER_JOB, this.assignments); + first.clientX = second.clientX; + first.clientY = second.clientY; + } else { + // reassign secondPincher to moveCamera + const cameraMover = assign(second.touch, MOVE_CAMERA_JOB, this.assignments); + cameraMover.clientX = second.clientX; + cameraMover.clientY = second.clientY; + cameraMover.delta = [0, 0]; + } + } + break; + case SECOND_PINCHER_JOB: + unassign(assignment.touch, assignment.job, this.assignments); + this.pinch = undefined; + if (jobIsAssigned(FIRST_PINCHER_JOB, this.assignments) && !jobIsAssigned(MOVE_CAMERA_JOB, this.assignments)) { + //reassign firstPincher to moveCamera + const first = findByJob(FIRST_PINCHER_JOB, this.assignments); + unassign(first.touch, first.job, this.assignments); + const cameraMover = assign(first.touch, MOVE_CAMERA_JOB, this.assignments); + cameraMover.clientX = first.clientX; + cameraMover.clientY = first.clientY; + cameraMover.delta = [0, 0]; + } + break; + } + } + + move(touch) { + if (!touchIsAssigned(touch, this.assignments)) { + console.warn("touch does not have job", touch); + return; + } + + const assignment = findByTouch(touch, this.assignments); + switch (assignment.job) { + case MOVE_CURSOR_JOB: + assignment.cursorPose.fromCameraProjection( + document.querySelector("#player-camera").components.camera.camera, + (touch.clientX / window.innerWidth) * 2 - 1, + -(touch.clientY / window.innerHeight) * 2 + 1 + ); + break; + case MOVE_CAMERA_JOB: + assignment.delta[0] += touch.clientX - assignment.clientX; + assignment.delta[1] += touch.clientY - assignment.clientY; + assignment.clientX = touch.clientX; + assignment.clientY = touch.clientY; + break; + case FIRST_PINCHER_JOB: + case SECOND_PINCHER_JOB: + assignment.clientX = touch.clientX; + assignment.clientY = touch.clientY; + if (jobIsAssigned(FIRST_PINCHER_JOB, this.assignments) && jobIsAssigned(SECOND_PINCHER_JOB, this.assignments)) { + const first = findByJob(FIRST_PINCHER_JOB, this.assignments); + const second = findByJob(SECOND_PINCHER_JOB, this.assignments); + const currentDistance = distance(first.clientX, first.clientY, second.clientX, second.clientY); + this.pinch.delta += currentDistance - this.pinch.currentDistance; + this.pinch.currentDistance = currentDistance; + } + break; + } + } + + start(touch) { + if (touchIsAssigned(touch, this.assignments)) { + console.error("touch already has a job"); + return; + } + + if (!jobIsAssigned(MOVE_CURSOR_JOB, this.assignments) && shouldMoveCursor(touch, this.raycaster)) { + const assignment = assign(touch, MOVE_CURSOR_JOB, this.assignments); + assignment.cursorPose = new Pose().fromCameraProjection( + document.querySelector("#player-camera").components.camera.camera, + (touch.clientX / window.innerWidth) * 2 - 1, + -(touch.clientY / window.innerHeight) * 2 + 1 + ); + assignment.isFirstFrame = true; + return; + } + + if (!jobIsAssigned(MOVE_CAMERA_JOB, this.assignments)) { + const assignment = assign(touch, MOVE_CAMERA_JOB, this.assignments); + assignment.clientX = touch.clientX; + assignment.clientY = touch.clientY; + assignment.delta = [0, 0]; + return; + } + + if (!jobIsAssigned(SECOND_PINCHER_JOB, this.assignments)) { + let first; + if (jobIsAssigned(FIRST_PINCHER_JOB, this.assignments)) { + first = findByJob(FIRST_PINCHER_JOB, this.assignments); + } else { + const cameraMover = findByJob(MOVE_CAMERA_JOB, this.assignments); + unassign(cameraMover.touch, cameraMover.job, this.assignments); + + first = assign(cameraMover.touch, FIRST_PINCHER_JOB, this.assignments); + first.clientX = cameraMover.clientX; + first.clientY = cameraMover.clientY; + } + + const second = assign(touch, SECOND_PINCHER_JOB, this.assignments); + second.clientX = touch.clientX; + second.clientY = touch.clientY; + + const initialDistance = distance(first.clientX, first.clientY, second.clientX, second.clientY); + this.pinch = { + initialDistance, + currentDistance: initialDistance, + delta: 0 + }; + return; + } + + console.warn("no job suitable for touch", touch); + } + + process(event) { + switch (event.type) { + case "touchstart": + for (const touch of event.changedTouches) { + this.start(touch); + } + break; + case "touchmove": + for (const touch of event.touches) { + this.move(touch); + } + break; + case "touchend": + case "touchcancel": + for (const touch of event.changedTouches) { + this.end(touch); + } + break; + } + } + + write(frame) { + if (this.pinch) { + this.pinch.delta = 0; + } + const cameraMover = + jobIsAssigned(MOVE_CAMERA_JOB, this.assignments) && findByJob(MOVE_CAMERA_JOB, this.assignments); + if (cameraMover) { + cameraMover.delta[0] = 0; + cameraMover.delta[1] = 0; + } + + this.events.forEach(event => { + this.process(event, frame); + }); + while (this.events.length) { + this.events.pop(); + } + + const path = paths.device.touchscreen; + if (jobIsAssigned(MOVE_CURSOR_JOB, this.assignments)) { + const assignment = findByJob(MOVE_CURSOR_JOB, this.assignments); + frame[path.cursorPose] = assignment.cursorPose; + // If you touch a grabbable, we want to wait 1 frame before admitting it to anyone else, because we + // want to hover on the first frame and grab on the next. + frame[path.isTouchingGrabbable] = !assignment.isFirstFrame; + assignment.isFirstFrame = false; + } + + if (jobIsAssigned(MOVE_CAMERA_JOB, this.assignments)) { + frame[path.cameraDelta] = findByJob(MOVE_CAMERA_JOB, this.assignments).delta; + } + + if (this.pinch) { + frame[path.pinchDelta] = this.pinch.delta; + frame[path.initialPinchDistance] = this.pinch.initialDistance; + frame[path.currentPinchDistance] = this.pinch.currentDistance; + } + } +} diff --git a/src/systems/userinput/devices/daydream-controller.js b/src/systems/userinput/devices/daydream-controller.js new file mode 100644 index 0000000000000000000000000000000000000000..2b08ce300c0a49ef4f18585cd24a8cae40ea8f99 --- /dev/null +++ b/src/systems/userinput/devices/daydream-controller.js @@ -0,0 +1,50 @@ +import { paths } from "../paths"; +import { Pose } from "../pose"; + +export class DaydreamControllerDevice { + constructor(gamepad) { + this.gamepad = gamepad; + this.buttonMap = [{ name: "touchpad", buttonId: 0 }]; + this.axisMap = [{ name: "touchpadX", axisId: 0 }, { name: "touchpadY", axisId: 1 }]; + + this.rayObjectRotation = new THREE.Quaternion(); + this.selector = `#player-${gamepad.hand}-controller`; + this.pose = new Pose(); + } + + write(frame) { + if (this.gamepad.connected) { + this.gamepad.buttons.forEach((button, i) => { + const buttonPath = paths.device.gamepad(this.gamepad.index).button(i); + frame[buttonPath.pressed] = !!button.pressed; + frame[buttonPath.touched] = !!button.touched; + frame[buttonPath.value] = button.value; + }); + this.gamepad.axes.forEach((axis, i) => { + frame[paths.device.gamepad(this.gamepad.index).axis(i)] = axis; + }); + + this.buttonMap.forEach(button => { + const outpath = paths.device.daydream.button(button.name); + frame[outpath.pressed] = !!frame[paths.device.gamepad(this.gamepad.index).button(button.buttonId).pressed]; + frame[outpath.touched] = !!frame[paths.device.gamepad(this.gamepad.index).button(button.buttonId).touched]; + frame[outpath.value] = frame[paths.device.gamepad(this.gamepad.index).button(button.buttonId).value]; + }); + this.axisMap.forEach(axis => { + frame[paths.device.daydream.axis(axis.name)] = + frame[paths.device.gamepad(this.gamepad.index).axis(axis.axisId)]; + }); + + // TODO ideally we should just be getting pose from the gamepad + if (!this.rayObject) { + this.rayObject = document.querySelector(this.selector).object3D; + } + this.rayObject.updateMatrixWorld(); + this.rayObjectRotation.setFromRotationMatrix(this.rayObject.matrixWorld); + this.pose.position.setFromMatrixPosition(this.rayObject.matrixWorld); + this.pose.direction.set(0, 0, -1).applyQuaternion(this.rayObjectRotation); + this.pose.fromOriginAndDirection(this.pose.position, this.pose.direction); + frame[paths.device.daydream.pose] = this.pose; + } + } +} diff --git a/src/systems/userinput/devices/gamepad.js b/src/systems/userinput/devices/gamepad.js new file mode 100644 index 0000000000000000000000000000000000000000..5acc7aa8218a6fce33fcbdf091a8a7502d6b09b4 --- /dev/null +++ b/src/systems/userinput/devices/gamepad.js @@ -0,0 +1,21 @@ +import { paths } from "../paths"; + +export class GamepadDevice { + constructor(gamepad) { + this.gamepad = gamepad; + } + + write(frame) { + if (this.gamepad.connected) { + this.gamepad.buttons.forEach((button, i) => { + const buttonPath = paths.device.gamepad(this.gamepad.index).button(i); + frame[buttonPath.pressed] = !!button.pressed; + frame[buttonPath.touched] = !!button.touched; + frame[buttonPath.value] = button.value; + }); + this.gamepad.axes.forEach((axis, i) => { + frame[paths.device.gamepad(this.gamepad.index).axis(i)] = axis; + }); + } + } +} diff --git a/src/systems/userinput/devices/hud.js b/src/systems/userinput/devices/hud.js new file mode 100644 index 0000000000000000000000000000000000000000..9981b01128d67ca090fcccd2b8ab13339368cfb0 --- /dev/null +++ b/src/systems/userinput/devices/hud.js @@ -0,0 +1,15 @@ +import { paths } from "../paths"; + +export class HudDevice { + constructor() { + this.events = []; + document.querySelector("a-scene").addEventListener("penButtonPressed", this.events.push.bind(this.events)); + } + + write(frame) { + frame[paths.device.hud.penButton] = this.events.length !== 0; + while (this.events.length) { + this.events.pop(); + } + } +} diff --git a/src/systems/userinput/devices/keyboard.js b/src/systems/userinput/devices/keyboard.js new file mode 100644 index 0000000000000000000000000000000000000000..572e78596eb677e96e04a70c49f09465e4f0efc8 --- /dev/null +++ b/src/systems/userinput/devices/keyboard.js @@ -0,0 +1,23 @@ +import { paths } from "../paths"; +export class KeyboardDevice { + constructor() { + this.keys = {}; + this.events = []; + + ["keydown", "keyup", "blur", "mouseout"].map(x => document.addEventListener(x, this.events.push.bind(this.events))); + } + + write(frame) { + this.events.forEach(event => { + if (event.type === "blur" || event.type === "mouseout") { + this.keys = {}; + return; + } + this.keys[paths.device.keyboard.key(event.key)] = event.type === "keydown"; + }); + while (this.events.length) { + this.events.pop(); + } + Object.assign(frame, this.keys); + } +} diff --git a/src/systems/userinput/devices/mouse.js b/src/systems/userinput/devices/mouse.js new file mode 100644 index 0000000000000000000000000000000000000000..0ab7a3ac2aea0ce0ec44ad55fb5804348443bcae --- /dev/null +++ b/src/systems/userinput/devices/mouse.js @@ -0,0 +1,77 @@ +import { paths } from "../paths"; + +// TODO: Where do these values (500, 10, 2) come from? +const modeMod = { + [WheelEvent.DOM_DELTA_PIXEL]: 500, + [WheelEvent.DOM_DELTA_LINE]: 10, + [WheelEvent.DOM_DELTA_PAGE]: 2 +}; + +export class MouseDevice { + constructor() { + this.events = []; + this.coords = [0, 0]; // normalized screenspace coordinates in [(-1, 1), (-1, 1)] + this.movementXY = [0, 0]; // deltas + this.buttonLeft = false; + this.buttonRight = false; + this.wheel = 0; // delta + + const queueEvent = this.events.push.bind(this.events); + const canvas = document.querySelector("canvas"); + ["mousedown", "mouseup", "mousemove", "wheel"].map(x => canvas.addEventListener(x, queueEvent)); + ["mouseout", "blur"].map(x => document.addEventListener(x, queueEvent)); + } + + process(event) { + if (event.type === "wheel") { + this.wheel += event.deltaY / modeMod[event.deltaMode]; + return; + } + if (event.type === "mouseout" || event.type === "blur") { + this.coords[0] = 0; + this.coords[1] = 0; + this.movementXY[0] = 0; + this.movementXY[1] = 0; + this.buttonLeft = false; + this.buttonRight = false; + this.wheel = 0; + } + const left = event.button === 0; + const right = event.button === 2; + this.coords[0] = (event.clientX / window.innerWidth) * 2 - 1; + this.coords[1] = -(event.clientY / window.innerHeight) * 2 + 1; + this.movementXY[0] += event.movementX; + this.movementXY[1] += event.movementY; + if (event.type === "mousedown" && left) { + this.buttonLeft = true; + } else if (event.type === "mousedown" && right) { + this.buttonRight = true; + } else if (event.type === "mouseup" && left) { + this.buttonLeft = false; + } else if (event.type === "mouseup" && right) { + this.buttonRight = false; + } + } + + write(frame) { + this.movementXY = [0, 0]; // deltas + this.wheel = 0; // delta + this.events.forEach(event => { + this.process(event, frame); + }); + + while (this.events.length) { + this.events.pop(); + } + + frame[paths.device.mouse.coords] = this.coords; + frame[paths.device.mouse.movementXY] = this.movementXY; + frame[paths.device.mouse.buttonLeft] = this.buttonLeft; + frame[paths.device.mouse.buttonRight] = this.buttonRight; + frame[paths.device.mouse.wheel] = this.wheel; + } +} + +window.oncontextmenu = e => { + e.preventDefault(); +}; diff --git a/src/systems/userinput/devices/oculus-go-controller.js b/src/systems/userinput/devices/oculus-go-controller.js new file mode 100644 index 0000000000000000000000000000000000000000..8112800cf01b8f9d775fbb1c222c6f521794dde0 --- /dev/null +++ b/src/systems/userinput/devices/oculus-go-controller.js @@ -0,0 +1,50 @@ +import { paths } from "../paths"; +import { Pose } from "../pose"; + +export class OculusGoControllerDevice { + constructor(gamepad) { + this.gamepad = gamepad; + this.buttonMap = [{ name: "touchpad", buttonId: 0 }, { name: "trigger", buttonId: 1 }]; + this.axisMap = [{ name: "touchpadX", axisId: 0 }, { name: "touchpadY", axisId: 1 }]; + + this.rayObjectRotation = new THREE.Quaternion(); + this.selector = `#player-${gamepad.hand}-controller`; + this.pose = new Pose(); + } + + write(frame) { + if (this.gamepad.connected) { + this.gamepad.buttons.forEach((button, i) => { + const buttonPath = paths.device.gamepad(this.gamepad.index).button(i); + frame[buttonPath.pressed] = !!button.pressed; + frame[buttonPath.touched] = !!button.touched; + frame[buttonPath.value] = button.value; + }); + this.gamepad.axes.forEach((axis, i) => { + frame[paths.device.gamepad(this.gamepad.index).axis(i)] = axis; + }); + + this.buttonMap.forEach(button => { + const outpath = paths.device.oculusgo.button(button.name); + frame[outpath.pressed] = !!frame[paths.device.gamepad(this.gamepad.index).button(button.buttonId).pressed]; + frame[outpath.touched] = !!frame[paths.device.gamepad(this.gamepad.index).button(button.buttonId).touched]; + frame[outpath.value] = frame[paths.device.gamepad(this.gamepad.index).button(button.buttonId).value]; + }); + this.axisMap.forEach(axis => { + frame[paths.device.oculusgo.axis(axis.name)] = + frame[paths.device.gamepad(this.gamepad.index).axis(axis.axisId)]; + }); + + // TODO ideally we should just be getting pose from the gamepad + if (!this.rayObject) { + this.rayObject = document.querySelector(this.selector).object3D; + } + this.rayObject.updateMatrixWorld(); + this.rayObjectRotation.setFromRotationMatrix(this.rayObject.matrixWorld); + this.pose.position.setFromMatrixPosition(this.rayObject.matrixWorld); + this.pose.direction.set(0, 0, -1).applyQuaternion(this.rayObjectRotation); + this.pose.fromOriginAndDirection(this.pose.position, this.pose.direction); + frame[paths.device.oculusgo.pose] = this.pose; + } + } +} diff --git a/src/systems/userinput/devices/oculus-touch-controller.js b/src/systems/userinput/devices/oculus-touch-controller.js new file mode 100644 index 0000000000000000000000000000000000000000..75658748c22e7aadef2832867ceefd1871758a93 --- /dev/null +++ b/src/systems/userinput/devices/oculus-touch-controller.js @@ -0,0 +1,75 @@ +import { paths } from "../paths"; +import { Pose } from "../pose"; + +export const leftOculusTouchButtonMap = [ + { name: "thumbStick", buttonId: 0 }, + { name: "trigger", buttonId: 1 }, + { name: "grip", buttonId: 2 }, + { name: "x", buttonId: 3 }, + { name: "y", buttonId: 4 } +]; +export const rightOculusTouchButtonMap = [ + { name: "thumbStick", buttonId: 0 }, + { name: "trigger", buttonId: 1 }, + { name: "grip", buttonId: 2 }, + { name: "a", buttonId: 3 }, + { name: "b", buttonId: 4 } +]; + +export class OculusTouchControllerDevice { + constructor(gamepad) { + this.rayObjectRotation = new THREE.Quaternion(); + + // wake the gamepad api up. otherwise it does not report touch controllers. + // in chrome it still won't unless you enter vr. + navigator.getVRDisplays(); + + const buttonMaps = { + left: leftOculusTouchButtonMap, + right: rightOculusTouchButtonMap + }; + + const devicePaths = { + left: paths.device.leftOculusTouch, + right: paths.device.rightOculusTouch + }; + + this.gamepad = gamepad; + this.pose = new Pose(); + this.buttonMap = buttonMaps[gamepad.hand]; + this.axisMap = [{ name: "joyX", axisId: 0 }, { name: "joyY", axisId: 1 }]; + this.path = devicePaths[gamepad.hand]; + this.selector = `#player-${gamepad.hand}-controller`; + } + write(frame) { + if (!this.gamepad.connected) return; + + this.gamepad.buttons.forEach((button, i) => { + const buttonPath = paths.device.gamepad(this.gamepad.index).button(i); + frame[buttonPath.pressed] = !!button.pressed; + frame[buttonPath.touched] = !!button.touched; + frame[buttonPath.value] = button.value; + }); + this.gamepad.axes.forEach((axis, i) => { + frame[paths.device.gamepad(this.gamepad.index).axis(i)] = axis; + }); + + this.buttonMap.forEach(button => { + const outpath = this.path.button(button.name); + frame[outpath.pressed] = !!frame[paths.device.gamepad(this.gamepad.index).button(button.buttonId).pressed]; + frame[outpath.touched] = !!frame[paths.device.gamepad(this.gamepad.index).button(button.buttonId).touched]; + frame[outpath.value] = frame[paths.device.gamepad(this.gamepad.index).button(button.buttonId).value]; + }); + this.axisMap.forEach(axis => { + frame[this.path.axis(axis.name)] = frame[paths.device.gamepad(this.gamepad.index).axis(axis.axisId)]; + }); + + const rayObject = document.querySelector(this.selector).object3D; + rayObject.updateMatrixWorld(); + this.rayObjectRotation.setFromRotationMatrix(rayObject.matrixWorld); + this.pose.position.setFromMatrixPosition(rayObject.matrixWorld); + this.pose.direction.set(0, 0, -1).applyQuaternion(this.rayObjectRotation); + this.pose.fromOriginAndDirection(this.pose.position, this.pose.direction); + frame[this.path.pose] = this.pose; + } +} diff --git a/src/systems/userinput/devices/touchscreen/assignments.js b/src/systems/userinput/devices/touchscreen/assignments.js new file mode 100644 index 0000000000000000000000000000000000000000..8fa17f18365cf0db594e25bf3bdd8489008d34f8 --- /dev/null +++ b/src/systems/userinput/devices/touchscreen/assignments.js @@ -0,0 +1,44 @@ +export function touchIsAssigned(touch, assignments) { + return ( + assignments.find(assignment => { + return assignment.touch.identifier === touch.identifier; + }) !== undefined + ); +} + +export function jobIsAssigned(job, assignments) { + return ( + assignments.find(assignment => { + return assignment.job === job; + }) !== undefined + ); +} + +export function assign(touch, job, assignments) { + if (touchIsAssigned(touch, assignments) || jobIsAssigned(job, assignments)) { + console.error("cannot reassign touches or jobs. unassign first"); + return undefined; + } + const assignment = { job, touch }; + assignments.push(assignment); + return assignment; +} + +export function unassign(touch, job, assignments) { + function match(assignment) { + return assignment.touch.identifier === touch.identifier && assignment.job === job; + } + assignments.splice(assignments.findIndex(match), 1); +} + +export function findByJob(job, assignments) { + return assignments.find(assignment => { + return assignment.job === job; + }); +} + +export function findByTouch(touch, assignments) { + return assignments.find(assignment => { + return assignment.touch.identifier === touch.identifier; + }); +} diff --git a/src/systems/userinput/devices/vive-controller.js b/src/systems/userinput/devices/vive-controller.js new file mode 100644 index 0000000000000000000000000000000000000000..4430c29aa5fd70aceaa3695f867e68a714488b90 --- /dev/null +++ b/src/systems/userinput/devices/vive-controller.js @@ -0,0 +1,74 @@ +import { paths } from "../paths"; +import { Pose } from "../pose"; + +export class ViveControllerDevice { + constructor(gamepad) { + this.rayObjectRotation = new THREE.Quaternion(); + + // wake the gamepad api up. otherwise it does not report touch controllers. + // in chrome it still won't unless you enter vr. + navigator.getVRDisplays(); + + this.buttonMap = [ + { name: "touchpad", buttonId: 0 }, + { name: "trigger", buttonId: 1 }, + { name: "grip", buttonId: 2 }, + { name: "top", buttonId: 3 } + ]; + + this.gamepad = gamepad; + this.pose = new Pose(); + this.axisMap = [{ name: "joyX", axisId: 0 }, { name: "joyY", axisId: 1 }]; + this.path = paths.device.vive[gamepad.hand || "right"]; + if (!gamepad.hand) { + console.warn("gamepad detected without hand specified"); + } else { + this.selector = `[super-hands]#player-${gamepad.hand}-controller`; + } + } + write(frame) { + if (!this.gamepad.connected) return; + + this.gamepad.buttons.forEach((button, i) => { + const buttonPath = paths.device.gamepad(this.gamepad.index).button(i); + frame[buttonPath.pressed] = !!button.pressed; + frame[buttonPath.touched] = !!button.touched; + frame[buttonPath.value] = button.value; + }); + this.gamepad.axes.forEach((axis, i) => { + frame[paths.device.gamepad(this.gamepad.index).axis(i)] = axis; + }); + + this.buttonMap.forEach(button => { + const outpath = this.path.button(button.name); + frame[outpath.pressed] = !!frame[paths.device.gamepad(this.gamepad.index).button(button.buttonId).pressed]; + frame[outpath.touched] = !!frame[paths.device.gamepad(this.gamepad.index).button(button.buttonId).touched]; + frame[outpath.value] = frame[paths.device.gamepad(this.gamepad.index).button(button.buttonId).value]; + }); + this.axisMap.forEach(axis => { + frame[this.path.axis(axis.name)] = frame[paths.device.gamepad(this.gamepad.index).axis(axis.axisId)]; + }); + + if (!this.selector) { + if (this.gamepad.hand) { + this.path = paths.device.vive[this.gamepad.hand]; + this.selector = `[super-hands]#player-${this.gamepad.hand}-controller`; + console.warn("gamepad hand eventually specified"); + } else { + return; + } + } + const el = document.querySelector(this.selector); + if (el.components["tracked-controls"].controller !== this.gamepad) { + el.components["tracked-controls"].controller = this.gamepad; + el.setAttribute("tracked-controls", "controller", this.gamepad.index); + } + const rayObject = el.object3D; + rayObject.updateMatrixWorld(); + this.rayObjectRotation.setFromRotationMatrix(rayObject.matrixWorld); + this.pose.position.setFromMatrixPosition(rayObject.matrixWorld); + this.pose.direction.set(0, 0, -1).applyQuaternion(this.rayObjectRotation); + this.pose.fromOriginAndDirection(this.pose.position, this.pose.direction); + frame[this.path.pose] = this.pose; + } +} diff --git a/src/systems/userinput/devices/xbox-controller.js b/src/systems/userinput/devices/xbox-controller.js new file mode 100644 index 0000000000000000000000000000000000000000..82de323a8f434cf7b782fcbf15966e9e66d353fe --- /dev/null +++ b/src/systems/userinput/devices/xbox-controller.js @@ -0,0 +1,55 @@ +import { paths } from "../paths"; + +export class XboxControllerDevice { + constructor(gamepad) { + this.gamepad = gamepad; + this.buttonMap = [ + { name: "a", buttonId: 0 }, + { name: "b", buttonId: 1 }, + { name: "x", buttonId: 2 }, + { name: "y", buttonId: 3 }, + { name: "leftBumper", buttonId: 4 }, + { name: "rightBumper", buttonId: 5 }, + { name: "leftTrigger", buttonId: 6 }, + { name: "rightTrigger", buttonId: 7 }, + { name: "back", buttonId: 8 }, + { name: "start", buttonId: 9 }, + { name: "leftJoystick", buttonId: 10 }, + { name: "rightJoystick", buttonId: 11 }, + { name: "dpadUp", buttonId: 12 }, + { name: "dpadDown", buttonId: 13 }, + { name: "dpadLeft", buttonId: 14 }, + { name: "dpadRight", buttonId: 15 } + ]; + this.axisMap = [ + { name: "leftJoystickHorizontal", axisId: 0 }, + { name: "leftJoystickVertical", axisId: 1 }, + { name: "rightJoystickHorizontal", axisId: 2 }, + { name: "rightJoystickVertical", axisId: 3 } + ]; + } + + write(frame) { + if (this.gamepad.connected) { + this.gamepad.buttons.forEach((button, i) => { + const buttonPath = paths.device.gamepad(this.gamepad.index).button(i); + frame[buttonPath.pressed] = !!button.pressed; + frame[buttonPath.touched] = !!button.touched; + frame[buttonPath.value] = button.value; + }); + this.gamepad.axes.forEach((axis, i) => { + frame[paths.device.gamepad(this.gamepad.index).axis(i)] = axis; + }); + + this.buttonMap.forEach(button => { + const outpath = paths.device.xbox.button(button.name); + frame[outpath.pressed] = !!frame[paths.device.gamepad(this.gamepad.index).button(button.buttonId).pressed]; + frame[outpath.touched] = !!frame[paths.device.gamepad(this.gamepad.index).button(button.buttonId).touched]; + frame[outpath.value] = frame[paths.device.gamepad(this.gamepad.index).button(button.buttonId).value]; + }); + this.axisMap.forEach(axis => { + frame[paths.device.xbox.axis(axis.name)] = frame[paths.device.gamepad(this.gamepad.index).axis(axis.axisId)]; + }); + } + } +} diff --git a/src/systems/userinput/paths.js b/src/systems/userinput/paths.js new file mode 100644 index 0000000000000000000000000000000000000000..28a8fa9111c436eaeaeff1408e236143dfffc134 --- /dev/null +++ b/src/systems/userinput/paths.js @@ -0,0 +1,180 @@ +export const paths = {}; +paths.noop = "/noop"; +paths.actions = {}; +paths.actions.log = "/actions/log"; +paths.actions.toggleScreenShare = "/actions/toggleScreenShare"; +paths.actions.snapRotateLeft = "/actions/snapRotateLeft"; +paths.actions.snapRotateRight = "/actions/snapRotateRight"; +paths.actions.logDebugFrame = "/actions/logDebugFrame"; +paths.actions.cameraDelta = "/actions/cameraDelta"; +paths.actions.characterAcceleration = "/actions/characterAcceleration"; +paths.actions.boost = "/actions/boost"; +paths.actions.startGazeTeleport = "/actions/startTeleport"; +paths.actions.stopGazeTeleport = "/actions/stopTeleport"; +paths.actions.translate = {}; +paths.actions.translate.forward = "/actions/translate/forward"; +paths.actions.translate.backward = "/actions/translate/backward"; +paths.actions.translate.up = "/actions/translate/up"; +paths.actions.translate.down = "/actions/translate/down"; +paths.actions.spawnPen = "/actions/spawnPen"; +paths.actions.cursor = {}; +paths.actions.cursor.pose = "/actions/cursorPose"; +paths.actions.cursor.grab = "/actions/cursorGrab"; +paths.actions.cursor.drop = "/actions/cursorDrop"; +paths.actions.cursor.modDelta = "/actions/cursorModDelta"; +paths.actions.cursor.startDrawing = "/actions/cursorStartDrawing"; +paths.actions.cursor.stopDrawing = "/actions/cursorStopDrawing"; +paths.actions.cursor.penNextColor = "/actions/cursorPenNextColor"; +paths.actions.cursor.penPrevColor = "/actions/cursorPenPrevColor"; +paths.actions.cursor.scalePenTip = "/actions/cursorScalePenTip"; +paths.actions.cursor.scaleGrabbedGrabbable = "/actions/cursorScaleGrabbedGrabbable"; +paths.actions.cursor.takeSnapshot = "/actions/cursorTakeSnapshot"; +paths.actions.rightHand = {}; +paths.actions.rightHand.pose = "/actions/rightHandPose"; +paths.actions.rightHand.grab = "/actions/rightHandGrab"; +paths.actions.rightHand.drop = "/actions/rightHandDrop"; +paths.actions.rightHand.modDelta = "/actions/rightHandModDelta"; +paths.actions.rightHand.startDrawing = "/actions/rightHandStartDrawing"; +paths.actions.rightHand.stopDrawing = "/actions/rightHandStopDrawing"; +paths.actions.rightHand.penNextColor = "/actions/rightHandPenNextColor"; +paths.actions.rightHand.penPrevColor = "/actions/rightHandPenPrevColor"; +paths.actions.rightHand.scalePenTip = "/actions/rightHandScalePenTip"; +paths.actions.rightHand.startTeleport = "/actions/rightHandStartTeleport"; +paths.actions.rightHand.stopTeleport = "/actions/rightHandStopTeleport"; +paths.actions.rightHand.takeSnapshot = "/actions/rightHandTakeSnapshot"; +paths.actions.leftHand = {}; +paths.actions.leftHand.pose = "/actions/leftHandPose"; +paths.actions.leftHand.grab = "/actions/leftHandGrab"; +paths.actions.leftHand.drop = "/actions/leftHandDrop"; +paths.actions.leftHand.modDelta = "/actions/leftHandModDelta"; +paths.actions.leftHand.startDrawing = "/actions/leftHandStartDrawing"; +paths.actions.leftHand.stopDrawing = "/actions/leftHandStopDrawing"; +paths.actions.leftHand.penNextColor = "/actions/leftHandPenNextColor"; +paths.actions.leftHand.penPrevColor = "/actions/leftHandPenPrevColor"; +paths.actions.leftHand.scalePenTip = "/actions/leftHandScalePenTip"; +paths.actions.leftHand.startTeleport = "/actions/leftHandStartTeleport"; +paths.actions.leftHand.stopTeleport = "/actions/leftHandStopTeleport"; +paths.actions.leftHand.takeSnapshot = "/actions/leftHandTakeSnapshot"; + +paths.device = {}; +paths.device.mouse = {}; +paths.device.mouse.coords = "/device/mouse/coords"; +paths.device.mouse.movementXY = "/device/mouse/movementXY"; +paths.device.mouse.buttonLeft = "/device/mouse/buttonLeft"; +paths.device.mouse.buttonRight = "/device/mouse/buttonRight"; +paths.device.mouse.wheel = "/device/mouse/wheel"; +paths.device.smartMouse = {}; +paths.device.smartMouse.cursorPose = "/device/smartMouse/cursorPose"; +paths.device.smartMouse.cameraDelta = "/device/smartMouse/cameraDelta"; +paths.device.touchscreen = {}; +paths.device.touchscreen.cursorPose = "/device/touchscreen/cursorPose"; +paths.device.touchscreen.cameraDelta = "/device/touchscreen/cameraDelta"; +paths.device.touchscreen.pinchDelta = "/device/touchscreen/pinchDelta"; +paths.device.touchscreen.initialPinchDistance = "/device/touchscreen/initialPinchDistance"; +paths.device.touchscreen.currentPinchDistance = "/device/touchscreen/currentPinchDistance"; +paths.device.touchscreen.isTouchingGrabbable = "/device/touchscreen/isTouchingGrabbable"; +paths.device.hud = {}; +paths.device.hud.penButton = "/device/hud/penButton"; + +paths.device.keyboard = { + key: key => { + return `/device/keyboard/${key.toLowerCase()}`; + } +}; + +paths.device.gamepad = gamepadIndex => ({ + button: buttonIndex => ({ + pressed: `/device/gamepad/${gamepadIndex}/button/${buttonIndex}/pressed`, + touched: `/device/gamepad/${gamepadIndex}/button/${buttonIndex}/touched`, + value: `/device/gamepad/${gamepadIndex}/button/${buttonIndex}/value` + }), + axis: axisIndex => `/device/gamepad/${gamepadIndex}/axis/${axisIndex}` +}); + +const xbox = "/device/xbox/"; +paths.device.xbox = { + button: buttonName => ({ + pressed: `${xbox}button/${buttonName}/pressed`, + touched: `${xbox}button/${buttonName}/touched`, + value: `${xbox}button/${buttonName}/value` + }), + axis: axisName => { + return `${xbox}axis/${axisName}`; + } +}; + +const oculusgo = "/device/oculusgo/"; +paths.device.oculusgo = { + button: buttonName => ({ + pressed: `${oculusgo}button/${buttonName}/pressed`, + touched: `${oculusgo}button/${buttonName}/touched`, + value: `${oculusgo}button/${buttonName}/value` + }), + axis: axisName => { + return `${oculusgo}axis/${axisName}`; + }, + pose: `${oculusgo}pose` +}; + +const daydream = "/device/daydream/"; +paths.device.daydream = { + button: buttonName => ({ + pressed: `${daydream}button/${buttonName}/pressed`, + touched: `${daydream}button/${buttonName}/touched`, + value: `${daydream}button/${buttonName}/value` + }), + axis: axisName => { + return `${daydream}axis/${axisName}`; + }, + pose: `${daydream}pose` +}; + +const rightOculusTouch = "/device/rightOculusTouch/"; +paths.device.rightOculusTouch = { + button: buttonName => ({ + pressed: `${rightOculusTouch}button/${buttonName}/pressed`, + touched: `${rightOculusTouch}button/${buttonName}/touched`, + value: `${rightOculusTouch}button/${buttonName}/value` + }), + axis: axisName => { + return `${rightOculusTouch}axis/${axisName}`; + }, + pose: `${rightOculusTouch}pose` +}; + +const leftOculusTouch = "/device/leftOculusTouch/"; +paths.device.leftOculusTouch = { + button: buttonName => ({ + pressed: `${leftOculusTouch}button/${buttonName}/pressed`, + touched: `${leftOculusTouch}button/${buttonName}/touched`, + value: `${leftOculusTouch}button/${buttonName}/value` + }), + axis: axisName => { + return `${leftOculusTouch}axis/${axisName}`; + }, + pose: `${leftOculusTouch}pose` +}; + +paths.device.vive = {}; +paths.device.vive.left = { + button: buttonName => ({ + pressed: `/device/vive/left/button/${buttonName}/pressed`, + touched: `/device/vive/left/button/${buttonName}/touched`, + value: `/device/vive/left/button/${buttonName}/value` + }), + axis: axisName => { + return `/device/vive/left/axis/${axisName}`; + }, + pose: `/device/vive/left/pose` +}; +paths.device.vive.right = { + button: buttonName => ({ + pressed: `/device/vive/right/button/${buttonName}/pressed`, + touched: `/device/vive/right/button/${buttonName}/touched`, + value: `/device/vive/right/button/${buttonName}/value` + }), + axis: axisName => { + return `/device/vive/right/axis/${axisName}`; + }, + pose: `/device/vive/right/pose` +}; diff --git a/src/systems/userinput/pose.js b/src/systems/userinput/pose.js new file mode 100644 index 0000000000000000000000000000000000000000..3a75b14203adc7c6acc23a7c3efca8c1001eaa58 --- /dev/null +++ b/src/systems/userinput/pose.js @@ -0,0 +1,24 @@ +const forward = new THREE.Vector3(0, 0, -1); +export function Pose() { + return { + position: new THREE.Vector3(), + direction: new THREE.Vector3(), + orientation: new THREE.Quaternion(), + fromOriginAndDirection: function(origin, direction) { + this.position = origin; + this.direction = direction; + this.orientation = this.orientation.setFromUnitVectors(forward, direction); + return this; + }, + fromCameraProjection: function(camera, normalizedX, normalizedY) { + this.position.setFromMatrixPosition(camera.matrixWorld); + this.direction + .set(normalizedX, normalizedY, 0.5) + .unproject(camera) + .sub(this.position) + .normalize(); + this.fromOriginAndDirection(this.position, this.direction); + return this; + } + }; +} diff --git a/src/systems/userinput/resolve-action-sets.js b/src/systems/userinput/resolve-action-sets.js new file mode 100644 index 0000000000000000000000000000000000000000..557fedba9c580a039489b0742098e2f54148f73e --- /dev/null +++ b/src/systems/userinput/resolve-action-sets.js @@ -0,0 +1,169 @@ +import { sets } from "./sets"; + +export function updateActionSetsBasedOnSuperhands() { + const rightHandState = document.querySelector("#player-right-controller").components["super-hands"].state; + const leftHandState = document.querySelector("#player-left-controller").components["super-hands"].state; + const cursorHand = document.querySelector("#cursor").components["super-hands"].state; + const leftTeleporter = document.querySelector("#player-left-controller").components["teleport-controls"]; + const rightTeleporter = document.querySelector("#player-right-controller").components["teleport-controls"]; + const cursorController = document.querySelector("#cursor-controller").components["cursor-controller"]; + + const leftHandHoveringOnInteractable = + !leftTeleporter.active && + leftHandState.has("hover-start") && + leftHandState.get("hover-start").matches(".interactable, .interactable *"); + const leftHandHoveringOnPen = + !leftTeleporter.active && + leftHandState.has("hover-start") && + leftHandState.get("hover-start").matches(".pen, .pen *"); + const leftHandHoveringOnCamera = + !leftTeleporter.active && + leftHandState.has("hover-start") && + leftHandState.get("hover-start").matches(".icamera, .icamera *"); + const leftHandHoldingInteractable = + !leftTeleporter.active && + leftHandState.has("grab-start") && + leftHandState.get("grab-start").matches(".interactable, .interactable *"); + const leftHandHoldingPen = + !leftTeleporter.active && + leftHandState.has("grab-start") && + leftHandState.get("grab-start").matches(".pen, .pen *"); + const leftHandHoldingCamera = + !leftTeleporter.active && + leftHandState.has("grab-start") && + leftHandState.get("grab-start").matches(".icamera, .icamera *"); + const leftHandHovering = !leftTeleporter.active && leftHandState.has("hover-start"); + const leftHandHoveringOnNothing = !leftHandHovering && !leftHandState.has("grab-start"); + const leftHandTeleporting = leftTeleporter.active; + + const cursorGrabbing = cursorHand.has("grab-start"); + + const rightHandTeleporting = rightTeleporter.active; + const rightHandHovering = !rightHandTeleporting && !cursorGrabbing && rightHandState.has("hover-start"); + const rightHandGrabbing = !rightHandTeleporting && !cursorGrabbing && rightHandState.has("grab-start"); + + const rightHandHoveringOnInteractable = + !rightHandTeleporting && + !cursorGrabbing && + rightHandState.has("hover-start") && + rightHandState.get("hover-start").matches(".interactable, .interactable *"); + const rightHandHoveringOnPen = + !rightHandTeleporting && + !cursorGrabbing && + rightHandState.has("hover-start") && + rightHandState.get("hover-start").matches(".pen, .pen *"); + const rightHandHoveringOnCamera = + !rightTeleporter.active && + !cursorGrabbing && + rightHandState.has("hover-start") && + rightHandState.get("hover-start").matches(".icamera, .icamera *"); + const rightHandHoldingInteractable = + !rightHandTeleporting && + !cursorGrabbing && + rightHandState.has("grab-start") && + rightHandState.get("grab-start").matches(".interactable, .interactable *"); + const rightHandHoldingPen = + !rightHandTeleporting && + !cursorGrabbing && + rightHandState.has("grab-start") && + rightHandState.get("grab-start").matches(".pen, .pen *"); + const rightHandHoldingCamera = + !rightTeleporter.active && + !cursorGrabbing && + rightHandState.has("grab-start") && + rightHandState.get("grab-start").matches(".icamera, .icamera *"); + + const rightHandHoveringOnNothing = + !rightHandTeleporting && + !rightHandHovering && + !cursorHand.has("hover-start") && + !cursorGrabbing && + !rightHandState.has("grab-start"); + + // Cursor + if (rightHandTeleporting || rightHandHovering || rightHandGrabbing) { + cursorController.disable(); + } else { + cursorController.enable(); + } + + const cursorHoveringOnInteractable = + cursorController.enabled && + !rightHandTeleporting && + !rightHandHovering && + !rightHandGrabbing && + cursorHand.has("hover-start") && + cursorHand.get("hover-start").matches(".interactable, .interactable *"); + const cursorHoveringOnCamera = + cursorController.enabled && + !rightTeleporter.active && + !rightHandHovering && + !rightHandGrabbing && + (cursorHand.has("hover-start") && cursorHand.get("hover-start").matches(".icamera, .icamera *")); + const cursorHoveringOnUI = + cursorController.enabled && + !rightHandTeleporting && + !rightHandHovering && + !rightHandGrabbing && + (cursorHand.has("hover-start") && cursorHand.get("hover-start").matches(".ui, .ui *")); + const cursorHoveringOnPen = + cursorController.enabled && + !rightHandTeleporting && + !rightHandHovering && + !rightHandGrabbing && + cursorHand.has("hover-start") && + cursorHand.get("hover-start").matches(".pen, .pen *"); + const cursorHoldingInteractable = + cursorController.enabled && + !rightHandTeleporting && + cursorHand.has("grab-start") && + cursorHand.get("grab-start").matches(".interactable, .interactable *"); + const cursorHoldingPen = + cursorController.enabled && + !rightHandTeleporting && + cursorHand.has("grab-start") && + cursorHand.get("grab-start").matches(".pen, .pen *"); + + const cursorHoldingCamera = + cursorController.enabled && + !rightTeleporter.active && + cursorHand.has("grab-start") && + cursorHand.get("grab-start").matches(".icamera, .icamera *"); + + const cursorHoveringOnNothing = + cursorController.enabled && + !rightHandTeleporting && + !rightHandHovering && + !rightHandGrabbing && + !cursorHand.has("hover-start") && + !cursorHand.has("grab-start") && + !cursorHoveringOnUI; + + const userinput = AFRAME.scenes[0].systems.userinput; + userinput.toggleActive(sets.leftHandHoveringOnInteractable, leftHandHoveringOnInteractable); + userinput.toggleActive(sets.leftHandHoveringOnPen, leftHandHoveringOnPen); + userinput.toggleActive(sets.leftHandHoveringOnCamera, leftHandHoveringOnCamera); + userinput.toggleActive(sets.leftHandHoveringOnNothing, leftHandHoveringOnNothing); + userinput.toggleActive(sets.leftHandHoldingPen, leftHandHoldingPen); + userinput.toggleActive(sets.leftHandHoldingInteractable, leftHandHoldingInteractable); + userinput.toggleActive(sets.leftHandHoldingCamera, leftHandHoldingCamera); + userinput.toggleActive(sets.leftHandTeleporting, leftHandTeleporting); + + userinput.toggleActive(sets.rightHandHoveringOnInteractable, rightHandHoveringOnInteractable); + userinput.toggleActive(sets.rightHandHoveringOnPen, rightHandHoveringOnPen); + userinput.toggleActive(sets.rightHandHoveringOnNothing, rightHandHoveringOnNothing); + userinput.toggleActive(sets.rightHandHoveringOnCamera, rightHandHoveringOnCamera); + userinput.toggleActive(sets.rightHandHoldingPen, rightHandHoldingPen); + userinput.toggleActive(sets.rightHandHoldingInteractable, rightHandHoldingInteractable); + userinput.toggleActive(sets.rightHandTeleporting, rightHandTeleporting); + userinput.toggleActive(sets.rightHandHoldingCamera, rightHandHoldingCamera); + + userinput.toggleActive(sets.cursorHoveringOnPen, cursorHoveringOnPen); + userinput.toggleActive(sets.cursorHoveringOnCamera, cursorHoveringOnCamera); + userinput.toggleActive(sets.cursorHoveringOnInteractable, cursorHoveringOnInteractable); + userinput.toggleActive(sets.cursorHoveringOnUI, cursorHoveringOnUI); + userinput.toggleActive(sets.cursorHoveringOnNothing, cursorHoveringOnNothing); + userinput.toggleActive(sets.cursorHoldingPen, cursorHoldingPen); + userinput.toggleActive(sets.cursorHoldingCamera, cursorHoldingCamera); + userinput.toggleActive(sets.cursorHoldingInteractable, cursorHoldingInteractable); +} diff --git a/src/systems/userinput/sets.js b/src/systems/userinput/sets.js new file mode 100644 index 0000000000000000000000000000000000000000..c764b47cfaddd0a0ea635bfff267031a033ee1c5 --- /dev/null +++ b/src/systems/userinput/sets.js @@ -0,0 +1,26 @@ +export const sets = {}; +sets.global = "global"; +sets.cursorHoveringOnPen = "cursorHoveringOnPen"; +sets.cursorHoveringOnCamera = "cursorHoveringOnCamera"; +sets.cursorHoveringOnInteractable = "cursorHoveringOnInteractable"; +sets.cursorHoveringOnUI = "cursorHoveringOnUI"; +sets.cursorHoveringOnNothing = "cursorHoveringOnNothing"; +sets.cursorHoldingPen = "cursorHoldingPen"; +sets.cursorHoldingCamera = "cursorHoldingCamera"; +sets.cursorHoldingInteractable = "cursorHoldingInteractable"; +sets.rightHandTeleporting = "rightHandTeleporting"; +sets.rightHandHoveringOnPen = "rightHandHoveringOnPen"; +sets.rightHandHoveringOnCamera = "rightHandHoveringOnCamera"; +sets.rightHandHoveringOnInteractable = "rightHandHoveringOnInteractable"; +sets.rightHandHoveringOnNothing = "rightHandHoveringOnNothing"; +sets.rightHandHoldingPen = "rightHandHoldingPen"; +sets.rightHandHoldingCamera = "rightHandHoldingCamera"; +sets.rightHandHoldingInteractable = "rightHandHoldingInteractable"; +sets.leftHandTeleporting = "leftHandTeleporting"; +sets.leftHandHoveringOnPen = "leftHandHoveringOnPen"; +sets.leftHandHoveringOnCamera = "leftHandHoveringOnCamera"; +sets.leftHandHoveringOnInteractable = "leftHandHoveringOnInteractable"; +sets.leftHandHoldingPen = "leftHandHoldingPen"; +sets.leftHandHoldingCamera = "leftHandHoldingCamera"; +sets.leftHandHoldingInteractable = "leftHandHoldingInteractable"; +sets.leftHandHoveringOnNothing = "leftHandHoveringOnNothing"; diff --git a/src/systems/userinput/userinput.js b/src/systems/userinput/userinput.js new file mode 100644 index 0000000000000000000000000000000000000000..de08bea49ed594bf270f4d419436cdf40b9442cd --- /dev/null +++ b/src/systems/userinput/userinput.js @@ -0,0 +1,213 @@ +import { paths } from "./paths"; +import { sets } from "./sets"; + +import { MouseDevice } from "./devices/mouse"; +import { KeyboardDevice } from "./devices/keyboard"; +import { HudDevice } from "./devices/hud"; +import { XboxControllerDevice } from "./devices/xbox-controller"; +import { OculusGoControllerDevice } from "./devices/oculus-go-controller"; +import { OculusTouchControllerDevice } from "./devices/oculus-touch-controller"; +import { DaydreamControllerDevice } from "./devices/daydream-controller"; +import { ViveControllerDevice } from "./devices/vive-controller"; + +import { AppAwareMouseDevice } from "./devices/app-aware-mouse"; +import { AppAwareTouchscreenDevice } from "./devices/app-aware-touchscreen"; + +import { keyboardMouseUserBindings } from "./bindings/keyboard-mouse-user"; +import { touchscreenUserBindings } from "./bindings/touchscreen-user"; +import { keyboardDebuggingBindings } from "./bindings/keyboard-debugging"; +import { oculusGoUserBindings } from "./bindings/oculus-go-user"; +import { oculusTouchUserBindings } from "./bindings/oculus-touch-user"; +import { viveUserBindings } from "./bindings/vive-user"; +import { xboxControllerUserBindings } from "./bindings/xbox-controller-user"; +import { daydreamUserBindings } from "./bindings/daydream-user"; + +import { updateActionSetsBasedOnSuperhands } from "./resolve-action-sets"; +import { GamepadDevice } from "./devices/gamepad"; +import { gamepadBindings } from "./bindings/generic-gamepad"; + +function prioritizeBindings(registeredMappings, activeSets, prioritizedBindings, activeBindings) { + registeredMappings.forEach(mapping => { + Object.keys(mapping).forEach(setName => { + if (!activeSets.has(setName)) return; + + const set = mapping[setName] || []; + set.forEach(binding => { + const { root, priority } = binding; + if (!root || !priority) { + // priority info not found : activate + activeBindings.add(binding); + return; + } + if (!prioritizedBindings.has(root)) { + // root seen for the first time : activate + prioritizedBindings.set(root, binding); + activeBindings.add(binding); + return; + } + const prevPriority = prioritizedBindings.get(root).priority; + if (priority < prevPriority) { + // priority too low : deactivate + // pause debugger here to see when bindings never get activated, + // because their priority is not the highest for this root. + return; + } + if (priority > prevPriority) { + // priority is higher : deactivate binding stored for this root + // pause debugger here to step thru bindings getting overwritten this frame + activeBindings.delete(prioritizedBindings.get(root)); + prioritizedBindings.delete(root); + prioritizedBindings.set(root, binding); + activeBindings.add(binding); + return; + } + if (prevPriority === priority) { + // (?) perhaps we could allow this somehow + console.error("equal priorities on same root", binding, prioritizedBindings.get(root)); + return; + } + }); + }); + }); +} + +AFRAME.registerSystem("userinput", { + readFrameValueAtPath(path) { + return this.frame && this.frame[path]; + }, + + toggleActive(set, value) { + this.pendingSetChanges.push({ set, value }); + }, + + activate(set) { + this.pendingSetChanges.push({ set, value: true }); + }, + + deactivate(set) { + this.pendingSetChanges.push({ set, value: false }); + }, + + init() { + this.frame = {}; + + this.activeSets = new Set([sets.global]); + this.pendingSetChanges = []; + this.activeDevices = new Set([new MouseDevice(), new AppAwareMouseDevice(), new KeyboardDevice(), new HudDevice()]); + + this.registeredMappings = new Set([keyboardDebuggingBindings]); + this.xformStates = new Map(); + + this.gamepads = []; + + const appAwareTouchscreenDevice = new AppAwareTouchscreenDevice(); + const updateBindingsForVRMode = () => { + const inVRMode = this.el.sceneEl.is("vr-mode"); + if (AFRAME.utils.device.isMobile()) { + if (inVRMode) { + this.activeDevices.delete(appAwareTouchscreenDevice); + this.registeredMappings.delete(touchscreenUserBindings); + } else { + this.activeDevices.add(appAwareTouchscreenDevice); + this.registeredMappings.add(touchscreenUserBindings); + } + } else { + if (inVRMode) { + this.registeredMappings.delete(keyboardMouseUserBindings); + } else { + this.registeredMappings.add(keyboardMouseUserBindings); + } + } + }; + this.el.sceneEl.addEventListener("enter-vr", updateBindingsForVRMode); + this.el.sceneEl.addEventListener("exit-vr", updateBindingsForVRMode); + updateBindingsForVRMode(); + + window.addEventListener( + "gamepadconnected", + e => { + console.log(e.gamepad); + let gamepadDevice; + if (e.gamepad.id === "OpenVR Gamepad") { + for (let i = 0; i < this.activeDevices.length; i++) { + const activeDevice = this.activeDevices[i]; + if (activeDevice.gamepad && activeDevice.gamepad === e.gamepad) { + console.warn("ignoring gamepad"); + return; // multiple connect events without a disconnect event + } + } + if (this.activeDevices) gamepadDevice = new ViveControllerDevice(e.gamepad); + this.registeredMappings.add(viveUserBindings); + } else if (e.gamepad.id.startsWith("Oculus Touch")) { + gamepadDevice = new OculusTouchControllerDevice(e.gamepad); + this.registeredMappings.add(oculusTouchUserBindings); + } else if (e.gamepad.id === "Oculus Go Controller") { + gamepadDevice = new OculusGoControllerDevice(e.gamepad); + this.registeredMappings.add(oculusGoUserBindings); + } else if (e.gamepad.id === "Daydream Controller") { + gamepadDevice = new DaydreamControllerDevice(e.gamepad); + this.registeredMappings.add(daydreamUserBindings); + } else if (e.gamepad.id.includes("Xbox")) { + gamepadDevice = new XboxControllerDevice(e.gamepad); + this.registeredMappings.add(xboxControllerUserBindings); + } else { + gamepadDevice = new GamepadDevice(e.gamepad); + this.registeredMappings.add(gamepadBindings); + } + this.activeDevices.add(gamepadDevice); + this.gamepads[e.gamepad.index] = gamepadDevice; + }, + false + ); + window.addEventListener( + "gamepaddisconnected", + e => { + if (this.gamepads[e.gamepad.index]) { + this.activeDevices.delete(this.gamepads[e.gamepad.index]); + delete this.gamepads[e.gamepad.index]; + } + }, + false + ); + }, + + tick() { + updateActionSetsBasedOnSuperhands(); + + this.pendingSetChanges.forEach(({ set, value }) => { + this.activeSets[value ? "add" : "delete"](set); + }); + this.pendingSetChanges = []; + + const frame = {}; + this.activeDevices.forEach(device => { + device.write(frame); + }); + + const priorityMap = new Map(); + const activeBindings = new Set(); + prioritizeBindings(this.registeredMappings, this.activeSets, priorityMap, activeBindings); + activeBindings.forEach(binding => { + const bindingExistedLastFrame = this.activeBindings && this.activeBindings.has(binding); + if (!bindingExistedLastFrame) { + this.xformStates.delete(binding); + } + + const { src, dest, xform } = binding; + const newState = xform(frame, src, dest, this.xformStates.get(binding)); + if (newState !== undefined) { + this.xformStates.set(binding, newState); + } + }); + + this.frame = frame; + this.activeBindings = activeBindings; + if (frame[paths.actions.logDebugFrame] || frame[paths.actions.log]) { + console.log("frame", this.frame); + console.log("sets", this.activeSets); + console.log("bindings", this.activeBindings); + console.log("devices", this.activeDevices); + console.log("xformStates", this.xformStates); + } + } +}); diff --git a/src/systems/userinput/userinput.org b/src/systems/userinput/userinput.org new file mode 100644 index 0000000000000000000000000000000000000000..d98d502f22374967ba4bbc6bddbe3792b9cdc513 --- /dev/null +++ b/src/systems/userinput/userinput.org @@ -0,0 +1,87 @@ +* The userinput system + +The userinput system is a module that manages mappings from device state changes to app state changes. + +** Overview +The userinput system happens to be an ~aframe~ ~system~; its ~tick~ is called once a frame within the ~aframe~ ~scene~'s ~tick~. When the userinput system ~tick~ happens, it is responsible for creating a map called the frame. The keys of the frame are called "paths". The values stored in the frame can be any type, but are usually one of: bool, number, vec2, vec3, vec4, pose. On each tick, each connected ~device~ writes "raw" input values to known "device paths" within the frame. Configuration units called ~bindings~ are then applied to transform "raw" input values to app-specific "actions". The userinput system exposes the state of a given ~action~ in the current frame via ~readFrameValueAtPath~. The ~bindings~ that are applied to transform input to "actions" must be ~available~, ~active~, and ~prioritized~. + +1) A ~binding~ is made ~available~ when the userinput system detects a change to the user's device configuration that matches certain criteria. A touchscreen user only has ~availableBindings~ related to touchscreen input. A mouse-and-keyboard only has ~availableBindings~ related to mouse-and-keyboard input. An oculus/vive user has ~bindings~ related to mouse, keyboard, and oculus/vive controllers. + +2) A ~binding~ is ~active~ if it is ~available~ and it belongs to an ~action set~ that is ~active~ this frame. The application is responsible for activating and deactivating ~action sets~ when appropriate. For example, when the user's avatar grabs a pen in its right hand, an action set called "rightHandHoldingPen" is activated. Though it depends on the way bindings have been configured, this will likely activate bindings responsible for writing to the following "actions": "rightHandStartDrawing", "rightHandStopDrawing", "rightHandPenNextColor", "rightHandPenPrevColor", "rightHandScalePenTip", "rightHandDrop". + +3) A ~binding~ is ~prioritized~ if, among all of the currently ~available~ and ~active~ bindings, it is defined with the highest "priority" value for the given "root". Within the oculus and vive bindings, for example, the binding that says "stop drawing from the pen in the right hand when the trigger is released" in the "rightHandHoldingPen" action set is defined with a higher priority than (and with the same root as) the binding that says "drop a grabbable from the avatar's right hand when the trigger is released" in the "rightHandHoldingInteractable" action set. Thus, you do not drop the pen in your right hand when the trigger is released, and we define a third binding for how to perform this action when the thing in your right hand happens to be a pen. + +** Terms and Conventions +*** path +A path is used as a key when writing or querying the state a user input frame. Paths happen to be strings for now. We conceptually separate "action" paths, which are used by app code to read user input from a frame, from "device" paths, which specify where device state is recorded. Bindings may also "vars" paths to store intermediate results of xforms. + +#+BEGIN_EXAMPLE js +paths.actions.rightHand.grab = "/actions/rightHandGrab"; +paths.actions.rightHand.drop = "/actions/rightHandDrop"; +paths.actions.rightHand.startDrawing = "/actions/rightHandStartDrawing"; +paths.actions.rightHand.stopDrawing = "/actions/rightHandStopDrawing"; +#+END_EXAMPLE + +#+BEGIN_EXAMPLE js +paths.device.mouse.coords = "/device/mouse/coords"; +paths.device.mouse.movementXY = "/device/mouse/movementXY"; +paths.device.mouse.buttonLeft = "/device/mouse/buttonLeft"; +paths.device.mouse.buttonRight = "/device/mouse/buttonRight"; +paths.device.keyboard = { + key: key => { + return ~/device/keyboard/${key.toLowerCase()}~; + } +}; +#+END_EXAMPLE + +#+BEGIN_EXAMPLE js +const lJoyScaled = "/vars/oculustouch/left/joy/scaled"; +#+END_EXAMPLE + +*** action +A path used by app code when reading a user input frame. + +#+BEGIN_EXAMPLE js +const userinput = AFRAME.scenes[0].systems.userinput; +if (userinput.readFrameValueAtPath("/actions/rightHandGrab")) { + this.startInteraction(); +} +#+END_EXAMPLE + +The value in the frame can be of any type, but we have tried to keep it to simple types like bool, number, vec2, vec3, and pose. + +#+BEGIN_EXAMPLE js +const userinput = AFRAME.scenes[0].systems.userinput; +const acceleration = userinput.readFrameValueAtPath("/actions/characterAcceleration"); +this.updateVelocity( this.velocity, acceleration || zero ); +this.move( this.velocity ); +#+END_EXAMPLE +*** frame +A key-value store created each time the userinput system ticks. The userinput system writes a new frame by processing input from devices and transforming them by the set of ~available~, ~active~, and ~prioritized~ bindings. + +*** device +A device is almost always mapped one-to-one with a device as we think about it in the real world. In the case of mouse, touchscreen, and keyboard input, the browser emits events that are captured into a queue to be processed in order once each frame. An exception to handling device input through the userinput system is the case of interacting with browser API's that require a user-gesture, like the pointer lock API. In this case, the browser prevents us from engaging pointer lock except in a short-running event listener to a user-gesture. +Most devices can write their input state to the frame without depending on any other app state. An exception are the "app aware" touchscreen and mouse devices, which decide whether a raycast sent out from the in-game camera through the projected touch/click point lands on an interactable object or not, and what should be done in the case that it does. +*** binding +A binding is an association of the form: + +#+BEGIN_EXAMPLE js + { + src: { xform_key_a : path, + xform_key_b : path }, + dest: { xform_key_1 : path, + xform_key_2 : path }, + xform: some_function, // f(frame, src, dest, prevState) -> newState + root: key_to_resolve_binding_conflicts, + priority: numerical_priority_of_this_binding // higher priority overrides lower priority bindings + }, +#+END_EXAMPLE + +Bindings are organized into sets, and written with active specific device combinations in mind. +*** xforms +Each binding specifies a ~xform~ (transformation) function that reads values in the frame at the paths provided by ~src~ and writes to the values in the frame at the paths in ~dest~. These would otherwise be pure functions but they happen to write to the frame and return mutated state so as to avoid creating more garbage each frame. (We have not yet done a performance pass, so making smarter choices about memory allocation and avoiding garbage has been postponed.) +These ought to be treated as user-customizable, although we are likely the only ones to do this customization for some time. +*** set +Sets are app state that correspond to sets of capabilities we expect to activate and deactivate all at once on behalf of the user. +*** priority and root +When bindings can be written such that multiple actions could be triggered by the device input, we express our desire to apply one over another via the ~binding~ s ~root~ s and ~priority~ s. When active bindings share the same root, the userinput system only applies active bindings with highest priority values. This mechanism allows us to craft context-sensitive interaction mechanics on devices with limited input, like the oculus go remote. diff --git a/src/utils/action-event-handler.js b/src/utils/action-event-handler.js deleted file mode 100644 index 472dd813a75ce4bbd11036057550d51105b9be2b..0000000000000000000000000000000000000000 --- a/src/utils/action-event-handler.js +++ /dev/null @@ -1,259 +0,0 @@ -const VERTICAL_SCROLL_TIMEOUT = 150; -const HORIZONTAL_SCROLL_TIMEOUT = 150; -const SCROLL_THRESHOLD = 0.05; -const SCROLL_MODIFIER = 0.1; - -export default class ActionEventHandler { - constructor(scene, cursor) { - this.scene = scene; - this.cursor = cursor; - this.cursorHand = this.cursor.data.cursor.components["super-hands"]; - this.isCursorInteracting = false; - this.isTeleporting = false; - this.handThatAlsoDrivesCursor = null; - this.hovered = false; - - this.gotPrimaryDown = false; - - this.onPrimaryDown = this.onPrimaryDown.bind(this); - this.onPrimaryUp = this.onPrimaryUp.bind(this); - this.onSecondaryDown = this.onSecondaryDown.bind(this); - this.onSecondaryUp = this.onSecondaryUp.bind(this); - this.onPrimaryGrab = this.onPrimaryGrab.bind(this); - this.onPrimaryRelease = this.onPrimaryRelease.bind(this); - this.onSecondaryGrab = this.onSecondaryGrab.bind(this); - this.onSecondaryRelease = this.onSecondaryRelease.bind(this); - this.onCardboardButtonDown = this.onCardboardButtonDown.bind(this); - this.onCardboardButtonUp = this.onCardboardButtonUp.bind(this); - this.onScrollMove = this.onScrollMove.bind(this); - this.addEventListeners(); - - this.lastVerticalScrollTime = 0; - this.lastHorizontalScrollTime = 0; - } - - addEventListeners() { - this.scene.addEventListener("action_primary_down", this.onPrimaryDown); - this.scene.addEventListener("action_primary_up", this.onPrimaryUp); - this.scene.addEventListener("action_secondary_down", this.onSecondaryDown); - this.scene.addEventListener("action_secondary_up", this.onSecondaryUp); - this.scene.addEventListener("primary_action_grab", this.onPrimaryGrab); - this.scene.addEventListener("primary_action_release", this.onPrimaryRelease); - this.scene.addEventListener("secondary_action_grab", this.onSecondaryGrab); - this.scene.addEventListener("secondary_action_release", this.onSecondaryRelease); - this.scene.addEventListener("scroll_move", this.onScrollMove); - this.scene.addEventListener("cardboardbuttondown", this.onCardboardButtonDown); // TODO: These should be actions - this.scene.addEventListener("cardboardbuttonup", this.onCardboardButtonUp); - } - - tearDown() { - this.scene.removeEventListener("action_primary_down", this.onPrimaryDown); - this.scene.removeEventListener("action_primary_up", this.onPrimaryUp); - this.scene.removeEventListener("action_secondary_down", this.onSecondaryDown); - this.scene.removeEventListener("action_secondary_up", this.onSecondaryUp); - this.scene.removeEventListener("primary_action_grab", this.onPrimaryGrab); - this.scene.removeEventListener("primary_action_release", this.onPrimaryRelease); - this.scene.removeEventListener("secondary_action_grab", this.onSecondaryGrab); - this.scene.removeEventListener("secondary_action_release", this.onSecondaryRelease); - this.scene.removeEventListener("scroll_move", this.onScrollMove); - this.scene.removeEventListener("cardboardbuttondown", this.onCardboardButtonDown); - this.scene.removeEventListener("cardboardbuttonup", this.onCardboardButtonUp); - } - - onScrollMove(e) { - let scrollY = e.detail.axis[1] * SCROLL_MODIFIER; - scrollY = Math.abs(scrollY) > SCROLL_THRESHOLD ? scrollY : 0; - const changed = this.cursor.changeDistanceMod(-scrollY); //TODO: don't negate this for certain controllers - - let scrollX = e.detail.axis[0] * SCROLL_MODIFIER; - scrollX = Math.abs(scrollX) > SCROLL_THRESHOLD ? scrollX : 0; - - this.isCursorInteracting = this.cursor.isInteracting(); - - if ( - Math.abs(scrollY) > 0 && - (this.lastVerticalScrollTime === 0 || this.lastVerticalScrollTime + VERTICAL_SCROLL_TIMEOUT < Date.now()) - ) { - if (!changed && this.isCursorInteracting && this.isHandThatAlsoDrivesCursor(e.target)) { - this.cursorHand.el.emit(scrollY < 0 ? "scroll_up" : "scroll_down"); - this.cursorHand.el.emit("vertical_scroll_release"); - } else { - e.target.emit(scrollY < 0 ? "scroll_up" : "scroll_down"); - e.target.emit("vertical_scroll_release"); - } - this.lastVerticalScrollTime = Date.now(); - } - - if ( - Math.abs(scrollX) > 0 && - (this.lastHorizontalScrollTime === 0 || this.lastHorizontalScrollTime + HORIZONTAL_SCROLL_TIMEOUT < Date.now()) - ) { - if (this.isCursorInteracting && this.isHandThatAlsoDrivesCursor(e.target)) { - this.cursorHand.el.emit(scrollX < 0 ? "scroll_left" : "scroll_right"); - this.cursorHand.el.emit("horizontal_scroll_release"); - } else { - e.target.emit(scrollX < 0 ? "scroll_left" : "scroll_right"); - e.target.emit("horizontal_scroll_release"); - } - this.lastHorizontalScrollTime = Date.now(); - } - } - - setHandThatAlsoDrivesCursor(handThatAlsoDrivesCursor) { - this.handThatAlsoDrivesCursor = handThatAlsoDrivesCursor; - } - - isToggle(el) { - return el && el.matches(".toggle, .toggle *"); - } - - isHandThatAlsoDrivesCursor(el) { - return this.handThatAlsoDrivesCursor === el; - } - - onGrab(e, event) { - event = event || e.type; - const superHand = e.target.components["super-hands"]; - const isCursorHand = this.isHandThatAlsoDrivesCursor(e.target); - this.isCursorInteracting = this.cursor.isInteracting(); - if (isCursorHand && !this.isCursorInteracting) { - if (superHand.state.has("hover-start") || superHand.state.get("grab-start")) { - e.target.emit(event); - } else { - this.isCursorInteracting = this.cursor.startInteraction(); - } - } else if (isCursorHand && this.isCursorInteracting) { - this.cursorHand.el.emit(event); - } else { - e.target.emit(event); - } - } - - onRelease(e, event) { - event = event || e.type; - const isCursorHand = this.isHandThatAlsoDrivesCursor(e.target); - if (this.isCursorInteracting && isCursorHand) { - //need to check both grab-start and hover-start in the case that the spawner is being grabbed this frame - if (this.isToggle(this.cursorHand.state.get("grab-start") || this.cursorHand.state.get("hover-start"))) { - this.cursorHand.el.emit(event); - this.isCursorInteracting = this.cursor.isInteracting(); - } else { - this.isCursorInteracting = false; - this.cursor.endInteraction(); - } - } else { - e.target.emit(event); - } - } - - onPrimaryGrab(e) { - this.onGrab(e, "primary_hand_grab"); - } - - onPrimaryRelease(e) { - this.onRelease(e, "primary_hand_release"); - } - - onSecondaryGrab(e) { - this.onGrab(e, "secondary_hand_grab"); - } - - onSecondaryRelease(e) { - this.onRelease(e, "secondary_hand_release"); - } - - onDown(e, event) { - this.onGrab(e, event); - - if ( - this.isHandThatAlsoDrivesCursor(e.target) && - !this.isCursorInteracting && - !this.cursorHand.state.get("grab-start") - ) { - this.cursor.setCursorVisibility(false); - const button = e.target.components["teleport-controls"].data.button; - e.target.emit(button + "down"); - this.isTeleporting = true; - } - } - - onUp(e, event) { - if (this.isTeleporting && this.isHandThatAlsoDrivesCursor(e.target)) { - const superHand = e.target.components["super-hands"]; - this.cursor.setCursorVisibility(!superHand.state.has("hover-start")); - const button = e.target.components["teleport-controls"].data.button; - e.target.emit(button + "up"); - this.isTeleporting = false; - } else { - this.onRelease(e, event); - } - } - - onPrimaryDown(e) { - if (!this.gotPrimaryDown) { - this.onDown(e, "primary_hand_grab"); - this.gotPrimaryDown = true; - } - } - - onPrimaryUp(e) { - if (this.gotPrimaryDown) { - this.onUp(e, "primary_hand_release"); - } else if (this.isToggle(this.cursorHand.state.get("grab-start") || this.cursorHand.state.get("hover-start"))) { - this.onUp(e, "secondary_hand_release"); - } - this.gotPrimaryDown = false; - } - - onSecondaryDown(e) { - this.onDown(e, "secondary_hand_grab"); - } - - onSecondaryUp(e) { - this.onUp(e, "secondary_hand_release"); - } - - onCardboardButtonDown(e) { - this.isCursorInteracting = this.cursor.startInteraction(); - if (this.isCursorInteracting) { - return; - } - - this.cursor.setCursorVisibility(false); - - const gazeTeleport = e.target.querySelector("#gaze-teleport"); - const button = gazeTeleport.components["teleport-controls"].data.button; - gazeTeleport.emit(button + "down"); - this.isTeleporting = true; - } - - onCardboardButtonUp(e) { - if (this.isCursorInteracting) { - this.isCursorInteracting = false; - this.cursor.endInteraction(); - return; - } - - this.cursor.setCursorVisibility(true); - - const gazeTeleport = e.target.querySelector("#gaze-teleport"); - const button = gazeTeleport.components["teleport-controls"].data.button; - gazeTeleport.emit(button + "up"); - this.isTeleporting = false; - } - - manageCursorEnabled() { - const handState = this.handThatAlsoDrivesCursor.components["super-hands"].state; - const handHoveredThisFrame = !this.hovered && handState.has("hover-start") && !this.isCursorInteracting; - const handStoppedHoveringThisFrame = - this.hovered === true && !handState.has("hover-start") && !handState.has("grab-start"); - if (handHoveredThisFrame) { - this.hovered = true; - this.cursor.disable(); - } else if (handStoppedHoveringThisFrame) { - this.hovered = false; - this.cursor.enable(); - this.cursor.setCursorVisibility(!this.isTeleporting); - } - } -} diff --git a/src/utils/gearvr-mouse-events-handler.js b/src/utils/gearvr-mouse-events-handler.js deleted file mode 100644 index e26495b96a53980f98c58c6351267927a7853fb4..0000000000000000000000000000000000000000 --- a/src/utils/gearvr-mouse-events-handler.js +++ /dev/null @@ -1,50 +0,0 @@ -export default class GearVRMouseEventsHandler { - constructor(cursor, gazeTeleporter) { - this.cursor = cursor; - this.gazeTeleporter = gazeTeleporter; - this.isMouseDownHandledByCursor = false; - this.isMouseDownHandledByGazeTeleporter = false; - - this.onMouseDown = this.onMouseDown.bind(this); - this.onMouseUp = this.onMouseUp.bind(this); - this.addEventListeners(); - } - - addEventListeners() { - document.addEventListener("mousedown", this.onMouseDown); - document.addEventListener("mouseup", this.onMouseUp); - } - - tearDown() { - document.removeEventListener("mousedown", this.onMouseDown); - document.removeEventListener("mouseup", this.onMouseUp); - } - - onMouseDown() { - this.isMouseDownHandledByCursor = this.cursor.startInteraction(); - if (this.isMouseDownHandledByCursor) { - return; - } - - this.cursor.setCursorVisibility(false); - - const button = this.gazeTeleporter.data.button; - this.gazeTeleporter.el.emit(button + "down"); - this.isMouseDownHandledByGazeTeleporter = true; - } - - onMouseUp() { - if (this.isMouseDownHandledByCursor) { - this.cursor.endInteraction(); - this.isMouseDownHandledByCursor = false; - } - - this.cursor.setCursorVisibility(true); - - if (this.isMouseDownHandledByGazeTeleporter) { - const button = this.gazeTeleporter.data.button; - this.gazeTeleporter.el.emit(button + "up"); - this.isMouseDownHandledByGazeTeleporter = false; - } - } -} diff --git a/src/utils/mouse-events-handler.js b/src/utils/mouse-events-handler.js deleted file mode 100644 index 510cb0cd25dd0ca792c294a73e544c60beb1b084..0000000000000000000000000000000000000000 --- a/src/utils/mouse-events-handler.js +++ /dev/null @@ -1,170 +0,0 @@ -// TODO: Make look speed adjustable by the user -const HORIZONTAL_LOOK_SPEED = 0.1; -const VERTICAL_LOOK_SPEED = 0.06; -const VERTICAL_SCROLL_TIMEOUT = 50; -const HORIZONTAL_SCROLL_TIMEOUT = 50; - -export default class MouseEventsHandler { - constructor(cursor, cameraController) { - this.cursor = cursor; - const cursorController = this.cursor.el.getAttribute("cursor-controller"); - this.superHand = cursorController.cursor.components["super-hands"]; - this.cameraController = cameraController; - this.isLeftButtonDown = false; - this.isLeftButtonHandledByCursor = false; - this.isPointerLocked = false; - - this.onMouseDown = this.onMouseDown.bind(this); - this.onMouseMove = this.onMouseMove.bind(this); - this.onMouseUp = this.onMouseUp.bind(this); - this.onMouseWheel = this.onMouseWheel.bind(this); - - this.addEventListeners(); - - this.lastVerticalScrollTime = 0; - this.lastHorizontalScrollTime = 0; - } - - tearDown() { - document.removeEventListener("mousedown", this.onMouseDown); - document.removeEventListener("mousemove", this.onMouseMove); - document.removeEventListener("mouseup", this.onMouseUp); - document.removeEventListener("wheel", this.onMouseWheel); - document.removeEventListener("contextmenu", this.onContextMenu); - } - - setInverseMouseLook(invert) { - this.invertMouseLook = invert; - } - - addEventListeners() { - document.addEventListener("mousedown", this.onMouseDown); - document.addEventListener("mousemove", this.onMouseMove); - document.addEventListener("mouseup", this.onMouseUp); - document.addEventListener("wheel", this.onMouseWheel); - document.addEventListener("contextmenu", this.onContextMenu); - } - - onContextMenu(e) { - e.preventDefault(); - } - - onMouseDown(e) { - switch (e.button) { - case 0: //left button - this.onLeftButtonDown(); - break; - case 1: //middle/scroll button - //TODO: rotation? scaling? - break; - case 2: //right button - this.onRightButtonDown(); - break; - } - } - - onLeftButtonDown() { - this.isLeftButtonDown = true; - if (this.isToggle(this.superHand.state.get("grab-start"))) { - this.superHand.el.emit("secondary-cursor-grab"); - } - this.isLeftButtonHandledByCursor = this.cursor.startInteraction(); - } - - onRightButtonDown() { - this.isLeftButtonHandledByCursor = this.cursor.isInteracting(); - if (!this.isLeftButtonHandledByCursor) { - if (this.isPointerLocked) { - document.exitPointerLock(); - this.isPointerLocked = false; - } else { - document.body.requestPointerLock(); - this.isPointerLocked = true; - } - } - } - - onMouseWheel(e) { - let changed = true; - if (!e.altKey && !e.shiftKey) { - changed = this.cursor.changeDistanceMod(this.getScrollMod(e.deltaY, e.deltaMode)); - } - - if ( - (!changed || e.shiftKey) && - (this.lastVerticalScrollTime === 0 || this.lastVerticalScrollTime + VERTICAL_SCROLL_TIMEOUT < Date.now()) - ) { - this.superHand.el.emit(e.deltaY > 0 ? "scroll_up" : "scroll_down"); - this.superHand.el.emit("vertical_scroll_release"); - this.lastVerticalScrollTime = Date.now(); - } - - const delta = e.altKey ? e.deltaY : e.deltaX; - if ( - Math.abs(delta) > 0 && - (this.lastHorizontalScrollTime === 0 || this.lastHorizontalScrollTime + HORIZONTAL_SCROLL_TIMEOUT < Date.now()) - ) { - this.superHand.el.emit(delta < 0 ? "scroll_left" : "scroll_right"); - this.superHand.el.emit("horizontal_scroll_release"); - this.lastHorizontalScrollTime = Date.now(); - } - - if (e.altKey) e.preventDefault(); //prevent forward/back on firefox - } - - getScrollMod(delta, deltaMode) { - switch (deltaMode) { - case WheelEvent.DOM_DELTA_PIXEL: - return delta / 500; - case WheelEvent.DOM_DELTA_LINE: - return delta / 10; - case WheelEvent.DOM_DELTA_PAGE: - return delta / 2; - } - } - - onMouseMove(e) { - const shouldLook = - this.isPointerLocked || - (!this.superHand.state.get("grab-start") && this.isLeftButtonDown && !this.isLeftButtonHandledByCursor); - if (shouldLook) { - this.look(e); - } - - this.cursor.moveCursor((e.clientX / window.innerWidth) * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1); - } - - onMouseUp(e) { - switch (e.button) { - case 0: //left button - if (this.isToggle(this.superHand.state.get("grab-start"))) { - this.superHand.el.emit("secondary-cursor-release"); - } else { - this.endInteraction(); - } - this.isLeftButtonDown = false; - break; - case 1: //middle/scroll button - break; - case 2: //right button - this.endInteraction(); - break; - } - } - - endInteraction() { - this.cursor.endInteraction(); - this.isLeftButtonHandledByCursor = false; - } - - isToggle(el) { - return el && el.matches(".toggle, .toggle *"); - } - - look(e) { - const sign = this.invertMouseLook ? 1 : -1; - const deltaPitch = e.movementY * VERTICAL_LOOK_SPEED * sign; - const deltaYaw = e.movementX * HORIZONTAL_LOOK_SPEED * sign; - this.cameraController.look(deltaPitch, deltaYaw); - } -} diff --git a/src/utils/touch-events-handler.js b/src/utils/touch-events-handler.js deleted file mode 100644 index 0cc392886162fbdfd1a6f32364f6c98275a2c4ec..0000000000000000000000000000000000000000 --- a/src/utils/touch-events-handler.js +++ /dev/null @@ -1,169 +0,0 @@ -const VIRTUAL_JOYSTICK_HEIGHT = 0.8; -const HORIZONTAL_LOOK_SPEED = 0.35; -const VERTICAL_LOOK_SPEED = 0.18; - -export default class TouchEventsHandler { - constructor(cursor, cameraController, pinchEmitter) { - this.cursor = cursor; - this.cameraController = cameraController; - this.pinchEmitter = pinchEmitter; - this.touches = []; - this.touchReservedForCursor = null; - this.touchesReservedForPinch = []; - this.touchReservedForLookControls = null; - this.needsPinch = false; - this.pinchTouchId1 = -1; - this.pinchTouchId2 = -1; - - this.handleTouchStart = this.handleTouchStart.bind(this); - this.singleTouchStart = this.singleTouchStart.bind(this); - this.handleTouchMove = this.handleTouchMove.bind(this); - this.singleTouchMove = this.singleTouchMove.bind(this); - this.handleTouchEnd = this.handleTouchEnd.bind(this); - this.singleTouchEnd = this.singleTouchEnd.bind(this); - - this.addEventListeners(); - } - - addEventListeners() { - document.addEventListener("touchstart", this.handleTouchStart); - document.addEventListener("touchmove", this.handleTouchMove); - document.addEventListener("touchend", this.handleTouchEnd); - document.addEventListener("touchcancel", this.handleTouchEnd); - } - - tearDown() { - document.removeEventListener("touchstart", this.handleTouchStart); - document.removeEventListener("touchmove", this.handleTouchMove); - document.removeEventListener("touchend", this.handleTouchEnd); - document.removeEventListener("touchcancel", this.handleTouchEnd); - } - - handleTouchStart(e) { - for (let i = 0; i < e.changedTouches.length; i++) { - this.singleTouchStart(e.changedTouches[i]); - } - } - - singleTouchStart(touch) { - if (touch.clientY / window.innerHeight >= VIRTUAL_JOYSTICK_HEIGHT) { - return; - } - if (!this.touchReservedForCursor) { - const targetX = (touch.clientX / window.innerWidth) * 2 - 1; - const targetY = -(touch.clientY / window.innerHeight) * 2 + 1; - this.cursor.moveCursor(targetX, targetY); - this.cursor.forceCursorUpdate(); - if (this.cursor.startInteraction()) { - this.touchReservedForCursor = touch; - } - } - this.touches.push(touch); - } - - handleTouchMove(e) { - for (let i = 0; i < e.touches.length; i++) { - this.singleTouchMove(e.touches[i]); - } - if (this.needsPinch) { - this.pinch(); - this.needsPinch = false; - } - } - - singleTouchMove(touch) { - if (this.touchReservedForCursor && touch.identifier === this.touchReservedForCursor.identifier) { - const targetX = (touch.clientX / window.innerWidth) * 2 - 1; - const targetY = -(touch.clientY / window.innerHeight) * 2 + 1; - this.cursor.moveCursor(targetX, targetY); - return; - } - if (touch.clientY / window.innerHeight >= VIRTUAL_JOYSTICK_HEIGHT) return; - if (!this.touches.some(t => touch.identifier === t.identifier)) { - return; - } - - let pinchIndex = this.touchesReservedForPinch.findIndex(t => touch.identifier === t.identifier); - if (pinchIndex !== -1) { - this.touchesReservedForPinch[pinchIndex] = touch; - } else if (this.touchesReservedForPinch.length < 2) { - this.touchesReservedForPinch.push(touch); - pinchIndex = this.touchesReservedForPinch.length - 1; - } - if (this.touchesReservedForPinch.length == 2 && pinchIndex !== -1) { - if (this.touchReservedForLookControls && touch.identifier === this.touchReservedForLookControls.identifier) { - this.touchReservedForLookControls = null; - } - this.needsPinch = true; - return; - } - - if (!this.touchReservedForLookControls) { - this.touchReservedForLookControls = touch; - } - if (touch.identifier === this.touchReservedForLookControls.identifier) { - if (!this.touchReservedForCursor) { - this.cursor.moveCursor( - (touch.clientX / window.innerWidth) * 2 - 1, - -(touch.clientY / window.innerHeight) * 2 + 1 - ); - } - this.look(this.touchReservedForLookControls, touch); - this.touchReservedForLookControls = touch; - return; - } - } - - pinch() { - const t1 = this.touchesReservedForPinch[0]; - const t2 = this.touchesReservedForPinch[1]; - const isNewPinch = t1.identifier !== this.pinchTouchId1 || t2.identifier !== this.pinchTouchId2; - const pinchDistance = TouchEventsHandler.distance(t1.clientX, t1.clientY, t2.clientX, t2.clientY); - this.pinchEmitter.emit("pinch", { isNewPinch: isNewPinch, distance: pinchDistance }); - this.pinchTouchId1 = t1.identifier; - this.pinchTouchId2 = t2.identifier; - } - - look(prevTouch, touch) { - const deltaPitch = (touch.clientY - prevTouch.clientY) * VERTICAL_LOOK_SPEED; - const deltaYaw = (touch.clientX - prevTouch.clientX) * HORIZONTAL_LOOK_SPEED; - this.cameraController.look(deltaPitch, deltaYaw); - } - - handleTouchEnd(e) { - for (let i = 0; i < e.changedTouches.length; i++) { - this.singleTouchEnd(e.changedTouches[i]); - } - } - - singleTouchEnd(touch) { - const touchIndex = this.touches.findIndex(t => touch.identifier === t.identifier); - if (touchIndex === -1) { - return; - } - this.touches.splice(touchIndex, 1); - - if (this.touchReservedForCursor && touch.identifier === this.touchReservedForCursor.identifier) { - this.cursor.endInteraction(touch); - this.touchReservedForCursor = null; - return; - } - - const pinchIndex = this.touchesReservedForPinch.findIndex(t => touch.identifier === t.identifier); - if (pinchIndex !== -1) { - this.touchesReservedForPinch.splice(pinchIndex, 1); - this.pinchTouchId1 = -1; - this.pinchTouchId2 = -1; - } - - if (this.touchReservedForLookControls && touch.identifier === this.touchReservedForLookControls.identifier) { - this.touchReservedForLookControls = null; - } - } - - static distance = (x1, y1, x2, y2) => { - const x = x1 - x2; - const y = y1 - y2; - return Math.sqrt(x * x + y * y); - }; -}