UNPKG

@playcanvas/web-components

Version:

Web Components for the PlayCanvas Engine

1,075 lines (1,067 loc) 189 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('playcanvas')) : typeof define === 'function' && define.amd ? define(['exports', 'playcanvas'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.pd = {}, global.pc)); })(this, (function (exports, playcanvas) { 'use strict'; /** * Base class for all PlayCanvas Web Components that initialize asynchronously. */ class AsyncElement extends HTMLElement { /** @ignore */ constructor() { super(); this._readyPromise = new Promise((resolve) => { this._readyResolve = resolve; }); } get closestApp() { var _a; return (_a = this.parentElement) === null || _a === void 0 ? void 0 : _a.closest('pc-app'); } get closestEntity() { var _a; return (_a = this.parentElement) === null || _a === void 0 ? void 0 : _a.closest('pc-entity'); } /** * Called when the element is fully initialized and ready. * Subclasses should call this when they're ready. */ _onReady() { this._readyResolve(); this.dispatchEvent(new CustomEvent('ready')); } /** * Returns a promise that resolves with this element when it's ready. * @returns A promise that resolves with this element when it's ready. */ ready() { return this._readyPromise.then(() => this); } } /** * The ModuleElement interface provides properties and methods for manipulating * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-module/ | `<pc-module>`} elements. * The ModuleElement interface also inherits the properties and methods of the * {@link HTMLElement} interface. */ class ModuleElement extends HTMLElement { /** @ignore */ constructor() { super(); this.loadPromise = this.loadModule(); } async loadModule() { const name = this.getAttribute('name'); const glueUrl = this.getAttribute('glue'); const wasmUrl = this.getAttribute('wasm'); const fallbackUrl = this.getAttribute('fallback'); const config = { glueUrl, wasmUrl, fallbackUrl }; if (name === 'Basis') { playcanvas.basisInitialize(config); } else { playcanvas.WasmModule.setConfig(name, config); await new Promise((resolve) => { playcanvas.WasmModule.getInstance(name, () => resolve()); }); } } getLoadPromise() { return this.loadPromise; } } customElements.define('pc-module', ModuleElement); /** * The AppElement interface provides properties and methods for manipulating * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-app/ | `<pc-app>`} elements. * The AppElement interface also inherits the properties and methods of the * {@link HTMLElement} interface. */ class AppElement extends AsyncElement { /** * Creates a new AppElement instance. * * @ignore */ constructor() { super(); /** * The canvas element. */ this._canvas = null; this._alpha = true; this._backend = 'webgl2'; this._antialias = true; this._depth = true; this._stencil = true; this._highResolution = true; this._hierarchyReady = false; this._picker = null; this._hasPointerListeners = { pointerenter: false, pointerleave: false, pointerdown: false, pointerup: false, pointermove: false }; this._hoveredEntity = null; this._pointerHandlers = { pointermove: null, pointerdown: null, pointerup: null }; /** * The PlayCanvas application instance. */ this.app = null; // Bind methods to maintain 'this' context this._onWindowResize = this._onWindowResize.bind(this); } async connectedCallback() { // Get all pc-module elements that are direct children of the pc-app element const moduleElements = this.querySelectorAll(':scope > pc-module'); // Wait for all modules to load await Promise.all(Array.from(moduleElements).map(module => module.getLoadPromise())); // Create and append the canvas to the element this._canvas = document.createElement('canvas'); this.appendChild(this._canvas); // Configure device types based on backend selection const backendToDeviceTypes = { webgpu: ['webgpu', 'webgl2'], // fallback to webgl2 if webgpu not available webgl2: ['webgl2'], null: ['null'] }; const deviceTypes = backendToDeviceTypes[this._backend] || []; const device = await playcanvas.createGraphicsDevice(this._canvas, { // @ts-ignore - alpha needs to be documented alpha: this._alpha, antialias: this._antialias, depth: this._depth, deviceTypes: deviceTypes, stencil: this._stencil }); device.maxPixelRatio = this._highResolution ? window.devicePixelRatio : 1; const createOptions = new playcanvas.AppOptions(); createOptions.graphicsDevice = device; createOptions.keyboard = new playcanvas.Keyboard(window); createOptions.mouse = new playcanvas.Mouse(this._canvas); createOptions.componentSystems = [ playcanvas.AnimComponentSystem, playcanvas.AnimationComponentSystem, playcanvas.AudioListenerComponentSystem, playcanvas.ButtonComponentSystem, playcanvas.CameraComponentSystem, playcanvas.CollisionComponentSystem, playcanvas.ElementComponentSystem, playcanvas.GSplatComponentSystem, playcanvas.JointComponentSystem, playcanvas.LayoutChildComponentSystem, playcanvas.LayoutGroupComponentSystem, playcanvas.LightComponentSystem, playcanvas.ModelComponentSystem, playcanvas.ParticleSystemComponentSystem, playcanvas.RenderComponentSystem, playcanvas.RigidBodyComponentSystem, playcanvas.ScreenComponentSystem, playcanvas.ScriptComponentSystem, playcanvas.ScrollbarComponentSystem, playcanvas.ScrollViewComponentSystem, playcanvas.SoundComponentSystem, playcanvas.SpriteComponentSystem, playcanvas.ZoneComponentSystem ]; createOptions.resourceHandlers = [ playcanvas.AnimClipHandler, playcanvas.AnimationHandler, playcanvas.AnimStateGraphHandler, playcanvas.AudioHandler, playcanvas.BinaryHandler, playcanvas.CssHandler, playcanvas.ContainerHandler, playcanvas.CubemapHandler, playcanvas.FolderHandler, playcanvas.FontHandler, playcanvas.GSplatHandler, playcanvas.HierarchyHandler, playcanvas.HtmlHandler, playcanvas.JsonHandler, playcanvas.MaterialHandler, playcanvas.ModelHandler, playcanvas.RenderHandler, playcanvas.ScriptHandler, playcanvas.SceneHandler, playcanvas.ShaderHandler, playcanvas.SpriteHandler, playcanvas.TemplateHandler, playcanvas.TextHandler, playcanvas.TextureAtlasHandler, playcanvas.TextureHandler ]; createOptions.soundManager = new playcanvas.SoundManager(); createOptions.lightmapper = playcanvas.Lightmapper; createOptions.batchManager = playcanvas.BatchManager; createOptions.xr = playcanvas.XrManager; this.app = new playcanvas.AppBase(this._canvas); this.app.init(createOptions); this.app.setCanvasFillMode(playcanvas.FILLMODE_FILL_WINDOW); this.app.setCanvasResolution(playcanvas.RESOLUTION_AUTO); this._pickerCreate(); // Get all pc-asset elements that are direct children of the pc-app element const assetElements = this.querySelectorAll(':scope > pc-asset'); Array.from(assetElements).forEach((assetElement) => { assetElement.createAsset(); const asset = assetElement.asset; if (asset) { this.app.assets.add(asset); } }); // Get all pc-material elements that are direct children of the pc-app element const materialElements = this.querySelectorAll(':scope > pc-material'); Array.from(materialElements).forEach((materialElement) => { materialElement.createMaterial(); }); // Create all entities const entityElements = this.querySelectorAll('pc-entity'); Array.from(entityElements).forEach((entityElement) => { entityElement.createEntity(this.app); }); // Build hierarchy entityElements.forEach((entityElement) => { entityElement.buildHierarchy(this.app); }); this._hierarchyReady = true; // Load assets before starting the application this.app.preload(() => { // Start the application this.app.start(); // Handle window resize to keep the canvas responsive window.addEventListener('resize', this._onWindowResize); this._onReady(); }); } disconnectedCallback() { this._pickerDestroy(); // Clean up the application if (this.app) { this.app.destroy(); this.app = null; } // Remove event listeners window.removeEventListener('resize', this._onWindowResize); // Remove the canvas if (this._canvas && this.contains(this._canvas)) { this.removeChild(this._canvas); this._canvas = null; } } _onWindowResize() { if (this.app) { this.app.resizeCanvas(); } } _pickerCreate() { const { width, height } = this.app.graphicsDevice; this._picker = new playcanvas.Picker(this.app, width, height); // Create bound handlers but don't attach them yet this._pointerHandlers.pointermove = this._onPointerMove.bind(this); this._pointerHandlers.pointerdown = this._onPointerDown.bind(this); this._pointerHandlers.pointerup = this._onPointerUp.bind(this); // Listen for pointer listeners being added/removed ['pointermove', 'pointerdown', 'pointerup', 'pointerenter', 'pointerleave'].forEach((type) => { this.addEventListener(`${type}:connect`, () => this._onPointerListenerAdded(type)); this.addEventListener(`${type}:disconnect`, () => this._onPointerListenerRemoved(type)); }); } _pickerDestroy() { if (this._canvas) { Object.entries(this._pointerHandlers).forEach(([type, handler]) => { if (handler) { this._canvas.removeEventListener(type, handler); } }); } this._picker = null; this._pointerHandlers = { pointermove: null, pointerdown: null, pointerup: null }; } // New helper to convert CSS coordinates to canvas (picker) coordinates _getPickerCoordinates(event) { // Get the canvas' bounding rectangle in CSS pixels. const canvasRect = this._canvas.getBoundingClientRect(); // Compute scale factors based on canvas actual resolution vs. its CSS display size. const scaleX = this._canvas.width / canvasRect.width; const scaleY = this._canvas.height / canvasRect.height; // Convert the client coordinates accordingly. const x = (event.clientX - canvasRect.left) * scaleX; const y = (event.clientY - canvasRect.top) * scaleY; return { x, y }; } _onPointerMove(event) { if (!this._picker || !this.app) return; const camera = this.app.root.findComponent('camera'); if (!camera) return; // Use the helper to convert event coordinates into canvas/picker coordinates. const { x, y } = this._getPickerCoordinates(event); this._picker.prepare(camera, this.app.scene); const selection = this._picker.getSelection(x, y); // Get the currently hovered entity by walking up the hierarchy let newHoverEntity = null; if (selection.length > 0) { let currentNode = selection[0].node; while (currentNode !== null) { const entityElement = this.querySelector(`pc-entity[name="${currentNode.name}"]`); if (entityElement) { newHoverEntity = entityElement; break; } currentNode = currentNode.parent; } } // Handle enter/leave events if (this._hoveredEntity !== newHoverEntity) { if (this._hoveredEntity && this._hoveredEntity.hasListeners('pointerleave')) { this._hoveredEntity.dispatchEvent(new PointerEvent('pointerleave', event)); } if (newHoverEntity && newHoverEntity.hasListeners('pointerenter')) { newHoverEntity.dispatchEvent(new PointerEvent('pointerenter', event)); } } // Update hover state this._hoveredEntity = newHoverEntity; // Handle pointermove event if (newHoverEntity && newHoverEntity.hasListeners('pointermove')) { newHoverEntity.dispatchEvent(new PointerEvent('pointermove', event)); } } _onPointerDown(event) { if (!this._picker || !this.app) return; const camera = this.app.root.findComponent('camera'); if (!camera) return; // Convert the event's pointer coordinates const { x, y } = this._getPickerCoordinates(event); this._picker.prepare(camera, this.app.scene); const selection = this._picker.getSelection(x, y); if (selection.length > 0) { let currentNode = selection[0].node; while (currentNode !== null) { const entityElement = this.querySelector(`pc-entity[name="${currentNode.name}"]`); if (entityElement && entityElement.hasListeners('pointerdown')) { entityElement.dispatchEvent(new PointerEvent('pointerdown', event)); break; } currentNode = currentNode.parent; } } } _onPointerUp(event) { if (!this._picker || !this.app) return; const camera = this.app.root.findComponent('camera'); if (!camera) return; // Convert CSS coordinates to picker coordinates const { x, y } = this._getPickerCoordinates(event); this._picker.prepare(camera, this.app.scene); const selection = this._picker.getSelection(x, y); if (selection.length > 0) { const entityElement = this.querySelector(`pc-entity[name="${selection[0].node.name}"]`); if (entityElement && entityElement.hasListeners('pointerup')) { entityElement.dispatchEvent(new PointerEvent('pointerup', event)); } } } _onPointerListenerAdded(type) { if (!this._hasPointerListeners[type] && this._canvas) { this._hasPointerListeners[type] = true; // For enter/leave events, we need the move handler const handler = (type === 'pointerenter' || type === 'pointerleave') ? this._pointerHandlers.pointermove : this._pointerHandlers[type]; if (handler) { this._canvas.addEventListener(type === 'pointerenter' || type === 'pointerleave' ? 'pointermove' : type, handler); } } } _onPointerListenerRemoved(type) { const hasListeners = Array.from(this.querySelectorAll('pc-entity')) .some(entity => entity.hasListeners(type)); if (!hasListeners && this._canvas) { this._hasPointerListeners[type] = false; const handler = (type === 'pointerenter' || type === 'pointerleave') ? this._pointerHandlers.pointermove : this._pointerHandlers[type]; if (handler) { this._canvas.removeEventListener(type === 'pointerenter' || type === 'pointerleave' ? 'pointermove' : type, handler); } } } /** * Sets the alpha flag. * @param value - The alpha flag. */ set alpha(value) { this._alpha = value; } /** * Gets the alpha flag. * @returns The alpha flag. */ get alpha() { return this._alpha; } /** * Sets the antialias flag. * @param value - The antialias flag. */ set antialias(value) { this._antialias = value; } /** * Gets the antialias flag. * @returns The antialias flag. */ get antialias() { return this._antialias; } /** * Sets the graphics backend. * @param value - The graphics backend ('webgpu', 'webgl2', or 'null'). */ set backend(value) { this._backend = value; } /** * Gets the graphics backend. * @returns The graphics backend. */ get backend() { return this._backend; } /** * Sets the depth flag. * @param value - The depth flag. */ set depth(value) { this._depth = value; } /** * Gets the depth flag. * @returns The depth flag. */ get depth() { return this._depth; } /** * Gets the hierarchy ready flag. * @returns The hierarchy ready flag. * @ignore */ get hierarchyReady() { return this._hierarchyReady; } /** * Sets the high resolution flag. When true, the application will render at the device's * physical resolution. When false, the application will render at CSS resolution. * @param value - The high resolution flag. */ set highResolution(value) { this._highResolution = value; if (this.app) { this.app.graphicsDevice.maxPixelRatio = value ? window.devicePixelRatio : 1; } } /** * Gets the high resolution flag. * @returns The high resolution flag. */ get highResolution() { return this._highResolution; } /** * Sets the stencil flag. * @param value - The stencil flag. */ set stencil(value) { this._stencil = value; } /** * Gets the stencil flag. * @returns The stencil flag. */ get stencil() { return this._stencil; } static get observedAttributes() { return ['alpha', 'antialias', 'backend', 'depth', 'stencil', 'high-resolution']; } attributeChangedCallback(name, _oldValue, newValue) { switch (name) { case 'alpha': this.alpha = newValue !== 'false'; break; case 'antialias': this.antialias = newValue !== 'false'; break; case 'backend': if (newValue === 'webgpu' || newValue === 'webgl2' || newValue === 'null') { this.backend = newValue; } break; case 'depth': this.depth = newValue !== 'false'; break; case 'high-resolution': this.highResolution = newValue !== 'false'; break; case 'stencil': this.stencil = newValue !== 'false'; break; } } } customElements.define('pc-app', AppElement); const CSS_COLORS = { aliceblue: '#f0f8ff', antiquewhite: '#faebd7', aqua: '#00ffff', aquamarine: '#7fffd4', azure: '#f0ffff', beige: '#f5f5dc', bisque: '#ffe4c4', black: '#000000', blanchedalmond: '#ffebcd', blue: '#0000ff', blueviolet: '#8a2be2', brown: '#a52a2a', burlywood: '#deb887', cadetblue: '#5f9ea0', chartreuse: '#7fff00', chocolate: '#d2691e', coral: '#ff7f50', cornflowerblue: '#6495ed', cornsilk: '#fff8dc', crimson: '#dc143c', cyan: '#00ffff', darkblue: '#00008b', darkcyan: '#008b8b', darkgoldenrod: '#b8860b', darkgray: '#a9a9a9', darkgreen: '#006400', darkgrey: '#a9a9a9', darkkhaki: '#bdb76b', darkmagenta: '#8b008b', darkolivegreen: '#556b2f', darkorange: '#ff8c00', darkorchid: '#9932cc', darkred: '#8b0000', darksalmon: '#e9967a', darkseagreen: '#8fbc8f', darkslateblue: '#483d8b', darkslategray: '#2f4f4f', darkslategrey: '#2f4f4f', darkturquoise: '#00ced1', darkviolet: '#9400d3', deeppink: '#ff1493', deepskyblue: '#00bfff', dimgray: '#696969', dimgrey: '#696969', dodgerblue: '#1e90ff', firebrick: '#b22222', floralwhite: '#fffaf0', forestgreen: '#228b22', fuchsia: '#ff00ff', gainsboro: '#dcdcdc', ghostwhite: '#f8f8ff', gold: '#ffd700', goldenrod: '#daa520', gray: '#808080', green: '#008000', greenyellow: '#adff2f', grey: '#808080', honeydew: '#f0fff0', hotpink: '#ff69b4', indianred: '#cd5c5c', indigo: '#4b0082', ivory: '#fffff0', khaki: '#f0e68c', lavender: '#e6e6fa', lavenderblush: '#fff0f5', lawngreen: '#7cfc00', lemonchiffon: '#fffacd', lightblue: '#add8e6', lightcoral: '#f08080', lightcyan: '#e0ffff', lightgoldenrodyellow: '#fafad2', lightgray: '#d3d3d3', lightgreen: '#90ee90', lightgrey: '#d3d3d3', lightpink: '#ffb6c1', lightsalmon: '#ffa07a', lightseagreen: '#20b2aa', lightskyblue: '#87cefa', lightslategray: '#778899', lightslategrey: '#778899', lightsteelblue: '#b0c4de', lightyellow: '#ffffe0', lime: '#00ff00', limegreen: '#32cd32', linen: '#faf0e6', magenta: '#ff00ff', maroon: '#800000', mediumaquamarine: '#66cdaa', mediumblue: '#0000cd', mediumorchid: '#ba55d3', mediumpurple: '#9370db', mediumseagreen: '#3cb371', mediumslateblue: '#7b68ee', mediumspringgreen: '#00fa9a', mediumturquoise: '#48d1cc', mediumvioletred: '#c71585', midnightblue: '#191970', mintcream: '#f5fffa', mistyrose: '#ffe4e1', moccasin: '#ffe4b5', navajowhite: '#ffdead', navy: '#000080', oldlace: '#fdf5e6', olive: '#808000', olivedrab: '#6b8e23', orange: '#ffa500', orangered: '#ff4500', orchid: '#da70d6', palegoldenrod: '#eee8aa', palegreen: '#98fb98', paleturquoise: '#afeeee', palevioletred: '#db7093', papayawhip: '#ffefd5', peachpuff: '#ffdab9', peru: '#cd853f', pink: '#ffc0cb', plum: '#dda0dd', powderblue: '#b0e0e6', purple: '#800080', rebeccapurple: '#663399', red: '#ff0000', rosybrown: '#bc8f8f', royalblue: '#4169e1', saddlebrown: '#8b4513', salmon: '#fa8072', sandybrown: '#f4a460', seagreen: '#2e8b57', seashell: '#fff5ee', sienna: '#a0522d', silver: '#c0c0c0', skyblue: '#87ceeb', slateblue: '#6a5acd', slategray: '#708090', slategrey: '#708090', snow: '#fffafa', springgreen: '#00ff7f', steelblue: '#4682b4', tan: '#d2b48c', teal: '#008080', thistle: '#d8bfd8', tomato: '#ff6347', turquoise: '#40e0d0', violet: '#ee82ee', wheat: '#f5deb3', white: '#ffffff', whitesmoke: '#f5f5f5', yellow: '#ffff00', yellowgreen: '#9acd32' }; /** * Parse a color string into a Color object. String can be in the format of '#rgb', '#rgba', * '#rrggbb', '#rrggbbaa', or a string of 3 or 4 comma-delimited numbers. * * @param value - The color string to parse. * @returns The parsed Color object. */ const parseColor = (value) => { // Check if it's a CSS color name first const hexColor = CSS_COLORS[value.toLowerCase()]; if (hexColor) { return new playcanvas.Color().fromString(hexColor); } if (value.startsWith('#')) { return new playcanvas.Color().fromString(value); } const components = value.split(' ').map(Number); return new playcanvas.Color(components); }; /** * Parse an Euler angles string into a Quat object. String can be in the format of 'x,y,z'. * * @param value - The Euler angles string to parse. * @returns The parsed Quat object. */ const parseQuat = (value) => { const [x, y, z] = value.split(' ').map(Number); const q = new playcanvas.Quat(); q.setFromEulerAngles(x, y, z); return q; }; /** * Parse a Vec2 string into a Vec2 object. String can be in the format of 'x,y'. * * @param value - The Vec2 string to parse. * @returns The parsed Vec2 object. */ const parseVec2 = (value) => { const components = value.split(' ').map(Number); return new playcanvas.Vec2(components); }; /** * Parse a Vec3 string into a Vec3 object. String can be in the format of 'x,y,z'. * * @param value - The Vec3 string to parse. * @returns The parsed Vec3 object. */ const parseVec3 = (value) => { const components = value.split(' ').map(Number); return new playcanvas.Vec3(components); }; /** * Parse a Vec4 string into a Vec4 object. String can be in the format of 'x,y,z,w'. * * @param value - The Vec4 string to parse. * @returns The parsed Vec4 object. */ const parseVec4 = (value) => { const components = value.split(' ').map(Number); return new playcanvas.Vec4(components); }; /** * The EntityElement interface provides properties and methods for manipulating * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-entity/ | `<pc-entity>`} elements. * The EntityElement interface also inherits the properties and methods of the * {@link HTMLElement} interface. */ class EntityElement extends AsyncElement { constructor() { super(...arguments); /** * Whether the entity is enabled. */ this._enabled = true; /** * The name of the entity. */ this._name = 'Untitled'; /** * The position of the entity. */ this._position = new playcanvas.Vec3(); /** * The rotation of the entity. */ this._rotation = new playcanvas.Vec3(); /** * The scale of the entity. */ this._scale = new playcanvas.Vec3(1, 1, 1); /** * The tags of the entity. */ this._tags = []; /** * The pointer event listeners for the entity. */ this._listeners = {}; /** * The PlayCanvas entity instance. */ this.entity = null; } createEntity(app) { // Create a new entity this.entity = new playcanvas.Entity(this.getAttribute('name') || this._name, app); const enabled = this.getAttribute('enabled'); if (enabled) { this.entity.enabled = enabled !== 'false'; } const position = this.getAttribute('position'); if (position) { this.entity.setLocalPosition(parseVec3(position)); } const rotation = this.getAttribute('rotation'); if (rotation) { this.entity.setLocalEulerAngles(parseVec3(rotation)); } const scale = this.getAttribute('scale'); if (scale) { this.entity.setLocalScale(parseVec3(scale)); } const tags = this.getAttribute('tags'); if (tags) { this.entity.tags.add(tags.split(',').map(tag => tag.trim())); } // Handle pointer events const pointerEvents = [ 'onpointerenter', 'onpointerleave', 'onpointerdown', 'onpointerup', 'onpointermove' ]; pointerEvents.forEach((eventName) => { const handler = this.getAttribute(eventName); if (handler) { const eventType = eventName.substring(2); // remove 'on' prefix const eventHandler = (event) => { try { /* eslint-disable-next-line no-new-func */ new Function('event', handler).call(this, event); } catch (e) { console.error('Error in event handler:', e); } }; this.addEventListener(eventType, eventHandler); } }); } buildHierarchy(app) { if (!this.entity) return; const closestEntity = this.closestEntity; if (closestEntity === null || closestEntity === void 0 ? void 0 : closestEntity.entity) { closestEntity.entity.addChild(this.entity); } else { app.root.addChild(this.entity); } this._onReady(); } connectedCallback() { // Wait for app to be ready const closestApp = this.closestApp; if (!closestApp) return; // If app is already running, create entity immediately if (closestApp.hierarchyReady) { const app = closestApp.app; this.createEntity(app); this.buildHierarchy(app); // Handle any child entities that might exist const childEntities = this.querySelectorAll('pc-entity'); childEntities.forEach((child) => { child.createEntity(app); }); childEntities.forEach((child) => { child.buildHierarchy(app); }); } } disconnectedCallback() { if (this.entity) { // Notify all children that their entities are about to become invalid const children = this.querySelectorAll('pc-entity'); children.forEach((child) => { child.entity = null; }); // Destroy the entity this.entity.destroy(); this.entity = null; } } /** * Sets the enabled state of the entity. * @param value - Whether the entity is enabled. */ set enabled(value) { this._enabled = value; if (this.entity) { this.entity.enabled = value; } } /** * Gets the enabled state of the entity. * @returns Whether the entity is enabled. */ get enabled() { return this._enabled; } /** * Sets the name of the entity. * @param value - The name of the entity. */ set name(value) { this._name = value; if (this.entity) { this.entity.name = value; } } /** * Gets the name of the entity. * @returns The name of the entity. */ get name() { return this._name; } /** * Sets the position of the entity. * @param value - The position of the entity. */ set position(value) { this._position = value; if (this.entity) { this.entity.setLocalPosition(this._position); } } /** * Gets the position of the entity. * @returns The position of the entity. */ get position() { return this._position; } /** * Sets the rotation of the entity. * @param value - The rotation of the entity. */ set rotation(value) { this._rotation = value; if (this.entity) { this.entity.setLocalEulerAngles(this._rotation); } } /** * Gets the rotation of the entity. * @returns The rotation of the entity. */ get rotation() { return this._rotation; } /** * Sets the scale of the entity. * @param value - The scale of the entity. */ set scale(value) { this._scale = value; if (this.entity) { this.entity.setLocalScale(this._scale); } } /** * Gets the scale of the entity. * @returns The scale of the entity. */ get scale() { return this._scale; } /** * Sets the tags of the entity. * @param value - The tags of the entity. */ set tags(value) { this._tags = value; if (this.entity) { this.entity.tags.clear(); this.entity.tags.add(this._tags); } } /** * Gets the tags of the entity. * @returns The tags of the entity. */ get tags() { return this._tags; } static get observedAttributes() { return [ 'enabled', 'name', 'position', 'rotation', 'scale', 'tags', 'onpointerenter', 'onpointerleave', 'onpointerdown', 'onpointerup', 'onpointermove' ]; } attributeChangedCallback(name, _oldValue, newValue) { switch (name) { case 'enabled': this.enabled = newValue !== 'false'; break; case 'name': this.name = newValue; break; case 'position': this.position = parseVec3(newValue); break; case 'rotation': this.rotation = parseVec3(newValue); break; case 'scale': this.scale = parseVec3(newValue); break; case 'tags': this.tags = newValue.split(',').map(tag => tag.trim()); break; case 'onpointerenter': case 'onpointerleave': case 'onpointerdown': case 'onpointerup': case 'onpointermove': if (newValue) { const eventName = name.substring(2); // Use Function.prototype.bind to avoid new Function const handler = (event) => { try { const handlerStr = this.getAttribute(eventName) || ''; /* eslint-disable-next-line no-new-func */ new Function('event', handlerStr).call(this, event); } catch (e) { console.error('Error in event handler:', e); } }; this.addEventListener(eventName, handler); } break; } } addEventListener(type, listener, options) { if (!this._listeners[type]) { this._listeners[type] = []; } this._listeners[type].push(listener); super.addEventListener(type, listener, options); if (type.startsWith('pointer')) { this.dispatchEvent(new CustomEvent(`${type}:connect`, { bubbles: true })); } } removeEventListener(type, listener, options) { if (this._listeners[type]) { this._listeners[type] = this._listeners[type].filter(l => l !== listener); } super.removeEventListener(type, listener, options); if (type.startsWith('pointer')) { this.dispatchEvent(new CustomEvent(`${type}:disconnect`, { bubbles: true })); } } hasListeners(type) { var _a; return Boolean((_a = this._listeners[type]) === null || _a === void 0 ? void 0 : _a.length); } } customElements.define('pc-entity', EntityElement); // This file is part of meshoptimizer library and is distributed under the terms of MIT License. // Copyright (C) 2016-2022, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) var MeshoptDecoder = (function() { // Built with clang version 14.0.4 // Built from meshoptimizer 0.18 var wasm_base = "b9H79Tebbbe8Fv9Gbb9Gvuuuuueu9Giuuub9Geueu9Giuuueuikqbeeedddillviebeoweuec:q;iekr;leDo9TW9T9VV95dbH9F9F939H79T9F9J9H229F9Jt9VV7bb8A9TW79O9V9Wt9F9KW9J9V9KW9wWVtW949c919M9MWVbeY9TW79O9V9Wt9F9KW9J9V9KW69U9KW949c919M9MWVbdE9TW79O9V9Wt9F9KW9J9V9KW69U9KW949tWG91W9U9JWbiL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9p9JtblK9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9r919HtbvL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWVT949Wbol79IV9Rbrq:P8Yqdbk;3sezu8Jjjjjbcj;eb9Rgv8Kjjjjbc9:hodnadcefal0mbcuhoaiRbbc:Ge9hmbavaialfgrad9Radz1jjjbhwcj;abad9UhoaicefhldnadTmbaoc;WFbGgocjdaocjd6EhDcbhqinaqae9pmeaDaeaq9RaqaDfae6Egkcsfgocl4cifcd4hxdndndndnaoc9WGgmTmbcbhPcehsawcjdfhzalhHinaraH9Rax6midnaraHaxfgl9RcK6mbczhoinawcj;cbfaogifgoc9WfhOdndndndndnaHaic9WfgAco4fRbbaAci4coG4ciGPlbedibkaO9cb83ibaOcwf9cb83ibxikaOalRblalRbbgAco4gCaCciSgCE86bbaocGfalclfaCfgORbbaAcl4ciGgCaCciSgCE86bbaocVfaOaCfgORbbaAcd4ciGgCaCciSgCE86bbaoc7faOaCfgORbbaAciGgAaAciSgAE86bbaoctfaOaAfgARbbalRbegOco4gCaCciSgCE86bbaoc91faAaCfgARbbaOcl4ciGgCaCciSgCE86bbaoc4faAaCfgARbbaOcd4ciGgCaCciSgCE86bbaoc93faAaCfgARbbaOciGgOaOciSgOE86bbaoc94faAaOfgARbbalRbdgOco4gCaCciSgCE86bbaoc95faAaCfgARbbaOcl4ciGgCaCciSgCE86bbaoc96faAaCfgARbbaOcd4ciGgCaCciSgCE86bbaoc97faAaCfgARbbaOciGgOaOciSgOE86bbaoc98faAaOfgORbbalRbiglco4gAaAciSgAE86bbaoc99faOaAfgORbbalcl4ciGgAaAciSgAE86bbaoc9:faOaAfgORbbalcd4ciGgAaAciSgAE86bbaocufaOaAfgoRbbalciGglalciSglE86bbaoalfhlxdkaOalRbwalRbbgAcl4gCaCcsSgCE86bbaocGfalcwfaCfgORbbaAcsGgAaAcsSgAE86bbaocVfaOaAfgORbbalRbegAcl4gCaCcsSgCE86bbaoc7faOaCfgORbbaAcsGgAaAcsSgAE86bbaoctfaOaAfgORbbalRbdgAcl4gCaCcsSgCE86bbaoc91faOaCfgORbbaAcsGgAaAcsSgAE86bbaoc4faOaAfgORbbalRbigAcl4gCaCcsSgCE86bbaoc93faOaCfgORbbaAcsGgAaAcsSgAE86bbaoc94faOaAfgORbbalRblgAcl4gCaCcsSgCE86bbaoc95faOaCfgORbbaAcsGgAaAcsSgAE86bbaoc96faOaAfgORbbalRbvgAcl4gCaCcsSgCE86bbaoc97faOaCfgORbbaAcsGgAaAcsSgAE86bbaoc98faOaAfgORbbalRbogAcl4gCaCcsSgCE86bbaoc99faOaCfgORbbaAcsGgAaAcsSgAE86bbaoc9:faOaAfgORbbalRbrglcl4gAaAcsSgAE86bbaocufaOaAfgoRbbalcsGglalcsSglE86bbaoalfhlxekaOal8Pbb83bbaOcwfalcwf8Pbb83bbalczfhlkdnaiam9pmbaiczfhoaral9RcL0mekkaiam6mialTmidnakTmbawaPfRbbhOcbhoazhiinaiawcj;cbfaofRbbgAce4cbaAceG9R7aOfgO86bbaiadfhiaocefgoak9hmbkkazcefhzaPcefgPad6hsalhHaPad9hmexvkkcbhlasceGmdxikalaxad2fhCdnakTmbcbhHcehsawcjdfhminaral9Rax6mialTmdalaxfhlawaHfRbbhOcbhoamhiinaiawcj;cbfaofRbbgAce4cbaAceG9R7aOfgO86bbaiadfhiaocefgoak9hmbkamcefhmaHcefgHad6hsaHad9hmbkaChlxikcbhocehsinaral9Rax6mdalTmealaxfhlaocefgoad6hsadao9hmbkaChlxdkcbhlasceGTmekc9:hoxikabaqad2fawcjdfakad2z1jjjb8Aawawcjdfakcufad2fadz1jjjb8Aakaqfhqalmbkc9:hoxekcbc99aral9Radcaadca0ESEhokavcj;ebf8Kjjjjbaok;yzeHu8Jjjjjbc;ae9Rgv8Kjjjjbc9:hodnaeci9UgrcHfal0mbcuhoaiRbbgwc;WeGc;Ge9hmbawcsGgDce0mbavc;abfcFecjez:jjjjb8AavcUf9cu83ibavc8Wf9cu83ibavcyf9cu83ibavcaf9cu83ibavcKf9cu83ibavczf9cu83ibav9cu83iwav9cu83ibaialfc9WfhqaicefgwarfhodnaeTmbcmcsaDceSEhkcbhxcbhmcbhDcbhicbhlindnaoaq9nmbc9:hoxikdndnawRbbgrc;Ve0mbavc;abfalarcl4cu7fcsGcitfgPydlhsaPydbhzdnarcsGgPak9pmbavaiarcu7fcsGcdtfydbaxaPEhraPThPdndnadcd9hmbabaDcetfgHaz87ebaHcdfas87ebaHclfar87ebxekabaDcdtfgHazBdbaHclfasBdbaHcwfarBdbkaxaPfhxavc;abfalcitfgHarBdbaHasBdlavaicdtfarBdbavc;abfalcefcsGglcitfgHazBdbaHarBdlaiaPfhialcefhlxdkdndnaPcsSmbamaPfaPc987fcefhmxekaocefhrao8SbbgPcFeGhHdndnaPcu9mmbarhoxekaocvfhoaHcFbGhHcrhPdninar8SbbgOcFbGaPtaHVhHaOcu9kmearcefhraPcrfgPc8J9hmbxdkkarcefhokaHce4cbaHceG9R7amfhmkdndnadcd9hmbabaDcetfgraz87ebarcdfas87ebarclfam87ebxekabaDcdtfgrazBdbarclfasBdbarcwfamBdbkavc;abfalcitfgramBdbarasBdlavaicdtfamBdbavc;abfalcefcsGglcitfgrazBdbaramBdlaicefhialcefhlxekdnarcpe0mbaxcefgOavaiaqarcsGfRbbgPcl49RcsGcdtfydbaPcz6gHEhravaiaP9RcsGcdtfydbaOaHfgsaPcsGgOEhPaOThOdndnadcd9hmbabaDcetfgzax87ebazcdfar87ebazclfaP87ebxekabaDcdtfgzaxBdbazclfarBdbazcwfaPBdbkavaicdtfaxBdbavc;abfalcitfgzarBdbazaxBdlavaicefgicsGcdtfarBdbavc;abfalcefcsGcitfgzaPBdbazarBdlavaiaHfcsGgicdtfaPBdbavc;abfalcdfcsGglcitfgraxBdbaraPBdlalcefhlaiaOfhiasaOfhxxekaxcbaoRbbgzEgAarc;:eSgrfhsazcsGhCazcl4hXdndnazcs0mbascefhOxekashOavaiaX9RcsGcdtfydbhskdndnaCmbaOcefhxxekaOhxavaiaz9RcsGcdtfydbhOkdndnarTmbaocefhrxekaocdfhrao8SbegHcFeGhPdnaHcu9kmbaocofhAaPcFbGhPcrhodninar8SbbgHcFbGaotaPVhPaHcu9kmearcefhraocrfgoc8J9hmbkaAhrxekarcefhrkaPce4cbaPceG9R7amfgmhAkdndnaXcsSmbarhPxekarcefhPar8SbbgocFeGhHdnaocu9kmbarcvfhsaHcFbGhHcrhodninaP8SbbgrcFbGaotaHVhHarcu9kmeaPcefhPaocrfgoc8J9hmbkashPxekaPcefhPkaHce4cbaHceG9R7amfgmhskdndnaCcsSmbaPhoxekaPcefhoaP8SbbgrcFeGhHdnarcu9kmbaPcvfhOaHcFbGhHcrhrdninao8SbbgPcFbGartaHVhHaPcu9kmeaocefhoarcrfgrc8J9hmbkaOhoxekaocefhokaHce4cbaHceG9R7amfgmhOkdndnadcd9hmbabaDcetfgraA87ebarcdfas87ebarclfaO87ebxekabaDcdtfgraABdbarclfasBdbarcwfaOBdbkavc;abfalcitfgrasBdbaraABdlavaicdtfaABdbavc;abfalcefcsGcitfgraOBdbarasBdlavaicefgicsGcdtfasBdbavc;abfalcdfcsGcitfgraABdbaraOBdlavaiazcz6aXcsSVfgicsGcdtfaOBdbaiaCTaCcsSVfhialcifhlkawcefhwalcsGhlaicsGhiaDcifgDae6mbkkcbc99aoaqSEhokavc;aef8Kjjjjbaok:llevu8Jjjjjbcz9Rhvc9:hodnaecvfal0mbcuhoaiRbbc;:eGc;qe9hmbav9cb83iwaicefhraialfc98fhwdnaeTmbdnadcdSmbcbhDindnaraw6mbc9:skarcefhoar8SbbglcFeGhidndnalcu9mmbaohrxekarcvfhraicFbGhicrhldninao8SbbgdcFbGaltaiVhiadcu9kmeaocefhoalcrfglc8J9hmbxdkkaocefhrkabaDcdtfaicd4cbaice4ceG9R7avcwfaiceGcdtVgoydbfglBdbaoalBdbaDcefgDae9hmbxdkkcbhDindnaraw6mbc9:skarcefhoar8SbbglcFeGhidndnalcu9mmbaohrxekarcvfhraicFbGhicrhldninao8SbbgdcFbGaltaiVhiadcu9kmeaocefhoalcrfglc8J9hmbxdkkaocefhrkabaDcetfaicd4cbaice4ceG9R7avcwfaiceGcdtVgoydbfgl87ebaoalBdbaDcefgDae9hmbkkcbc99arawSEhokaok:Lvoeue99dud99eud99dndnadcl9hmbaeTmeindndnabcdfgd8Sbb:Yab8Sbbgi:Ygl:l:tabcefgv8Sbbgo:Ygr:l:tgwJbb;:9cawawNJbbbbawawJbbbb9GgDEgq:mgkaqaicb9iEalMgwawNakaqaocb9iEarMgqaqNMM:r:vglNJbbbZJbbb:;aDEMgr:lJbbb9p9DTmbar:Ohixekcjjjj94hikadai86bbdndnaqalNJbbbZJbbb:;aqJbbbb9GEMgq:lJbbb9p9DTmbaq:Ohdxekcjjjj94hdkavad86bbdndnawalNJbbbZJbbb:;awJbbbb9GEMgw:lJbbb9p9DTmbaw:Ohdxekcjjjj94hdkabad86bbabclfhbaecufgembxdkkaeTmbindndnabclfgd8Ueb:Yab8Uebgi:Ygl:l:tabcdfgv8Uebgo:Ygr:l:tgwJb;:FSawawNJbbbbawawJbbbb9GgDEgq:mgkaqaicb9iEalMgwawNakaqaocb9iEarMgqaqNMM:r:vglNJbbbZJbbb:;aDEMgr:lJbbb9p9DTmbar:Ohixekcjjjj94hikadai87ebdndnaqalNJbbbZJbbb:;aqJbbbb9GEMgq:lJbbb9p9DTmbaq:Ohdxekcjjjj94hdkavad87ebdndnawalNJbbbZJbbb:;awJbbbb9GEMgw:lJbbb9p9DTmbaw:Ohdxekcjjjj94hdkabad87ebabcwfhbaecufgembkkk;siliui99iue99dnaeTmbcbhiabhlindndnJ;Zl81Zalcof8UebgvciV:Y:vgoal8Ueb:YNgrJb;:FSNJbbbZJbbb:;arJbbbb9GEMgw:lJbbb9p9DTmbaw:OhDxekcjjjj94hDkalclf8Uebhqalcdf8UebhkabavcefciGaiVcetfaD87ebdndnaoak:YNgwJb;:FSNJbbbZJbbb:;awJbbbb9GEMgx:lJbbb9p9DTmbax:Ohkxekcjjjj94hkkabavcdfciGaiVcetfak87ebdndnaoaq:YNgoJb;:FSNJbbbZJbbb:;aoJbbbb9GEMgx:lJbbb9p9DTmbax:Ohqxekcjjjj94hqkabavcufciGaiVcetfaq87ebdndnJbbjZararN:tawawN:taoaoN:tgrJbbbbarJbbbb9GE:rJb;:FSNJbbbZMgr:lJbbb9p9DTmbar:Ohqxekcjjjj94hqkabavciGaiVcetfaq87ebalcwfhlaiclfhiaecufgembkkk9mbdnadcd4ae2geTmbinababydbgdcwtcw91:Yadce91cjjj;8ifcjjj98G::NUdbabclfhbaecufgembkkk9teiucbcbydj1jjbgeabcifc98GfgbBdj1jjbdndnabZbcztgd9nmbcuhiabad9RcFFifcz4nbcuSmekaehikaik;LeeeudndnaeabVciGTmbabhixekdndnadcz9pmbabhixekabhiinaiaeydbBdbaiclfaeclfydbBdbaicwfaecwfydbBdbaicxfaecxfydbBdbaiczfhiaeczfheadc9Wfgdcs0mbkkadcl6mbinaiaeydbBdbaeclfheaiclfhiadc98fgdci0mbkkdnadTmbinaiaeRbb86bbaicefhiaecefheadcufgdmbkkabk;aeedudndnabciGTmbabhixekaecFeGc:b:c:ew2hldndnadcz9pmbabhixekabhiinaialBdbaicxfalBdbaicwfalBdbaiclfalBdbaiczfhiadc9Wfgdcs0mbkkadcl6mbinaialBdbaiclfhiadc98fgdci0mbkkdnadTmbinaiae86bbaicefhiadcufgdmbkkabkkkebcjwklz9Kbb"; var wasm_simd = "b9H79TebbbeKl9Gbb9Gvuuuuueu9Giuuub9Geueuikqbbebeedddilve9Weeeviebeoweuec:q;Aekr;leDo9TW9T9VV95dbH9F9F939H79T9F9J9H229F9Jt9VV7bb8A9TW79O9V9Wt9F9KW9J9V9KW9wWVtW949c919M9MWVbdY9TW79O9V9Wt9F9KW9J9V9KW69U9KW949c919M9MWVblE9TW79O9V9Wt9F9KW9J9V9KW69U9KW949tWG91W9U9JWbvL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9p9JtboK9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9r919HtbrL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWVT949Wbwl79IV9RbDq;t9tqlbzik9:evu8Jjjjjbcz9Rhbcbheincbhdcbhiinabcwfadfaicjuaead4ceGglE86bbaialfhiadcefgdcw9hmbkaec:q:yjjbfai86bbaecitc:q1jjbfab8Piw83ibaecefgecjd9hmbkk;h8JlHud97euo978Jjjjjbcj;kb9Rgv8Kjjjjbc9:hodnadcefal0mbcuhoaiRbbc:Ge9hmbavaialfgrad9Rad;8qbbcj;abad9UhoaicefhldnadTmbaoc;WFbGgocjdaocjd6EhwcbhDinaDae9pmeawaeaD9RaDawfae6Egqcsfgoc9WGgkci2hxakcethmaocl4cifcd4hPabaDad2fhscbhzdnincehHalhOcbhAdninaraO9RaP6miavcj;cbfaAak2fhCaOaPfhlcbhidnakc;ab6mbaral9Rc;Gb6mbcbhoinaCaofhidndndndndnaOaoco4fRbbgXciGPlbedibkaipxbbbbbbbbbbbbbbbbpklbxikaialpbblalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLgQcdp:meaQpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9ogLpxiiiiiiiiiiiiiiiip8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibaKc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spklbalclfaYpQbfaKc:q:yjjbfRbbfhlxdkaialpbbwalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9ogLpxssssssssssssssssp8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibaKc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spklbalcwfaYpQbfaKc:q:yjjbfRbbfhlxekaialpbbbpklbalczfhlkdndndndndnaXcd4ciGPlbedibkaipxbbbbbbbbbbbbbbbbpklzxikaialpbblalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLgQcdp:meaQpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9ogLpxiiiiiiiiiiiiiiiip8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibaKc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spklzalclfaYpQbfaKc:q:yjjbfRbbfhlxdkaialpbbwalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9ogLpxssssssssssssssssp8JgQp5b9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibaKc:q:yjjbfpbbbgYaYpmbbbbbbbbbbbbbbbbaQp5e9cjF;8;4;W;G;ab9:9cU1:NgKcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPaLaQp9spklzalcwfaYpQbfaKc:q:yjjbfRbbfhlxekaialpbbbpklzalczfhlkdndndndndnaXcl4ciGPlbedibkaipxbbbbbbbbbbbbbbbbpklaxikaialpbblalpbbbgQclp:meaQpmbzeHdOiAlCvXoQrLgQcdp:meaQpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9ogLpxiiiiiiiiiiiiiiiip8JgQp5b9cjF;8;4