// components.js import {Engine} from './engine.js'; import {World, WorldFrame} from './world.js'; import {Diorama} from './diorama.js'; import {SpacetimeController} from './controller.js'; // Global Engine Singleton let engineInstance = null; function getEngine() { if (!engineInstance) { engineInstance = new Engine(); document.body.appendChild(engineInstance.canvas) } return engineInstance; } export class MCWorldElement extends HTMLElement { connectedCallback() { if (this.world) return; // Read text content directly, allowing inline scripting const script = this.textContent || ''; this.world = new World(script); } } export class MCFrameElement extends HTMLElement { connectedCallback() { // Use a microtask to ensure children are parsed setTimeout(() => this.init(), 0); } init() { if (this.frame) return; let world; const worldId = this.getAttribute('worldid'); if (worldId) { world = document.getElementById(worldId)?.world; } else { const worldEl = this.querySelector('mc-world'); world = worldEl?.world; } if (world) { this.frame = new WorldFrame(world); } } } export class MCDioramaElement extends HTMLElement { connectedCallback() { setTimeout(() => this.init(), 0); } init() { const engine = getEngine(); let frame; // 1. Resolve the Frame context const frameId = this.getAttribute('frameid'); const worldId = this.getAttribute('worldid'); if (frameId) { frame = document.getElementById(frameId)?.frame; } else if (worldId) { const world = document.getElementById(worldId)?.world; if (world) frame = new WorldFrame(world); } else { const frameEl = this.querySelector('mc-frame'); if (frameEl) { frame = frameEl.frame; } else { const worldEl = this.querySelector('mc-world'); if (worldEl) frame = new WorldFrame(worldEl.world); } } if (!frame) { console.error("mc-diorama requires a resolved frame or world."); return; } // 2. Build the Diorama const diorama = new Diorama(this, frame, () => engine.requestRender()); // Process spatial attributes if (this.hasAttribute('radius')) diorama.radius = parseFloat(this.getAttribute('radius')); if (this.hasAttribute('theta')) diorama.theta = parseFloat(this.getAttribute('theta')); if (this.hasAttribute('phi')) diorama.phi = parseFloat(this.getAttribute('phi')); diorama.centerView(); diorama.saveState(); // Lock this in for the Reset button // 3. Resolve the Controllers const timelineEl = this.querySelector('mc-timeline'); const cameraEl = this.querySelector('mc-camera'); // Default to fully static const opts = { timeline: !!timelineEl, orbit: !!cameraEl, pan: !!cameraEl, zoom: !!cameraEl, autoplay: false, loop: false, subticks: false, }; if (timelineEl) { if (timelineEl.hasAttribute('autoplay')) opts.autoplay = timelineEl.getAttribute('autoplay') !== 'false'; if (timelineEl.hasAttribute('loop')) opts.loop = timelineEl.getAttribute('loop') !== 'false'; if (timelineEl.hasAttribute('subticks')) opts.subticks = timelineEl.getAttribute('subticks') !== 'false'; if (timelineEl.hasAttribute('controls')) opts.timeline = timelineEl.getAttribute('controls') !== 'false'; if (timelineEl.hasAttribute('speed')) opts.speed = parseFloat(timelineEl.getAttribute('speed')); } if (cameraEl) { if (cameraEl.hasAttribute('orbit')) opts.orbit = cameraEl.getAttribute('orbit') !== 'false'; if (cameraEl.hasAttribute('pan')) opts.pan = cameraEl.getAttribute('pan') !== 'false'; if (cameraEl.hasAttribute('zoom')) opts.zoom = cameraEl.getAttribute('zoom') !== 'false'; } this.controller = new SpacetimeController(diorama, opts); engine.addDiorama(diorama); } } // Register Elements customElements.define('mc-world', MCWorldElement); customElements.define('mc-frame', MCFrameElement); customElements.define('mc-diorama', MCDioramaElement);