
Thatopen Core Architecture
- 5 installs
- 17 repo stars
- Updated July 8, 2026
- openaec-foundation/thatopen-claude-skill-package
Helps with ai & agent building tasks.
About
thatopen-core-architecture is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- thatopen-core-architecture
- AI & Agent Building
- AI-coding skill
Thatopen Core Architecture by the numbers
- 5 all-time installs (skills.sh)
- Ranked #13,065 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 26, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/thatopen-claude-skill-package --skill thatopen-core-architectureAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 5 |
|---|---|
| repo stars | ★ 17 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/thatopen-claude-skill-package ↗ |
What it does
Helps with ai & agent building tasks.
Files
ThatOpen Core Architecture
Overview
ThatOpen engine_components is a component-based BIM/IFC viewer built on Three.js. It converts IFC models into optimized Fragment geometry (instanced meshes) and provides tools for visualization, selection, measurement, and classification.
Version: @thatopen/components 3.3.x License: MIT
Dependency Chain
@thatopen/ui-obc
└─ @thatopen/components-front (browser-only)
└─ @thatopen/components (core, browser + Node.js)
├─ @thatopen/fragments (FlatBuffers, web workers)
│ ├─ web-ifc (WASM IFC parser)
│ └─ three (3D rendering, >=0.175)
├─ three-mesh-bvh (accelerated raycasting)
├─ camera-controls (orbit/pan/zoom, >=3.1.2)
├─ jszip (BCF import/export)
└─ fast-xml-parser (BCF XML)Components Container (Singleton Registry)
Components is the top-level container. It manages all component instances and the animation loop.
import * as OBC from "@thatopen/components";
const components = new OBC.Components();Key behaviors:
- ALWAYS use
components.get(ComponentClass)to obtain a component instance.
NEVER instantiate a component with new directly.
get<U>()creates the instance on first call (lazy singleton pattern).
Subsequent calls return the same instance.
- Each component registers itself by its static
uuidin the constructor. init()starts therequestAnimationFrameloop usingTHREE.Clockfor
delta time. ALWAYS call init() after setting up your world.
dispose()iterates all components and disposes them.FragmentsManager
is ALWAYS disposed last to prevent dangling references.
- BVH acceleration is auto-patched onto
THREE.BufferGeometryin the
Components constructor. No manual BVH setup is needed.
See references/methods.md for full API signatures.
Component Base Class
Every ThatOpen component extends Component, which extends Base:
abstract class Base {
constructor(public components: Components) {}
isDisposeable(): this is Disposable;
isUpdateable(): this is Updateable;
isConfigurable(): this is Configurable<any, any>;
isResizeable(): this is Resizeable;
isHideable(): this is Hideable;
isSerializable(): this is Serializable<any>;
}
abstract class Component extends Base {
abstract enabled: boolean;
static readonly uuid: string;
}Key points:
- Every component MUST have a static
uuidstring. - Every component MUST have an
enabledproperty. - The
Baseclass provides runtime interface detection viais*()methods.
These use duck-typing, not instanceof checks.
- Components self-register in their constructor by calling
components.add(MyComponent.uuid, this).
Lifecycle Interfaces
Components opt into behaviors by implementing interfaces (mixin pattern). This replaces deep inheritance hierarchies.
| Interface | Methods / Properties | Purpose |
|---|---|---|
Disposable | dispose(), onDisposed: Event<void> | Cleanup resources |
Updateable | update(delta?), onBeforeUpdate, onAfterUpdate | Per-frame updates |
Configurable | setup(config?), config, isSetup, onSetup | Deferred init |
Resizeable | resize(), getSize(), onResize | Viewport resizing |
Hideable | visible: boolean | Show/hide |
Createable | create(), delete(), endCreation(), cancelCreation() | Interactive creation |
Serializable | import(), export() | Persistence |
Lifecycle flow:
Constructor → [setup()] → enabled=true → update() loop → dispose()
↑ ↑
Configurable only Updateable only- The
Componentsanimation loop callsupdate(delta)on EVERY enabled
Updateable component each frame.
Configurablecomponents ALWAYS require an explicitsetup()call before
they function. Check isSetup to verify.
- ALWAYS call
dispose()on cleanup. Failing to dispose causes memory leaks
that crash browser tabs with large BIM models.
World System
A World connects Scene + Camera + Renderer into a single viewport.
import * as OBC from "@thatopen/components";
const components = new OBC.Components();
const worlds = components.get(OBC.Worlds);
const world = worlds.create<
OBC.SimpleScene,
OBC.OrthoPerspectiveCamera,
OBC.SimpleRenderer
>();
world.scene = new OBC.SimpleScene(components);
world.scene.setup();
world.renderer = new OBC.SimpleRenderer(components, container);
world.camera = new OBC.OrthoPerspectiveCamera(components);
components.init();Abstraction layers:
| Abstract | Wraps | Access underlying Three.js |
|---|---|---|
BaseScene | THREE.Scene | .three |
BaseCamera | THREE.Camera | .three |
BaseRenderer | THREE.WebGLRenderer | .three |
SimpleWorld | Scene+Camera+Renderer | .scene / .camera / .renderer |
- ALWAYS access the underlying Three.js object via the
.threeproperty
when you need direct Three.js manipulation.
Worldsis itself aComponentthat implementsUpdateableand
Disposable. It manages a DataMap<string, World> of all worlds.
- When a world is disposed via
worlds.delete(world), its scene, camera,
and renderer are ALSO disposed automatically.
Package Split: Core vs Front
@thatopen/components (core)
Works in both browser and Node.js:
- Components container, event system, lifecycle interfaces
- World management (Worlds, SimpleWorld, SimpleScene, SimpleRenderer)
- Camera (OrthoPerspectiveCamera with camera-controls)
- Fragment loading (IfcLoader, FragmentsManager)
- Fragment manipulation (Hider, BoundingBoxer, Classifier, ItemsFinder)
- Raycasting, clipping planes (Clipper), grids
- OpenBIM standards (BCFTopics, IDSSpecifications)
@thatopen/components-front (front)
Browser-only (requires DOM and WebGL context):
- PostproductionRenderer (AO, edge detection, outlines, SMAA)
- Highlighter (selection visualization with color maps)
- Hoverer (fade animation hover effect) — NEW in v3
- Outliner (geometry outline rendering)
- Marker (2D screen-space annotations with clustering)
- Measurements (length, area, angle, volume)
- ClipStyler (section/plan edge visualization) — replaces v2 Plans/ClipEdges
- Mesher (convert fragments to regular THREE.Mesh) — NEW in v3
- FastModelPicker (GPU-based element picking) — NEW in v3
NEVER import from @thatopen/components-front in Node.js code. It will fail.
Event System
ThatOpen uses a custom pub/sub Event<T> class throughout:
class Event<T> {
add(handler: (data: T) => void): void;
remove(handler: (data: T) => void): void;
trigger(data?: T): void;
reset(): void;
enabled: boolean;
}- Events are used for lifecycle hooks (
onDisposed,onSetup), state
changes (onBeforeUpdate, onAfterUpdate), and domain events (onFragmentsLoaded).
- ALWAYS remove event handlers when disposing custom code to prevent leaks.
- Set
event.enabled = falseto temporarily suppress an event.
Reactive Collections: DataMap and DataSet
ThatOpen provides reactive wrappers around Map and Set:
- DataMap<K, V> — extends
MapwithonItemSet,onItemUpdated,
onItemDeleted, onCleared events.
- DataSet<T> — extends
SetwithonItemAdded,onItemDeleted,
onCleared events.
These are used throughout the API (e.g., Components.list, Worlds.list, Classifier.list, Highlighter.styles). ALWAYS use event hooks on these collections when you need to react to changes.
ModelIdMap
The universal data structure for targeting specific items across models:
type ModelIdMap = Record<string, Set<number>>;
// Keys: model IDs (strings)
// Values: sets of local element IDs (numbers)Used by: Hider, Classifier, FragmentsManager, BoundingBoxer, Highlighter, and all selection/query operations.
Critical Warnings
1. NEVER instantiate components directly with new. ALWAYS use components.get(ComponentClass). 2. NEVER forget to call components.dispose() on cleanup. Memory leaks from undisposed BIM models crash browser tabs. 3. NEVER import @thatopen/components-front in Node.js environments. 4. NEVER skip components.init() — without it, the render loop does not start and nothing renders. 5. NEVER use deprecated packages: web-ifc-three, web-ifc-viewer, or openbim-components. Use @thatopen/components 3.x. 6. NEVER use v2 APIs (Plans, ClipEdges) — use ClipStyler + View in v3. 7. ALWAYS call setup() on Configurable components before using them (e.g., IfcLoader, SimpleScene, Highlighter). 8. ALWAYS initialize FragmentsManager with a worker URL before loading models: fragments.init(workerURL).
Key Design Decisions
1. Singleton per Components instance — components.get(X) ALWAYS returns the same instance for a given component class. 2. Mixin interfaces over inheritance — Components opt into behaviors (Disposable, Updateable, etc.) rather than using deep class hierarchies. 3. Worker offloading — Fragment operations run in web workers to keep the main thread responsive. 4. BVH raycasting — three-mesh-bvh is auto-patched. No manual setup is needed for fast raycasting on large models. 5. Configurable deferred setup — Components with complex initialization implement Configurable and require explicit setup() calls.
Reference Files
- references/methods.md — Full API signatures for
Components, Component, Base, World, Worlds, Event, DataMap, DataSet
- references/examples.md — World setup, component
registration, lifecycle patterns
- references/anti-patterns.md — Common
mistakes and what NOT to do
Source Verification
All API signatures verified against:
- GitHub:
ThatOpen/engine_componentsmain branch (packages/core/src/) - npm:
@thatopen/components@3.3.3 - Research:
docs/research/vooronderzoek-thatopen.md
Anti-Patterns — Core Architecture
Version: @thatopen/components 3.3.x
Common mistakes when working with ThatOpen core architecture and what to do instead.
---
AP-001: Direct Component Instantiation
WRONG:
// NEVER do this — bypasses the singleton registry
const worlds = new OBC.Worlds(components);
const ifcLoader = new OBC.IfcLoader(components);CORRECT:
// ALWAYS use components.get()
const worlds = components.get(OBC.Worlds);
const ifcLoader = components.get(OBC.IfcLoader);Why: components.get() implements the lazy singleton pattern. Direct instantiation creates duplicate instances that are not tracked by the container, leading to lifecycle bugs and memory leaks.
---
AP-002: Missing components.init()
WRONG:
const components = new OBC.Components();
const worlds = components.get(OBC.Worlds);
const world = worlds.create();
world.scene = new OBC.SimpleScene(components);
world.renderer = new OBC.SimpleRenderer(components, container);
world.camera = new OBC.OrthoPerspectiveCamera(components);
// Forgot components.init() — nothing renders, no update loop runsCORRECT:
// ... same setup as above ...
components.init(); // ALWAYS call this to start the render loopWhy: Without init(), the requestAnimationFrame loop never starts. No Updateable components receive update() calls, and the renderer never draws frames.
---
AP-003: Missing Disposal
WRONG:
// Component unmount without cleanup
function destroyViewer() {
container.innerHTML = "";
// Memory leak: WebGL context, GPU buffers, WASM memory all leaked
}CORRECT:
function destroyViewer() {
components.dispose(); // Disposes ALL components, worlds, renderers
}Why: BIM models consume hundreds of megabytes of GPU memory and WASM heap. Failing to dispose causes browser tabs to crash. ALWAYS call components.dispose() when the viewer is no longer needed.
---
AP-004: Skipping setup() on Configurable Components
WRONG:
const ifcLoader = components.get(OBC.IfcLoader);
// Forgot setup() — WASM not initialized
const model = await ifcLoader.load(data, true, "test"); // Fails silently or throwsCORRECT:
const ifcLoader = components.get(OBC.IfcLoader);
await ifcLoader.setup(); // ALWAYS call setup() first
const model = await ifcLoader.load(data, true, "test");Why: Configurable components defer their initialization to setup(). Without it, internal state (WASM engine, shader compilation, etc.) is not ready. ALWAYS check isSetup if uncertain.
---
AP-005: Using Deprecated Packages
WRONG:
// NEVER use these — they are deprecated and abandoned
import { IfcViewerAPI } from "web-ifc-viewer";
import { IFCLoader } from "web-ifc-three";
import * as OBC from "openbim-components";CORRECT:
// ALWAYS use the @thatopen scoped packages
import * as OBC from "@thatopen/components";
import * as OBCF from "@thatopen/components-front";Why: web-ifc-viewer, web-ifc-three, and openbim-components are the predecessor packages. They are no longer maintained. The @thatopen scoped packages (v3) are the current, actively developed versions.
---
AP-006: Importing components-front in Node.js
WRONG:
// In a Node.js script or server-side code
import * as OBCF from "@thatopen/components-front"; // Crashes — needs DOM + WebGLCORRECT:
// In Node.js, only use core
import * as OBC from "@thatopen/components";
// components-front is for browser-only code
// Use it ONLY in browser environmentsWhy: @thatopen/components-front depends on DOM APIs (HTMLCanvasElement, WebGLRenderingContext, PointerEvent, etc.) that do not exist in Node.js.
---
AP-007: Forgetting to Remove Event Handlers
WRONG:
function setupHandler() {
const fragments = components.get(OBC.FragmentsManager);
fragments.onFragmentsLoaded.add((model) => {
// This handler is never removed — accumulates on repeated calls
processModel(model);
});
}CORRECT:
const handler = (model: OBC.FragmentsModel) => {
processModel(model);
};
function setupHandler() {
const fragments = components.get(OBC.FragmentsManager);
fragments.onFragmentsLoaded.add(handler);
}
function teardownHandler() {
const fragments = components.get(OBC.FragmentsManager);
fragments.onFragmentsLoaded.remove(handler);
}Why: Anonymous event handlers cannot be removed. Over time, duplicate handlers accumulate, causing performance degradation and unexpected behavior (handlers fire multiple times per event).
---
AP-008: Using v2 Plan/Section APIs
WRONG:
// These components DO NOT EXIST in v3
const plans = components.get(OBC.Plans); // Error
const clipEdges = components.get(OBC.ClipEdges); // ErrorCORRECT:
// v3 uses ClipStyler + View for plans and sections
import * as OBCF from "@thatopen/components-front";
const clipper = components.get(OBC.Clipper);
const clipStyler = components.get(OBCF.ClipStyler);
// Create clipping plane, then add edge visualizationWhy: The plan/section system was completely redesigned in v3. Plans and ClipEdges are replaced by ClipStyler with createFromClipping() and createFromView().
---
AP-009: Not Initializing FragmentsManager Worker
WRONG:
const fragments = components.get(OBC.FragmentsManager);
// Forgot fragments.init(workerURL)
const ifcLoader = components.get(OBC.IfcLoader);
await ifcLoader.setup();
const model = await ifcLoader.load(data, true, "test"); // May fail or run on main threadCORRECT:
const fragments = components.get(OBC.FragmentsManager);
fragments.init("/workers/fragments-worker.js"); // ALWAYS init with worker URL
const ifcLoader = components.get(OBC.IfcLoader);
await ifcLoader.setup();
const model = await ifcLoader.load(data, true, "test");Why: FragmentsManager offloads heavy geometry operations to a web worker. Without init(workerURL), operations either fail or block the main thread, causing the UI to freeze on large models.
---
AP-010: Using components After dispose()
WRONG:
components.dispose();
// NEVER use components or any of its managed instances after this
const worlds = components.get(OBC.Worlds); // Undefined behaviorCORRECT:
components.dispose();
// Set reference to null, create new Components if needed
components = null;Why: After dispose(), all internal state is cleaned up. Accessing disposed components leads to undefined behavior, null reference errors, and potential use-after-free bugs in WASM memory.
---
AP-011: Hardcoding WASM Paths
WRONG:
await ifcLoader.setup({
wasm: { path: "/node_modules/web-ifc/0.0.57/", absolute: true }
});
// Hardcoded version — breaks when web-ifc is updatedCORRECT:
// Let setup() auto-detect, or use a version-agnostic path
await ifcLoader.setup();
// Or if customizing, use a path that matches your build output
await ifcLoader.setup({
autoSetWasm: false,
wasm: { path: "/wasm/", absolute: true }
});Why: Hardcoded version strings in WASM paths break silently when dependencies are updated. Either use auto-detection or copy WASM files to a stable path during your build process.
Examples — Core Architecture
Version: @thatopen/components 3.3.x
---
1. Minimal World Setup (Browser)
import * as OBC from "@thatopen/components";
// 1. Create the components container
const components = new OBC.Components();
// 2. Get the Worlds manager via the singleton registry
const worlds = components.get(OBC.Worlds);
// 3. Create a typed world
const world = worlds.create<
OBC.SimpleScene,
OBC.OrthoPerspectiveCamera,
OBC.SimpleRenderer
>();
// 4. Assign scene, renderer, camera
const container = document.getElementById("viewer")!;
world.scene = new OBC.SimpleScene(components);
world.scene.setup(); // Creates default lights and background
world.renderer = new OBC.SimpleRenderer(components, container);
world.camera = new OBC.OrthoPerspectiveCamera(components);
// 5. Start the render loop — ALWAYS required
components.init();
// 6. Optional: add a grid
components.get(OBC.Grids).create(world);2. Component Registration Pattern
Every custom component follows this pattern:
import * as OBC from "@thatopen/components";
class MyTool extends OBC.Component implements OBC.Disposable {
static readonly uuid = "a1b2c3d4-e5f6-7890-abcd-ef1234567890" as const;
enabled = true;
onDisposed = new OBC.Event<void>();
constructor(components: OBC.Components) {
super(components);
components.add(MyTool.uuid, this);
}
dispose(): void {
this.enabled = false;
this.onDisposed.trigger();
this.onDisposed.reset();
}
}
// Usage — ALWAYS via get(), NEVER via new:
const myTool = components.get(MyTool);3. Configurable Component Pattern
Components that need async or complex initialization implement Configurable:
import * as OBC from "@thatopen/components";
// IfcLoader is Configurable — ALWAYS call setup() before load()
const ifcLoader = components.get(OBC.IfcLoader);
await ifcLoader.setup(); // Downloads WASM, initializes web-ifc
// Verify setup completed
console.log(ifcLoader.isSetup); // true
// Now safe to load
const model = await ifcLoader.load(ifcBytes, true, "MyModel");4. Updateable Component Pattern
Components that need per-frame updates implement Updateable:
import * as OBC from "@thatopen/components";
class AnimationController extends OBC.Component implements OBC.Updateable {
static readonly uuid = "b2c3d4e5-f6a7-8901-bcde-f12345678901" as const;
enabled = true;
onBeforeUpdate = new OBC.Event<void>();
onAfterUpdate = new OBC.Event<void>();
constructor(components: OBC.Components) {
super(components);
components.add(AnimationController.uuid, this);
}
// Called automatically by Components every frame when enabled=true
update(delta?: number): void {
this.onBeforeUpdate.trigger();
// Animation logic using delta (seconds since last frame)
this.onAfterUpdate.trigger();
}
}5. Event System Usage
import * as OBC from "@thatopen/components";
const fragments = components.get(OBC.FragmentsManager);
// Subscribe to model loading events
const onLoaded = (model: OBC.FragmentsModel) => {
console.log("Model loaded:", model.modelId);
};
fragments.onFragmentsLoaded.add(onLoaded);
// Later: unsubscribe to prevent memory leaks
fragments.onFragmentsLoaded.remove(onLoaded);
// Temporarily disable an event (handlers stay registered but do not fire)
fragments.onFragmentsLoaded.enabled = false;
// Re-enable
fragments.onFragmentsLoaded.enabled = true;
// Remove ALL handlers (use on dispose)
fragments.onFragmentsLoaded.reset();6. DataMap Reactive Collection
import * as OBC from "@thatopen/components";
const worlds = components.get(OBC.Worlds);
// React to new worlds being created
worlds.list.onItemSet.add(({ key, value }) => {
console.log(`World created: ${key}`);
});
// React to worlds being removed
worlds.list.onItemDeleted.add(({ key }) => {
console.log(`World removed: ${key}`);
});7. Multiple Worlds
import * as OBC from "@thatopen/components";
const components = new OBC.Components();
const worlds = components.get(OBC.Worlds);
// Main 3D viewport
const world3D = worlds.create();
world3D.scene = new OBC.SimpleScene(components);
world3D.scene.setup();
world3D.renderer = new OBC.SimpleRenderer(components, container3D);
world3D.camera = new OBC.OrthoPerspectiveCamera(components);
// Plan view
const worldPlan = worlds.create();
worldPlan.scene = new OBC.SimpleScene(components);
worldPlan.scene.setup();
worldPlan.renderer = new OBC.SimpleRenderer(components, containerPlan);
worldPlan.camera = new OBC.OrthoPerspectiveCamera(components);
// Switch plan camera to orthographic top-down
const planCamera = worldPlan.camera as OBC.OrthoPerspectiveCamera;
planCamera.set("Plan");
components.init(); // Starts render loop for ALL worlds8. Proper Disposal
import * as OBC from "@thatopen/components";
// Listen for disposal
components.onDisposed.add(() => {
console.log("All components disposed");
});
// On application shutdown — ALWAYS call this
components.dispose();
// After this call, all components, worlds, renderers, scenes, and
// cameras are cleaned up. The animation loop stops.
// Do NOT use any component references after dispose().9. Accessing Three.js Objects
import * as OBC from "@thatopen/components";
import * as THREE from "three";
const worlds = components.get(OBC.Worlds);
const world = worlds.create();
world.scene = new OBC.SimpleScene(components);
world.renderer = new OBC.SimpleRenderer(components, container);
world.camera = new OBC.OrthoPerspectiveCamera(components);
// Access underlying Three.js objects via .three
const threeScene: THREE.Scene = world.scene.three;
const threeRenderer: THREE.WebGLRenderer = world.renderer.three;
const threeCamera: THREE.PerspectiveCamera = (world.camera as OBC.OrthoPerspectiveCamera).threePersp;
// Add custom Three.js objects directly
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
threeScene.add(cube);
// Track meshes for raycasting (optional)
world.meshes.add(cube);10. Full Application Bootstrap
import * as OBC from "@thatopen/components";
import * as OBCF from "@thatopen/components-front";
const components = new OBC.Components();
// World with post-processing renderer (browser-only)
const worlds = components.get(OBC.Worlds);
const world = worlds.create<
OBC.SimpleScene,
OBC.OrthoPerspectiveCamera,
OBCF.PostproductionRenderer
>();
const container = document.getElementById("viewer")!;
world.scene = new OBC.SimpleScene(components);
world.scene.setup();
world.renderer = new OBCF.PostproductionRenderer(components, container);
world.camera = new OBC.OrthoPerspectiveCamera(components);
// Enable post-processing
world.renderer.postproduction.enabled = true;
// Grid
components.get(OBC.Grids).create(world);
// Fragments manager — ALWAYS init with worker URL
const fragments = components.get(OBC.FragmentsManager);
fragments.init("/workers/fragments-worker.js");
// IFC loader — ALWAYS setup before loading
const ifcLoader = components.get(OBC.IfcLoader);
await ifcLoader.setup();
// Highlighter — ALWAYS setup with world reference
const highlighter = components.get(OBCF.Highlighter);
highlighter.setup({ world });
// Start render loop
components.init();
// Load a model
const response = await fetch("/models/building.ifc");
const data = new Uint8Array(await response.arrayBuffer());
const model = await ifcLoader.load(data, true, "building");
// Frame the model in view
world.camera.fit(world.meshes);
// Cleanup on unmount
window.addEventListener("beforeunload", () => {
components.dispose();
});API Signatures — Core Architecture
Version: @thatopen/components 3.3.x
---
Components Container
class Components implements Disposable {
readonly list: DataMap<string, Component>;
enabled: boolean;
onDisposed: Event<void>;
onInit: Event<undefined>;
static release: string; // version string
/**
* Lazy singleton getter. Creates the component on first call,
* returns the cached instance on subsequent calls.
*/
get<U extends Component>(Ctor: new (c: Components) => U): U;
/**
* Registers a component instance by UUID.
* Called internally by component constructors.
*/
add(uuid: string, instance: Component): void;
/**
* Starts the requestAnimationFrame loop.
* Uses THREE.Clock for delta time calculation.
* Calls update(delta) on all enabled Updateable components each frame.
*/
init(): void;
/**
* Disposes all registered components.
* FragmentsManager is ALWAYS disposed last.
*/
dispose(): void;
}Base Class
abstract class Base {
constructor(public components: Components) {}
/** Runtime check: does this implement Disposable? */
isDisposeable(): this is Disposable;
/** Runtime check: does this implement Updateable? */
isUpdateable(): this is Updateable;
/** Runtime check: does this implement Configurable? */
isConfigurable(): this is Configurable<any, any>;
/** Runtime check: does this implement Resizeable? */
isResizeable(): this is Resizeable;
/** Runtime check: does this implement Hideable? */
isHideable(): this is Hideable;
/** Runtime check: does this implement Serializable? */
isSerializable(): this is Serializable<any>;
}Component Class
abstract class Component extends Base {
/** MUST be unique across all components. Used as registry key. */
static readonly uuid: string;
/** Whether this component is active. */
abstract enabled: boolean;
}Lifecycle Interfaces
Disposable
interface Disposable {
dispose(): void;
onDisposed: Event<void>;
}Updateable
interface Updateable {
update(delta?: number): void;
onBeforeUpdate: Event<any>;
onAfterUpdate: Event<any>;
}Configurable
interface Configurable<TConfig, TPartialConfig> {
setup(config?: TPartialConfig): void;
config: TConfig;
isSetup: boolean;
onSetup: Event<any>;
}Resizeable
interface Resizeable {
resize(size?: THREE.Vector2): void;
getSize(): THREE.Vector2;
onResize: Event<THREE.Vector2>;
}Hideable
interface Hideable {
visible: boolean;
}Createable
interface Createable {
create(data?: any): void;
delete(data?: any): void;
endCreation(data?: any): void;
cancelCreation(data?: any): void;
}Serializable
interface Serializable<T> {
import(data: T): void;
export(): T;
}CameraControllable
interface CameraControllable {
controls: CameraControls;
}Eventable
interface Eventable {
eventManager: EventManager;
}Worlds Manager
class Worlds extends Component implements Updateable, Disposable {
static readonly uuid: string;
enabled: boolean;
list: DataMap<string, World>;
/** Creates a new World with typed scene, camera, renderer. */
create<TScene, TCamera, TRenderer>(): SimpleWorld;
/** Disposes and removes a world from the list. */
delete(world: World): void;
/** Called by Components animation loop. Updates all worlds. */
update(delta?: number): void;
dispose(): void;
onDisposed: Event<void>;
}SimpleWorld
class SimpleWorld implements World, Disposable {
uuid: string;
scene: BaseScene;
camera: BaseCamera;
renderer: BaseRenderer;
meshes: Set<THREE.Mesh>;
onDisposed: Event<void>;
dispose(): void;
}SimpleScene
class SimpleScene extends BaseScene implements Configurable {
three: THREE.Scene;
/** Creates default directional + ambient lights and sets background. */
setup(config?: Partial<SimpleSceneConfig>): void;
config: SimpleSceneConfig;
isSetup: boolean;
onSetup: Event<SimpleScene>;
}SimpleRenderer
class SimpleRenderer extends BaseRenderer implements Disposable, Resizeable, Updateable {
three: THREE.WebGLRenderer;
constructor(components: Components, container: HTMLElement, parameters?: Partial<THREE.WebGLRendererParameters>);
resize(size?: THREE.Vector2): void;
getSize(): THREE.Vector2;
update(): void;
dispose(): void;
onResize: Event<THREE.Vector2>;
onDisposed: Event<void>;
onBeforeUpdate: Event<any>;
onAfterUpdate: Event<any>;
}OrthoPerspectiveCamera
class OrthoPerspectiveCamera extends BaseCamera
implements CameraControllable, Updateable, Disposable, Configurable {
three: THREE.PerspectiveCamera | THREE.OrthographicCamera;
threePersp: THREE.PerspectiveCamera;
threeOrtho: THREE.OrthographicCamera;
controls: CameraControls;
projection: ProjectionManager;
/** Switch navigation mode: "Orbit" | "FirstPerson" | "Plan" */
set(mode: NavigationMode): void;
/** Frame objects in view */
fit(meshes?: Iterable<THREE.Mesh>, offset?: number): void;
/** Add a custom navigation mode */
addCustomNavigationMode(mode: NavigationMode): void;
setup(config?: any): void;
update(delta?: number): void;
dispose(): void;
onDisposed: Event<void>;
}Event
class Event<T> {
enabled: boolean;
/** Subscribe a handler. */
add(handler: (data: T) => void): void;
/** Unsubscribe a handler. */
remove(handler: (data: T) => void): void;
/** Fire the event, calling all handlers with the provided data. */
trigger(data?: T): void;
/** Remove all handlers. */
reset(): void;
}DataMap
class DataMap<K, V> extends Map<K, V> {
onItemSet: Event<{ key: K; value: V }>;
onItemUpdated: Event<{ key: K; value: V }>;
onItemDeleted: Event<{ key: K }>;
onCleared: Event<void>;
}DataSet
class DataSet<T> extends Set<T> {
onItemAdded: Event<T>;
onItemDeleted: Event<T>;
onCleared: Event<void>;
}ModelIdMap
/**
* Universal item targeting structure.
* Keys: model IDs (strings).
* Values: sets of local element IDs (numbers).
*/
type ModelIdMap = Record<string, Set<number>>;