aframe-mouse-manipulation
Version:
Enables the user to move objects in 3D space using a mouse on desktop.
86 lines (73 loc) • 44.3 kB
JavaScript
/*
* ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
/******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ "./index.js":
/*!******************!*\
!*** ./index.js ***!
\******************/
/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
eval("if (!AFRAME.components['object-parent']) __webpack_require__(/*! aframe-object-parent */ \"./node_modules/aframe-object-parent/index.js\")\r\nif (!AFRAME.components['cursor-tracker']) __webpack_require__(/*! aframe-cursor-tracker */ \"./node_modules/aframe-cursor-tracker/index.js\")\r\nif (!AFRAME.components['label']) __webpack_require__(/*! aframe-label */ \"./node_modules/aframe-label/index.js\")\r\n\r\n// Add this to the same entity as the cursor component.\r\nAFRAME.registerComponent('mouse-manipulation', {\r\n\r\n schema: {\r\n debug: {type: 'boolean', default: false},\r\n showHints: {type: 'boolean', default: true},\r\n grabEvents: {type: 'boolean', default: false},\r\n grabEvent: {type: 'string', default: 'mouseGrab'},\r\n releaseEvent: {type: 'string', default: 'mouseRelease'},\r\n controlMethod: {type: 'string', default: 'parent', oneOf: ['parent', 'transform']}\r\n },\r\n\r\n events: {\r\n mousedown: function(evt) { this.mouseDown(evt) }, \r\n mouseup: function(evt) { this.mouseUp(evt) },\r\n mouseenter: function(evt) { this.mouseEnter(evt) }, \r\n mouseleave: function(evt) { this.mouseLeave(evt) }\r\n },\r\n \r\n init() {\r\n // cursor must have an ID so that we can reference it when attaching an object-parent\r\n if (!this.el.id) {\r\n this.el.id = \"cursor-\" + THREE.MathUtils.generateUUID()\r\n }\r\n \r\n // This is a rate per second. We scale distance by this factor per second.\r\n // Take a root of this to get a scaling factor.\r\n this.moveSpeed = 3;\r\n \r\n // variable to track any grabbed element, and active controls state.\r\n this.grabbedEl = null;\r\n this.activeContactPoint = null;\r\n this.activeControlMethod = '';\r\n\r\n // We create 2 children beneath the camera\r\n // - cursorTracker. This is set up to match the orientation of the cursor\r\n // (which does not match the camera, when using rayOrigin: mouse)\r\n this.camera = document.querySelector('[camera]')\r\n this.cursorTracker = document.createElement('a-entity')\r\n this.cursorTracker.setAttribute('cursor-tracker', `cursor:#${this.el.id}`)\r\n this.camera.appendChild(this.cursorTracker)\r\n\r\n // A container for any entity that can be grabbed.\r\n // For mouse controls, this is a child of the cursor tracker.\r\n // (this helps with move in/out, rotation etc. of grabbed entity)\r\n this.cursorContactPoint = document.createElement('a-entity')\r\n this.cursorContactPoint.setAttribute('id', `${this.el.id}-cursor-contact-point`)\r\n if (this.data.debug) {\r\n this.cursorContactPoint.setAttribute('geometry', \"primitive:box; height:0.1; width: 0.1; depth:0.1\")\r\n this.cursorContactPoint.setAttribute('material', \"color: blue\")\r\n }\r\n \r\n this.cursorTracker.appendChild(this.cursorContactPoint)\r\n\r\n // A container for any entity that can be grabbed.\r\n // This is a child of the camera, for controls where the object\r\n // shouldn't follow the mouse pointer, e.g. rotation.\r\n // (this helps with move in/out, rotation etc. of grabbed entity)\r\n this.cameraContactPoint = document.createElement('a-entity')\r\n this.cameraContactPoint.setAttribute('id', `${this.el.id}-camera-contact-point`)\r\n if (this.data.debug) {\r\n this.cameraContactPoint.setAttribute('geometry', \"primitive:box; height:0.1; width: 0.1; depth:0.1\")\r\n this.cameraContactPoint.setAttribute('material', \"color: red\")\r\n }\r\n this.camera.appendChild(this.cameraContactPoint)\r\n\r\n // for working\r\n this.vector1 = new THREE.Vector3()\r\n this.vector2 = new THREE.Vector3()\r\n\r\n this.windowMouseUp = this.windowMouseUp.bind(this)\r\n this.windowMouseDown = this.windowMouseDown.bind(this)\r\n\r\n window.addEventListener('mouseup', this.windowMouseUp);\r\n window.addEventListener('mousedown', this.windowMouseDown);\r\n window.addEventListener('contextmenu', event => event.preventDefault());\r\n\r\n // state of mouse buttons\r\n this.lbDown = false\r\n this.mbDown = false\r\n this.rbDown = false\r\n\r\n // adjustments to control ratio of mouse pixels to radians for otations.\r\n this.radiansPerMousePixel = 0.01\r\n\r\n this.lastContactPointTransform = new THREE.Object3D()\r\n },\r\n\r\n update: function() {\r\n \r\n if (this.data.showHints) {\r\n this.createHints()\r\n }\r\n else {\r\n this.removeHints()\r\n }\r\n \r\n },\r\n\r\n remove() {\r\n\r\n this.removeHints()\r\n\r\n this.cursorTracker.parentNode.removeChild(this.cursorTracker)\r\n this.cameraContactPoint.parentNode.removeChild(this.cameraContactPoint)\r\n\r\n window.removeEventListener('mouseup', this.windowMouseUp);\r\n window.removeEventListener('mousedown', this.windowMouseDown);\r\n },\r\n\r\n windowMouseDown(evt) {\r\n\r\n // we are looking for the original mouseEvent, which has details of buttons pressed\r\n // And we need to have registered an element to be grabbed.\r\n if (evt.buttons === undefined) return;\r\n if (!this.grabbedEl) return;\r\n\r\n if (this.data.debug) console.log(\"MouseDown:\", evt)\r\n\r\n this.recordMouseButtonsState(evt)\r\n this.updateMouseControls()\r\n this.updateHints()\r\n\r\n if (this.lbDown) {\r\n // left button is pressed (either just pressed or already down) \r\n // - grab to cursor contact point\r\n this.grabElToContactPoint(this.cursorContactPoint,\r\n `#${this.el.id}-cursor-contact-point`)\r\n\r\n }\r\n else {\r\n // right or middle button - grab to camera contact point\r\n this.grabElToContactPoint(this.cameraContactPoint,\r\n `#${this.el.id}-camera-contact-point`)\r\n }\r\n },\r\n\r\n windowMouseUp(evt) {\r\n // we are looking for the original mouseEvent, which has details of buttons pressed\r\n // And we need to have a grabbed element.\r\n if (evt.buttons === undefined) return;\r\n if (!this.grabbedEl) return;\r\n\r\n if (this.data.debug) console.log(\"MouseUp:\", evt)\r\n\r\n this.recordMouseButtonsState(evt)\r\n this.updateMouseControls()\r\n this.updateHints()\r\n \r\n // Reparenting\r\n if (this.lbDown) {\r\n // left button is still down\r\n // leave attached to cursor contact point.\r\n if (this.data.debug) console.log(\"Left button still down\")\r\n }\r\n else if (evt.buttons === 0){\r\n // no button now pressed.\r\n if (this.data.debug) console.log(\"No buttons down - releasing\")\r\n this.releaseEl()\r\n }\r\n else if (evt.button === 0) {\r\n if (this.data.debug) console.log(\"Left button released, middle or right still down\")\r\n // left button released, but right or middle button still down \r\n // - grab to camera contact point\r\n this.grabElToContactPoint(this.cameraContactPoint,\r\n `#${this.el.id}-camera-contact-point`)\r\n }\r\n },\r\n\r\n recordMouseButtonsState(evt) {\r\n this.lbDown = (evt.buttons & 1)\r\n this.mbDown = (evt.buttons & 4)\r\n this.rbDown = (evt.buttons & 2)\r\n\r\n if (this.data.debug) {\r\n console.log(\"this.lbDown:\", this.lbDown)\r\n console.log(\"this.rbDown:\", this.rbDown)\r\n console.log(\"this.mbDown:\", this.mbDown)\r\n }\r\n },\r\n\r\n updateMouseControls() {\r\n\r\n if (this.lbDown) {\r\n this.cursorContactPoint.setAttribute(\"mouse-dolly\", \"\")\r\n }\r\n else if (this.rbDown){\r\n this.cursorContactPoint.removeAttribute(\"mouse-dolly\")\r\n this.cameraContactPoint.setAttribute(\"mouse-dolly\", \"\")\r\n\r\n }\r\n else {\r\n this.cursorContactPoint.removeAttribute(\"mouse-dolly\")\r\n this.cameraContactPoint.removeAttribute(\"mouse-dolly\")\r\n }\r\n\r\n if (this.rbDown) {\r\n this.cameraContactPoint.setAttribute(\"mouse-pitch-yaw\", \"\")\r\n }\r\n else {\r\n this.cameraContactPoint.removeAttribute(\"mouse-pitch-yaw\")\r\n }\r\n\r\n if (this.mbDown) {\r\n this.cameraContactPoint.setAttribute(\"mouse-roll\", \"\")\r\n }\r\n else {\r\n this.cameraContactPoint.removeAttribute(\"mouse-roll\")\r\n }\r\n },\r\n\r\n createHints() {\r\n\r\n if (!this.data.showHints) return\r\n\r\n this.hints = document.createElement('a-entity')\r\n this.hints.setAttribute(\"label\", \"overwrite: true; forceDesktopMode: true\") \r\n this.hints.setAttribute(\"mouse-manipulation-hints\", \"\")\r\n this.el.appendChild(this.hints)\r\n\r\n this.updateHints()\r\n },\r\n\r\n updateHints() {\r\n\r\n if (!this.data.showHints) return\r\n\r\n const show = (x) => { this.hints.setAttribute(\"mouse-manipulation-hints\", \"view\", x) }\r\n \r\n if (this.lbDown) {\r\n show(\"left\")\r\n }\r\n else if (this.rbDown) {\r\n show(\"right\")\r\n }\r\n else if (this.mbDown) {\r\n show(\"middle\")\r\n }\r\n else if (this.hoverEl) {\r\n show(\"hover\")\r\n }\r\n else {\r\n show(\"none\")\r\n }\r\n },\r\n\r\n removeHints() {\r\n\r\n if (this.hints) {\r\n this.hints.parentNode.removeChild(this.hints)\r\n this.hints = null\r\n }\r\n },\r\n\r\n // records details of grabbed object, but actual grabbing is deferred to be handled on MouseEvent\r\n // based on detail about which button is pressed (not avalable on this event)\r\n mouseDown(evt) {\r\n \r\n const intersections = this.getIntersections(evt.target);\r\n \r\n if (intersections.length === 0) return;\r\n \r\n const element = intersections[0]\r\n var newGrabbedEl = this.getRaycastTarget(element)\r\n\r\n if (this.grabbedEl && \r\n this.grabbedEl !== newGrabbedEl) {\r\n console.warn(\"Grabbed 2nd element without releasing the first:\", newGrabbedEl.id, this.grabbedEl.id)\r\n }\r\n\r\n this.grabbedEl = newGrabbedEl\r\n \r\n },\r\n\r\n // Ensure an element has a usable ID.\r\n // If it has no ID, add one.\r\n // If it has an ID but it's not usable to identify the element...\r\n // ...log an error (preferable to creating confusion by modifying existing IDs)\r\n assureUsableId(el) {\r\n\r\n if (!el.id) {\r\n // No ID, just set one\r\n el.setAttribute(\"id\", Math.random().toString(36).slice(2))\r\n }\r\n else {\r\n const reference = document.getElementById(el.id)\r\n if (reference !== el) {\r\n console.error(`Element ID for ${el.id} does not unambiguously identify it. Check for duplicate IDs.`)\r\n }\r\n }\r\n },\r\n\r\n // Get scene graph parent element of an element.\r\n // Includes the case where the parent is the a-scene.\r\n getParentEl(el) {\r\n\r\n const parentObject = el.object3D.parent\r\n\r\n if (parentObject.type === 'Scene') {\r\n return(this.el.sceneEl)\r\n }\r\n else {\r\n return parentObject.el\r\n }\r\n },\r\n\r\n grabElToContactPoint(contactPoint, contactPointSelector) {\r\n\r\n // Save record of original parent, and make sure it has a usable ID.\r\n if (!this.originalParentEl) {\r\n this.originalParentEl = this.getParentEl(this.grabbedEl)\r\n }\r\n this.assureUsableId(this.originalParentEl)\r\n\r\n // set up a contact point at the position of the grabbed entity\r\n const pos = contactPoint.object3D.position\r\n this.grabbedEl.object3D.getWorldPosition(pos)\r\n contactPoint.object3D.parent.worldToLocal(pos)\r\n\r\n if (this.data.controlMethod === 'parent') {\r\n this.activeControlMethod = 'parent'\r\n this.grabbedEl.setAttribute('object-parent', 'parent', contactPointSelector)\r\n }\r\n else {\r\n this.activeControlMethod = 'transform'\r\n this.saveContactPointTransform(contactPoint)\r\n }\r\n\r\n this.hints.object3D.position.set(0, 0 , 0)\r\n contactPoint.object3D.add(this.hints.object3D)\r\n\r\n if (this.data.grabEvents) {\r\n this.grabbedEl.emit(this.data.grabEvent)\r\n }\r\n },\r\n\r\n releaseEl() {\r\n const contactPoint = this.grabbedEl.object3D.parent\r\n\r\n if (this.activeControlMethod === 'parent') {\r\n this.grabbedEl.setAttribute('object-parent', 'parent', `#${this.originalParentEl.id}`)\r\n }\r\n\r\n if (this.data.grabEvents) {\r\n // defer event to next schedule, to allow reparenting to have completed.\r\n const releasedEl = this.grabbedEl\r\n setTimeout(() => {\r\n releasedEl.emit(this.data.releaseEvent)\r\n })\r\n }\r\n\r\n this.grabbedEl = null\r\n this.activeControlMethod = ''\r\n this.originalParentEl = null\r\n \r\n this.el.object3D.add(this.hints.object3D)\r\n\r\n if (this.hoverEl) {\r\n const pos = this.hints.object3D.position\r\n this.hoverEl.object3D.getWorldPosition(pos)\r\n this.hints.object3D.parent.worldToLocal(pos)\r\n }\r\n },\r\n\r\n mouseUp() {\r\n // all work done on MouseEvent, where we have detail as to *which* button is pressed.\r\n },\r\n\r\n getRaycastTarget(el) {\r\n if (el.components['raycast-target']) {\r\n return el.components['raycast-target'].target\r\n }\r\n else {\r\n return el\r\n }\r\n },\r\n\r\n mouseEnter(evt) {\r\n\r\n // similar logic to mouseDown - could be commonized\r\n // or we could even *only* do some of this processing on mouseenter?\r\n const intersections = this.getIntersections(evt.target);\r\n \r\n if (intersections.length === 0) return;\r\n \r\n const element = intersections[0]\r\n\r\n this.hoverEl = this.getRaycastTarget(element)\r\n if (this.data.debug) console.log(\"HoverEl set:\", this.hoverEl)\r\n \r\n // don't do actual hover display behaviour when another entity is already grabbed.\r\n // (but do do the state tracking bits - above).\r\n if (this.grabbedEl) return;\r\n\r\n const contactPoint = this.cursorContactPoint\r\n const pos = this.hints.object3D.position\r\n this.hoverEl.object3D.getWorldPosition(pos)\r\n this.hints.object3D.parent.worldToLocal(pos)\r\n\r\n this.updateHints()\r\n },\r\n\r\n mouseLeave(evt) {\r\n this.hoverEl = null\r\n if (this.data.debug) console.log(\"HoverEl cleared\")\r\n this.updateHints()\r\n },\r\n\r\n getIntersections(cursorEl) {\r\n \r\n const els = cursorEl.components.raycaster.intersectedEls\r\n return els\r\n },\r\n\r\n saveContactPointTransform(contactPoint) {\r\n const transform = this.lastContactPointTransform\r\n transform.quaternion.identity()\r\n transform.position.set(0, 0, 0)\r\n transform.scale.set(1, 1, 1)\r\n contactPoint.object3D.add(transform)\r\n this.el.sceneEl.object3D.attach(transform)\r\n\r\n this.activeContactPoint = contactPoint\r\n },\r\n\r\n followContactPoint() {\r\n const object = this.grabbedEl.object3D\r\n this.lastContactPointTransform.attach(object)\r\n this.saveContactPointTransform(this.activeContactPoint)\r\n this.originalParentEl.object3D.attach(object)\r\n },\r\n \r\n tick() {\r\n if (this.activeControlMethod === 'transform') {\r\n this.followContactPoint()\r\n }\r\n }\r\n});\r\n\r\nAFRAME.registerComponent('mouse-manipulation-hints', {\r\n schema: {\r\n view: {type: 'string',\r\n oneOf: ['none', 'hover', 'left', 'middle', 'right'],\r\n default: 'none'}\r\n },\r\n\r\n init() {\r\n this.views = {}\r\n const views = this.views\r\n\r\n this.createHoverView()\r\n this.createLeftView()\r\n this.createRightView()\r\n this.createMiddleView()\r\n },\r\n\r\n createHoverView() {\r\n\r\n const views = this.views\r\n views.hover = document.createElement('a-entity')\r\n views.hover.setAttribute('id', 'hint-hover')\r\n this.el.appendChild(views.hover)\r\n\r\n const rows = [[\"left-mouse\", \"move-arrows\", \"left-mouse\", \"pitch-yaw-arrow\"],\r\n [\"mouse-wheel\", \"in-out-arrow\", \"middle-mouse\", \"roll\"]]\r\n\r\n const rotations = [[0, 0, 0, 0],\r\n [0, 0, 0, 0]]\r\n \r\n const reflections = [[1, 1, -1, 1],\r\n [1, 1, 1, 1]]\r\n\r\n this.addRowsToView(views.hover, rows, rotations, reflections, \"above\")\r\n },\r\n\r\n createLeftView() {\r\n\r\n const views = this.views\r\n views.left = document.createElement('a-entity')\r\n views.left.setAttribute('id', 'hint-left')\r\n //views.left.setAttribute(\"text\", \"value: left; align: center; anchor: center\") \r\n this.el.appendChild(views.left)\r\n\r\n const rows = [[\"mouse-wheel\", \"in-out-arrow\"]]\r\n const rotations = [[0, 0]]\r\n const reflections = [[1, 1]]\r\n\r\n this.addRowsToView(views.left, rows, rotations, reflections, \"below\")\r\n\r\n const cRows = [[\"left-arrow\"],\r\n [\"left-arrow\"],\r\n [\"left-arrow\"],\r\n [\"left-arrow\"]]\r\n const cRotations = [[270], [90], [0], [180]]\r\n const cReflections = [[1], [1], [1], [1]]\r\n\r\n this.addRowsToView(views.left, cRows, cRotations, cReflections, \"compass\")\r\n },\r\n\r\n createRightView() {\r\n\r\n const views = this.views\r\n views.right = document.createElement('a-entity')\r\n views.right.setAttribute('id', 'hint-right')\r\n this.el.appendChild(views.right)\r\n\r\n const rows = [[\"mouse-wheel\", \"in-out-arrow\"]]\r\n const rotations = [[0, 0]]\r\n const reflections = [[1, 1]]\r\n\r\n this.addRowsToView(views.right, rows, rotations, reflections, \"below\")\r\n\r\n const cRows = [[\"yaw-arrow\"],\r\n [\"yaw-arrow\"],\r\n [\"yaw-arrow\"],\r\n [\"yaw-arrow\"]]\r\n const cRotations = [[90], [90], [0], [0]]\r\n const cReflections = [[1], [-1], [-1], [1]]\r\n\r\n this.addRowsToView(views.right, cRows, cRotations, cReflections, \"compass\")\r\n },\r\n\r\n createMiddleView() {\r\n\r\n const views = this.views\r\n\r\n views.middle = document.createElement('a-entity')\r\n views.middle.setAttribute('id', 'hint-middle')\r\n this.el.appendChild(views.middle)\r\n\r\n const rows = [[\"roll\"]]\r\n const aRotations = [[0]]\r\n const bRotations = [[180]]\r\n const reflections = [[1]]\r\n\r\n this.addRowsToView(views.middle, rows, aRotations, reflections, \"above\")\r\n this.addRowsToView(views.middle, rows, bRotations, reflections, \"below\")\r\n },\r\n\r\n addRowsToView(view, rows, rotations, reflections, layout) {\r\n\r\n const spacing = 0.15\r\n const imgSize = 0.1\r\n const iconsPath = \"https://cdn.jsdelivr.net/gh/diarmidmackenzie/aframe-components@latest/assets/icons/\"\r\n\r\n var xOffset, yOffset\r\n \r\n xOffset = -((rows[0].length - 1) * spacing / 2)\r\n yOffset = 0.2 + rows.length * spacing / 2 \r\n \r\n if (layout === \"below\") {\r\n yOffset -= 0.5\r\n }\r\n \r\n function createIcon(iconName, xPos, yPos, rotation, reflect) {\r\n\r\n const icon = document.createElement('a-image')\r\n const src = `${iconsPath}${iconName}.svg`\r\n\r\n icon.setAttribute(\"src\", src)\r\n icon.object3D.position.set(xPos, yPos, 0)\r\n icon.object3D.rotation.set(0, 0, THREE.MathUtils.degToRad(rotation))\r\n icon.object3D.scale.set(imgSize * reflect, imgSize, imgSize)\r\n view.appendChild(icon)\r\n }\r\n\r\n function createRow(row, xStart, yPos, rowIndex) {\r\n\r\n row.forEach((iconName, index) => {\r\n createIcon(iconName, xStart + (index * spacing), yPos,\r\n rotations[rowIndex][index],\r\n reflections[rowIndex][index])\r\n })\r\n }\r\n\r\n if ((layout === \"above\") ||\r\n (layout === \"below\"))\r\n {\r\n // lay rows out in a grid above the entity\r\n rows.forEach((row, index) => {\r\n createRow(row, xOffset, yOffset - (index * spacing), index)\r\n })\r\n }\r\n else if (layout === \"compass\") {\r\n // lay rows out at N, S, E & W positions.\r\n console.assert(rows.length == 4)\r\n\r\n const radius = 0.4\r\n \r\n createRow(rows[0], 0, radius, 0) // N\r\n createRow(rows[1], 0, -radius, 1) // S\r\n createRow(rows[2], -radius, 0, 2) // E\r\n createRow(rows[3], radius, 0, 3) // W\r\n\r\n }\r\n },\r\n\r\n update() {\r\n\r\n const show = (x) => { x.object3D.visible = true }\r\n const hide = (x) => { x.object3D.visible = false }\r\n\r\n const views = this.views\r\n\r\n hide(views.hover)\r\n hide(views.left)\r\n hide(views.right)\r\n hide(views.middle)\r\n \r\n const viewToShow = views[this.data.view]\r\n if (viewToShow) {\r\n show(viewToShow)\r\n }\r\n }\r\n})\r\n \r\nAFRAME.registerComponent('mouse-pitch-yaw', {\r\n\r\n schema: {\r\n // whether to only allow rotation on a single axis (whichever moves first)\r\n singleAxis : {type: 'boolean', default: false},\r\n // Number of mouse pixels movement required to lock onto an axis.\r\n threshold : {type: 'number', default: 5}\r\n },\r\n\r\n init: function () {\r\n \r\n this.axis = null\r\n this.cumX = 0\r\n this.cumY = 0\r\n\r\n this.xQuaternion = new THREE.Quaternion();\r\n this.yQuaternion = new THREE.Quaternion();\r\n this.yAxis = new THREE.Vector3(0, 1, 0);\r\n this.xAxis = new THREE.Vector3(1, 0, 0);\r\n \r\n this.onMouseMove = this.onMouseMove.bind(this);\r\n document.addEventListener('mousemove', this.onMouseMove);\r\n },\r\n\r\n\r\n remove() {\r\n document.removeEventListener('mousemove', this.onMouseMove);\r\n },\r\n \r\n onMouseMove: function (evt) {\r\n this.rotateModel(evt);\r\n },\r\n \r\n rotateModel: function (evt) {\r\n\r\n // get normalized vector perpendicular to camera to use as xAxis (to pitch around)\r\n this.xAxis.copy(this.el.object3D.position)\r\n this.xAxis.normalize()\r\n this.xAxis.cross(this.yAxis)\r\n //console.log(\"xAxis: \", this.xAxis)\r\n\r\n var dX = evt.movementX;\r\n var dY = evt.movementY;\r\n\r\n // constrain to single axis if required.\r\n if (this.data.singleAxis) {\r\n\r\n // cumulative movements in X & Y. Used to measure vs. threshold for\r\n // single axis movement.\r\n this.cumX += dX\r\n this.cumY += dY\r\n\r\n if (!this.axis && \r\n ((Math.abs(this.cumX) > this.data.threshold) ||\r\n (Math.abs(this.cumY) > this.data.threshold))) {\r\n this.axis = (Math.abs(this.cumX) > Math.abs(this.cumY)) ? \"x\" : \"y\"\r\n }\r\n\r\n if (this.axis === \"x\") {\r\n dY = 0\r\n }\r\n else if (this.axis === \"y\"){\r\n dX = 0\r\n }\r\n else {\r\n // if not locked onto an axis yet, don't allow amny movement.\r\n dX = 0\r\n dY = 0\r\n }\r\n }\r\n \r\n this.xQuaternion.setFromAxisAngle(this.yAxis, dX / 200)\r\n this.yQuaternion.setFromAxisAngle(this.xAxis, dY / 200)\r\n \r\n this.el.object3D.quaternion.premultiply(this.xQuaternion);\r\n this.el.object3D.quaternion.premultiply(this.yQuaternion);\r\n\r\n // avoid issues that can result from accumulation of small Floating Point inaccuracies.\r\n this.el.object3D.quaternion.normalize()\r\n }\r\n});\r\n\r\nAFRAME.registerComponent('mouse-roll', {\r\n\r\n schema: {\r\n slowdownRadius: {type: 'number', default: 50}\r\n },\r\n\r\n init: function () {\r\n \r\n this.zQuaternion = new THREE.Quaternion();\r\n this.zAxis = new THREE.Vector3(0, 0, 1);\r\n \r\n this.onMouseMove = this.onMouseMove.bind(this);\r\n document.addEventListener('mousemove', this.onMouseMove);\r\n\r\n this.currPointer = new THREE.Vector2()\r\n this.prevPointer = new THREE.Vector2()\r\n\r\n this.el.setAttribute(\"entity-screen-position\", \"\")\r\n\r\n this.modelPos = new THREE.Vector2()\r\n this.el.components['entity-screen-position'].getEntityScreenPosition(this.modelPos)\r\n },\r\n \r\n\r\n remove() {\r\n this.el.removeAttribute(\"entity-screen-position\")\r\n document.removeEventListener('mousemove', this.onMouseMove);\r\n },\r\n \r\n onMouseMove: function (evt) {\r\n this.rotateModel(evt);\r\n },\r\n \r\n rotateModel: function (evt) {\r\n\r\n // get normalized vector away from camera to use as zAxis (to roll around)\r\n this.zAxis.copy(this.el.object3D.position)\r\n this.zAxis.multiplyScalar(-1)\r\n this.zAxis.normalize()\r\n //console.log(\"zAxis: \", this.zAxis)\r\n\r\n this.el.components['entity-screen-position'].getEntityScreenPosition(this.modelPos)\r\n //console.log(\"Model position on screen:\", this.modelPos)\r\n\r\n const dX = evt.movementX;\r\n const dY = evt.movementY;\r\n this.currPointer.set(evt.clientX, evt.clientY)\r\n this.currPointer.sub(this.modelPos)\r\n this.prevPointer.set(evt.clientX - dX, evt.clientY - dY)\r\n this.prevPointer.sub(this.modelPos)\r\n\r\n let angle = this.prevPointer.angle() - this.currPointer.angle()\r\n\r\n // Normalize to rangw PI -> -PI, so that scaling angle down doesn't give unexpected results.\r\n if (angle < (-Math.PI)) angle += (2 * Math.PI)\r\n if (angle > (Math.PI)) angle -= (2 * Math.PI)\r\n \r\n const distanceToCenter = Math.min(this.currPointer.length(), this.prevPointer.length())\r\n if (distanceToCenter < this.data.slowdownRadius) {\r\n const scaleFactor = distanceToCenter / this.data.slowdownRadius\r\n angle *= scaleFactor\r\n }\r\n \r\n this.zQuaternion.setFromAxisAngle(this.zAxis, angle)\r\n this.el.object3D.quaternion.premultiply(this.zQuaternion);\r\n }\r\n});\r\n\r\n// Make available the screen position of an entity\r\nAFRAME.registerComponent('entity-screen-position', {\r\n\r\n init: function () {\r\n \r\n this.vector = new THREE.Vector3()\r\n\r\n // need to keep an up-to-date view of canvs bounds\r\n this.canvasBounds = document.body.getBoundingClientRect();\r\n this.updateCanvasBounds = AFRAME.utils.debounce(() => {\r\n this.canvasBounds = this.el.sceneEl.canvas.getBoundingClientRect()\r\n }, 500);\r\n \r\n window.addEventListener('resize', this.updateCanvasBounds);\r\n window.addEventListener('scroll', this.updateCanvasBounds);\r\n\r\n this.getEntityScreenPosition = this.getEntityScreenPosition.bind(this)\r\n },\r\n \r\n\r\n remove() {\r\n window.removeEventListener('resize', this.updateCanvasBounds);\r\n window.removeEventListener('scroll', this.updateCanvasBounds);\r\n },\r\n\r\n getEntityScreenPosition(vector2) {\r\n\r\n this.el.object3D.getWorldPosition(this.vector)\r\n //console.log(\"World Position:\", this.vector)\r\n this.vector.project(this.el.sceneEl.camera)\r\n\r\n //console.log(\"Projected vector x, y:\", this.vector.x, this.vector.y)\r\n\r\n const bounds = this.canvasBounds;\r\n //console.log(\"Canvas Bounds:\", bounds)\r\n vector2.set((this.vector.x + 1) * bounds.width / 2,\r\n bounds.height - ((this.vector.y + 1) * bounds.height / 2))\r\n //console.log(\"Model position on screen:\", vector2)\r\n\r\n return vector2\r\n }\r\n});\r\n\r\nAFRAME.registerComponent('mouse-dolly', {\r\n\r\n init: function () {\r\n\r\n // 1 - no movement; < 1 = reverse movement.\r\n this.moveSpeed = 1.3\r\n \r\n this.zQuaternion = new THREE.Quaternion();\r\n this.zAxis = new THREE.Vector3(0, 0, 1);\r\n \r\n this.onMouseWheel = this.onMouseWheel.bind(this);\r\n document.addEventListener('mousewheel', this.onMouseWheel);\r\n },\r\n\r\n remove() {\r\n document.removeEventListener('mousewheel', this.onMouseWheel);\r\n },\r\n \r\n onMouseWheel: function (evt) {\r\n this.dollyModel(evt);\r\n },\r\n \r\n dollyModel: function (evt) {\r\n\r\n const dY = evt.deltaY;\r\n\r\n const scalar = Math.pow(this.moveSpeed, -dY/400);\r\n this.el.object3D.position.multiplyScalar(scalar)\r\n }\r\n});\r\n \n\n//# sourceURL=webpack://aframe-mouse-manipulation/./index.js?");
/***/ }),
/***/ "./node_modules/aframe-cursor-tracker/index.js":
/*!*****************************************************!*\
!*** ./node_modules/aframe-cursor-tracker/index.js ***!
\*****************************************************/
/***/ (() => {
eval("// Set on an entity to track the orientation of the cursor's ray.\r\n// Typically set on a enitity that is a child of the camera that the cursor uses.\r\nAFRAME.registerComponent('cursor-tracker', {\r\n\r\n schema: {\r\n cursor: {type: 'selector', default: \"#cursor\"},\r\n },\r\n\r\n init() {\r\n this.cursor = this.data.cursor\r\n this.raycaster = this.cursor.components['raycaster'].raycaster\r\n this.forward = new THREE.Vector3(0, 0, -1)\r\n this.localRayVector = new THREE.Vector3();\r\n },\r\n\r\n tick() {\r\n\r\n // Get ray direction vector in the space of this object.\r\n this.el.object3D.getWorldPosition(this.localRayVector)\r\n this.localRayVector.add(this.raycaster.ray.direction)\r\n this.el.object3D.parent.worldToLocal(this.localRayVector)\r\n this.localRayVector.normalize() \r\n this.el.object3D.quaternion.setFromUnitVectors(this.forward, this.localRayVector)\r\n }\r\n});\r\n\n\n//# sourceURL=webpack://aframe-mouse-manipulation/./node_modules/aframe-cursor-tracker/index.js?");
/***/ }),
/***/ "./node_modules/aframe-label/index.js":
/*!********************************************!*\
!*** ./node_modules/aframe-label/index.js ***!
\********************************************/
/***/ (() => {
eval("\r\nAFRAME.registerComponent('label-anchor', {\r\n\r\n schema: {\r\n // vector from the anchor to the label. When non-zero, a line is drawn from \r\n // the label to this point.\r\n offsetVector: {type: 'vec3'},\r\n\r\n // whether to show a line, and what color?\r\n showLine: {type: 'boolean', default: true},\r\n lineColor: {type: 'color', default: 'white'}\r\n },\r\n\r\n init() {\r\n\r\n // Find this label that is a child of this label anchor, and position it\r\n // with the configured offset.\r\n this.label = this.el.querySelector(\"[label]\")\r\n\r\n this.cameraWorldPosition = new THREE.Vector3();\r\n this.objectWorldPosition = new THREE.Vector3();\r\n },\r\n\r\n update() {\r\n\r\n if (this.data.showLine) {\r\n this.el.setAttribute(\"line__label-anchor\", `start: 0 0 0; end: 0 0 0; color: ${this.data.lineColor}`)\r\n }\r\n else {\r\n this.el.removeAttribute(\"line__label-anchor\")\r\n }\r\n },\r\n\r\n tick() {\r\n\r\n const camera = this.el.sceneEl.camera;\r\n\r\n // if using a perspective camera, we adjust the position of the label based on the distance\r\n // from the camera, so that it appears like a fixed distance on camera.\r\n var distance = 1;\r\n if (camera.isPerspectiveCamera)\r\n {\r\n // Can't use getWorldPosition on camera, as it doesn't work in VR mode.\r\n // See: https://github.com/mrdoob/three.js/issues/18448\r\n this.cameraWorldPosition.setFromMatrixPosition(camera.matrixWorld);\r\n this.el.object3D.getWorldPosition(this.objectWorldPosition)\r\n distance = this.objectWorldPosition.distanceTo(this.cameraWorldPosition)\r\n }\r\n\r\n this.label.object3D.position.copy(this.data.offsetVector)\r\n this.label.object3D.position.multiplyScalar(distance)\r\n\r\n if (this.data.showLine) {\r\n const pos = this.label.object3D.position\r\n const vectorString = `${pos.x} ${pos.y} ${pos.z}`\r\n this.el.setAttribute(\"line__label-anchor\", `end: ${vectorString}`)\r\n }\r\n }\r\n})\r\n\r\nAFRAME.registerComponent('label', {\r\n\r\n schema: {\r\n // Should the label overwrite objects that are in front of it in space?\r\n overwrite: {type: 'boolean', default: false},\r\n forceDesktopMode: {type: 'boolean', default: false}\r\n },\r\n\r\n init() {\r\n this.enterVR = this.enterVR.bind(this)\r\n this.exitVR = this.exitVR.bind(this)\r\n\r\n this.el.sceneEl.addEventListener('enter-vr', this.enterVR);\r\n this.el.sceneEl.addEventListener('exit-vr', this.exitVR);\r\n },\r\n\r\n update() {\r\n if (this.el.sceneEl.is('vr-mode')) {\r\n this.enterVR()\r\n }\r\n else {\r\n this.exitVR()\r\n }\r\n },\r\n\r\n enterVR: function() {\r\n this.el.setAttribute(\"face-camera\", {fixedSize: this.data.forceDesktopMode,\r\n spriteMode: this.data.forceDesktopMode,\r\n overwrite: this.data.overwrite});\r\n },\r\n\r\n exitVR: function() {\r\n this.el.setAttribute(\"face-camera\", {fixedSize: true,\r\n spriteMode: true,\r\n overwrite: this.data.overwrite});\r\n }\r\n});\r\n\r\n// Makes an element always face directly to the camera.\r\n// Like a THREE.js sprite, but usable with any geometry, not just a PNG.\r\nAFRAME.registerComponent('face-camera', {\r\n\r\n schema: {\r\n // Keep the element a fixed size on camera regardless of distance.\r\n // This works well on desktop, but is disorienting in VR.\r\n // fixedSize assumes the entity is scaled at 1, 1, 1.\r\n fixedSize: {type: 'boolean', default: false},\r\n \r\n // If using a perspecive camera, face back with a normal that exactly reverses the gaze\r\n // direction of the camera.\r\n // If this is false, the label simply faces directly at the camera\r\n // (this looks good in VR, but gives a distorting effect on a 2D screen).\r\n // For an orthographic camera, we always use sprite Mode\r\n spriteMode: {type: 'boolean', default: false},\r\n\r\n // Should the label overwrite objects that are in front of it in space?\r\n overwrite: {type: 'boolean', default: false}\r\n },\r\n\r\n init: function() {\r\n this.cameraWorldPosition = new THREE.Vector3();\r\n this.objectWorldPosition = new THREE.Vector3();\r\n this.cameraQuaternion = new THREE.Quaternion();\r\n this.spriteDistanceVector = new THREE.Vector3();\r\n this.cameraDirectionVector = new THREE.Vector3();\r\n this.parentInverseQuaternion = new THREE.Quaternion();\r\n\r\n\r\n this.object3DSet = this.object3DSet.bind(this)\r\n\r\n if (this.data.overwrite) {\r\n this.el.addEventListener('object3dset', this.object3DSet)\r\n }\r\n },\r\n\r\n object3DSet(evt) {\r\n\r\n const mesh = evt.target.getObject3D(evt.detail.type)\r\n mesh.material.depthTest = false;\r\n mesh.material.depthWrite = false;\r\n \r\n },\r\n\r\n tick: function() {\r\n const camera = this.el.sceneEl.camera;\r\n\r\n if (this.data.spriteMode ||\r\n camera.isOrthographicCamera) {\r\n\r\n // On an Orthographic camera, we always use Sprite mode, as this matches how other geometry\r\n // is rendered.\r\n\r\n setWorldQuaternion = (object, quaternion) => {\r\n\r\n object.updateMatrixWorld()\r\n object.parent.getWorldQuaternion(this.parentInverseQuaternion)\r\n this.parentInverseQuaternion.invert();\r\n\r\n object.quaternion.copy(quaternion)\r\n object.quaternion.premultiply(this.parentInverseQuaternion)\r\n }\r\n\r\n // set the world quaternion of this entity to match the camera\r\n this.cameraQuaternion.setFromRotationMatrix(camera.matrixWorld)\r\n setWorldQuaternion(this.el.object3D, this.cameraQuaternion)\r\n }\r\n else {\r\n // Can't use getWorldPosition on camera, as it doesn't work in VR mode.\r\n // See: https://github.com/mrdoob/three.js/issues/18448\r\n this.cameraWorldPosition.setFromMatrixPosition(camera.matrixWorld);\r\n this.el.object3D.lookAt(this.cameraWorldPosition);\r\n }\r\n\r\n if (this.data.fixedSize) {\r\n if (camera.isPerspectiveCamera)\r\n {\r\n if (this.data.spriteMode) {\r\n // in sprite mode, we just take the distance along the main camera axis.\r\n this.cameraDirectionVector.set(0, 0, -1);\r\n this.cameraDirectionVector.transformDirection(camera.matrixWorld);\r\n\r\n this.cameraWorldPosition.setFromMatrixPosition(camera.matrixWorld);\r\n this.el.object3D.getWorldPosition(this.objectWorldPosition)\r\n this.spriteDistanceVector.subVectors(this.objectWorldPosition,\r\n this.cameraWorldPosition)\r\n\r\n this.spriteDistanceVector.projectOnVector(this.cameraDirectionVector)\r\n const distance = this.spriteDistanceVector.length();\r\n\r\n this.el.object3D.scale.set(distance, distance, distance);\r\n }\r\n else {\r\n this.el.object3D.getWorldPosition(this.objectWorldPosition)\r\n const distance = this.objectWorldPosition.distanceTo(this.cameraWorldPosition)\r\n this.el.object3D.scale.set(distance, distance, distance);\r\n }\r\n }\r\n else {\r\n this.el.object3D.scale.set(1, 1, 1);\r\n }\r\n }\r\n }\r\n});\r\n\n\n//# sourceURL=webpack://aframe-mouse-manipulation/./node_modules/aframe-label/index.js?");
/***/ }),
/***/ "./node_modules/aframe-object-parent/index.js":
/*!****************************************************!*\
!*** ./node_modules/aframe-object-parent/index.js ***!
\****************************************************/
/***/ (() => {
eval("// Change the parent of an object without changing its transform.\r\nAFRAME.registerComponent('object-parent', {\r\n\r\n schema: {\r\n parent: {type: 'selector'}, \r\n },\r\n\r\n update() {\r\n\r\n const matches = document.querySelectorAll(`#${parent.id}`)\r\n if (matches.length > 1) {\r\n console.warn(`object-parent matches duplicate entities for new parent ${parent.id}`)\r\n }\r\n\r\n const newParent = this.data.parent.object3D\r\n this.reparent(newParent)\r\n \r\n },\r\n\r\n remove() {\r\n\r\n const originalParentEl = this.el.parentEl\r\n this.reparent(originalParentEl.object3D)\r\n\r\n },\r\n\r\n reparent(newParent) {\r\n\r\n const object = this.el.object3D\r\n const oldParent = object.parent\r\n\r\n if (object.parent === newParent) {\r\n return;\r\n }\r\n\r\n objectEl = (o) => {\r\n if (o.type === 'Scene') {\r\n return (this.el.sceneEl)\r\n }\r\n else {\r\n return o.el\r\n }\r\n }\r\n\r\n console.log(`Reparenting ${object.el.id} from ${objectEl(oldParent).id} to ${objectEl(newParent).id}`);\r\n \r\n newParent.attach(object);\r\n },\r\n});\r\n\n\n//# sourceURL=webpack://aframe-mouse-manipulation/./node_modules/aframe-object-parent/index.js?");
/***/ })
/******/ });
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
/******/
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module can't be inlined because the eval devtool is used.
/******/ var __webpack_exports__ = __webpack_require__("./index.js");
/******/
/******/ })()
;