diff --git a/dist/mr.js b/dist/mr.js index 7fc92c5a..3d190fde 100644 --- a/dist/mr.js +++ b/dist/mr.js @@ -443,7 +443,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ MRApp: () => (/* binding */ MRApp)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var three_addons_controls_OrbitControls_js__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(/*! three/addons/controls/OrbitControls.js */ \"./node_modules/three/examples/jsm/controls/OrbitControls.js\");\n/* harmony import */ var three_addons_webxr_XRButton_js__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(/*! three/addons/webxr/XRButton.js */ \"./node_modules/three/examples/jsm/webxr/XRButton.js\");\n/* harmony import */ var stats_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! stats.js */ \"./node_modules/stats.js/build/stats.min.js\");\n/* harmony import */ var stats_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(stats_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var mrjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs */ \"./src/index.js\");\n/* harmony import */ var mrjs_core_MRElement__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! mrjs/core/MRElement */ \"./src/core/MRElement.js\");\n/* harmony import */ var mrjs_core_MREntity__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! mrjs/core/MREntity */ \"./src/core/MREntity.js\");\n/* harmony import */ var mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! mrjs/core/MRSystem */ \"./src/core/MRSystem.js\");\n/* harmony import */ var mrjs_core_user_MRUser__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! mrjs/core/user/MRUser */ \"./src/core/user/MRUser.js\");\n/* harmony import */ var mrjs_core_entities_MRSkyBoxEntity__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! mrjs/core/entities/MRSkyBoxEntity */ \"./src/core/entities/MRSkyBoxEntity.js\");\n/* harmony import */ var mrjs_core_entities_MRStatsEntity__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! mrjs/core/entities/MRStatsEntity */ \"./src/core/entities/MRStatsEntity.js\");\n/* harmony import */ var mrjs_core_componentSystems_AnchorSystem__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! mrjs/core/componentSystems/AnchorSystem */ \"./src/core/componentSystems/AnchorSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_AnimationSystem__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! mrjs/core/componentSystems/AnimationSystem */ \"./src/core/componentSystems/AnimationSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_AudioSystem__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! mrjs/core/componentSystems/AudioSystem */ \"./src/core/componentSystems/AudioSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_BoundaryVisibilitySystem__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! mrjs/core/componentSystems/BoundaryVisibilitySystem */ \"./src/core/componentSystems/BoundaryVisibilitySystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_ClippingSystem__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! mrjs/core/componentSystems/ClippingSystem */ \"./src/core/componentSystems/ClippingSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_ControlSystem__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! mrjs/core/componentSystems/ControlSystem */ \"./src/core/componentSystems/ControlSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_GeometryStyleSystem__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! mrjs/core/componentSystems/GeometryStyleSystem */ \"./src/core/componentSystems/GeometryStyleSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_LayoutSystem__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! mrjs/core/componentSystems/LayoutSystem */ \"./src/core/componentSystems/LayoutSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_MaskingSystem__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! mrjs/core/componentSystems/MaskingSystem */ \"./src/core/componentSystems/MaskingSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_MaterialStyleSystem__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! mrjs/core/componentSystems/MaterialStyleSystem */ \"./src/core/componentSystems/MaterialStyleSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_PanelSystem__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! mrjs/core/componentSystems/PanelSystem */ \"./src/core/componentSystems/PanelSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_PhysicsSystem__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! mrjs/core/componentSystems/PhysicsSystem */ \"./src/core/componentSystems/PhysicsSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_SkyBoxSystem__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(/*! mrjs/core/componentSystems/SkyBoxSystem */ \"./src/core/componentSystems/SkyBoxSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_StatsSystem__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(/*! mrjs/core/componentSystems/StatsSystem */ \"./src/core/componentSystems/StatsSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_TextSystem__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(/*! mrjs/core/componentSystems/TextSystem */ \"./src/core/componentSystems/TextSystem.js\");\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n('use strict');\nwindow.mobileCheck = function () {\n return mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.display.mobileCheckFunction();\n};\n\n// events that trigger the eventUpdate call for all MRSystems\nconst GLOBAL_UPDATE_EVENTS = ['enterxr', 'exitxr', 'load', 'anchored', 'panelupdate', 'engine-started', 'resize'];\n\n/**\n * @class MRApp\n * @classdesc The engine handler for running MRjs as an App. `mr-app`\n * @augments MRElement\n */\nclass MRApp extends mrjs_core_MRElement__WEBPACK_IMPORTED_MODULE_2__.MRElement {\n /**\n * @class\n * @description Constructs the base information of the app including system, camera, engine, xr, and rendering defaults.\n */\n constructor() {\n super();\n Object.defineProperty(this, 'isApp', {\n value: true,\n writable: false,\n });\n\n this.xrsupport = false;\n this.isMobile = window.mobileCheck(); // resolves true/false\n\n this.inspect = false;\n\n this.clock = new three__WEBPACK_IMPORTED_MODULE_23__.Clock();\n this.systems = new Set();\n this.scene = new three__WEBPACK_IMPORTED_MODULE_23__.Scene();\n this.scene.matrixWorldAutoUpdate = false;\n this.anchor = null;\n this.origin = new three__WEBPACK_IMPORTED_MODULE_23__.Object3D();\n\n this.scene.add(this.origin);\n\n // The rest of the renderer is filled out in this.connectedCallback()-->this.init() since\n // the renderer relies on certain component flags attached to the itself.\n this.renderer = null;\n\n this.lighting = {\n enabled: true,\n color: 0xffffff,\n intensity: 1,\n radius: 5,\n shadows: true,\n };\n\n this.cameraOptions = {\n mode: 'orthographic',\n };\n this.render = this.render.bind(this);\n this.onWindowResize = this.onWindowResize.bind(this);\n }\n\n /**\n * @function\n * @memberof MRApp\n * @returns {number} width in 3d or pixel space (depending on if in xr) of the current open app\n */\n get appWidth() {\n let result = parseFloat(this.compStyle.width.split('px')[0]);\n if (mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.isPresenting) {\n result = (result / window.innerWidth) * mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.display.VIRTUAL_DISPLAY_RESOLUTION;\n }\n return result;\n }\n\n /**\n * @function\n * @memberof MRApp\n * @returns {number} height in 3d or pixel space (depending on if in xr) of the current open app\n */\n get appHeight() {\n let result = parseFloat(this.compStyle.height.split('px')[0]);\n if (mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.isPresenting) {\n result = (result / window.screen.height) * mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.display.VIRTUAL_DISPLAY_RESOLUTION;\n }\n return result;\n }\n\n /**\n * @function Connected\n * @memberof MRApp\n * @description The connectedCallback function that runs whenever this entity component becomes connected to something else.\n */\n connectedCallback() {\n this.compStyle = window.getComputedStyle(this);\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.physics.initializePhysics();\n this.init();\n\n this.observer = new MutationObserver(this.mutationCallback);\n this.observer.observe(this, { attributes: true, childList: true });\n\n // initialize built in Systems\n document.addEventListener('engine-started', (event) => {\n this.user = new mrjs_core_user_MRUser__WEBPACK_IMPORTED_MODULE_5__[\"default\"](this.camera, this.scene);\n\n if (this.dataset.occlusion == 'spotlight') {\n this.scene.add(this.user.initSpotlight());\n }\n\n // order matters for all the below system creation items\n this.panelSystem = new mrjs_core_componentSystems_PanelSystem__WEBPACK_IMPORTED_MODULE_18__.PanelSystem();\n this.layoutSystem = new mrjs_core_componentSystems_LayoutSystem__WEBPACK_IMPORTED_MODULE_15__.LayoutSystem();\n this.textSystem = new mrjs_core_componentSystems_TextSystem__WEBPACK_IMPORTED_MODULE_22__.TextSystem();\n this.geometryStyleSystem = new mrjs_core_componentSystems_GeometryStyleSystem__WEBPACK_IMPORTED_MODULE_14__.GeometryStyleSystem();\n this.materialStyleSystem = new mrjs_core_componentSystems_MaterialStyleSystem__WEBPACK_IMPORTED_MODULE_17__.MaterialStyleSystem();\n this.boundaryVisibilitySystem = new mrjs_core_componentSystems_BoundaryVisibilitySystem__WEBPACK_IMPORTED_MODULE_11__.BoundaryVisibilitySystem();\n this.statsSystem = new mrjs_core_componentSystems_StatsSystem__WEBPACK_IMPORTED_MODULE_21__.StatsSystem();\n this.physicsSystem = new mrjs_core_componentSystems_PhysicsSystem__WEBPACK_IMPORTED_MODULE_19__.PhysicsSystem();\n this.controlSystem = new mrjs_core_componentSystems_ControlSystem__WEBPACK_IMPORTED_MODULE_13__.ControlSystem();\n this.anchorSystem = new mrjs_core_componentSystems_AnchorSystem__WEBPACK_IMPORTED_MODULE_8__.AnchorSystem();\n this.animationSystem = new mrjs_core_componentSystems_AnimationSystem__WEBPACK_IMPORTED_MODULE_9__.AnimationSystem();\n this.skyBoxSystem = new mrjs_core_componentSystems_SkyBoxSystem__WEBPACK_IMPORTED_MODULE_20__.SkyBoxSystem();\n this.audioSystem = new mrjs_core_componentSystems_AudioSystem__WEBPACK_IMPORTED_MODULE_10__.AudioSystem();\n\n // These must be the last three systems since\n // they affect rendering. Clipping must happen\n // before masking. Rendering must be the last step.\n this.clippingSystem = new mrjs_core_componentSystems_ClippingSystem__WEBPACK_IMPORTED_MODULE_12__.ClippingSystem();\n this.maskingSystem = new mrjs_core_componentSystems_MaskingSystem__WEBPACK_IMPORTED_MODULE_16__.MaskingSystem();\n });\n\n this.addEventListener('entityadded', (event) => {\n for (const system of this.systems) {\n system._onNewEntity(event.target);\n }\n });\n\n document.addEventListener('entityremoved', async (event) => {\n for (const system of this.systems) {\n system._entityRemoved(event.detail.entity);\n }\n\n while (event.detail.entity.object3D.parent) {\n event.detail.entity.object3D.removeFromParent();\n }\n });\n\n // Call `eventUpdate` on all systems if any of the global events are triggered\n for (const eventType of GLOBAL_UPDATE_EVENTS) {\n document.addEventListener(eventType, (event) => {\n for (const system of this.systems) {\n system.eventUpdate();\n }\n });\n }\n }\n\n /**\n * @function Disconnected\n * @memberof MRApp\n * @description The disconnectedCallback function that runs whenever this entity component becomes connected to something else.\n */\n disconnectedCallback() {\n this.denit();\n this.observer.disconnect();\n }\n\n // TODO: These are for toggling debug and app level flags in realtime.\n // Currently only 'debug' is implemented. but we should add:\n // - stats\n // - lighting\n // - controllers\n // - ?\n /**\n * @function\n * @param {object} mutation - TODO\n */\n mutatedAttribute(mutation) {}\n\n /**\n * @function\n * @param {object} mutation - TODO\n */\n mutatedChildList(mutation) {}\n\n /**\n * @function\n * @description The mutationCallback function that runs whenever this entity component should be mutated.\n * @param {object} mutationList - the list of update/change/mutation(s) to be handled.\n * @param {object} observer - w3 standard object that watches for changes on the HTMLElement\n */\n mutationCallback = (mutationList, observer) => {\n for (const mutation of mutationList) {\n if (mutation.type === 'childList') {\n this.mutatedChildList(mutation);\n }\n if (mutation.type === 'attributes') {\n this.mutatedAttribute(mutation);\n }\n }\n };\n\n /**\n * @function\n * @description Initializes the engine state for the MRApp. This function is run whenever the MRApp is connected.\n */\n init() {\n window.addEventListener('resize', this.onWindowResize);\n\n this.debug = this.dataset.debug ?? false;\n\n /* --- Renderer Setup --- */\n\n this.renderer = new three__WEBPACK_IMPORTED_MODULE_23__.WebGLRenderer({\n antialias: true,\n alpha: true,\n // There's issues in the timing to enable taking screenshots of threejs scenes unless you have direct access to the code.\n // Using the preserveDrawingBuffer to ignore timing issues is the best approach instead. Though this has a performance hit,\n // we're allowing it to be enabled by users when necessary.\n //\n // References:\n // https://stackoverflow.com/questions/15558418/how-do-you-save-an-image-from-a-three-js-canvas\n // https://stackoverflow.com/questions/30628064/how-to-toggle-preservedrawingbuffer-in-three-js\n preserveDrawingBuffer: this.dataset.preserveDrawingBuffer ?? false,\n });\n this.renderer.setPixelRatio(window.devicePixelRatio);\n this.renderer.setSize(this.appWidth, this.appHeight);\n this.renderer.autoClear = false;\n this.renderer.shadowMap.enabled = true;\n this.renderer.xr.enabled = true;\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr = this.renderer.xr;\n this.renderer.toneMapping = three__WEBPACK_IMPORTED_MODULE_23__.ACESFilmicToneMapping;\n this.renderer.toneMappingExposure = 1;\n this.renderer.localClippingEnabled = true;\n\n this.appendChild(this.renderer.domElement);\n\n this.renderer.setAnimationLoop(this.render);\n\n /* --- Camera Setup --- */\n\n this.initCamera();\n\n const layersString = this.dataset.layers;\n if (layersString) {\n this.layers = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(layersString);\n\n for (const layer of this.layers) {\n this.camera.layers.enable(layer);\n }\n }\n\n const orbitalOptionsString = this.dataset.orbital;\n let orbitalOptions = {};\n if (orbitalOptionsString) {\n orbitalOptions = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToJson(orbitalOptionsString);\n }\n this.orbital = orbitalOptions.mode ?? false;\n if (this.debug || this.orbital) {\n const orbitControls = new three_addons_controls_OrbitControls_js__WEBPACK_IMPORTED_MODULE_24__.OrbitControls(this.camera, this.renderer.domElement);\n orbitControls.minDistance = 1;\n orbitControls.maxDistance = 2;\n\n // set target location if requested\n if (orbitalOptions.targetPos) {\n if (orbitalOptions.targetPos.length !== 3) {\n console.error('Invalid orbital target position format. Please provide \"x y z\".');\n }\n orbitControls.target.set(orbitalOptions.targetPos[0], orbitalOptions.targetPos[1], orbitalOptions.targetPos[2]);\n orbitControls.update();\n }\n\n // Note: order of the two below if-statements matter.\n // Want if both debug=true and orbital=true for orbital to take priority.\n if (this.orbital) {\n // always allow orbital controls\n orbitControls.enabled = true;\n } else if (this.debug) {\n // only allow orbital controls on += keypress\n orbitControls.enabled = false;\n document.addEventListener('keydown', (event) => {\n if (event.key == '=') {\n orbitControls.enabled = true;\n }\n });\n document.addEventListener('keyup', (event) => {\n if (event.key == '=') {\n orbitControls.enabled = false;\n }\n });\n }\n }\n\n /* --- Lighting Setup --- */\n\n if (this.dataset.lighting ?? false) {\n this.lighting = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToJson(this.dataset.lighting);\n }\n this.initLights(this.lighting);\n\n /* --- Stats Setup --- */\n\n if (this.dataset.stats ?? false) {\n // Old version of stats using the Stats.js visual\n // setup. Leaving to allow for top left quick visual of stats.\n // Is /not/ performant in headset. Documentation notes this.\n //\n this.stats = new (stats_js__WEBPACK_IMPORTED_MODULE_0___default())();\n this.stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom\n document.body.appendChild(this.stats.dom);\n }\n\n /* --- Background Setup --- */\n\n // allows for mr-app style to have background:value to set the skybox\n if (this.compStyle.backgroundImage !== 'none') {\n let skybox = new mrjs_core_entities_MRSkyBoxEntity__WEBPACK_IMPORTED_MODULE_6__.MRSkyBoxEntity();\n let imageUrl = this.compStyle.backgroundImage.match(/url\\(\"?(.+?)\"?\\)/)[1];\n skybox.setAttribute('src', imageUrl);\n this.appendChild(skybox);\n\n // Need to zero out the background-image property otherwise\n // we'll end up with a canvas background as well as the skybox\n // when the canvas background is not needed in this 3d setup.\n //\n // We can do this because panel backgrounds are actual webpage\n // backgrounds and the app itself's background is separate from\n // that, being understood as the skybox of the entire app itself.\n this.style.setProperty('background-image', 'none', 'important');\n this.compStyle = window.getComputedStyle(this);\n }\n\n /* --- Mobile VS XR Setup --- */\n\n // We don't support mobile XR yet\n if (!this.isMobile) {\n navigator.xr?.isSessionSupported('immersive-ar').then((supported) => {\n this.xrsupport = supported;\n\n if (this.xrsupport) {\n this.XRButton = three_addons_webxr_XRButton_js__WEBPACK_IMPORTED_MODULE_25__.XRButton.createButton(this.renderer, {\n requiredFeatures: ['local', 'hand-tracking'],\n optionalFeatures: ['hit-test', 'anchors', 'plane-detection'],\n });\n\n this.XRButton.addEventListener('click', () => {\n this.classList.add('inXR');\n this.XRButton.blur();\n });\n document.body.appendChild(this.XRButton);\n\n this.XRButton.style.position = 'fixed';\n this.XRButton.style.zIndex = 10000;\n }\n });\n }\n }\n\n /**\n * @function\n * @description Initializes the user information for the MRApp including appropriate HMD direction and camera information and the default scene anchor location.\n */\n initCamera = () => {\n const cameraOptionsString = this.dataset.camera ?? '';\n if (cameraOptionsString) {\n Object.assign(this.cameraOptions, mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToJson(this.cameraOptionString) ?? {});\n }\n\n __webpack_require__.g.appWidth = this.appWidth;\n __webpack_require__.g.appHeight = this.appHeight;\n\n switch (this.cameraOptions.mode) {\n case 'orthographic':\n __webpack_require__.g.viewPortWidth = this.appWidth / 1000;\n __webpack_require__.g.viewPortHeight = this.appHeight / 1000;\n\n // In an orthographic camera, unlike perspective, objects are rendered at the same scale regardless of their\n // distance from the camera, meaning near and far clipping planes are more about what objects are visible in\n // terms of their distance from the camera, rather than affecting the size of the objects.\n this.camera = new three__WEBPACK_IMPORTED_MODULE_23__.OrthographicCamera(__webpack_require__.g.viewPortWidth / -2, __webpack_require__.g.viewPortWidth / 2, __webpack_require__.g.viewPortHeight / 2, __webpack_require__.g.viewPortHeight / -2, 0.01, 1000);\n break;\n case 'perspective':\n default:\n this.camera = new three__WEBPACK_IMPORTED_MODULE_23__.PerspectiveCamera(70, this.appWidth / this.appHeight, 0.01, 20);\n this.vFOV = three__WEBPACK_IMPORTED_MODULE_23__.MathUtils.degToRad(this.camera.fov);\n __webpack_require__.g.viewPortHeight = 2 * Math.tan(this.vFOV / 2);\n __webpack_require__.g.viewPortWidth = __webpack_require__.g.viewPortHeight * this.camera.aspect;\n break;\n }\n this.camera.matrixWorldAutoUpdate = false;\n\n let posUpdated = false;\n if (this.cameraOptions.hasOwnProperty('startPos')) {\n const startPosString = comp.startPos;\n if (startPosString) {\n const startPosArray = startPosString.split(' ').map(parseFloat);\n if (startPosArray.length === 3) {\n const [x, y, z] = startPosArray;\n this.camera.position.set(x, y, z);\n posUpdated = true;\n } else {\n console.error('Invalid camera starting position format. Please provide \"x y z\".');\n }\n }\n }\n if (!posUpdated) {\n // default\n this.camera.position.set(0, 0, 1);\n }\n };\n\n /**\n * @function\n * @description Initializes default lighting and shadows for the main scene.\n * @param {object} data - the lights data (color, intensity, shadows, etc)\n */\n initLights = (data) => {\n if (!data.enabled) {\n return;\n }\n this.globalLight = new three__WEBPACK_IMPORTED_MODULE_23__.AmbientLight(data.color);\n this.globalLight.intensity = data.intensity;\n this.globalLight.position.set(0, 5, 0);\n this.scene.add(this.globalLight);\n\n if (!this.isMobile) {\n if (data.shadows) {\n this.shadowLight = new three__WEBPACK_IMPORTED_MODULE_23__.PointLight(data.color);\n this.shadowLight.position.set(-1, 1, 1);\n this.shadowLight.intensity = data.intensity;\n this.shadowLight.castShadow = data.shadows;\n this.shadowLight.shadow.radius = data.radius;\n this.shadowLight.shadow.camera.near = 0.01; // default\n this.shadowLight.shadow.camera.far = 20; // default\n this.shadowLight.shadow.mapSize.set(2048, 2048);\n this.scene.add(this.shadowLight);\n }\n }\n };\n\n /**\n * @function\n * @description De-initializes rendering and MR\n */\n denit() {\n document.body.removeChild(this.renderer.domElement);\n this.removeChild(this.XRButton);\n window.removeEventListener('resize', this.onWindowResize);\n }\n\n /**\n * @function\n * @description Registers a new system addition to the MRApp engine.\n * @param {MRSystem} system - the system to be added.\n */\n registerSystem(system) {\n this.systems.add(system);\n }\n\n /**\n * @function\n * @description Unregisters a system from the MRApp engine.\n * @param {MRSystem} system - the system to be removed.\n */\n unregisterSystem(system) {\n this.systems.delete(system);\n }\n\n /**\n * @function\n * @description Adding an entity as an object in this MRApp engine's scene.\n * @param {MREntity} entity - the entity to be added.\n */\n add(entity) {\n this.origin.add(entity.object3D);\n }\n\n /**\n * @function\n * @description Removing an entity as an object in this MRApp engine's scene.\n * @param {MREntity} entity - the entity to be removed.\n */\n removeEntity(entity) {\n this.origin.remove(entity.object3D);\n }\n\n /**\n * @function\n * @description Handles what is necessary rendering, camera, and user-wise when the viewing window is resized.\n */\n onWindowResize() {\n __webpack_require__.g.appWidth = this.appWidth;\n __webpack_require__.g.appHeight = this.appHeight;\n switch (this.cameraOptions.mode) {\n case 'orthographic':\n __webpack_require__.g.viewPortWidth = this.appWidth / 1000;\n __webpack_require__.g.viewPortHeight = this.appHeight / 1000;\n\n this.camera.left = __webpack_require__.g.viewPortWidth / -2;\n this.camera.right = __webpack_require__.g.viewPortWidth / 2;\n this.camera.top = __webpack_require__.g.viewPortHeight / 2;\n this.camera.bottom = __webpack_require__.g.viewPortHeight / -2;\n break;\n case 'perspective':\n default:\n this.camera.aspect = this.appWidth / this.appHeight;\n __webpack_require__.g.viewPortWidth = __webpack_require__.g.viewPortHeight * this.camera.aspect;\n break;\n }\n this.camera.updateProjectionMatrix();\n this.renderer.setSize(this.appWidth, this.appHeight);\n }\n\n /**\n * @function\n * @description Default function header needed by threejs. The render function that is called during ever frame. Calls every systems' update function.\n * @param {number} timeStamp - timeStamp of the current frame.\n * @param {object} frame - given frame information to be used for any feature changes\n */\n render(timeStamp, frame) {\n // ----- grab important vars ----- //\n\n const deltaTime = this.clock.getDelta();\n\n // ----- If using the threejs stats for 'stats=true' ---- //\n\n if (this.stats) {\n this.stats.update();\n }\n\n // ----- Update needed items ----- //\n\n if (mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.isPresenting && !mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.session) {\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.session = this.renderer.xr.getSession();\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.referenceSpace = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.getReferenceSpace();\n\n this.dispatchEvent(new CustomEvent('enterxr', { bubbles: true }));\n\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.session.addEventListener('end', () => {\n this.camera.position.set(0, 0, 1);\n this.camera.quaternion.set(0, 0, 0, 1);\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.session = undefined;\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.referenceSpace = undefined;\n this.classList.remove('inXR');\n\n this.onWindowResize();\n this.dispatchEvent(new CustomEvent('exitxr', { bubbles: true }));\n });\n }\n\n this.user?.update();\n\n // ----- System Updates ----- //\n\n for (const system of this.systems) {\n system._update(deltaTime, frame);\n }\n\n // ----- Actually Render ----- //\n\n // TODO (in future) - once this gets more complicated, it will be nice to have a render system separate\n // from the pure loop but it is okay as is here for now.\n\n this.scene.updateMatrixWorld();\n if (this.camera.parent === null) {\n this.camera.updateMatrixWorld();\n }\n this.renderer.clear();\n\n // Need to wait until we have all needed rendering-associated systems loaded.\n if (this.maskingSystem !== undefined) {\n this.maskingSystem.sync();\n const currentShadowEnabled = this.renderer.shadowMap.enabled;\n this.renderer.shadowMap.enabled = false;\n this.renderer.render(this.maskingSystem.scene, this.camera);\n this.renderer.shadowMap.enabled = currentShadowEnabled;\n }\n\n this.renderer.render(this.scene, this.camera);\n }\n}\n\ncustomElements.get('mr-app') || customElements.define('mr-app', MRApp);\n\n\n//# sourceURL=webpack://mrjs/./src/core/MRApp.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ MRApp: () => (/* binding */ MRApp)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var three_addons_controls_OrbitControls_js__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(/*! three/addons/controls/OrbitControls.js */ \"./node_modules/three/examples/jsm/controls/OrbitControls.js\");\n/* harmony import */ var three_addons_webxr_XRButton_js__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(/*! three/addons/webxr/XRButton.js */ \"./node_modules/three/examples/jsm/webxr/XRButton.js\");\n/* harmony import */ var stats_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! stats.js */ \"./node_modules/stats.js/build/stats.min.js\");\n/* harmony import */ var stats_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(stats_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var mrjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs */ \"./src/index.js\");\n/* harmony import */ var mrjs_core_MRElement__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! mrjs/core/MRElement */ \"./src/core/MRElement.js\");\n/* harmony import */ var mrjs_core_MREntity__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! mrjs/core/MREntity */ \"./src/core/MREntity.js\");\n/* harmony import */ var mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! mrjs/core/MRSystem */ \"./src/core/MRSystem.js\");\n/* harmony import */ var mrjs_core_user_MRUser__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! mrjs/core/user/MRUser */ \"./src/core/user/MRUser.js\");\n/* harmony import */ var mrjs_core_entities_MRSkyBoxEntity__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! mrjs/core/entities/MRSkyBoxEntity */ \"./src/core/entities/MRSkyBoxEntity.js\");\n/* harmony import */ var mrjs_core_entities_MRStatsEntity__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! mrjs/core/entities/MRStatsEntity */ \"./src/core/entities/MRStatsEntity.js\");\n/* harmony import */ var mrjs_core_componentSystems_AnchorSystem__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! mrjs/core/componentSystems/AnchorSystem */ \"./src/core/componentSystems/AnchorSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_AnimationSystem__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! mrjs/core/componentSystems/AnimationSystem */ \"./src/core/componentSystems/AnimationSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_AudioSystem__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! mrjs/core/componentSystems/AudioSystem */ \"./src/core/componentSystems/AudioSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_BoundaryVisibilitySystem__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! mrjs/core/componentSystems/BoundaryVisibilitySystem */ \"./src/core/componentSystems/BoundaryVisibilitySystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_ClippingSystem__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! mrjs/core/componentSystems/ClippingSystem */ \"./src/core/componentSystems/ClippingSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_ControlSystem__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! mrjs/core/componentSystems/ControlSystem */ \"./src/core/componentSystems/ControlSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_GeometryStyleSystem__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! mrjs/core/componentSystems/GeometryStyleSystem */ \"./src/core/componentSystems/GeometryStyleSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_LayoutSystem__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! mrjs/core/componentSystems/LayoutSystem */ \"./src/core/componentSystems/LayoutSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_MaskingSystem__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! mrjs/core/componentSystems/MaskingSystem */ \"./src/core/componentSystems/MaskingSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_MaterialStyleSystem__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! mrjs/core/componentSystems/MaterialStyleSystem */ \"./src/core/componentSystems/MaterialStyleSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_PanelSystem__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! mrjs/core/componentSystems/PanelSystem */ \"./src/core/componentSystems/PanelSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_PhysicsSystem__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! mrjs/core/componentSystems/PhysicsSystem */ \"./src/core/componentSystems/PhysicsSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_SkyBoxSystem__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(/*! mrjs/core/componentSystems/SkyBoxSystem */ \"./src/core/componentSystems/SkyBoxSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_StatsSystem__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(/*! mrjs/core/componentSystems/StatsSystem */ \"./src/core/componentSystems/StatsSystem.js\");\n/* harmony import */ var mrjs_core_componentSystems_TextSystem__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(/*! mrjs/core/componentSystems/TextSystem */ \"./src/core/componentSystems/TextSystem.js\");\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n('use strict');\nwindow.mobileCheck = function () {\n return mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.display.mobileCheckFunction();\n};\n\n// events that trigger the eventUpdate call for all MRSystems\nconst GLOBAL_UPDATE_EVENTS = ['enterxr', 'exitxr', 'load', 'anchored', 'panelupdate', 'engine-started', 'resize'];\n\n/**\n * @class MRApp\n * @classdesc The engine handler for running MRjs as an App. `mr-app`\n * @augments MRElement\n */\nclass MRApp extends mrjs_core_MRElement__WEBPACK_IMPORTED_MODULE_2__.MRElement {\n /**\n * @class\n * @description Constructs the base information of the app including system, camera, engine, xr, and rendering defaults.\n */\n constructor() {\n super();\n Object.defineProperty(this, 'isApp', {\n value: true,\n writable: false,\n });\n\n this.xrsupport = false;\n this.isMobile = window.mobileCheck(); // resolves true/false\n\n this.inspect = false;\n\n this.clock = new three__WEBPACK_IMPORTED_MODULE_23__.Clock();\n this.systems = new Set();\n this.scene = new three__WEBPACK_IMPORTED_MODULE_23__.Scene();\n this.scene.matrixWorldAutoUpdate = false;\n this.anchor = null;\n this.origin = new three__WEBPACK_IMPORTED_MODULE_23__.Object3D();\n\n this.scene.add(this.origin);\n\n // The rest of the renderer is filled out in this.connectedCallback()-->this.init() since\n // the renderer relies on certain component flags attached to the itself.\n this.renderer = null;\n this.render = this.render.bind(this);\n\n this.lighting = {\n enabled: true,\n color: 0xffffff,\n intensity: 1,\n radius: 5,\n shadows: true,\n };\n\n this.cameraOptions = {\n mode: 'orthographic',\n };\n this.onWindowResize = this.onWindowResize.bind(this);\n }\n\n /**\n * @function\n * @memberof MRApp\n * @returns {number} width in 3d or pixel space (depending on if in xr) of the current open app\n */\n get appWidth() {\n let result = parseFloat(this.compStyle.width.split('px')[0]);\n if (mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.isPresenting) {\n result = (result / window.innerWidth) * mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.display.VIRTUAL_DISPLAY_RESOLUTION;\n }\n return result;\n }\n\n /**\n * @function\n * @memberof MRApp\n * @returns {number} height in 3d or pixel space (depending on if in xr) of the current open app\n */\n get appHeight() {\n let result = parseFloat(this.compStyle.height.split('px')[0]);\n if (mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.isPresenting) {\n result = (result / window.screen.height) * mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.display.VIRTUAL_DISPLAY_RESOLUTION;\n }\n return result;\n }\n\n /**\n * @function Connected\n * @memberof MRApp\n * @description The connectedCallback function that runs whenever this entity component becomes connected to something else.\n */\n connectedCallback() {\n this.compStyle = window.getComputedStyle(this);\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.physics.initializePhysics();\n this.init();\n\n this.observer = new MutationObserver(this.mutationCallback);\n this.observer.observe(this, { attributes: true, childList: true });\n\n // initialize built in Systems\n document.addEventListener('engine-started', (event) => {\n this.user = new mrjs_core_user_MRUser__WEBPACK_IMPORTED_MODULE_5__[\"default\"](this.camera, this.scene);\n\n if (this.dataset.occlusion == 'spotlight') {\n this.scene.add(this.user.initSpotlight());\n }\n\n // order matters for all the below system creation items\n this.panelSystem = new mrjs_core_componentSystems_PanelSystem__WEBPACK_IMPORTED_MODULE_18__.PanelSystem();\n this.layoutSystem = new mrjs_core_componentSystems_LayoutSystem__WEBPACK_IMPORTED_MODULE_15__.LayoutSystem();\n this.textSystem = new mrjs_core_componentSystems_TextSystem__WEBPACK_IMPORTED_MODULE_22__.TextSystem();\n this.geometryStyleSystem = new mrjs_core_componentSystems_GeometryStyleSystem__WEBPACK_IMPORTED_MODULE_14__.GeometryStyleSystem();\n this.materialStyleSystem = new mrjs_core_componentSystems_MaterialStyleSystem__WEBPACK_IMPORTED_MODULE_17__.MaterialStyleSystem();\n this.boundaryVisibilitySystem = new mrjs_core_componentSystems_BoundaryVisibilitySystem__WEBPACK_IMPORTED_MODULE_11__.BoundaryVisibilitySystem();\n this.statsSystem = new mrjs_core_componentSystems_StatsSystem__WEBPACK_IMPORTED_MODULE_21__.StatsSystem();\n this.physicsSystem = new mrjs_core_componentSystems_PhysicsSystem__WEBPACK_IMPORTED_MODULE_19__.PhysicsSystem();\n this.controlSystem = new mrjs_core_componentSystems_ControlSystem__WEBPACK_IMPORTED_MODULE_13__.ControlSystem();\n this.anchorSystem = new mrjs_core_componentSystems_AnchorSystem__WEBPACK_IMPORTED_MODULE_8__.AnchorSystem();\n this.animationSystem = new mrjs_core_componentSystems_AnimationSystem__WEBPACK_IMPORTED_MODULE_9__.AnimationSystem();\n this.skyBoxSystem = new mrjs_core_componentSystems_SkyBoxSystem__WEBPACK_IMPORTED_MODULE_20__.SkyBoxSystem();\n this.audioSystem = new mrjs_core_componentSystems_AudioSystem__WEBPACK_IMPORTED_MODULE_10__.AudioSystem();\n\n // These must be the last three systems since\n // they affect rendering. Clipping must happen\n // before masking. Rendering must be the last step.\n this.clippingSystem = new mrjs_core_componentSystems_ClippingSystem__WEBPACK_IMPORTED_MODULE_12__.ClippingSystem();\n this.maskingSystem = new mrjs_core_componentSystems_MaskingSystem__WEBPACK_IMPORTED_MODULE_16__.MaskingSystem();\n });\n\n this.addEventListener('entityadded', (event) => {\n for (const system of this.systems) {\n system._onNewEntity(event.target);\n }\n });\n\n document.addEventListener('entityremoved', async (event) => {\n for (const system of this.systems) {\n system._entityRemoved(event.detail.entity);\n }\n\n while (event.detail.entity.object3D.parent) {\n event.detail.entity.object3D.removeFromParent();\n }\n });\n\n // Call `eventUpdate` on all systems if any of the global events are triggered\n for (const eventType of GLOBAL_UPDATE_EVENTS) {\n document.addEventListener(eventType, (event) => {\n for (const system of this.systems) {\n system.eventUpdate();\n }\n });\n }\n }\n\n /**\n * @function Disconnected\n * @memberof MRApp\n * @description The disconnectedCallback function that runs whenever this entity component becomes connected to something else.\n */\n disconnectedCallback() {\n this.denit();\n this.observer.disconnect();\n }\n\n // TODO: These are for toggling debug and app level flags in realtime.\n // Currently only 'debug' is implemented. but we should add:\n // - stats\n // - lighting\n // - controllers\n // - ?\n /**\n * @function\n * @param {object} mutation - TODO\n */\n mutatedAttribute(mutation) {}\n\n /**\n * @function\n * @param {object} mutation - TODO\n */\n mutatedChildList(mutation) {}\n\n /**\n * @function\n * @description The mutationCallback function that runs whenever this entity component should be mutated.\n * @param {object} mutationList - the list of update/change/mutation(s) to be handled.\n * @param {object} observer - w3 standard object that watches for changes on the HTMLElement\n */\n mutationCallback = (mutationList, observer) => {\n for (const mutation of mutationList) {\n if (mutation.type === 'childList') {\n this.mutatedChildList(mutation);\n }\n if (mutation.type === 'attributes') {\n this.mutatedAttribute(mutation);\n }\n }\n };\n\n /**\n * @function\n * @description Initializes the engine state for the MRApp. This function is run whenever the MRApp is connected.\n */\n init() {\n window.addEventListener('resize', this.onWindowResize);\n\n this.debug = this.dataset.debug ?? false;\n\n /* --- Renderer Setup --- */\n\n this.renderer = new three__WEBPACK_IMPORTED_MODULE_23__.WebGLRenderer({\n antialias: true,\n alpha: true,\n // There's issues in the timing to enable taking screenshots of threejs scenes unless you have direct access to the code.\n // Using the preserveDrawingBuffer to ignore timing issues is the best approach instead. Though this has a performance hit,\n // we're allowing it to be enabled by users when necessary.\n //\n // References:\n // https://stackoverflow.com/questions/15558418/how-do-you-save-an-image-from-a-three-js-canvas\n // https://stackoverflow.com/questions/30628064/how-to-toggle-preservedrawingbuffer-in-three-js\n preserveDrawingBuffer: this.dataset.preserveDrawingBuffer ?? false,\n });\n this.renderer.setPixelRatio(window.devicePixelRatio);\n this.renderer.setSize(this.appWidth, this.appHeight);\n this.renderer.autoClear = false;\n this.renderer.shadowMap.enabled = true;\n this.renderer.xr.enabled = true;\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr = this.renderer.xr;\n this.renderer.toneMapping = three__WEBPACK_IMPORTED_MODULE_23__.ACESFilmicToneMapping;\n this.renderer.toneMappingExposure = 1;\n this.renderer.localClippingEnabled = true;\n\n this.appendChild(this.renderer.domElement);\n\n this.renderer.setAnimationLoop(this.render);\n\n /* --- Camera Setup --- */\n\n this.initCamera();\n\n const layersString = this.dataset.layers;\n if (layersString) {\n this.layers = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(layersString);\n\n for (const layer of this.layers) {\n this.camera.layers.enable(layer);\n }\n }\n\n const orbitalOptionsString = this.dataset.orbital;\n let orbitalOptions = {};\n if (orbitalOptionsString) {\n orbitalOptions = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToJson(orbitalOptionsString);\n }\n this.orbital = orbitalOptions.mode ?? false;\n if (this.debug || this.orbital) {\n const orbitControls = new three_addons_controls_OrbitControls_js__WEBPACK_IMPORTED_MODULE_24__.OrbitControls(this.camera, this.renderer.domElement);\n orbitControls.minDistance = 1;\n orbitControls.maxDistance = 2;\n\n // set target location if requested\n if (orbitalOptions.targetPos) {\n if (orbitalOptions.targetPos.length !== 3) {\n console.error('Invalid orbital target position format. Please provide \"x y z\".');\n }\n orbitControls.target.set(orbitalOptions.targetPos[0], orbitalOptions.targetPos[1], orbitalOptions.targetPos[2]);\n orbitControls.update();\n }\n\n // Note: order of the two below if-statements matter.\n // Want if both debug=true and orbital=true for orbital to take priority.\n if (this.orbital) {\n // always allow orbital controls\n orbitControls.enabled = true;\n } else if (this.debug) {\n // only allow orbital controls on += keypress\n orbitControls.enabled = false;\n document.addEventListener('keydown', (event) => {\n if (event.key == '=') {\n orbitControls.enabled = true;\n }\n });\n document.addEventListener('keyup', (event) => {\n if (event.key == '=') {\n orbitControls.enabled = false;\n }\n });\n }\n }\n\n /* --- Lighting Setup --- */\n\n if (this.dataset.lighting ?? false) {\n this.lighting = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToJson(this.dataset.lighting);\n }\n this.initLights(this.lighting);\n\n /* --- Stats Setup --- */\n\n if (this.dataset.stats ?? false) {\n // Old version of stats using the Stats.js visual\n // setup. Leaving to allow for top left quick visual of stats.\n // Is /not/ performant in headset. Documentation notes this.\n //\n this.stats = new (stats_js__WEBPACK_IMPORTED_MODULE_0___default())();\n this.stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom\n document.body.appendChild(this.stats.dom);\n }\n\n /* --- Background Setup --- */\n\n // allows for mr-app style to have background:value to set the skybox\n if (this.compStyle.backgroundImage !== 'none') {\n let skybox = new mrjs_core_entities_MRSkyBoxEntity__WEBPACK_IMPORTED_MODULE_6__.MRSkyBoxEntity();\n let imageUrl = this.compStyle.backgroundImage.match(/url\\(\"?(.+?)\"?\\)/)[1];\n skybox.setAttribute('src', imageUrl);\n this.appendChild(skybox);\n\n // Need to zero out the background-image property otherwise\n // we'll end up with a canvas background as well as the skybox\n // when the canvas background is not needed in this 3d setup.\n //\n // We can do this because panel backgrounds are actual webpage\n // backgrounds and the app itself's background is separate from\n // that, being understood as the skybox of the entire app itself.\n this.style.setProperty('background-image', 'none', 'important');\n this.compStyle = window.getComputedStyle(this);\n }\n\n /* --- Mobile VS XR Setup --- */\n\n // We don't support mobile XR yet\n if (!this.isMobile) {\n navigator.xr?.isSessionSupported('immersive-ar').then((supported) => {\n this.xrsupport = supported;\n\n if (this.xrsupport) {\n this.XRButton = three_addons_webxr_XRButton_js__WEBPACK_IMPORTED_MODULE_25__.XRButton.createButton(this.renderer, {\n requiredFeatures: ['local', 'hand-tracking'],\n optionalFeatures: ['hit-test', 'anchors', 'plane-detection'],\n });\n\n this.XRButton.addEventListener('click', () => {\n this.classList.add('inXR');\n this.XRButton.blur();\n });\n document.body.appendChild(this.XRButton);\n\n this.XRButton.style.position = 'fixed';\n this.XRButton.style.zIndex = 10000;\n }\n });\n }\n }\n\n /**\n * @function\n * @description Initializes the user information for the MRApp including appropriate HMD direction and camera information and the default scene anchor location.\n */\n initCamera = () => {\n const cameraOptionsString = this.dataset.camera ?? '';\n if (cameraOptionsString) {\n Object.assign(this.cameraOptions, mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToJson(this.cameraOptionString) ?? {});\n }\n\n __webpack_require__.g.appWidth = this.appWidth;\n __webpack_require__.g.appHeight = this.appHeight;\n\n switch (this.cameraOptions.mode) {\n case 'orthographic':\n __webpack_require__.g.viewPortWidth = this.appWidth / 1000;\n __webpack_require__.g.viewPortHeight = this.appHeight / 1000;\n\n // In an orthographic camera, unlike perspective, objects are rendered at the same scale regardless of their\n // distance from the camera, meaning near and far clipping planes are more about what objects are visible in\n // terms of their distance from the camera, rather than affecting the size of the objects.\n this.camera = new three__WEBPACK_IMPORTED_MODULE_23__.OrthographicCamera(__webpack_require__.g.viewPortWidth / -2, __webpack_require__.g.viewPortWidth / 2, __webpack_require__.g.viewPortHeight / 2, __webpack_require__.g.viewPortHeight / -2, 0.01, 1000);\n break;\n case 'perspective':\n default:\n this.camera = new three__WEBPACK_IMPORTED_MODULE_23__.PerspectiveCamera(70, this.appWidth / this.appHeight, 0.01, 20);\n this.vFOV = three__WEBPACK_IMPORTED_MODULE_23__.MathUtils.degToRad(this.camera.fov);\n __webpack_require__.g.viewPortHeight = 2 * Math.tan(this.vFOV / 2);\n __webpack_require__.g.viewPortWidth = __webpack_require__.g.viewPortHeight * this.camera.aspect;\n break;\n }\n this.camera.matrixWorldAutoUpdate = false;\n\n let posUpdated = false;\n if (this.cameraOptions.hasOwnProperty('startPos')) {\n const startPosString = comp.startPos;\n if (startPosString) {\n const startPosArray = startPosString.split(' ').map(parseFloat);\n if (startPosArray.length === 3) {\n const [x, y, z] = startPosArray;\n this.camera.position.set(x, y, z);\n posUpdated = true;\n } else {\n console.error('Invalid camera starting position format. Please provide \"x y z\".');\n }\n }\n }\n if (!posUpdated) {\n // default\n this.camera.position.set(0, 0, 1);\n }\n };\n\n /**\n * @function\n * @description Initializes default lighting and shadows for the main scene.\n * @param {object} data - the lights data (color, intensity, shadows, etc)\n */\n initLights = (data) => {\n if (!data.enabled) {\n return;\n }\n this.globalLight = new three__WEBPACK_IMPORTED_MODULE_23__.AmbientLight(data.color);\n this.globalLight.intensity = data.intensity;\n this.globalLight.position.set(0, 5, 0);\n this.scene.add(this.globalLight);\n\n if (!this.isMobile) {\n if (data.shadows) {\n this.shadowLight = new three__WEBPACK_IMPORTED_MODULE_23__.PointLight(data.color);\n this.shadowLight.position.set(-1, 1, 1);\n this.shadowLight.intensity = data.intensity;\n this.shadowLight.castShadow = data.shadows;\n this.shadowLight.shadow.radius = data.radius;\n this.shadowLight.shadow.camera.near = 0.01; // default\n this.shadowLight.shadow.camera.far = 20; // default\n this.shadowLight.shadow.mapSize.set(2048, 2048);\n this.scene.add(this.shadowLight);\n }\n }\n };\n\n /**\n * @function\n * @description De-initializes rendering and MR\n */\n denit() {\n document.body.removeChild(this.renderer.domElement);\n this.removeChild(this.XRButton);\n window.removeEventListener('resize', this.onWindowResize);\n }\n\n /**\n * @function\n * @description Registers a new system addition to the MRApp engine.\n * @param {MRSystem} system - the system to be added.\n */\n registerSystem(system) {\n this.systems.add(system);\n }\n\n /**\n * @function\n * @description Unregisters a system from the MRApp engine.\n * @param {MRSystem} system - the system to be removed.\n */\n unregisterSystem(system) {\n this.systems.delete(system);\n }\n\n /**\n * @function\n * @description Adding an entity as an object in this MRApp engine's scene.\n * @param {MREntity} entity - the entity to be added.\n */\n add(entity) {\n this.origin.add(entity.object3D);\n }\n\n /**\n * @function\n * @description Removing an entity as an object in this MRApp engine's scene.\n * @param {MREntity} entity - the entity to be removed.\n */\n removeEntity(entity) {\n this.origin.remove(entity.object3D);\n }\n\n /**\n * @function\n * @description Handles what is necessary rendering, camera, and user-wise when the viewing window is resized.\n */\n onWindowResize() {\n __webpack_require__.g.appWidth = this.appWidth;\n __webpack_require__.g.appHeight = this.appHeight;\n switch (this.cameraOptions.mode) {\n case 'orthographic':\n __webpack_require__.g.viewPortWidth = this.appWidth / 1000;\n __webpack_require__.g.viewPortHeight = this.appHeight / 1000;\n\n this.camera.left = __webpack_require__.g.viewPortWidth / -2;\n this.camera.right = __webpack_require__.g.viewPortWidth / 2;\n this.camera.top = __webpack_require__.g.viewPortHeight / 2;\n this.camera.bottom = __webpack_require__.g.viewPortHeight / -2;\n break;\n case 'perspective':\n default:\n this.camera.aspect = this.appWidth / this.appHeight;\n __webpack_require__.g.viewPortWidth = __webpack_require__.g.viewPortHeight * this.camera.aspect;\n break;\n }\n this.camera.updateProjectionMatrix();\n this.renderer.setSize(this.appWidth, this.appHeight);\n }\n\n /**\n * @function\n * @description Default function header needed by threejs. The render function that is called during ever frame. Calls every systems' update function.\n * @param {number} timeStamp - timeStamp of the current frame.\n * @param {object} frame - given frame information to be used for any feature changes\n */\n render(timeStamp, frame) {\n // ----- grab important vars ----- //\n\n const deltaTime = this.clock.getDelta();\n\n // ----- If using the threejs stats for 'stats=true' ---- //\n\n if (this.stats) {\n this.stats.update();\n }\n\n // ----- Update needed items ----- //\n\n if (mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.isPresenting && !mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.session) {\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.session = this.renderer.xr.getSession();\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.referenceSpace = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.getReferenceSpace();\n\n this.dispatchEvent(new CustomEvent('enterxr', { bubbles: true }));\n\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.session.addEventListener('end', () => {\n this.camera.position.set(0, 0, 1);\n this.camera.quaternion.set(0, 0, 0, 1);\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.session = undefined;\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.referenceSpace = undefined;\n this.classList.remove('inXR');\n\n this.onWindowResize();\n this.dispatchEvent(new CustomEvent('exitxr', { bubbles: true }));\n });\n }\n\n this.user?.update();\n\n // ----- System Updates ----- //\n\n for (const system of this.systems) {\n system._update(deltaTime, frame);\n }\n\n // ----- Actually Render ----- //\n\n // TODO (in future) - once this gets more complicated, it will be nice to have a render system separate\n // from the pure loop but it is okay as is here for now.\n\n this.scene.updateMatrixWorld();\n if (this.camera.parent === null) {\n this.camera.updateMatrixWorld();\n }\n this.renderer.clear();\n\n // Need to wait until we have all needed rendering-associated systems loaded.\n if (this.maskingSystem !== undefined && this.maskingSystem.scene.length > 0) {\n this.maskingSystem.sync();\n const currentShadowEnabled = this.renderer.shadowMap.enabled;\n this.renderer.shadowMap.enabled = false;\n this.renderer.render(this.maskingSystem.scene, this.camera);\n this.renderer.shadowMap.enabled = currentShadowEnabled;\n }\n\n this.renderer.render(this.scene, this.camera);\n }\n}\n\ncustomElements.get('mr-app') || customElements.define('mr-app', MRApp);\n\n\n//# sourceURL=webpack://mrjs/./src/core/MRApp.js?"); /***/ }), @@ -531,7 +531,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ ClippingSystem: () => (/* binding */ ClippingSystem)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/MRSystem */ \"./src/core/MRSystem.js\");\n/* harmony import */ var mrjs_core_MREntity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs/core/MREntity */ \"./src/core/MREntity.js\");\n/* harmony import */ var mrjs_dataTypes_MRClippingGeometry__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! mrjs/dataTypes/MRClippingGeometry */ \"./src/dataTypes/MRClippingGeometry.js\");\n/* harmony import */ var _entities_MRVolumeEntity__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../entities/MRVolumeEntity */ \"./src/core/entities/MRVolumeEntity.js\");\n/* harmony import */ var _entities_MRModelEntity__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../entities/MRModelEntity */ \"./src/core/entities/MRModelEntity.js\");\n\n\n\n\n\n\n\n\n\nconst PLANE_NUM = 6;\n\n/**\n * @class ClippingSystem\n * @classdesc This system supports 3D clipping following threejs's clipping planes setup.\n * See https://threejs.org/docs/?q=material#api/en/materials/Material.clippingPlanes for more information.\n * @augments MRSystem\n */\nclass ClippingSystem extends mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_0__.MRSystem {\n /**\n * @class\n * @description ClippingSystem's default constructor that sets up coplanar points and the default clipping information.\n */\n constructor() {\n super(false);\n // Using coplanar points to calculate the orientation and position of the plane.\n // They're here to be reused for every plane so we're not generating a ton of vectors\n // to be garbage collected.\n this.coplanarPointA = new three__WEBPACK_IMPORTED_MODULE_5__.Vector3();\n this.coplanarPointB = new three__WEBPACK_IMPORTED_MODULE_5__.Vector3();\n this.coplanarPointC = new three__WEBPACK_IMPORTED_MODULE_5__.Vector3();\n }\n\n /**\n * @function\n * @description The generic system update call. Updates the clipped view of every entity in this system's registry.\n * @param {number} deltaTime - given timestep to be used for any feature changes\n * @param {object} frame - given frame information to be used for any feature changes\n */\n update(deltaTime, frame) {\n for (const entity of this.registry) {\n this.updatePlanes(entity);\n }\n }\n\n /**\n * @function\n * @description Updates the stored clipping planes to be based on the passed in entity.\n * @param {MREntity} entity - given entity that will be used to create the clipping planes positioning.\n */\n updatePlanes(entity) {\n // clipping.geometry is one segment BoxGeometry. See MRClippingGeometry.\n\n const geometry = entity.clipping.geometry;\n const positions = geometry.getAttribute('position').array;\n const indices = geometry.getIndex().array;\n\n for (let i = 0; i < PLANE_NUM; i++) {\n const indexOffset = i * 6;\n const positionIndexA = indices[indexOffset] * 3;\n const positionIndexB = indices[indexOffset + 1] * 3;\n const positionIndexC = indices[indexOffset + 2] * 3;\n\n this.coplanarPointA.set(-positions[positionIndexA], -positions[positionIndexA + 1], -positions[positionIndexA + 2]);\n this.coplanarPointB.set(-positions[positionIndexB], -positions[positionIndexB + 1], -positions[positionIndexB + 2]);\n this.coplanarPointC.set(-positions[positionIndexC], -positions[positionIndexC + 1], -positions[positionIndexC + 2]);\n\n if (entity instanceof _entities_MRVolumeEntity__WEBPACK_IMPORTED_MODULE_3__.MRVolumeEntity) {\n entity.volume.localToWorld(this.coplanarPointA);\n entity.volume.localToWorld(this.coplanarPointB);\n entity.volume.localToWorld(this.coplanarPointC);\n } else {\n entity.panel.localToWorld(this.coplanarPointA);\n entity.panel.localToWorld(this.coplanarPointB);\n entity.panel.localToWorld(this.coplanarPointC);\n }\n\n entity.clipping.planes[i].setFromCoplanarPoints(this.coplanarPointA, this.coplanarPointB, this.coplanarPointC);\n }\n }\n\n /**\n * @function\n * @description Helper method for `onNewEntity`. Actually applies the clipping planes to the material setup for rendering.\n * Uses threejs in the background following https://threejs.org/docs/?q=material#api/en/materials/Material.clippingPlanes\n * @param {MREntity} entity - the entity to be clipped\n * @param {MRClippingGeometry} clipping - the clipping information to be passed to the material\n */\n applyClipping(entity, clipping) {\n // only apply clipping planes to entities that arent masked through the stencil\n // since doubling up on that is redundant and not helpful for runtime\n if (!entity.ignoreStencil) {\n return;\n }\n\n entity.traverseObjects((object) => {\n if (object.isMesh) {\n object.material.clippingPlanes = clipping.planes;\n object.material.clipIntersection = clipping.intersection;\n }\n });\n }\n\n /**\n * @function\n * @description Helper method for `onNewEntity`. Creates a clipping planes information (still writing this description)\n * @param {MREntity} entity - the entity to which we're adding the clipping planes information\n */\n addClippingPlanes(entity) {\n for (let i = 0; i < PLANE_NUM; i++) {\n const newPlane = new three__WEBPACK_IMPORTED_MODULE_5__.Plane();\n\n // if (this.app.debug) {\n // const helper = new THREE.PlaneHelper( newPlane, 1, 0xff00ff );\n // this.app.scene.add( helper );\n // }\n\n entity.clipping.planes.push(newPlane);\n }\n this.updatePlanes(entity);\n }\n\n // TODO: polish and move this into MRSystem\n /**\n * @function\n * @description a function called when a specific entity has an event update\n * @param {Event} e - the event generated by the entity\n */\n entityEventUpdate = (e) => {\n let entity = e.target;\n for (const parent of this.registry) {\n if (parent.contains(entity)) {\n this.applyClipping(entity, parent.clipping);\n }\n }\n };\n\n /**\n * @function\n * @description When the system swaps to a new entity, this handles applying the clipping planes as needed in the system run.\n * @param {MREntity} entity - given entity that will be clipped by the planes.\n */\n onNewEntity(entity) {\n if (!entity.ignoreStencil) {\n // only apply clipping planes to entities that arent masked through the stencil\n // since doubling up on that is redundant and not helpful for runtime\n return;\n }\n\n if (!entity.clipping) {\n for (const parent of this.registry) {\n if (parent.contains(entity)) {\n entity.traverse((child) => {\n this.applyClipping(child, parent.clipping);\n });\n if (entity instanceof _entities_MRModelEntity__WEBPACK_IMPORTED_MODULE_4__.MRModelEntity) {\n entity.addEventListener('modelchange', this.entityEventUpdate);\n }\n }\n }\n return;\n }\n\n this.registry.add(entity);\n this.addClippingPlanes(entity);\n entity.traverse((child) => {\n if (entity === child) {\n return;\n }\n this.applyClipping(child, entity.clipping);\n if (entity instanceof _entities_MRModelEntity__WEBPACK_IMPORTED_MODULE_4__.MRModelEntity) {\n entity.addEventListener('modelchange', this.entityEventUpdate);\n }\n });\n }\n}\n\n\n//# sourceURL=webpack://mrjs/./src/core/componentSystems/ClippingSystem.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ ClippingSystem: () => (/* binding */ ClippingSystem)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/MRSystem */ \"./src/core/MRSystem.js\");\n/* harmony import */ var mrjs_core_MREntity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs/core/MREntity */ \"./src/core/MREntity.js\");\n/* harmony import */ var mrjs_dataTypes_MRClippingGeometry__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! mrjs/dataTypes/MRClippingGeometry */ \"./src/dataTypes/MRClippingGeometry.js\");\n/* harmony import */ var _entities_MRVolumeEntity__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../entities/MRVolumeEntity */ \"./src/core/entities/MRVolumeEntity.js\");\n/* harmony import */ var _entities_MRModelEntity__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../entities/MRModelEntity */ \"./src/core/entities/MRModelEntity.js\");\n\n\n\n\n\n\n\n\n\nconst PLANE_NUM = 6;\n\n/**\n * @class ClippingSystem\n * @classdesc This system supports 3D clipping following threejs's clipping planes setup.\n * See https://threejs.org/docs/?q=material#api/en/materials/Material.clippingPlanes for more information.\n * @augments MRSystem\n */\nclass ClippingSystem extends mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_0__.MRSystem {\n /**\n * @class\n * @description ClippingSystem's default constructor that sets up coplanar points and the default clipping information.\n */\n constructor() {\n super(false);\n // Using coplanar points to calculate the orientation and position of the plane.\n // They're here to be reused for every plane so we're not generating a ton of vectors\n // to be garbage collected.\n this.coplanarPointA = new three__WEBPACK_IMPORTED_MODULE_5__.Vector3();\n this.coplanarPointB = new three__WEBPACK_IMPORTED_MODULE_5__.Vector3();\n this.coplanarPointC = new three__WEBPACK_IMPORTED_MODULE_5__.Vector3();\n }\n\n /**\n * @function\n * @description The generic system update call. Updates the clipped view of every entity in this system's registry.\n * @param {number} deltaTime - given timestep to be used for any feature changes\n * @param {object} frame - given frame information to be used for any feature changes\n */\n update(deltaTime, frame) {\n for (const entity of this.registry) {\n if (entity.visible) {\n this.updatePlanes(entity);\n }\n }\n }\n\n /**\n * @function\n * @description Updates the stored clipping planes to be based on the passed in entity.\n * @param {MREntity} entity - given entity that will be used to create the clipping planes positioning.\n */\n updatePlanes(entity) {\n // clipping.geometry is one segment BoxGeometry. See MRClippingGeometry.\n\n const geometry = entity.clipping.geometry;\n const positions = geometry.getAttribute('position').array;\n const indices = geometry.getIndex().array;\n\n for (let i = 0; i < PLANE_NUM; i++) {\n const indexOffset = i * 6;\n const positionIndexA = indices[indexOffset] * 3;\n const positionIndexB = indices[indexOffset + 1] * 3;\n const positionIndexC = indices[indexOffset + 2] * 3;\n\n this.coplanarPointA.set(-positions[positionIndexA], -positions[positionIndexA + 1], -positions[positionIndexA + 2]);\n this.coplanarPointB.set(-positions[positionIndexB], -positions[positionIndexB + 1], -positions[positionIndexB + 2]);\n this.coplanarPointC.set(-positions[positionIndexC], -positions[positionIndexC + 1], -positions[positionIndexC + 2]);\n\n if (entity instanceof _entities_MRVolumeEntity__WEBPACK_IMPORTED_MODULE_3__.MRVolumeEntity) {\n entity.volume.localToWorld(this.coplanarPointA);\n entity.volume.localToWorld(this.coplanarPointB);\n entity.volume.localToWorld(this.coplanarPointC);\n } else {\n entity.panel.localToWorld(this.coplanarPointA);\n entity.panel.localToWorld(this.coplanarPointB);\n entity.panel.localToWorld(this.coplanarPointC);\n }\n\n entity.clipping.planes[i].setFromCoplanarPoints(this.coplanarPointA, this.coplanarPointB, this.coplanarPointC);\n }\n }\n\n /**\n * @function\n * @description Helper method for `onNewEntity`. Actually applies the clipping planes to the material setup for rendering.\n * Uses threejs in the background following https://threejs.org/docs/?q=material#api/en/materials/Material.clippingPlanes\n * @param {MREntity} entity - the entity to be clipped\n * @param {MRClippingGeometry} clipping - the clipping information to be passed to the material\n */\n applyClipping(entity, clipping) {\n // only apply clipping planes to entities that arent masked through the stencil\n // since doubling up on that is redundant and not helpful for runtime\n if (!entity.ignoreStencil) {\n return;\n }\n\n entity.traverseObjects((object) => {\n if (object.isMesh) {\n object.material.clippingPlanes = clipping.planes;\n object.material.clipIntersection = clipping.intersection;\n }\n });\n }\n\n /**\n * @function\n * @description Helper method for `onNewEntity`. Creates a clipping planes information (still writing this description)\n * @param {MREntity} entity - the entity to which we're adding the clipping planes information\n */\n addClippingPlanes(entity) {\n for (let i = 0; i < PLANE_NUM; i++) {\n const newPlane = new three__WEBPACK_IMPORTED_MODULE_5__.Plane();\n\n // if (this.app.debug) {\n // const helper = new THREE.PlaneHelper( newPlane, 1, 0xff00ff );\n // this.app.scene.add( helper );\n // }\n\n entity.clipping.planes.push(newPlane);\n }\n this.updatePlanes(entity);\n }\n\n // TODO: polish and move this into MRSystem\n /**\n * @function\n * @description a function called when a specific entity has an event update\n * @param {Event} e - the event generated by the entity\n */\n entityEventUpdate = (e) => {\n let entity = e.target;\n for (const parent of this.registry) {\n if (parent.contains(entity)) {\n this.applyClipping(entity, parent.clipping);\n }\n }\n };\n\n /**\n * @function\n * @description When the system swaps to a new entity, this handles applying the clipping planes as needed in the system run.\n * @param {MREntity} entity - given entity that will be clipped by the planes.\n */\n onNewEntity(entity) {\n if (!entity.ignoreStencil) {\n // only apply clipping planes to entities that arent masked through the stencil\n // since doubling up on that is redundant and not helpful for runtime\n return;\n }\n\n if (!entity.clipping) {\n for (const parent of this.registry) {\n if (parent.contains(entity)) {\n entity.traverse((child) => {\n this.applyClipping(child, parent.clipping);\n });\n if (entity instanceof _entities_MRModelEntity__WEBPACK_IMPORTED_MODULE_4__.MRModelEntity) {\n entity.addEventListener('modelchange', this.entityEventUpdate);\n }\n }\n }\n return;\n }\n\n this.registry.add(entity);\n this.addClippingPlanes(entity);\n entity.traverse((child) => {\n if (entity === child) {\n return;\n }\n this.applyClipping(child, entity.clipping);\n if (entity instanceof _entities_MRModelEntity__WEBPACK_IMPORTED_MODULE_4__.MRModelEntity) {\n entity.addEventListener('modelchange', this.entityEventUpdate);\n }\n });\n }\n}\n\n\n//# sourceURL=webpack://mrjs/./src/core/componentSystems/ClippingSystem.js?"); /***/ }), @@ -542,7 +542,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ ControlSystem: () => (/* binding */ ControlSystem)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/MRSystem */ \"./src/core/MRSystem.js\");\n/* harmony import */ var mrjs_core_user_MRHand__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs/core/user/MRHand */ \"./src/core/user/MRHand.js\");\n/* harmony import */ var mrjs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! mrjs */ \"./src/index.js\");\n\n\n\n\n\n\n\n/**\n * @class ControlSystem\n * @classdesc This system supports interaction event information including mouse and controller interfacing.\n * @augments MRSystem\n */\nclass ControlSystem extends mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_0__.MRSystem {\n /**\n * @class\n * @description ControlSystem's Default constructor that sets up the app's mouse information along with any relevant physics and cursor information.\n */\n constructor() {\n super(false);\n this.activeHand = this.app.user.hands.left;\n\n document.addEventListener('selectstart', (event) => {\n if (event.detail == null) {\n return;\n }\n if (event.detail?.handedness == 'left') {\n this.activeHand = this.app.user.hands.left;\n } else {\n this.activeHand = this.app.user.hands.right;\n }\n\n this.down = true;\n this.cursorViz.material.color.setStyle('blue');\n this.onMouseDown(event);\n });\n\n document.addEventListener('selectend', (event) => {\n if (event.detail.handedness == null) {\n return;\n }\n this.down = false;\n this.cursorViz.material.color.setStyle('black');\n\n this.onMouseUp(event);\n });\n\n this.origin = new three__WEBPACK_IMPORTED_MODULE_3__.Vector3();\n this.direction = new three__WEBPACK_IMPORTED_MODULE_3__.Vector3();\n this.ray = new mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.RAPIER.Ray({ x: 0.0, y: 0.0, z: 0.0 }, { x: 0.0, y: 1.0, z: 0.0 });\n this.hit;\n\n this.restPosition = new three__WEBPACK_IMPORTED_MODULE_3__.Vector3(1000, 1000, 1000);\n this.hitPosition = new three__WEBPACK_IMPORTED_MODULE_3__.Vector3();\n this.hitNormal = new three__WEBPACK_IMPORTED_MODULE_3__.Vector3();\n\n this.tempWorldPosition = new three__WEBPACK_IMPORTED_MODULE_3__.Vector3();\n this.tempLocalPosition = new three__WEBPACK_IMPORTED_MODULE_3__.Vector3();\n this.tempPreviousPosition = new three__WEBPACK_IMPORTED_MODULE_3__.Vector3();\n this.touchDelta = new three__WEBPACK_IMPORTED_MODULE_3__.Vector3();\n\n this.directTouch = false;\n\n this.currentEntity = null;\n\n this.cursorViz = new three__WEBPACK_IMPORTED_MODULE_3__.Mesh(new three__WEBPACK_IMPORTED_MODULE_3__.RingGeometry(0.005, 0.007, 32), new three__WEBPACK_IMPORTED_MODULE_3__.MeshBasicMaterial({ color: 0x000000, opacity: 0.7, transparent: true }));\n\n this.app.scene.add(this.cursorViz);\n this.cursorViz.visible = false;\n\n this.down = false;\n\n this.app.renderer.domElement.addEventListener('mousedown', this.onMouseDown);\n this.app.renderer.domElement.addEventListener('mouseup', this.onMouseUp);\n this.app.renderer.domElement.addEventListener('mousemove', this.mouseOver);\n this.app.renderer.domElement.addEventListener('mouseover', this.mouseOver);\n\n this.app.renderer.domElement.addEventListener('touchstart', this.onMouseDown);\n this.app.renderer.domElement.addEventListener('touchend', this.onMouseUp);\n this.app.renderer.domElement.addEventListener('touchmove', this.mouseOver);\n this.app.renderer.domElement.addEventListener('touch', this.mouseOver);\n }\n\n /**\n * @function\n * @description The generic system update call. Updates the meshes and states for both the left and right hand visuals.\n * @param {number} deltaTime - given timestep to be used for any feature changes\n * @param {object} frame - given frame information to be used for any feature changes\n */\n update(deltaTime, frame) {\n if (!mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.xr.isPresenting) {\n return;\n }\n\n if (!this.down) {\n mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.eventQueue.drainCollisionEvents((handle1, handle2, started) => {\n /* Handle the collision event. */\n if (started) {\n this.onContactStart(handle1, handle2);\n } else {\n this.onContactEnd(handle1, handle2);\n }\n });\n\n this.checkCollisions(this.app.user.hands.left);\n this.checkCollisions(this.app.user.hands.right);\n }\n\n if (!this.directTouch) {\n this.pointerRay();\n } else if (this.cursorViz.visible) {\n this.clearPointer();\n }\n this.directTouch = false;\n }\n\n /**\n * @function\n * @description Check for any collisions with this MRHand and the rapier physics world.\n * @param {object} hand - the MRHand object whose collisions we are checking with this function.\n */\n checkCollisions(hand) {\n for (let jointCursor of hand.jointCursors) {\n mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.world.contactPairsWith(jointCursor.collider, (collider2) => {\n const entity = mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.COLLIDER_ENTITY_MAP[collider2.handle];\n\n if (entity) {\n this.directTouch = true;\n this.tempPreviousPosition.copy(this.tempLocalPosition);\n\n this.tempLocalPosition.copy(jointCursor.collider.translation());\n entity.object3D.worldToLocal(this.tempLocalPosition);\n\n this.touchDelta.subVectors(this.tempLocalPosition, this.tempPreviousPosition);\n\n if (!jointCursor.name.includes('hover') && entity.touch) {\n entity.dispatchEvent(\n new CustomEvent('touchmove', {\n bubbles: true,\n detail: {\n joint: jointCursor.name,\n worldPosition: jointCursor.collider.translation(),\n position: this.tempLocalPosition,\n delta: this.touchDelta,\n },\n })\n );\n } else if (jointCursor.name.includes('hover') && !entity.touch) {\n entity.dispatchEvent(\n new CustomEvent('hovermove', {\n bubbles: true,\n detail: {\n joint: jointCursor.name,\n worldPosition: jointCursor.collider.translation(),\n position: this.tempLocalPosition,\n delta: this.touchDelta,\n },\n })\n );\n }\n }\n });\n }\n }\n\n /**\n * @function\n * @description Handles the start of collisions between two different colliders.\n * @param {number} handle1 - the first collider\n * @param {number} handle2 - the second collider\n */\n onContactStart = (handle1, handle2) => {\n const collider1 = mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.world.colliders.get(handle1);\n const collider2 = mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.world.colliders.get(handle2);\n\n const joint = mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.INPUT_COLLIDER_HANDLE_NAMES[handle1];\n const entity = mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.COLLIDER_ENTITY_MAP[handle2];\n\n if (!joint || !entity) {\n return;\n }\n if (!joint.includes('hover')) {\n this.touchStart(collider1, collider2, entity);\n } else {\n this.hoverStart(collider1, collider2, entity);\n }\n };\n\n /**\n * @function\n * @description Handles the end of collisions between two different colliders.\n * @param {number} handle1 - the first collider\n * @param {number} handle2 - the second collider\n */\n onContactEnd = (handle1, handle2) => {\n const joint = mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.INPUT_COLLIDER_HANDLE_NAMES[handle1];\n const entity = mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.COLLIDER_ENTITY_MAP[handle2];\n\n if (!joint || !entity) {\n return;\n }\n if (!joint.includes('hover')) {\n this.touchEnd(entity);\n } else {\n this.hoverEnd(entity);\n }\n };\n\n /**\n * @function\n * @description Handles the start of touch between two different colliders and the current entity.\n * @param {number} collider1 - the first collider\n * @param {number} collider2 - the second collider\n * @param {object} entity - the current entity\n */\n touchStart = (collider1, collider2, entity) => {\n entity.touch = true;\n mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.world.contactPair(collider1, collider2, (manifold, flipped) => {\n this.tempLocalPosition.copy(manifold.localContactPoint2(0));\n this.tempWorldPosition.copy(manifold.localContactPoint2(0));\n entity.object3D.localToWorld(this.tempWorldPosition);\n entity.classList.remove('hover');\n\n entity.dispatchEvent(\n new CustomEvent('touchstart', {\n bubbles: true,\n detail: {\n worldPosition: this.tempWorldPosition,\n position: this.tempLocalPosition,\n },\n })\n );\n });\n };\n\n /**\n * @function\n * @description Handles the end of touch for the current entity\n * @param {object} entity - the current entity\n */\n touchEnd = (entity) => {\n this.tempPreviousPosition.set(0, 0, 0);\n this.tempLocalPosition.set(0, 0, 0);\n this.tempWorldPosition.set(0, 0, 0);\n entity.touch = false;\n entity.click();\n\n entity.dispatchEvent(\n new CustomEvent('touchend', {\n bubbles: true,\n })\n );\n };\n\n /**\n * @function\n * @description Handles the start of hovering over/around a specific entity.\n * @param {number} collider1 - the first collider\n * @param {number} collider2 - the second collider\n * @param {object} entity - the current entity\n */\n hoverStart = (collider1, collider2, entity) => {\n entity.classList.add('hover');\n mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.world.contactPair(collider1, collider2, (manifold, flipped) => {\n this.tempLocalPosition.copy(manifold.localContactPoint2(0));\n this.tempWorldPosition.copy(manifold.localContactPoint2(0));\n entity.object3D.localToWorld(this.tempWorldPosition);\n entity.dispatchEvent(\n new CustomEvent('hoverstart', {\n bubbles: true,\n detail: {\n worldPosition: this.tempWorldPosition,\n position: this.tempLocalPosition,\n },\n })\n );\n\n entity.dispatchEvent(new MouseEvent('mouseover'));\n });\n };\n\n /**\n * @function\n * @description Handles the end of hovering over/around a specific entity.\n * @param {object} entity - the current entity\n */\n hoverEnd = (entity) => {\n entity.classList.remove('hover');\n entity.dispatchEvent(\n new CustomEvent('hoverend', {\n bubbles: true,\n })\n );\n\n entity.dispatchEvent(new MouseEvent('mouseout'));\n };\n\n /**\n * @function\n * @description Fills in the this.origin,direction,ray, and hit values based on the rapier world\n */\n pointerRay() {\n this.origin.setFromMatrixPosition(this.app.user.origin.matrixWorld);\n this.direction.setFromMatrixPosition(this.activeHand.pointer.matrixWorld).sub(this.origin).normalize();\n\n this.ray.origin = { ...this.origin };\n this.ray.dir = { ...this.direction };\n\n this.hit = mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.world.castRayAndGetNormal(this.ray, 100, true, null, mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.CollisionGroups.USER, null, null);\n if (this.hit != null) {\n this.hitPosition.copy(this.ray.pointAt(this.hit.toi));\n this.hitNormal.copy(this.hit.normal);\n this.cursorViz.visible = true;\n this.cursorViz.position.copy(this.hitPosition);\n\n this.cursorViz.quaternion.setFromUnitVectors(new three__WEBPACK_IMPORTED_MODULE_3__.Vector3(0, 0, 1), this.hit.normal);\n\n this.interact(mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.COLLIDER_ENTITY_MAP[this.hit.collider.handle]);\n } else {\n this.cursorViz.visible = false;\n }\n }\n\n /**\n * @function\n * @description clears the gaze/pinch pointer from the scene\n */\n clearPointer() {\n this.cursorViz.visible = false;\n if (this.currentEntity) {\n this.currentEntity.focus = false;\n this.hoverEndEvents();\n }\n\n this.currentEntity = null;\n }\n\n /************ Interaction Events ************/\n\n /**\n * @function\n * @description Handles the mouse over event\n * @param {event} event - the mouse over event\n */\n mouseOver = (event) => {\n this.hit = this.pixelRayCast(event);\n\n if (this.hit != null) {\n this.hitPosition.copy(this.ray.pointAt(this.hit.toi));\n }\n\n this.interact(mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.COLLIDER_ENTITY_MAP[this.hit?.collider.handle]);\n };\n\n /**\n * @function\n * @description Handles the mouse down event\n * @param {event} event - the mouse down event\n */\n onMouseDown = (event) => {\n this.down = true;\n this.currentEntity?.classList.remove('hover');\n this.currentEntity?.classList.add('active');\n\n this.currentEntity?.dispatchEvent(\n new CustomEvent('hoverend', {\n bubbles: true,\n })\n );\n\n this.currentEntity?.dispatchEvent(\n new CustomEvent('touchstart', {\n bubbles: true,\n detail: {\n worldPosition: this.hitPosition,\n },\n })\n );\n };\n\n /**\n * @function\n * @description Handles the mouse up event\n * @param {event} event - the mouse up event\n */\n onMouseUp = (event) => {\n this.down = false;\n this.currentEntity?.classList.remove('active');\n this.currentEntity?.dispatchEvent(new Event('click'));\n\n this.currentEntity?.dispatchEvent(\n new CustomEvent('touchend', {\n bubbles: true,\n })\n );\n\n this.hoverStartEvents();\n };\n\n /**\n * @function\n * @description Checks what kind of interactions should happen based on the current entity and any events that\n * have happened so far.\n * @param {object} entity - checking if there is any interaction required based on current events and this entity.\n */\n interact(entity) {\n if (!this.down && this.currentEntity != entity) {\n if (this.currentEntity) {\n this.currentEntity.focus = false;\n this.hoverEndEvents();\n }\n\n this.currentEntity = null;\n }\n\n if (!entity) {\n return;\n }\n\n if (this.down) {\n this.currentEntity?.dispatchEvent(\n new CustomEvent('touchmove', {\n bubbles: true,\n detail: {\n worldPosition: this.hitPosition,\n },\n })\n );\n return;\n }\n\n if (!this.currentEntity) {\n this.currentEntity = entity;\n this.currentEntity.focus = true;\n this.hoverStartEvents();\n }\n }\n\n hoverStartEvents = () => {\n this.currentEntity?.classList.add('hover');\n this.currentEntity?.dispatchEvent(\n new MouseEvent('mouseover', {\n bubbles: true,\n })\n );\n this.currentEntity?.dispatchEvent(\n new CustomEvent('hoverstart', {\n bubbles: true,\n })\n );\n\n // TODO: this will require slightly more complex logic to implement correctly\n // this.currentEntity.dispatchEvent(\n // new MouseEvent('mouseenter', {\n // bubbles: false,\n // })\n // );\n };\n\n hoverEndEvents = () => {\n this.currentEntity?.classList.remove('hover');\n this.currentEntity?.dispatchEvent(\n new MouseEvent('mouseout', {\n bubbles: true,\n })\n );\n\n this.currentEntity?.dispatchEvent(\n new CustomEvent('hoverend', {\n bubbles: true,\n })\n );\n\n // TODO: this will require slightly more complex logic to implement correctly\n // this.currentEntity.dispatchEvent(\n // new MouseEvent('mouseout', {\n // bubbles: false,\n // })\n // );\n };\n\n /************ Tools && Helpers ************/\n\n /**\n * @function\n * @description Raycast into the scene using the information from the event that called it.\n * @param {object} event - the event being handled\n * @returns {object} - collision item for what the ray hit in the 3d scene.\n */\n pixelRayCast(event) {\n let x = 0;\n let y = 0;\n if (event.type.includes('touchmove')) {\n if (event.touches.length == 0) {\n return null;\n }\n\n x = event.touches[0].clientX;\n y = event.touches[0].clientY;\n } else {\n x = event.clientX;\n y = event.clientY;\n }\n\n if (this.app.camera instanceof three__WEBPACK_IMPORTED_MODULE_3__.OrthographicCamera) {\n this.direction.set((x / this.app.appWidth) * 2 - 1, -(y / this.app.appHeight) * 2 + 1, -1); // z = - 1 important!\n this.direction.unproject(this.app.camera);\n const direction = new three__WEBPACK_IMPORTED_MODULE_3__.Vector3(0, 0, -1);\n direction.transformDirection(this.app.camera.matrixWorld);\n\n this.ray.origin = { ...this.direction };\n this.ray.dir = { ...direction };\n } else {\n this.direction.set((x / this.app.appWidth) * 2 - 1, -(y / this.app.appHeight) * 2 + 1, 0.5);\n this.direction.unproject(this.app.camera);\n this.direction.sub(this.app.camera.position).normalize();\n this.ray.origin = { ...this.app.camera.position };\n this.ray.dir = { ...this.direction };\n }\n\n return mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.world.castRayAndGetNormal(this.ray, 100, true, null, mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.CollisionGroups.USER, null, null);\n }\n}\n\n\n//# sourceURL=webpack://mrjs/./src/core/componentSystems/ControlSystem.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ ControlSystem: () => (/* binding */ ControlSystem)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/MRSystem */ \"./src/core/MRSystem.js\");\n/* harmony import */ var mrjs_core_user_MRHand__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs/core/user/MRHand */ \"./src/core/user/MRHand.js\");\n/* harmony import */ var mrjs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! mrjs */ \"./src/index.js\");\n\n\n\n\n\n\n\n/**\n * @class ControlSystem\n * @classdesc This system supports interaction event information including mouse and controller interfacing.\n * @augments MRSystem\n */\nclass ControlSystem extends mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_0__.MRSystem {\n /**\n * @class\n * @description ControlSystem's Default constructor that sets up the app's mouse information along with any relevant physics and cursor information.\n */\n constructor() {\n super(false);\n this.activeHand = this.app.user.hands.left;\n\n document.addEventListener('selectstart', (event) => {\n if (event.detail == null) {\n return;\n }\n if (event.detail?.handedness == 'left') {\n this.activeHand = this.app.user.hands.left;\n } else {\n this.activeHand = this.app.user.hands.right;\n }\n\n this.down = true;\n this.cursorViz.material.color.setStyle('blue');\n this.onMouseDown(event);\n });\n\n document.addEventListener('selectend', (event) => {\n if (event.detail.handedness == null) {\n return;\n }\n this.down = false;\n this.cursorViz.material.color.setStyle('black');\n\n this.onMouseUp(event);\n });\n\n this.origin = new three__WEBPACK_IMPORTED_MODULE_3__.Vector3();\n this.direction = new three__WEBPACK_IMPORTED_MODULE_3__.Vector3();\n this.ray = new mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.RAPIER.Ray({ x: 0.0, y: 0.0, z: 0.0 }, { x: 0.0, y: 1.0, z: 0.0 });\n this.hit;\n\n this.restPosition = new three__WEBPACK_IMPORTED_MODULE_3__.Vector3(1000, 1000, 1000);\n this.hitPosition = new three__WEBPACK_IMPORTED_MODULE_3__.Vector3();\n this.hitNormal = new three__WEBPACK_IMPORTED_MODULE_3__.Vector3();\n\n this.tempWorldPosition = new three__WEBPACK_IMPORTED_MODULE_3__.Vector3();\n this.tempLocalPosition = new three__WEBPACK_IMPORTED_MODULE_3__.Vector3();\n this.tempPreviousPosition = new three__WEBPACK_IMPORTED_MODULE_3__.Vector3();\n this.touchDelta = new three__WEBPACK_IMPORTED_MODULE_3__.Vector3();\n\n this.directTouch = false;\n\n this.currentEntity = null;\n\n const cursorMaterial = mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.material.MeshBasicMaterial.clone();\n cursorMaterial.color.set(0x000000);\n cursorMaterial.opacity = 0.7;\n cursorMaterial.transparent = true;\n cursorMaterial.name = 'cursorMaterial';\n this.cursorViz = new three__WEBPACK_IMPORTED_MODULE_3__.Mesh(new three__WEBPACK_IMPORTED_MODULE_3__.RingGeometry(0.005, 0.007, 32), cursorMaterial);\n\n this.app.scene.add(this.cursorViz);\n this.cursorViz.visible = false;\n\n this.down = false;\n\n this.app.renderer.domElement.addEventListener('mousedown', this.onMouseDown);\n this.app.renderer.domElement.addEventListener('mouseup', this.onMouseUp);\n this.app.renderer.domElement.addEventListener('mousemove', this.mouseOver);\n this.app.renderer.domElement.addEventListener('mouseover', this.mouseOver);\n\n this.app.renderer.domElement.addEventListener('touchstart', this.onMouseDown);\n this.app.renderer.domElement.addEventListener('touchend', this.onMouseUp);\n this.app.renderer.domElement.addEventListener('touchmove', this.mouseOver);\n this.app.renderer.domElement.addEventListener('touch', this.mouseOver);\n }\n\n /**\n * @function\n * @description The generic system update call. Updates the meshes and states for both the left and right hand visuals.\n * @param {number} deltaTime - given timestep to be used for any feature changes\n * @param {object} frame - given frame information to be used for any feature changes\n */\n update(deltaTime, frame) {\n if (!mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.xr.isPresenting) {\n return;\n }\n\n if (!this.down) {\n mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.eventQueue.drainCollisionEvents((handle1, handle2, started) => {\n /* Handle the collision event. */\n if (started) {\n this.onContactStart(handle1, handle2);\n } else {\n this.onContactEnd(handle1, handle2);\n }\n });\n\n this.checkCollisions(this.app.user.hands.left);\n this.checkCollisions(this.app.user.hands.right);\n }\n\n if (!this.directTouch) {\n this.pointerRay();\n } else if (this.cursorViz.visible) {\n this.clearPointer();\n }\n this.directTouch = false;\n }\n\n /**\n * @function\n * @description Check for any collisions with this MRHand and the rapier physics world.\n * @param {object} hand - the MRHand object whose collisions we are checking with this function.\n */\n checkCollisions(hand) {\n for (let jointCursor of hand.jointCursors) {\n mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.world.contactPairsWith(jointCursor.collider, (collider2) => {\n const entity = mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.COLLIDER_ENTITY_MAP[collider2.handle];\n\n if (entity) {\n this.directTouch = true;\n this.tempPreviousPosition.copy(this.tempLocalPosition);\n\n this.tempLocalPosition.copy(jointCursor.collider.translation());\n entity.object3D.worldToLocal(this.tempLocalPosition);\n\n this.touchDelta.subVectors(this.tempLocalPosition, this.tempPreviousPosition);\n\n if (!jointCursor.name.includes('hover') && entity.touch) {\n entity.dispatchEvent(\n new CustomEvent('touchmove', {\n bubbles: true,\n detail: {\n joint: jointCursor.name,\n worldPosition: jointCursor.collider.translation(),\n position: this.tempLocalPosition,\n delta: this.touchDelta,\n },\n })\n );\n } else if (jointCursor.name.includes('hover') && !entity.touch) {\n entity.dispatchEvent(\n new CustomEvent('hovermove', {\n bubbles: true,\n detail: {\n joint: jointCursor.name,\n worldPosition: jointCursor.collider.translation(),\n position: this.tempLocalPosition,\n delta: this.touchDelta,\n },\n })\n );\n }\n }\n });\n }\n }\n\n /**\n * @function\n * @description Handles the start of collisions between two different colliders.\n * @param {number} handle1 - the first collider\n * @param {number} handle2 - the second collider\n */\n onContactStart = (handle1, handle2) => {\n const collider1 = mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.world.colliders.get(handle1);\n const collider2 = mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.world.colliders.get(handle2);\n\n const joint = mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.INPUT_COLLIDER_HANDLE_NAMES[handle1];\n const entity = mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.COLLIDER_ENTITY_MAP[handle2];\n\n if (!joint || !entity) {\n return;\n }\n if (!joint.includes('hover')) {\n this.touchStart(collider1, collider2, entity);\n } else {\n this.hoverStart(collider1, collider2, entity);\n }\n };\n\n /**\n * @function\n * @description Handles the end of collisions between two different colliders.\n * @param {number} handle1 - the first collider\n * @param {number} handle2 - the second collider\n */\n onContactEnd = (handle1, handle2) => {\n const joint = mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.INPUT_COLLIDER_HANDLE_NAMES[handle1];\n const entity = mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.COLLIDER_ENTITY_MAP[handle2];\n\n if (!joint || !entity) {\n return;\n }\n if (!joint.includes('hover')) {\n this.touchEnd(entity);\n } else {\n this.hoverEnd(entity);\n }\n };\n\n /**\n * @function\n * @description Handles the start of touch between two different colliders and the current entity.\n * @param {number} collider1 - the first collider\n * @param {number} collider2 - the second collider\n * @param {object} entity - the current entity\n */\n touchStart = (collider1, collider2, entity) => {\n entity.touch = true;\n mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.world.contactPair(collider1, collider2, (manifold, flipped) => {\n this.tempLocalPosition.copy(manifold.localContactPoint2(0));\n this.tempWorldPosition.copy(manifold.localContactPoint2(0));\n entity.object3D.localToWorld(this.tempWorldPosition);\n entity.classList.remove('hover');\n\n entity.dispatchEvent(\n new CustomEvent('touchstart', {\n bubbles: true,\n detail: {\n worldPosition: this.tempWorldPosition,\n position: this.tempLocalPosition,\n },\n })\n );\n });\n };\n\n /**\n * @function\n * @description Handles the end of touch for the current entity\n * @param {object} entity - the current entity\n */\n touchEnd = (entity) => {\n this.tempPreviousPosition.set(0, 0, 0);\n this.tempLocalPosition.set(0, 0, 0);\n this.tempWorldPosition.set(0, 0, 0);\n entity.touch = false;\n entity.click();\n\n entity.dispatchEvent(\n new CustomEvent('touchend', {\n bubbles: true,\n })\n );\n };\n\n /**\n * @function\n * @description Handles the start of hovering over/around a specific entity.\n * @param {number} collider1 - the first collider\n * @param {number} collider2 - the second collider\n * @param {object} entity - the current entity\n */\n hoverStart = (collider1, collider2, entity) => {\n entity.classList.add('hover');\n mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.world.contactPair(collider1, collider2, (manifold, flipped) => {\n this.tempLocalPosition.copy(manifold.localContactPoint2(0));\n this.tempWorldPosition.copy(manifold.localContactPoint2(0));\n entity.object3D.localToWorld(this.tempWorldPosition);\n entity.dispatchEvent(\n new CustomEvent('hoverstart', {\n bubbles: true,\n detail: {\n worldPosition: this.tempWorldPosition,\n position: this.tempLocalPosition,\n },\n })\n );\n\n entity.dispatchEvent(new MouseEvent('mouseover'));\n });\n };\n\n /**\n * @function\n * @description Handles the end of hovering over/around a specific entity.\n * @param {object} entity - the current entity\n */\n hoverEnd = (entity) => {\n entity.classList.remove('hover');\n entity.dispatchEvent(\n new CustomEvent('hoverend', {\n bubbles: true,\n })\n );\n\n entity.dispatchEvent(new MouseEvent('mouseout'));\n };\n\n /**\n * @function\n * @description Fills in the this.origin,direction,ray, and hit values based on the rapier world\n */\n pointerRay() {\n this.origin.setFromMatrixPosition(this.app.user.origin.matrixWorld);\n this.direction.setFromMatrixPosition(this.activeHand.pointer.matrixWorld).sub(this.origin).normalize();\n\n this.ray.origin = { ...this.origin };\n this.ray.dir = { ...this.direction };\n\n this.hit = mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.world.castRayAndGetNormal(this.ray, 100, true, null, mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.CollisionGroups.USER, null, null);\n if (this.hit != null) {\n this.hitPosition.copy(this.ray.pointAt(this.hit.toi));\n this.hitNormal.copy(this.hit.normal);\n this.cursorViz.visible = true;\n this.cursorViz.position.copy(this.hitPosition);\n\n this.cursorViz.quaternion.setFromUnitVectors(new three__WEBPACK_IMPORTED_MODULE_3__.Vector3(0, 0, 1), this.hit.normal);\n\n this.interact(mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.COLLIDER_ENTITY_MAP[this.hit.collider.handle]);\n } else {\n this.cursorViz.visible = false;\n }\n }\n\n /**\n * @function\n * @description clears the gaze/pinch pointer from the scene\n */\n clearPointer() {\n this.cursorViz.visible = false;\n if (this.currentEntity) {\n this.currentEntity.focus = false;\n this.hoverEndEvents();\n }\n\n this.currentEntity = null;\n }\n\n /************ Interaction Events ************/\n\n /**\n * @function\n * @description Handles the mouse over event\n * @param {event} event - the mouse over event\n */\n mouseOver = (event) => {\n this.hit = this.pixelRayCast(event);\n\n if (this.hit != null) {\n this.hitPosition.copy(this.ray.pointAt(this.hit.toi));\n }\n\n this.interact(mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.COLLIDER_ENTITY_MAP[this.hit?.collider.handle]);\n };\n\n /**\n * @function\n * @description Handles the mouse down event\n * @param {event} event - the mouse down event\n */\n onMouseDown = (event) => {\n this.down = true;\n this.currentEntity?.classList.remove('hover');\n this.currentEntity?.classList.add('active');\n\n this.currentEntity?.dispatchEvent(\n new CustomEvent('hoverend', {\n bubbles: true,\n })\n );\n\n this.currentEntity?.dispatchEvent(\n new CustomEvent('touchstart', {\n bubbles: true,\n detail: {\n worldPosition: this.hitPosition,\n },\n })\n );\n };\n\n /**\n * @function\n * @description Handles the mouse up event\n * @param {event} event - the mouse up event\n */\n onMouseUp = (event) => {\n this.down = false;\n this.currentEntity?.classList.remove('active');\n this.currentEntity?.dispatchEvent(new Event('click'));\n\n this.currentEntity?.dispatchEvent(\n new CustomEvent('touchend', {\n bubbles: true,\n })\n );\n\n this.hoverStartEvents();\n };\n\n /**\n * @function\n * @description Checks what kind of interactions should happen based on the current entity and any events that\n * have happened so far.\n * @param {object} entity - checking if there is any interaction required based on current events and this entity.\n */\n interact(entity) {\n if (!this.down && this.currentEntity != entity) {\n if (this.currentEntity) {\n this.currentEntity.focus = false;\n this.hoverEndEvents();\n }\n\n this.currentEntity = null;\n }\n\n if (!entity) {\n return;\n }\n\n if (this.down) {\n this.currentEntity?.dispatchEvent(\n new CustomEvent('touchmove', {\n bubbles: true,\n detail: {\n worldPosition: this.hitPosition,\n },\n })\n );\n return;\n }\n\n if (!this.currentEntity) {\n this.currentEntity = entity;\n this.currentEntity.focus = true;\n this.hoverStartEvents();\n }\n }\n\n hoverStartEvents = () => {\n this.currentEntity?.classList.add('hover');\n this.currentEntity?.dispatchEvent(\n new MouseEvent('mouseover', {\n bubbles: true,\n })\n );\n this.currentEntity?.dispatchEvent(\n new CustomEvent('hoverstart', {\n bubbles: true,\n })\n );\n\n // TODO: this will require slightly more complex logic to implement correctly\n // this.currentEntity.dispatchEvent(\n // new MouseEvent('mouseenter', {\n // bubbles: false,\n // })\n // );\n };\n\n hoverEndEvents = () => {\n this.currentEntity?.classList.remove('hover');\n this.currentEntity?.dispatchEvent(\n new MouseEvent('mouseout', {\n bubbles: true,\n })\n );\n\n this.currentEntity?.dispatchEvent(\n new CustomEvent('hoverend', {\n bubbles: true,\n })\n );\n\n // TODO: this will require slightly more complex logic to implement correctly\n // this.currentEntity.dispatchEvent(\n // new MouseEvent('mouseout', {\n // bubbles: false,\n // })\n // );\n };\n\n /************ Tools && Helpers ************/\n\n /**\n * @function\n * @description Raycast into the scene using the information from the event that called it.\n * @param {object} event - the event being handled\n * @returns {object} - collision item for what the ray hit in the 3d scene.\n */\n pixelRayCast(event) {\n let x = 0;\n let y = 0;\n if (event.type.includes('touchmove')) {\n if (event.touches.length == 0) {\n return null;\n }\n\n x = event.touches[0].clientX;\n y = event.touches[0].clientY;\n } else {\n x = event.clientX;\n y = event.clientY;\n }\n\n if (this.app.camera instanceof three__WEBPACK_IMPORTED_MODULE_3__.OrthographicCamera) {\n this.direction.set((x / this.app.appWidth) * 2 - 1, -(y / this.app.appHeight) * 2 + 1, -1); // z = - 1 important!\n this.direction.unproject(this.app.camera);\n const direction = new three__WEBPACK_IMPORTED_MODULE_3__.Vector3(0, 0, -1);\n direction.transformDirection(this.app.camera.matrixWorld);\n\n this.ray.origin = { ...this.direction };\n this.ray.dir = { ...direction };\n } else {\n this.direction.set((x / this.app.appWidth) * 2 - 1, -(y / this.app.appHeight) * 2 + 1, 0.5);\n this.direction.unproject(this.app.camera);\n this.direction.sub(this.app.camera.position).normalize();\n this.ray.origin = { ...this.app.camera.position };\n this.ray.dir = { ...this.direction };\n }\n\n return mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.world.castRayAndGetNormal(this.ray, 100, true, null, mrjs__WEBPACK_IMPORTED_MODULE_2__.mrjsUtils.physics.CollisionGroups.USER, null, null);\n }\n}\n\n\n//# sourceURL=webpack://mrjs/./src/core/componentSystems/ControlSystem.js?"); /***/ }), @@ -564,7 +564,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ InstancingSystem: () => (/* binding */ InstancingSystem)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/MRSystem */ \"./src/core/MRSystem.js\");\n/* harmony import */ var mrjs_core_MREntity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs/core/MREntity */ \"./src/core/MREntity.js\");\n\n\n\n\n\n/**\n * @class InstancingSystem\n * @classdesc System that allows for instancing of meshes based on a given entity where the instances can be modified separately.\n * @augments MRSystem\n */\nclass InstancingSystem extends mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_0__.MRSystem {\n /**\n * @class\n * @description InstancingSystem's default constructor that sets up default instancing count, transformations, and mesh information.\n */\n constructor() {\n super();\n\n this.instanceCount = 5;\n this.transformations = [];\n this.instancedMesh = null;\n }\n\n /**\n * @function\n * @description The generic system update call. Updates the entity and its instances to their appropriate transformations and visuals\n * based on the picked predefined option.\n * @param {number} deltaTime - given timestep to be used for any feature changes\n * @param {object} frame - given frame information to be used for any feature changes\n */\n update(deltaTime, frame) {\n for (const entity of this.registry) {\n switch (entity.components.get('instancing')?.type) {\n case 'random':\n this.random(entity);\n break;\n\n default:\n break;\n }\n }\n }\n\n /**\n * @function\n * @description Determines what meshes are attached from this entity and When a component is attached.\n * Setups up instancing based on the predefined setup option and the entity's geometry (handling properly whether that be a group or mesh).\n * @param {MREntity} entity - the entity with the geometry to be instanced and the chosen setup option\n */\n attachedComponent(entity) {\n // ----- setup for instanced geometry -----\n\n let originalMesh = entity.object3D;\n let combinedGeometry = new three__WEBPACK_IMPORTED_MODULE_2__.BufferGeometry();\n\n // grab usable mesh\n if (originalMesh instanceof three__WEBPACK_IMPORTED_MODULE_2__.Mesh) {\n combinedGeometry = originalMesh.geometry.clone();\n } else if (originalMesh instanceof three__WEBPACK_IMPORTED_MODULE_2__.Group) {\n originalMesh.traverse((child) => {\n if (child instanceof three__WEBPACK_IMPORTED_MODULE_2__.Mesh) {\n const geometry = child.geometry.clone();\n geometry.applyMatrix4(child.matrixWorld); // Apply the child's world matrix\n combinedGeometry.merge(geometry);\n }\n });\n }\n\n // ----- create instances information -----\n\n // Setup for the to-be-used instance\n const instancedGeometry = new three__WEBPACK_IMPORTED_MODULE_2__.InstancedBufferGeometry();\n instancedGeometry.copy(combinedGeometry);\n for (let i = 0; i < this.instanceCount; ++i) {\n const matrix = new three__WEBPACK_IMPORTED_MODULE_2__.Matrix4();\n matrix.makeTranslation(Math.random() * 10 - 5, Math.random() * 10 - 5, Math.random() * 10 - 5);\n\n this.transformations.push(matrix);\n }\n\n // ----- add instances to scene -----\n\n // Create an InstancedMesh using the instanced geometry and matrices\n const material = new three__WEBPACK_IMPORTED_MODULE_2__.MeshBasicMaterial({ color: 0xffff00 });\n const instancedMesh = new three__WEBPACK_IMPORTED_MODULE_2__.InstancedMesh(instancedGeometry, material, this.instanceCount);\n instancedMesh.instanceMatrix.setUsage(three__WEBPACK_IMPORTED_MODULE_2__.DynamicDrawUsage);\n\n // Set matrices for instances\n for (let i = 0; i < this.instanceCount; ++i) {\n instancedMesh.setMatrixAt(i, this.transformations[i]);\n }\n instancedMesh.instanceMatrix.needsUpdate = true;\n this.instancedMesh = instancedMesh;\n\n // Add the instanced mesh to the scene\n entity.object3D.add(instancedMesh);\n }\n\n /************ Some options for default instancing setup ************/\n\n /**\n * @function\n * @description An option for default instancing. Places the given entity instancing it at a bunch of random transformation locations.Uses threejs's `InstancedMesh`.\n * @param {MREntity} entity - the entity to be instanced in random locations\n */\n random = (entity) => {\n // update mesh for each instance\n for (let i = 0; i < this.instanceCount; ++i) {\n const matrix = new three__WEBPACK_IMPORTED_MODULE_2__.Matrix4();\n matrix.makeScale(Math.random() * 10 - 5, Math.random() * 10 - 5, Math.random() * 10 - 5);\n matrix.makeRotation(Math.random() * 10 - 5, Math.random() * 10 - 5, Math.random() * 10 - 5);\n matrix.makeTranslation(Math.random() * 10 - 5, Math.random() * 10 - 5, Math.random() * 10 - 5);\n\n this.transformations[i].copy(matrix);\n this.instancedMesh.setMatrixAt(i, this.transformations[i]);\n }\n };\n}\n\n\n//# sourceURL=webpack://mrjs/./src/core/componentSystems/InstancingSystem.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ InstancingSystem: () => (/* binding */ InstancingSystem)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/MRSystem */ \"./src/core/MRSystem.js\");\n/* harmony import */ var mrjs_core_MREntity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs/core/MREntity */ \"./src/core/MREntity.js\");\n\n\n\n\n\n/**\n * @class InstancingSystem\n * @classdesc System that allows for instancing of meshes based on a given entity where the instances can be modified separately.\n * @augments MRSystem\n */\nclass InstancingSystem extends mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_0__.MRSystem {\n /**\n * @class\n * @description InstancingSystem's default constructor that sets up default instancing count, transformations, and mesh information.\n */\n constructor() {\n super();\n\n this.instanceCount = 5;\n this.transformations = [];\n this.instancedMesh = null;\n }\n\n /**\n * @function\n * @description The generic system update call. Updates the entity and its instances to their appropriate transformations and visuals\n * based on the picked predefined option.\n * @param {number} deltaTime - given timestep to be used for any feature changes\n * @param {object} frame - given frame information to be used for any feature changes\n */\n update(deltaTime, frame) {\n for (const entity of this.registry) {\n switch (entity.components.get('instancing')?.type) {\n case 'random':\n this.random(entity);\n break;\n\n default:\n break;\n }\n }\n }\n\n /**\n * @function\n * @description Determines what meshes are attached from this entity and When a component is attached.\n * Setups up instancing based on the predefined setup option and the entity's geometry (handling properly whether that be a group or mesh).\n * @param {MREntity} entity - the entity with the geometry to be instanced and the chosen setup option\n */\n attachedComponent(entity) {\n // ----- setup for instanced geometry -----\n\n let originalMesh = entity.object3D;\n let combinedGeometry = new three__WEBPACK_IMPORTED_MODULE_2__.BufferGeometry();\n\n // grab usable mesh\n if (originalMesh instanceof three__WEBPACK_IMPORTED_MODULE_2__.Mesh) {\n combinedGeometry = originalMesh.geometry.clone();\n } else if (originalMesh instanceof three__WEBPACK_IMPORTED_MODULE_2__.Group) {\n originalMesh.traverse((child) => {\n if (child instanceof three__WEBPACK_IMPORTED_MODULE_2__.Mesh) {\n const geometry = child.geometry.clone();\n geometry.applyMatrix4(child.matrixWorld); // Apply the child's world matrix\n combinedGeometry.merge(geometry);\n }\n });\n }\n\n // ----- create instances information -----\n\n // Setup for the to-be-used instance\n const instancedGeometry = new three__WEBPACK_IMPORTED_MODULE_2__.InstancedBufferGeometry();\n instancedGeometry.copy(combinedGeometry);\n for (let i = 0; i < this.instanceCount; ++i) {\n const matrix = new three__WEBPACK_IMPORTED_MODULE_2__.Matrix4();\n matrix.makeTranslation(Math.random() * 10 - 5, Math.random() * 10 - 5, Math.random() * 10 - 5);\n\n this.transformations.push(matrix);\n }\n\n // ----- add instances to scene -----\n\n // Create an InstancedMesh using the instanced geometry and matrices\n const material = mrjsUtils.material.MeshBasicMaterial.clone();\n material.color.set(0xffff00);\n const instancedMesh = new three__WEBPACK_IMPORTED_MODULE_2__.InstancedMesh(instancedGeometry, material, this.instanceCount);\n instancedMesh.instanceMatrix.setUsage(three__WEBPACK_IMPORTED_MODULE_2__.DynamicDrawUsage);\n\n // Set matrices for instances\n for (let i = 0; i < this.instanceCount; ++i) {\n instancedMesh.setMatrixAt(i, this.transformations[i]);\n }\n instancedMesh.instanceMatrix.needsUpdate = true;\n this.instancedMesh = instancedMesh;\n\n // Add the instanced mesh to the scene\n entity.object3D.add(instancedMesh);\n }\n\n /************ Some options for default instancing setup ************/\n\n /**\n * @function\n * @description An option for default instancing. Places the given entity instancing it at a bunch of random transformation locations.Uses threejs's `InstancedMesh`.\n * @param {MREntity} entity - the entity to be instanced in random locations\n */\n random = (entity) => {\n // update mesh for each instance\n for (let i = 0; i < this.instanceCount; ++i) {\n const matrix = new three__WEBPACK_IMPORTED_MODULE_2__.Matrix4();\n matrix.makeScale(Math.random() * 10 - 5, Math.random() * 10 - 5, Math.random() * 10 - 5);\n matrix.makeRotation(Math.random() * 10 - 5, Math.random() * 10 - 5, Math.random() * 10 - 5);\n matrix.makeTranslation(Math.random() * 10 - 5, Math.random() * 10 - 5, Math.random() * 10 - 5);\n\n this.transformations[i].copy(matrix);\n this.instancedMesh.setMatrixAt(i, this.transformations[i]);\n }\n };\n}\n\n\n//# sourceURL=webpack://mrjs/./src/core/componentSystems/InstancingSystem.js?"); /***/ }), @@ -586,7 +586,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ MaskingSystem: () => (/* binding */ MaskingSystem)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/MRSystem */ \"./src/core/MRSystem.js\");\n/* harmony import */ var mrjs_core_entities_MRDivEntity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs/core/entities/MRDivEntity */ \"./src/core/entities/MRDivEntity.js\");\n/* harmony import */ var mrjs_core_MREntity__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! mrjs/core/MREntity */ \"./src/core/MREntity.js\");\n/* harmony import */ var mrjs_core_entities_MRPanelEntity__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! mrjs/core/entities/MRPanelEntity */ \"./src/core/entities/MRPanelEntity.js\");\n/* harmony import */ var mrjs_core_entities_MRTextEntity__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! mrjs/core/entities/MRTextEntity */ \"./src/core/entities/MRTextEntity.js\");\n\n\n\n\n\n\n\n\n/*\n * A system that handles elements that mask other elements by using stencil.\n * Eg: A Panel does not display child elements if the elements are positioned\n * outside the Panel's bounds.\n *\n * We use two rendering passes, one for writing to stencil buffer and another one\n * is for rendering the result with testing stencil. Basic idea is the following.\n *\n * 1. Have a scene, called stencil scene here to distinguish from the main scene,\n * for writing to stencil buffer\n * 2. When an entity that mask others is added\n * 2-1. Assign an id (0-) to the entity\n * 2-2. Create a new mesh from the entity's object and add it to stencil scene\n * 2-3. Set up the material for the created mesh to write stencil buffer with 1 << id\n * 2-4. Set up the materials of the child entities to test stencil buffer with 1 << id\n * 3. In an animation loop\n * 3-1. Update the matrices of the main scene\n * 3-2. Copy the matrices to the the meshes in the stencil scene from the associated\n * entities in the main scene\n * 3-3. Clear stencil buffer\n * 3-4. Writing to stencil buffer by calling renderer.render() with the stencil scene\n * 3-5. Rendering the main scene\n *\n * Advantage:\n * - Simple, we don't need to manage much stencil specialities (eg: No need to control\n * Object3D.renderOrder)\n * - Using one stencil bit per panel allows for transparent and overlap processing.\n *\n * Limitation:\n * - Up to eight entities that mask others because it's safe to think stencil has 8 bits.\n *\n * > stencil buffer of at least 8 bits.\n * https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext#stencil\n */\n\nconst MAX_PANEL_NUM = 8;\n\n/**\n * @function\n * @description Setting up a material for an object that maskes other elements\n * @param {THREE.Material} material - the material whose values are augmented to mask as expected.\n * @param {number} shiftBit - used to offset the stencilWriteMask and stencilRef.\n * @param {boolean} debug - true if in debug mode, false otherwise\n */\nconst setupMaskingMaterial = (material, shiftBit, debug = false) => {\n material.transparent = true;\n // Nothing to render to color buffer so setting 0.0 unless debug mode\n material.opacity = debug ? 0.5 : 0.0;\n material.stencilWrite = true;\n material.stencilWriteMask = 1 << shiftBit;\n material.stencilRef = 1 << shiftBit;\n material.stencilFunc = three__WEBPACK_IMPORTED_MODULE_5__.AlwaysStencilFunc;\n material.stencilZPass = three__WEBPACK_IMPORTED_MODULE_5__.ReplaceStencilOp;\n\n // Disable depth testing to avoid complexity. Depth testing and other related\n // tasks are left to the main scene rendering.\n material.depthTest = false;\n material.depthWrite = false;\n};\n\n/**\n * @function\n * @description Setting up a material for an object that is masked by another element\n * @param {THREE.Material} material - the material whose values are augmented to mask as expected.\n * @param {number} shiftBit - used to offset the stencilWriteMask and stencilRef.\n */\nconst setupMaskedMaterial = (material, shiftBit) => {\n material.stencilWrite = true;\n material.stencilRef |= 1 << shiftBit;\n material.stencilFunc = three__WEBPACK_IMPORTED_MODULE_5__.EqualStencilFunc;\n material.stencilFuncMask |= 1 << shiftBit;\n material.stencilFail = three__WEBPACK_IMPORTED_MODULE_5__.KeepStencilOp;\n material.stencilZFail = three__WEBPACK_IMPORTED_MODULE_5__.KeepStencilOp;\n material.stencilZPass = three__WEBPACK_IMPORTED_MODULE_5__.KeepStencilOp;\n};\n\n/**\n * @class MaskingSystem\n * @classdesc Handles specific needs for setting up the masking for all necessary items.\n * @augments MRSystem\n */\nclass MaskingSystem extends mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_0__.MRSystem {\n /**\n * @class\n * @description MaskingSystem's default constructor.\n */\n constructor() {\n super(false);\n\n this.scene = new three__WEBPACK_IMPORTED_MODULE_5__.Scene();\n this.scene.matrixAutoUpdate = false;\n this.scene.matrixWorldAutoUpdate = false;\n\n this.sourceElementMap = new Map();\n this.maskingMeshMap = new Map();\n this.panels = [];\n }\n\n /**\n * @function\n * @description ...\n * @param {number} deltaTime - given timestep to be used for any feature changes\n * @param {object} frame - given frame information to be used for any feature changes\n */\n update(deltaTime, frame) {\n // leave for when needed.\n }\n\n /**\n * @function\n * @description Copy the source world matrices to the objects writing to stencil buffer\n */\n sync() {\n // TODO: Move to update().\n // This method needs to be called after the matrices of the main scene are updated.\n // However, currently the matrices are updated MRApp after running systems' update(),\n // so the conditions are not met when executed in update(). Therefore, as a\n // temporary workaround, a new method has been added and is explicitly called\n // from MRApp. Moving this code into the update() method would reduce the specialities\n // and improve maintainability.\n for (const child of this.scene.children) {\n const source = this.sourceElementMap.get(child).background;\n\n // TODO: Consider a properer way.\n // It seems that the geometry of a source object can be replaced with a different\n // geometry by other systems, so this check and replacing are needed. However,\n // replacing the geometry is not an appropriate use of the Three.js API. We\n // should consider a more robust approach to copying the shape of the source object.\n if (child.geometry !== source.geometry) {\n child.geometry = source.geometry;\n }\n\n child.matrixWorld.copy(source.matrixWorld);\n }\n }\n\n /**\n * @function\n * @description Called when a new entity is added to the scene. Handles masking elements to their panel.\n * @param {MREntity} entity - the entity being added.\n */\n onNewEntity(entity) {\n if (entity instanceof mrjs_core_entities_MRPanelEntity__WEBPACK_IMPORTED_MODULE_3__.MRPanelEntity) {\n if (this.panels.length >= MAX_PANEL_NUM) {\n console.warn('Masking system supports up to eight panels.');\n return;\n }\n\n // Ignoring panel removal for now.\n // TODO: Handle panel removal\n const stencilRefShift = this.panels.length;\n this.panels.push(entity);\n\n // We need to mask based off the background mesh of this object.\n const sourceObj = entity.background;\n\n // TODO: Optimize material.\n // Since only needs to write to the stencil buffer, no need to write to the color buffer,\n // therefore, we can use a simpler material than MeshBasicMaterial. Should we use\n // ShaderMaterial?\n const mesh = new three__WEBPACK_IMPORTED_MODULE_5__.Mesh(sourceObj.geometry, new three__WEBPACK_IMPORTED_MODULE_5__.MeshBasicMaterial());\n setupMaskingMaterial(mesh.material, stencilRefShift, this.app.debug);\n\n // No automatic matrices update because world matrices are updated in sync().\n mesh.matrixAutoUpdate = false;\n mesh.matrixWorldAutoUpdate = false;\n\n this.scene.add(mesh);\n this.sourceElementMap.set(mesh, entity);\n this.maskingMeshMap.set(entity, mesh);\n\n // Child elements are masked by this panel so traverse children and set up their materials for stencil\n entity.traverse((child) => {\n if (entity === child) {\n return;\n }\n\n if (child instanceof mrjs_core_entities_MRDivEntity__WEBPACK_IMPORTED_MODULE_1__.MRDivEntity && !child.ignoreStencil) {\n // The children we want to mask by the panel should only be DivEntities (ie UI elements). Other items\n // will be clipped by the panel instead. Additionally, we want to allow for items (such as 3D elements)\n // to be manually excluded from this masking by default or manual addition.\n //\n // Since we're stepping through every child, we only need to touch each mesh's material instead of\n // updating group objects as a whole.\n child.traverseObjects((object) => {\n if (object.isMesh) {\n setupMaskedMaterial(object.material, stencilRefShift);\n }\n });\n }\n });\n } else if (entity instanceof mrjs_core_entities_MRDivEntity__WEBPACK_IMPORTED_MODULE_1__.MRDivEntity && !entity.ignoreStencil) {\n // There is a chance that a child entity is added after parent panel addition.\n // Check registered panels and set up the material if panels are found in parents.\n for (const panel of this.panels) {\n if (panel.contains(entity)) {\n const mesh = this.maskingMeshMap.get(panel);\n const stencilRefShift = this.panels.indexOf(panel);\n entity.traverseObjects((object) => {\n if (object.isMesh) {\n // setupMaskedMaterial is a very light function so\n // calling again for materials already set up should not be a big deal\n setupMaskedMaterial(object.material, stencilRefShift);\n }\n });\n }\n }\n }\n }\n}\n\n\n//# sourceURL=webpack://mrjs/./src/core/componentSystems/MaskingSystem.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ MaskingSystem: () => (/* binding */ MaskingSystem)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/MRSystem */ \"./src/core/MRSystem.js\");\n/* harmony import */ var mrjs_core_entities_MRDivEntity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs/core/entities/MRDivEntity */ \"./src/core/entities/MRDivEntity.js\");\n/* harmony import */ var mrjs_core_MREntity__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! mrjs/core/MREntity */ \"./src/core/MREntity.js\");\n/* harmony import */ var mrjs_core_entities_MRPanelEntity__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! mrjs/core/entities/MRPanelEntity */ \"./src/core/entities/MRPanelEntity.js\");\n/* harmony import */ var mrjs_core_entities_MRTextEntity__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! mrjs/core/entities/MRTextEntity */ \"./src/core/entities/MRTextEntity.js\");\n/* harmony import */ var mrjs__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! mrjs */ \"./src/index.js\");\n\n\n\n\n\n\n\n\n\n\n/*\n * A system that handles elements that mask other elements by using stencil.\n * Eg: A Panel does not display child elements if the elements are positioned\n * outside the Panel's bounds.\n *\n * We use two rendering passes, one for writing to stencil buffer and another one\n * is for rendering the result with testing stencil. Basic idea is the following.\n *\n * 1. Have a scene, called stencil scene here to distinguish from the main scene,\n * for writing to stencil buffer\n * 2. When an entity that mask others is added\n * 2-1. Assign an id (0-) to the entity\n * 2-2. Create a new mesh from the entity's object and add it to stencil scene\n * 2-3. Set up the material for the created mesh to write stencil buffer with 1 << id\n * 2-4. Set up the materials of the child entities to test stencil buffer with 1 << id\n * 3. In an animation loop\n * 3-1. Update the matrices of the main scene\n * 3-2. Copy the matrices to the the meshes in the stencil scene from the associated\n * entities in the main scene\n * 3-3. Clear stencil buffer\n * 3-4. Writing to stencil buffer by calling renderer.render() with the stencil scene\n * 3-5. Rendering the main scene\n *\n * Advantage:\n * - Simple, we don't need to manage much stencil specialities (eg: No need to control\n * Object3D.renderOrder)\n * - Using one stencil bit per panel allows for transparent and overlap processing.\n *\n * Limitation:\n * - Up to eight entities that mask others because it's safe to think stencil has 8 bits.\n *\n * > stencil buffer of at least 8 bits.\n * https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext#stencil\n */\n\nconst MAX_PANEL_NUM = 8;\n\n/**\n * @function\n * @description Setting up a material for an object that maskes other elements\n * @param {THREE.Material} material - the material whose values are augmented to mask as expected.\n * @param {number} shiftBit - used to offset the stencilWriteMask and stencilRef.\n * @param {boolean} debug - true if in debug mode, false otherwise\n */\nconst setupMaskingMaterial = (material, shiftBit, debug = false) => {\n material.transparent = true;\n // Nothing to render to color buffer so setting 0.0 unless debug mode\n material.opacity = debug ? 0.5 : 0.0;\n material.stencilWrite = true;\n material.stencilWriteMask = 1 << shiftBit;\n material.stencilRef = 1 << shiftBit;\n material.stencilFunc = three__WEBPACK_IMPORTED_MODULE_6__.AlwaysStencilFunc;\n material.stencilZPass = three__WEBPACK_IMPORTED_MODULE_6__.ReplaceStencilOp;\n\n // Disable depth testing to avoid complexity. Depth testing and other related\n // tasks are left to the main scene rendering.\n material.depthTest = false;\n material.depthWrite = false;\n};\n\n/**\n * @function\n * @description Setting up a material for an object that is masked by another element\n * @param {THREE.Material} material - the material whose values are augmented to mask as expected.\n * @param {number} shiftBit - used to offset the stencilWriteMask and stencilRef.\n */\nconst setupMaskedMaterial = (material, shiftBit) => {\n material.stencilWrite = true;\n material.stencilRef |= 1 << shiftBit;\n material.stencilFunc = three__WEBPACK_IMPORTED_MODULE_6__.EqualStencilFunc;\n material.stencilFuncMask |= 1 << shiftBit;\n material.stencilFail = three__WEBPACK_IMPORTED_MODULE_6__.KeepStencilOp;\n material.stencilZFail = three__WEBPACK_IMPORTED_MODULE_6__.KeepStencilOp;\n material.stencilZPass = three__WEBPACK_IMPORTED_MODULE_6__.KeepStencilOp;\n};\n\n/**\n * @class MaskingSystem\n * @classdesc Handles specific needs for setting up the masking for all necessary items.\n * @augments MRSystem\n */\nclass MaskingSystem extends mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_0__.MRSystem {\n /**\n * @class\n * @description MaskingSystem's default constructor.\n */\n constructor() {\n super(false);\n\n this.scene = new three__WEBPACK_IMPORTED_MODULE_6__.Scene();\n this.scene.matrixAutoUpdate = false;\n this.scene.matrixWorldAutoUpdate = false;\n\n this.sourceElementMap = new Map();\n this.maskingMeshMap = new Map();\n this.panels = [];\n }\n\n /**\n * @function\n * @description ...\n * @param {number} deltaTime - given timestep to be used for any feature changes\n * @param {object} frame - given frame information to be used for any feature changes\n */\n update(deltaTime, frame) {\n // leave for when needed.\n }\n\n /**\n * @function\n * @description Copy the source world matrices to the objects writing to stencil buffer\n */\n sync() {\n // TODO: Move to update().\n // This method needs to be called after the matrices of the main scene are updated.\n // However, currently the matrices are updated MRApp after running systems' update(),\n // so the conditions are not met when executed in update(). Therefore, as a\n // temporary workaround, a new method has been added and is explicitly called\n // from MRApp. Moving this code into the update() method would reduce the specialities\n // and improve maintainability.\n for (const child of this.scene.children) {\n const source = this.sourceElementMap.get(child).background;\n\n // TODO: Consider a properer way.\n // It seems that the geometry of a source object can be replaced with a different\n // geometry by other systems, so this check and replacing are needed. However,\n // replacing the geometry is not an appropriate use of the Three.js API. We\n // should consider a more robust approach to copying the shape of the source object.\n if (child.geometry !== source.geometry) {\n child.geometry = source.geometry;\n }\n\n child.matrixWorld.copy(source.matrixWorld);\n }\n }\n\n /**\n * @function\n * @description Called when a new entity is added to the scene. Handles masking elements to their panel.\n * @param {MREntity} entity - the entity being added.\n */\n onNewEntity(entity) {\n if (entity instanceof mrjs_core_entities_MRPanelEntity__WEBPACK_IMPORTED_MODULE_3__.MRPanelEntity) {\n if (this.panels.length >= MAX_PANEL_NUM) {\n console.warn('Masking system supports up to eight panels.');\n return;\n }\n\n // Ignoring panel removal for now.\n // TODO: Handle panel removal\n const stencilRefShift = this.panels.length;\n this.panels.push(entity);\n\n // We need to mask based off the background mesh of this object.\n const sourceObj = entity.background;\n\n // TODO: Optimize material.\n // Since only needs to write to the stencil buffer, no need to write to the color buffer,\n // therefore, we can use a simpler material than MeshBasicMaterial. Should we use\n // ShaderMaterial?\n const material = mrjs__WEBPACK_IMPORTED_MODULE_5__.mrjsUtils.material.MeshBasicMaterial.clone();\n material.programName = 'maskingMaterial';\n const mesh = new three__WEBPACK_IMPORTED_MODULE_6__.Mesh(sourceObj.geometry, material);\n setupMaskingMaterial(mesh.material, stencilRefShift, this.app.debug);\n\n // No automatic matrices update because world matrices are updated in sync().\n mesh.matrixAutoUpdate = false;\n mesh.matrixWorldAutoUpdate = false;\n\n this.scene.add(mesh);\n this.sourceElementMap.set(mesh, entity);\n this.maskingMeshMap.set(entity, mesh);\n\n // Child elements are masked by this panel so traverse children and set up their materials for stencil\n entity.traverse((child) => {\n if (entity === child) {\n return;\n }\n\n if (child instanceof mrjs_core_entities_MRDivEntity__WEBPACK_IMPORTED_MODULE_1__.MRDivEntity && !child.ignoreStencil) {\n // The children we want to mask by the panel should only be DivEntities (ie UI elements). Other items\n // will be clipped by the panel instead. Additionally, we want to allow for items (such as 3D elements)\n // to be manually excluded from this masking by default or manual addition.\n //\n // Since we're stepping through every child, we only need to touch each mesh's material instead of\n // updating group objects as a whole.\n child.traverseObjects((object) => {\n if (object.isMesh) {\n setupMaskedMaterial(object.material, stencilRefShift);\n }\n });\n }\n });\n } else if (entity instanceof mrjs_core_entities_MRDivEntity__WEBPACK_IMPORTED_MODULE_1__.MRDivEntity && !entity.ignoreStencil) {\n // There is a chance that a child entity is added after parent panel addition.\n // Check registered panels and set up the material if panels are found in parents.\n for (const panel of this.panels) {\n if (panel.contains(entity)) {\n const mesh = this.maskingMeshMap.get(panel);\n const stencilRefShift = this.panels.indexOf(panel);\n entity.traverseObjects((object) => {\n if (object.isMesh) {\n // setupMaskedMaterial is a very light function so\n // calling again for materials already set up should not be a big deal\n setupMaskedMaterial(object.material, stencilRefShift);\n }\n });\n }\n }\n }\n }\n}\n\n\n//# sourceURL=webpack://mrjs/./src/core/componentSystems/MaskingSystem.js?"); /***/ }), @@ -674,7 +674,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ MRDivEntity: () => (/* binding */ MRDivEntity)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var mrjs_core_MREntity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/MREntity */ \"./src/core/MREntity.js\");\n/* harmony import */ var mrjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs */ \"./src/index.js\");\n\n\n\n\n\n\n/**\n * @class MRDivEntity\n * @classdesc The MREntity that is used to solely describe UI Elements. Defaults as the html `mr-div` representation. `mr-div`\n * @augments MREntity\n */\nclass MRDivEntity extends mrjs_core_MREntity__WEBPACK_IMPORTED_MODULE_0__.MREntity {\n /**\n * @class\n * @description Constructor sets up the defaults for the background mesh, scaling, and world relevant elements.\n */\n constructor() {\n super();\n this.worldScale = new three__WEBPACK_IMPORTED_MODULE_2__.Vector3();\n this.halfExtents = new three__WEBPACK_IMPORTED_MODULE_2__.Vector3();\n this.physics.type = 'ui';\n\n const geometry = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.geometry.UIPlane(1, 1, [0], 18);\n const material = new three__WEBPACK_IMPORTED_MODULE_2__.MeshStandardMaterial({\n color: 0xfff,\n roughness: 0.7,\n metalness: 0.0,\n side: three__WEBPACK_IMPORTED_MODULE_2__.DoubleSide,\n });\n\n this.background = new three__WEBPACK_IMPORTED_MODULE_2__.Mesh(geometry, material);\n this.background.receiveShadow = true;\n this.background.renderOrder = 3;\n this.background.visible = false;\n this.background.name = 'background';\n this.object3D.add(this.background);\n this.object3D.name = 'mrDivEntity';\n\n // allow stenciling when needed by default for UI elements, but also allows\n // overriding when needed.\n this.ignoreStencil = false;\n }\n\n _storedWidth = -1;\n _storedHeight = -1;\n _storedBorderRadii = -1;\n\n /**\n * @function\n * @description Calculates the height of the Entity based on the viewing-client's shape. If in Mixed Reality, adjusts the value appropriately.\n * @returns {number} - the resolved height\n */\n get height() {\n const rect = this.getBoundingClientRect();\n\n if (mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.isPresenting) {\n return rect.height / mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.display.VIRTUAL_DISPLAY_RESOLUTION;\n }\n return (rect.height / __webpack_require__.g.appHeight) * __webpack_require__.g.viewPortHeight;\n }\n\n /**\n * @function\n * @description Calculates the width of the Entity based on the viewing-client's shape. If in Mixed Reality, adjusts the value appropriately.\n * @returns {number} - the resolved width\n */\n get width() {\n const rect = this.getBoundingClientRect();\n\n if (mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.isPresenting) {\n return rect.width / mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.display.VIRTUAL_DISPLAY_RESOLUTION;\n }\n return (rect.width / __webpack_require__.g.appWidth) * __webpack_require__.g.viewPortWidth;\n }\n\n /**\n * @function\n * @description Calculates the border radius of the img based on the img tag in the shadow root\n * @returns {number} - the resolved height\n */\n get borderRadii() {\n return this.compStyle.borderRadius.split(' ').map((r) => mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.css.domToThree(r));\n }\n\n /**\n * @function\n * @description Adding an entity as a sub-object of this panel (for example an mr-model, button, etc).\n * @param {MREntity} entity - the entity to be added.\n */\n add(entity) {\n // `this` must have `mr-panel` as its closest parent entity for threejs to handle positioning appropriately.\n let panel = this.closest('mr-panel');\n if (panel && entity instanceof MRDivEntity) {\n panel.add(entity);\n } else {\n this.object3D.add(entity.object3D);\n }\n\n // slight bump needed to avoid overlapping, glitchy visuals.\n entity.object3D.position.z = this.object3D.position.z + 0.001;\n }\n\n /**\n * @function\n * @description Removing an entity as a sub-object of this panel (for example an mr-model, button, etc).\n * @param {MREntity} entity - the entity to be removed added.\n */\n removeEntity(entity) {\n // `this` must have `mr-panel` as its closest parent entity for threejs to handle positioning appropriately.\n let panel = this.closest('mr-panel');\n if (panel && entity instanceof MRDivEntity) {\n panel.removeEntity(entity);\n } else {\n this.object3D.remove(entity.object3D);\n }\n }\n\n /**\n * @function\n * @description (async) connects the background geometry of this item to an actual UIPlane geometry.\n */\n async connected() {\n this.background.geometry = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.geometry.UIPlane(this.width, this.height, [0], 18);\n }\n}\n\ncustomElements.get('mr-div') || customElements.define('mr-div', MRDivEntity);\n\n\n//# sourceURL=webpack://mrjs/./src/core/entities/MRDivEntity.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ MRDivEntity: () => (/* binding */ MRDivEntity)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var mrjs_core_MREntity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/MREntity */ \"./src/core/MREntity.js\");\n/* harmony import */ var mrjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs */ \"./src/index.js\");\n\n\n\n\n\n\n/**\n * @class MRDivEntity\n * @classdesc The MREntity that is used to solely describe UI Elements. Defaults as the html `mr-div` representation. `mr-div`\n * @augments MREntity\n */\nclass MRDivEntity extends mrjs_core_MREntity__WEBPACK_IMPORTED_MODULE_0__.MREntity {\n /**\n * @class\n * @description Constructor sets up the defaults for the background mesh, scaling, and world relevant elements.\n */\n constructor() {\n super();\n this.worldScale = new three__WEBPACK_IMPORTED_MODULE_2__.Vector3();\n this.halfExtents = new three__WEBPACK_IMPORTED_MODULE_2__.Vector3();\n this.physics.type = 'ui';\n\n const geometry = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.geometry.UIPlane(1, 1, [0], 18);\n const material = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.material.MeshStandardMaterial.clone();\n material.color.set(0xfff);\n material.color.roughness = 0.7;\n material.color.metalness = 0.0;\n material.side = three__WEBPACK_IMPORTED_MODULE_2__.DoubleSide;\n\n this.background = new three__WEBPACK_IMPORTED_MODULE_2__.Mesh(geometry, material);\n this.background.receiveShadow = true;\n this.background.renderOrder = 3;\n this.background.visible = false;\n this.background.name = 'background';\n this.object3D.add(this.background);\n this.object3D.name = 'mrDivEntity';\n\n // allow stenciling when needed by default for UI elements, but also allows\n // overriding when needed.\n this.ignoreStencil = false;\n }\n\n _storedWidth = -1;\n _storedHeight = -1;\n _storedBorderRadii = -1;\n\n /**\n * @function\n * @description Calculates the height of the Entity based on the viewing-client's shape. If in Mixed Reality, adjusts the value appropriately.\n * @returns {number} - the resolved height\n */\n get height() {\n const rect = this.getBoundingClientRect();\n\n if (mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.isPresenting) {\n return rect.height / mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.display.VIRTUAL_DISPLAY_RESOLUTION;\n }\n return (rect.height / __webpack_require__.g.appHeight) * __webpack_require__.g.viewPortHeight;\n }\n\n /**\n * @function\n * @description Calculates the width of the Entity based on the viewing-client's shape. If in Mixed Reality, adjusts the value appropriately.\n * @returns {number} - the resolved width\n */\n get width() {\n const rect = this.getBoundingClientRect();\n\n if (mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.xr.isPresenting) {\n return rect.width / mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.display.VIRTUAL_DISPLAY_RESOLUTION;\n }\n return (rect.width / __webpack_require__.g.appWidth) * __webpack_require__.g.viewPortWidth;\n }\n\n /**\n * @function\n * @description Calculates the border radius of the img based on the img tag in the shadow root\n * @returns {number} - the resolved height\n */\n get borderRadii() {\n return this.compStyle.borderRadius.split(' ').map((r) => mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.css.domToThree(r));\n }\n\n /**\n * @function\n * @description Adding an entity as a sub-object of this panel (for example an mr-model, button, etc).\n * @param {MREntity} entity - the entity to be added.\n */\n add(entity) {\n // `this` must have `mr-panel` as its closest parent entity for threejs to handle positioning appropriately.\n let panel = this.closest('mr-panel');\n if (panel && entity instanceof MRDivEntity) {\n panel.add(entity);\n } else {\n this.object3D.add(entity.object3D);\n }\n\n // slight bump needed to avoid overlapping, glitchy visuals.\n entity.object3D.position.z = this.object3D.position.z + 0.001;\n }\n\n /**\n * @function\n * @description Removing an entity as a sub-object of this panel (for example an mr-model, button, etc).\n * @param {MREntity} entity - the entity to be removed added.\n */\n removeEntity(entity) {\n // `this` must have `mr-panel` as its closest parent entity for threejs to handle positioning appropriately.\n let panel = this.closest('mr-panel');\n if (panel && entity instanceof MRDivEntity) {\n panel.removeEntity(entity);\n } else {\n this.object3D.remove(entity.object3D);\n }\n }\n\n /**\n * @function\n * @description (async) connects the background geometry of this item to an actual UIPlane geometry.\n */\n async connected() {\n this.background.geometry = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.geometry.UIPlane(this.width, this.height, [0], 18);\n }\n}\n\ncustomElements.get('mr-div') || customElements.define('mr-div', MRDivEntity);\n\n\n//# sourceURL=webpack://mrjs/./src/core/entities/MRDivEntity.js?"); /***/ }), @@ -718,7 +718,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ MRMediaEntity: () => (/* binding */ MRMediaEntity)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var mrjs_core_entities_MRDivEntity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/entities/MRDivEntity */ \"./src/core/entities/MRDivEntity.js\");\n/* harmony import */ var mrjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs */ \"./src/index.js\");\n\n\n\n\n\n\n/**\n * @class MRMediaEntity\n * @classdesc Base html media entity represented in 3D space. `mr-media`\n * @augments MRDivEntity\n */\nclass MRMediaEntity extends mrjs_core_entities_MRDivEntity__WEBPACK_IMPORTED_MODULE_0__.MRDivEntity {\n /**\n * @class\n * @description Constructs a base media entity using a UIPlane and other 3D elements as necessary.\n */\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n\n // Create the object3D. Dont need default value for geometry\n // until the connected call since this will get overwritten anyways.\n const material = new three__WEBPACK_IMPORTED_MODULE_2__.MeshStandardMaterial({\n side: three__WEBPACK_IMPORTED_MODULE_2__.FrontSide,\n });\n // Object3D for MRMediaEntity (mrimage,mrvideo,etc) is the actual image/video/etc itself in 3D space\n this.object3D = new three__WEBPACK_IMPORTED_MODULE_2__.Mesh(undefined, material);\n this.object3D.receiveShadow = true;\n this.object3D.renderOrder = 3;\n this.object3D.name = 'media';\n\n // the media to be filled out.\n // for ex: document.createElement('video') or document.createElement('img');\n this.media = null;\n\n // This is a reference to the texture that is used as part of the\n // threejs material. Separating it out for easier updating after it is loaded.\n // The texture is filled-in in the connected function.\n this.texture = null;\n\n // This is used to aid in the formatting for certain object-fit setups\n // ex: contain, scale-down\n this.subMediaMesh = new three__WEBPACK_IMPORTED_MODULE_2__.Mesh();\n this.subMediaMesh.receiveShadow = true;\n this.subMediaMesh.renderOrder = 3;\n this.subMediaMesh.name = 'subMedia';\n this.object3D.add(this.subMediaMesh);\n }\n\n /**\n * @function\n * @description Calculates the width of the MRMedia object\n * @returns {number} - the resolved width\n */\n get width() {\n let width = this.objectFitDimensions?.width;\n return width > 0 ? width : super.width;\n }\n\n /**\n * @function\n * @description Calculates the height of the MRMedia object\n * @returns {number} - the resolved height\n */\n get height() {\n let height = this.objectFitDimensions?.height;\n return height > 0 ? height : super.height;\n }\n\n /**\n * @function\n * @description Calculates the width of the media based on the media tag itself\n * This function will error if called directly as an MRMedia item. Made to be overridden\n * by children.\n * @returns {number} - the resolved height\n */\n get mediaWidth() {\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.error.emptyParentFunction();\n return null;\n }\n\n /**\n * @function\n * @description Calculates the height of the media based on the media tag itself\n * This function will error if called directly as an MRMedia item. Made to be overridden\n * by children.\n * @returns {number} - the resolved height\n */\n get mediaHeight() {\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.error.emptyParentFunction();\n return null;\n }\n\n /**\n * @function\n * @description Creates the Media Plane Geometry used to draw the Image,Video,etc\n * This is a separate object to allow for common css styling such as 'contain' and 'scale-down'.\n */\n generateNewMediaPlaneGeometry() {\n if (this.object3D.geometry !== undefined) {\n this.object3D.geometry.dispose();\n }\n this.object3D.geometry = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.geometry.UIPlane(this.width, this.height, this.borderRadii, 18);\n }\n\n /**\n * @function\n * @description Loads the associated media into 3D based on its html properties.\n * This function will error if called directly as an MRMedia item. Made to be overridden\n * by children.\n */\n loadMediaTexture() {\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.error.emptyParentFunction();\n }\n\n /**\n * @function\n * @description (async) handles setting up this media and associated 3D geometry style (from css) once it is connected to run as an entity component.\n */\n async connected() {\n await super.connected();\n this.media.setAttribute('src', mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.html.resolvePath(this.getAttribute('src')));\n this.media.setAttribute('style', 'object-fit:inherit; width:inherit');\n\n this.objectFitDimensions = { height: 0, width: 0 };\n if (this.getAttribute('src') !== undefined) {\n this.computeObjectFitDimensions();\n this.generateNewMediaPlaneGeometry();\n this.loadMediaTexture();\n }\n }\n\n /**\n * @function\n * @description Callback function of MREntity - Updates the media's cover,fill,etc based on the mutation request.\n * @param {object} mutation - the update/change/mutation to be handled.\n */\n mutated(mutation) {\n super.mutated();\n\n if (mutation.type == 'attributes' && mutation.attributeName == 'src') {\n this.media.setAttribute('src', this.getAttribute('src'));\n this.computeObjectFitDimensions();\n this.loadMediaTexture();\n }\n }\n\n /**\n * @function\n * @description computes the width and height values for the image considering the value of object-fit\n */\n computeObjectFitDimensions() {\n if (!this.texture || !this.media) {\n // We assume every media item exists and has its attached texture.\n // If texture doesnt exist, it's just not loaded in yet, meaning\n // we can skip the below until it is.\n return;\n }\n\n const _oldSubMediaMeshNotNeeded = () => {\n // All switch options other than 'contain' and 'scale-down' start with this\n // function.\n //\n // 'contain' and 'scale-down' are the only options that use this additional\n // mesh for positioning.\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.model.disposeObject3D(this.subMediaMesh);\n };\n\n const _showMainMediaMesh = () => {\n // used if transitioning away from 'contain' or 'scale-down'\n // to make sure that texture is set properly\n this.object3D.material.visible = true;\n this.object3D.material.needsUpdate = true;\n };\n\n const _hideMainMediaMesh = () => {\n // to parallel the '_makeSureMainMediaMeshHasTexture' for readability\n // and debugging later on.\n this.object3D.material.visible = false;\n this.object3D.material.needsUpdate = true;\n };\n\n let containerWidth = this.parentElement.width;\n let containerHeight = this.parentElement.height;\n let mediaWidth = this.mediaWidth;\n let mediaHeight = this.mediaHeight;\n const mediaAspect = mediaWidth / mediaHeight;\n const containerAspect = containerWidth / containerHeight;\n switch (this.compStyle.objectFit) {\n case 'fill':\n _oldSubMediaMeshNotNeeded();\n _showMainMediaMesh();\n this.objectFitDimensions = { width: containerWidth, height: containerHeight };\n\n break;\n\n case 'contain':\n case 'scale-down':\n // `contain` and `scale-down` are the same except for one factor:\n // - `contain` will always scale the media to fit\n // - `scale-down` will only scale the media to fit if the media is larger than the container\n //\n // Implemented by making the main mesh turn into the same dimensions as the container\n // and making the submesh scale itself based on those values. This allows the original hit\n // box of mr-media to stay as expected while the media itself still changes based on object-fit.\n\n if (this.compStyle.objectFit === 'contain' || mediaWidth > containerWidth || mediaHeight > containerHeight) {\n const scaleRatio = Math.min(containerWidth / mediaWidth, containerHeight / mediaHeight);\n mediaWidth *= scaleRatio;\n mediaHeight *= scaleRatio;\n }\n\n const mediaGeometry = new three__WEBPACK_IMPORTED_MODULE_2__.PlaneGeometry(mediaWidth, mediaHeight);\n const mediaMaterial = new three__WEBPACK_IMPORTED_MODULE_2__.MeshStandardMaterial({\n map: this.texture,\n });\n _oldSubMediaMeshNotNeeded();\n this.subMediaMesh.geometry = mediaGeometry;\n this.subMediaMesh.material = mediaMaterial;\n this.subMediaMesh.geometry.needsUpdate = true;\n this.subMediaMesh.material.visible = true;\n this.subMediaMesh.material.needsUpdate = true;\n _hideMainMediaMesh();\n\n this.objectFitDimensions = {\n width: containerWidth,\n height: containerHeight,\n };\n\n break;\n\n case 'cover':\n _oldSubMediaMeshNotNeeded();\n _showMainMediaMesh();\n\n this.texture.repeat.set(1, 1); // Reset scaling\n\n if (containerAspect > mediaAspect) {\n // Plane is wider than the texture\n const scale = containerAspect / mediaAspect;\n this.texture.repeat.y = 1 / scale;\n this.texture.offset.y = (1 - 1 / scale) / 2; // Center the texture vertically\n } else {\n // Plane is taller than the texture\n const scale = mediaAspect / containerAspect;\n this.texture.repeat.x = 1 / scale;\n this.texture.offset.x = (1 - 1 / scale) / 2; // Center the texture horizontally\n }\n this.texture.needsUpdate = true; // Important to update the texture\n\n this.objectFitDimensions = {\n width: containerWidth,\n height: containerHeight,\n };\n\n break;\n\n case 'none':\n _oldSubMediaMeshNotNeeded();\n _showMainMediaMesh();\n this.objectFitDimensions = { width: mediaWidth, height: mediaHeight };\n\n break;\n\n default:\n throw new Error(`Unsupported object-fit value ${this.compStyle.objectFit}`);\n }\n\n this.style.width = `${this.objectFitDimensions.width}px`;\n this.style.height = `${this.objectFitDimensions.height}px`;\n }\n}\n\n\n//# sourceURL=webpack://mrjs/./src/core/entities/MRMediaEntity.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ MRMediaEntity: () => (/* binding */ MRMediaEntity)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var mrjs_core_entities_MRDivEntity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/entities/MRDivEntity */ \"./src/core/entities/MRDivEntity.js\");\n/* harmony import */ var mrjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs */ \"./src/index.js\");\n\n\n\n\n\n\n/**\n * @class MRMediaEntity\n * @classdesc Base html media entity represented in 3D space. `mr-media`\n * @augments MRDivEntity\n */\nclass MRMediaEntity extends mrjs_core_entities_MRDivEntity__WEBPACK_IMPORTED_MODULE_0__.MRDivEntity {\n /**\n * @class\n * @description Constructs a base media entity using a UIPlane and other 3D elements as necessary.\n */\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n\n // Create the object3D. Dont need default value for geometry\n // until the connected call since this will get overwritten anyways.\n const material = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.material.MeshStandardMaterial.clone();\n material.side = three__WEBPACK_IMPORTED_MODULE_2__.FrontSide;\n // Object3D for MRMediaEntity (mrimage,mrvideo,etc) is the actual image/video/etc itself in 3D space\n this.object3D = new three__WEBPACK_IMPORTED_MODULE_2__.Mesh(undefined, material);\n this.object3D.receiveShadow = true;\n this.object3D.renderOrder = 3;\n this.object3D.name = 'media';\n\n // the media to be filled out.\n // for ex: document.createElement('video') or document.createElement('img');\n this.media = null;\n\n // This is a reference to the texture that is used as part of the\n // threejs material. Separating it out for easier updating after it is loaded.\n // The texture is filled-in in the connected function.\n this.texture = null;\n\n // This is used to aid in the formatting for certain object-fit setups\n // ex: contain, scale-down\n this.subMediaMesh = new three__WEBPACK_IMPORTED_MODULE_2__.Mesh();\n this.subMediaMesh.receiveShadow = true;\n this.subMediaMesh.renderOrder = 3;\n this.subMediaMesh.name = 'subMedia';\n this.object3D.add(this.subMediaMesh);\n }\n\n /**\n * @function\n * @description Calculates the width of the MRMedia object\n * @returns {number} - the resolved width\n */\n get width() {\n let width = this.objectFitDimensions?.width;\n return width > 0 ? width : super.width;\n }\n\n /**\n * @function\n * @description Calculates the height of the MRMedia object\n * @returns {number} - the resolved height\n */\n get height() {\n let height = this.objectFitDimensions?.height;\n return height > 0 ? height : super.height;\n }\n\n /**\n * @function\n * @description Calculates the width of the media based on the media tag itself\n * This function will error if called directly as an MRMedia item. Made to be overridden\n * by children.\n * @returns {number} - the resolved height\n */\n get mediaWidth() {\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.error.emptyParentFunction();\n return null;\n }\n\n /**\n * @function\n * @description Calculates the height of the media based on the media tag itself\n * This function will error if called directly as an MRMedia item. Made to be overridden\n * by children.\n * @returns {number} - the resolved height\n */\n get mediaHeight() {\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.error.emptyParentFunction();\n return null;\n }\n\n /**\n * @function\n * @description Creates the Media Plane Geometry used to draw the Image,Video,etc\n * This is a separate object to allow for common css styling such as 'contain' and 'scale-down'.\n */\n generateNewMediaPlaneGeometry() {\n if (this.object3D.geometry !== undefined) {\n this.object3D.geometry.dispose();\n }\n this.object3D.geometry = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.geometry.UIPlane(this.width, this.height, this.borderRadii, 18);\n }\n\n /**\n * @function\n * @description Loads the associated media into 3D based on its html properties.\n * This function will error if called directly as an MRMedia item. Made to be overridden\n * by children.\n */\n loadMediaTexture() {\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.error.emptyParentFunction();\n }\n\n /**\n * @function\n * @description (async) handles setting up this media and associated 3D geometry style (from css) once it is connected to run as an entity component.\n */\n async connected() {\n await super.connected();\n this.media.setAttribute('src', mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.html.resolvePath(this.getAttribute('src')));\n this.media.setAttribute('style', 'object-fit:inherit; width:inherit');\n\n this.objectFitDimensions = { height: 0, width: 0 };\n if (this.getAttribute('src') !== undefined) {\n this.computeObjectFitDimensions();\n this.generateNewMediaPlaneGeometry();\n this.loadMediaTexture();\n }\n }\n\n /**\n * @function\n * @description Callback function of MREntity - Updates the media's cover,fill,etc based on the mutation request.\n * @param {object} mutation - the update/change/mutation to be handled.\n */\n mutated(mutation) {\n super.mutated();\n\n if (mutation.type == 'attributes' && mutation.attributeName == 'src') {\n this.media.setAttribute('src', this.getAttribute('src'));\n this.computeObjectFitDimensions();\n this.loadMediaTexture();\n }\n }\n\n /**\n * @function\n * @description computes the width and height values for the image considering the value of object-fit\n */\n computeObjectFitDimensions() {\n if (!this.texture || !this.media) {\n // We assume every media item exists and has its attached texture.\n // If texture doesnt exist, it's just not loaded in yet, meaning\n // we can skip the below until it is.\n return;\n }\n\n const _oldSubMediaMeshNotNeeded = () => {\n // All switch options other than 'contain' and 'scale-down' start with this\n // function.\n //\n // 'contain' and 'scale-down' are the only options that use this additional\n // mesh for positioning.\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.model.disposeObject3D(this.subMediaMesh);\n };\n\n const _showMainMediaMesh = () => {\n // used if transitioning away from 'contain' or 'scale-down'\n // to make sure that texture is set properly\n this.object3D.material.visible = true;\n this.object3D.material.needsUpdate = true;\n };\n\n const _hideMainMediaMesh = () => {\n // to parallel the '_makeSureMainMediaMeshHasTexture' for readability\n // and debugging later on.\n this.object3D.material.visible = false;\n this.object3D.material.needsUpdate = true;\n };\n\n let containerWidth = this.parentElement.width;\n let containerHeight = this.parentElement.height;\n let mediaWidth = this.mediaWidth;\n let mediaHeight = this.mediaHeight;\n const mediaAspect = mediaWidth / mediaHeight;\n const containerAspect = containerWidth / containerHeight;\n switch (this.compStyle.objectFit) {\n case 'fill':\n _oldSubMediaMeshNotNeeded();\n _showMainMediaMesh();\n this.objectFitDimensions = { width: containerWidth, height: containerHeight };\n\n break;\n\n case 'contain':\n case 'scale-down':\n // `contain` and `scale-down` are the same except for one factor:\n // - `contain` will always scale the media to fit\n // - `scale-down` will only scale the media to fit if the media is larger than the container\n //\n // Implemented by making the main mesh turn into the same dimensions as the container\n // and making the submesh scale itself based on those values. This allows the original hit\n // box of mr-media to stay as expected while the media itself still changes based on object-fit.\n\n if (this.compStyle.objectFit === 'contain' || mediaWidth > containerWidth || mediaHeight > containerHeight) {\n const scaleRatio = Math.min(containerWidth / mediaWidth, containerHeight / mediaHeight);\n mediaWidth *= scaleRatio;\n mediaHeight *= scaleRatio;\n }\n\n const mediaGeometry = new three__WEBPACK_IMPORTED_MODULE_2__.PlaneGeometry(mediaWidth, mediaHeight);\n const mediaMaterial = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.material.MeshStandardMaterial.clone();\n mediaMaterial.map = this.texture;\n mediaMaterial.name = 'mediaMaterial';\n _oldSubMediaMeshNotNeeded();\n this.subMediaMesh.geometry = mediaGeometry;\n this.subMediaMesh.material = mediaMaterial;\n this.subMediaMesh.geometry.needsUpdate = true;\n this.subMediaMesh.material.visible = true;\n this.subMediaMesh.material.needsUpdate = true;\n _hideMainMediaMesh();\n\n this.objectFitDimensions = {\n width: containerWidth,\n height: containerHeight,\n };\n\n break;\n\n case 'cover':\n _oldSubMediaMeshNotNeeded();\n _showMainMediaMesh();\n\n this.texture.repeat.set(1, 1); // Reset scaling\n\n if (containerAspect > mediaAspect) {\n // Plane is wider than the texture\n const scale = containerAspect / mediaAspect;\n this.texture.repeat.y = 1 / scale;\n this.texture.offset.y = (1 - 1 / scale) / 2; // Center the texture vertically\n } else {\n // Plane is taller than the texture\n const scale = mediaAspect / containerAspect;\n this.texture.repeat.x = 1 / scale;\n this.texture.offset.x = (1 - 1 / scale) / 2; // Center the texture horizontally\n }\n this.texture.needsUpdate = true; // Important to update the texture\n\n this.objectFitDimensions = {\n width: containerWidth,\n height: containerHeight,\n };\n\n break;\n\n case 'none':\n _oldSubMediaMeshNotNeeded();\n _showMainMediaMesh();\n this.objectFitDimensions = { width: mediaWidth, height: mediaHeight };\n\n break;\n\n default:\n throw new Error(`Unsupported object-fit value ${this.compStyle.objectFit}`);\n }\n\n this.style.width = `${this.objectFitDimensions.width}px`;\n this.style.height = `${this.objectFitDimensions.height}px`;\n }\n}\n\n\n//# sourceURL=webpack://mrjs/./src/core/entities/MRMediaEntity.js?"); /***/ }), @@ -729,7 +729,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ MRModelEntity: () => (/* binding */ MRModelEntity)\n/* harmony export */ });\n/* harmony import */ var mrjs_core_MREntity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/MREntity */ \"./src/core/MREntity.js\");\n/* harmony import */ var mrjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs */ \"./src/index.js\");\n/* harmony import */ var mrjs_core_entities_MRDivEntity__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! mrjs/core/entities/MRDivEntity */ \"./src/core/entities/MRDivEntity.js\");\n\n\n\n\n\n/**\n * @class MRModelEntity\n * @classdesc Loads in any supported 3D model type to the requested location. `mr-model`\n * @augments MRDivEntity\n */\nclass MRModelEntity extends mrjs_core_entities_MRDivEntity__WEBPACK_IMPORTED_MODULE_2__.MRDivEntity {\n /**\n * @class\n * @description Constructor for the Model entity, does the default.\n */\n constructor() {\n super();\n\n this.ignoreStencil = true;\n this.object3D.name = 'model';\n\n // TODO: replace with single status enum.\n this.loading = false;\n this.loaded = false;\n\n this.modelObj = null;\n\n // Store animations for the AnimationSystem to use\n // Need to store this separately from the model, because with\n // the threejs load from glb, we cant directly add it back to\n // the model group itself as overarching animation as we're not\n // guaranteed that theyre not animations for sub-group objects.\n this.animations = [];\n }\n\n #src = null;\n\n /**\n * @function\n * @description Pair getter for the src property of . Important so that when a user tries\n * to run modelObject.src = `...` or perform something on modelObject.src it properly gets the html\n * attribute as expected instead of the pure js one.\n *\n * note: we can do this because only htmlimageelement has a `src` property by default, not htmlimagelement,\n * and none of the above class extensions for Model have it as a defined property.\n * @returns {string} the value of the src html attribute\n */\n get src() {\n return this.#src;\n }\n\n /**\n * @function\n * @description Setter for the src property of . Important so that when a user tries\n * to run modelObject.src = `...` it properly sets the html attribute as expected instead of the\n * pure js one.\n *\n * note: we can do this because only htmlimageelement has a `src` property by default, not htmlimagelement,\n * and none of the above class extensions for Model have it as a defined property.\n */\n set src(value) {\n if (this.#src != value) {\n this.#src = value;\n if (this.#src != this.getAttribute('src')) {\n this.setAttribute('src', value);\n }\n }\n }\n\n /**\n * @function\n * @description Callback function of MREntity - Updates the media's cover,fill,etc based on the mutation request.\n * @param {object} mutation - the update/change/mutation to be handled.\n */\n mutated(mutation) {\n super.mutated();\n\n if (mutation.type == 'attributes' && mutation.attributeName == 'src') {\n this.src = this.getAttribute('src');\n if (!this.loading) {\n this.loadModel();\n }\n }\n }\n\n /**\n * @function\n * @description Async function that fills in this Model object based on src file information\n */\n async loadModel() {\n this.loading = true;\n\n const extension = this.src.slice(((this.src.lastIndexOf('.') - 1) >>> 0) + 2);\n\n let modelChanged = false;\n if (this.modelObj) {\n this.modelObj.visible = false;\n while (this.modelObj.parent) {\n this.modelObj.removeFromParent();\n }\n\n this.modelObj = null;\n modelChanged = true;\n }\n\n try {\n let url = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.html.resolvePath(this.src);\n const result = await mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.model.loadModel(url, extension);\n\n // Handle the different formats of the loaded result\n this.modelObj =\n result?.scene ?? false\n ? // For loaders that return an object with multiple properties (scene, animation, joints, etc)\n // For ex: GLB\n result.scene\n : // For loaders that return the object directly\n // For ex: STL, OBJ, FBX\n result;\n let animations = result.animations;\n if (animations && animations.length > 0) {\n this.animations = animations;\n }\n\n this.object3D.add(this.modelObj);\n\n this.modelObj.receiveShadow = true;\n this.modelObj.renderOrder = 3;\n\n this.traverseObjects((object) => {\n if (object.isMesh) {\n object.renderOrder = 3;\n object.receiveShadow = true;\n object.castShadow = true;\n }\n });\n\n this.onLoad();\n\n this.loading = false;\n\n this.loaded = true;\n\n if (this.isConnected && modelChanged) {\n this.dispatchEvent(new CustomEvent('modelchange', { bubbles: true }));\n }\n } catch (error) {\n console.error(`ERR: in loading model ${this.src}. Error was:`, error);\n }\n }\n\n /**\n * @function\n * @description (async) Callback function of MREntity - handles setting up this Model once it is connected to run as an entity component.\n * Includes loading up the model and associated data.\n */\n async connected() {\n this.src = this.getAttribute('src');\n if (!this.src || this.loaded) {\n return new Promise((resolve) => {\n const interval = setInterval(() => {\n if (this.loaded) {\n clearInterval(interval);\n resolve();\n }\n }, 100);\n });\n }\n\n if (!this.loading) {\n await this.loadModel();\n } else {\n return new Promise((resolve) => {\n const interval = setInterval(() => {\n if (this.loaded) {\n clearInterval(interval);\n resolve();\n }\n }, 100);\n });\n }\n }\n\n /**\n * @function\n * @description On load event function - right now defaults to do nothing.\n */\n onLoad = () => {};\n}\n\ncustomElements.get('mr-model') || customElements.define('mr-model', MRModelEntity);\n\n\n//# sourceURL=webpack://mrjs/./src/core/entities/MRModelEntity.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ MRModelEntity: () => (/* binding */ MRModelEntity)\n/* harmony export */ });\n/* harmony import */ var mrjs_core_MREntity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/MREntity */ \"./src/core/MREntity.js\");\n/* harmony import */ var mrjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs */ \"./src/index.js\");\n/* harmony import */ var mrjs_core_entities_MRDivEntity__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! mrjs/core/entities/MRDivEntity */ \"./src/core/entities/MRDivEntity.js\");\n\n\n\n\n\n/**\n * @class MRModelEntity\n * @classdesc Loads in any supported 3D model type to the requested location. `mr-model`\n * @augments MRDivEntity\n */\nclass MRModelEntity extends mrjs_core_entities_MRDivEntity__WEBPACK_IMPORTED_MODULE_2__.MRDivEntity {\n /**\n * @class\n * @description Constructor for the Model entity, does the default.\n */\n constructor() {\n super();\n\n this.ignoreStencil = true;\n this.object3D.name = 'model';\n\n // TODO: replace with single status enum.\n this.loading = false;\n this.loaded = false;\n\n this.modelObj = null;\n\n // Store animations for the AnimationSystem to use\n // Need to store this separately from the model, because with\n // the threejs load from glb, we cant directly add it back to\n // the model group itself as overarching animation as we're not\n // guaranteed that theyre not animations for sub-group objects.\n this.animations = [];\n }\n\n #src = null;\n\n /**\n * @function\n * @description Pair getter for the src property of . Important so that when a user tries\n * to run modelObject.src = `...` or perform something on modelObject.src it properly gets the html\n * attribute as expected instead of the pure js one.\n *\n * note: we can do this because only htmlimageelement has a `src` property by default, not htmlimagelement,\n * and none of the above class extensions for Model have it as a defined property.\n * @returns {string} the value of the src html attribute\n */\n get src() {\n return this.#src;\n }\n\n /**\n * @function\n * @description Setter for the src property of . Important so that when a user tries\n * to run modelObject.src = `...` it properly sets the html attribute as expected instead of the\n * pure js one.\n *\n * note: we can do this because only htmlimageelement has a `src` property by default, not htmlimagelement,\n * and none of the above class extensions for Model have it as a defined property.\n */\n set src(value) {\n if (this.#src != value) {\n this.#src = value;\n if (this.#src != this.getAttribute('src')) {\n this.setAttribute('src', value);\n }\n }\n }\n\n /**\n * @function\n * @description Callback function of MREntity - Updates the media's cover,fill,etc based on the mutation request.\n * @param {object} mutation - the update/change/mutation to be handled.\n */\n mutated(mutation) {\n super.mutated();\n\n if (mutation.type == 'attributes' && mutation.attributeName == 'src') {\n this.src = this.getAttribute('src');\n if (!this.loading) {\n this.loadModel();\n }\n }\n }\n\n /**\n * @function\n * @description Async function that fills in this Model object based on src file information\n */\n async loadModel() {\n this.loading = true;\n\n const extension = this.src.slice(((this.src.lastIndexOf('.') - 1) >>> 0) + 2);\n\n let modelChanged = false;\n if (this.modelObj) {\n this.modelObj.visible = false;\n while (this.modelObj.parent) {\n this.modelObj.removeFromParent();\n }\n\n this.modelObj = null;\n modelChanged = true;\n }\n\n try {\n let url = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.html.resolvePath(this.src);\n const result = await mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.model.loadModel(url, extension);\n\n // Handle the different formats of the loaded result\n this.modelObj =\n result?.scene ?? false\n ? // For loaders that return an object with multiple properties (scene, animation, joints, etc)\n // For ex: GLB\n result.scene\n : // For loaders that return the object directly\n // For ex: STL, OBJ, FBX\n result;\n let animations = result.animations;\n if (animations && animations.length > 0) {\n this.animations = animations;\n }\n\n this.modelObj.receiveShadow = true;\n this.modelObj.renderOrder = 3;\n\n this.traverseObjects((object) => {\n if (object.isMesh) {\n object.renderOrder = 3;\n object.receiveShadow = true;\n object.castShadow = true;\n }\n });\n\n this.object3D.add(this.modelObj);\n this.object3D.name = this.src;\n\n this.onLoad();\n\n this.loading = false;\n\n this.loaded = true;\n\n if (this.isConnected && modelChanged) {\n this.dispatchEvent(new CustomEvent('modelchange', { bubbles: true }));\n }\n } catch (error) {\n console.error(`ERR: in loading model ${this.src}. Error was:`, error);\n }\n }\n\n /**\n * @function\n * @description (async) Callback function of MREntity - handles setting up this Model once it is connected to run as an entity component.\n * Includes loading up the model and associated data.\n */\n async connected() {\n this.src = this.getAttribute('src');\n if (!this.src || this.loaded) {\n return new Promise((resolve) => {\n const interval = setInterval(() => {\n if (this.loaded) {\n clearInterval(interval);\n resolve();\n }\n }, 100);\n });\n }\n\n if (!this.loading) {\n await this.loadModel();\n } else {\n return new Promise((resolve) => {\n const interval = setInterval(() => {\n if (this.loaded) {\n clearInterval(interval);\n resolve();\n }\n }, 100);\n });\n }\n }\n\n /**\n * @function\n * @description On load event function - right now defaults to do nothing.\n */\n onLoad = () => {};\n}\n\ncustomElements.get('mr-model') || customElements.define('mr-model', MRModelEntity);\n\n\n//# sourceURL=webpack://mrjs/./src/core/entities/MRModelEntity.js?"); /***/ }), @@ -751,7 +751,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ MRSkyBoxEntity: () => (/* binding */ MRSkyBoxEntity)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var mrjs_core_MREntity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/MREntity */ \"./src/core/MREntity.js\");\n/* harmony import */ var mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs/core/MRSystem */ \"./src/core/MRSystem.js\");\n\n\n\n\n\n/**\n * @class MRSkyBoxEntity\n * @classdesc The skybox entity that allows users to give multiple images to pattern into the 3D background space. `mr-skybox`\n * @augments MREntity\n */\nclass MRSkyBoxEntity extends mrjs_core_MREntity__WEBPACK_IMPORTED_MODULE_0__.MREntity {\n /**\n * @class\n * @description Constructor for skybox - defaults to the usual impl of an Entity.\n */\n constructor() {\n super();\n this.object3D.name = 'skybox';\n\n this.skybox = null;\n this.textureLoadedCallbacks = [];\n }\n\n /**\n * @function\n * @description Callback function triggered when the texture is successfully loaded.\n * It sets the loaded texture as the background and notifies all registered callbacks.\n * @param {THREE.Texture} texture - The loaded texture.\n */\n onTextureLoaded(texture) {\n if (this.skybox) {\n if (Array.isArray(texture.images) && texture.images.length === 6) {\n // Handle cube texture case\n if (this.skybox.material !== undefined) {\n this.skybox.material.dispose();\n }\n this.skybox.material = new three__WEBPACK_IMPORTED_MODULE_2__.MeshStandardMaterial({\n envMap: texture,\n side: three__WEBPACK_IMPORTED_MODULE_2__.BackSide, // Render only on the inside\n });\n } else {\n // Handle single texture case\n if (this.skybox.material !== undefined) {\n this.skybox.material.dispose();\n }\n this.skybox.material = new three__WEBPACK_IMPORTED_MODULE_2__.MeshBasicMaterial({\n map: texture,\n side: three__WEBPACK_IMPORTED_MODULE_2__.BackSide, // Render only on the inside\n opacity: 1,\n });\n }\n }\n this.textureLoadedCallbacks.forEach((callback) => callback(texture));\n }\n\n /**\n * @function\n * @description (async) Lifecycle method that is called when the entity is connected.\n * This method initializes and starts the texture loading process.\n */\n async connected() {\n // you can have texturesList be all individual textures\n // or you can store them in a specified path and just\n // load them up solely by filename in that path.\n\n this.texturesList = mrjsUtils.html.resolvePath(this.getAttribute('src'));\n if (!this.texturesList) {\n return;\n }\n\n const textureNames = this.texturesList.split(',');\n const path = this.getAttribute('pathToTextures');\n const textureUrls = textureNames.map((name) => mrjsUtils.html.resolvePath(path ? path + name : name));\n\n let geometry;\n let textureLoader;\n if (textureNames.length > 1) {\n geometry = new three__WEBPACK_IMPORTED_MODULE_2__.BoxGeometry(900, 900, 900);\n textureLoader = new three__WEBPACK_IMPORTED_MODULE_2__.CubeTextureLoader();\n textureLoader.load(textureUrls, this.onTextureLoaded.bind(this));\n } else if (textureUrls.length == 1) {\n geometry = new three__WEBPACK_IMPORTED_MODULE_2__.SphereGeometry(900, 32, 16);\n textureLoader = new three__WEBPACK_IMPORTED_MODULE_2__.TextureLoader();\n textureLoader.load(textureUrls[0], this.onTextureLoaded.bind(this));\n }\n\n if (this.skybox) {\n // Remove existing skybox if present\n this.object3D.remove(this.skybox);\n this.skybox.dispose();\n }\n this.skybox = new three__WEBPACK_IMPORTED_MODULE_2__.Mesh(geometry); // going to passively load texture on async\n this.object3D.add(this.skybox);\n }\n\n /**\n * @function\n * @description Set the opacity of the skybox itself. Useful for blending between the outside and MR. Also\n * useful for cases where you want to blend between different skybox versions.\n */\n set setOpacity(val) {\n this.object3D.traverse((child) => {\n if (child.isMesh) {\n child.material.transparent = true;\n child.material.opacity = val;\n child.material.needsUpdate = true;\n }\n });\n }\n\n /**\n * @function\n * @description On load event function - right now defaults to do nothing.\n */\n onLoad = () => {};\n}\ncustomElements.define('mr-skybox', MRSkyBoxEntity);\n\n\n//# sourceURL=webpack://mrjs/./src/core/entities/MRSkyBoxEntity.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ MRSkyBoxEntity: () => (/* binding */ MRSkyBoxEntity)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var mrjs_core_MREntity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/MREntity */ \"./src/core/MREntity.js\");\n/* harmony import */ var mrjs_core_MRSystem__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs/core/MRSystem */ \"./src/core/MRSystem.js\");\n\n\n\n\n\n/**\n * @class MRSkyBoxEntity\n * @classdesc The skybox entity that allows users to give multiple images to pattern into the 3D background space. `mr-skybox`\n * @augments MREntity\n */\nclass MRSkyBoxEntity extends mrjs_core_MREntity__WEBPACK_IMPORTED_MODULE_0__.MREntity {\n /**\n * @class\n * @description Constructor for skybox - defaults to the usual impl of an Entity.\n */\n constructor() {\n super();\n this.object3D.name = 'skybox';\n\n this.skybox = null;\n this.textureLoadedCallbacks = [];\n }\n\n /**\n * @function\n * @description Callback function triggered when the texture is successfully loaded.\n * It sets the loaded texture as the background and notifies all registered callbacks.\n * @param {THREE.Texture} texture - The loaded texture.\n */\n onTextureLoaded(texture) {\n if (this.skybox) {\n if (Array.isArray(texture.images) && texture.images.length === 6) {\n // Handle cube texture case\n if (this.skybox.material !== undefined) {\n this.skybox.material.dispose();\n }\n const material = mrjsUtils.material.MeshStandardMaterial.clone();\n material.envMap = texture;\n material.side = three__WEBPACK_IMPORTED_MODULE_2__.BackSide;\n material.programName = 'skyboxMaterial-1';\n this.skybox.material = material;\n } else {\n // Handle single texture case\n if (this.skybox.material !== undefined) {\n this.skybox.material.dispose();\n }\n const material = mrjsUtils.material.MeshBasicMaterial.clone();\n material.envMap = texture;\n material.side = three__WEBPACK_IMPORTED_MODULE_2__.BackSide;\n material.opacity = 1;\n material.programName = 'skyboxMaterial-2';\n this.skybox.material = material;\n }\n }\n this.textureLoadedCallbacks.forEach((callback) => callback(texture));\n }\n\n /**\n * @function\n * @description (async) Lifecycle method that is called when the entity is connected.\n * This method initializes and starts the texture loading process.\n */\n async connected() {\n // you can have texturesList be all individual textures\n // or you can store them in a specified path and just\n // load them up solely by filename in that path.\n\n this.texturesList = mrjsUtils.html.resolvePath(this.getAttribute('src'));\n if (!this.texturesList) {\n return;\n }\n\n const textureNames = this.texturesList.split(',');\n const path = this.getAttribute('pathToTextures');\n const textureUrls = textureNames.map((name) => mrjsUtils.html.resolvePath(path ? path + name : name));\n\n let geometry;\n let textureLoader;\n if (textureNames.length > 1) {\n geometry = new three__WEBPACK_IMPORTED_MODULE_2__.BoxGeometry(900, 900, 900);\n textureLoader = new three__WEBPACK_IMPORTED_MODULE_2__.CubeTextureLoader();\n textureLoader.load(textureUrls, this.onTextureLoaded.bind(this));\n } else if (textureUrls.length == 1) {\n geometry = new three__WEBPACK_IMPORTED_MODULE_2__.SphereGeometry(900, 32, 16);\n textureLoader = new three__WEBPACK_IMPORTED_MODULE_2__.TextureLoader();\n textureLoader.load(textureUrls[0], this.onTextureLoaded.bind(this));\n }\n\n if (this.skybox) {\n // Remove existing skybox if present\n this.object3D.remove(this.skybox);\n this.skybox.dispose();\n }\n this.skybox = new three__WEBPACK_IMPORTED_MODULE_2__.Mesh(geometry); // going to passively load texture on async\n this.object3D.add(this.skybox);\n }\n\n /**\n * @function\n * @description Set the opacity of the skybox itself. Useful for blending between the outside and MR. Also\n * useful for cases where you want to blend between different skybox versions.\n */\n set setOpacity(val) {\n this.object3D.traverse((child) => {\n if (child.isMesh) {\n child.material.transparent = true;\n child.material.opacity = val;\n child.material.needsUpdate = true;\n }\n });\n }\n\n /**\n * @function\n * @description On load event function - right now defaults to do nothing.\n */\n onLoad = () => {};\n}\ncustomElements.define('mr-skybox', MRSkyBoxEntity);\n\n\n//# sourceURL=webpack://mrjs/./src/core/entities/MRSkyBoxEntity.js?"); /***/ }), @@ -806,7 +806,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ MRTextInputEntity: () => (/* binding */ MRTextInputEntity)\n/* harmony export */ });\n/* harmony import */ var troika_three_text__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! troika-three-text */ \"./node_modules/troika-three-text/dist/troika-three-text.esm.js\");\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var mrjs_core_entities_MRTextEntity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/entities/MRTextEntity */ \"./src/core/entities/MRTextEntity.js\");\n\n\n\n\n\n\n/**\n * @class MRTextInputEntity\n * @classdesc Base text inpu entity represented in 3D space. `mr-text-input`\n * @augments MRTextEntity\n */\nclass MRTextInputEntity extends mrjs_core_entities_MRTextEntity__WEBPACK_IMPORTED_MODULE_0__.MRTextEntity {\n /**\n * @class\n * @description Constructor for the MRTextInputEntity entity component.\n */\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n /**\n * @function\n * @description Gets the value of the text for the current hiddenInput DOM object\n * @returns {string} value - the text value of the current hiddenInput DOM object\n */\n get value() {\n return this.hiddenInput.value;\n }\n\n /**\n * @function\n * @description Sets the value of the text for the current hiddenInput DOM object\n */\n set value(val) {\n this.hiddenInput.value = val;\n }\n\n /**\n * @function\n * @description Function to be overwritten by children. Called by connected to make sure\n * the hiddenInput dom element is created as expected.\n */\n createHiddenInputElement() {\n mrjsUtils.error.emptyParentFunction();\n }\n\n /**\n * @function\n * @description Function to be overwritten by children. Called by connected after\n * createHiddenInputElement to fill it in with the user's given\n * attribute information.\n */\n fillInHiddenInputElementWithUserData() {\n mrjsUtils.error.emptyParentFunction();\n }\n\n /**\n * @function\n * @description Function to be overwritten by children. Used on event trigger to\n * update the textObj visual based on the hiddenInput DOM element.\n * @param {boolean} fromCursorMove - default set as false if not supplied. See `MRTextArea`\n * and `MRTextField` as examples. This param is helpful for cases where the visible\n * region of text can differ from the full text value. Since cursor movement already handles\n * scrolling for that region change, then we only need to update the new text. Otherwise, we\n * also need to scroll and update the new text.\n */\n updateTextDisplay(fromCursorMove = false) {\n mrjsUtils.error.emptyParentFunction();\n }\n\n /**\n * @function\n * @description (async) Handles setting up this textarea once it is connected to run as an entity component.\n */\n async connected() {\n // await super.connected(); // TODO - uncomment this in the textfield pr if this is actually needed, but atm it's not since not exposed.\n\n // Cursor Setup\n this.cursorWidth = 0.002;\n this.cursorHeight = mrjsUtils.css.pxToThree(mrjsUtils.css.extractNumFromPixelStr(this.compStyle.fontSize));\n this._createCursorObject();\n // initial style\n this.cursor.position.z += 0.001;\n this.cursor.visible = false;\n // We store this for the geometry so we can do our geometry vs web origin calculations\n // more easily as well. We update this based on the geometry's own changes.\n //\n // Set as 0,0,0 to start, and updated when the geometry updates in case it changes in 3d space.\n this.cursorStartingPosition = new three__WEBPACK_IMPORTED_MODULE_1__.Vector3(0, 0, 0);\n this.object3D.add(this.cursor);\n\n // DOM\n this.createHiddenInputElement();\n // this.fillInHiddenInputElementWithUserData(); // TODO - need good list of defaults\n\n // Make it trigger happy\n this.setupEventListeners();\n\n // Updates for baseline visual\n this.triggerGeometryStyleUpdate();\n this.triggerTextStyleUpdate();\n\n // All items should start out as 'not selected'\n // unless noted otherwise.\n if (!this.hiddenInput.getAttribute('autofocus') ?? false) {\n this._blur();\n }\n\n // Handle any placeholder setup s.t. it can be overwritten easily.\n if (this.hiddenInput.getAttribute('placeholder') ?? false) {\n this.textObj.text = this.hiddenInput.getAttribute('placeholder');\n }\n\n // TODO - this needs better boolean naming\n if (this.hasTextSubsetForVerticalScrolling) {\n this.verticalTextObjStartLineIndex = 0;\n this.verticalTextObjEndLineIndex = 0;\n }\n }\n\n /**\n * @function\n * @description Internal function used to setup the cursor object and associated variables\n * needed during runtime. Sets the cursor geometry based on dev updated cursorWidth and\n * cursorHeight MRTextInputEntity variables.\n */\n _createCursorObject() {\n if (!this.cursor) {\n // Setup basic cursor info and material for if it was reset.\n this.cursor = new three__WEBPACK_IMPORTED_MODULE_1__.Mesh();\n const material = new three__WEBPACK_IMPORTED_MODULE_1__.MeshBasicMaterial({\n color: 0x000000,\n side: three__WEBPACK_IMPORTED_MODULE_1__.DoubleSide,\n });\n this.cursor.material = material;\n }\n if (this.cursor.geometry !== undefined) {\n // Handle geometry reclearing\n this.cursor.geometry.dispose();\n }\n // Setup basic cursor geometry\n this.cursor.geometry = new three__WEBPACK_IMPORTED_MODULE_1__.PlaneGeometry(this.cursorWidth, this.cursorHeight);\n this.cursor.geometry.needsUpdate = true;\n }\n\n /**\n * @function\n * @description Internal function used to setup the cursor object and associated variables\n * needed during runtime. User can pass in a new height directly or the function checks\n * whether cursor height should be updated based on fontSize compared to line height\n * and other aspects.\n * @param {number} newHeight - an optional parameter to be used as the cursor's new height.\n */\n _updateCursorSize(newHeight) {\n const cursorVisibleHeight = newHeight ?? this.textObj.fontSize * this.lineHeight;\n if (this.cursor.geometry.parameters.height != cursorVisibleHeight) {\n this.cursorHeight = cursorVisibleHeight;\n this._createCursorObject();\n }\n }\n\n /**\n * @function\n * @description Function to be overwritten by children. Called by the keydown event trigger.\n * @param {event} event - the keydown event\n */\n handleKeydown(event) {\n mrjsUtils.error.emptyParentFunction();\n }\n\n /**\n * @function\n * @description Called by the mouse click event trigger. Handles determining the\n * caret position based on the 3D textObj to hiddenInput DOM position conversion.\n * @param {event} event - the mouseclick event\n */\n handleMouseClick(event) {\n console.log(event);\n // Convert isx position from world position to local:\n // - make sure textObj has updated matrices so we're not calculating info wrong\n // - note: textObj doesnt need sync\n this.textObj.updateMatrixWorld(true);\n const inverseMatrixWorld = new three__WEBPACK_IMPORTED_MODULE_1__.Matrix4().copy(this.textObj.matrixWorld).invert();\n const localPosition = inverseMatrixWorld * event.worldPosition;\n\n // update cursor position based on click\n // TODO - hitting an issue where carret's hit locations are undefined (ie not hit when it should)\n // so the return is defaulting to 0 index. Tried also the below of maybe with textObj but then it\n // never actually runs the code either (bc no text sync update is needed).\n // this.textObj.sync(() => {\n // const caret = getCaretAtPoint(this.textObj.textRenderInfo, localPosition.x, localPosition.y);\n // console.log('caret position: ', caret);\n // this.hiddenInput.selectionStart = caret.charIndex;\n // this.updateCursorPosition();\n // });\n const caret = (0,troika_three_text__WEBPACK_IMPORTED_MODULE_2__.getCaretAtPoint)(this.textObj.textRenderInfo, localPosition.x, localPosition.y);\n console.log('caret position: ', caret);\n this.hiddenInput.selectionStart = caret.charIndex;\n this.updateCursorPosition();\n }\n\n /**\n * @function\n * @description Called by the focus event trigger and in other 'focus' situations. We use the\n * private version of this function signature to not hit the intersection of the actual 'focus()'\n * event naming that we have connected. See 'setupEventListeners()' description for more info.\n * @param {boolean} isPureFocusEvent - Boolean to allow us to update the cursor position with this function\n * directly. Otherwise, we assume there's other things happening after focus was called as part of the event\n * and that the cursor position will be handled there instead.\n */\n _focus(isPureFocusEvent = false) {\n if (!this.hiddenInput) {\n return;\n }\n this.hiddenInput.focus();\n\n if (isPureFocusEvent) {\n // Only want to update cursor and selection position if\n // this is a pure focus event; otherwise, we're assuming\n // the other event will position those properly (so that\n // we dont do redundant positioning here and then there as well).\n this.hiddenInput.selectionStart = this.hiddenInput.value.length;\n this.updateCursorPosition();\n }\n\n this.cursor.visible = true;\n }\n\n /**\n * @function\n * @description Called by the blur event trigger and in other 'blur' situations. We use the\n * private version of this function signature to not hit the intersection of the actual 'blur()'\n * event naming that we have connected. See 'setupEventListeners()' description for more info.\n */\n _blur() {\n if (!this.hiddenInput) {\n return;\n }\n this.hiddenInput.blur();\n\n this.cursor.visible = false;\n }\n\n // TODO - better name?\n /**\n * @function\n * @description Getter for whether this textinput should handle vertical scrolling or not.\n * @returns {boolean} true if it should be handled, false otherwise\n */\n get hasTextSubsetForVerticalScrolling() {\n // Leaving this as a function instead of a pure boolean if in case it's dependant\n // on certain parameters changing that need to be recalculated depending on the input\n // setup, etc.\n mrjsUtils.error.emptyParentFunction();\n return false;\n }\n\n // TODO - better name?\n /**\n * @function\n * @description Getter for whether this textinput should handle horizontal scrolling or not.\n * @returns {boolean} true if it should be handled, false otherwise\n */\n get hasTextSubsetForHorizontalScrolling() {\n // Leaving this as a function instead of a pure boolean if in case it's dependant\n // on certain parameters changing that need to be recalculated depending on the input\n // setup, etc.\n mrjsUtils.error.emptyParentFunction();\n return false;\n }\n\n /**\n * @function\n * @description Getter for a commonly needed attribute: 'disabled' for whether this input is still being updated.\n * @returns {boolean} true if disabled, false otherwise\n */\n get inputIsDisabled() {\n return this.hiddenInput.getAttribute('disabled') ?? false;\n }\n\n /**\n * @function\n * @description Getter for a commonly needed attribute: 'readonly' for whether this input's text can still be changed.\n * @returns {boolean} true if readonly, false otherwise\n */\n get inputIsReadOnly() {\n return this.hiddenInput.getAttribute('readonly') ?? false;\n }\n\n /**\n * @function\n * @description Connecting the event listeners to the actual functions that handle them. Includes\n * additional calls where necessary.\n *\n * Since we want the text input children to be able\n * to override the parent function event triggers,\n * separating them into an actual function here\n * and calling them manually instead of doing the pure\n * 'functionname () => {} event type setup'. This manual\n * connection allows us to call super.func() for event\n * functions; otherwise, theyre not accessible nor implemented\n * in the subclasses.\n */\n setupEventListeners() {\n // Blur events\n this.addEventListener('blur', () => {\n this._blur();\n });\n\n // Pure Focus Events\n this.addEventListener('focus', () => {\n if (this.inputIsDisabled || this.inputIsReadOnly) {\n return;\n }\n this._focus(true);\n });\n this.addEventListener('click', () => {\n if (this.inputIsDisabled || this.inputIsReadOnly) {\n return;\n }\n this._focus(true);\n });\n\n // Focus and Handle Event\n this.addEventListener('touchstart', (event) => {\n if (this.inputIsDisabled || this.inputIsReadOnly) {\n return;\n }\n this._focus();\n this.handleMouseClick(event);\n });\n\n // Keyboard events to capture text in the hidden input.\n this.hiddenInput.addEventListener('input', (event) => {\n if (this.inputIsDisabled || this.inputIsReadOnly) {\n return;\n }\n\n // Input captures all main text character inputs\n // BUT it does not capture arrow keys, so we handle\n // those specifically by the 'keydown' event.\n //\n // We handle all the rest by relying on the internal\n // 'hiddenInput's update system so we dont have to\n // manage as many things directly ourselves.\n\n this.updateTextDisplay(false);\n this.updateCursorPosition(false);\n });\n this.hiddenInput.addEventListener('keydown', (event) => {\n if (this.inputIsDisabled || this.inputIsReadOnly) {\n return;\n }\n\n // Only using keydown for arrow keys, everything else is\n // handled by the input event - check the comment there\n // for more reasoning.\n\n if (event.key == 'ArrowUp' || event.key == 'ArrowDown' || event.key == 'ArrowLeft' || event.key == 'ArrowRight') {\n this.handleKeydown(event);\n }\n });\n\n // Separate trigger call just in case.\n this.addEventListener('update-cursor-position', () => {\n if (this.inputIsDisabled || this.inputIsReadOnly) {\n return;\n }\n\n this.updateCursorPosition();\n });\n }\n\n // TODO - see note where this is called - need to rethink small part of indexing\n // logic so this manual aspect is not necessary.\n /**\n * @function\n * @description Helper function for `handleKeyDown` and `updateCursorPosition` when\n * handling textObj.\n * @param {number} lineIndex - the ending line index non-inclusive of the summation.\n * @param {Array} allLines - the array of line strings\n * @returns {number} length of summed string.\n */\n _totalLengthUpToLineIndex(lineIndex, allLines) {\n let totalLengthTolineIndex = 0;\n for (let i = 0; i < lineIndex; ++i) {\n totalLengthTolineIndex += allLines[i].length + 1; // one additional for '\\n' char\n }\n // TODO - will we need a check/fix to handle the case where the last index\n // may or may not have a '\\n' in it?\n return totalLengthTolineIndex;\n }\n\n // TODO - see note where this is called - need to rethink small part of indexing\n // logic so this manual aspect is not necessary.\n /**\n * @function\n * @description Helper function for `handleKeyDown` and `updateCursorPosition` when\n * handling textObj.\n * @param {number} lineIndexStart - the starting line index inclusive of the summation.\n * @param {number} lineIndexEnd - the starting line index non-inclusive of the summation.\n * @param {Array} allLines - the array of line strings\n * @returns {number} length of summed string.\n */\n _totalLengthBetweenLineIndices(lineIndexStart, lineIndexEnd, allLines) {\n let totalLengthTolineIndex = 0;\n for (let i = lineIndexStart; i < lineIndexEnd; ++i) {\n totalLengthTolineIndex += allLines[i].length + 1; // one additional for '\\n' char\n }\n // TODO - will we need a check/fix to handle the case where the last index\n // may or may not have a '\\n' in it?\n return totalLengthTolineIndex;\n }\n\n /**\n * @function\n * @description Updates the cursor position based on click and selection location.\n * @param {boolean} fromCursorMove - false by default. Used to determine if we need to run\n * based off a text object update sync or we can directly grab information. This requirement\n * occurs because the sync isnt usable if no text content changed.\n *\n * Note: this function does not change anything about the this.hiddenInput.selectionStart nor\n * this.hiddenInput.selectionEnd. Those values should be changed prior to this function being\n * called.\n */\n updateCursorPosition(fromCursorMove = false) {\n // TODO - QUESTION: handle '\\n' --> as '/\\r?\\n/' for crossplatform compat\n // does the browser handle this for us?\n\n const updateBasedOnSelectionRects = (cursorIndex) => {\n // Setup variables for calculations.\n let textBeforeCursor = this.hiddenInput.value.substring(0, cursorIndex);\n let allLines = this.hiddenInput.value.split('\\n');\n let linesBeforeCursor = textBeforeCursor.split('\\n');\n let cursorIsOnLineIndex = linesBeforeCursor.length - 1;\n\n let rectX = undefined;\n let rectY = undefined;\n let rect = undefined;\n\n // Setup textObj variables for calculations\n let cursorIsOnTextObjLineIndex = cursorIsOnLineIndex - this.verticalTextObjStartLineIndex;\n // TODO - there needs to be a cleaner way to do this than summing up every time\n let lengthToCursorTextObjStartLineIndex = this._totalLengthUpToLineIndex(this.verticalTextObjStartLineIndex, allLines);\n let lengthToTextObjCursorLine = this._totalLengthBetweenLineIndices(this.verticalTextObjStartLineIndex, cursorIsOnLineIndex, allLines);\n // note: add one to start it on the actual start line so we can index cursor Index at 0 if beg of line\n let cursorIndexWithinTextObj = cursorIndex - (lengthToCursorTextObjStartLineIndex + 1);\n\n const prevIsNewlineChar = '\\n' === textBeforeCursor.charAt(textBeforeCursor.length - 1);\n if (prevIsNewlineChar) {\n // When on newline char, hiddenInput puts selection at end of newline char,\n // not beginning of next line. Make sure cursor visual is at beginning\n // of the next line without moving selection point.\n //\n // \"\"\"\n // This is an example of text\\n\n // the way troika handles it\n // \"\"\"\n //\n // In the below, using (*) to denote the 'you are here'.\n const isFakeNewLine = cursorIsOnLineIndex == allLines.length - 1;\n let indexOfBegOfLine = lengthToTextObjCursorLine;\n if (isFakeNewLine) {\n // \"\"\"\n // This is an example of text\\n\n // the way troika handles it\\n(*)\n // \"\"\"\n //\n // Special case where next line doesnt exist yet, fake it sitting below with our\n // current line's information.\n let usingIndex = indexOfBegOfLine - 1;\n let selectionRects = (0,troika_three_text__WEBPACK_IMPORTED_MODULE_2__.getSelectionRects)(this.textObj.textRenderInfo, usingIndex, usingIndex + 1);\n // rect information for use in cursor positioning\n rect = selectionRects[0];\n rectY = rect.bottom - this.cursorHeight;\n } else {\n // \"\"\"\n // This is an example of text\\n(*)\n // the way troika handles it\n // \"\"\"\n let usingIndex = cursorIndexWithinTextObj;\n let selectionRects = (0,troika_three_text__WEBPACK_IMPORTED_MODULE_2__.getSelectionRects)(this.textObj.textRenderInfo, usingIndex, usingIndex + 1);\n rect = selectionRects[0];\n rectY = rect.bottom;\n }\n rectX = 0;\n } else {\n // default\n let usingIndex = cursorIndexWithinTextObj;\n let selectionRects = (0,troika_three_text__WEBPACK_IMPORTED_MODULE_2__.getSelectionRects)(this.textObj.textRenderInfo, usingIndex, usingIndex + 1);\n // rect information for use in cursor positioning\n rect = selectionRects[0];\n rectX = rect.right;\n rectY = rect.bottom;\n }\n\n this._updateCursorSize();\n\n // Add the cursor dimension info to the position s.t. it doesnt touch the text itself. We want\n // a little bit of buffer room.\n const cursorXOffsetPosition = rectX + this.cursorWidth;\n const cursorYOffsetPosition = rectY + this.cursorHeight;\n\n // Update the cursor's 3D position\n this.cursor.position.x = this.cursorStartingPosition.x + cursorXOffsetPosition;\n this.cursor.position.y = this.cursorStartingPosition.y + cursorYOffsetPosition;\n this.cursor.visible = true;\n };\n\n // Check if we have any DOM element to work with.\n if (!this.hiddenInput) {\n return;\n }\n\n // Since no text is selected, this and selectionEnd are just the cursor position.\n // XXX - when we actually allow for selection in future, some of the below will need to\n // be thought through again.\n const cursorIndex = this.hiddenInput.selectionStart;\n\n // early escape for empty text based on hiddenInput top line\n if (cursorIndex == 0) {\n this._updateCursorSize();\n this.cursor.position.x = this.cursorStartingPosition.x;\n this.cursor.position.y = this.cursorStartingPosition.y;\n this.cursor.visible = true;\n return;\n }\n\n // Separating textObj sync from the cursor update based on rects\n // since textObj sync resolves when there's actual changes to the\n // object. Otherwise, it'll hang and never hit the update function.\n if (fromCursorMove) {\n updateBasedOnSelectionRects(cursorIndex);\n } else {\n this.textObj.sync(() => {\n updateBasedOnSelectionRects(cursorIndex);\n });\n }\n }\n}\n\n\n//# sourceURL=webpack://mrjs/./src/core/entities/MRTextInputEntity.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ MRTextInputEntity: () => (/* binding */ MRTextInputEntity)\n/* harmony export */ });\n/* harmony import */ var troika_three_text__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! troika-three-text */ \"./node_modules/troika-three-text/dist/troika-three-text.esm.js\");\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var mrjs_core_entities_MRTextEntity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/entities/MRTextEntity */ \"./src/core/entities/MRTextEntity.js\");\n\n\n\n\n\n\n/**\n * @class MRTextInputEntity\n * @classdesc Base text inpu entity represented in 3D space. `mr-text-input`\n * @augments MRTextEntity\n */\nclass MRTextInputEntity extends mrjs_core_entities_MRTextEntity__WEBPACK_IMPORTED_MODULE_0__.MRTextEntity {\n /**\n * @class\n * @description Constructor for the MRTextInputEntity entity component.\n */\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n /**\n * @function\n * @description Gets the value of the text for the current hiddenInput DOM object\n * @returns {string} value - the text value of the current hiddenInput DOM object\n */\n get value() {\n return this.hiddenInput.value;\n }\n\n /**\n * @function\n * @description Sets the value of the text for the current hiddenInput DOM object\n */\n set value(val) {\n this.hiddenInput.value = val;\n }\n\n /**\n * @function\n * @description Function to be overwritten by children. Called by connected to make sure\n * the hiddenInput dom element is created as expected.\n */\n createHiddenInputElement() {\n mrjsUtils.error.emptyParentFunction();\n }\n\n /**\n * @function\n * @description Function to be overwritten by children. Called by connected after\n * createHiddenInputElement to fill it in with the user's given\n * attribute information.\n */\n fillInHiddenInputElementWithUserData() {\n mrjsUtils.error.emptyParentFunction();\n }\n\n /**\n * @function\n * @description Function to be overwritten by children. Used on event trigger to\n * update the textObj visual based on the hiddenInput DOM element.\n * @param {boolean} fromCursorMove - default set as false if not supplied. See `MRTextArea`\n * and `MRTextField` as examples. This param is helpful for cases where the visible\n * region of text can differ from the full text value. Since cursor movement already handles\n * scrolling for that region change, then we only need to update the new text. Otherwise, we\n * also need to scroll and update the new text.\n */\n updateTextDisplay(fromCursorMove = false) {\n mrjsUtils.error.emptyParentFunction();\n }\n\n /**\n * @function\n * @description (async) Handles setting up this textarea once it is connected to run as an entity component.\n */\n async connected() {\n // await super.connected(); // TODO - uncomment this in the textfield pr if this is actually needed, but atm it's not since not exposed.\n\n // Cursor Setup\n this.cursorWidth = 0.002;\n this.cursorHeight = mrjsUtils.css.pxToThree(mrjsUtils.css.extractNumFromPixelStr(this.compStyle.fontSize));\n this._createCursorObject();\n // initial style\n this.cursor.position.z += 0.001;\n this.cursor.visible = false;\n // We store this for the geometry so we can do our geometry vs web origin calculations\n // more easily as well. We update this based on the geometry's own changes.\n //\n // Set as 0,0,0 to start, and updated when the geometry updates in case it changes in 3d space.\n this.cursorStartingPosition = new three__WEBPACK_IMPORTED_MODULE_1__.Vector3(0, 0, 0);\n this.object3D.add(this.cursor);\n\n // DOM\n this.createHiddenInputElement();\n // this.fillInHiddenInputElementWithUserData(); // TODO - need good list of defaults\n\n // Make it trigger happy\n this.setupEventListeners();\n\n // Updates for baseline visual\n this.triggerGeometryStyleUpdate();\n this.triggerTextStyleUpdate();\n\n // All items should start out as 'not selected'\n // unless noted otherwise.\n if (!this.hiddenInput.getAttribute('autofocus') ?? false) {\n this._blur();\n }\n\n // Handle any placeholder setup s.t. it can be overwritten easily.\n if (this.hiddenInput.getAttribute('placeholder') ?? false) {\n this.textObj.text = this.hiddenInput.getAttribute('placeholder');\n }\n\n // TODO - this needs better boolean naming\n if (this.hasTextSubsetForVerticalScrolling) {\n this.verticalTextObjStartLineIndex = 0;\n this.verticalTextObjEndLineIndex = 0;\n }\n }\n\n /**\n * @function\n * @description Internal function used to setup the cursor object and associated variables\n * needed during runtime. Sets the cursor geometry based on dev updated cursorWidth and\n * cursorHeight MRTextInputEntity variables.\n */\n _createCursorObject() {\n if (!this.cursor) {\n // Setup basic cursor info and material for if it was reset.\n this.cursor = new three__WEBPACK_IMPORTED_MODULE_1__.Mesh();\n const material = mrjsUtils.material.MeshBasicMaterial.clone();\n material.color.set(0x000000);\n material.side = three__WEBPACK_IMPORTED_MODULE_1__.DoubleSide;\n material.programName = 'text:cursorMaterial';\n this.cursor.material = material;\n }\n if (this.cursor.geometry !== undefined) {\n // Handle geometry reclearing\n this.cursor.geometry.dispose();\n }\n // Setup basic cursor geometry\n this.cursor.geometry = new three__WEBPACK_IMPORTED_MODULE_1__.PlaneGeometry(this.cursorWidth, this.cursorHeight);\n this.cursor.geometry.needsUpdate = true;\n }\n\n /**\n * @function\n * @description Internal function used to setup the cursor object and associated variables\n * needed during runtime. User can pass in a new height directly or the function checks\n * whether cursor height should be updated based on fontSize compared to line height\n * and other aspects.\n * @param {number} newHeight - an optional parameter to be used as the cursor's new height.\n */\n _updateCursorSize(newHeight) {\n const cursorVisibleHeight = newHeight ?? this.textObj.fontSize * this.lineHeight;\n if (this.cursor.geometry.parameters.height != cursorVisibleHeight) {\n this.cursorHeight = cursorVisibleHeight;\n this._createCursorObject();\n }\n }\n\n /**\n * @function\n * @description Function to be overwritten by children. Called by the keydown event trigger.\n * @param {event} event - the keydown event\n */\n handleKeydown(event) {\n mrjsUtils.error.emptyParentFunction();\n }\n\n /**\n * @function\n * @description Called by the mouse click event trigger. Handles determining the\n * caret position based on the 3D textObj to hiddenInput DOM position conversion.\n * @param {event} event - the mouseclick event\n */\n handleMouseClick(event) {\n console.log(event);\n // Convert isx position from world position to local:\n // - make sure textObj has updated matrices so we're not calculating info wrong\n // - note: textObj doesnt need sync\n this.textObj.updateMatrixWorld(true);\n const inverseMatrixWorld = new three__WEBPACK_IMPORTED_MODULE_1__.Matrix4().copy(this.textObj.matrixWorld).invert();\n const localPosition = inverseMatrixWorld * event.worldPosition;\n\n // update cursor position based on click\n // TODO - hitting an issue where carret's hit locations are undefined (ie not hit when it should)\n // so the return is defaulting to 0 index. Tried also the below of maybe with textObj but then it\n // never actually runs the code either (bc no text sync update is needed).\n // this.textObj.sync(() => {\n // const caret = getCaretAtPoint(this.textObj.textRenderInfo, localPosition.x, localPosition.y);\n // console.log('caret position: ', caret);\n // this.hiddenInput.selectionStart = caret.charIndex;\n // this.updateCursorPosition();\n // });\n const caret = (0,troika_three_text__WEBPACK_IMPORTED_MODULE_2__.getCaretAtPoint)(this.textObj.textRenderInfo, localPosition.x, localPosition.y);\n console.log('caret position: ', caret);\n this.hiddenInput.selectionStart = caret.charIndex;\n this.updateCursorPosition();\n }\n\n /**\n * @function\n * @description Called by the focus event trigger and in other 'focus' situations. We use the\n * private version of this function signature to not hit the intersection of the actual 'focus()'\n * event naming that we have connected. See 'setupEventListeners()' description for more info.\n * @param {boolean} isPureFocusEvent - Boolean to allow us to update the cursor position with this function\n * directly. Otherwise, we assume there's other things happening after focus was called as part of the event\n * and that the cursor position will be handled there instead.\n */\n _focus(isPureFocusEvent = false) {\n if (!this.hiddenInput) {\n return;\n }\n this.hiddenInput.focus();\n\n if (isPureFocusEvent) {\n // Only want to update cursor and selection position if\n // this is a pure focus event; otherwise, we're assuming\n // the other event will position those properly (so that\n // we dont do redundant positioning here and then there as well).\n this.hiddenInput.selectionStart = this.hiddenInput.value.length;\n this.updateCursorPosition();\n }\n\n this.cursor.visible = true;\n }\n\n /**\n * @function\n * @description Called by the blur event trigger and in other 'blur' situations. We use the\n * private version of this function signature to not hit the intersection of the actual 'blur()'\n * event naming that we have connected. See 'setupEventListeners()' description for more info.\n */\n _blur() {\n if (!this.hiddenInput) {\n return;\n }\n this.hiddenInput.blur();\n\n this.cursor.visible = false;\n }\n\n // TODO - better name?\n /**\n * @function\n * @description Getter for whether this textinput should handle vertical scrolling or not.\n * @returns {boolean} true if it should be handled, false otherwise\n */\n get hasTextSubsetForVerticalScrolling() {\n // Leaving this as a function instead of a pure boolean if in case it's dependant\n // on certain parameters changing that need to be recalculated depending on the input\n // setup, etc.\n mrjsUtils.error.emptyParentFunction();\n return false;\n }\n\n // TODO - better name?\n /**\n * @function\n * @description Getter for whether this textinput should handle horizontal scrolling or not.\n * @returns {boolean} true if it should be handled, false otherwise\n */\n get hasTextSubsetForHorizontalScrolling() {\n // Leaving this as a function instead of a pure boolean if in case it's dependant\n // on certain parameters changing that need to be recalculated depending on the input\n // setup, etc.\n mrjsUtils.error.emptyParentFunction();\n return false;\n }\n\n /**\n * @function\n * @description Getter for a commonly needed attribute: 'disabled' for whether this input is still being updated.\n * @returns {boolean} true if disabled, false otherwise\n */\n get inputIsDisabled() {\n return this.hiddenInput.getAttribute('disabled') ?? false;\n }\n\n /**\n * @function\n * @description Getter for a commonly needed attribute: 'readonly' for whether this input's text can still be changed.\n * @returns {boolean} true if readonly, false otherwise\n */\n get inputIsReadOnly() {\n return this.hiddenInput.getAttribute('readonly') ?? false;\n }\n\n /**\n * @function\n * @description Connecting the event listeners to the actual functions that handle them. Includes\n * additional calls where necessary.\n *\n * Since we want the text input children to be able\n * to override the parent function event triggers,\n * separating them into an actual function here\n * and calling them manually instead of doing the pure\n * 'functionname () => {} event type setup'. This manual\n * connection allows us to call super.func() for event\n * functions; otherwise, theyre not accessible nor implemented\n * in the subclasses.\n */\n setupEventListeners() {\n // Blur events\n this.addEventListener('blur', () => {\n this._blur();\n });\n\n // Pure Focus Events\n this.addEventListener('focus', () => {\n if (this.inputIsDisabled || this.inputIsReadOnly) {\n return;\n }\n this._focus(true);\n });\n this.addEventListener('click', () => {\n if (this.inputIsDisabled || this.inputIsReadOnly) {\n return;\n }\n this._focus(true);\n });\n\n // Focus and Handle Event\n this.addEventListener('touchstart', (event) => {\n if (this.inputIsDisabled || this.inputIsReadOnly) {\n return;\n }\n this._focus();\n this.handleMouseClick(event);\n });\n\n // Keyboard events to capture text in the hidden input.\n this.hiddenInput.addEventListener('input', (event) => {\n if (this.inputIsDisabled || this.inputIsReadOnly) {\n return;\n }\n\n // Input captures all main text character inputs\n // BUT it does not capture arrow keys, so we handle\n // those specifically by the 'keydown' event.\n //\n // We handle all the rest by relying on the internal\n // 'hiddenInput's update system so we dont have to\n // manage as many things directly ourselves.\n\n this.updateTextDisplay(false);\n this.updateCursorPosition(false);\n });\n this.hiddenInput.addEventListener('keydown', (event) => {\n if (this.inputIsDisabled || this.inputIsReadOnly) {\n return;\n }\n\n // Only using keydown for arrow keys, everything else is\n // handled by the input event - check the comment there\n // for more reasoning.\n\n if (event.key == 'ArrowUp' || event.key == 'ArrowDown' || event.key == 'ArrowLeft' || event.key == 'ArrowRight') {\n this.handleKeydown(event);\n }\n });\n\n // Separate trigger call just in case.\n this.addEventListener('update-cursor-position', () => {\n if (this.inputIsDisabled || this.inputIsReadOnly) {\n return;\n }\n\n this.updateCursorPosition();\n });\n }\n\n // TODO - see note where this is called - need to rethink small part of indexing\n // logic so this manual aspect is not necessary.\n /**\n * @function\n * @description Helper function for `handleKeyDown` and `updateCursorPosition` when\n * handling textObj.\n * @param {number} lineIndex - the ending line index non-inclusive of the summation.\n * @param {Array} allLines - the array of line strings\n * @returns {number} length of summed string.\n */\n _totalLengthUpToLineIndex(lineIndex, allLines) {\n let totalLengthTolineIndex = 0;\n for (let i = 0; i < lineIndex; ++i) {\n totalLengthTolineIndex += allLines[i].length + 1; // one additional for '\\n' char\n }\n // TODO - will we need a check/fix to handle the case where the last index\n // may or may not have a '\\n' in it?\n return totalLengthTolineIndex;\n }\n\n // TODO - see note where this is called - need to rethink small part of indexing\n // logic so this manual aspect is not necessary.\n /**\n * @function\n * @description Helper function for `handleKeyDown` and `updateCursorPosition` when\n * handling textObj.\n * @param {number} lineIndexStart - the starting line index inclusive of the summation.\n * @param {number} lineIndexEnd - the starting line index non-inclusive of the summation.\n * @param {Array} allLines - the array of line strings\n * @returns {number} length of summed string.\n */\n _totalLengthBetweenLineIndices(lineIndexStart, lineIndexEnd, allLines) {\n let totalLengthTolineIndex = 0;\n for (let i = lineIndexStart; i < lineIndexEnd; ++i) {\n totalLengthTolineIndex += allLines[i].length + 1; // one additional for '\\n' char\n }\n // TODO - will we need a check/fix to handle the case where the last index\n // may or may not have a '\\n' in it?\n return totalLengthTolineIndex;\n }\n\n /**\n * @function\n * @description Updates the cursor position based on click and selection location.\n * @param {boolean} fromCursorMove - false by default. Used to determine if we need to run\n * based off a text object update sync or we can directly grab information. This requirement\n * occurs because the sync isnt usable if no text content changed.\n *\n * Note: this function does not change anything about the this.hiddenInput.selectionStart nor\n * this.hiddenInput.selectionEnd. Those values should be changed prior to this function being\n * called.\n */\n updateCursorPosition(fromCursorMove = false) {\n // TODO - QUESTION: handle '\\n' --> as '/\\r?\\n/' for crossplatform compat\n // does the browser handle this for us?\n\n const updateBasedOnSelectionRects = (cursorIndex) => {\n // Setup variables for calculations.\n let textBeforeCursor = this.hiddenInput.value.substring(0, cursorIndex);\n let allLines = this.hiddenInput.value.split('\\n');\n let linesBeforeCursor = textBeforeCursor.split('\\n');\n let cursorIsOnLineIndex = linesBeforeCursor.length - 1;\n\n let rectX = undefined;\n let rectY = undefined;\n let rect = undefined;\n\n // Setup textObj variables for calculations\n let cursorIsOnTextObjLineIndex = cursorIsOnLineIndex - this.verticalTextObjStartLineIndex;\n // TODO - there needs to be a cleaner way to do this than summing up every time\n let lengthToCursorTextObjStartLineIndex = this._totalLengthUpToLineIndex(this.verticalTextObjStartLineIndex, allLines);\n let lengthToTextObjCursorLine = this._totalLengthBetweenLineIndices(this.verticalTextObjStartLineIndex, cursorIsOnLineIndex, allLines);\n // note: add one to start it on the actual start line so we can index cursor Index at 0 if beg of line\n let cursorIndexWithinTextObj = cursorIndex - (lengthToCursorTextObjStartLineIndex + 1);\n\n const prevIsNewlineChar = '\\n' === textBeforeCursor.charAt(textBeforeCursor.length - 1);\n if (prevIsNewlineChar) {\n // When on newline char, hiddenInput puts selection at end of newline char,\n // not beginning of next line. Make sure cursor visual is at beginning\n // of the next line without moving selection point.\n //\n // \"\"\"\n // This is an example of text\\n\n // the way troika handles it\n // \"\"\"\n //\n // In the below, using (*) to denote the 'you are here'.\n const isFakeNewLine = cursorIsOnLineIndex == allLines.length - 1;\n let indexOfBegOfLine = lengthToTextObjCursorLine;\n if (isFakeNewLine) {\n // \"\"\"\n // This is an example of text\\n\n // the way troika handles it\\n(*)\n // \"\"\"\n //\n // Special case where next line doesnt exist yet, fake it sitting below with our\n // current line's information.\n let usingIndex = indexOfBegOfLine - 1;\n let selectionRects = (0,troika_three_text__WEBPACK_IMPORTED_MODULE_2__.getSelectionRects)(this.textObj.textRenderInfo, usingIndex, usingIndex + 1);\n // rect information for use in cursor positioning\n rect = selectionRects[0];\n rectY = rect.bottom - this.cursorHeight;\n } else {\n // \"\"\"\n // This is an example of text\\n(*)\n // the way troika handles it\n // \"\"\"\n let usingIndex = cursorIndexWithinTextObj;\n let selectionRects = (0,troika_three_text__WEBPACK_IMPORTED_MODULE_2__.getSelectionRects)(this.textObj.textRenderInfo, usingIndex, usingIndex + 1);\n rect = selectionRects[0];\n rectY = rect.bottom;\n }\n rectX = 0;\n } else {\n // default\n let usingIndex = cursorIndexWithinTextObj;\n let selectionRects = (0,troika_three_text__WEBPACK_IMPORTED_MODULE_2__.getSelectionRects)(this.textObj.textRenderInfo, usingIndex, usingIndex + 1);\n // rect information for use in cursor positioning\n rect = selectionRects[0];\n rectX = rect.right;\n rectY = rect.bottom;\n }\n\n this._updateCursorSize();\n\n // Add the cursor dimension info to the position s.t. it doesnt touch the text itself. We want\n // a little bit of buffer room.\n const cursorXOffsetPosition = rectX + this.cursorWidth;\n const cursorYOffsetPosition = rectY + this.cursorHeight;\n\n // Update the cursor's 3D position\n this.cursor.position.x = this.cursorStartingPosition.x + cursorXOffsetPosition;\n this.cursor.position.y = this.cursorStartingPosition.y + cursorYOffsetPosition;\n this.cursor.visible = true;\n };\n\n // Check if we have any DOM element to work with.\n if (!this.hiddenInput) {\n return;\n }\n\n // Since no text is selected, this and selectionEnd are just the cursor position.\n // XXX - when we actually allow for selection in future, some of the below will need to\n // be thought through again.\n const cursorIndex = this.hiddenInput.selectionStart;\n\n // early escape for empty text based on hiddenInput top line\n if (cursorIndex == 0) {\n this._updateCursorSize();\n this.cursor.position.x = this.cursorStartingPosition.x;\n this.cursor.position.y = this.cursorStartingPosition.y;\n this.cursor.visible = true;\n return;\n }\n\n // Separating textObj sync from the cursor update based on rects\n // since textObj sync resolves when there's actual changes to the\n // object. Otherwise, it'll hang and never hit the update function.\n if (fromCursorMove) {\n updateBasedOnSelectionRects(cursorIndex);\n } else {\n this.textObj.sync(() => {\n updateBasedOnSelectionRects(cursorIndex);\n });\n }\n }\n}\n\n\n//# sourceURL=webpack://mrjs/./src/core/entities/MRTextInputEntity.js?"); /***/ }), @@ -850,7 +850,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ MRUser)\n/* harmony export */ });\n/* harmony import */ var mrjs_core_user_MRHand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/user/MRHand */ \"./src/core/user/MRHand.js\");\n\n\n/**\n * @class MRUser\n */\nclass MRUser {\n forward = new THREE.Object3D();\n\n origin = new THREE.Object3D();\n\n spotlight = null;\n\n hands = {\n left: null,\n right: null,\n };\n /**\n * Constructor for the MRUser class, sets up the camera, hands, and spotlight information.\n * @param {object} camera - the threejs camera to be used as the user's pov.\n * @param {object} scene - the threejs scene in which the user will be immersed.\n */\n constructor(camera, scene) {\n this.camera = camera;\n\n this.hands.left = new mrjs_core_user_MRHand__WEBPACK_IMPORTED_MODULE_0__.MRHand('left', scene);\n this.hands.right = new mrjs_core_user_MRHand__WEBPACK_IMPORTED_MODULE_0__.MRHand('right', scene);\n\n this.camera.add(this.forward);\n this.forward.position.setZ(-0.5);\n this.forward.position.setX(0.015);\n\n this.camera.add(this.origin);\n this.origin.position.setX(0.015);\n\n this.leftWorldPosition = new THREE.Vector3();\n this.rightWorldPosition = new THREE.Vector3();\n this.worldPosition = new THREE.Vector3();\n\n this.leftDistance = 0;\n this.rightDistance = 0;\n\n this.spotLightScale = 1;\n }\n\n /**\n * Initializes the spotlight associated with the user's pov.\n * @returns {object} spotlight - the spotlight to be used.\n */\n initSpotlight() {\n this.spotlight = new THREE.Mesh(new THREE.CircleGeometry(1.3, 64), new THREE.MeshBasicMaterial());\n this.spotlight.material.colorWrite = false;\n this.spotlight.renderOrder = 2;\n this.spotlight.rotation.x = -Math.PI / 2;\n\n return this.spotlight;\n }\n\n /**\n * The update function for a user, its spotlight, and its hands.\n */\n update() {\n this.hands.left.update();\n this.hands.right.update();\n\n if (this.spotlight) {\n this.worldPosition.setFromMatrixPosition(this.origin.matrixWorld);\n this.worldPosition.y = 0;\n\n if (this.hands.left.active) {\n this.hands.left.controller.getWorldPosition(this.leftWorldPosition);\n this.leftWorldPosition.y = 0;\n this.leftDistance = this.worldPosition.distanceTo(this.leftWorldPosition);\n } else {\n this.leftDistance = 0;\n }\n\n if (this.hands.right.active) {\n this.hands.right.controller.getWorldPosition(this.rightWorldPosition);\n this.rightWorldPosition.y = 0;\n this.rightDistance = this.worldPosition.distanceTo(this.rightWorldPosition);\n } else {\n this.rightDistance = 0;\n }\n\n this.spotLightScale = this.leftDistance > this.rightDistance ? this.leftDistance : this.rightDistance;\n\n if (this.spotLightScale > 0) {\n this.spotLightScale += 1;\n this.spotlight.scale.setScalar(this.spotLightScale);\n }\n\n this.spotlight.position.setFromMatrixPosition(this.origin.matrixWorld);\n this.spotlight.position.y = 0;\n }\n }\n}\n\n\n//# sourceURL=webpack://mrjs/./src/core/user/MRUser.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ MRUser)\n/* harmony export */ });\n/* harmony import */ var mrjs_core_user_MRHand__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/user/MRHand */ \"./src/core/user/MRHand.js\");\n\n\n/**\n * @class MRUser\n */\nclass MRUser {\n forward = new THREE.Object3D();\n\n origin = new THREE.Object3D();\n\n spotlight = null;\n\n hands = {\n left: null,\n right: null,\n };\n /**\n * Constructor for the MRUser class, sets up the camera, hands, and spotlight information.\n * @param {object} camera - the threejs camera to be used as the user's pov.\n * @param {object} scene - the threejs scene in which the user will be immersed.\n */\n constructor(camera, scene) {\n this.camera = camera;\n\n this.hands.left = new mrjs_core_user_MRHand__WEBPACK_IMPORTED_MODULE_0__.MRHand('left', scene);\n this.hands.right = new mrjs_core_user_MRHand__WEBPACK_IMPORTED_MODULE_0__.MRHand('right', scene);\n\n this.camera.add(this.forward);\n this.forward.position.setZ(-0.5);\n this.forward.position.setX(0.015);\n\n this.camera.add(this.origin);\n this.origin.position.setX(0.015);\n\n this.leftWorldPosition = new THREE.Vector3();\n this.rightWorldPosition = new THREE.Vector3();\n this.worldPosition = new THREE.Vector3();\n\n this.leftDistance = 0;\n this.rightDistance = 0;\n\n this.spotLightScale = 1;\n }\n\n /**\n * Initializes the spotlight associated with the user's pov.\n * @returns {object} spotlight - the spotlight to be used.\n */\n initSpotlight() {\n const material = mrjsUtils.material.MeshBasicMaterial.clone();\n material.colorWrite = false;\n material.programName = 'spotlightMaterial';\n this.spotlight = new THREE.Mesh(new THREE.CircleGeometry(1.3, 64), material);\n\n this.spotlight.renderOrder = 2;\n this.spotlight.rotation.x = -Math.PI / 2;\n\n return this.spotlight;\n }\n\n /**\n * The update function for a user, its spotlight, and its hands.\n */\n update() {\n this.hands.left.update();\n this.hands.right.update();\n\n if (this.spotlight) {\n this.worldPosition.setFromMatrixPosition(this.origin.matrixWorld);\n this.worldPosition.y = 0;\n\n if (this.hands.left.active) {\n this.hands.left.controller.getWorldPosition(this.leftWorldPosition);\n this.leftWorldPosition.y = 0;\n this.leftDistance = this.worldPosition.distanceTo(this.leftWorldPosition);\n } else {\n this.leftDistance = 0;\n }\n\n if (this.hands.right.active) {\n this.hands.right.controller.getWorldPosition(this.rightWorldPosition);\n this.rightWorldPosition.y = 0;\n this.rightDistance = this.worldPosition.distanceTo(this.rightWorldPosition);\n } else {\n this.rightDistance = 0;\n }\n\n this.spotLightScale = this.leftDistance > this.rightDistance ? this.leftDistance : this.rightDistance;\n\n if (this.spotLightScale > 0) {\n this.spotLightScale += 1;\n this.spotlight.scale.setScalar(this.spotLightScale);\n }\n\n this.spotlight.position.setFromMatrixPosition(this.origin.matrixWorld);\n this.spotlight.position.y = 0;\n }\n }\n}\n\n\n//# sourceURL=webpack://mrjs/./src/core/user/MRUser.js?"); /***/ }), @@ -861,7 +861,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ MRPlaneManager: () => (/* binding */ MRPlaneManager)\n/* harmony export */ });\n/* harmony import */ var mrjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs */ \"./src/index.js\");\n/* harmony import */ var mrjs_dataTypes_MRPlane__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs/dataTypes/MRPlane */ \"./src/dataTypes/MRPlane.js\");\n\n\n\nconst PLANE_LABELS = ['floor', 'wall', 'ceiling', 'table', 'desk', 'couch', 'door', 'window', 'shelf', 'bed', 'screen', 'lamp', 'plant', 'wall art', 'other'];\n\n/**\n * @class MRPlaneManager\n * @classdesc creates and manages the MRjs representation of XR planes.\n * The resulting planes have RAPIER rigid bodies and THREE.js meshes that occlude virtual content by default\n */\nclass MRPlaneManager {\n /**\n * @class\n * @param {object} scene - the MRApp's threejs scene object\n * @param {boolean} occlusion - whether or not the MRPlaneManager should make the planes visible or not\n */\n constructor(scene, occlusion) {\n // TODO: add app level controls for:\n // - planes\n // - mesh\n\n this.occlusion = occlusion ?? 'enable';\n\n this.scene = scene;\n\n this.matrix = new THREE.Matrix4();\n\n this.currentPlanes = new Map();\n\n this.planeDictionary = {};\n\n for (const label of PLANE_LABELS) {\n this.planeDictionary[label] = new Set();\n }\n\n this.tempPosition = new THREE.Vector3();\n this.tempQuaternion = new THREE.Quaternion();\n this.tempScale = new THREE.Vector3();\n\n this.tempDimensions = new THREE.Vector3();\n\n let floorPlane = {\n semanticLabel: 'floor',\n orientation: 'horizontal',\n };\n\n let floorMRPlane = this.initPlane(floorPlane, 10, 10);\n\n floorMRPlane.mesh.geometry = new THREE.CircleGeometry(2, 32);\n floorMRPlane.mesh.position.set(0, 0, 0);\n floorMRPlane.mesh.rotation.x = -Math.PI / 2;\n\n floorMRPlane.mesh.visible = false;\n floorMRPlane.body.setEnabled(false);\n\n document.addEventListener('enterxr', () => {\n floorMRPlane.mesh.visible = true;\n floorMRPlane.body.setEnabled(true);\n });\n\n document.addEventListener('exitxr', () => {\n for (const [plane, mrplane] of this.currentPlanes) {\n this.removePlane(plane, mrplane);\n }\n });\n\n mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.xr.addEventListener('planesdetected', (event) => {\n const planes = event.data.detectedPlanes;\n\n mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.xr.session.requestAnimationFrame((t, frame) => {\n for (const plane of planes) {\n if (this.currentPlanes.has(plane) === false) {\n const pose = frame.getPose(plane.planeSpace, mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.xr.referenceSpace);\n this.matrix.fromArray(pose.transform.matrix);\n this.matrix.decompose(this.tempPosition, this.tempQuaternion, this.tempScale);\n\n const polygon = plane.polygon;\n\n let minX = Number.MAX_SAFE_INTEGER;\n let maxX = Number.MIN_SAFE_INTEGER;\n let minZ = Number.MAX_SAFE_INTEGER;\n let maxZ = Number.MIN_SAFE_INTEGER;\n\n for (const point of polygon) {\n minX = Math.min(minX, point.x);\n maxX = Math.max(maxX, point.x);\n minZ = Math.min(minZ, point.z);\n maxZ = Math.max(maxZ, point.z);\n }\n\n const width = maxX - minX;\n const height = maxZ - minZ;\n\n if (plane.semanticLabel == 'floor') {\n this.removePlane(floorPlane, floorMRPlane);\n }\n\n this.initPlane(plane, width, height);\n }\n }\n });\n });\n }\n\n /**\n * @function\n * @description Initializes the MRPlane for this.currentPlanes at the 'plane' key\n * @param {object} plane - the map key of this.currentPlanes for which we want to initPlane to fill in its value.\n * @param {number} width - expected width of the new MRPlane\n * @param {number} height - expected height of the new MRPlane\n * @returns {object} MRPlane - the MRPlane object that was initialized by this function.\n */\n initPlane(plane, width, height) {\n let mrPlane = new mrjs_dataTypes_MRPlane__WEBPACK_IMPORTED_MODULE_1__.MRPlane();\n\n mrPlane.label = plane.semanticLabel;\n mrPlane.orientation = plane.orientation;\n mrPlane.dimensions.setX(width);\n mrPlane.dimensions.setY(0.001);\n mrPlane.dimensions.setZ(height);\n\n const geometry = new THREE.BoxGeometry(width, 0.01, height);\n const material = new THREE.MeshBasicMaterial({ color: 0xffffff });\n\n mrPlane.mesh = new THREE.Mesh(geometry, material);\n mrPlane.mesh.position.setFromMatrixPosition(this.matrix);\n mrPlane.mesh.quaternion.setFromRotationMatrix(this.matrix);\n mrPlane.mesh.material.colorWrite = false;\n mrPlane.mesh.renderOrder = 2;\n this.scene.add(mrPlane.mesh);\n\n if (this.occlusion != 'enable') {\n mrPlane.mesh.visible = false;\n }\n\n this.tempDimensions.setX(width / 2);\n this.tempDimensions.setY(0.01);\n this.tempDimensions.setZ(height / 2);\n\n mrPlane.body = this.initPhysicsBody();\n\n this.currentPlanes.set(plane, mrPlane);\n this.planeDictionary[mrPlane.label].add(mrPlane);\n return mrPlane;\n }\n\n /**\n * @function\n * @description Removes the MRPlane from the scene and removes the plane object from the currentPlanes map.\n * @param {object} plane - plane object associated with this specific MRPlane in the scene\n * @param {object} mrplane - the specific MRPlane object being removed from the scene\n */\n removePlane(plane, mrplane) {\n mrplane.mesh.geometry.dispose();\n mrplane.mesh.material.dispose();\n this.scene.remove(mrplane.mesh);\n\n mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.physics.world.removeRigidBody(mrplane.body);\n\n this.currentPlanes.delete(plane);\n this.planeDictionary[mrplane.label].delete(mrplane);\n }\n\n /**\n * @function\n * @description Initializes the physics body of an MRPlane\n * @returns {object} body - the created rigid body for the plane\n */\n initPhysicsBody() {\n const rigidBodyDesc = mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.physics.RAPIER.RigidBodyDesc.kinematicPositionBased().setTranslation(...this.tempPosition);\n let colliderDesc = mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.physics.RAPIER.ColliderDesc.cuboid(...this.tempDimensions);\n colliderDesc.setCollisionGroups(mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.physics.CollisionGroups.PLANES);\n let body = mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.physics.world.createRigidBody(rigidBodyDesc);\n body.setRotation(this.tempQuaternion, true);\n let collider = mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.physics.world.createCollider(colliderDesc, body);\n\n collider.setActiveCollisionTypes(mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.physics.RAPIER.ActiveCollisionTypes.DEFAULT | mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.physics.RAPIER.ActiveCollisionTypes.KINEMATIC_FIXED);\n collider.setActiveEvents(mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.physics.RAPIER.ActiveEvents.COLLISION_EVENTS);\n\n return body;\n }\n}\n\n\n//# sourceURL=webpack://mrjs/./src/dataManagers/MRPlaneManager.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ MRPlaneManager: () => (/* binding */ MRPlaneManager)\n/* harmony export */ });\n/* harmony import */ var mrjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs */ \"./src/index.js\");\n/* harmony import */ var mrjs_dataTypes_MRPlane__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs/dataTypes/MRPlane */ \"./src/dataTypes/MRPlane.js\");\n\n\n\nconst PLANE_LABELS = ['floor', 'wall', 'ceiling', 'table', 'desk', 'couch', 'door', 'window', 'shelf', 'bed', 'screen', 'lamp', 'plant', 'wall art', 'other'];\n\n/**\n * @class MRPlaneManager\n * @classdesc creates and manages the MRjs representation of XR planes.\n * The resulting planes have RAPIER rigid bodies and THREE.js meshes that occlude virtual content by default\n */\nclass MRPlaneManager {\n /**\n * @class\n * @param {object} scene - the MRApp's threejs scene object\n * @param {boolean} occlusion - whether or not the MRPlaneManager should make the planes visible or not\n */\n constructor(scene, occlusion) {\n // TODO: add app level controls for:\n // - planes\n // - mesh\n\n this.occlusion = occlusion ?? 'enable';\n\n this.scene = scene;\n\n this.matrix = new THREE.Matrix4();\n\n this.currentPlanes = new Map();\n\n this.planeDictionary = {};\n\n for (const label of PLANE_LABELS) {\n this.planeDictionary[label] = new Set();\n }\n\n this.tempPosition = new THREE.Vector3();\n this.tempQuaternion = new THREE.Quaternion();\n this.tempScale = new THREE.Vector3();\n\n this.tempDimensions = new THREE.Vector3();\n\n let floorPlane = {\n semanticLabel: 'floor',\n orientation: 'horizontal',\n };\n\n let floorMRPlane = this.initPlane(floorPlane, 10, 10);\n\n floorMRPlane.mesh.geometry = new THREE.CircleGeometry(2, 32);\n floorMRPlane.mesh.position.set(0, 0, 0);\n floorMRPlane.mesh.rotation.x = -Math.PI / 2;\n\n floorMRPlane.mesh.visible = false;\n floorMRPlane.body.setEnabled(false);\n\n document.addEventListener('enterxr', () => {\n floorMRPlane.mesh.visible = true;\n floorMRPlane.body.setEnabled(true);\n });\n\n document.addEventListener('exitxr', () => {\n for (const [plane, mrplane] of this.currentPlanes) {\n this.removePlane(plane, mrplane);\n }\n });\n\n mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.xr.addEventListener('planesdetected', (event) => {\n const planes = event.data.detectedPlanes;\n\n mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.xr.session.requestAnimationFrame((t, frame) => {\n for (const plane of planes) {\n if (this.currentPlanes.has(plane) === false) {\n const pose = frame.getPose(plane.planeSpace, mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.xr.referenceSpace);\n this.matrix.fromArray(pose.transform.matrix);\n this.matrix.decompose(this.tempPosition, this.tempQuaternion, this.tempScale);\n\n const polygon = plane.polygon;\n\n let minX = Number.MAX_SAFE_INTEGER;\n let maxX = Number.MIN_SAFE_INTEGER;\n let minZ = Number.MAX_SAFE_INTEGER;\n let maxZ = Number.MIN_SAFE_INTEGER;\n\n for (const point of polygon) {\n minX = Math.min(minX, point.x);\n maxX = Math.max(maxX, point.x);\n minZ = Math.min(minZ, point.z);\n maxZ = Math.max(maxZ, point.z);\n }\n\n const width = maxX - minX;\n const height = maxZ - minZ;\n\n if (plane.semanticLabel == 'floor') {\n this.removePlane(floorPlane, floorMRPlane);\n }\n\n this.initPlane(plane, width, height);\n }\n }\n });\n });\n }\n\n /**\n * @function\n * @description Initializes the MRPlane for this.currentPlanes at the 'plane' key\n * @param {object} plane - the map key of this.currentPlanes for which we want to initPlane to fill in its value.\n * @param {number} width - expected width of the new MRPlane\n * @param {number} height - expected height of the new MRPlane\n * @returns {object} MRPlane - the MRPlane object that was initialized by this function.\n */\n initPlane(plane, width, height) {\n let mrPlane = new mrjs_dataTypes_MRPlane__WEBPACK_IMPORTED_MODULE_1__.MRPlane();\n\n mrPlane.label = plane.semanticLabel;\n mrPlane.orientation = plane.orientation;\n mrPlane.dimensions.setX(width);\n mrPlane.dimensions.setY(0.001);\n mrPlane.dimensions.setZ(height);\n\n const geometry = new THREE.BoxGeometry(width, 0.01, height);\n const material = mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.material.MeshBasicMaterial.clone();\n material.color.set(0xffffff);\n material.colorWrite = false;\n material.programName = 'planeMeshMaterial';\n\n mrPlane.mesh = new THREE.Mesh(geometry, material);\n mrPlane.mesh.position.setFromMatrixPosition(this.matrix);\n mrPlane.mesh.quaternion.setFromRotationMatrix(this.matrix);\n mrPlane.mesh.material.colorWrite = false;\n mrPlane.mesh.renderOrder = 2;\n this.scene.add(mrPlane.mesh);\n\n if (this.occlusion != 'enable') {\n mrPlane.mesh.visible = false;\n }\n\n this.tempDimensions.setX(width / 2);\n this.tempDimensions.setY(0.01);\n this.tempDimensions.setZ(height / 2);\n\n mrPlane.body = this.initPhysicsBody();\n\n this.currentPlanes.set(plane, mrPlane);\n this.planeDictionary[mrPlane.label].add(mrPlane);\n return mrPlane;\n }\n\n /**\n * @function\n * @description Removes the MRPlane from the scene and removes the plane object from the currentPlanes map.\n * @param {object} plane - plane object associated with this specific MRPlane in the scene\n * @param {object} mrplane - the specific MRPlane object being removed from the scene\n */\n removePlane(plane, mrplane) {\n mrplane.mesh.geometry.dispose();\n mrplane.mesh.material.dispose();\n this.scene.remove(mrplane.mesh);\n\n mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.physics.world.removeRigidBody(mrplane.body);\n\n this.currentPlanes.delete(plane);\n this.planeDictionary[mrplane.label].delete(mrplane);\n }\n\n /**\n * @function\n * @description Initializes the physics body of an MRPlane\n * @returns {object} body - the created rigid body for the plane\n */\n initPhysicsBody() {\n const rigidBodyDesc = mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.physics.RAPIER.RigidBodyDesc.kinematicPositionBased().setTranslation(...this.tempPosition);\n let colliderDesc = mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.physics.RAPIER.ColliderDesc.cuboid(...this.tempDimensions);\n colliderDesc.setCollisionGroups(mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.physics.CollisionGroups.PLANES);\n let body = mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.physics.world.createRigidBody(rigidBodyDesc);\n body.setRotation(this.tempQuaternion, true);\n let collider = mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.physics.world.createCollider(colliderDesc, body);\n\n collider.setActiveCollisionTypes(mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.physics.RAPIER.ActiveCollisionTypes.DEFAULT | mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.physics.RAPIER.ActiveCollisionTypes.KINEMATIC_FIXED);\n collider.setActiveEvents(mrjs__WEBPACK_IMPORTED_MODULE_0__.mrjsUtils.physics.RAPIER.ActiveEvents.COLLISION_EVENTS);\n\n return body;\n }\n}\n\n\n//# sourceURL=webpack://mrjs/./src/dataManagers/MRPlaneManager.js?"); /***/ }), @@ -1025,7 +1025,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ material: () => (/* binding */ material)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var mrjsUtils_HTML__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjsUtils/HTML */ \"./src/utils/HTML.js\");\n\n\n\n/**\n * @namespace material\n * @description Useful namespace for helping with Materials and threejs utility functions\n */\nlet material = {};\n\n/**\n * @function\n * @memberof material\n * @param {object} parent - either a THREE.Group or a THREE.mesh/object\n * @description Given the parent, grabs either the parent's direct material or (in the case of a group) the\n * material of the first child hit.\n * @returns {object} material - the grabbed material\n */\nmaterial.getObjectMaterial = function (parent) {\n let foundMesh = false;\n let material;\n\n if (parent instanceof three__WEBPACK_IMPORTED_MODULE_1__.Group) {\n parent.traverse((child) => {\n if (!foundMesh && child instanceof three__WEBPACK_IMPORTED_MODULE_1__.Mesh) {\n material = child.material;\n foundMesh = true;\n }\n });\n } else {\n material = parent.material;\n }\n\n return material;\n};\n\n/**\n * @function\n * @memberof material\n * @param {object} parent - either a THREE.Group or a THREE.mesh/object\n * @param {object} material - a threejs material to be set for either the parent's direct material or\n * (in the case of a group) the material of all children within the parent group.\n * @description Given the parent, grabs either the parents direct material or (in the case of a group) the\n * material of the first child hit.\n * @returns {object} parent - the updated parent object\n */\nmaterial.setObjectMaterial = function (parent, material) {\n if (parent instanceof three__WEBPACK_IMPORTED_MODULE_1__.Group) {\n parent.traverse((child) => {\n if (child instanceof three__WEBPACK_IMPORTED_MODULE_1__.Mesh) {\n child.material = material;\n child.material.needsUpdate = true;\n }\n });\n } else {\n parent.material = material;\n parent.material.needsUpdate = true;\n }\n return parent;\n};\n\n/**\n * @function\n * @memberof material\n * @param {object} src - the url path to the data to be loaded\n * @description Function to load the texture asynchronously and return a promise\n * @returns {object} texture - the fully loaded texture\n */\nmaterial.loadTextureAsync = function (src) {\n return new Promise((resolve, reject) => {\n const textureLoader = new three__WEBPACK_IMPORTED_MODULE_1__.TextureLoader();\n\n let resolvedSrc = mrjsUtils_HTML__WEBPACK_IMPORTED_MODULE_0__.html.resolvePath(src);\n\n // Use the img's src to load the texture\n textureLoader.load(\n resolvedSrc,\n (texture) => {\n resolve(texture);\n },\n undefined,\n (error) => {\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof material\n * @param {object} video - the html video element whose src contains the path to the data to be loaded\n * @description Function to load the texture asynchronously and return a promise\n * @returns {object} texture - the fully loaded texture\n */\nmaterial.loadVideoTextureAsync = function (video) {\n video.src = mrjsUtils_HTML__WEBPACK_IMPORTED_MODULE_0__.html.resolvePath(video.src);\n\n video.muted = true; // Mute the video to allow autoplay\n video.autoplay = false; //true; // Attempt to autoplay\n\n return new Promise((resolve, reject) => {\n // Event listener to ensure video is ready\n video.onloadeddata = () => {\n const videoTexture = new three__WEBPACK_IMPORTED_MODULE_1__.VideoTexture(video);\n videoTexture.needsUpdate = true; // Ensure the texture updates when the video plays\n\n video\n .play()\n .then(() => {\n console.log('Video playback started');\n resolve(videoTexture);\n })\n .catch((e) => {\n console.error('Error trying to play the video:', e);\n reject(e);\n });\n };\n\n video.onerror = (error) => {\n reject(new Error('Error loading video: ' + error.message));\n };\n\n // This can help with ensuring the video loads in some cases\n video.load();\n });\n};\n\n\n\n\n//# sourceURL=webpack://mrjs/./src/utils/Material.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ material: () => (/* binding */ material)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var mrjsUtils_HTML__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjsUtils/HTML */ \"./src/utils/HTML.js\");\n\n\n\n/**\n * @namespace material\n * @description Useful namespace for helping with Materials and threejs utility functions\n */\nlet material = {};\n\n/**\n * Defining materials here to only need to create them once\n * since render calls are proportional to the number of gl Materials.\n *\n * An issue creating a large number of render calls per frame\n * is that we have multiple normal THREEjs materials that we're reusing\n * in places. Since these all just modify the base threejs with uniforms\n * we should just grab and clone from here.\n */\nmaterial.MeshBasicMaterial = new three__WEBPACK_IMPORTED_MODULE_1__.MeshBasicMaterial();\nmaterial.MeshPhongMaterial = new three__WEBPACK_IMPORTED_MODULE_1__.MeshPhongMaterial();\nmaterial.MeshStandardMaterial = new three__WEBPACK_IMPORTED_MODULE_1__.MeshStandardMaterial();\n\n/**\n * @function\n * @memberof material\n * @param {object} parent - either a THREE.Group or a THREE.mesh/object\n * @description Given the parent, grabs either the parent's direct material or (in the case of a group) the\n * material of the first child hit.\n * @returns {object} material - the grabbed material\n */\nmaterial.getObjectMaterial = function (parent) {\n let foundMesh = false;\n let material;\n\n if (parent instanceof three__WEBPACK_IMPORTED_MODULE_1__.Group) {\n parent.traverse((child) => {\n if (!foundMesh && child instanceof three__WEBPACK_IMPORTED_MODULE_1__.Mesh) {\n material = child.material;\n foundMesh = true;\n }\n });\n } else {\n material = parent.material;\n }\n\n return material;\n};\n\n/**\n * @function\n * @memberof material\n * @param {object} parent - either a THREE.Group or a THREE.mesh/object\n * @param {object} material - a threejs material to be set for either the parent's direct material or\n * (in the case of a group) the material of all children within the parent group.\n * @description Given the parent, grabs either the parents direct material or (in the case of a group) the\n * material of the first child hit.\n * @returns {object} parent - the updated parent object\n */\nmaterial.setObjectMaterial = function (parent, material) {\n if (parent instanceof three__WEBPACK_IMPORTED_MODULE_1__.Group) {\n parent.traverse((child) => {\n if (child instanceof three__WEBPACK_IMPORTED_MODULE_1__.Mesh) {\n child.material = material;\n child.material.needsUpdate = true;\n }\n });\n } else {\n parent.material = material;\n parent.material.needsUpdate = true;\n }\n return parent;\n};\n\n/**\n * @function\n * @memberof material\n * @param {object} src - the url path to the data to be loaded\n * @description Function to load the texture asynchronously and return a promise\n * @returns {object} texture - the fully loaded texture\n */\nmaterial.loadTextureAsync = function (src) {\n return new Promise((resolve, reject) => {\n const textureLoader = new three__WEBPACK_IMPORTED_MODULE_1__.TextureLoader();\n\n let resolvedSrc = mrjsUtils_HTML__WEBPACK_IMPORTED_MODULE_0__.html.resolvePath(src);\n\n // Use the img's src to load the texture\n textureLoader.load(\n resolvedSrc,\n (texture) => {\n resolve(texture);\n },\n undefined,\n (error) => {\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof material\n * @param {object} video - the html video element whose src contains the path to the data to be loaded\n * @description Function to load the texture asynchronously and return a promise\n * @returns {object} texture - the fully loaded texture\n */\nmaterial.loadVideoTextureAsync = function (video) {\n video.src = mrjsUtils_HTML__WEBPACK_IMPORTED_MODULE_0__.html.resolvePath(video.src);\n\n video.muted = true; // Mute the video to allow autoplay\n video.autoplay = false; //true; // Attempt to autoplay\n\n return new Promise((resolve, reject) => {\n // Event listener to ensure video is ready\n video.onloadeddata = () => {\n const videoTexture = new three__WEBPACK_IMPORTED_MODULE_1__.VideoTexture(video);\n videoTexture.needsUpdate = true; // Ensure the texture updates when the video plays\n\n video\n .play()\n .then(() => {\n console.log('Video playback started');\n resolve(videoTexture);\n })\n .catch((e) => {\n console.error('Error trying to play the video:', e);\n reject(e);\n });\n };\n\n video.onerror = (error) => {\n reject(new Error('Error loading video: ' + error.message));\n };\n\n // This can help with ensuring the video loads in some cases\n video.load();\n });\n};\n\n\n\n\n//# sourceURL=webpack://mrjs/./src/utils/Material.js?"); /***/ }), @@ -1382,7 +1382,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac /******/ var scripts = document.getElementsByTagName("script"); /******/ if(scripts.length) { /******/ var i = scripts.length - 1; -/******/ while (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src; +/******/ while (i > -1 && !scriptUrl) scriptUrl = scripts[i--].src; /******/ } /******/ } /******/ } diff --git a/samples/examples/physics.html b/samples/examples/physics.html index 138cabb3..846fe392 100644 --- a/samples/examples/physics.html +++ b/samples/examples/physics.html @@ -121,17 +121,17 @@ // tempSize.multiplyScalar(model.compStyle.scale) let geometry = new THREE.BoxGeometry(...tempSize) - let hoverMaterial = new THREE.MeshPhongMaterial({ - color: 0x00ff00, - transparent: true, - opacity: 0.5 - }) - - let touchMaterial = new THREE.MeshPhongMaterial({ - color: 0xff0000, - transparent: true, - opacity: 0.5 - }) + let hoverMaterial = mrjsUtils.material.MeshPhongMaterial.clone(); + hoverMaterial.color.set(0x00ff00); + hoverMaterial.transparent = true; + hoverMaterial.opacity = 0.5; + hoverMaterial.name = "hoverMaterial"; + + let touchMaterial = mrjsUtils.material.MeshPhongMaterial.clone(); + touchMaterial.color.set(0xff0000); + touchMaterial.transparent = true; + touchMaterial.opacity = 0.5; + touchMaterial.name = "touchMaterial"; let hoverMesh = new THREE.Mesh(geometry, hoverMaterial) let touchMesh = new THREE.Mesh(geometry, touchMaterial) diff --git a/src/core/MRApp.js b/src/core/MRApp.js index 43709c63..20e46623 100644 --- a/src/core/MRApp.js +++ b/src/core/MRApp.js @@ -71,6 +71,7 @@ export class MRApp extends MRElement { // The rest of the renderer is filled out in this.connectedCallback()-->this.init() since // the renderer relies on certain component flags attached to the itself. this.renderer = null; + this.render = this.render.bind(this); this.lighting = { enabled: true, @@ -83,7 +84,6 @@ export class MRApp extends MRElement { this.cameraOptions = { mode: 'orthographic', }; - this.render = this.render.bind(this); this.onWindowResize = this.onWindowResize.bind(this); } diff --git a/src/core/componentSystems/ClippingSystem.js b/src/core/componentSystems/ClippingSystem.js index 29ff7a2c..08b460b1 100644 --- a/src/core/componentSystems/ClippingSystem.js +++ b/src/core/componentSystems/ClippingSystem.js @@ -38,7 +38,9 @@ export class ClippingSystem extends MRSystem { */ update(deltaTime, frame) { for (const entity of this.registry) { - this.updatePlanes(entity); + if (entity.visible) { + this.updatePlanes(entity); + } } } diff --git a/src/core/componentSystems/ControlSystem.js b/src/core/componentSystems/ControlSystem.js index 8a41ad9e..aed5c70f 100644 --- a/src/core/componentSystems/ControlSystem.js +++ b/src/core/componentSystems/ControlSystem.js @@ -62,7 +62,12 @@ export class ControlSystem extends MRSystem { this.currentEntity = null; - this.cursorViz = new THREE.Mesh(new THREE.RingGeometry(0.005, 0.007, 32), new THREE.MeshBasicMaterial({ color: 0x000000, opacity: 0.7, transparent: true })); + const cursorMaterial = mrjsUtils.material.MeshBasicMaterial.clone(); + cursorMaterial.color.set(0x000000); + cursorMaterial.opacity = 0.7; + cursorMaterial.transparent = true; + cursorMaterial.name = 'cursorMaterial'; + this.cursorViz = new THREE.Mesh(new THREE.RingGeometry(0.005, 0.007, 32), cursorMaterial); this.app.scene.add(this.cursorViz); this.cursorViz.visible = false; diff --git a/src/core/componentSystems/InstancingSystem.js b/src/core/componentSystems/InstancingSystem.js index 0d885972..739638b0 100644 --- a/src/core/componentSystems/InstancingSystem.js +++ b/src/core/componentSystems/InstancingSystem.js @@ -81,7 +81,8 @@ export class InstancingSystem extends MRSystem { // ----- add instances to scene ----- // Create an InstancedMesh using the instanced geometry and matrices - const material = new THREE.MeshBasicMaterial({ color: 0xffff00 }); + const material = mrjsUtils.material.MeshBasicMaterial.clone(); + material.color.set(0xffff00); const instancedMesh = new THREE.InstancedMesh(instancedGeometry, material, this.instanceCount); instancedMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage); diff --git a/src/core/componentSystems/MaskingSystem.js b/src/core/componentSystems/MaskingSystem.js index 571caf88..8a385e4a 100644 --- a/src/core/componentSystems/MaskingSystem.js +++ b/src/core/componentSystems/MaskingSystem.js @@ -6,6 +6,8 @@ import { MREntity } from 'mrjs/core/MREntity'; import { MRPanelEntity } from 'mrjs/core/entities/MRPanelEntity'; import { MRTextEntity } from 'mrjs/core/entities/MRTextEntity'; +import { mrjsUtils } from 'mrjs'; + /* * A system that handles elements that mask other elements by using stencil. * Eg: A Panel does not display child elements if the elements are positioned @@ -166,7 +168,9 @@ export class MaskingSystem extends MRSystem { // Since only needs to write to the stencil buffer, no need to write to the color buffer, // therefore, we can use a simpler material than MeshBasicMaterial. Should we use // ShaderMaterial? - const mesh = new THREE.Mesh(sourceObj.geometry, new THREE.MeshBasicMaterial()); + const material = mrjsUtils.material.MeshBasicMaterial.clone(); + material.programName = 'maskingMaterial'; + const mesh = new THREE.Mesh(sourceObj.geometry, material); setupMaskingMaterial(mesh.material, stencilRefShift, this.app.debug); // No automatic matrices update because world matrices are updated in sync(). diff --git a/src/core/entities/MRDivEntity.js b/src/core/entities/MRDivEntity.js index c27e847d..5b99d281 100644 --- a/src/core/entities/MRDivEntity.js +++ b/src/core/entities/MRDivEntity.js @@ -21,12 +21,11 @@ export class MRDivEntity extends MREntity { this.physics.type = 'ui'; const geometry = mrjsUtils.geometry.UIPlane(1, 1, [0], 18); - const material = new THREE.MeshStandardMaterial({ - color: 0xfff, - roughness: 0.7, - metalness: 0.0, - side: THREE.DoubleSide, - }); + const material = mrjsUtils.material.MeshStandardMaterial.clone(); + material.color.set(0xfff); + material.color.roughness = 0.7; + material.color.metalness = 0.0; + material.side = THREE.DoubleSide; this.background = new THREE.Mesh(geometry, material); this.background.receiveShadow = true; diff --git a/src/core/entities/MRMediaEntity.js b/src/core/entities/MRMediaEntity.js index 08f574e4..69bb8961 100644 --- a/src/core/entities/MRMediaEntity.js +++ b/src/core/entities/MRMediaEntity.js @@ -20,9 +20,8 @@ export class MRMediaEntity extends MRDivEntity { // Create the object3D. Dont need default value for geometry // until the connected call since this will get overwritten anyways. - const material = new THREE.MeshStandardMaterial({ - side: THREE.FrontSide, - }); + const material = mrjsUtils.material.MeshStandardMaterial.clone(); + material.side = THREE.FrontSide; // Object3D for MRMediaEntity (mrimage,mrvideo,etc) is the actual image/video/etc itself in 3D space this.object3D = new THREE.Mesh(undefined, material); this.object3D.receiveShadow = true; @@ -211,9 +210,9 @@ export class MRMediaEntity extends MRDivEntity { } const mediaGeometry = new THREE.PlaneGeometry(mediaWidth, mediaHeight); - const mediaMaterial = new THREE.MeshStandardMaterial({ - map: this.texture, - }); + const mediaMaterial = mrjsUtils.material.MeshStandardMaterial.clone(); + mediaMaterial.map = this.texture; + mediaMaterial.name = 'mediaMaterial'; _oldSubMediaMeshNotNeeded(); this.subMediaMesh.geometry = mediaGeometry; this.subMediaMesh.material = mediaMaterial; diff --git a/src/core/entities/MRModelEntity.js b/src/core/entities/MRModelEntity.js index 3f0120b7..ea7bcc7d 100644 --- a/src/core/entities/MRModelEntity.js +++ b/src/core/entities/MRModelEntity.js @@ -121,8 +121,6 @@ export class MRModelEntity extends MRDivEntity { this.animations = animations; } - this.object3D.add(this.modelObj); - this.modelObj.receiveShadow = true; this.modelObj.renderOrder = 3; @@ -134,6 +132,9 @@ export class MRModelEntity extends MRDivEntity { } }); + this.object3D.add(this.modelObj); + this.object3D.name = this.src; + this.onLoad(); this.loading = false; diff --git a/src/core/entities/MRSkyBoxEntity.js b/src/core/entities/MRSkyBoxEntity.js index 083a4de6..98e4d5ce 100644 --- a/src/core/entities/MRSkyBoxEntity.js +++ b/src/core/entities/MRSkyBoxEntity.js @@ -34,20 +34,22 @@ export class MRSkyBoxEntity extends MREntity { if (this.skybox.material !== undefined) { this.skybox.material.dispose(); } - this.skybox.material = new THREE.MeshStandardMaterial({ - envMap: texture, - side: THREE.BackSide, // Render only on the inside - }); + const material = mrjsUtils.material.MeshStandardMaterial.clone(); + material.envMap = texture; + material.side = THREE.BackSide; + material.programName = 'skyboxMaterial-1'; + this.skybox.material = material; } else { // Handle single texture case if (this.skybox.material !== undefined) { this.skybox.material.dispose(); } - this.skybox.material = new THREE.MeshBasicMaterial({ - map: texture, - side: THREE.BackSide, // Render only on the inside - opacity: 1, - }); + const material = mrjsUtils.material.MeshBasicMaterial.clone(); + material.envMap = texture; + material.side = THREE.BackSide; + material.opacity = 1; + material.programName = 'skyboxMaterial-2'; + this.skybox.material = material; } } this.textureLoadedCallbacks.forEach((callback) => callback(texture)); diff --git a/src/core/entities/MRTextInputEntity.js b/src/core/entities/MRTextInputEntity.js index 29dfd095..99566bae 100644 --- a/src/core/entities/MRTextInputEntity.js +++ b/src/core/entities/MRTextInputEntity.js @@ -129,10 +129,10 @@ export class MRTextInputEntity extends MRTextEntity { if (!this.cursor) { // Setup basic cursor info and material for if it was reset. this.cursor = new THREE.Mesh(); - const material = new THREE.MeshBasicMaterial({ - color: 0x000000, - side: THREE.DoubleSide, - }); + const material = mrjsUtils.material.MeshBasicMaterial.clone(); + material.color.set(0x000000); + material.side = THREE.DoubleSide; + material.programName = 'text:cursorMaterial'; this.cursor.material = material; } if (this.cursor.geometry !== undefined) { diff --git a/src/core/user/MRUser.js b/src/core/user/MRUser.js index b8c0948b..cac3f8fa 100644 --- a/src/core/user/MRUser.js +++ b/src/core/user/MRUser.js @@ -47,8 +47,11 @@ export default class MRUser { * @returns {object} spotlight - the spotlight to be used. */ initSpotlight() { - this.spotlight = new THREE.Mesh(new THREE.CircleGeometry(1.3, 64), new THREE.MeshBasicMaterial()); - this.spotlight.material.colorWrite = false; + const material = mrjsUtils.material.MeshBasicMaterial.clone(); + material.colorWrite = false; + material.programName = 'spotlightMaterial'; + this.spotlight = new THREE.Mesh(new THREE.CircleGeometry(1.3, 64), material); + this.spotlight.renderOrder = 2; this.spotlight.rotation.x = -Math.PI / 2; diff --git a/src/dataManagers/MRPlaneManager.js b/src/dataManagers/MRPlaneManager.js index 7337b1f2..511d08d6 100644 --- a/src/dataManagers/MRPlaneManager.js +++ b/src/dataManagers/MRPlaneManager.js @@ -120,7 +120,10 @@ export class MRPlaneManager { mrPlane.dimensions.setZ(height); const geometry = new THREE.BoxGeometry(width, 0.01, height); - const material = new THREE.MeshBasicMaterial({ color: 0xffffff }); + const material = mrjsUtils.material.MeshBasicMaterial.clone(); + material.color.set(0xffffff); + material.colorWrite = false; + material.programName = 'planeMeshMaterial'; mrPlane.mesh = new THREE.Mesh(geometry, material); mrPlane.mesh.position.setFromMatrixPosition(this.matrix); diff --git a/src/utils/Material.js b/src/utils/Material.js index 13029c31..0293c7a9 100644 --- a/src/utils/Material.js +++ b/src/utils/Material.js @@ -7,6 +7,19 @@ import { html } from 'mrjsUtils/HTML'; */ let material = {}; +/** + * Defining materials here to only need to create them once + * since render calls are proportional to the number of gl Materials. + * + * An issue creating a large number of render calls per frame + * is that we have multiple normal THREEjs materials that we're reusing + * in places. Since these all just modify the base threejs with uniforms + * we should just grab and clone from here. + */ +material.MeshBasicMaterial = new THREE.MeshBasicMaterial(); +material.MeshPhongMaterial = new THREE.MeshPhongMaterial(); +material.MeshStandardMaterial = new THREE.MeshStandardMaterial(); + /** * @function * @memberof material