From 9d20d9362eb326fbe6fae329438e8c3f9b47767b Mon Sep 17 00:00:00 2001 From: Hannah Bollar Date: Thu, 2 May 2024 15:24:44 -0500 Subject: [PATCH] add garbage collection for `mr-entity`'s `object3D` items on `disconnectedCallback` (#603) Signed-off-by: hanbollar --- dist/mr.js | 4 ++-- src/core/MREntity.js | 2 ++ src/utils/Model.js | 5 +---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/dist/mr.js b/dist/mr.js index 9e97af38..466149b0 100644 --- a/dist/mr.js +++ b/dist/mr.js @@ -465,7 +465,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 */ MREntity: () => (/* binding */ MREntity)\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_MRElement__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/MRElement */ \"./src/core/MRElement.js\");\n/* harmony import */ var mrjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs */ \"./src/index.js\");\n\n\n\n\n\n\nconst MOUSE_EVENTS = ['click', 'mouseenter', 'mouseleave', 'mousedown', 'mouseup', 'mouseover', 'mouseout', 'mousemove'];\n\n/**\n * @class MREntity\n * @classdesc The default representation of an MRElement to be expanded upon by actual details ECS Entity items. `mr-entity`\n * @augments MRElement\n */\nclass MREntity extends mrjs_core_MRElement__WEBPACK_IMPORTED_MODULE_0__.MRElement {\n aabb = new three__WEBPACK_IMPORTED_MODULE_2__.Box3();\n\n size = new three__WEBPACK_IMPORTED_MODULE_2__.Vector3();\n\n layer = 0;\n\n physics = {\n type: 'none',\n };\n\n /**\n * @class\n * @description Constructor for the default Entity Component (MREntity).\n * Sets up the base object3D and useful Mixed Reality information including rendering, touching, and component basics.\n */\n constructor() {\n super();\n\n this.object3D = new three__WEBPACK_IMPORTED_MODULE_2__.Group();\n this.object3D.userData.isEntityObject3DRoot = true;\n this.object3D.userData.bbox = new three__WEBPACK_IMPORTED_MODULE_2__.Box3();\n this.object3D.userData.size = new three__WEBPACK_IMPORTED_MODULE_2__.Vector3();\n\n this.object3D.receiveShadow = true;\n this.object3D.castShadow = true;\n this.object3D.renderOrder = 3;\n this.object3D.name = 'entity';\n\n this.scale = 1;\n\n this.componentMutated = this.componentMutated.bind(this);\n\n this.touch = false;\n this.grabbed = false;\n this.focus = false;\n\n this.ignoreStencil = true;\n }\n\n /*************************/\n /*** Object Dimensions ***/\n /*************************/\n\n /**\n * @function\n * @description Calculates the width of the Entity based on the viewPort's shape. If in Mixed Reality, adjusts the value appropriately.\n * @returns {number} - the resolved width\n */\n get width() {\n return (this.compStyle.width.split('px')[0] / __webpack_require__.g.appWidth) * __webpack_require__.g.viewPortWidth;\n }\n\n /**\n * @function\n * @description The actual 3D value of the content's width.\n * @returns {number} - width of the 3D object.\n */\n get contentWidth() {\n this.aabb.setFromObject(this.object3D).getSize(this.size);\n return this.size.x;\n }\n\n /**\n * @function\n * @description Calculates the height of the Entity based on the viewPort's shape. If in Mixed Reality, adjusts the value appropriately.\n * @returns {number} - the resolved height\n */\n get height() {\n return (this.compStyle.height.split('px')[0] / __webpack_require__.g.appHeight) * __webpack_require__.g.viewPortHeight;\n }\n\n /**\n * @function\n * @description The actual 3D value of the content's height.\n * @returns {number} - height of the 3D object.\n */\n get contentHeight() {\n this.aabb.setFromObject(this.object3D).getSize(this.size);\n return this.size.y;\n }\n\n /****************************/\n /*** Updates And Triggers ***/\n /****************************/\n\n /**\n * @function\n * @description Triggers a system run to update geometry specifically for the entity calling it. Useful when it's not an overall scene event and for cases where\n * relying on an overall scene or all items to update isnt beneficial.\n */\n triggerGeometryStyleUpdate() {\n this.dispatchEvent(new CustomEvent('trigger-geometry-style-update', { detail: this, bubbles: true }));\n }\n\n /**\n * @function\n * @description Triggers a system run to update material specifically for the entity calling it. Useful when it's not an overall scene event and for cases where\n * relying on an overall scene or all items to update isnt beneficial.\n */\n triggerMaterialStyleUpdate() {\n this.dispatchEvent(new CustomEvent('trigger-material-style-update', { detail: this, bubbles: true }));\n }\n\n /**\n * @function\n * @description Directly in MRjs, this function is empty. It is called directly in the\n * MaterialStyleSystem. This allows outside users to add their own additional functionality\n * for the entities. These are run after the MaterialStyleSystem does its own update on the entity.\n */\n updateMaterialStyle() {}\n\n /**\n * @function\n * @description Directly in MRjs, this function is empty. It is called directly in the\n * GeometryStyleSystem. This allows outside users to add their own additional functionality\n * for the entities. These are run after the GeometryStyleSystem does its own update on the entity.\n */\n updateGeometryStyle() {}\n\n /*****************************/\n /*** On Interaction Events ***/\n /*****************************/\n\n /**\n * @function\n * @description Handles the hover event\n * @param {object} event - the hover event\n */\n onHover = (event) => {};\n\n /**\n * @function\n * @description Handles the touch event\n * @param {object} event - the touch event\n */\n onTouch = (event) => {};\n\n /**\n * @function\n * @description Handles the scroll event\n * @param {object} event - the scroll event\n */\n onScroll = (event) => {\n this.parentElement?.onScroll(event);\n };\n\n /********************************/\n /*** Handling data-attributes ***/\n /********************************/\n\n components = {\n get: (name) => {\n const dataName = `comp${name[0].toUpperCase()}${name.slice(1)}`;\n return mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToJson(this.dataset[dataName]);\n },\n\n set: (name, data) => {\n const dataName = `comp${name[0].toUpperCase()}${name.slice(1)}`;\n const component = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToJson(this.dataset[dataName]) ?? {};\n for (const key in data) {\n component[key] = data[key];\n }\n this.dataset[dataName] = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.jsonToString(component);\n },\n\n toJSON: () => {\n let all = {};\n for (const key in this.dataset) {\n let compName = key.split('comp')[1];\n if (compName) {\n all[compName.toLowerCase()] = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToJson(this.dataset[key]);\n }\n }\n return all;\n },\n };\n\n position = {\n get: () => {\n return mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.position);\n },\n\n set: (arr) => {\n if (arr.length != 3) {\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.error.err('position must be set with an array of all three elements [x, y, z]');\n }\n let vec = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.position ?? '0 0 0');\n vec[0] = arr[0];\n vec[1] = arr[1];\n vec[2] = arr[2];\n this.dataset.position = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.vectorToString(vec);\n },\n\n x: () => {\n return this.dataset.position.split(' ')[0];\n },\n\n setX: (val) => {\n let vec = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.position);\n vec[0] = val;\n this.dataset.position = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.vectorToString(vec);\n },\n\n y: () => {\n return this.dataset.position.split(' ')[1];\n },\n\n setY: (val) => {\n let vec = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.position);\n vec[1] = val;\n this.dataset.position = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.vectorToString(vec);\n },\n\n z: () => {\n return this.dataset.position.split(' ')[2];\n },\n\n setZ: (val) => {\n let vec = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.position);\n vec[2] = val;\n this.dataset.position = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.vectorToString(vec);\n },\n };\n\n rotation = {\n get: () => {\n return mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.rotation);\n },\n\n set: (arr) => {\n if (arr.length != 3) {\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.error.err('rotation must be set with an array of all three elements [x, y, z]');\n }\n let vec = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.rotation ?? '0 0 0');\n vec[0] = arr[0];\n vec[1] = arr[1];\n vec[2] = arr[2];\n this.dataset.rotation = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.vectorToString(vec);\n },\n\n x: () => {\n return this.dataset.rotation.split(' ')[0];\n },\n\n x: (val) => {\n let vec = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.rotation);\n vec[0] = val;\n this.dataset.rotation = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.vectorToString(vec);\n },\n\n y: () => {\n return this.dataset.rotation.split(' ')[1];\n },\n\n y: (val) => {\n let vec = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.rotation);\n vec[1] = val;\n this.dataset.rotation = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.vectorToString(vec);\n },\n\n z: () => {\n return this.dataset.rotation.split(' ')[2];\n },\n\n z: (val) => {\n let vec = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.rotation);\n vec[2] = val;\n this.dataset.rotation = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.vectorToString(vec);\n },\n };\n\n /**\n * @function\n * @description Loads all attributes of this entity's stored dataset including components, attaching them, and their associated rotations and positions.\n */\n loadAttributes() {\n for (const attr in this.dataset) {\n if (attr.includes('comp')) {\n const compName = attr.split('comp')[1].toLocaleLowerCase();\n this.dispatchEvent(\n new CustomEvent(`${attr}-attached`, {\n bubbles: true,\n detail: { entity: this },\n })\n );\n } else {\n switch (attr) {\n case 'rotation':\n this.object3D.rotation.fromArray(mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToDegVector(this.dataset.rotation));\n break;\n case 'position':\n this.object3D.position.fromArray(mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.position));\n break;\n }\n }\n }\n }\n\n /************************************/\n /*** Handling Entity Interactions ***/\n /************************************/\n\n /**\n * @function\n * @description (async) does nothing. Is called by the connectedCallback.\n */\n async connected() {}\n\n /**\n * @function\n * @description The connectedCallback function that runs whenever this entity component becomes connected to something else.\n */\n async connectedCallback() {\n this.compStyle = window.getComputedStyle(this);\n\n if (!(this.parentElement instanceof mrjs_core_MRElement__WEBPACK_IMPORTED_MODULE_0__.MRElement)) {\n return;\n }\n this.parentElement.add(this);\n\n if (this.parentElement.user) {\n this.user = this.parentElement.user;\n }\n if (this.parentElement.env) {\n this.env = this.parentElement.env;\n }\n\n this.object3D.userData.element = this;\n\n this.object3D.userData.bbox = new three__WEBPACK_IMPORTED_MODULE_2__.Box3();\n this.object3D.userData.size = new three__WEBPACK_IMPORTED_MODULE_2__.Vector3();\n\n this.object3D.userData.bbox.setFromObject(this.object3D);\n\n this.object3D.userData.bbox.getSize(this.object3D.userData.size);\n\n this.mutationCallback = this.mutationCallback.bind(this);\n this.observer = new MutationObserver(this.mutationCallback);\n this.observer.observe(this, { attributes: true, childList: true, attributeOldValue: true });\n\n /** Handle events specific to this entity */\n // note: these will need the trigger for Material or Geometry if applicable\n\n MOUSE_EVENTS.forEach((eventType) => {\n this.addEventListener(eventType, (event) => {\n this.triggerGeometryStyleUpdate();\n this.triggerMaterialStyleUpdate();\n });\n });\n\n this.addEventListener('touchstart', (event) => {\n this.triggerGeometryStyleUpdate();\n this.triggerMaterialStyleUpdate();\n this.onTouch(event);\n });\n this.addEventListener('touchmove', (event) => {\n this.triggerGeometryStyleUpdate();\n this.triggerMaterialStyleUpdate();\n this.onTouch(event);\n });\n this.addEventListener('touchend', (event) => {\n this.triggerGeometryStyleUpdate();\n this.triggerMaterialStyleUpdate();\n this.onTouch(event);\n });\n this.addEventListener('hoverstart', (event) => {\n this.triggerGeometryStyleUpdate();\n this.triggerMaterialStyleUpdate();\n this.onHover(event);\n });\n this.addEventListener('hoverend', (event) => {\n this.triggerGeometryStyleUpdate();\n this.triggerMaterialStyleUpdate();\n this.onHover(event);\n });\n\n this.addEventListener('entityupdated', (event) => {\n this.triggerGeometryStyleUpdate();\n this.triggerMaterialStyleUpdate();\n });\n\n // TODO: find alternative solution. This breaks with the switch to asychronous entity initialization\n // const intersectionObserver = new IntersectionObserver((entries) => {\n // for (const entry of entries) {\n // this._boundingClientRect = entry.boundingClientRect;\n // }\n // // Refresh the rect info to keep it up-to-date as much as possible.\n // // It seems that the callback is always called once soon after observe() is called,\n // // regardless of the intersection state of the entity.\n // // TODO: Confirm whether this behavior is intended. If it is not, there may be future\n // // behavior changes or it may not work as intended on certain platforms.\n // intersectionObserver.disconnect();\n // intersectionObserver.observe(this);\n // });\n // intersectionObserver.observe(this);\n\n // If the physics not yet inititalized, set an event listener and wait til the engine\n // has started before completing initialization\n if (mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.physics.initialized) {\n await this.connected();\n this.loadAttributes();\n this.dispatchEvent(new CustomEvent('entityadded', { bubbles: true }));\n } else {\n document.addEventListener('engine-started', async (event) => {\n await this.connected();\n this.loadAttributes();\n this.dispatchEvent(new CustomEvent('entityadded', { bubbles: true }));\n });\n }\n }\n\n /**\n * @function\n * @description Callback function of MREntity - does nothing. Is called by the disconnectedCallback.\n */\n disconnected() {}\n\n /**\n * @function\n * @description The disconnectedCallback function that runs whenever this entity component becomes disconnected from something else.\n */\n disconnectedCallback() {\n document.dispatchEvent(\n new CustomEvent('entityremoved', {\n detail: {\n entity: this,\n },\n })\n );\n\n this.environment = null;\n this.observer.disconnect();\n\n this.disconnected();\n }\n\n /**\n * @function\n * @description Callback function of MREntity - does nothing. Is called by mutation Callback.\n * @param {object} mutation - the update/change/mutation to be handled.\n */\n mutated(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 this.mutated(mutation);\n\n switch (mutation.type) {\n case 'childList':\n this.triggerGeometryStyleUpdate();\n this.triggerMaterialStyleUpdate();\n break;\n case 'attributes':\n if (mutation.attributeName.includes('comp')) {\n this.componentMutated(mutation);\n }\n switch (mutation.attributeName) {\n case 'data-position':\n this.object3D.position.fromArray(mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.position));\n break;\n case 'data-rotation':\n this.object3D.rotation.fromArray(mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToDegVector(this.dataset.rotation));\n break;\n\n default:\n break;\n }\n break;\n\n default:\n break;\n }\n }\n }\n\n /**\n * @function\n * @description Helper function for the mutationCallback. Handles actually updating this entity component with all the associated dispatchEvents.\n * @param {object} mutation - the update/change/mutation to be handled.\n */\n componentMutated(mutation) {\n const compName = mutation.attributeName.split('comp-')[1];\n const dataName = `comp${compName[0].toUpperCase()}${compName.slice(1)}`;\n if (!this.dataset[dataName]) {\n this.dispatchEvent(new CustomEvent(`${dataName}-detached`, { bubbles: true }));\n } else if (mutation.oldValue) {\n this.dispatchEvent(\n new CustomEvent(`${dataName}-updated`, {\n bubbles: true,\n detail: { oldData: mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.jsonToString(mutation.oldValue) },\n })\n );\n } else {\n this.dispatchEvent(\n new CustomEvent(`${dataName}-attached`, {\n bubbles: true,\n })\n );\n }\n }\n\n /**\n * @function\n * @description Adding an entity as a sub-object of this entity.\n * @param {MREntity} entity - the entity to be added.\n */\n add(entity) {\n entity.object3D.receiveShadow = true;\n entity.object3D.renderOrder = 3;\n this.object3D.add(entity.object3D);\n }\n\n /**\n * @function\n * @description Removing an entity as a sub-object of this entity.\n * @param {MREntity} entity - the entity to be removed.\n */\n removeEntity(entity) {\n this.object3D.remove(entity.object3D);\n }\n\n /**\n * @function\n * @description Runs the passed through function on this object and every child of this object.\n * @param {Function} callBack - the function to run recursively.\n */\n traverse(callBack) {\n callBack(this);\n const children = Array.from(this.children);\n for (const child of children) {\n // if child is an entity, traverse it again\n if (child instanceof MREntity) {\n child.traverse(callBack);\n }\n }\n }\n\n /**\n * @function\n * @description Runs the passed through function on the objects associated with this Entity\n * @param {Function} callBack - the function to run recursively.\n */\n traverseObjects(callBack) {\n const traverse = (object) => {\n callBack(object);\n for (const child of object.children) {\n if (!child.userData.isEntityObject3DRoot) {\n traverse(child);\n }\n }\n };\n traverse(this.object3D);\n }\n}\n\ncustomElements.get('mr-entity') || customElements.define('mr-entity', MREntity);\n\n\n//# sourceURL=webpack://mrjs/./src/core/MREntity.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ MREntity: () => (/* binding */ MREntity)\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_MRElement__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! mrjs/core/MRElement */ \"./src/core/MRElement.js\");\n/* harmony import */ var mrjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mrjs */ \"./src/index.js\");\n\n\n\n\n\n\nconst MOUSE_EVENTS = ['click', 'mouseenter', 'mouseleave', 'mousedown', 'mouseup', 'mouseover', 'mouseout', 'mousemove'];\n\n/**\n * @class MREntity\n * @classdesc The default representation of an MRElement to be expanded upon by actual details ECS Entity items. `mr-entity`\n * @augments MRElement\n */\nclass MREntity extends mrjs_core_MRElement__WEBPACK_IMPORTED_MODULE_0__.MRElement {\n aabb = new three__WEBPACK_IMPORTED_MODULE_2__.Box3();\n\n size = new three__WEBPACK_IMPORTED_MODULE_2__.Vector3();\n\n layer = 0;\n\n physics = {\n type: 'none',\n };\n\n /**\n * @class\n * @description Constructor for the default Entity Component (MREntity).\n * Sets up the base object3D and useful Mixed Reality information including rendering, touching, and component basics.\n */\n constructor() {\n super();\n\n this.object3D = new three__WEBPACK_IMPORTED_MODULE_2__.Group();\n this.object3D.userData.isEntityObject3DRoot = true;\n this.object3D.userData.bbox = new three__WEBPACK_IMPORTED_MODULE_2__.Box3();\n this.object3D.userData.size = new three__WEBPACK_IMPORTED_MODULE_2__.Vector3();\n\n this.object3D.receiveShadow = true;\n this.object3D.castShadow = true;\n this.object3D.renderOrder = 3;\n this.object3D.name = 'entity';\n\n this.scale = 1;\n\n this.componentMutated = this.componentMutated.bind(this);\n\n this.touch = false;\n this.grabbed = false;\n this.focus = false;\n\n this.ignoreStencil = true;\n }\n\n /*************************/\n /*** Object Dimensions ***/\n /*************************/\n\n /**\n * @function\n * @description Calculates the width of the Entity based on the viewPort's shape. If in Mixed Reality, adjusts the value appropriately.\n * @returns {number} - the resolved width\n */\n get width() {\n return (this.compStyle.width.split('px')[0] / __webpack_require__.g.appWidth) * __webpack_require__.g.viewPortWidth;\n }\n\n /**\n * @function\n * @description The actual 3D value of the content's width.\n * @returns {number} - width of the 3D object.\n */\n get contentWidth() {\n this.aabb.setFromObject(this.object3D).getSize(this.size);\n return this.size.x;\n }\n\n /**\n * @function\n * @description Calculates the height of the Entity based on the viewPort's shape. If in Mixed Reality, adjusts the value appropriately.\n * @returns {number} - the resolved height\n */\n get height() {\n return (this.compStyle.height.split('px')[0] / __webpack_require__.g.appHeight) * __webpack_require__.g.viewPortHeight;\n }\n\n /**\n * @function\n * @description The actual 3D value of the content's height.\n * @returns {number} - height of the 3D object.\n */\n get contentHeight() {\n this.aabb.setFromObject(this.object3D).getSize(this.size);\n return this.size.y;\n }\n\n /****************************/\n /*** Updates And Triggers ***/\n /****************************/\n\n /**\n * @function\n * @description Triggers a system run to update geometry specifically for the entity calling it. Useful when it's not an overall scene event and for cases where\n * relying on an overall scene or all items to update isnt beneficial.\n */\n triggerGeometryStyleUpdate() {\n this.dispatchEvent(new CustomEvent('trigger-geometry-style-update', { detail: this, bubbles: true }));\n }\n\n /**\n * @function\n * @description Triggers a system run to update material specifically for the entity calling it. Useful when it's not an overall scene event and for cases where\n * relying on an overall scene or all items to update isnt beneficial.\n */\n triggerMaterialStyleUpdate() {\n this.dispatchEvent(new CustomEvent('trigger-material-style-update', { detail: this, bubbles: true }));\n }\n\n /**\n * @function\n * @description Directly in MRjs, this function is empty. It is called directly in the\n * MaterialStyleSystem. This allows outside users to add their own additional functionality\n * for the entities. These are run after the MaterialStyleSystem does its own update on the entity.\n */\n updateMaterialStyle() {}\n\n /**\n * @function\n * @description Directly in MRjs, this function is empty. It is called directly in the\n * GeometryStyleSystem. This allows outside users to add their own additional functionality\n * for the entities. These are run after the GeometryStyleSystem does its own update on the entity.\n */\n updateGeometryStyle() {}\n\n /*****************************/\n /*** On Interaction Events ***/\n /*****************************/\n\n /**\n * @function\n * @description Handles the hover event\n * @param {object} event - the hover event\n */\n onHover = (event) => {};\n\n /**\n * @function\n * @description Handles the touch event\n * @param {object} event - the touch event\n */\n onTouch = (event) => {};\n\n /**\n * @function\n * @description Handles the scroll event\n * @param {object} event - the scroll event\n */\n onScroll = (event) => {\n this.parentElement?.onScroll(event);\n };\n\n /********************************/\n /*** Handling data-attributes ***/\n /********************************/\n\n components = {\n get: (name) => {\n const dataName = `comp${name[0].toUpperCase()}${name.slice(1)}`;\n return mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToJson(this.dataset[dataName]);\n },\n\n set: (name, data) => {\n const dataName = `comp${name[0].toUpperCase()}${name.slice(1)}`;\n const component = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToJson(this.dataset[dataName]) ?? {};\n for (const key in data) {\n component[key] = data[key];\n }\n this.dataset[dataName] = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.jsonToString(component);\n },\n\n toJSON: () => {\n let all = {};\n for (const key in this.dataset) {\n let compName = key.split('comp')[1];\n if (compName) {\n all[compName.toLowerCase()] = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToJson(this.dataset[key]);\n }\n }\n return all;\n },\n };\n\n position = {\n get: () => {\n return mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.position);\n },\n\n set: (arr) => {\n if (arr.length != 3) {\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.error.err('position must be set with an array of all three elements [x, y, z]');\n }\n let vec = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.position ?? '0 0 0');\n vec[0] = arr[0];\n vec[1] = arr[1];\n vec[2] = arr[2];\n this.dataset.position = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.vectorToString(vec);\n },\n\n x: () => {\n return this.dataset.position.split(' ')[0];\n },\n\n setX: (val) => {\n let vec = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.position);\n vec[0] = val;\n this.dataset.position = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.vectorToString(vec);\n },\n\n y: () => {\n return this.dataset.position.split(' ')[1];\n },\n\n setY: (val) => {\n let vec = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.position);\n vec[1] = val;\n this.dataset.position = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.vectorToString(vec);\n },\n\n z: () => {\n return this.dataset.position.split(' ')[2];\n },\n\n setZ: (val) => {\n let vec = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.position);\n vec[2] = val;\n this.dataset.position = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.vectorToString(vec);\n },\n };\n\n rotation = {\n get: () => {\n return mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.rotation);\n },\n\n set: (arr) => {\n if (arr.length != 3) {\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.error.err('rotation must be set with an array of all three elements [x, y, z]');\n }\n let vec = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.rotation ?? '0 0 0');\n vec[0] = arr[0];\n vec[1] = arr[1];\n vec[2] = arr[2];\n this.dataset.rotation = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.vectorToString(vec);\n },\n\n x: () => {\n return this.dataset.rotation.split(' ')[0];\n },\n\n x: (val) => {\n let vec = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.rotation);\n vec[0] = val;\n this.dataset.rotation = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.vectorToString(vec);\n },\n\n y: () => {\n return this.dataset.rotation.split(' ')[1];\n },\n\n y: (val) => {\n let vec = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.rotation);\n vec[1] = val;\n this.dataset.rotation = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.vectorToString(vec);\n },\n\n z: () => {\n return this.dataset.rotation.split(' ')[2];\n },\n\n z: (val) => {\n let vec = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.rotation);\n vec[2] = val;\n this.dataset.rotation = mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.vectorToString(vec);\n },\n };\n\n /**\n * @function\n * @description Loads all attributes of this entity's stored dataset including components, attaching them, and their associated rotations and positions.\n */\n loadAttributes() {\n for (const attr in this.dataset) {\n if (attr.includes('comp')) {\n const compName = attr.split('comp')[1].toLocaleLowerCase();\n this.dispatchEvent(\n new CustomEvent(`${attr}-attached`, {\n bubbles: true,\n detail: { entity: this },\n })\n );\n } else {\n switch (attr) {\n case 'rotation':\n this.object3D.rotation.fromArray(mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToDegVector(this.dataset.rotation));\n break;\n case 'position':\n this.object3D.position.fromArray(mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.position));\n break;\n }\n }\n }\n }\n\n /************************************/\n /*** Handling Entity Interactions ***/\n /************************************/\n\n /**\n * @function\n * @description (async) does nothing. Is called by the connectedCallback.\n */\n async connected() {}\n\n /**\n * @function\n * @description The connectedCallback function that runs whenever this entity component becomes connected to something else.\n */\n async connectedCallback() {\n this.compStyle = window.getComputedStyle(this);\n\n if (!(this.parentElement instanceof mrjs_core_MRElement__WEBPACK_IMPORTED_MODULE_0__.MRElement)) {\n return;\n }\n this.parentElement.add(this);\n\n if (this.parentElement.user) {\n this.user = this.parentElement.user;\n }\n if (this.parentElement.env) {\n this.env = this.parentElement.env;\n }\n\n this.object3D.userData.element = this;\n\n this.object3D.userData.bbox = new three__WEBPACK_IMPORTED_MODULE_2__.Box3();\n this.object3D.userData.size = new three__WEBPACK_IMPORTED_MODULE_2__.Vector3();\n\n this.object3D.userData.bbox.setFromObject(this.object3D);\n\n this.object3D.userData.bbox.getSize(this.object3D.userData.size);\n\n this.mutationCallback = this.mutationCallback.bind(this);\n this.observer = new MutationObserver(this.mutationCallback);\n this.observer.observe(this, { attributes: true, childList: true, attributeOldValue: true });\n\n /** Handle events specific to this entity */\n // note: these will need the trigger for Material or Geometry if applicable\n\n MOUSE_EVENTS.forEach((eventType) => {\n this.addEventListener(eventType, (event) => {\n this.triggerGeometryStyleUpdate();\n this.triggerMaterialStyleUpdate();\n });\n });\n\n this.addEventListener('touchstart', (event) => {\n this.triggerGeometryStyleUpdate();\n this.triggerMaterialStyleUpdate();\n this.onTouch(event);\n });\n this.addEventListener('touchmove', (event) => {\n this.triggerGeometryStyleUpdate();\n this.triggerMaterialStyleUpdate();\n this.onTouch(event);\n });\n this.addEventListener('touchend', (event) => {\n this.triggerGeometryStyleUpdate();\n this.triggerMaterialStyleUpdate();\n this.onTouch(event);\n });\n this.addEventListener('hoverstart', (event) => {\n this.triggerGeometryStyleUpdate();\n this.triggerMaterialStyleUpdate();\n this.onHover(event);\n });\n this.addEventListener('hoverend', (event) => {\n this.triggerGeometryStyleUpdate();\n this.triggerMaterialStyleUpdate();\n this.onHover(event);\n });\n\n this.addEventListener('entityupdated', (event) => {\n this.triggerGeometryStyleUpdate();\n this.triggerMaterialStyleUpdate();\n });\n\n // TODO: find alternative solution. This breaks with the switch to asychronous entity initialization\n // const intersectionObserver = new IntersectionObserver((entries) => {\n // for (const entry of entries) {\n // this._boundingClientRect = entry.boundingClientRect;\n // }\n // // Refresh the rect info to keep it up-to-date as much as possible.\n // // It seems that the callback is always called once soon after observe() is called,\n // // regardless of the intersection state of the entity.\n // // TODO: Confirm whether this behavior is intended. If it is not, there may be future\n // // behavior changes or it may not work as intended on certain platforms.\n // intersectionObserver.disconnect();\n // intersectionObserver.observe(this);\n // });\n // intersectionObserver.observe(this);\n\n // If the physics not yet inititalized, set an event listener and wait til the engine\n // has started before completing initialization\n if (mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.physics.initialized) {\n await this.connected();\n this.loadAttributes();\n this.dispatchEvent(new CustomEvent('entityadded', { bubbles: true }));\n } else {\n document.addEventListener('engine-started', async (event) => {\n await this.connected();\n this.loadAttributes();\n this.dispatchEvent(new CustomEvent('entityadded', { bubbles: true }));\n });\n }\n }\n\n /**\n * @function\n * @description Callback function of MREntity - does nothing. Is called by the disconnectedCallback.\n */\n disconnected() {}\n\n /**\n * @function\n * @description The disconnectedCallback function that runs whenever this entity component becomes disconnected from something else.\n */\n disconnectedCallback() {\n document.dispatchEvent(\n new CustomEvent('entityremoved', {\n detail: {\n entity: this,\n },\n })\n );\n\n this.environment = null;\n this.observer.disconnect();\n\n this.disconnected();\n\n mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.model.disposeObject3D(this.object3D);\n }\n\n /**\n * @function\n * @description Callback function of MREntity - does nothing. Is called by mutation Callback.\n * @param {object} mutation - the update/change/mutation to be handled.\n */\n mutated(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 this.mutated(mutation);\n\n switch (mutation.type) {\n case 'childList':\n this.triggerGeometryStyleUpdate();\n this.triggerMaterialStyleUpdate();\n break;\n case 'attributes':\n if (mutation.attributeName.includes('comp')) {\n this.componentMutated(mutation);\n }\n switch (mutation.attributeName) {\n case 'data-position':\n this.object3D.position.fromArray(mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToVector(this.dataset.position));\n break;\n case 'data-rotation':\n this.object3D.rotation.fromArray(mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.stringToDegVector(this.dataset.rotation));\n break;\n\n default:\n break;\n }\n break;\n\n default:\n break;\n }\n }\n }\n\n /**\n * @function\n * @description Helper function for the mutationCallback. Handles actually updating this entity component with all the associated dispatchEvents.\n * @param {object} mutation - the update/change/mutation to be handled.\n */\n componentMutated(mutation) {\n const compName = mutation.attributeName.split('comp-')[1];\n const dataName = `comp${compName[0].toUpperCase()}${compName.slice(1)}`;\n if (!this.dataset[dataName]) {\n this.dispatchEvent(new CustomEvent(`${dataName}-detached`, { bubbles: true }));\n } else if (mutation.oldValue) {\n this.dispatchEvent(\n new CustomEvent(`${dataName}-updated`, {\n bubbles: true,\n detail: { oldData: mrjs__WEBPACK_IMPORTED_MODULE_1__.mrjsUtils.string.jsonToString(mutation.oldValue) },\n })\n );\n } else {\n this.dispatchEvent(\n new CustomEvent(`${dataName}-attached`, {\n bubbles: true,\n })\n );\n }\n }\n\n /**\n * @function\n * @description Adding an entity as a sub-object of this entity.\n * @param {MREntity} entity - the entity to be added.\n */\n add(entity) {\n entity.object3D.receiveShadow = true;\n entity.object3D.renderOrder = 3;\n this.object3D.add(entity.object3D);\n }\n\n /**\n * @function\n * @description Removing an entity as a sub-object of this entity.\n * @param {MREntity} entity - the entity to be removed.\n */\n removeEntity(entity) {\n this.object3D.remove(entity.object3D);\n }\n\n /**\n * @function\n * @description Runs the passed through function on this object and every child of this object.\n * @param {Function} callBack - the function to run recursively.\n */\n traverse(callBack) {\n callBack(this);\n const children = Array.from(this.children);\n for (const child of children) {\n // if child is an entity, traverse it again\n if (child instanceof MREntity) {\n child.traverse(callBack);\n }\n }\n }\n\n /**\n * @function\n * @description Runs the passed through function on the objects associated with this Entity\n * @param {Function} callBack - the function to run recursively.\n */\n traverseObjects(callBack) {\n const traverse = (object) => {\n callBack(object);\n for (const child of object.children) {\n if (!child.userData.isEntityObject3DRoot) {\n traverse(child);\n }\n }\n };\n traverse(this.object3D);\n }\n}\n\ncustomElements.get('mr-entity') || customElements.define('mr-entity', MREntity);\n\n\n//# sourceURL=webpack://mrjs/./src/core/MREntity.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 // Recursively dispose of node (object3D) materials and geometries\n disposeNode(object3D);\n\n // Remove the object from the scene\n scene.remove(object3D);\n\n // Optional: Clean up references for GC if necessary\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\n\n\n\n//# sourceURL=webpack://mrjs/./src/utils/Model.js?"); /***/ }), diff --git a/src/core/MREntity.js b/src/core/MREntity.js index 4b805c0d..e8051d58 100644 --- a/src/core/MREntity.js +++ b/src/core/MREntity.js @@ -446,6 +446,8 @@ export class MREntity extends MRElement { this.observer.disconnect(); this.disconnected(); + + mrjsUtils.model.disposeObject3D(this.object3D); } /** diff --git a/src/utils/Model.js b/src/utils/Model.js index 7d2c333c..a0289a28 100644 --- a/src/utils/Model.js +++ b/src/utils/Model.js @@ -312,10 +312,7 @@ model.disposeObject3D = function (parentObject3D) { }; model.removeObject3DFromScene = function (object3D, scene) { - // Recursively dispose of node (object3D) materials and geometries - disposeNode(object3D); - - // Remove the object from the scene + model.disposeObject3D(object3D); scene.remove(object3D); // Optional: Clean up references for GC if necessary