From fe071497a66aaac57b037b86d248eb6d1316c567 Mon Sep 17 00:00:00 2001 From: hanbollar Date: Mon, 13 May 2024 13:27:10 -0700 Subject: [PATCH 01/12] start Signed-off-by: hanbollar --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cf40521e..67b16940 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ![The MRjs logo, an indigo and purple bowtie.](https://docs.mrjs.io/static/mrjs-logo.svg) - + An extensible library of Web Components for the spatial web. [![npm run build](https://github.com/Volumetrics-io/mrjs/actions/workflows/build.yml/badge.svg)](https://github.com/Volumetrics-io/mrjs/actions/workflows/build.yml) [![npm run test](https://github.com/Volumetrics-io/mrjs/actions/workflows/test.yml/badge.svg)](https://github.com/Volumetrics-io/mrjs/actions/workflows/test.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/Volumetrics-io/mrjs/blob/main/LICENSE) From 2099ebd25eaf59812113d2ee44412abb34d4d23f Mon Sep 17 00:00:00 2001 From: hanbollar Date: Mon, 13 May 2024 14:11:11 -0700 Subject: [PATCH 02/12] temp save Signed-off-by: hanbollar --- dist/mr.js | 4 +- package-lock.json | 17 +++ package.json | 7 +- samples/index.html | 290 ++++++++------------------------------------- src/core/MRApp.js | 15 ++- 5 files changed, 87 insertions(+), 246 deletions(-) diff --git a/dist/mr.js b/dist/mr.js index f5412853..4c30121a 100644 --- a/dist/mr.js +++ b/dist/mr.js @@ -59,7 +59,7 @@ eval("\n\nmodule.exports = function (i) {\n return i[1];\n};\n\n//# sourceURL=w \**********************/ /***/ ((module) => { -eval("module.exports = {\"name\":\"mrjs\",\"version\":\"0.6.3\",\"type\":\"module\",\"description\":\"an MR first webXR framework\",\"engines\":{\"node\":\"^18.17.0 || >=20.5.0\"},\"main\":\"dist/mr.js\",\"homepage\":\"https://mrjs.io\",\"scripts\":{\"build\":\"npx webpack --config webpack.config.js\",\"update-submodules\":\"./scripts/update-all-submodules.sh\",\"server\":\"npx webpack serve\",\"test-server\":\"NODE_ENV=testing npx webpack serve\",\"test\":\"NODE_OPTIONS=--experimental-vm-modules npx jest\",\"test-serially\":\"NODE_OPTIONS=--experimental-vm-modules npx jest --runInBand\",\"test-randomized\":\"NODE_OPTIONS=--experimental-vm-modules npx jest --runInBand --random\",\"clear-testing-cache\":\"npx jest --clearCache\",\"docs\":\"./scripts/create-docs.sh\",\"prettier-check\":\"prettier --check \\\"src/**/*.js\\\" \\\"*.js\\\"\",\"prettier-fix\":\"prettier --write \\\"src/**/*.js\\\" \\\"*.js\\\"\",\"lint-check\":\"eslint \\\"src/**/*.js\\\" \\\"*.js\\\" --ignore-pattern \\\"src/extras/**\\\" --max-warnings 0\",\"lint-fix\":\"eslint \\\"src/**/*.js\\\" \\\"*.js\\\" --ignore-pattern \\\"src/extras/**\\\" --fix\",\"format\":\"npm run prettier-fix && npm run lint-fix\",\"check-format\":\"npm run prettier-check && npm run lint-check\"},\"repository\":{\"type\":\"git\",\"url\":\"git+https://github.com/Volumetrics-io/mrjs.git\"},\"author\":\"Volumetrics\",\"license\":\"MIT\",\"bugs\":{\"url\":\"https://github.com/Volumetrics-io/mrjs/issues\"},\"testMatch\":[\"**/__tests__/**/*.test.mjs\"],\"testEnvironment\":\"node\",\"devDependencies\":{\"@babel/core\":\"^7.23.9\",\"babel-jest\":\"^29.7.0\",\"copy-webpack-plugin\":\"^11.0.0\",\"css-loader\":\"^7.1.1\",\"eslint\":\"^8.56.0\",\"eslint-config-airbnb\":\"^19.0.4\",\"eslint-config-airbnb-base\":\"^15.0.0\",\"eslint-config-prettier\":\"^8.10.0\",\"eslint-plugin-import\":\"^2.29.1\",\"eslint-plugin-jsdoc\":\"^48.2.4\",\"eslint-plugin-jsx-a11y\":\"^6.8.0\",\"eslint-plugin-prettier\":\"^5.1.3\",\"eslint-plugin-react\":\"^7.33.2\",\"eslint-plugin-react-hooks\":\"^4.6.0\",\"eslint-plugin-unused-imports\":\"^3.0.0\",\"esm\":\"^3.2.25\",\"html-webpack-plugin\":\"^5.6.0\",\"install\":\"^0.13.0\",\"jest\":\"^29.7.0\",\"jest-environment-jsdom\":\"^29.7.0\",\"jest-fetch-mock\":\"^3.0.3\",\"jest-puppeteer\":\"^9.0.2\",\"jsdoc\":\"^4.0.2\",\"jsdoc-to-markdown\":\"^8.0.1\",\"json-loader\":\"^0.5.7\",\"mini-css-extract-plugin\":\"^2.8.0\",\"npm\":\"^10.4.0\",\"playwright\":\"^1.41.2\",\"puppeteer\":\"^22.8.0\",\"style-loader\":\"^4.0.0\",\"tui-jsdoc-template\":\"^1.2.2\",\"url\":\"^0.11.3\",\"webpack\":\"^5.90.1\",\"webpack-cli\":\"^5.1.4\",\"webpack-dev-server\":\"^4.15.1\"},\"dependencies\":{\"@babel/eslint-parser\":\"^7.23.10\",\"@dimforge/rapier3d\":\"^0.12.0\",\"docdash\":\"^2.0.2\",\"jaguarjs-jsdoc\":\"^1.1.0\",\"jsdom\":\"^24.0.0\",\"prettier\":\"^3.2.4\",\"prettier-eslint-cli\":\"^8.0.1\",\"stats.js\":\"^0.17.0\",\"three\":\"^0.161.0\",\"troika-three-text\":\"^0.48.1\"}}\n\n//# sourceURL=webpack://mrjs/./package.json?"); +eval("module.exports = {\"name\":\"mrjs\",\"version\":\"0.6.3\",\"type\":\"module\",\"description\":\"an MR first webXR framework\",\"engines\":{\"node\":\"^18.17.0 || >=20.5.0\"},\"main\":\"dist/mr.js\",\"homepage\":\"https://mrjs.io\",\"scripts\":{\"build\":\"npx webpack --config webpack.config.js\",\"update-submodules\":\"./scripts/update-all-submodules.sh\",\"server\":\"npx webpack serve\",\"test-server\":\"NODE_ENV=testing npx webpack serve\",\"test\":\"NODE_OPTIONS=--experimental-vm-modules npx jest\",\"test-serially\":\"NODE_OPTIONS=--experimental-vm-modules npx jest --runInBand\",\"test-randomized\":\"NODE_OPTIONS=--experimental-vm-modules npx jest --runInBand --random\",\"clear-testing-cache\":\"npx jest --clearCache\",\"docs\":\"./scripts/create-docs.sh\",\"prettier-check\":\"prettier --check \\\"src/**/*.js\\\" \\\"*.js\\\"\",\"prettier-fix\":\"prettier --write \\\"src/**/*.js\\\" \\\"*.js\\\"\",\"lint-check\":\"eslint \\\"src/**/*.js\\\" \\\"*.js\\\" --ignore-pattern \\\"src/extras/**\\\" --max-warnings 0\",\"lint-fix\":\"eslint \\\"src/**/*.js\\\" \\\"*.js\\\" --ignore-pattern \\\"src/extras/**\\\" --fix\",\"format\":\"npm run prettier-fix && npm run lint-fix\",\"check-format\":\"npm run prettier-check && npm run lint-check\"},\"repository\":{\"type\":\"git\",\"url\":\"git+https://github.com/Volumetrics-io/mrjs.git\"},\"author\":\"Volumetrics\",\"license\":\"MIT\",\"bugs\":{\"url\":\"https://github.com/Volumetrics-io/mrjs/issues\"},\"testMatch\":[\"**/__tests__/**/*.test.mjs\"],\"testEnvironment\":\"node\",\"devDependencies\":{\"@babel/core\":\"^7.23.9\",\"babel-jest\":\"^29.7.0\",\"copy-webpack-plugin\":\"^11.0.0\",\"css-loader\":\"^7.1.1\",\"eslint\":\"^8.56.0\",\"eslint-config-airbnb\":\"^19.0.4\",\"eslint-config-airbnb-base\":\"^15.0.0\",\"eslint-config-prettier\":\"^8.10.0\",\"eslint-plugin-import\":\"^2.29.1\",\"eslint-plugin-jsdoc\":\"^48.2.4\",\"eslint-plugin-jsx-a11y\":\"^6.8.0\",\"eslint-plugin-prettier\":\"^5.1.3\",\"eslint-plugin-react\":\"^7.33.2\",\"eslint-plugin-react-hooks\":\"^4.6.0\",\"eslint-plugin-unused-imports\":\"^3.0.0\",\"esm\":\"^3.2.25\",\"html-webpack-plugin\":\"^5.6.0\",\"install\":\"^0.13.0\",\"jest\":\"^29.7.0\",\"jest-environment-jsdom\":\"^29.7.0\",\"jest-fetch-mock\":\"^3.0.3\",\"jest-puppeteer\":\"^9.0.2\",\"jsdoc\":\"^4.0.2\",\"jsdoc-to-markdown\":\"^8.0.1\",\"json-loader\":\"^0.5.7\",\"mini-css-extract-plugin\":\"^2.8.0\",\"npm\":\"^10.4.0\",\"playwright\":\"^1.41.2\",\"puppeteer\":\"^22.8.0\",\"style-loader\":\"^4.0.0\",\"tui-jsdoc-template\":\"^1.2.2\",\"url\":\"^0.11.3\",\"webpack\":\"^5.90.1\",\"webpack-cli\":\"^5.1.4\",\"webpack-dev-server\":\"^4.15.1\"},\"dependencies\":{\"@babel/eslint-parser\":\"^7.23.10\",\"@dimforge/rapier3d\":\"^0.12.0\",\"docdash\":\"^2.0.2\",\"jaguarjs-jsdoc\":\"^1.1.0\",\"jsdom\":\"^24.0.0\",\"prettier\":\"^3.2.4\",\"prettier-eslint-cli\":\"^8.0.1\",\"spectorjs\":\"^0.9.30\",\"stats.js\":\"^0.17.0\",\"three\":\"^0.161.0\",\"troika-three-text\":\"^0.48.1\"}}\n\n//# sourceURL=webpack://mrjs/./package.json?"); /***/ }), @@ -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.getAttribute('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.getAttribute('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.getAttribute('preserve-drawing-buffer') ?? 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.getAttribute('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.getAttribute('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.getAttribute('lighting') ?? false) {\n this.lighting = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToJson(this.lighting);\n }\n this.initLights(this.lighting);\n\n /* --- Stats Setup --- */\n\n if (this.getAttribute('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.getAttribute('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.camera) {\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// import * as SPECTOR from 'spectorjs';\n// let spector = new SPECTOR.Spector();\n// spector.displayUI();\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.getAttribute('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.getAttribute('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.getAttribute('preserve-drawing-buffer') ?? 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.getAttribute('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.getAttribute('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.getAttribute('lighting') ?? false) {\n this.lighting = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToJson(this.lighting);\n }\n this.initLights(this.lighting);\n\n /* --- Stats Setup --- */\n\n if (this.getAttribute('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.getAttribute('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.camera) {\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.scene.traverse((object) => {\n if (object.isMesh) {\n console.log(`Rendering ${object.name} with material ${object.material.name}`);\n }\n });\n // renderer.render(scene, camera);\n\n this.renderer.render(this.scene, this.camera);\n\n // Log the number of draw calls\n console.log('number of draw calls:', this.renderer.info.render.calls);\n }\n}\n\ncustomElements.get('mr-app') || customElements.define('mr-app', MRApp);\n\n\n//# sourceURL=webpack://mrjs/./src/core/MRApp.js?"); /***/ }), diff --git a/package-lock.json b/package-lock.json index 9ff8c64a..ea364727 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "jsdom": "^24.0.0", "prettier": "^3.2.4", "prettier-eslint-cli": "^8.0.1", + "spectorjs": "^0.9.30", "stats.js": "^0.17.0", "three": "^0.161.0", "troika-three-text": "^0.48.1" @@ -1808,6 +1809,14 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/@shaderfrog/glsl-parser": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@shaderfrog/glsl-parser/-/glsl-parser-2.1.5.tgz", + "integrity": "sha512-fw7U8Y7BBV9NZVLs7lyM8lpQ9YUK7XBNJjN4HhkY3nqkKpSiIQ62PVtPYtW7uZME5072QXQNQBhHJzG3t+NH0w==", + "engines": { + "node": ">=16" + } + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -16039,6 +16048,14 @@ "wbuf": "^1.7.3" } }, + "node_modules/spectorjs": { + "version": "0.9.30", + "resolved": "https://registry.npmjs.org/spectorjs/-/spectorjs-0.9.30.tgz", + "integrity": "sha512-I9/VI3gfO3UiBCUXj20lQjP3fbezP2y03Virnv3jcMpIiF+/NOyWDrBQ+U8vf29IXHPscxOXzzmZDCkETznsMg==", + "dependencies": { + "@shaderfrog/glsl-parser": "^2.0.1" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", diff --git a/package.json b/package.json index b5b8343f..55366814 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,7 @@ "type": "module", "description": "an MR first webXR framework", "engines": { - "node": ">=20.0.0", - "npm": ">=6.14.4" + "node": "^18.17.0 || >=20.5.0" }, "main": "dist/mr.js", "homepage": "https://mrjs.io", @@ -35,9 +34,6 @@ "bugs": { "url": "https://github.com/Volumetrics-io/mrjs/issues" }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - }, "testMatch": [ "**/__tests__/**/*.test.mjs" ], @@ -87,6 +83,7 @@ "jsdom": "^24.0.0", "prettier": "^3.2.4", "prettier-eslint-cli": "^8.0.1", + "spectorjs": "^0.9.30", "stats.js": "^0.17.0", "three": "^0.161.0", "troika-three-text": "^0.48.1" diff --git a/samples/index.html b/samples/index.html index 969e4bd8..51fbb04a 100644 --- a/samples/index.html +++ b/samples/index.html @@ -1,257 +1,71 @@ - - + - + + Volumetrics — Your Spatial developement companion + + + + - - - - - - - - - An extensible library of Web Components for the spatial web. - MRjs is a mixed-reality-first, WebXR interface library meant to bootstrap - spatial web app development. It implements much of the foundational work so that developers can - spend less time on the basics and more time on their app. - See on GitHub - Join us on Discord - Documentation - - - - - - Extensible - Being open-source and built on top of web standards, you can easily - implement custom elements and contribute to the ecosystem. - - - - Familiar - Looks and feels like native HTML and CSS, but designed from the ground up - for mixed-reality. Start with 2D and ease your way into creating 3D content. - - - - All-in-one - Asset management, physics, and user interaction come built in, so you can - spend less time laying the foundation and more time building your app. - - - - - - - Examples - - - - - Anchoring - - - - Audio - - - - Camera - - - - Debugging - - - - Embed - - - - Images - - - - Models - - - - Skybox - - - - Video - - - - - - - - + + + + + - diff --git a/src/core/MRApp.js b/src/core/MRApp.js index 29253b93..9f612988 100644 --- a/src/core/MRApp.js +++ b/src/core/MRApp.js @@ -2,6 +2,9 @@ import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { XRButton } from 'three/addons/webxr/XRButton.js'; import Stats from 'stats.js'; +// import * as SPECTOR from 'spectorjs'; +// let spector = new SPECTOR.Spector(); +// spector.displayUI(); import { mrjsUtils } from 'mrjs'; @@ -595,7 +598,7 @@ export class MRApp extends MRElement { this.renderer.clear(); // Need to wait until we have all needed rendering-associated systems loaded. - if (this.maskingSystem !== undefined) { + if (this.maskingSystem !== undefined && this.maskingSystem.scene.length > 0) { this.maskingSystem.sync(); const currentShadowEnabled = this.renderer.shadowMap.enabled; this.renderer.shadowMap.enabled = false; @@ -603,7 +606,17 @@ export class MRApp extends MRElement { this.renderer.shadowMap.enabled = currentShadowEnabled; } + // this.scene.traverse((object) => { + // if (object.isMesh) { + // console.log(`Rendering `, object, `name: ${object.name} with num children: ${object.children.length} with material ${object.material.name}`); + // } + // }); + this.renderer.render(this.scene, this.camera); + + // Log the number of draw calls + // console.log('number of draw calls:', this.renderer.info.render.calls); + // console.log(this.renderer.info); } } From 0efe13a0be99a23cf3badf75dbb3150ec91ebfd7 Mon Sep 17 00:00:00 2001 From: hanbollar Date: Mon, 13 May 2024 14:18:00 -0700 Subject: [PATCH 03/12] separate page for debug Signed-off-by: hanbollar --- dist/mr.js | 2 +- samples/examples/testing.html | 71 +++++++++++++++++++++++++++++++++++ samples/index.html | 4 +- samples/testing.html | 71 +++++++++++++++++++++++++++++++++++ src/core/MRApp.js | 4 +- 5 files changed, 147 insertions(+), 5 deletions(-) create mode 100644 samples/examples/testing.html create mode 100644 samples/testing.html diff --git a/dist/mr.js b/dist/mr.js index 4c30121a..3f55993d 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// import * as SPECTOR from 'spectorjs';\n// let spector = new SPECTOR.Spector();\n// spector.displayUI();\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.getAttribute('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.getAttribute('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.getAttribute('preserve-drawing-buffer') ?? 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.getAttribute('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.getAttribute('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.getAttribute('lighting') ?? false) {\n this.lighting = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToJson(this.lighting);\n }\n this.initLights(this.lighting);\n\n /* --- Stats Setup --- */\n\n if (this.getAttribute('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.getAttribute('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.camera) {\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.scene.traverse((object) => {\n if (object.isMesh) {\n console.log(`Rendering ${object.name} with material ${object.material.name}`);\n }\n });\n // renderer.render(scene, camera);\n\n this.renderer.render(this.scene, this.camera);\n\n // Log the number of draw calls\n console.log('number of draw calls:', this.renderer.info.render.calls);\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// import * as SPECTOR from 'spectorjs';\n// let spector = new SPECTOR.Spector();\n// spector.displayUI();\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.getAttribute('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.getAttribute('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.getAttribute('preserve-drawing-buffer') ?? 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.getAttribute('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.getAttribute('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.getAttribute('lighting') ?? false) {\n this.lighting = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToJson(this.lighting);\n }\n this.initLights(this.lighting);\n\n /* --- Stats Setup --- */\n\n if (this.getAttribute('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.getAttribute('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.camera) {\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.scene.traverse((object) => {\n // if (object.isMesh) {\n // console.log(`Rendering `, object, `name: ${object.name} with num children: ${object.children.length} with material ${object.material.name}`);\n // }\n // });\n\n this.renderer.render(this.scene, this.camera);\n\n // Log the number of draw calls\n // console.log('number of draw calls:', this.renderer.info.render.calls);\n // console.log(this.renderer.info);\n }\n}\n\ncustomElements.get('mr-app') || customElements.define('mr-app', MRApp);\n\n\n//# sourceURL=webpack://mrjs/./src/core/MRApp.js?"); /***/ }), diff --git a/samples/examples/testing.html b/samples/examples/testing.html new file mode 100644 index 00000000..3a38d6ef --- /dev/null +++ b/samples/examples/testing.html @@ -0,0 +1,71 @@ + + + + + + + Volumetrics — Your Spatial developement companion + + + + + + + + + + + + + + + + + + + diff --git a/samples/index.html b/samples/index.html index 51fbb04a..2b15d4e8 100644 --- a/samples/index.html +++ b/samples/index.html @@ -34,11 +34,11 @@ - + - + + + + + + + + + + + + + + + + + diff --git a/src/core/MRApp.js b/src/core/MRApp.js index 9f612988..865b0ccd 100644 --- a/src/core/MRApp.js +++ b/src/core/MRApp.js @@ -615,8 +615,8 @@ export class MRApp extends MRElement { this.renderer.render(this.scene, this.camera); // Log the number of draw calls - // console.log('number of draw calls:', this.renderer.info.render.calls); - // console.log(this.renderer.info); + console.log('number of draw calls:', this.renderer.info.render.calls); + console.log(this.renderer.info); } } From eff1724aa3e9c61bc49277bc6b79b43e9c41a826 Mon Sep 17 00:00:00 2001 From: hanbollar Date: Mon, 13 May 2024 15:08:17 -0700 Subject: [PATCH 04/12] update Signed-off-by: hanbollar --- dist/mr.js | 2 +- samples/examples/physics.html | 20 +- samples/index.html | 290 +++++++++++++++++---- src/core/MRApp.js | 2 +- src/core/componentSystems/ControlSystem.js | 6 +- src/core/entities/MRMediaEntity.js | 5 +- src/core/entities/MRSkyBoxEntity.js | 16 +- src/core/entities/MRTextInputEntity.js | 9 +- src/core/user/MRUser.js | 2 +- src/dataManagers/MRPlaneManager.js | 3 +- src/utils/Material.js | 13 + src/utils/Model.js | 3 +- 12 files changed, 285 insertions(+), 86 deletions(-) diff --git a/dist/mr.js b/dist/mr.js index 3f55993d..d7725c76 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// import * as SPECTOR from 'spectorjs';\n// let spector = new SPECTOR.Spector();\n// spector.displayUI();\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.getAttribute('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.getAttribute('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.getAttribute('preserve-drawing-buffer') ?? 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.getAttribute('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.getAttribute('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.getAttribute('lighting') ?? false) {\n this.lighting = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToJson(this.lighting);\n }\n this.initLights(this.lighting);\n\n /* --- Stats Setup --- */\n\n if (this.getAttribute('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.getAttribute('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.camera) {\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.scene.traverse((object) => {\n // if (object.isMesh) {\n // console.log(`Rendering `, object, `name: ${object.name} with num children: ${object.children.length} with material ${object.material.name}`);\n // }\n // });\n\n this.renderer.render(this.scene, this.camera);\n\n // Log the number of draw calls\n // console.log('number of draw calls:', this.renderer.info.render.calls);\n // console.log(this.renderer.info);\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// import * as SPECTOR from 'spectorjs';\n// let spector = new SPECTOR.Spector();\n// spector.displayUI();\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.getAttribute('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.getAttribute('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.getAttribute('preserve-drawing-buffer') ?? 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.getAttribute('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.getAttribute('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.getAttribute('lighting') ?? false) {\n this.lighting = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToJson(this.lighting);\n }\n this.initLights(this.lighting);\n\n /* --- Stats Setup --- */\n\n if (this.getAttribute('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.getAttribute('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.camera) {\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.scene.traverse((object) => {\n // if (object.isMesh) {\n // console.log(`Rendering `, object, `name: ${object.name} with num children: ${object.children.length} with material ${object.material.name}`);\n // }\n // });\n\n this.renderer.render(this.scene, this.camera);\n\n // Log the number of draw calls\n console.log('number of draw calls:', this.renderer.info.render.calls);\n console.log(this.renderer.info);\n }\n}\n\ncustomElements.get('mr-app') || customElements.define('mr-app', MRApp);\n\n\n//# sourceURL=webpack://mrjs/./src/core/MRApp.js?"); /***/ }), diff --git a/samples/examples/physics.html b/samples/examples/physics.html index 7031f648..f7962325 100644 --- a/samples/examples/physics.html +++ b/samples/examples/physics.html @@ -121,17 +121,15 @@ // 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 = 0x00ff00; + hoverMaterial.transparent = true; + hoverMaterial.opacity = 0.5; + + let touchMaterial = mrjsUtils.material.MeshPhongMaterial.clone(); + touchMaterial.color = 0xff0000; + touchMaterial.transparent = true; + touchMaterial.opacity = 0.5; let hoverMesh = new THREE.Mesh(geometry, hoverMaterial) let touchMesh = new THREE.Mesh(geometry, touchMaterial) diff --git a/samples/index.html b/samples/index.html index 2b15d4e8..969e4bd8 100644 --- a/samples/index.html +++ b/samples/index.html @@ -1,71 +1,257 @@ + - + - + - Volumetrics — Your Spatial developement companion - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + An extensible library of Web Components for the spatial web. + MRjs is a mixed-reality-first, WebXR interface library meant to bootstrap + spatial web app development. It implements much of the foundational work so that developers can + spend less time on the basics and more time on their app. + See on GitHub + Join us on Discord + Documentation + + + + + + Extensible + Being open-source and built on top of web standards, you can easily + implement custom elements and contribute to the ecosystem. + + + + Familiar + Looks and feels like native HTML and CSS, but designed from the ground up + for mixed-reality. Start with 2D and ease your way into creating 3D content. + + + + All-in-one + Asset management, physics, and user interaction come built in, so you can + spend less time laying the foundation and more time building your app. + + + + + + + Examples + + + + + Anchoring + + + + Audio + + + + Camera + + + + Debugging + + + + Embed + + + + Images + + + + Models + + + + Skybox + + + + Video + + + + + + + + + diff --git a/src/core/MRApp.js b/src/core/MRApp.js index 865b0ccd..5e45775e 100644 --- a/src/core/MRApp.js +++ b/src/core/MRApp.js @@ -598,7 +598,7 @@ export class MRApp extends MRElement { this.renderer.clear(); // Need to wait until we have all needed rendering-associated systems loaded. - if (this.maskingSystem !== undefined && this.maskingSystem.scene.length > 0) { + if (this.maskingSystem !== undefined) {//} && this.maskingSystem.scene.length > 0) { this.maskingSystem.sync(); const currentShadowEnabled = this.renderer.shadowMap.enabled; this.renderer.shadowMap.enabled = false; diff --git a/src/core/componentSystems/ControlSystem.js b/src/core/componentSystems/ControlSystem.js index 8a41ad9e..8550cdbe 100644 --- a/src/core/componentSystems/ControlSystem.js +++ b/src/core/componentSystems/ControlSystem.js @@ -62,7 +62,11 @@ 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 = 0x000000; + cursorMaterial.opacity = 0.7; + cursorMaterial.transparent = true; + 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/entities/MRMediaEntity.js b/src/core/entities/MRMediaEntity.js index 08f574e4..c0eb27c5 100644 --- a/src/core/entities/MRMediaEntity.js +++ b/src/core/entities/MRMediaEntity.js @@ -211,9 +211,8 @@ 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(); + mediaMeterial.map = this.texture; _oldSubMediaMeshNotNeeded(); this.subMediaMesh.geometry = mediaGeometry; this.subMediaMesh.material = mediaMaterial; diff --git a/src/core/entities/MRSkyBoxEntity.js b/src/core/entities/MRSkyBoxEntity.js index 083a4de6..b7a9742e 100644 --- a/src/core/entities/MRSkyBoxEntity.js +++ b/src/core/entities/MRSkyBoxEntity.js @@ -34,20 +34,18 @@ 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 - }); + this.skybox.material = mrjsUtils.material.MeshStandardMaterial.clone(); + this.skybox.material.envMap = texture; + this.skybox.material.side = THREE.BackSide; // Render only on the inside } 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, - }); + this.skybox.material = mrjsUtils.material.MeshBasicMaterial.clone(); + this.skybox.material.map = texture; + this.skybox.material.side = THREE.BackSide; // Render only on the inside + this.skybox.material.opacity = 1; } } this.textureLoadedCallbacks.forEach((callback) => callback(texture)); diff --git a/src/core/entities/MRTextInputEntity.js b/src/core/entities/MRTextInputEntity.js index 29dfd095..e84bdb5f 100644 --- a/src/core/entities/MRTextInputEntity.js +++ b/src/core/entities/MRTextInputEntity.js @@ -129,10 +129,11 @@ 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 = 0x000000; + material.side = THREE.DoubleSide; + 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..36c56a9f 100644 --- a/src/core/user/MRUser.js +++ b/src/core/user/MRUser.js @@ -47,7 +47,7 @@ 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 = new THREE.Mesh(new THREE.CircleGeometry(1.3, 64), mrjsUtils.material.MeshBasicMaterial.clone()); this.spotlight.material.colorWrite = false; 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..84d900f0 100644 --- a/src/dataManagers/MRPlaneManager.js +++ b/src/dataManagers/MRPlaneManager.js @@ -120,7 +120,8 @@ 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 = 0xffffff; 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..029099f3 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 diff --git a/src/utils/Model.js b/src/utils/Model.js index aa5dd273..eb307b87 100644 --- a/src/utils/Model.js +++ b/src/utils/Model.js @@ -212,8 +212,7 @@ model.loadSTL = async function (filePath) { loader.load( filePath, (geometry) => { - const material = new THREE.MeshPhongMaterial(); - const mesh = new THREE.Mesh(geometry, material); + const mesh = new THREE.Mesh(geometry, mrjsUtils.material.MeshPhongMaterial.clone()); resolve(mesh); // Resolve the promise with the loaded mesh }, From 9d840048beb59d76b714a742904b31217c137bdc Mon Sep 17 00:00:00 2001 From: hanbollar Date: Mon, 13 May 2024 19:25:36 -0700 Subject: [PATCH 05/12] temp save Signed-off-by: hanbollar --- dist/mr.js | 26 +++---- samples/examples/physics.html | 6 +- samples/testing.html | 71 ------------------- src/core/MRApp.js | 47 +++++++++++- src/core/componentSystems/ControlSystem.js | 3 +- src/core/componentSystems/InstancingSystem.js | 3 +- src/core/componentSystems/MaskingSystem.js | 6 +- src/core/entities/MRDivEntity.js | 11 ++- src/core/entities/MRMediaEntity.js | 9 +-- src/core/entities/MRModelEntity.js | 6 +- src/core/entities/MRSkyBoxEntity.js | 18 +++-- src/core/entities/MRTextInputEntity.js | 5 +- src/core/user/MRUser.js | 6 +- src/dataManagers/MRPlaneManager.js | 5 +- src/utils/Model.js | 17 ++++- 15 files changed, 122 insertions(+), 117 deletions(-) delete mode 100644 samples/testing.html diff --git a/dist/mr.js b/dist/mr.js index d7725c76..9e780af0 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// import * as SPECTOR from 'spectorjs';\n// let spector = new SPECTOR.Spector();\n// spector.displayUI();\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.getAttribute('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.getAttribute('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.getAttribute('preserve-drawing-buffer') ?? 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.getAttribute('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.getAttribute('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.getAttribute('lighting') ?? false) {\n this.lighting = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToJson(this.lighting);\n }\n this.initLights(this.lighting);\n\n /* --- Stats Setup --- */\n\n if (this.getAttribute('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.getAttribute('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.camera) {\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.scene.traverse((object) => {\n // if (object.isMesh) {\n // console.log(`Rendering `, object, `name: ${object.name} with num children: ${object.children.length} with material ${object.material.name}`);\n // }\n // });\n\n this.renderer.render(this.scene, this.camera);\n\n // Log the number of draw calls\n console.log('number of draw calls:', this.renderer.info.render.calls);\n console.log(this.renderer.info);\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// import * as SPECTOR from 'spectorjs';\n// let spector = new SPECTOR.Spector();\n// spector.displayUI();\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.getAttribute('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.getAttribute('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.getAttribute('preserve-drawing-buffer') ?? 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.getAttribute('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.getAttribute('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.getAttribute('lighting') ?? false) {\n this.lighting = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToJson(this.lighting);\n }\n this.initLights(this.lighting);\n\n /* --- Stats Setup --- */\n\n if (this.getAttribute('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.getAttribute('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.camera) {\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.scene.traverse((object) => {\n // if (object.isMesh) {\n // console.log(`Rendering `, object, `name: ${object.name} with num children: ${object.children.length} with material ${object.material.name}`);\n // }\n // });\n\n this.renderer.render(this.scene, this.camera);\n\n // Log the number of draw calls\n console.log(this.renderer.info);\n console.log('NumDrawCalls:', this.renderer.info.render.calls, 'should be 2xNumGLPrograms(', this.renderer.info.programs.length, ') = ', 2*this.renderer.info.programs.length);\n // this.renderer.info.programs.forEach(program => {\n // console.log(`Program ID: ${program.id}, Linked Material: ${yourCustomMapping[program.id] || 'Unknown'}`);\n // });\n // console.log(this.renderer.info);\n if (this.renderer.info.programs) {\n // this.renderer.info.programs.forEach(program => {\n // console.log(`Program: `, program, `Used times in last frame: ${program.usedTimes}`);\n // });\n function printSceneObjectsAndMaterials(scene, renderer) {\n // First, traverse the scene and log details about each object.\n scene.traverse(function (object) {\n if (object.isMesh) {\n console.log(`Object: ${object.name} | Material Type: ${object.material.type}`);\n }\n });\n\n // Then, log all active WebGL programs separately.\n if (renderer.info.programs) {\n renderer.info.programs.forEach(program => {\n console.log(`Program ID: ${program.id}, Program Info:`, program);\n });\n }\n }\n\n // Call this function in your render loop or after the scene is fully loaded\n printSceneObjectsAndMaterials(this.scene, this.renderer);\n\n }\n }\n}\n\ncustomElements.get('mr-app') || customElements.define('mr-app', MRApp);\n\n\n//# sourceURL=webpack://mrjs/./src/core/MRApp.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 \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 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 = url;\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\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\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 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.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?"); /***/ }), @@ -1047,7 +1047,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 */ model: () => (/* binding */ model)\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 three_addons_loaders_ColladaLoader_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! three/addons/loaders/ColladaLoader.js */ \"./node_modules/three/examples/jsm/loaders/ColladaLoader.js\");\n/* harmony import */ var three_addons_loaders_FBXLoader_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! three/addons/loaders/FBXLoader.js */ \"./node_modules/three/examples/jsm/loaders/FBXLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_GLTFLoader_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! three/examples/jsm/loaders/GLTFLoader.js */ \"./node_modules/three/examples/jsm/loaders/GLTFLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_STLLoader_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! three/examples/jsm/loaders/STLLoader.js */ \"./node_modules/three/examples/jsm/loaders/STLLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_USDZLoader_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! three/examples/jsm/loaders/USDZLoader.js */ \"./node_modules/three/examples/jsm/loaders/USDZLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! three/examples/jsm/loaders/OBJLoader.js */ \"./node_modules/three/examples/jsm/loaders/OBJLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_MTLLoader_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! three/examples/jsm/loaders/MTLLoader.js */ \"./node_modules/three/examples/jsm/loaders/MTLLoader.js\");\n\n\n\n\n\n\n\n\n\n// Keeping the below imports in as reference for future items we can add.\n// import { AMFLoader } from 'three/addons/loaders/AMFLoader.js';\n// import { BVHLoader } from 'three/addons/loaders/BVHLoader.js';\n// import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';\n// import { GCodeLoader } from 'three/addons/loaders/GCodeLoader.js';\n// import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';\n// // import { IFCLoader } from 'web-ifc-three';\n// // import { IFCSPACE } from 'web-ifc';\n// import { Rhino3dmLoader } from 'three/addons/loaders/3DMLoader.js';\n// import { PCDLoader } from 'three/addons/loaders/PCDLoader.js';\n// import { PDBLoader } from 'three/addons/loaders/PDBLoader.js';\n// import { PLYLoader } from 'three/addons/loaders/PLYLoader.js';\n// import { SVGLoader } from 'three/addons/loaders/SVGLoader.js';\n// import { TDSLoader } from 'three/addons/loaders/TDSLoader.js';\n// import { ThreeMFLoader } from 'three/addons/loaders/3MFLoader.js';\n\n/**\n * @namespace model\n * @description Useful namespace for helping with Model utility functions\n */\nlet model = {};\n\n/**\n * @function\n * @memberof model\n * @description Loads Collada file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadDAE = async function (filePath) {\n const loader = new three_addons_loaders_ColladaLoader_js__WEBPACK_IMPORTED_MODULE_0__.ColladaLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (dae) => {\n resolve(dae.scene);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads OBJ file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadOBJ = async function (filePath) {\n const loader = new three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_1__.OBJLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (obj) => {\n resolve(obj);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads OBJ file with externally hosted MTL file\n * @param {string} filePath - The path of the form '/path/to/mtlFile.mtl,/path/to/objFile.obj'.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadOBJWithMTL = function (filePath) {\n let paths = filePath.split(',');\n // Assigning each path to a variable\n if (paths.length != 2) {\n console.error('Expected the loading of an MTL file and an OBJ file like \"path/to/mtlFile.mtl,path/to/the/objFile.obj\" - got:', filePath);\n return Promise.reject(new Error('Invalid path format for OBJ and MTL files.'));\n }\n\n const filePathMTL = paths[0];\n const filePathOBJ = paths[1];\n\n const loadMTL = (url) =>\n new Promise((resolve, reject) => {\n const mtlLoader = new three_examples_jsm_loaders_MTLLoader_js__WEBPACK_IMPORTED_MODULE_2__.MTLLoader();\n mtlLoader.load(\n url,\n (materials) => {\n materials.preload();\n resolve(materials);\n },\n undefined,\n (error) => {\n console.error('Failed to load MTL from URL:', error);\n reject(error);\n }\n );\n });\n\n const loadOBJ = (filePath, materials) =>\n new Promise((resolve, reject) => {\n const objLoader = new three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_1__.OBJLoader();\n objLoader.setMaterials(materials);\n objLoader.load(\n filePath,\n (obj) => {\n resolve(obj);\n },\n undefined,\n (error) => {\n console.error('Failed to load OBJ:', error);\n reject(error);\n }\n );\n });\n\n return loadMTL(filePathMTL)\n .then((materials) => loadOBJ(filePathOBJ, materials))\n .catch((error) => {\n console.error('An error occurred while loading OBJ with external MTL:', error);\n throw error; // Ensure errors are propagated\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads FBX file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadFBX = async function (filePath) {\n const loader = new three_addons_loaders_FBXLoader_js__WEBPACK_IMPORTED_MODULE_3__.FBXLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (fbx) => {\n resolve(fbx);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads GLTF/GLB file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadGLTF = async function (filePath) {\n const loader = new three_examples_jsm_loaders_GLTFLoader_js__WEBPACK_IMPORTED_MODULE_4__.GLTFLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (gltf) => {\n const scene = gltf.scene;\n const animations = gltf.animations;\n\n // Resolve the promise with the loaded scene and animations\n resolve({ scene, animations });\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads stl file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadSTL = async function (filePath) {\n const loader = new three_examples_jsm_loaders_STLLoader_js__WEBPACK_IMPORTED_MODULE_5__.STLLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (geometry) => {\n const material = new three__WEBPACK_IMPORTED_MODULE_6__.MeshPhongMaterial();\n const mesh = new three__WEBPACK_IMPORTED_MODULE_6__.Mesh(geometry, material);\n\n resolve(mesh); // Resolve the promise with the loaded mesh\n },\n (xhr) => {\n // Progress callback\n },\n (error) => {\n console.error(error);\n reject(error); // Reject the promise if there's an error\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads USD/USDZ file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadUSDZ = async function (filePath) {\n const usdzLoader = new three_examples_jsm_loaders_USDZLoader_js__WEBPACK_IMPORTED_MODULE_7__.USDZLoader();\n\n const [model] = await Promise.all([usdzLoader.loadAsync(filePath)], undefined, (error) => {\n console.error(error);\n return null;\n });\n\n return model;\n};\n\n/// ////////////////////////\n// Main Loading Function //\n/// ////////////////////////\n\n/**\n * @function\n * @memberof model\n * @description The main loading function\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @param {string} extension - The extension of the file type. Current allowed extensions are `dae`, fbx`, `glb`, `obj`, and `stl`.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadModel = async function (filePath, extension) {\n // Flag used for debugging the ones that are only 'partially implemented' and\n // still as todos.\n const allowed = false;\n\n if (extension == 'fbx') {\n return model.loadFBX(filePath);\n } else if (extension == 'glb') {\n return model.loadGLTF(filePath);\n } else if (allowed && extension == 'gltf') {\n // TODO\n return model.loadGLTF(filePath);\n } else if (extension == 'stl') {\n return model.loadSTL(filePath);\n } else if (extension == 'obj') {\n if (filePath.includes(',')) {\n // has a preceeding material file\n return model.loadOBJWithMTL(filePath);\n } else {\n return model.loadOBJ(filePath);\n }\n } else if (extension == 'dae') {\n return model.loadDAE(filePath);\n } else if (allowed && (extension == 'usdc' || extension == 'usdz')) {\n // TODO\n return model.loadUSDZ(filePath);\n }\n console.error(`ERR: the extensions ${extension} is not supported by MR.js`);\n return null;\n};\n\nmodel.disposeObject3D = function (parentObject3D) {\n parentObject3D.traverse(function (node) {\n if (node.isMesh) {\n if (node.geometry) {\n node.geometry.dispose();\n }\n\n if (node.material) {\n if (node.material instanceof Array) {\n // An array of materials\n node.material.forEach((material) => material.dispose());\n } else {\n // A single material\n node.material.dispose();\n }\n }\n }\n });\n};\n\nmodel.removeObject3DFromScene = function (object3D, scene) {\n model.disposeObject3D(object3D);\n scene.remove(object3D);\n\n // Optional: Clean up references for GC if necessary\n};\n\nmodel.currentRunningAnimationClip = function (entity) {\n if (!entity.mixer) {\n console.log('No mixer found for :', entity);\n return;\n }\n // If no animation is currently playing\n if (!entity.mixer._actions.some((action) => action.isRunning())) {\n console.log('No animation is currently playing');\n return;\n }\n\n // Iterate over all clip actions in the mixer\n for (let i = 0; i < entity.mixer._actions.length; i++) {\n let clipAction = entity._actions[i];\n if (clipAction.isRunning()) {\n let clipName = clipAction.getClip().name;\n console.log(\"Animation '\" + clipName + \"' is currently playing\");\n // You can do whatever you need with this information\n // break; // Break the loop if you only want to know the first running animation\n }\n }\n};\n\n\n\n\n//# sourceURL=webpack://mrjs/./src/utils/Model.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ model: () => (/* binding */ model)\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 three_addons_loaders_ColladaLoader_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! three/addons/loaders/ColladaLoader.js */ \"./node_modules/three/examples/jsm/loaders/ColladaLoader.js\");\n/* harmony import */ var three_addons_loaders_FBXLoader_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! three/addons/loaders/FBXLoader.js */ \"./node_modules/three/examples/jsm/loaders/FBXLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_GLTFLoader_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! three/examples/jsm/loaders/GLTFLoader.js */ \"./node_modules/three/examples/jsm/loaders/GLTFLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_STLLoader_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! three/examples/jsm/loaders/STLLoader.js */ \"./node_modules/three/examples/jsm/loaders/STLLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_USDZLoader_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! three/examples/jsm/loaders/USDZLoader.js */ \"./node_modules/three/examples/jsm/loaders/USDZLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! three/examples/jsm/loaders/OBJLoader.js */ \"./node_modules/three/examples/jsm/loaders/OBJLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_MTLLoader_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! three/examples/jsm/loaders/MTLLoader.js */ \"./node_modules/three/examples/jsm/loaders/MTLLoader.js\");\n\n\n\n\n\n\n\n\n\n// Keeping the below imports in as reference for future items we can add.\n// import { AMFLoader } from 'three/addons/loaders/AMFLoader.js';\n// import { BVHLoader } from 'three/addons/loaders/BVHLoader.js';\n// import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';\n// import { GCodeLoader } from 'three/addons/loaders/GCodeLoader.js';\n// import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';\n// // import { IFCLoader } from 'web-ifc-three';\n// // import { IFCSPACE } from 'web-ifc';\n// import { Rhino3dmLoader } from 'three/addons/loaders/3DMLoader.js';\n// import { PCDLoader } from 'three/addons/loaders/PCDLoader.js';\n// import { PDBLoader } from 'three/addons/loaders/PDBLoader.js';\n// import { PLYLoader } from 'three/addons/loaders/PLYLoader.js';\n// import { SVGLoader } from 'three/addons/loaders/SVGLoader.js';\n// import { TDSLoader } from 'three/addons/loaders/TDSLoader.js';\n// import { ThreeMFLoader } from 'three/addons/loaders/3MFLoader.js';\n\n/**\n * @namespace model\n * @description Useful namespace for helping with Model utility functions\n */\nlet model = {};\n\n/**\n * @function\n * @memberof model\n * @description Loads Collada file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadDAE = async function (filePath) {\n const loader = new three_addons_loaders_ColladaLoader_js__WEBPACK_IMPORTED_MODULE_0__.ColladaLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (dae) => {\n resolve(dae.scene);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads OBJ file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadOBJ = async function (filePath) {\n const loader = new three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_1__.OBJLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (obj) => {\n resolve(obj);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads OBJ file with externally hosted MTL file\n * @param {string} filePath - The path of the form '/path/to/mtlFile.mtl,/path/to/objFile.obj'.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadOBJWithMTL = function (filePath) {\n let paths = filePath.split(',');\n // Assigning each path to a variable\n if (paths.length != 2) {\n console.error('Expected the loading of an MTL file and an OBJ file like \"path/to/mtlFile.mtl,path/to/the/objFile.obj\" - got:', filePath);\n return Promise.reject(new Error('Invalid path format for OBJ and MTL files.'));\n }\n\n const filePathMTL = paths[0];\n const filePathOBJ = paths[1];\n\n console.log('in load obj with mtl');\n\n const loadMTL = (url) =>\n new Promise((resolve, reject) => {\n const mtlLoader = new three_examples_jsm_loaders_MTLLoader_js__WEBPACK_IMPORTED_MODULE_2__.MTLLoader();\n mtlLoader.load(\n url,\n (materials) => {\n materials.preload();\n console.log(materials);\n resolve(materials);\n },\n undefined,\n (error) => {\n console.error('Failed to load MTL from URL:', error);\n reject(error);\n }\n );\n });\n\n const loadOBJ = (filePath, materials) =>\n new Promise((resolve, reject) => {\n const objLoader = new three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_1__.OBJLoader();\n objLoader.setMaterials(materials);\n objLoader.load(\n filePath,\n (obj) => {\n resolve(obj);\n },\n undefined,\n (error) => {\n console.error('Failed to load OBJ:', error);\n reject(error);\n }\n );\n });\n\n return loadMTL(filePathMTL)\n .then((materials) => loadOBJ(filePathOBJ, materials))\n .catch((error) => {\n console.error('An error occurred while loading OBJ with external MTL:', error);\n throw error; // Ensure errors are propagated\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads FBX file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadFBX = async function (filePath) {\n const loader = new three_addons_loaders_FBXLoader_js__WEBPACK_IMPORTED_MODULE_3__.FBXLoader();\n\n console.log('in load FBX');\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (fbx) => {\n console.log(fbx);\n resolve(fbx);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads GLTF/GLB file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadGLTF = async function (filePath) {\n const loader = new three_examples_jsm_loaders_GLTFLoader_js__WEBPACK_IMPORTED_MODULE_4__.GLTFLoader();\n\n console.log('in load gltf/glb');\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (gltf) => {\n const scene = gltf.scene;\n const animations = gltf.animations;\n\n console.log(scene);\n\n // Resolve the promise with the loaded scene and animations\n resolve({ scene, animations });\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads stl file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadSTL = async function (filePath) {\n const loader = new three_examples_jsm_loaders_STLLoader_js__WEBPACK_IMPORTED_MODULE_5__.STLLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (geometry) => {\n const material = mrjsUtils.material.MeshPhongMaterial.clone();\n material.programName = \"stlMaterial\";\n const mesh = new three__WEBPACK_IMPORTED_MODULE_6__.Mesh(geometry, material);\n\n resolve(mesh); // Resolve the promise with the loaded mesh\n },\n (xhr) => {\n // Progress callback\n },\n (error) => {\n console.error(error);\n reject(error); // Reject the promise if there's an error\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads USD/USDZ file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadUSDZ = async function (filePath) {\n const usdzLoader = new three_examples_jsm_loaders_USDZLoader_js__WEBPACK_IMPORTED_MODULE_7__.USDZLoader();\n\n console.log('in load usdz');\n\n const [model] = await Promise.all([usdzLoader.loadAsync(filePath)], undefined, (error) => {\n console.error(error);\n return null;\n });\n\n console.log(model);\n return model;\n};\n\n/// ////////////////////////\n// Main Loading Function //\n/// ////////////////////////\n\n/**\n * @function\n * @memberof model\n * @description The main loading function\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @param {string} extension - The extension of the file type. Current allowed extensions are `dae`, fbx`, `glb`, `obj`, and `stl`.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadModel = async function (filePath, extension) {\n // Flag used for debugging the ones that are only 'partially implemented' and\n // still as todos.\n const allowed = false;\n\n if (extension == 'fbx') {\n return model.loadFBX(filePath);\n } else if (extension == 'glb') {\n return model.loadGLTF(filePath);\n } else if (allowed && extension == 'gltf') {\n // TODO\n return model.loadGLTF(filePath);\n } else if (extension == 'stl') {\n return model.loadSTL(filePath);\n } else if (extension == 'obj') {\n if (filePath.includes(',')) {\n // has a preceeding material file\n return model.loadOBJWithMTL(filePath);\n } else {\n return model.loadOBJ(filePath);\n }\n } else if (extension == 'dae') {\n return model.loadDAE(filePath);\n } else if (allowed && (extension == 'usdc' || extension == 'usdz')) {\n // TODO\n return model.loadUSDZ(filePath);\n }\n console.error(`ERR: the extensions ${extension} is not supported by MR.js`);\n return null;\n};\n\nmodel.disposeObject3D = function (parentObject3D) {\n parentObject3D.traverse(function (node) {\n if (node.isMesh) {\n if (node.geometry) {\n node.geometry.dispose();\n }\n\n if (node.material) {\n if (node.material instanceof Array) {\n // An array of materials\n node.material.forEach((material) => material.dispose());\n } else {\n // A single material\n node.material.dispose();\n }\n }\n }\n });\n};\n\nmodel.removeObject3DFromScene = function (object3D, scene) {\n model.disposeObject3D(object3D);\n scene.remove(object3D);\n\n // Optional: Clean up references for GC if necessary\n};\n\nmodel.currentRunningAnimationClip = function (entity) {\n if (!entity.mixer) {\n console.log('No mixer found for :', entity);\n return;\n }\n // If no animation is currently playing\n if (!entity.mixer._actions.some((action) => action.isRunning())) {\n console.log('No animation is currently playing');\n return;\n }\n\n // Iterate over all clip actions in the mixer\n for (let i = 0; i < entity.mixer._actions.length; i++) {\n let clipAction = entity._actions[i];\n if (clipAction.isRunning()) {\n let clipName = clipAction.getClip().name;\n console.log(\"Animation '\" + clipName + \"' is currently playing\");\n // You can do whatever you need with this information\n // break; // Break the loop if you only want to know the first running animation\n }\n }\n};\n\n\n\n\n//# sourceURL=webpack://mrjs/./src/utils/Model.js?"); /***/ }), diff --git a/samples/examples/physics.html b/samples/examples/physics.html index f7962325..5a4a1dcc 100644 --- a/samples/examples/physics.html +++ b/samples/examples/physics.html @@ -122,14 +122,16 @@ let geometry = new THREE.BoxGeometry(...tempSize) let hoverMaterial = mrjsUtils.material.MeshPhongMaterial.clone(); - hoverMaterial.color = 0x00ff00; + hoverMaterial.color.set(0x00ff00); hoverMaterial.transparent = true; hoverMaterial.opacity = 0.5; + hoverMaterial.name = "hoverMaterial"; let touchMaterial = mrjsUtils.material.MeshPhongMaterial.clone(); - touchMaterial.color = 0xff0000; + 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/samples/testing.html b/samples/testing.html deleted file mode 100644 index 2b15d4e8..00000000 --- a/samples/testing.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - Volumetrics — Your Spatial developement companion - - - - - - - - - - - - - - - - - - - diff --git a/src/core/MRApp.js b/src/core/MRApp.js index 5e45775e..c2a99386 100644 --- a/src/core/MRApp.js +++ b/src/core/MRApp.js @@ -615,8 +615,53 @@ export class MRApp extends MRElement { this.renderer.render(this.scene, this.camera); // Log the number of draw calls - console.log('number of draw calls:', this.renderer.info.render.calls); console.log(this.renderer.info); + console.log('NumDrawCalls:', this.renderer.info.render.calls, 'should be 2xNumGLPrograms(', this.renderer.info.programs.length, ') = ', 2*this.renderer.info.programs.length); + // this.renderer.info.programs.forEach(program => { + // console.log(`Program ID: ${program.id}, Linked Material: ${yourCustomMapping[program.id] || 'Unknown'}`); + // }); + // console.log(this.renderer.info); + if (this.renderer.info.programs) { + // this.renderer.info.programs.forEach(program => { + // console.log(`Program: `, program, `Used times in last frame: ${program.usedTimes}`); + // }); + function printSceneObjectsAndMaterials(scene, renderer) { + let groupedByMaterial = {}; + + // Traverse the scene and group objects by material UUID + scene.traverse(function (object) { + if (object.isMesh && object.material) { + const uuid = object.material.uuid; + if (!groupedByMaterial[uuid]) { + groupedByMaterial[uuid] = []; // Initialize array if it doesn't exist + } + groupedByMaterial[uuid].push({ + objectName: object.name, + objectType: object.type + }); + } + }); + + // Log details about each group + Object.keys(groupedByMaterial).forEach(uuid => { + console.log(`Material UUID: ${uuid}, num items: ${groupedByMaterial[uuid].length}`); + groupedByMaterial[uuid].forEach(entry => { + console.log(`Object: ${entry.objectName} | Type: ${entry.objectType}`); + }); + }); + + // Then, log all active WebGL programs separately. + if (renderer.info.programs) { + renderer.info.programs.forEach(program => { + console.log(`Program ID: ${program.id}, Program Info:`, program); + }); + } + } + + // Call this function where appropriate in your application + printSceneObjectsAndMaterials(this.scene, this.renderer); + + } } } diff --git a/src/core/componentSystems/ControlSystem.js b/src/core/componentSystems/ControlSystem.js index 8550cdbe..f48815b8 100644 --- a/src/core/componentSystems/ControlSystem.js +++ b/src/core/componentSystems/ControlSystem.js @@ -63,9 +63,10 @@ export class ControlSystem extends MRSystem { this.currentEntity = null; const cursorMaterial = mrjsUtils.material.MeshBasicMaterial.clone(); - cursorMaterial.color = 0x000000; + 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); 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..2e512b72 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 c0eb27c5..3e775388 100644 --- a/src/core/entities/MRMediaEntity.js +++ b/src/core/entities/MRMediaEntity.js @@ -20,9 +20,9 @@ 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; @@ -212,7 +212,8 @@ export class MRMediaEntity extends MRDivEntity { const mediaGeometry = new THREE.PlaneGeometry(mediaWidth, mediaHeight); const mediaMaterial = mrjsUtils.material.MeshStandardMaterial.clone(); - mediaMeterial.map = this.texture; + 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..50e31bca 100644 --- a/src/core/entities/MRModelEntity.js +++ b/src/core/entities/MRModelEntity.js @@ -121,11 +121,8 @@ export class MRModelEntity extends MRDivEntity { this.animations = animations; } - this.object3D.add(this.modelObj); - this.modelObj.receiveShadow = true; this.modelObj.renderOrder = 3; - this.traverseObjects((object) => { if (object.isMesh) { object.renderOrder = 3; @@ -134,6 +131,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 b7a9742e..c911c101 100644 --- a/src/core/entities/MRSkyBoxEntity.js +++ b/src/core/entities/MRSkyBoxEntity.js @@ -34,18 +34,22 @@ export class MRSkyBoxEntity extends MREntity { if (this.skybox.material !== undefined) { this.skybox.material.dispose(); } - this.skybox.material = mrjsUtils.material.MeshStandardMaterial.clone(); - this.skybox.material.envMap = texture; - this.skybox.material.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 = mrjsUtils.material.MeshBasicMaterial.clone(); - this.skybox.material.map = texture; - this.skybox.material.side = THREE.BackSide; // Render only on the inside - this.skybox.material.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 e84bdb5f..b8f9be0f 100644 --- a/src/core/entities/MRTextInputEntity.js +++ b/src/core/entities/MRTextInputEntity.js @@ -129,10 +129,11 @@ 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 = mrjsUtils.material.MeshBasicMaterial.clone(); - material.color = 0x000000; + material.color.set(0x000000); material.side = THREE.DoubleSide; + material.programName = "text:cursorMaterial"; this.cursor.material = material; } diff --git a/src/core/user/MRUser.js b/src/core/user/MRUser.js index 36c56a9f..73cf4777 100644 --- a/src/core/user/MRUser.js +++ b/src/core/user/MRUser.js @@ -47,8 +47,10 @@ export default class MRUser { * @returns {object} spotlight - the spotlight to be used. */ initSpotlight() { - this.spotlight = new THREE.Mesh(new THREE.CircleGeometry(1.3, 64), mrjsUtils.material.MeshBasicMaterial.clone()); - 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 84d900f0..e297981b 100644 --- a/src/dataManagers/MRPlaneManager.js +++ b/src/dataManagers/MRPlaneManager.js @@ -121,12 +121,13 @@ export class MRPlaneManager { const geometry = new THREE.BoxGeometry(width, 0.01, height); const material = mrjsUtils.material.MeshBasicMaterial.clone(); - material.color = 0xffffff; + material.color.set(0xffffff); + material.colorWrite = false; + material.programName = "planeMeshMaterial"; mrPlane.mesh = new THREE.Mesh(geometry, material); mrPlane.mesh.position.setFromMatrixPosition(this.matrix); mrPlane.mesh.quaternion.setFromRotationMatrix(this.matrix); - mrPlane.mesh.material.colorWrite = false; mrPlane.mesh.renderOrder = 2; this.scene.add(mrPlane.mesh); diff --git a/src/utils/Model.js b/src/utils/Model.js index eb307b87..bdf18d6e 100644 --- a/src/utils/Model.js +++ b/src/utils/Model.js @@ -99,6 +99,8 @@ model.loadOBJWithMTL = function (filePath) { const filePathMTL = paths[0]; const filePathOBJ = paths[1]; + console.log('in load obj with mtl'); + const loadMTL = (url) => new Promise((resolve, reject) => { const mtlLoader = new MTLLoader(); @@ -106,6 +108,7 @@ model.loadOBJWithMTL = function (filePath) { url, (materials) => { materials.preload(); + console.log(materials); resolve(materials); }, undefined, @@ -152,10 +155,13 @@ model.loadOBJWithMTL = function (filePath) { model.loadFBX = async function (filePath) { const loader = new FBXLoader(); + console.log('in load FBX'); + return new Promise((resolve, reject) => { loader.load( filePath, (fbx) => { + console.log(fbx); resolve(fbx); }, undefined, @@ -178,6 +184,8 @@ model.loadFBX = async function (filePath) { model.loadGLTF = async function (filePath) { const loader = new GLTFLoader(); + console.log('in load gltf/glb'); + return new Promise((resolve, reject) => { loader.load( filePath, @@ -185,6 +193,8 @@ model.loadGLTF = async function (filePath) { const scene = gltf.scene; const animations = gltf.animations; + console.log(scene); + // Resolve the promise with the loaded scene and animations resolve({ scene, animations }); }, @@ -212,7 +222,9 @@ model.loadSTL = async function (filePath) { loader.load( filePath, (geometry) => { - const mesh = new THREE.Mesh(geometry, mrjsUtils.material.MeshPhongMaterial.clone()); + const material = mrjsUtils.material.MeshPhongMaterial.clone(); + material.programName = "stlMaterial"; + const mesh = new THREE.Mesh(geometry, material); resolve(mesh); // Resolve the promise with the loaded mesh }, @@ -238,11 +250,14 @@ model.loadSTL = async function (filePath) { model.loadUSDZ = async function (filePath) { const usdzLoader = new USDZLoader(); + console.log('in load usdz'); + const [model] = await Promise.all([usdzLoader.loadAsync(filePath)], undefined, (error) => { console.error(error); return null; }); + console.log(model); return model; }; From 871fdb18a3e8e1ef452ad36e492f8194fdd3bc04 Mon Sep 17 00:00:00 2001 From: hanbollar Date: Thu, 16 May 2024 16:59:54 -0700 Subject: [PATCH 06/12] temp save - separate materials registry Signed-off-by: hanbollar --- dist/mr.js | 4 +- src/core/MRApp.js | 62 ++++++------- src/utils/Model.js | 214 +++++++++++++++++++++++---------------------- 3 files changed, 141 insertions(+), 139 deletions(-) diff --git a/dist/mr.js b/dist/mr.js index 08abfb5f..83078437 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// import * as SPECTOR from 'spectorjs';\n// let spector = new SPECTOR.Spector();\n// spector.displayUI();\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) {//} && 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.scene.traverse((object) => {\n // if (object.isMesh) {\n // console.log(`Rendering `, object, `name: ${object.name} with num children: ${object.children.length} with material ${object.material.name}`);\n // }\n // });\n\n this.renderer.render(this.scene, this.camera);\n\n // Log the number of draw calls\n console.log(this.renderer.info);\n console.log('NumDrawCalls:', this.renderer.info.render.calls, 'should be 2xNumGLPrograms(', this.renderer.info.programs.length, ') = ', 2*this.renderer.info.programs.length);\n // this.renderer.info.programs.forEach(program => {\n // console.log(`Program ID: ${program.id}, Linked Material: ${yourCustomMapping[program.id] || 'Unknown'}`);\n // });\n // console.log(this.renderer.info);\n if (this.renderer.info.programs) {\n // this.renderer.info.programs.forEach(program => {\n // console.log(`Program: `, program, `Used times in last frame: ${program.usedTimes}`);\n // });\n function printSceneObjectsAndMaterials(scene, renderer) {\n let groupedByMaterial = {};\n\n // Traverse the scene and group objects by material UUID\n scene.traverse(function (object) {\n if (object.isMesh && object.material) {\n const uuid = object.material.uuid;\n if (!groupedByMaterial[uuid]) {\n groupedByMaterial[uuid] = []; // Initialize array if it doesn't exist\n }\n groupedByMaterial[uuid].push({\n objectName: object.name,\n objectType: object.type\n });\n }\n });\n\n // Log details about each group\n Object.keys(groupedByMaterial).forEach(uuid => {\n console.log(`Material UUID: ${uuid}, num items: ${groupedByMaterial[uuid].length}`);\n groupedByMaterial[uuid].forEach(entry => {\n console.log(`Object: ${entry.objectName} | Type: ${entry.objectType}`);\n });\n });\n\n // Then, log all active WebGL programs separately.\n if (renderer.info.programs) {\n renderer.info.programs.forEach(program => {\n console.log(`Program ID: ${program.id}, Program Info:`, program);\n });\n }\n }\n\n // Call this function where appropriate in your application\n printSceneObjectsAndMaterials(this.scene, this.renderer);\n\n }\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// import * as SPECTOR from 'spectorjs';\n// let spector = new SPECTOR.Spector();\n// spector.displayUI();\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) {//} && 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.scene.traverse((object) => {\n // if (object.isMesh) {\n // console.log(`Rendering `, object, `name: ${object.name} with num children: ${object.children.length} with material ${object.material.name}`);\n // }\n // });\n\n this.renderer.render(this.scene, this.camera);\n\n // Log the number of draw calls\n console.log(this.renderer.info);\n console.log('NumDrawCalls:', this.renderer.info.render.calls, 'should be 2xNumGLPrograms(', this.renderer.info.programs.length, ') = ', 2*this.renderer.info.programs.length);\n // this.renderer.info.programs.forEach(program => {\n // console.log(`Program ID: ${program.id}, Linked Material: ${yourCustomMapping[program.id] || 'Unknown'}`);\n // });\n // console.log(this.renderer.info);\n if (this.renderer.info.programs) {\n // this.renderer.info.programs.forEach(program => {\n // console.log(`Program: `, program, `Used times in last frame: ${program.usedTimes}`);\n // });\n // function printSceneObjectsAndMaterials(scene, renderer) {\n // let groupedByMaterial = {};\n\n // // Traverse the scene and group objects by material UUID\n // scene.traverse(function (object) {\n // if (object.isMesh && object.material) {\n // const uuid = object.material.uuid;\n // if (!groupedByMaterial[uuid]) {\n // groupedByMaterial[uuid] = []; // Initialize array if it doesn't exist\n // }\n // groupedByMaterial[uuid].push({\n // objectName: object.name,\n // objectType: object.type\n // });\n // }\n // });\n\n // Log details about each group\n // Object.keys(groupedByMaterial).forEach(uuid => {\n // console.log(`Material UUID: ${uuid}, num items: ${groupedByMaterial[uuid].length}`);\n // groupedByMaterial[uuid].forEach(entry => {\n // console.log(`Object: ${entry.objectName} | Type: ${entry.objectType}`);\n // });\n // });\n\n // // Then, log all active WebGL programs separately.\n // if (renderer.info.programs) {\n // renderer.info.programs.forEach(program => {\n // console.log(`Program ID: ${program.id}, Program Info:`, program);\n // });\n // }\n // }\n\n // Call this function where appropriate in your application\n // printSceneObjectsAndMaterials(this.scene, this.renderer);\n\n }\n }\n}\n\ncustomElements.get('mr-app') || customElements.define('mr-app', MRApp);\n\n\n//# sourceURL=webpack://mrjs/./src/core/MRApp.js?"); /***/ }), @@ -1047,7 +1047,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 */ model: () => (/* binding */ model)\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 three_addons_loaders_ColladaLoader_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! three/addons/loaders/ColladaLoader.js */ \"./node_modules/three/examples/jsm/loaders/ColladaLoader.js\");\n/* harmony import */ var three_addons_loaders_FBXLoader_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! three/addons/loaders/FBXLoader.js */ \"./node_modules/three/examples/jsm/loaders/FBXLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_GLTFLoader_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! three/examples/jsm/loaders/GLTFLoader.js */ \"./node_modules/three/examples/jsm/loaders/GLTFLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_STLLoader_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! three/examples/jsm/loaders/STLLoader.js */ \"./node_modules/three/examples/jsm/loaders/STLLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_USDZLoader_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! three/examples/jsm/loaders/USDZLoader.js */ \"./node_modules/three/examples/jsm/loaders/USDZLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! three/examples/jsm/loaders/OBJLoader.js */ \"./node_modules/three/examples/jsm/loaders/OBJLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_MTLLoader_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! three/examples/jsm/loaders/MTLLoader.js */ \"./node_modules/three/examples/jsm/loaders/MTLLoader.js\");\n\n\n\n\n\n\n\n\n\n// Keeping the below imports in as reference for future items we can add.\n// import { AMFLoader } from 'three/addons/loaders/AMFLoader.js';\n// import { BVHLoader } from 'three/addons/loaders/BVHLoader.js';\n// import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';\n// import { GCodeLoader } from 'three/addons/loaders/GCodeLoader.js';\n// import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';\n// // import { IFCLoader } from 'web-ifc-three';\n// // import { IFCSPACE } from 'web-ifc';\n// import { Rhino3dmLoader } from 'three/addons/loaders/3DMLoader.js';\n// import { PCDLoader } from 'three/addons/loaders/PCDLoader.js';\n// import { PDBLoader } from 'three/addons/loaders/PDBLoader.js';\n// import { PLYLoader } from 'three/addons/loaders/PLYLoader.js';\n// import { SVGLoader } from 'three/addons/loaders/SVGLoader.js';\n// import { TDSLoader } from 'three/addons/loaders/TDSLoader.js';\n// import { ThreeMFLoader } from 'three/addons/loaders/3MFLoader.js';\n\n/**\n * @namespace model\n * @description Useful namespace for helping with Model utility functions\n */\nlet model = {};\n\n/**\n * @function\n * @memberof model\n * @description Loads Collada file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadDAE = async function (filePath) {\n const loader = new three_addons_loaders_ColladaLoader_js__WEBPACK_IMPORTED_MODULE_0__.ColladaLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (dae) => {\n resolve(dae.scene);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads OBJ file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadOBJ = async function (filePath) {\n const loader = new three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_1__.OBJLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (obj) => {\n resolve(obj);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads OBJ file with externally hosted MTL file\n * @param {string} filePath - The path of the form '/path/to/mtlFile.mtl,/path/to/objFile.obj'.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadOBJWithMTL = function (filePath) {\n let paths = filePath.split(',');\n // Assigning each path to a variable\n if (paths.length != 2) {\n console.error('Expected the loading of an MTL file and an OBJ file like \"path/to/mtlFile.mtl,path/to/the/objFile.obj\" - got:', filePath);\n return Promise.reject(new Error('Invalid path format for OBJ and MTL files.'));\n }\n\n const filePathMTL = paths[0];\n const filePathOBJ = paths[1];\n\n console.log('in load obj with mtl');\n\n const loadMTL = (url) =>\n new Promise((resolve, reject) => {\n const mtlLoader = new three_examples_jsm_loaders_MTLLoader_js__WEBPACK_IMPORTED_MODULE_2__.MTLLoader();\n mtlLoader.load(\n url,\n (materials) => {\n materials.preload();\n console.log(materials);\n resolve(materials);\n },\n undefined,\n (error) => {\n console.error('Failed to load MTL from URL:', error);\n reject(error);\n }\n );\n });\n\n const loadOBJ = (filePath, materials) =>\n new Promise((resolve, reject) => {\n const objLoader = new three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_1__.OBJLoader();\n objLoader.setMaterials(materials);\n objLoader.load(\n filePath,\n (obj) => {\n resolve(obj);\n },\n undefined,\n (error) => {\n console.error('Failed to load OBJ:', error);\n reject(error);\n }\n );\n });\n\n return loadMTL(filePathMTL)\n .then((materials) => loadOBJ(filePathOBJ, materials))\n .catch((error) => {\n console.error('An error occurred while loading OBJ with external MTL:', error);\n throw error; // Ensure errors are propagated\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads FBX file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadFBX = async function (filePath) {\n const loader = new three_addons_loaders_FBXLoader_js__WEBPACK_IMPORTED_MODULE_3__.FBXLoader();\n\n console.log('in load FBX');\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (fbx) => {\n console.log(fbx);\n resolve(fbx);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads GLTF/GLB file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadGLTF = async function (filePath) {\n const loader = new three_examples_jsm_loaders_GLTFLoader_js__WEBPACK_IMPORTED_MODULE_4__.GLTFLoader();\n\n console.log('in load gltf/glb');\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (gltf) => {\n const scene = gltf.scene;\n const animations = gltf.animations;\n\n console.log(scene);\n\n // Resolve the promise with the loaded scene and animations\n resolve({ scene, animations });\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads stl file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadSTL = async function (filePath) {\n const loader = new three_examples_jsm_loaders_STLLoader_js__WEBPACK_IMPORTED_MODULE_5__.STLLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (geometry) => {\n const material = mrjsUtils.material.MeshPhongMaterial.clone();\n material.programName = \"stlMaterial\";\n const mesh = new three__WEBPACK_IMPORTED_MODULE_6__.Mesh(geometry, material);\n\n resolve(mesh); // Resolve the promise with the loaded mesh\n },\n (xhr) => {\n // Progress callback\n },\n (error) => {\n console.error(error);\n reject(error); // Reject the promise if there's an error\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads USD/USDZ file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadUSDZ = async function (filePath) {\n const usdzLoader = new three_examples_jsm_loaders_USDZLoader_js__WEBPACK_IMPORTED_MODULE_7__.USDZLoader();\n\n console.log('in load usdz');\n\n const [model] = await Promise.all([usdzLoader.loadAsync(filePath)], undefined, (error) => {\n console.error(error);\n return null;\n });\n\n console.log(model);\n return model;\n};\n\n/// ////////////////////////\n// Main Loading Function //\n/// ////////////////////////\n\n/**\n * @function\n * @memberof model\n * @description The main loading function\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @param {string} extension - The extension of the file type. Current allowed extensions are `dae`, fbx`, `glb`, `obj`, and `stl`.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadModel = async function (filePath, extension) {\n // Flag used for debugging the ones that are only 'partially implemented' and\n // still as todos.\n const allowed = false;\n\n if (extension == 'fbx') {\n return model.loadFBX(filePath);\n } else if (extension == 'glb') {\n return model.loadGLTF(filePath);\n } else if (allowed && extension == 'gltf') {\n // TODO\n return model.loadGLTF(filePath);\n } else if (extension == 'stl') {\n return model.loadSTL(filePath);\n } else if (extension == 'obj') {\n if (filePath.includes(',')) {\n // has a preceeding material file\n return model.loadOBJWithMTL(filePath);\n } else {\n return model.loadOBJ(filePath);\n }\n } else if (extension == 'dae') {\n return model.loadDAE(filePath);\n } else if (allowed && (extension == 'usdc' || extension == 'usdz')) {\n // TODO\n return model.loadUSDZ(filePath);\n }\n console.error(`ERR: the extensions ${extension} is not supported by MR.js`);\n return null;\n};\n\nmodel.disposeObject3D = function (parentObject3D) {\n parentObject3D.traverse(function (node) {\n if (node.isMesh) {\n if (node.geometry) {\n node.geometry.dispose();\n }\n\n if (node.material) {\n if (node.material instanceof Array) {\n // An array of materials\n node.material.forEach((material) => material.dispose());\n } else {\n // A single material\n node.material.dispose();\n }\n }\n }\n });\n};\n\nmodel.removeObject3DFromScene = function (object3D, scene) {\n model.disposeObject3D(object3D);\n scene.remove(object3D);\n\n // Optional: Clean up references for GC if necessary\n};\n\nmodel.currentRunningAnimationClip = function (entity) {\n if (!entity.mixer) {\n console.log('No mixer found for :', entity);\n return;\n }\n // If no animation is currently playing\n if (!entity.mixer._actions.some((action) => action.isRunning())) {\n console.log('No animation is currently playing');\n return;\n }\n\n // Iterate over all clip actions in the mixer\n for (let i = 0; i < entity.mixer._actions.length; i++) {\n let clipAction = entity._actions[i];\n if (clipAction.isRunning()) {\n let clipName = clipAction.getClip().name;\n console.log(\"Animation '\" + clipName + \"' is currently playing\");\n // You can do whatever you need with this information\n // break; // Break the loop if you only want to know the first running animation\n }\n }\n};\n\n\n\n\n//# sourceURL=webpack://mrjs/./src/utils/Model.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ model: () => (/* binding */ model)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var three_addons_loaders_ColladaLoader_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! three/addons/loaders/ColladaLoader.js */ \"./node_modules/three/examples/jsm/loaders/ColladaLoader.js\");\n/* harmony import */ var three_addons_loaders_FBXLoader_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! three/addons/loaders/FBXLoader.js */ \"./node_modules/three/examples/jsm/loaders/FBXLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_GLTFLoader_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! three/examples/jsm/loaders/GLTFLoader.js */ \"./node_modules/three/examples/jsm/loaders/GLTFLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_STLLoader_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! three/examples/jsm/loaders/STLLoader.js */ \"./node_modules/three/examples/jsm/loaders/STLLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_USDZLoader_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! three/examples/jsm/loaders/USDZLoader.js */ \"./node_modules/three/examples/jsm/loaders/USDZLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! three/examples/jsm/loaders/OBJLoader.js */ \"./node_modules/three/examples/jsm/loaders/OBJLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_MTLLoader_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! three/examples/jsm/loaders/MTLLoader.js */ \"./node_modules/three/examples/jsm/loaders/MTLLoader.js\");\n\n\n\n\n\n\n\n\n\n// Singleton material pool for reuse\nconst materialPool = {};\n\n/**\n * @function getMaterialKey\n * @description Generates a unique key for a material based on its properties\n * @param {THREE.Material} material - The material to generate a key for\n * @returns {string} - The generated key\n */\nconst getMaterialKey = (material) => {\n let key = material.type;\n for (const [property, value] of Object.entries(material)) {\n if (value instanceof three__WEBPACK_IMPORTED_MODULE_0__.Color || value instanceof three__WEBPACK_IMPORTED_MODULE_0__.Texture) {\n key += `|${property}:${value.uuid}`;\n } else if (typeof value !== 'object') {\n key += `|${property}:${value}`;\n }\n }\n return key;\n};\n\n/**\n * @function getOrCreateMaterial\n * @description Reuses or creates a new material based on its properties\n * @param {THREE.Material} material - The material to reuse or create\n * @returns {THREE.Material} - The reused or newly created material\n */\nconst getOrCreateMaterial = (material) => {\n const key = getMaterialKey(material);\n if (materialPool[key]) {\n return materialPool[key];\n } else {\n materialPool[key] = material;\n return material;\n }\n};\n\nlet model = {};\n\n/**\n * @function\n * @memberof model\n * @description Loads Collada file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadDAE = async function (filePath) {\n const loader = new three_addons_loaders_ColladaLoader_js__WEBPACK_IMPORTED_MODULE_1__.ColladaLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (dae) => {\n dae.scene.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n resolve(dae.scene);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads OBJ file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadOBJ = async function (filePath) {\n const loader = new three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_2__.OBJLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (obj) => {\n obj.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n resolve(obj);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads OBJ file with externally hosted MTL file\n * @param {string} filePath - The path of the form '/path/to/mtlFile.mtl,/path/to/objFile.obj'.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadOBJWithMTL = function (filePath) {\n let paths = filePath.split(',');\n if (paths.length != 2) {\n console.error('Expected the loading of an MTL file and an OBJ file like \"path/to/mtlFile.mtl,path/to/the/objFile.obj\" - got:', filePath);\n return Promise.reject(new Error('Invalid path format for OBJ and MTL files.'));\n }\n\n const filePathMTL = paths[0];\n const filePathOBJ = paths[1];\n\n const loadMTL = (url) =>\n new Promise((resolve, reject) => {\n const mtlLoader = new three_examples_jsm_loaders_MTLLoader_js__WEBPACK_IMPORTED_MODULE_3__.MTLLoader();\n mtlLoader.load(\n url,\n (materials) => {\n materials.preload();\n resolve(materials);\n },\n undefined,\n (error) => {\n console.error('Failed to load MTL from URL:', error);\n reject(error);\n }\n );\n });\n\n const loadOBJ = (filePath, materials) =>\n new Promise((resolve, reject) => {\n const objLoader = new three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_2__.OBJLoader();\n objLoader.setMaterials(materials);\n objLoader.load(\n filePath,\n (obj) => {\n obj.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n resolve(obj);\n },\n undefined,\n (error) => {\n console.error('Failed to load OBJ:', error);\n reject(error);\n }\n );\n });\n\n return loadMTL(filePathMTL)\n .then((materials) => loadOBJ(filePathOBJ, materials))\n .catch((error) => {\n console.error('An error occurred while loading OBJ with external MTL:', error);\n throw error;\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads FBX file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadFBX = async function (filePath) {\n const loader = new three_addons_loaders_FBXLoader_js__WEBPACK_IMPORTED_MODULE_4__.FBXLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (fbx) => {\n fbx.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n resolve(fbx);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads GLTF/GLB file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadGLTF = async function (filePath) {\n const loader = new three_examples_jsm_loaders_GLTFLoader_js__WEBPACK_IMPORTED_MODULE_5__.GLTFLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (gltf) => {\n const scene = gltf.scene;\n scene.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n const animations = gltf.animations;\n resolve({ scene, animations });\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads STL file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadSTL = async function (filePath) {\n const loader = new three_examples_jsm_loaders_STLLoader_js__WEBPACK_IMPORTED_MODULE_6__.STLLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (geometry) => {\n const mesh = new three__WEBPACK_IMPORTED_MODULE_0__.Mesh(geometry, getOrCreateMaterial(new three__WEBPACK_IMPORTED_MODULE_0__.MeshPhongMaterial()));\n resolve(mesh);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads USD/USDZ file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadUSDZ = async function (filePath) {\n const usdzLoader = new three_examples_jsm_loaders_USDZLoader_js__WEBPACK_IMPORTED_MODULE_7__.USDZLoader();\n\n return usdzLoader.loadAsync(filePath)\n .then((model) => {\n model.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n return model;\n })\n .catch((error) => {\n console.error(error);\n return null;\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description The main loading function\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @param {string} extension - The extension of the file type. Current allowed extensions are `dae`, fbx`, `glb`, `obj`, and `stl`.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadModel = async function (filePath, extension) {\n switch (extension) {\n case 'fbx':\n return model.loadFBX(filePath);\n case 'glb':\n case 'gltf':\n return model.loadGLTF(filePath);\n case 'stl':\n return model.loadSTL(filePath);\n case 'obj':\n if (filePath.includes(',')) {\n return model.loadOBJWithMTL(filePath);\n } else {\n return model.loadOBJ(filePath);\n }\n case 'dae':\n return model.loadDAE(filePath);\n case 'usdz':\n case 'usdc':\n return model.loadUSDZ(filePath);\n default:\n console.error(`ERR: the extensions ${extension} is not supported by MR.js`);\n return null;\n }\n};\n\nmodel.disposeObject3D = function (parentObject3D) {\n parentObject3D.traverse(function (node) {\n if (node.isMesh) {\n if (node.geometry) {\n node.geometry.dispose();\n }\n if (node.material) {\n if (Array.isArray(node.material)) {\n node.material.forEach((material) => material.dispose());\n } else {\n node.material.dispose();\n }\n }\n }\n });\n};\n\nmodel.removeObject3DFromScene = function (object3D, scene) {\n model.disposeObject3D(object3D);\n scene.remove(object3D);\n};\n\nmodel.currentRunningAnimationClip = function (entity) {\n if (!entity.mixer) {\n console.log('No mixer found for :', entity);\n return;\n }\n if (!entity.mixer._actions.some((action) => action.isRunning())) {\n console.log('No animation is currently playing');\n return;\n }\n for (let i = 0; i < entity.mixer._actions.length; i++) {\n let clipAction = entity._actions[i];\n if (clipAction.isRunning()) {\n let clipName = clipAction.getClip().name;\n console.log(\"Animation '\" + clipName + \"' is currently playing\");\n }\n }\n};\n\n\n\n\n//# sourceURL=webpack://mrjs/./src/utils/Model.js?"); /***/ }), diff --git a/src/core/MRApp.js b/src/core/MRApp.js index b4eb7e4d..a5473807 100644 --- a/src/core/MRApp.js +++ b/src/core/MRApp.js @@ -625,41 +625,41 @@ export class MRApp extends MRElement { // this.renderer.info.programs.forEach(program => { // console.log(`Program: `, program, `Used times in last frame: ${program.usedTimes}`); // }); - function printSceneObjectsAndMaterials(scene, renderer) { - let groupedByMaterial = {}; - - // Traverse the scene and group objects by material UUID - scene.traverse(function (object) { - if (object.isMesh && object.material) { - const uuid = object.material.uuid; - if (!groupedByMaterial[uuid]) { - groupedByMaterial[uuid] = []; // Initialize array if it doesn't exist - } - groupedByMaterial[uuid].push({ - objectName: object.name, - objectType: object.type - }); - } - }); + // function printSceneObjectsAndMaterials(scene, renderer) { + // let groupedByMaterial = {}; + + // // Traverse the scene and group objects by material UUID + // scene.traverse(function (object) { + // if (object.isMesh && object.material) { + // const uuid = object.material.uuid; + // if (!groupedByMaterial[uuid]) { + // groupedByMaterial[uuid] = []; // Initialize array if it doesn't exist + // } + // groupedByMaterial[uuid].push({ + // objectName: object.name, + // objectType: object.type + // }); + // } + // }); // Log details about each group - Object.keys(groupedByMaterial).forEach(uuid => { - console.log(`Material UUID: ${uuid}, num items: ${groupedByMaterial[uuid].length}`); - groupedByMaterial[uuid].forEach(entry => { - console.log(`Object: ${entry.objectName} | Type: ${entry.objectType}`); - }); - }); - - // Then, log all active WebGL programs separately. - if (renderer.info.programs) { - renderer.info.programs.forEach(program => { - console.log(`Program ID: ${program.id}, Program Info:`, program); - }); - } - } + // Object.keys(groupedByMaterial).forEach(uuid => { + // console.log(`Material UUID: ${uuid}, num items: ${groupedByMaterial[uuid].length}`); + // groupedByMaterial[uuid].forEach(entry => { + // console.log(`Object: ${entry.objectName} | Type: ${entry.objectType}`); + // }); + // }); + + // // Then, log all active WebGL programs separately. + // if (renderer.info.programs) { + // renderer.info.programs.forEach(program => { + // console.log(`Program ID: ${program.id}, Program Info:`, program); + // }); + // } + // } // Call this function where appropriate in your application - printSceneObjectsAndMaterials(this.scene, this.renderer); + // printSceneObjectsAndMaterials(this.scene, this.renderer); } } diff --git a/src/utils/Model.js b/src/utils/Model.js index bdf18d6e..ddfd40be 100644 --- a/src/utils/Model.js +++ b/src/utils/Model.js @@ -7,34 +7,50 @@ import { USDZLoader } from 'three/examples/jsm/loaders/USDZLoader.js'; import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'; import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js'; -// Keeping the below imports in as reference for future items we can add. -// import { AMFLoader } from 'three/addons/loaders/AMFLoader.js'; -// import { BVHLoader } from 'three/addons/loaders/BVHLoader.js'; -// import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'; -// import { GCodeLoader } from 'three/addons/loaders/GCodeLoader.js'; -// import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; -// // import { IFCLoader } from 'web-ifc-three'; -// // import { IFCSPACE } from 'web-ifc'; -// import { Rhino3dmLoader } from 'three/addons/loaders/3DMLoader.js'; -// import { PCDLoader } from 'three/addons/loaders/PCDLoader.js'; -// import { PDBLoader } from 'three/addons/loaders/PDBLoader.js'; -// import { PLYLoader } from 'three/addons/loaders/PLYLoader.js'; -// import { SVGLoader } from 'three/addons/loaders/SVGLoader.js'; -// import { TDSLoader } from 'three/addons/loaders/TDSLoader.js'; -// import { ThreeMFLoader } from 'three/addons/loaders/3MFLoader.js'; +// Singleton material pool for reuse +const materialPool = {}; /** - * @namespace model - * @description Useful namespace for helping with Model utility functions + * @function getMaterialKey + * @description Generates a unique key for a material based on its properties + * @param {THREE.Material} material - The material to generate a key for + * @returns {string} - The generated key */ +const getMaterialKey = (material) => { + let key = material.type; + for (const [property, value] of Object.entries(material)) { + if (value instanceof THREE.Color || value instanceof THREE.Texture) { + key += `|${property}:${value.uuid}`; + } else if (typeof value !== 'object') { + key += `|${property}:${value}`; + } + } + return key; +}; + +/** + * @function getOrCreateMaterial + * @description Reuses or creates a new material based on its properties + * @param {THREE.Material} material - The material to reuse or create + * @returns {THREE.Material} - The reused or newly created material + */ +const getOrCreateMaterial = (material) => { + const key = getMaterialKey(material); + if (materialPool[key]) { + return materialPool[key]; + } else { + materialPool[key] = material; + return material; + } +}; + let model = {}; /** * @function * @memberof model * @description Loads Collada file - * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports - * the full path and the relative path directly to the file. + * @param {string} filePath - The path to the file(s) needing to be loaded. * @returns {Promise} - the promise of the loaded mesh object. */ model.loadDAE = async function (filePath) { @@ -44,6 +60,11 @@ model.loadDAE = async function (filePath) { loader.load( filePath, (dae) => { + dae.scene.traverse((child) => { + if (child.isMesh) { + child.material = getOrCreateMaterial(child.material); + } + }); resolve(dae.scene); }, undefined, @@ -59,8 +80,7 @@ model.loadDAE = async function (filePath) { * @function * @memberof model * @description Loads OBJ file - * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports - * the full path and the relative path directly to the file. + * @param {string} filePath - The path to the file(s) needing to be loaded. * @returns {Promise} - the promise of the loaded mesh object. */ model.loadOBJ = async function (filePath) { @@ -70,6 +90,11 @@ model.loadOBJ = async function (filePath) { loader.load( filePath, (obj) => { + obj.traverse((child) => { + if (child.isMesh) { + child.material = getOrCreateMaterial(child.material); + } + }); resolve(obj); }, undefined, @@ -90,7 +115,6 @@ model.loadOBJ = async function (filePath) { */ model.loadOBJWithMTL = function (filePath) { let paths = filePath.split(','); - // Assigning each path to a variable if (paths.length != 2) { console.error('Expected the loading of an MTL file and an OBJ file like "path/to/mtlFile.mtl,path/to/the/objFile.obj" - got:', filePath); return Promise.reject(new Error('Invalid path format for OBJ and MTL files.')); @@ -99,8 +123,6 @@ model.loadOBJWithMTL = function (filePath) { const filePathMTL = paths[0]; const filePathOBJ = paths[1]; - console.log('in load obj with mtl'); - const loadMTL = (url) => new Promise((resolve, reject) => { const mtlLoader = new MTLLoader(); @@ -108,7 +130,6 @@ model.loadOBJWithMTL = function (filePath) { url, (materials) => { materials.preload(); - console.log(materials); resolve(materials); }, undefined, @@ -126,6 +147,11 @@ model.loadOBJWithMTL = function (filePath) { objLoader.load( filePath, (obj) => { + obj.traverse((child) => { + if (child.isMesh) { + child.material = getOrCreateMaterial(child.material); + } + }); resolve(obj); }, undefined, @@ -140,7 +166,7 @@ model.loadOBJWithMTL = function (filePath) { .then((materials) => loadOBJ(filePathOBJ, materials)) .catch((error) => { console.error('An error occurred while loading OBJ with external MTL:', error); - throw error; // Ensure errors are propagated + throw error; }); }; @@ -148,20 +174,21 @@ model.loadOBJWithMTL = function (filePath) { * @function * @memberof model * @description Loads FBX file - * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports - * the full path and the relative path directly to the file. + * @param {string} filePath - The path to the file(s) needing to be loaded. * @returns {Promise} - the promise of the loaded mesh object. */ model.loadFBX = async function (filePath) { const loader = new FBXLoader(); - console.log('in load FBX'); - return new Promise((resolve, reject) => { loader.load( filePath, (fbx) => { - console.log(fbx); + fbx.traverse((child) => { + if (child.isMesh) { + child.material = getOrCreateMaterial(child.material); + } + }); resolve(fbx); }, undefined, @@ -177,25 +204,23 @@ model.loadFBX = async function (filePath) { * @function * @memberof model * @description Loads GLTF/GLB file - * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports - * the full path and the relative path directly to the file. + * @param {string} filePath - The path to the file(s) needing to be loaded. * @returns {Promise} - the promise of the loaded mesh object. */ model.loadGLTF = async function (filePath) { const loader = new GLTFLoader(); - console.log('in load gltf/glb'); - return new Promise((resolve, reject) => { loader.load( filePath, (gltf) => { const scene = gltf.scene; + scene.traverse((child) => { + if (child.isMesh) { + child.material = getOrCreateMaterial(child.material); + } + }); const animations = gltf.animations; - - console.log(scene); - - // Resolve the promise with the loaded scene and animations resolve({ scene, animations }); }, undefined, @@ -210,9 +235,8 @@ model.loadGLTF = async function (filePath) { /** * @function * @memberof model - * @description Loads stl file - * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports - * the full path and the relative path directly to the file. + * @description Loads STL file + * @param {string} filePath - The path to the file(s) needing to be loaded. * @returns {Promise} - the promise of the loaded mesh object. */ model.loadSTL = async function (filePath) { @@ -222,18 +246,13 @@ model.loadSTL = async function (filePath) { loader.load( filePath, (geometry) => { - const material = mrjsUtils.material.MeshPhongMaterial.clone(); - material.programName = "stlMaterial"; - const mesh = new THREE.Mesh(geometry, material); - - resolve(mesh); // Resolve the promise with the loaded mesh - }, - (xhr) => { - // Progress callback + const mesh = new THREE.Mesh(geometry, getOrCreateMaterial(new THREE.MeshPhongMaterial())); + resolve(mesh); }, + undefined, (error) => { console.error(error); - reject(error); // Reject the promise if there's an error + reject(error); } ); }); @@ -243,66 +262,59 @@ model.loadSTL = async function (filePath) { * @function * @memberof model * @description Loads USD/USDZ file - * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports - * the full path and the relative path directly to the file. + * @param {string} filePath - The path to the file(s) needing to be loaded. * @returns {Promise} - the promise of the loaded mesh object. */ model.loadUSDZ = async function (filePath) { const usdzLoader = new USDZLoader(); - console.log('in load usdz'); - - const [model] = await Promise.all([usdzLoader.loadAsync(filePath)], undefined, (error) => { - console.error(error); - return null; - }); - - console.log(model); - return model; + return usdzLoader.loadAsync(filePath) + .then((model) => { + model.traverse((child) => { + if (child.isMesh) { + child.material = getOrCreateMaterial(child.material); + } + }); + return model; + }) + .catch((error) => { + console.error(error); + return null; + }); }; -/// //////////////////////// -// Main Loading Function // -/// //////////////////////// - /** * @function * @memberof model * @description The main loading function - * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports - * the full path and the relative path directly to the file. + * @param {string} filePath - The path to the file(s) needing to be loaded. * @param {string} extension - The extension of the file type. Current allowed extensions are `dae`, fbx`, `glb`, `obj`, and `stl`. * @returns {Promise} - the promise of the loaded mesh object. */ model.loadModel = async function (filePath, extension) { - // Flag used for debugging the ones that are only 'partially implemented' and - // still as todos. - const allowed = false; - - if (extension == 'fbx') { - return model.loadFBX(filePath); - } else if (extension == 'glb') { - return model.loadGLTF(filePath); - } else if (allowed && extension == 'gltf') { - // TODO - return model.loadGLTF(filePath); - } else if (extension == 'stl') { - return model.loadSTL(filePath); - } else if (extension == 'obj') { - if (filePath.includes(',')) { - // has a preceeding material file - return model.loadOBJWithMTL(filePath); - } else { - return model.loadOBJ(filePath); - } - } else if (extension == 'dae') { - return model.loadDAE(filePath); - } else if (allowed && (extension == 'usdc' || extension == 'usdz')) { - // TODO - return model.loadUSDZ(filePath); + switch (extension) { + case 'fbx': + return model.loadFBX(filePath); + case 'glb': + case 'gltf': + return model.loadGLTF(filePath); + case 'stl': + return model.loadSTL(filePath); + case 'obj': + if (filePath.includes(',')) { + return model.loadOBJWithMTL(filePath); + } else { + return model.loadOBJ(filePath); + } + case 'dae': + return model.loadDAE(filePath); + case 'usdz': + case 'usdc': + return model.loadUSDZ(filePath); + default: + console.error(`ERR: the extensions ${extension} is not supported by MR.js`); + return null; } - console.error(`ERR: the extensions ${extension} is not supported by MR.js`); - return null; }; model.disposeObject3D = function (parentObject3D) { @@ -311,13 +323,10 @@ model.disposeObject3D = function (parentObject3D) { if (node.geometry) { node.geometry.dispose(); } - if (node.material) { - if (node.material instanceof Array) { - // An array of materials + if (Array.isArray(node.material)) { node.material.forEach((material) => material.dispose()); } else { - // A single material node.material.dispose(); } } @@ -328,8 +337,6 @@ model.disposeObject3D = function (parentObject3D) { model.removeObject3DFromScene = function (object3D, scene) { model.disposeObject3D(object3D); scene.remove(object3D); - - // Optional: Clean up references for GC if necessary }; model.currentRunningAnimationClip = function (entity) { @@ -337,20 +344,15 @@ model.currentRunningAnimationClip = function (entity) { console.log('No mixer found for :', entity); return; } - // If no animation is currently playing if (!entity.mixer._actions.some((action) => action.isRunning())) { console.log('No animation is currently playing'); return; } - - // Iterate over all clip actions in the mixer for (let i = 0; i < entity.mixer._actions.length; i++) { let clipAction = entity._actions[i]; if (clipAction.isRunning()) { let clipName = clipAction.getClip().name; console.log("Animation '" + clipName + "' is currently playing"); - // You can do whatever you need with this information - // break; // Break the loop if you only want to know the first running animation } } }; From b2332e1f5246cb964a32cf8080469ce172d2e12c Mon Sep 17 00:00:00 2001 From: hanbollar Date: Thu, 16 May 2024 17:12:05 -0700 Subject: [PATCH 07/12] another variation Signed-off-by: hanbollar --- dist/mr.js | 2 +- src/utils/Model.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/dist/mr.js b/dist/mr.js index 83078437..a8735133 100644 --- a/dist/mr.js +++ b/dist/mr.js @@ -1047,7 +1047,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 */ model: () => (/* binding */ model)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var three_addons_loaders_ColladaLoader_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! three/addons/loaders/ColladaLoader.js */ \"./node_modules/three/examples/jsm/loaders/ColladaLoader.js\");\n/* harmony import */ var three_addons_loaders_FBXLoader_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! three/addons/loaders/FBXLoader.js */ \"./node_modules/three/examples/jsm/loaders/FBXLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_GLTFLoader_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! three/examples/jsm/loaders/GLTFLoader.js */ \"./node_modules/three/examples/jsm/loaders/GLTFLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_STLLoader_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! three/examples/jsm/loaders/STLLoader.js */ \"./node_modules/three/examples/jsm/loaders/STLLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_USDZLoader_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! three/examples/jsm/loaders/USDZLoader.js */ \"./node_modules/three/examples/jsm/loaders/USDZLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! three/examples/jsm/loaders/OBJLoader.js */ \"./node_modules/three/examples/jsm/loaders/OBJLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_MTLLoader_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! three/examples/jsm/loaders/MTLLoader.js */ \"./node_modules/three/examples/jsm/loaders/MTLLoader.js\");\n\n\n\n\n\n\n\n\n\n// Singleton material pool for reuse\nconst materialPool = {};\n\n/**\n * @function getMaterialKey\n * @description Generates a unique key for a material based on its properties\n * @param {THREE.Material} material - The material to generate a key for\n * @returns {string} - The generated key\n */\nconst getMaterialKey = (material) => {\n let key = material.type;\n for (const [property, value] of Object.entries(material)) {\n if (value instanceof three__WEBPACK_IMPORTED_MODULE_0__.Color || value instanceof three__WEBPACK_IMPORTED_MODULE_0__.Texture) {\n key += `|${property}:${value.uuid}`;\n } else if (typeof value !== 'object') {\n key += `|${property}:${value}`;\n }\n }\n return key;\n};\n\n/**\n * @function getOrCreateMaterial\n * @description Reuses or creates a new material based on its properties\n * @param {THREE.Material} material - The material to reuse or create\n * @returns {THREE.Material} - The reused or newly created material\n */\nconst getOrCreateMaterial = (material) => {\n const key = getMaterialKey(material);\n if (materialPool[key]) {\n return materialPool[key];\n } else {\n materialPool[key] = material;\n return material;\n }\n};\n\nlet model = {};\n\n/**\n * @function\n * @memberof model\n * @description Loads Collada file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadDAE = async function (filePath) {\n const loader = new three_addons_loaders_ColladaLoader_js__WEBPACK_IMPORTED_MODULE_1__.ColladaLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (dae) => {\n dae.scene.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n resolve(dae.scene);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads OBJ file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadOBJ = async function (filePath) {\n const loader = new three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_2__.OBJLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (obj) => {\n obj.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n resolve(obj);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads OBJ file with externally hosted MTL file\n * @param {string} filePath - The path of the form '/path/to/mtlFile.mtl,/path/to/objFile.obj'.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadOBJWithMTL = function (filePath) {\n let paths = filePath.split(',');\n if (paths.length != 2) {\n console.error('Expected the loading of an MTL file and an OBJ file like \"path/to/mtlFile.mtl,path/to/the/objFile.obj\" - got:', filePath);\n return Promise.reject(new Error('Invalid path format for OBJ and MTL files.'));\n }\n\n const filePathMTL = paths[0];\n const filePathOBJ = paths[1];\n\n const loadMTL = (url) =>\n new Promise((resolve, reject) => {\n const mtlLoader = new three_examples_jsm_loaders_MTLLoader_js__WEBPACK_IMPORTED_MODULE_3__.MTLLoader();\n mtlLoader.load(\n url,\n (materials) => {\n materials.preload();\n resolve(materials);\n },\n undefined,\n (error) => {\n console.error('Failed to load MTL from URL:', error);\n reject(error);\n }\n );\n });\n\n const loadOBJ = (filePath, materials) =>\n new Promise((resolve, reject) => {\n const objLoader = new three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_2__.OBJLoader();\n objLoader.setMaterials(materials);\n objLoader.load(\n filePath,\n (obj) => {\n obj.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n resolve(obj);\n },\n undefined,\n (error) => {\n console.error('Failed to load OBJ:', error);\n reject(error);\n }\n );\n });\n\n return loadMTL(filePathMTL)\n .then((materials) => loadOBJ(filePathOBJ, materials))\n .catch((error) => {\n console.error('An error occurred while loading OBJ with external MTL:', error);\n throw error;\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads FBX file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadFBX = async function (filePath) {\n const loader = new three_addons_loaders_FBXLoader_js__WEBPACK_IMPORTED_MODULE_4__.FBXLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (fbx) => {\n fbx.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n resolve(fbx);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads GLTF/GLB file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadGLTF = async function (filePath) {\n const loader = new three_examples_jsm_loaders_GLTFLoader_js__WEBPACK_IMPORTED_MODULE_5__.GLTFLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (gltf) => {\n const scene = gltf.scene;\n scene.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n const animations = gltf.animations;\n resolve({ scene, animations });\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads STL file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadSTL = async function (filePath) {\n const loader = new three_examples_jsm_loaders_STLLoader_js__WEBPACK_IMPORTED_MODULE_6__.STLLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (geometry) => {\n const mesh = new three__WEBPACK_IMPORTED_MODULE_0__.Mesh(geometry, getOrCreateMaterial(new three__WEBPACK_IMPORTED_MODULE_0__.MeshPhongMaterial()));\n resolve(mesh);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads USD/USDZ file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadUSDZ = async function (filePath) {\n const usdzLoader = new three_examples_jsm_loaders_USDZLoader_js__WEBPACK_IMPORTED_MODULE_7__.USDZLoader();\n\n return usdzLoader.loadAsync(filePath)\n .then((model) => {\n model.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n return model;\n })\n .catch((error) => {\n console.error(error);\n return null;\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description The main loading function\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @param {string} extension - The extension of the file type. Current allowed extensions are `dae`, fbx`, `glb`, `obj`, and `stl`.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadModel = async function (filePath, extension) {\n switch (extension) {\n case 'fbx':\n return model.loadFBX(filePath);\n case 'glb':\n case 'gltf':\n return model.loadGLTF(filePath);\n case 'stl':\n return model.loadSTL(filePath);\n case 'obj':\n if (filePath.includes(',')) {\n return model.loadOBJWithMTL(filePath);\n } else {\n return model.loadOBJ(filePath);\n }\n case 'dae':\n return model.loadDAE(filePath);\n case 'usdz':\n case 'usdc':\n return model.loadUSDZ(filePath);\n default:\n console.error(`ERR: the extensions ${extension} is not supported by MR.js`);\n return null;\n }\n};\n\nmodel.disposeObject3D = function (parentObject3D) {\n parentObject3D.traverse(function (node) {\n if (node.isMesh) {\n if (node.geometry) {\n node.geometry.dispose();\n }\n if (node.material) {\n if (Array.isArray(node.material)) {\n node.material.forEach((material) => material.dispose());\n } else {\n node.material.dispose();\n }\n }\n }\n });\n};\n\nmodel.removeObject3DFromScene = function (object3D, scene) {\n model.disposeObject3D(object3D);\n scene.remove(object3D);\n};\n\nmodel.currentRunningAnimationClip = function (entity) {\n if (!entity.mixer) {\n console.log('No mixer found for :', entity);\n return;\n }\n if (!entity.mixer._actions.some((action) => action.isRunning())) {\n console.log('No animation is currently playing');\n return;\n }\n for (let i = 0; i < entity.mixer._actions.length; i++) {\n let clipAction = entity._actions[i];\n if (clipAction.isRunning()) {\n let clipName = clipAction.getClip().name;\n console.log(\"Animation '\" + clipName + \"' is currently playing\");\n }\n }\n};\n\n\n\n\n//# sourceURL=webpack://mrjs/./src/utils/Model.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ model: () => (/* binding */ model)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var three_addons_loaders_ColladaLoader_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! three/addons/loaders/ColladaLoader.js */ \"./node_modules/three/examples/jsm/loaders/ColladaLoader.js\");\n/* harmony import */ var three_addons_loaders_FBXLoader_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! three/addons/loaders/FBXLoader.js */ \"./node_modules/three/examples/jsm/loaders/FBXLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_GLTFLoader_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! three/examples/jsm/loaders/GLTFLoader.js */ \"./node_modules/three/examples/jsm/loaders/GLTFLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_STLLoader_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! three/examples/jsm/loaders/STLLoader.js */ \"./node_modules/three/examples/jsm/loaders/STLLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_USDZLoader_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! three/examples/jsm/loaders/USDZLoader.js */ \"./node_modules/three/examples/jsm/loaders/USDZLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! three/examples/jsm/loaders/OBJLoader.js */ \"./node_modules/three/examples/jsm/loaders/OBJLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_MTLLoader_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! three/examples/jsm/loaders/MTLLoader.js */ \"./node_modules/three/examples/jsm/loaders/MTLLoader.js\");\n\n\n\n\n\n\n\n\n\n// Singleton material pool for reuse\nconst materialPool = {};\n\n/**\n * @function getMaterialKey\n * @description Generates a unique key for a material based on its properties\n * @param {THREE.Material} material - The material to generate a key for\n * @returns {string} - The generated key\n */\nconst getMaterialKey = (material) => {\n let key = material.type;\n for (const [property, value] of Object.entries(material)) {\n if (value instanceof three__WEBPACK_IMPORTED_MODULE_0__.Color || value instanceof three__WEBPACK_IMPORTED_MODULE_0__.Texture) {\n key += `|${property}:${value.uuid}`;\n } else if (typeof value !== 'object') {\n key += `|${property}:${value}`;\n }\n }\n return key;\n};\n\n/**\n * @function getOrCreateMaterial\n * @description Reuses or creates a new material based on its properties\n * @param {THREE.Material} material - The material to reuse or create\n * @returns {THREE.Material} - The reused or newly created material\n */\nconst getOrCreateMaterial = (material) => {\n const key = getMaterialKey(material);\n if (materialPool[key]) {\n return materialPool[key];\n } else {\n const newMaterial = material.clone();\n materialPool[key] = newMaterial;\n return newMaterial;\n }\n};\n\nlet model = {};\n\n/**\n * @function\n * @memberof model\n * @description Loads Collada file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadDAE = async function (filePath) {\n const loader = new three_addons_loaders_ColladaLoader_js__WEBPACK_IMPORTED_MODULE_1__.ColladaLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (dae) => {\n dae.scene.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n resolve(dae.scene);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads OBJ file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadOBJ = async function (filePath) {\n const loader = new three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_2__.OBJLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (obj) => {\n obj.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n resolve(obj);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads OBJ file with externally hosted MTL file\n * @param {string} filePath - The path of the form '/path/to/mtlFile.mtl,/path/to/objFile.obj'.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadOBJWithMTL = function (filePath) {\n let paths = filePath.split(',');\n if (paths.length != 2) {\n console.error('Expected the loading of an MTL file and an OBJ file like \"path/to/mtlFile.mtl,path/to/the/objFile.obj\" - got:', filePath);\n return Promise.reject(new Error('Invalid path format for OBJ and MTL files.'));\n }\n\n const filePathMTL = paths[0];\n const filePathOBJ = paths[1];\n\n const loadMTL = (url) =>\n new Promise((resolve, reject) => {\n const mtlLoader = new three_examples_jsm_loaders_MTLLoader_js__WEBPACK_IMPORTED_MODULE_3__.MTLLoader();\n mtlLoader.load(\n url,\n (materials) => {\n materials.preload();\n resolve(materials);\n },\n undefined,\n (error) => {\n console.error('Failed to load MTL from URL:', error);\n reject(error);\n }\n );\n });\n\n const loadOBJ = (filePath, materials) =>\n new Promise((resolve, reject) => {\n const objLoader = new three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_2__.OBJLoader();\n objLoader.setMaterials(materials);\n objLoader.load(\n filePath,\n (obj) => {\n obj.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n resolve(obj);\n },\n undefined,\n (error) => {\n console.error('Failed to load OBJ:', error);\n reject(error);\n }\n );\n });\n\n return loadMTL(filePathMTL)\n .then((materials) => loadOBJ(filePathOBJ, materials))\n .catch((error) => {\n console.error('An error occurred while loading OBJ with external MTL:', error);\n throw error;\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads FBX file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadFBX = async function (filePath) {\n const loader = new three_addons_loaders_FBXLoader_js__WEBPACK_IMPORTED_MODULE_4__.FBXLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (fbx) => {\n fbx.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n resolve(fbx);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads GLTF/GLB file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadGLTF = async function (filePath) {\n const loader = new three_examples_jsm_loaders_GLTFLoader_js__WEBPACK_IMPORTED_MODULE_5__.GLTFLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (gltf) => {\n const scene = gltf.scene;\n scene.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n const animations = gltf.animations;\n resolve({ scene, animations });\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads STL file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadSTL = async function (filePath) {\n const loader = new three_examples_jsm_loaders_STLLoader_js__WEBPACK_IMPORTED_MODULE_6__.STLLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (geometry) => {\n const mesh = new three__WEBPACK_IMPORTED_MODULE_0__.Mesh(geometry, getOrCreateMaterial(new three__WEBPACK_IMPORTED_MODULE_0__.MeshPhongMaterial()));\n resolve(mesh);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads USD/USDZ file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadUSDZ = async function (filePath) {\n const usdzLoader = new three_examples_jsm_loaders_USDZLoader_js__WEBPACK_IMPORTED_MODULE_7__.USDZLoader();\n\n return usdzLoader.loadAsync(filePath)\n .then((model) => {\n model.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n return model;\n })\n .catch((error) => {\n console.error(error);\n return null;\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description The main loading function\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @param {string} extension - The extension of the file type. Current allowed extensions are `dae`, fbx`, `glb`, `obj`, and `stl`.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadModel = async function (filePath, extension) {\n switch (extension) {\n case 'fbx':\n return model.loadFBX(filePath);\n case 'glb':\n case 'gltf':\n return model.loadGLTF(filePath);\n case 'stl':\n return model.loadSTL(filePath);\n case 'obj':\n if (filePath.includes(',')) {\n return model.loadOBJWithMTL(filePath);\n } else {\n return model.loadOBJ(filePath);\n }\n case 'dae':\n return model.loadDAE(filePath);\n case 'usdz':\n case 'usdc':\n return model.loadUSDZ(filePath);\n default:\n console.error(`ERR: the extensions ${extension} is not supported by MR.js`);\n return null;\n }\n};\n\nmodel.disposeObject3D = function (parentObject3D) {\n parentObject3D.traverse(function (node) {\n if (node.isMesh) {\n if (node.geometry) {\n node.geometry.dispose();\n }\n if (node.material) {\n if (Array.isArray(node.material)) {\n node.material.forEach((material) => material.dispose());\n } else {\n node.material.dispose();\n }\n }\n }\n });\n};\n\nmodel.removeObject3DFromScene = function (object3D, scene) {\n model.disposeObject3D(object3D);\n scene.remove(object3D);\n};\n\nmodel.currentRunningAnimationClip = function (entity) {\n if (!entity.mixer) {\n console.log('No mixer found for :', entity);\n return;\n }\n if (!entity.mixer._actions.some((action) => action.isRunning())) {\n console.log('No animation is currently playing');\n return;\n }\n for (let i = 0; i < entity.mixer._actions.length; i++) {\n let clipAction = entity._actions[i];\n if (clipAction.isRunning()) {\n let clipName = clipAction.getClip().name;\n console.log(\"Animation '\" + clipName + \"' is currently playing\");\n }\n }\n};\n\n\n\n\n//# sourceURL=webpack://mrjs/./src/utils/Model.js?"); /***/ }), diff --git a/src/utils/Model.js b/src/utils/Model.js index ddfd40be..da1e9974 100644 --- a/src/utils/Model.js +++ b/src/utils/Model.js @@ -39,8 +39,9 @@ const getOrCreateMaterial = (material) => { if (materialPool[key]) { return materialPool[key]; } else { - materialPool[key] = material; - return material; + const newMaterial = material.clone(); + materialPool[key] = newMaterial; + return newMaterial; } }; From cd3af5db36f4a4faac52ab6c70d969565e24b5f6 Mon Sep 17 00:00:00 2001 From: hanbollar Date: Thu, 16 May 2024 17:33:34 -0700 Subject: [PATCH 08/12] our version Signed-off-by: hanbollar --- dist/mr.js | 4 +- samples/examples/testing.html | 3 +- src/utils/Material.js | 38 +++++++ src/utils/Model.js | 201 ++++++++++++++++------------------ 4 files changed, 133 insertions(+), 113 deletions(-) diff --git a/dist/mr.js b/dist/mr.js index a8735133..09337c43 100644 --- a/dist/mr.js +++ b/dist/mr.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 * 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?"); +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// Singleton material pool for reuse\nconst materialPool = {};\n\n/**\n * @function getMaterialKey\n * @description Generates a unique key for a material based on its properties\n * @param {THREE.Material} material - The material to generate a key for\n * @returns {string} - The generated key\n */\nconst getMaterialKey = (material) => {\n let key = material.type;\n for (const [property, value] of Object.entries(material)) {\n if (value instanceof three__WEBPACK_IMPORTED_MODULE_1__.Color || value instanceof three__WEBPACK_IMPORTED_MODULE_1__.Texture) {\n key += `|${property}:${value.uuid}`;\n } else if (typeof value !== 'object') {\n key += `|${property}:${value}`;\n }\n }\n return key;\n};\n\n/**\n * @function getOrCreateMaterial\n * @description Reuses or creates a new material based on its properties\n * @param {THREE.Material} material - The material to reuse or create\n * @returns {THREE.Material} - The reused or newly created material\n */\nconst getOrCreateMaterial = (material) => {\n const key = getMaterialKey(material);\n if (materialPool[key]) {\n return materialPool[key];\n } else {\n const newMaterial = material.clone();\n materialPool[key] = newMaterial;\n return newMaterial;\n }\n};\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?"); /***/ }), @@ -1047,7 +1047,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 */ model: () => (/* binding */ model)\n/* harmony export */ });\n/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! three */ \"./node_modules/three/build/three.module.js\");\n/* harmony import */ var three_addons_loaders_ColladaLoader_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! three/addons/loaders/ColladaLoader.js */ \"./node_modules/three/examples/jsm/loaders/ColladaLoader.js\");\n/* harmony import */ var three_addons_loaders_FBXLoader_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! three/addons/loaders/FBXLoader.js */ \"./node_modules/three/examples/jsm/loaders/FBXLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_GLTFLoader_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! three/examples/jsm/loaders/GLTFLoader.js */ \"./node_modules/three/examples/jsm/loaders/GLTFLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_STLLoader_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! three/examples/jsm/loaders/STLLoader.js */ \"./node_modules/three/examples/jsm/loaders/STLLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_USDZLoader_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! three/examples/jsm/loaders/USDZLoader.js */ \"./node_modules/three/examples/jsm/loaders/USDZLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! three/examples/jsm/loaders/OBJLoader.js */ \"./node_modules/three/examples/jsm/loaders/OBJLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_MTLLoader_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! three/examples/jsm/loaders/MTLLoader.js */ \"./node_modules/three/examples/jsm/loaders/MTLLoader.js\");\n\n\n\n\n\n\n\n\n\n// Singleton material pool for reuse\nconst materialPool = {};\n\n/**\n * @function getMaterialKey\n * @description Generates a unique key for a material based on its properties\n * @param {THREE.Material} material - The material to generate a key for\n * @returns {string} - The generated key\n */\nconst getMaterialKey = (material) => {\n let key = material.type;\n for (const [property, value] of Object.entries(material)) {\n if (value instanceof three__WEBPACK_IMPORTED_MODULE_0__.Color || value instanceof three__WEBPACK_IMPORTED_MODULE_0__.Texture) {\n key += `|${property}:${value.uuid}`;\n } else if (typeof value !== 'object') {\n key += `|${property}:${value}`;\n }\n }\n return key;\n};\n\n/**\n * @function getOrCreateMaterial\n * @description Reuses or creates a new material based on its properties\n * @param {THREE.Material} material - The material to reuse or create\n * @returns {THREE.Material} - The reused or newly created material\n */\nconst getOrCreateMaterial = (material) => {\n const key = getMaterialKey(material);\n if (materialPool[key]) {\n return materialPool[key];\n } else {\n const newMaterial = material.clone();\n materialPool[key] = newMaterial;\n return newMaterial;\n }\n};\n\nlet model = {};\n\n/**\n * @function\n * @memberof model\n * @description Loads Collada file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadDAE = async function (filePath) {\n const loader = new three_addons_loaders_ColladaLoader_js__WEBPACK_IMPORTED_MODULE_1__.ColladaLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (dae) => {\n dae.scene.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n resolve(dae.scene);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads OBJ file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadOBJ = async function (filePath) {\n const loader = new three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_2__.OBJLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (obj) => {\n obj.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n resolve(obj);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads OBJ file with externally hosted MTL file\n * @param {string} filePath - The path of the form '/path/to/mtlFile.mtl,/path/to/objFile.obj'.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadOBJWithMTL = function (filePath) {\n let paths = filePath.split(',');\n if (paths.length != 2) {\n console.error('Expected the loading of an MTL file and an OBJ file like \"path/to/mtlFile.mtl,path/to/the/objFile.obj\" - got:', filePath);\n return Promise.reject(new Error('Invalid path format for OBJ and MTL files.'));\n }\n\n const filePathMTL = paths[0];\n const filePathOBJ = paths[1];\n\n const loadMTL = (url) =>\n new Promise((resolve, reject) => {\n const mtlLoader = new three_examples_jsm_loaders_MTLLoader_js__WEBPACK_IMPORTED_MODULE_3__.MTLLoader();\n mtlLoader.load(\n url,\n (materials) => {\n materials.preload();\n resolve(materials);\n },\n undefined,\n (error) => {\n console.error('Failed to load MTL from URL:', error);\n reject(error);\n }\n );\n });\n\n const loadOBJ = (filePath, materials) =>\n new Promise((resolve, reject) => {\n const objLoader = new three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_2__.OBJLoader();\n objLoader.setMaterials(materials);\n objLoader.load(\n filePath,\n (obj) => {\n obj.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n resolve(obj);\n },\n undefined,\n (error) => {\n console.error('Failed to load OBJ:', error);\n reject(error);\n }\n );\n });\n\n return loadMTL(filePathMTL)\n .then((materials) => loadOBJ(filePathOBJ, materials))\n .catch((error) => {\n console.error('An error occurred while loading OBJ with external MTL:', error);\n throw error;\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads FBX file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadFBX = async function (filePath) {\n const loader = new three_addons_loaders_FBXLoader_js__WEBPACK_IMPORTED_MODULE_4__.FBXLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (fbx) => {\n fbx.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n resolve(fbx);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads GLTF/GLB file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadGLTF = async function (filePath) {\n const loader = new three_examples_jsm_loaders_GLTFLoader_js__WEBPACK_IMPORTED_MODULE_5__.GLTFLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (gltf) => {\n const scene = gltf.scene;\n scene.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n const animations = gltf.animations;\n resolve({ scene, animations });\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads STL file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadSTL = async function (filePath) {\n const loader = new three_examples_jsm_loaders_STLLoader_js__WEBPACK_IMPORTED_MODULE_6__.STLLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (geometry) => {\n const mesh = new three__WEBPACK_IMPORTED_MODULE_0__.Mesh(geometry, getOrCreateMaterial(new three__WEBPACK_IMPORTED_MODULE_0__.MeshPhongMaterial()));\n resolve(mesh);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads USD/USDZ file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadUSDZ = async function (filePath) {\n const usdzLoader = new three_examples_jsm_loaders_USDZLoader_js__WEBPACK_IMPORTED_MODULE_7__.USDZLoader();\n\n return usdzLoader.loadAsync(filePath)\n .then((model) => {\n model.traverse((child) => {\n if (child.isMesh) {\n child.material = getOrCreateMaterial(child.material);\n }\n });\n return model;\n })\n .catch((error) => {\n console.error(error);\n return null;\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description The main loading function\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @param {string} extension - The extension of the file type. Current allowed extensions are `dae`, fbx`, `glb`, `obj`, and `stl`.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadModel = async function (filePath, extension) {\n switch (extension) {\n case 'fbx':\n return model.loadFBX(filePath);\n case 'glb':\n case 'gltf':\n return model.loadGLTF(filePath);\n case 'stl':\n return model.loadSTL(filePath);\n case 'obj':\n if (filePath.includes(',')) {\n return model.loadOBJWithMTL(filePath);\n } else {\n return model.loadOBJ(filePath);\n }\n case 'dae':\n return model.loadDAE(filePath);\n case 'usdz':\n case 'usdc':\n return model.loadUSDZ(filePath);\n default:\n console.error(`ERR: the extensions ${extension} is not supported by MR.js`);\n return null;\n }\n};\n\nmodel.disposeObject3D = function (parentObject3D) {\n parentObject3D.traverse(function (node) {\n if (node.isMesh) {\n if (node.geometry) {\n node.geometry.dispose();\n }\n if (node.material) {\n if (Array.isArray(node.material)) {\n node.material.forEach((material) => material.dispose());\n } else {\n node.material.dispose();\n }\n }\n }\n });\n};\n\nmodel.removeObject3DFromScene = function (object3D, scene) {\n model.disposeObject3D(object3D);\n scene.remove(object3D);\n};\n\nmodel.currentRunningAnimationClip = function (entity) {\n if (!entity.mixer) {\n console.log('No mixer found for :', entity);\n return;\n }\n if (!entity.mixer._actions.some((action) => action.isRunning())) {\n console.log('No animation is currently playing');\n return;\n }\n for (let i = 0; i < entity.mixer._actions.length; i++) {\n let clipAction = entity._actions[i];\n if (clipAction.isRunning()) {\n let clipName = clipAction.getClip().name;\n console.log(\"Animation '\" + clipName + \"' is currently playing\");\n }\n }\n};\n\n\n\n\n//# sourceURL=webpack://mrjs/./src/utils/Model.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ model: () => (/* binding */ model)\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 three_addons_loaders_ColladaLoader_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! three/addons/loaders/ColladaLoader.js */ \"./node_modules/three/examples/jsm/loaders/ColladaLoader.js\");\n/* harmony import */ var three_addons_loaders_FBXLoader_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! three/addons/loaders/FBXLoader.js */ \"./node_modules/three/examples/jsm/loaders/FBXLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_GLTFLoader_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! three/examples/jsm/loaders/GLTFLoader.js */ \"./node_modules/three/examples/jsm/loaders/GLTFLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_STLLoader_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! three/examples/jsm/loaders/STLLoader.js */ \"./node_modules/three/examples/jsm/loaders/STLLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_USDZLoader_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! three/examples/jsm/loaders/USDZLoader.js */ \"./node_modules/three/examples/jsm/loaders/USDZLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! three/examples/jsm/loaders/OBJLoader.js */ \"./node_modules/three/examples/jsm/loaders/OBJLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_MTLLoader_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! three/examples/jsm/loaders/MTLLoader.js */ \"./node_modules/three/examples/jsm/loaders/MTLLoader.js\");\n\n\n\n\n\n\n\n\n\nlet model = {};\n\n/**\n * @function\n * @memberof model\n * @description Loads Collada file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadDAE = async function (filePath) {\n const loader = new three_addons_loaders_ColladaLoader_js__WEBPACK_IMPORTED_MODULE_0__.ColladaLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (dae) => {\n dae.scene.traverse((child) => {\n if (child.isMesh) {\n child.material = mrjsUtils.material.getOrCreateMaterial(child.material);\n }\n });\n resolve(dae.scene);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads OBJ file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadOBJ = async function (filePath) {\n const loader = new three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_1__.OBJLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (obj) => {\n obj.traverse((child) => {\n if (child.isMesh) {\n child.material = mrjsUtils.material.getOrCreateMaterial(child.material);\n }\n });\n resolve(obj);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads OBJ file with externally hosted MTL file\n * @param {string} filePath - The path of the form '/path/to/mtlFile.mtl,/path/to/objFile.obj'.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadOBJWithMTL = function (filePath) {\n let paths = filePath.split(',');\n if (paths.length != 2) {\n console.error('Expected the loading of an MTL file and an OBJ file like \"path/to/mtlFile.mtl,path/to/the/objFile.obj\" - got:', filePath);\n return Promise.reject(new Error('Invalid path format for OBJ and MTL files.'));\n }\n\n const filePathMTL = paths[0];\n const filePathOBJ = paths[1];\n\n const loadMTL = (url) =>\n new Promise((resolve, reject) => {\n const mtlLoader = new three_examples_jsm_loaders_MTLLoader_js__WEBPACK_IMPORTED_MODULE_2__.MTLLoader();\n mtlLoader.load(\n url,\n (materials) => {\n materials.preload();\n resolve(materials);\n },\n undefined,\n (error) => {\n console.error('Failed to load MTL from URL:', error);\n reject(error);\n }\n );\n });\n\n const loadOBJ = (filePath, materials) =>\n new Promise((resolve, reject) => {\n const objLoader = new three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_1__.OBJLoader();\n objLoader.setMaterials(materials);\n objLoader.load(\n filePath,\n (obj) => {\n obj.traverse((child) => {\n if (child.isMesh) {\n child.material = mrjsUtils.material.getOrCreateMaterial(child.material);\n }\n });\n resolve(obj);\n },\n undefined,\n (error) => {\n console.error('Failed to load OBJ:', error);\n reject(error);\n }\n );\n });\n\n return loadMTL(filePathMTL)\n .then((materials) => loadOBJ(filePathOBJ, materials))\n .catch((error) => {\n console.error('An error occurred while loading OBJ with external MTL:', error);\n throw error;\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads FBX file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadFBX = async function (filePath) {\n const loader = new three_addons_loaders_FBXLoader_js__WEBPACK_IMPORTED_MODULE_3__.FBXLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (fbx) => {\n fbx.traverse((child) => {\n if (child.isMesh) {\n child.material = mrjsUtils.material.getOrCreateMaterial(child.material);\n }\n });\n resolve(fbx);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads GLTF/GLB file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadGLTF = async function (filePath) {\n const loader = new three_examples_jsm_loaders_GLTFLoader_js__WEBPACK_IMPORTED_MODULE_4__.GLTFLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (gltf) => {\n const scene = gltf.scene;\n scene.traverse((child) => {\n if (child.isMesh) {\n child.material = mrjsUtils.material.getOrCreateMaterial(child.material);\n }\n });\n const animations = gltf.animations;\n resolve({ scene, animations });\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads STL file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadSTL = async function (filePath) {\n const loader = new three_examples_jsm_loaders_STLLoader_js__WEBPACK_IMPORTED_MODULE_5__.STLLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (geometry) => {\n const mesh = new three__WEBPACK_IMPORTED_MODULE_6__.Mesh(geometry, mrjsUtils.material.getOrCreateMaterial(new three__WEBPACK_IMPORTED_MODULE_6__.MeshPhongMaterial()));\n resolve(mesh);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads USD/USDZ file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadUSDZ = async function (filePath) {\n const usdzLoader = new three_examples_jsm_loaders_USDZLoader_js__WEBPACK_IMPORTED_MODULE_7__.USDZLoader();\n\n return usdzLoader.loadAsync(filePath)\n .then((model) => {\n model.traverse((child) => {\n if (child.isMesh) {\n child.material = mrjsUtils.material.getOrCreateMaterial(child.material);\n }\n });\n return model;\n })\n .catch((error) => {\n console.error(error);\n return null;\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description The main loading function\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @param {string} extension - The extension of the file type. Current allowed extensions are `dae`, fbx`, `glb`, `obj`, and `stl`.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadModel = async function (filePath, extension) {\n switch (extension) {\n case 'fbx':\n return model.loadFBX(filePath);\n case 'glb':\n case 'gltf':\n return model.loadGLTF(filePath);\n case 'stl':\n return model.loadSTL(filePath);\n case 'obj':\n if (filePath.includes(',')) {\n return model.loadOBJWithMTL(filePath);\n } else {\n return model.loadOBJ(filePath);\n }\n case 'dae':\n return model.loadDAE(filePath);\n case 'usdz':\n case 'usdc':\n return model.loadUSDZ(filePath);\n default:\n console.error(`ERR: the extensions ${extension} is not supported by MR.js`);\n return null;\n }\n};\n\nmodel.disposeObject3D = function (parentObject3D) {\n parentObject3D.traverse(function (node) {\n if (node.isMesh) {\n if (node.geometry) {\n node.geometry.dispose();\n }\n if (node.material) {\n if (Array.isArray(node.material)) {\n node.material.forEach((material) => material.dispose());\n } else {\n node.material.dispose();\n }\n }\n }\n });\n};\n\nmodel.removeObject3DFromScene = function (object3D, scene) {\n model.disposeObject3D(object3D);\n scene.remove(object3D);\n};\n\nmodel.currentRunningAnimationClip = function (entity) {\n if (!entity.mixer) {\n console.log('No mixer found for :', entity);\n return;\n }\n if (!entity.mixer._actions.some((action) => action.isRunning())) {\n console.log('No animation is currently playing');\n return;\n }\n for (let i = 0; i < entity.mixer._actions.length; i++) {\n let clipAction = entity._actions[i];\n if (clipAction.isRunning()) {\n let clipName = clipAction.getClip().name;\n console.log(\"Animation '\" + clipName + \"' is currently playing\");\n }\n }\n};\n\n\n\n\n//# sourceURL=webpack://mrjs/./src/utils/Model.js?"); /***/ }), diff --git a/samples/examples/testing.html b/samples/examples/testing.html index 3a38d6ef..5f132e52 100644 --- a/samples/examples/testing.html +++ b/samples/examples/testing.html @@ -27,14 +27,13 @@ width: 300px; height: 300px; z-index: 70; - scale: 3.5; } - + diff --git a/src/utils/Material.js b/src/utils/Material.js index 029099f3..d2d6616c 100644 --- a/src/utils/Material.js +++ b/src/utils/Material.js @@ -20,6 +20,44 @@ material.MeshBasicMaterial = new THREE.MeshBasicMaterial(); material.MeshPhongMaterial = new THREE.MeshPhongMaterial(); material.MeshStandardMaterial = new THREE.MeshStandardMaterial(); +// // Singleton material pool for reuse +// const materialPool = {}; + +// /** +// * @function getMaterialKey +// * @description Generates a unique key for a material based on its properties +// * @param {THREE.Material} material - The material to generate a key for +// * @returns {string} - The generated key +// */ +// function getMaterialKey(material) { +// let key = material.type; +// for (const [property, value] of Object.entries(material)) { +// if (value instanceof THREE.Color || value instanceof THREE.Texture) { +// key += `|${property}:${value.uuid}`; +// } else if (typeof value !== 'object') { +// key += `|${property}:${value}`; +// } +// } +// return key; +// }; + +// /** +// * @function getOrCreateMaterial +// * @description Reuses or creates a new material based on its properties +// * @param {THREE.Material} material - The material to reuse or create +// * @returns {THREE.Material} - The reused or newly created material +// */ +// material.getOrCreateMaterial = (material) => { +// const key = getMaterialKey(material); +// if (materialPool[key]) { +// return materialPool[key]; +// } else { +// const newMaterial = material.clone(); +// materialPool[key] = newMaterial; +// return newMaterial; +// } +// }; + /** * @function * @memberof material diff --git a/src/utils/Model.js b/src/utils/Model.js index da1e9974..aa5dd273 100644 --- a/src/utils/Model.js +++ b/src/utils/Model.js @@ -7,51 +7,34 @@ import { USDZLoader } from 'three/examples/jsm/loaders/USDZLoader.js'; import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'; import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js'; -// Singleton material pool for reuse -const materialPool = {}; +// Keeping the below imports in as reference for future items we can add. +// import { AMFLoader } from 'three/addons/loaders/AMFLoader.js'; +// import { BVHLoader } from 'three/addons/loaders/BVHLoader.js'; +// import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'; +// import { GCodeLoader } from 'three/addons/loaders/GCodeLoader.js'; +// import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; +// // import { IFCLoader } from 'web-ifc-three'; +// // import { IFCSPACE } from 'web-ifc'; +// import { Rhino3dmLoader } from 'three/addons/loaders/3DMLoader.js'; +// import { PCDLoader } from 'three/addons/loaders/PCDLoader.js'; +// import { PDBLoader } from 'three/addons/loaders/PDBLoader.js'; +// import { PLYLoader } from 'three/addons/loaders/PLYLoader.js'; +// import { SVGLoader } from 'three/addons/loaders/SVGLoader.js'; +// import { TDSLoader } from 'three/addons/loaders/TDSLoader.js'; +// import { ThreeMFLoader } from 'three/addons/loaders/3MFLoader.js'; /** - * @function getMaterialKey - * @description Generates a unique key for a material based on its properties - * @param {THREE.Material} material - The material to generate a key for - * @returns {string} - The generated key + * @namespace model + * @description Useful namespace for helping with Model utility functions */ -const getMaterialKey = (material) => { - let key = material.type; - for (const [property, value] of Object.entries(material)) { - if (value instanceof THREE.Color || value instanceof THREE.Texture) { - key += `|${property}:${value.uuid}`; - } else if (typeof value !== 'object') { - key += `|${property}:${value}`; - } - } - return key; -}; - -/** - * @function getOrCreateMaterial - * @description Reuses or creates a new material based on its properties - * @param {THREE.Material} material - The material to reuse or create - * @returns {THREE.Material} - The reused or newly created material - */ -const getOrCreateMaterial = (material) => { - const key = getMaterialKey(material); - if (materialPool[key]) { - return materialPool[key]; - } else { - const newMaterial = material.clone(); - materialPool[key] = newMaterial; - return newMaterial; - } -}; - let model = {}; /** * @function * @memberof model * @description Loads Collada file - * @param {string} filePath - The path to the file(s) needing to be loaded. + * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports + * the full path and the relative path directly to the file. * @returns {Promise} - the promise of the loaded mesh object. */ model.loadDAE = async function (filePath) { @@ -61,11 +44,6 @@ model.loadDAE = async function (filePath) { loader.load( filePath, (dae) => { - dae.scene.traverse((child) => { - if (child.isMesh) { - child.material = getOrCreateMaterial(child.material); - } - }); resolve(dae.scene); }, undefined, @@ -81,7 +59,8 @@ model.loadDAE = async function (filePath) { * @function * @memberof model * @description Loads OBJ file - * @param {string} filePath - The path to the file(s) needing to be loaded. + * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports + * the full path and the relative path directly to the file. * @returns {Promise} - the promise of the loaded mesh object. */ model.loadOBJ = async function (filePath) { @@ -91,11 +70,6 @@ model.loadOBJ = async function (filePath) { loader.load( filePath, (obj) => { - obj.traverse((child) => { - if (child.isMesh) { - child.material = getOrCreateMaterial(child.material); - } - }); resolve(obj); }, undefined, @@ -116,6 +90,7 @@ model.loadOBJ = async function (filePath) { */ model.loadOBJWithMTL = function (filePath) { let paths = filePath.split(','); + // Assigning each path to a variable if (paths.length != 2) { console.error('Expected the loading of an MTL file and an OBJ file like "path/to/mtlFile.mtl,path/to/the/objFile.obj" - got:', filePath); return Promise.reject(new Error('Invalid path format for OBJ and MTL files.')); @@ -148,11 +123,6 @@ model.loadOBJWithMTL = function (filePath) { objLoader.load( filePath, (obj) => { - obj.traverse((child) => { - if (child.isMesh) { - child.material = getOrCreateMaterial(child.material); - } - }); resolve(obj); }, undefined, @@ -167,7 +137,7 @@ model.loadOBJWithMTL = function (filePath) { .then((materials) => loadOBJ(filePathOBJ, materials)) .catch((error) => { console.error('An error occurred while loading OBJ with external MTL:', error); - throw error; + throw error; // Ensure errors are propagated }); }; @@ -175,7 +145,8 @@ model.loadOBJWithMTL = function (filePath) { * @function * @memberof model * @description Loads FBX file - * @param {string} filePath - The path to the file(s) needing to be loaded. + * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports + * the full path and the relative path directly to the file. * @returns {Promise} - the promise of the loaded mesh object. */ model.loadFBX = async function (filePath) { @@ -185,11 +156,6 @@ model.loadFBX = async function (filePath) { loader.load( filePath, (fbx) => { - fbx.traverse((child) => { - if (child.isMesh) { - child.material = getOrCreateMaterial(child.material); - } - }); resolve(fbx); }, undefined, @@ -205,7 +171,8 @@ model.loadFBX = async function (filePath) { * @function * @memberof model * @description Loads GLTF/GLB file - * @param {string} filePath - The path to the file(s) needing to be loaded. + * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports + * the full path and the relative path directly to the file. * @returns {Promise} - the promise of the loaded mesh object. */ model.loadGLTF = async function (filePath) { @@ -216,12 +183,9 @@ model.loadGLTF = async function (filePath) { filePath, (gltf) => { const scene = gltf.scene; - scene.traverse((child) => { - if (child.isMesh) { - child.material = getOrCreateMaterial(child.material); - } - }); const animations = gltf.animations; + + // Resolve the promise with the loaded scene and animations resolve({ scene, animations }); }, undefined, @@ -236,8 +200,9 @@ model.loadGLTF = async function (filePath) { /** * @function * @memberof model - * @description Loads STL file - * @param {string} filePath - The path to the file(s) needing to be loaded. + * @description Loads stl file + * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports + * the full path and the relative path directly to the file. * @returns {Promise} - the promise of the loaded mesh object. */ model.loadSTL = async function (filePath) { @@ -247,13 +212,17 @@ model.loadSTL = async function (filePath) { loader.load( filePath, (geometry) => { - const mesh = new THREE.Mesh(geometry, getOrCreateMaterial(new THREE.MeshPhongMaterial())); - resolve(mesh); + const material = new THREE.MeshPhongMaterial(); + const mesh = new THREE.Mesh(geometry, material); + + resolve(mesh); // Resolve the promise with the loaded mesh + }, + (xhr) => { + // Progress callback }, - undefined, (error) => { console.error(error); - reject(error); + reject(error); // Reject the promise if there's an error } ); }); @@ -263,59 +232,63 @@ model.loadSTL = async function (filePath) { * @function * @memberof model * @description Loads USD/USDZ file - * @param {string} filePath - The path to the file(s) needing to be loaded. + * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports + * the full path and the relative path directly to the file. * @returns {Promise} - the promise of the loaded mesh object. */ model.loadUSDZ = async function (filePath) { const usdzLoader = new USDZLoader(); - return usdzLoader.loadAsync(filePath) - .then((model) => { - model.traverse((child) => { - if (child.isMesh) { - child.material = getOrCreateMaterial(child.material); - } - }); - return model; - }) - .catch((error) => { - console.error(error); - return null; - }); + const [model] = await Promise.all([usdzLoader.loadAsync(filePath)], undefined, (error) => { + console.error(error); + return null; + }); + + return model; }; +/// //////////////////////// +// Main Loading Function // +/// //////////////////////// + /** * @function * @memberof model * @description The main loading function - * @param {string} filePath - The path to the file(s) needing to be loaded. + * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports + * the full path and the relative path directly to the file. * @param {string} extension - The extension of the file type. Current allowed extensions are `dae`, fbx`, `glb`, `obj`, and `stl`. * @returns {Promise} - the promise of the loaded mesh object. */ model.loadModel = async function (filePath, extension) { - switch (extension) { - case 'fbx': - return model.loadFBX(filePath); - case 'glb': - case 'gltf': - return model.loadGLTF(filePath); - case 'stl': - return model.loadSTL(filePath); - case 'obj': - if (filePath.includes(',')) { - return model.loadOBJWithMTL(filePath); - } else { - return model.loadOBJ(filePath); - } - case 'dae': - return model.loadDAE(filePath); - case 'usdz': - case 'usdc': - return model.loadUSDZ(filePath); - default: - console.error(`ERR: the extensions ${extension} is not supported by MR.js`); - return null; + // Flag used for debugging the ones that are only 'partially implemented' and + // still as todos. + const allowed = false; + + if (extension == 'fbx') { + return model.loadFBX(filePath); + } else if (extension == 'glb') { + return model.loadGLTF(filePath); + } else if (allowed && extension == 'gltf') { + // TODO + return model.loadGLTF(filePath); + } else if (extension == 'stl') { + return model.loadSTL(filePath); + } else if (extension == 'obj') { + if (filePath.includes(',')) { + // has a preceeding material file + return model.loadOBJWithMTL(filePath); + } else { + return model.loadOBJ(filePath); + } + } else if (extension == 'dae') { + return model.loadDAE(filePath); + } else if (allowed && (extension == 'usdc' || extension == 'usdz')) { + // TODO + return model.loadUSDZ(filePath); } + console.error(`ERR: the extensions ${extension} is not supported by MR.js`); + return null; }; model.disposeObject3D = function (parentObject3D) { @@ -324,10 +297,13 @@ model.disposeObject3D = function (parentObject3D) { if (node.geometry) { node.geometry.dispose(); } + if (node.material) { - if (Array.isArray(node.material)) { + if (node.material instanceof Array) { + // An array of materials node.material.forEach((material) => material.dispose()); } else { + // A single material node.material.dispose(); } } @@ -338,6 +314,8 @@ model.disposeObject3D = function (parentObject3D) { model.removeObject3DFromScene = function (object3D, scene) { model.disposeObject3D(object3D); scene.remove(object3D); + + // Optional: Clean up references for GC if necessary }; model.currentRunningAnimationClip = function (entity) { @@ -345,15 +323,20 @@ model.currentRunningAnimationClip = function (entity) { console.log('No mixer found for :', entity); return; } + // If no animation is currently playing if (!entity.mixer._actions.some((action) => action.isRunning())) { console.log('No animation is currently playing'); return; } + + // Iterate over all clip actions in the mixer for (let i = 0; i < entity.mixer._actions.length; i++) { let clipAction = entity._actions[i]; if (clipAction.isRunning()) { let clipName = clipAction.getClip().name; console.log("Animation '" + clipName + "' is currently playing"); + // You can do whatever you need with this information + // break; // Break the loop if you only want to know the first running animation } } }; From d808d151bea07aea1ab2a2f5d709ac4bfbcf0fb1 Mon Sep 17 00:00:00 2001 From: hanbollar Date: Thu, 16 May 2024 17:46:27 -0700 Subject: [PATCH 09/12] clipping and frustum culling Signed-off-by: hanbollar --- src/core/MRApp.js | 1 + src/core/componentSystems/ClippingSystem.js | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/MRApp.js b/src/core/MRApp.js index a5473807..0f8db113 100644 --- a/src/core/MRApp.js +++ b/src/core/MRApp.js @@ -262,6 +262,7 @@ export class MRApp extends MRElement { this.renderer.toneMapping = THREE.ACESFilmicToneMapping; this.renderer.toneMappingExposure = 1; this.renderer.localClippingEnabled = true; + this.renderer.setFrustumCulling(true); this.appendChild(this.renderer.domElement); 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); + } } } From 3f3dcab791af3b67de042e171c37ce7becb63dba Mon Sep 17 00:00:00 2001 From: hanbollar Date: Thu, 16 May 2024 17:53:08 -0700 Subject: [PATCH 10/12] update Signed-off-by: hanbollar --- dist/mr.js | 8 ++++---- src/core/MRApp.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dist/mr.js b/dist/mr.js index 09337c43..f461d3da 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// import * as SPECTOR from 'spectorjs';\n// let spector = new SPECTOR.Spector();\n// spector.displayUI();\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) {//} && 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.scene.traverse((object) => {\n // if (object.isMesh) {\n // console.log(`Rendering `, object, `name: ${object.name} with num children: ${object.children.length} with material ${object.material.name}`);\n // }\n // });\n\n this.renderer.render(this.scene, this.camera);\n\n // Log the number of draw calls\n console.log(this.renderer.info);\n console.log('NumDrawCalls:', this.renderer.info.render.calls, 'should be 2xNumGLPrograms(', this.renderer.info.programs.length, ') = ', 2*this.renderer.info.programs.length);\n // this.renderer.info.programs.forEach(program => {\n // console.log(`Program ID: ${program.id}, Linked Material: ${yourCustomMapping[program.id] || 'Unknown'}`);\n // });\n // console.log(this.renderer.info);\n if (this.renderer.info.programs) {\n // this.renderer.info.programs.forEach(program => {\n // console.log(`Program: `, program, `Used times in last frame: ${program.usedTimes}`);\n // });\n // function printSceneObjectsAndMaterials(scene, renderer) {\n // let groupedByMaterial = {};\n\n // // Traverse the scene and group objects by material UUID\n // scene.traverse(function (object) {\n // if (object.isMesh && object.material) {\n // const uuid = object.material.uuid;\n // if (!groupedByMaterial[uuid]) {\n // groupedByMaterial[uuid] = []; // Initialize array if it doesn't exist\n // }\n // groupedByMaterial[uuid].push({\n // objectName: object.name,\n // objectType: object.type\n // });\n // }\n // });\n\n // Log details about each group\n // Object.keys(groupedByMaterial).forEach(uuid => {\n // console.log(`Material UUID: ${uuid}, num items: ${groupedByMaterial[uuid].length}`);\n // groupedByMaterial[uuid].forEach(entry => {\n // console.log(`Object: ${entry.objectName} | Type: ${entry.objectType}`);\n // });\n // });\n\n // // Then, log all active WebGL programs separately.\n // if (renderer.info.programs) {\n // renderer.info.programs.forEach(program => {\n // console.log(`Program ID: ${program.id}, Program Info:`, program);\n // });\n // }\n // }\n\n // Call this function where appropriate in your application\n // printSceneObjectsAndMaterials(this.scene, this.renderer);\n\n }\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// import * as SPECTOR from 'spectorjs';\n// let spector = new SPECTOR.Spector();\n// spector.displayUI();\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 this.renderer.setFrustumCulling(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.scene.traverse((object) => {\n // if (object.isMesh) {\n // console.log(`Rendering `, object, `name: ${object.name} with num children: ${object.children.length} with material ${object.material.name}`);\n // }\n // });\n\n this.renderer.render(this.scene, this.camera);\n\n // Log the number of draw calls\n console.log(this.renderer.info);\n console.log('NumDrawCalls:', this.renderer.info.render.calls, 'should be 2xNumGLPrograms(', this.renderer.info.programs.length, ') = ', 2*this.renderer.info.programs.length);\n // this.renderer.info.programs.forEach(program => {\n // console.log(`Program ID: ${program.id}, Linked Material: ${yourCustomMapping[program.id] || 'Unknown'}`);\n // });\n // console.log(this.renderer.info);\n if (this.renderer.info.programs) {\n // this.renderer.info.programs.forEach(program => {\n // console.log(`Program: `, program, `Used times in last frame: ${program.usedTimes}`);\n // });\n // function printSceneObjectsAndMaterials(scene, renderer) {\n // let groupedByMaterial = {};\n\n // // Traverse the scene and group objects by material UUID\n // scene.traverse(function (object) {\n // if (object.isMesh && object.material) {\n // const uuid = object.material.uuid;\n // if (!groupedByMaterial[uuid]) {\n // groupedByMaterial[uuid] = []; // Initialize array if it doesn't exist\n // }\n // groupedByMaterial[uuid].push({\n // objectName: object.name,\n // objectType: object.type\n // });\n // }\n // });\n\n // Log details about each group\n // Object.keys(groupedByMaterial).forEach(uuid => {\n // console.log(`Material UUID: ${uuid}, num items: ${groupedByMaterial[uuid].length}`);\n // groupedByMaterial[uuid].forEach(entry => {\n // console.log(`Object: ${entry.objectName} | Type: ${entry.objectType}`);\n // });\n // });\n\n // // Then, log all active WebGL programs separately.\n // if (renderer.info.programs) {\n // renderer.info.programs.forEach(program => {\n // console.log(`Program ID: ${program.id}, Program Info:`, program);\n // });\n // }\n // }\n\n // Call this function where appropriate in your application\n // printSceneObjectsAndMaterials(this.scene, this.renderer);\n\n }\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?"); /***/ }), @@ -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 * 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// Singleton material pool for reuse\nconst materialPool = {};\n\n/**\n * @function getMaterialKey\n * @description Generates a unique key for a material based on its properties\n * @param {THREE.Material} material - The material to generate a key for\n * @returns {string} - The generated key\n */\nconst getMaterialKey = (material) => {\n let key = material.type;\n for (const [property, value] of Object.entries(material)) {\n if (value instanceof three__WEBPACK_IMPORTED_MODULE_1__.Color || value instanceof three__WEBPACK_IMPORTED_MODULE_1__.Texture) {\n key += `|${property}:${value.uuid}`;\n } else if (typeof value !== 'object') {\n key += `|${property}:${value}`;\n }\n }\n return key;\n};\n\n/**\n * @function getOrCreateMaterial\n * @description Reuses or creates a new material based on its properties\n * @param {THREE.Material} material - The material to reuse or create\n * @returns {THREE.Material} - The reused or newly created material\n */\nconst getOrCreateMaterial = (material) => {\n const key = getMaterialKey(material);\n if (materialPool[key]) {\n return materialPool[key];\n } else {\n const newMaterial = material.clone();\n materialPool[key] = newMaterial;\n return newMaterial;\n }\n};\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// // Singleton material pool for reuse\n// const materialPool = {};\n\n// /**\n// * @function getMaterialKey\n// * @description Generates a unique key for a material based on its properties\n// * @param {THREE.Material} material - The material to generate a key for\n// * @returns {string} - The generated key\n// */\n// function getMaterialKey(material) {\n// let key = material.type;\n// for (const [property, value] of Object.entries(material)) {\n// if (value instanceof THREE.Color || value instanceof THREE.Texture) {\n// key += `|${property}:${value.uuid}`;\n// } else if (typeof value !== 'object') {\n// key += `|${property}:${value}`;\n// }\n// }\n// return key;\n// };\n\n// /**\n// * @function getOrCreateMaterial\n// * @description Reuses or creates a new material based on its properties\n// * @param {THREE.Material} material - The material to reuse or create\n// * @returns {THREE.Material} - The reused or newly created material\n// */\n// material.getOrCreateMaterial = (material) => {\n// const key = getMaterialKey(material);\n// if (materialPool[key]) {\n// return materialPool[key];\n// } else {\n// const newMaterial = material.clone();\n// materialPool[key] = newMaterial;\n// return newMaterial;\n// }\n// };\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?"); /***/ }), @@ -1047,7 +1047,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 */ model: () => (/* binding */ model)\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 three_addons_loaders_ColladaLoader_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! three/addons/loaders/ColladaLoader.js */ \"./node_modules/three/examples/jsm/loaders/ColladaLoader.js\");\n/* harmony import */ var three_addons_loaders_FBXLoader_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! three/addons/loaders/FBXLoader.js */ \"./node_modules/three/examples/jsm/loaders/FBXLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_GLTFLoader_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! three/examples/jsm/loaders/GLTFLoader.js */ \"./node_modules/three/examples/jsm/loaders/GLTFLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_STLLoader_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! three/examples/jsm/loaders/STLLoader.js */ \"./node_modules/three/examples/jsm/loaders/STLLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_USDZLoader_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! three/examples/jsm/loaders/USDZLoader.js */ \"./node_modules/three/examples/jsm/loaders/USDZLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! three/examples/jsm/loaders/OBJLoader.js */ \"./node_modules/three/examples/jsm/loaders/OBJLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_MTLLoader_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! three/examples/jsm/loaders/MTLLoader.js */ \"./node_modules/three/examples/jsm/loaders/MTLLoader.js\");\n\n\n\n\n\n\n\n\n\nlet model = {};\n\n/**\n * @function\n * @memberof model\n * @description Loads Collada file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadDAE = async function (filePath) {\n const loader = new three_addons_loaders_ColladaLoader_js__WEBPACK_IMPORTED_MODULE_0__.ColladaLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (dae) => {\n dae.scene.traverse((child) => {\n if (child.isMesh) {\n child.material = mrjsUtils.material.getOrCreateMaterial(child.material);\n }\n });\n resolve(dae.scene);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads OBJ file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadOBJ = async function (filePath) {\n const loader = new three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_1__.OBJLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (obj) => {\n obj.traverse((child) => {\n if (child.isMesh) {\n child.material = mrjsUtils.material.getOrCreateMaterial(child.material);\n }\n });\n resolve(obj);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads OBJ file with externally hosted MTL file\n * @param {string} filePath - The path of the form '/path/to/mtlFile.mtl,/path/to/objFile.obj'.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadOBJWithMTL = function (filePath) {\n let paths = filePath.split(',');\n if (paths.length != 2) {\n console.error('Expected the loading of an MTL file and an OBJ file like \"path/to/mtlFile.mtl,path/to/the/objFile.obj\" - got:', filePath);\n return Promise.reject(new Error('Invalid path format for OBJ and MTL files.'));\n }\n\n const filePathMTL = paths[0];\n const filePathOBJ = paths[1];\n\n const loadMTL = (url) =>\n new Promise((resolve, reject) => {\n const mtlLoader = new three_examples_jsm_loaders_MTLLoader_js__WEBPACK_IMPORTED_MODULE_2__.MTLLoader();\n mtlLoader.load(\n url,\n (materials) => {\n materials.preload();\n resolve(materials);\n },\n undefined,\n (error) => {\n console.error('Failed to load MTL from URL:', error);\n reject(error);\n }\n );\n });\n\n const loadOBJ = (filePath, materials) =>\n new Promise((resolve, reject) => {\n const objLoader = new three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_1__.OBJLoader();\n objLoader.setMaterials(materials);\n objLoader.load(\n filePath,\n (obj) => {\n obj.traverse((child) => {\n if (child.isMesh) {\n child.material = mrjsUtils.material.getOrCreateMaterial(child.material);\n }\n });\n resolve(obj);\n },\n undefined,\n (error) => {\n console.error('Failed to load OBJ:', error);\n reject(error);\n }\n );\n });\n\n return loadMTL(filePathMTL)\n .then((materials) => loadOBJ(filePathOBJ, materials))\n .catch((error) => {\n console.error('An error occurred while loading OBJ with external MTL:', error);\n throw error;\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads FBX file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadFBX = async function (filePath) {\n const loader = new three_addons_loaders_FBXLoader_js__WEBPACK_IMPORTED_MODULE_3__.FBXLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (fbx) => {\n fbx.traverse((child) => {\n if (child.isMesh) {\n child.material = mrjsUtils.material.getOrCreateMaterial(child.material);\n }\n });\n resolve(fbx);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads GLTF/GLB file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadGLTF = async function (filePath) {\n const loader = new three_examples_jsm_loaders_GLTFLoader_js__WEBPACK_IMPORTED_MODULE_4__.GLTFLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (gltf) => {\n const scene = gltf.scene;\n scene.traverse((child) => {\n if (child.isMesh) {\n child.material = mrjsUtils.material.getOrCreateMaterial(child.material);\n }\n });\n const animations = gltf.animations;\n resolve({ scene, animations });\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads STL file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadSTL = async function (filePath) {\n const loader = new three_examples_jsm_loaders_STLLoader_js__WEBPACK_IMPORTED_MODULE_5__.STLLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (geometry) => {\n const mesh = new three__WEBPACK_IMPORTED_MODULE_6__.Mesh(geometry, mrjsUtils.material.getOrCreateMaterial(new three__WEBPACK_IMPORTED_MODULE_6__.MeshPhongMaterial()));\n resolve(mesh);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads USD/USDZ file\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadUSDZ = async function (filePath) {\n const usdzLoader = new three_examples_jsm_loaders_USDZLoader_js__WEBPACK_IMPORTED_MODULE_7__.USDZLoader();\n\n return usdzLoader.loadAsync(filePath)\n .then((model) => {\n model.traverse((child) => {\n if (child.isMesh) {\n child.material = mrjsUtils.material.getOrCreateMaterial(child.material);\n }\n });\n return model;\n })\n .catch((error) => {\n console.error(error);\n return null;\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description The main loading function\n * @param {string} filePath - The path to the file(s) needing to be loaded.\n * @param {string} extension - The extension of the file type. Current allowed extensions are `dae`, fbx`, `glb`, `obj`, and `stl`.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadModel = async function (filePath, extension) {\n switch (extension) {\n case 'fbx':\n return model.loadFBX(filePath);\n case 'glb':\n case 'gltf':\n return model.loadGLTF(filePath);\n case 'stl':\n return model.loadSTL(filePath);\n case 'obj':\n if (filePath.includes(',')) {\n return model.loadOBJWithMTL(filePath);\n } else {\n return model.loadOBJ(filePath);\n }\n case 'dae':\n return model.loadDAE(filePath);\n case 'usdz':\n case 'usdc':\n return model.loadUSDZ(filePath);\n default:\n console.error(`ERR: the extensions ${extension} is not supported by MR.js`);\n return null;\n }\n};\n\nmodel.disposeObject3D = function (parentObject3D) {\n parentObject3D.traverse(function (node) {\n if (node.isMesh) {\n if (node.geometry) {\n node.geometry.dispose();\n }\n if (node.material) {\n if (Array.isArray(node.material)) {\n node.material.forEach((material) => material.dispose());\n } else {\n node.material.dispose();\n }\n }\n }\n });\n};\n\nmodel.removeObject3DFromScene = function (object3D, scene) {\n model.disposeObject3D(object3D);\n scene.remove(object3D);\n};\n\nmodel.currentRunningAnimationClip = function (entity) {\n if (!entity.mixer) {\n console.log('No mixer found for :', entity);\n return;\n }\n if (!entity.mixer._actions.some((action) => action.isRunning())) {\n console.log('No animation is currently playing');\n return;\n }\n for (let i = 0; i < entity.mixer._actions.length; i++) {\n let clipAction = entity._actions[i];\n if (clipAction.isRunning()) {\n let clipName = clipAction.getClip().name;\n console.log(\"Animation '\" + clipName + \"' is currently playing\");\n }\n }\n};\n\n\n\n\n//# sourceURL=webpack://mrjs/./src/utils/Model.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ model: () => (/* binding */ model)\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 three_addons_loaders_ColladaLoader_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! three/addons/loaders/ColladaLoader.js */ \"./node_modules/three/examples/jsm/loaders/ColladaLoader.js\");\n/* harmony import */ var three_addons_loaders_FBXLoader_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! three/addons/loaders/FBXLoader.js */ \"./node_modules/three/examples/jsm/loaders/FBXLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_GLTFLoader_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! three/examples/jsm/loaders/GLTFLoader.js */ \"./node_modules/three/examples/jsm/loaders/GLTFLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_STLLoader_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! three/examples/jsm/loaders/STLLoader.js */ \"./node_modules/three/examples/jsm/loaders/STLLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_USDZLoader_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! three/examples/jsm/loaders/USDZLoader.js */ \"./node_modules/three/examples/jsm/loaders/USDZLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! three/examples/jsm/loaders/OBJLoader.js */ \"./node_modules/three/examples/jsm/loaders/OBJLoader.js\");\n/* harmony import */ var three_examples_jsm_loaders_MTLLoader_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! three/examples/jsm/loaders/MTLLoader.js */ \"./node_modules/three/examples/jsm/loaders/MTLLoader.js\");\n\n\n\n\n\n\n\n\n\n// Keeping the below imports in as reference for future items we can add.\n// import { AMFLoader } from 'three/addons/loaders/AMFLoader.js';\n// import { BVHLoader } from 'three/addons/loaders/BVHLoader.js';\n// import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';\n// import { GCodeLoader } from 'three/addons/loaders/GCodeLoader.js';\n// import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';\n// // import { IFCLoader } from 'web-ifc-three';\n// // import { IFCSPACE } from 'web-ifc';\n// import { Rhino3dmLoader } from 'three/addons/loaders/3DMLoader.js';\n// import { PCDLoader } from 'three/addons/loaders/PCDLoader.js';\n// import { PDBLoader } from 'three/addons/loaders/PDBLoader.js';\n// import { PLYLoader } from 'three/addons/loaders/PLYLoader.js';\n// import { SVGLoader } from 'three/addons/loaders/SVGLoader.js';\n// import { TDSLoader } from 'three/addons/loaders/TDSLoader.js';\n// import { ThreeMFLoader } from 'three/addons/loaders/3MFLoader.js';\n\n/**\n * @namespace model\n * @description Useful namespace for helping with Model utility functions\n */\nlet model = {};\n\n/**\n * @function\n * @memberof model\n * @description Loads Collada file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadDAE = async function (filePath) {\n const loader = new three_addons_loaders_ColladaLoader_js__WEBPACK_IMPORTED_MODULE_0__.ColladaLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (dae) => {\n resolve(dae.scene);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads OBJ file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadOBJ = async function (filePath) {\n const loader = new three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_1__.OBJLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (obj) => {\n resolve(obj);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads OBJ file with externally hosted MTL file\n * @param {string} filePath - The path of the form '/path/to/mtlFile.mtl,/path/to/objFile.obj'.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadOBJWithMTL = function (filePath) {\n let paths = filePath.split(',');\n // Assigning each path to a variable\n if (paths.length != 2) {\n console.error('Expected the loading of an MTL file and an OBJ file like \"path/to/mtlFile.mtl,path/to/the/objFile.obj\" - got:', filePath);\n return Promise.reject(new Error('Invalid path format for OBJ and MTL files.'));\n }\n\n const filePathMTL = paths[0];\n const filePathOBJ = paths[1];\n\n const loadMTL = (url) =>\n new Promise((resolve, reject) => {\n const mtlLoader = new three_examples_jsm_loaders_MTLLoader_js__WEBPACK_IMPORTED_MODULE_2__.MTLLoader();\n mtlLoader.load(\n url,\n (materials) => {\n materials.preload();\n resolve(materials);\n },\n undefined,\n (error) => {\n console.error('Failed to load MTL from URL:', error);\n reject(error);\n }\n );\n });\n\n const loadOBJ = (filePath, materials) =>\n new Promise((resolve, reject) => {\n const objLoader = new three_examples_jsm_loaders_OBJLoader_js__WEBPACK_IMPORTED_MODULE_1__.OBJLoader();\n objLoader.setMaterials(materials);\n objLoader.load(\n filePath,\n (obj) => {\n resolve(obj);\n },\n undefined,\n (error) => {\n console.error('Failed to load OBJ:', error);\n reject(error);\n }\n );\n });\n\n return loadMTL(filePathMTL)\n .then((materials) => loadOBJ(filePathOBJ, materials))\n .catch((error) => {\n console.error('An error occurred while loading OBJ with external MTL:', error);\n throw error; // Ensure errors are propagated\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads FBX file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadFBX = async function (filePath) {\n const loader = new three_addons_loaders_FBXLoader_js__WEBPACK_IMPORTED_MODULE_3__.FBXLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (fbx) => {\n resolve(fbx);\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads GLTF/GLB file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadGLTF = async function (filePath) {\n const loader = new three_examples_jsm_loaders_GLTFLoader_js__WEBPACK_IMPORTED_MODULE_4__.GLTFLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (gltf) => {\n const scene = gltf.scene;\n const animations = gltf.animations;\n\n // Resolve the promise with the loaded scene and animations\n resolve({ scene, animations });\n },\n undefined,\n (error) => {\n console.error(error);\n reject(error);\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads stl file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadSTL = async function (filePath) {\n const loader = new three_examples_jsm_loaders_STLLoader_js__WEBPACK_IMPORTED_MODULE_5__.STLLoader();\n\n return new Promise((resolve, reject) => {\n loader.load(\n filePath,\n (geometry) => {\n const material = new three__WEBPACK_IMPORTED_MODULE_6__.MeshPhongMaterial();\n const mesh = new three__WEBPACK_IMPORTED_MODULE_6__.Mesh(geometry, material);\n\n resolve(mesh); // Resolve the promise with the loaded mesh\n },\n (xhr) => {\n // Progress callback\n },\n (error) => {\n console.error(error);\n reject(error); // Reject the promise if there's an error\n }\n );\n });\n};\n\n/**\n * @function\n * @memberof model\n * @description Loads USD/USDZ file\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadUSDZ = async function (filePath) {\n const usdzLoader = new three_examples_jsm_loaders_USDZLoader_js__WEBPACK_IMPORTED_MODULE_7__.USDZLoader();\n\n const [model] = await Promise.all([usdzLoader.loadAsync(filePath)], undefined, (error) => {\n console.error(error);\n return null;\n });\n\n return model;\n};\n\n/// ////////////////////////\n// Main Loading Function //\n/// ////////////////////////\n\n/**\n * @function\n * @memberof model\n * @description The main loading function\n * @param {string} filePath - The path to the file(s) needing to be loaded. For now this only supports\n * the full path and the relative path directly to the file.\n * @param {string} extension - The extension of the file type. Current allowed extensions are `dae`, fbx`, `glb`, `obj`, and `stl`.\n * @returns {Promise} - the promise of the loaded mesh object.\n */\nmodel.loadModel = async function (filePath, extension) {\n // Flag used for debugging the ones that are only 'partially implemented' and\n // still as todos.\n const allowed = false;\n\n if (extension == 'fbx') {\n return model.loadFBX(filePath);\n } else if (extension == 'glb') {\n return model.loadGLTF(filePath);\n } else if (allowed && extension == 'gltf') {\n // TODO\n return model.loadGLTF(filePath);\n } else if (extension == 'stl') {\n return model.loadSTL(filePath);\n } else if (extension == 'obj') {\n if (filePath.includes(',')) {\n // has a preceeding material file\n return model.loadOBJWithMTL(filePath);\n } else {\n return model.loadOBJ(filePath);\n }\n } else if (extension == 'dae') {\n return model.loadDAE(filePath);\n } else if (allowed && (extension == 'usdc' || extension == 'usdz')) {\n // TODO\n return model.loadUSDZ(filePath);\n }\n console.error(`ERR: the extensions ${extension} is not supported by MR.js`);\n return null;\n};\n\nmodel.disposeObject3D = function (parentObject3D) {\n parentObject3D.traverse(function (node) {\n if (node.isMesh) {\n if (node.geometry) {\n node.geometry.dispose();\n }\n\n if (node.material) {\n if (node.material instanceof Array) {\n // An array of materials\n node.material.forEach((material) => material.dispose());\n } else {\n // A single material\n node.material.dispose();\n }\n }\n }\n });\n};\n\nmodel.removeObject3DFromScene = function (object3D, scene) {\n model.disposeObject3D(object3D);\n scene.remove(object3D);\n\n // Optional: Clean up references for GC if necessary\n};\n\nmodel.currentRunningAnimationClip = function (entity) {\n if (!entity.mixer) {\n console.log('No mixer found for :', entity);\n return;\n }\n // If no animation is currently playing\n if (!entity.mixer._actions.some((action) => action.isRunning())) {\n console.log('No animation is currently playing');\n return;\n }\n\n // Iterate over all clip actions in the mixer\n for (let i = 0; i < entity.mixer._actions.length; i++) {\n let clipAction = entity._actions[i];\n if (clipAction.isRunning()) {\n let clipName = clipAction.getClip().name;\n console.log(\"Animation '\" + clipName + \"' is currently playing\");\n // You can do whatever you need with this information\n // break; // Break the loop if you only want to know the first running animation\n }\n }\n};\n\n\n\n\n//# sourceURL=webpack://mrjs/./src/utils/Model.js?"); /***/ }), diff --git a/src/core/MRApp.js b/src/core/MRApp.js index 0f8db113..d76e6e09 100644 --- a/src/core/MRApp.js +++ b/src/core/MRApp.js @@ -262,7 +262,7 @@ export class MRApp extends MRElement { this.renderer.toneMapping = THREE.ACESFilmicToneMapping; this.renderer.toneMappingExposure = 1; this.renderer.localClippingEnabled = true; - this.renderer.setFrustumCulling(true); + // this.renderer.setFrustumCulling(true); this.appendChild(this.renderer.domElement); From 522dc4d8d561b71a308fb00d9b47ab0192a7d1af Mon Sep 17 00:00:00 2001 From: hanbollar Date: Thu, 16 May 2024 18:54:30 -0700 Subject: [PATCH 11/12] add material caching start Signed-off-by: hanbollar --- src/utils/Material.js | 78 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/utils/Material.js b/src/utils/Material.js index d2d6616c..418ec6bc 100644 --- a/src/utils/Material.js +++ b/src/utils/Material.js @@ -176,4 +176,82 @@ material.loadVideoTextureAsync = function (video) { }); }; +class AdvancedMaterialCache { + constructor() { + this.cache = new Map(); + } + + generateKey(material) { + // This function generates a key based on several material properties. + let key = `${material.type}|${material.color.getHexString()}`; + + if (material.map) key += `|map:${material.map.uuid}`; + if (material.alphaMap) key += `|alphaMap:${material.alphaMap.uuid}`; + if (material.bumpMap) key += `|bumpMap:${material.bumpMap.uuid}`; + // Add other maps and properties as needed + + return key; + } + + getCachedMaterial(material) { + const key = this.generateKey(material); + if (this.cache.has(key)) { + return this.cache.get(key); + } else { + this.cache.set(key, material); + this.watchMaterial(material); + return material; + } + } + + watchMaterial(material) { + // This function sets up watchers on the material properties + // For example, watching the color property + const colorHandler = { + set: (target, prop, value) => { + target[prop] = value; + // Invalidate the cache entry when a property changes + this.cache.delete(this.generateKey(material)); + // Optionally re-cache the material with new properties + this.cache.set(this.generateKey(material), material); + return true; + } + }; + material.color = new Proxy(material.color, colorHandler); + // Extend this to other properties as needed + } +} + +// const materialCache = new AdvancedMaterialCache(); +// const scene = new THREE.Scene(); + +// scene.add = new Proxy(scene.add, { +// apply: (target, thisArg, args) => { +// const object = args[0]; +// if (object.material) { +// object.material = materialCache.getCachedMaterial(object.material); +// } +// object.traverse((child) => { +// if (child.material && !(child.material instanceof Array)) { +// child.material = materialCache.getCachedMaterial(child.material); +// } +// }); +// return Reflect.apply(target, thisArg, args); +// } +// }); + +// // Example usage +// const geometry = new THREE.BoxGeometry(1, 1, 1); +// const material1 = new THREE.MeshBasicMaterial({ color: 0xff0000 }); +// const material2 = new THREE.MeshBasicMaterial({ color: 0xff0000 }); + +// const mesh1 = new THREE.Mesh(geometry, material1); +// const mesh2 = new THREE.Mesh(geometry, material2); + +// scene.add(mesh1); +// scene.add(mesh2); + +// console.log(mesh1.material === mesh2.material); // Should log true if caching works + + export { material }; From 04b0169021623a488b5d2b0e537deea271429119 Mon Sep 17 00:00:00 2001 From: hanbollar Date: Thu, 16 May 2024 18:54:55 -0700 Subject: [PATCH 12/12] some text system changes Signed-off-by: hanbollar --- dist/mr.js | 2 +- src/core/componentSystems/TextSystem.js | 79 ++++++++++++++++--------- 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/dist/mr.js b/dist/mr.js index f461d3da..d5f615dc 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// import * as SPECTOR from 'spectorjs';\n// let spector = new SPECTOR.Spector();\n// spector.displayUI();\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 this.renderer.setFrustumCulling(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.scene.traverse((object) => {\n // if (object.isMesh) {\n // console.log(`Rendering `, object, `name: ${object.name} with num children: ${object.children.length} with material ${object.material.name}`);\n // }\n // });\n\n this.renderer.render(this.scene, this.camera);\n\n // Log the number of draw calls\n console.log(this.renderer.info);\n console.log('NumDrawCalls:', this.renderer.info.render.calls, 'should be 2xNumGLPrograms(', this.renderer.info.programs.length, ') = ', 2*this.renderer.info.programs.length);\n // this.renderer.info.programs.forEach(program => {\n // console.log(`Program ID: ${program.id}, Linked Material: ${yourCustomMapping[program.id] || 'Unknown'}`);\n // });\n // console.log(this.renderer.info);\n if (this.renderer.info.programs) {\n // this.renderer.info.programs.forEach(program => {\n // console.log(`Program: `, program, `Used times in last frame: ${program.usedTimes}`);\n // });\n // function printSceneObjectsAndMaterials(scene, renderer) {\n // let groupedByMaterial = {};\n\n // // Traverse the scene and group objects by material UUID\n // scene.traverse(function (object) {\n // if (object.isMesh && object.material) {\n // const uuid = object.material.uuid;\n // if (!groupedByMaterial[uuid]) {\n // groupedByMaterial[uuid] = []; // Initialize array if it doesn't exist\n // }\n // groupedByMaterial[uuid].push({\n // objectName: object.name,\n // objectType: object.type\n // });\n // }\n // });\n\n // Log details about each group\n // Object.keys(groupedByMaterial).forEach(uuid => {\n // console.log(`Material UUID: ${uuid}, num items: ${groupedByMaterial[uuid].length}`);\n // groupedByMaterial[uuid].forEach(entry => {\n // console.log(`Object: ${entry.objectName} | Type: ${entry.objectType}`);\n // });\n // });\n\n // // Then, log all active WebGL programs separately.\n // if (renderer.info.programs) {\n // renderer.info.programs.forEach(program => {\n // console.log(`Program ID: ${program.id}, Program Info:`, program);\n // });\n // }\n // }\n\n // Call this function where appropriate in your application\n // printSceneObjectsAndMaterials(this.scene, this.renderer);\n\n }\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// import * as SPECTOR from 'spectorjs';\n// let spector = new SPECTOR.Spector();\n// spector.displayUI();\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 // this.renderer.setFrustumCulling(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.scene.traverse((object) => {\n // if (object.isMesh) {\n // console.log(`Rendering `, object, `name: ${object.name} with num children: ${object.children.length} with material ${object.material.name}`);\n // }\n // });\n\n this.renderer.render(this.scene, this.camera);\n\n // Log the number of draw calls\n console.log(this.renderer.info);\n console.log('NumDrawCalls:', this.renderer.info.render.calls, 'should be 2xNumGLPrograms(', this.renderer.info.programs.length, ') = ', 2*this.renderer.info.programs.length);\n // this.renderer.info.programs.forEach(program => {\n // console.log(`Program ID: ${program.id}, Linked Material: ${yourCustomMapping[program.id] || 'Unknown'}`);\n // });\n // console.log(this.renderer.info);\n if (this.renderer.info.programs) {\n // this.renderer.info.programs.forEach(program => {\n // console.log(`Program: `, program, `Used times in last frame: ${program.usedTimes}`);\n // });\n // function printSceneObjectsAndMaterials(scene, renderer) {\n // let groupedByMaterial = {};\n\n // // Traverse the scene and group objects by material UUID\n // scene.traverse(function (object) {\n // if (object.isMesh && object.material) {\n // const uuid = object.material.uuid;\n // if (!groupedByMaterial[uuid]) {\n // groupedByMaterial[uuid] = []; // Initialize array if it doesn't exist\n // }\n // groupedByMaterial[uuid].push({\n // objectName: object.name,\n // objectType: object.type\n // });\n // }\n // });\n\n // Log details about each group\n // Object.keys(groupedByMaterial).forEach(uuid => {\n // console.log(`Material UUID: ${uuid}, num items: ${groupedByMaterial[uuid].length}`);\n // groupedByMaterial[uuid].forEach(entry => {\n // console.log(`Object: ${entry.objectName} | Type: ${entry.objectType}`);\n // });\n // });\n\n // // Then, log all active WebGL programs separately.\n // if (renderer.info.programs) {\n // renderer.info.programs.forEach(program => {\n // console.log(`Program ID: ${program.id}, Program Info:`, program);\n // });\n // }\n // }\n\n // Call this function where appropriate in your application\n // printSceneObjectsAndMaterials(this.scene, this.renderer);\n\n }\n }\n}\n\ncustomElements.get('mr-app') || customElements.define('mr-app', MRApp);\n\n\n//# sourceURL=webpack://mrjs/./src/core/MRApp.js?"); /***/ }), diff --git a/src/core/componentSystems/TextSystem.js b/src/core/componentSystems/TextSystem.js index a0769125..1bf62586 100644 --- a/src/core/componentSystems/TextSystem.js +++ b/src/core/componentSystems/TextSystem.js @@ -27,23 +27,15 @@ export class TextSystem extends MRSystem { this.preloadedFonts = {}; const styleSheets = Array.from(document.styleSheets); styleSheets.forEach((styleSheet) => { - const cssRules = Array.from(styleSheet.cssRules); - // all the font-faces rules - const rulesFontFace = cssRules.filter((rule) => rule.cssText.startsWith('@font-face')); - - rulesFontFace.forEach((fontFace) => { - const fontData = this.parseFontFace(fontFace.cssText); - - preloadFont( - { - font: fontData.src, - }, - () => { + Array.from(styleSheet.cssRules) + .filter(rule => rule.cssText.startsWith('@font-face')) + .forEach((fontFace) => { + const fontData = this.parseFontFace(fontFace.cssText); + preloadFont({ font: fontData.src }, () => { this.preloadedFonts[fontFace.style.getPropertyValue('font-family')] = fontData.src; document.dispatchEvent(new CustomEvent('font-loaded')); - } - ); - }); + }); + }); }); // Handle text style needs update @@ -53,6 +45,23 @@ export class TextSystem extends MRSystem { this._updateSpecificEntity(e.detail); } }); + + this.sharedMaterialMap = new Map(); + } + + #getSharedMaterial(options) { + const key = this.getMaterialKey(options); + if (!this.sharedMaterialMap.has(key)) { + const material = new THREE.MeshBasicMaterial({ + transparent: true, + opacity: options.opacity || 1 + }); + this.sharedMaterialMap.set(key, material); + } + const material = this.materials.get(key); + // Set color dynamically + this.setTextObject3DColor(material, options.color); + return material; } /** @@ -76,6 +85,7 @@ export class TextSystem extends MRSystem { _updateSpecificEntity(entity) { this.checkIfTextContentChanged(entity); this.handleTextContentUpdate(entity); + this.handleTextMaterialUpdate(entity); } /** @@ -180,6 +190,15 @@ export class TextSystem extends MRSystem { }); } + handleTextMaterialUpdate(entity) { + const options = { + font: entity.textObj.font, + fontSize: entity.textObj.fontSize, + color: this.compStyle.color, + }; + entity.textObj.material = this.getSharedMaterial(options); + } + /** * @function * @description The per global scene event update call. Handles updating all text items including updates for style and cleaning of content for special characters. @@ -188,6 +207,7 @@ export class TextSystem extends MRSystem { for (const entity of this.registry) { this.checkIfTextContentChanged(entity); this.handleTextContentUpdate(entity); + this.handleTextMaterialUpdate(entity); } }; @@ -333,23 +353,24 @@ export class TextSystem extends MRSystem { parseFontFace(cssString) { const obj = {}; const match = cssString.match(/@font-face\s*{\s*([^}]*)\s*}/); + if (!match) { + return; + } - if (match) { - const fontFaceAttributes = match[1]; - const attributes = fontFaceAttributes.split(';'); + const fontFaceAttributes = match[1]; + const attributes = fontFaceAttributes.split(';'); - attributes.forEach((attribute) => { - const [key, value] = attribute.split(':').map((item) => item.trim()); - if (key === 'src') { - const urlMatch = value.match(/url\("([^"]+)"\)/); - if (urlMatch) { - obj[key] = urlMatch[1]; - } - } else { - obj[key] = value; + attributes.forEach((attribute) => { + const [key, value] = attribute.split(':').map((item) => item.trim()); + if (key === 'src') { + const urlMatch = value.match(/url\("([^"]+)"\)/); + if (urlMatch) { + obj[key] = urlMatch[1]; } - }); - } + } else { + obj[key] = value; + } + }); return obj; }