@playcanvas/web-components
Version:
Web Components for the PlayCanvas Engine
1,075 lines (1,067 loc) • 189 kB
JavaScript
(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