@aura3d/ecs 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,81 +0,0 @@
1
- import { ValidationError } from "@aura3d/core";
2
- import { type System, type SystemContext, type SystemPhase } from "./System.js";
3
- import { type World } from "./World.js";
4
-
5
- const phaseOrder: SystemPhase[] = ["fixed", "update", "late"];
6
-
7
- export class SystemScheduler {
8
- private readonly systems = new Map<string, System>();
9
-
10
- add(system: System, world?: World): void {
11
- if (this.systems.has(system.name)) throw new ValidationError("DUPLICATE_SYSTEM", `Duplicate system name: ${system.name}`);
12
- this.systems.set(system.name, system);
13
- system.onAdd?.(world as World);
14
- }
15
-
16
- remove(name: string, world?: World): boolean {
17
- const system = this.systems.get(name);
18
- if (!system) return false;
19
- system.onRemove?.(world as World);
20
- return this.systems.delete(name);
21
- }
22
-
23
- order(phase?: SystemPhase): System[] {
24
- const systems = [...this.systems.values()].filter((system) => (system.phase ?? "update") === (phase ?? system.phase ?? "update"));
25
- return sortSystems(systems);
26
- }
27
-
28
- update(world: World, context: SystemContext): void {
29
- for (const phase of phaseOrder) {
30
- for (const system of this.order(phase)) system.update(world, context);
31
- }
32
- }
33
- }
34
-
35
- function sortSystems(systems: System[]): System[] {
36
- const byName = new Map(systems.map((system) => [system.name, system]));
37
- const graph = new Map<string, Set<string>>();
38
- const indegree = new Map<string, number>();
39
- const insertion = new Map(systems.map((system, index) => [system.name, index]));
40
- for (const system of systems) {
41
- graph.set(system.name, new Set());
42
- indegree.set(system.name, 0);
43
- }
44
- for (const system of systems) {
45
- for (const dependency of system.after ?? []) addEdge(dependency, system.name);
46
- for (const dependent of system.before ?? []) addEdge(system.name, dependent);
47
- }
48
- const ready = systems
49
- .filter((system) => indegree.get(system.name) === 0)
50
- .sort(compareSystems);
51
- const result: System[] = [];
52
- while (ready.length > 0) {
53
- const system = ready.shift() as System;
54
- result.push(system);
55
- for (const dependent of graph.get(system.name) ?? []) {
56
- indegree.set(dependent, (indegree.get(dependent) ?? 0) - 1);
57
- if (indegree.get(dependent) === 0) {
58
- ready.push(byName.get(dependent) as System);
59
- ready.sort(compareSystems);
60
- }
61
- }
62
- }
63
- if (result.length !== systems.length) throw new ValidationError("ECS_SYSTEM_CYCLE", "ECS system dependency cycle rejected.");
64
- return result;
65
-
66
- function addEdge(from: string, to: string): void {
67
- // `before`/`after` are soft ordering constraints: if the referenced
68
- // system is not registered (or runs in a different phase), the edge is
69
- // simply skipped instead of failing the whole schedule.
70
- if (!byName.has(from) || !byName.has(to)) return;
71
- const edges = graph.get(from) as Set<string>;
72
- if (!edges.has(to)) {
73
- edges.add(to);
74
- indegree.set(to, (indegree.get(to) ?? 0) + 1);
75
- }
76
- }
77
-
78
- function compareSystems(a: System, b: System): number {
79
- return (a.priority ?? 0) - (b.priority ?? 0) || (insertion.get(a.name) ?? 0) - (insertion.get(b.name) ?? 0);
80
- }
81
- }
package/src/World.ts DELETED
@@ -1,76 +0,0 @@
1
- import { ValidationError } from "@aura3d/core";
2
- import { type ComponentCtor, type ComponentSchema } from "./Component.js";
3
- import { ComponentRegistry } from "./ComponentRegistry.js";
4
- import { ComponentStore } from "./ComponentStore.js";
5
- import { type Entity } from "./Entity.js";
6
- import { EntityManager } from "./EntityManager.js";
7
- import { Query, type QueryOptions } from "./Query.js";
8
- import { SystemScheduler } from "./SystemScheduler.js";
9
- import { type SystemContext } from "./System.js";
10
-
11
- export class World {
12
- readonly entities = new EntityManager();
13
- readonly registry = new ComponentRegistry();
14
- readonly components = new ComponentStore(this.registry);
15
- readonly systems = new SystemScheduler();
16
- private version = 0;
17
-
18
- registerComponent<T extends object>(ctor: ComponentCtor<T>, schema?: ComponentSchema): void {
19
- this.registry.register(ctor, schema);
20
- }
21
-
22
- createEntity(): Entity {
23
- this.version += 1;
24
- return this.entities.create();
25
- }
26
-
27
- destroyEntity(entity: Entity): boolean {
28
- this.assertAlive(entity);
29
- const destroyed = this.entities.destroy(entity);
30
- if (destroyed) {
31
- this.components.deleteEntity(entity);
32
- this.version += 1;
33
- }
34
- return destroyed;
35
- }
36
-
37
- add<T extends object>(entity: Entity, ctor: ComponentCtor<T>, component: T): void {
38
- this.assertAlive(entity);
39
- if (!this.registry.get(ctor)) this.registry.register(ctor);
40
- this.components.add(entity, ctor, component);
41
- this.version += 1;
42
- }
43
-
44
- remove<T extends object>(entity: Entity, ctor: ComponentCtor<T>): boolean {
45
- this.assertAlive(entity);
46
- const removed = this.components.remove(entity, ctor);
47
- if (removed) this.version += 1;
48
- return removed;
49
- }
50
-
51
- get<T extends object>(entity: Entity, ctor: ComponentCtor<T>): T | undefined {
52
- this.assertAlive(entity);
53
- return this.components.get(entity, ctor);
54
- }
55
-
56
- has<T extends object>(entity: Entity, ctor: ComponentCtor<T>): boolean {
57
- this.assertAlive(entity);
58
- return this.components.has(entity, ctor);
59
- }
60
-
61
- query(options: QueryOptions): Query {
62
- return new Query(this.entities, this.components, this.registry, options);
63
- }
64
-
65
- update(context: SystemContext): void {
66
- this.systems.update(this, context);
67
- }
68
-
69
- getVersion(): number {
70
- return this.version;
71
- }
72
-
73
- assertAlive(entity: Entity): void {
74
- if (!this.entities.isAlive(entity)) throw new ValidationError("ENTITY_NOT_ALIVE", `Entity is not alive: ${entity.id}:${entity.generation}`);
75
- }
76
- }
@@ -1,56 +0,0 @@
1
- import { ValidationError } from "@aura3d/core";
2
- import { type ComponentSchema } from "../Component.js";
3
-
4
- export interface SerializedActiveComponent {
5
- readonly activeSelf: boolean;
6
- }
7
-
8
- export class ActiveComponent {
9
- activeSelf: boolean;
10
- activeInHierarchy: boolean;
11
-
12
- constructor(active = true) {
13
- if (typeof active !== "boolean") throw new ValidationError("ACTIVE_COMPONENT", "ActiveComponent expects a boolean active state.");
14
- this.activeSelf = active;
15
- this.activeInHierarchy = active;
16
- }
17
-
18
- get isActive(): boolean {
19
- return this.activeSelf;
20
- }
21
-
22
- get isActiveInHierarchy(): boolean {
23
- return this.activeInHierarchy;
24
- }
25
-
26
- setActive(active: boolean): void {
27
- if (typeof active !== "boolean") throw new ValidationError("ACTIVE_COMPONENT", "Active state must be boolean.");
28
- this.activeSelf = active;
29
- }
30
-
31
- toggle(): void {
32
- this.activeSelf = !this.activeSelf;
33
- }
34
-
35
- toJSON(): SerializedActiveComponent {
36
- return { activeSelf: this.activeSelf };
37
- }
38
-
39
- static readonly schema: ComponentSchema = {
40
- version: 1,
41
- serialize(component: object): SerializedActiveComponent {
42
- const active = component as ActiveComponent;
43
- return { activeSelf: active.activeSelf };
44
- },
45
- deserialize(data: unknown): ActiveComponent {
46
- if (!isRecord(data) || typeof data.activeSelf !== "boolean") {
47
- throw new ValidationError("ACTIVE_COMPONENT_DESERIALIZE", "ActiveComponent data requires activeSelf boolean.");
48
- }
49
- return new ActiveComponent(data.activeSelf);
50
- }
51
- };
52
- }
53
-
54
- function isRecord(value: unknown): value is Record<string, unknown> {
55
- return typeof value === "object" && value !== null;
56
- }
@@ -1,32 +0,0 @@
1
- export type CameraKind = "perspective" | "orthographic";
2
-
3
- /**
4
- * Camera parameters + computed matrices for an ECS entity.
5
- *
6
- * {@link CameraSystem} computes `projectionMatrix`, `viewMatrix`, and
7
- * `viewProjectionMatrix` each frame from the entity's
8
- * {@link WorldTransformComponent}.
9
- */
10
- export class CameraComponent {
11
- kind: CameraKind = "perspective";
12
-
13
- // Perspective
14
- fovYRadians = Math.PI / 3;
15
- aspect = 1;
16
- near = 0.1;
17
- far = 1000;
18
-
19
- // Orthographic
20
- left = -1;
21
- right = 1;
22
- bottom = -1;
23
- top = 1;
24
- zoom = 1;
25
-
26
- // Computed each frame by CameraSystem
27
- projectionMatrix = new Float32Array(16);
28
- viewMatrix = new Float32Array(16);
29
- viewProjectionMatrix = new Float32Array(16);
30
-
31
- viewport = { x: 0, y: 0, width: 1, height: 1 };
32
- }
@@ -1,134 +0,0 @@
1
- import { ValidationError } from "@aura3d/core";
2
- import { type ComponentSchema } from "../Component.js";
3
- import { entityKey, sameEntity, type Entity } from "../Entity.js";
4
-
5
- export interface SerializedHierarchyComponent {
6
- readonly parent: Entity | null;
7
- readonly children: readonly Entity[];
8
- readonly previousSibling: Entity | null;
9
- readonly nextSibling: Entity | null;
10
- readonly depth: number;
11
- }
12
-
13
- export class HierarchyComponent {
14
- /**
15
- * Parent entity, or `null` for root entities.
16
- *
17
- * Must be set via `HierarchySystem.setParent` (or `addChild`), which
18
- * maintains {@link depth}, sibling links, and the parent's child list.
19
- * Setting this field directly (including via the constructor argument)
20
- * leaves `depth` stale at 0 — `TransformSystem` recomputes depth
21
- * defensively for its parents-before-children ordering, but child/sibling
22
- * bookkeeping will remain out of sync until `setParent` is used.
23
- */
24
- parent: Entity | null;
25
- previousSibling: Entity | null = null;
26
- nextSibling: Entity | null = null;
27
- depth = 0;
28
- private readonly childEntities: Entity[] = [];
29
-
30
- constructor(parent: Entity | null = null) {
31
- this.parent = parent;
32
- }
33
-
34
- get children(): readonly Entity[] {
35
- return this.childEntities;
36
- }
37
-
38
- get childCount(): number {
39
- return this.childEntities.length;
40
- }
41
-
42
- get firstChild(): Entity | null {
43
- return this.childEntities[0] ?? null;
44
- }
45
-
46
- get lastChild(): Entity | null {
47
- return this.childEntities[this.childEntities.length - 1] ?? null;
48
- }
49
-
50
- hasParent(): boolean {
51
- return this.parent !== null;
52
- }
53
-
54
- hasChildren(): boolean {
55
- return this.childEntities.length > 0;
56
- }
57
-
58
- includesChild(entity: Entity): boolean {
59
- return this.childEntities.some((child) => sameEntity(child, entity));
60
- }
61
-
62
- forEachChild(callback: (child: Entity) => void): void {
63
- for (const child of this.childEntities) callback(child);
64
- }
65
-
66
- getSiblingIndex(entity?: Entity): number {
67
- if (!entity) return this.parent ? 0 : -1;
68
- return this.childEntities.findIndex((child) => sameEntity(child, entity));
69
- }
70
-
71
- _setParent(parent: Entity | null): void {
72
- this.parent = parent;
73
- }
74
-
75
- _addChild(child: Entity): void {
76
- if (!this.includesChild(child)) this.childEntities.push(child);
77
- }
78
-
79
- _removeChild(child: Entity): boolean {
80
- const index = this.childEntities.findIndex((entry) => sameEntity(entry, child));
81
- if (index < 0) return false;
82
- this.childEntities.splice(index, 1);
83
- return true;
84
- }
85
-
86
- _clearChildren(): void {
87
- this.childEntities.length = 0;
88
- }
89
-
90
- _moveChild(child: Entity, index: number): void {
91
- const current = this.childEntities.findIndex((entry) => sameEntity(entry, child));
92
- if (current < 0) throw new ValidationError("ECS_HIERARCHY_CHILD", `Entity is not a child: ${entityKey(child)}`);
93
- const [entry] = this.childEntities.splice(current, 1);
94
- this.childEntities.splice(Math.max(0, Math.min(index, this.childEntities.length)), 0, entry);
95
- }
96
-
97
- toJSON(): SerializedHierarchyComponent {
98
- return {
99
- parent: this.parent ? { ...this.parent } : null,
100
- children: this.childEntities.map((child) => ({ ...child })),
101
- previousSibling: this.previousSibling ? { ...this.previousSibling } : null,
102
- nextSibling: this.nextSibling ? { ...this.nextSibling } : null,
103
- depth: this.depth
104
- };
105
- }
106
-
107
- static readonly schema: ComponentSchema = {
108
- version: 1,
109
- serialize(component: object): SerializedHierarchyComponent {
110
- return (component as HierarchyComponent).toJSON();
111
- },
112
- deserialize(data: unknown): HierarchyComponent {
113
- if (!isRecord(data) || !Array.isArray(data.children)) {
114
- throw new ValidationError("HIERARCHY_COMPONENT_DESERIALIZE", "HierarchyComponent data requires children array.");
115
- }
116
- const hierarchy = new HierarchyComponent(isEntity(data.parent) ? data.parent : null);
117
- for (const child of data.children) {
118
- if (isEntity(child)) hierarchy._addChild(child);
119
- }
120
- hierarchy.previousSibling = isEntity(data.previousSibling) ? data.previousSibling : null;
121
- hierarchy.nextSibling = isEntity(data.nextSibling) ? data.nextSibling : null;
122
- hierarchy.depth = typeof data.depth === "number" && Number.isFinite(data.depth) ? Math.max(0, Math.floor(data.depth)) : 0;
123
- return hierarchy;
124
- }
125
- };
126
- }
127
-
128
- function isRecord(value: unknown): value is Record<string, unknown> {
129
- return typeof value === "object" && value !== null;
130
- }
131
-
132
- function isEntity(value: unknown): value is Entity {
133
- return isRecord(value) && Number.isSafeInteger(value.id) && Number.isSafeInteger(value.generation);
134
- }
@@ -1,18 +0,0 @@
1
- export type LightKind = "directional" | "point" | "spot" | "ambient";
2
-
3
- /**
4
- * Light parameters for an ECS entity.
5
- *
6
- * The ECS render bridge collects lights each frame and passes them to the
7
- * renderer as part of the {@link RenderSource} lighting state.
8
- */
9
- export class LightComponent {
10
- kind: LightKind = "directional";
11
- color: [number, number, number] = [1, 1, 1];
12
- intensity = 1;
13
- range = 10;
14
- angle = Math.PI / 4; // spot only
15
- penumbra = 0; // spot only
16
- castsShadow = false;
17
- layerMask = 1;
18
- }
@@ -1,23 +0,0 @@
1
- import type { SkinningPaletteBinding } from "@aura3d/rendering";
2
-
3
- /**
4
- * Renderable geometry + material binding for an ECS entity.
5
- *
6
- * References resources by name/handle; the ECS render bridge resolves them
7
- * through the {@link ECSRenderLibraries} map at collection time.
8
- */
9
- export class MeshComponent {
10
- geometry: string;
11
- material: string;
12
- castShadow = true;
13
- receiveShadow = true;
14
- skinning?: SkinningPaletteBinding;
15
- morphWeights?: readonly number[];
16
- instanceTransforms?: Float32Array;
17
- instanceColors?: Float32Array;
18
-
19
- constructor(geometry: string, material: string) {
20
- this.geometry = geometry;
21
- this.material = material;
22
- }
23
- }
@@ -1,7 +0,0 @@
1
- import { ValidationError } from "@aura3d/core";
2
-
3
- export class NameComponent {
4
- constructor(public name: string) {
5
- if (!name) throw new ValidationError("NAME_COMPONENT", "NameComponent requires a non-empty name.");
6
- }
7
- }
@@ -1,7 +0,0 @@
1
- import { ValidationError } from "@aura3d/core";
2
-
3
- export class TagComponent {
4
- constructor(public tag: string) {
5
- if (!tag) throw new ValidationError("TAG_COMPONENT", "TagComponent requires a non-empty tag.");
6
- }
7
- }
@@ -1,34 +0,0 @@
1
- import { Quaternion, Vector3 } from "@aura3d/math";
2
-
3
- export class TransformComponent {
4
- position: [number, number, number];
5
- rotation: [number, number, number, number];
6
- scale: [number, number, number];
7
-
8
- constructor(
9
- position: Vector3 | readonly [number, number, number] = Vector3.zero.clone(),
10
- rotation: Quaternion | readonly [number, number, number, number] = Quaternion.identity.clone(),
11
- scale: Vector3 | readonly [number, number, number] = Vector3.one.clone()
12
- ) {
13
- this.position = position instanceof Vector3 ? position.toArray() : [...position];
14
- const normalizedRotation = rotation instanceof Quaternion ? rotation.normalize() : new Quaternion(rotation[0], rotation[1], rotation[2], rotation[3]).normalize();
15
- this.rotation = [normalizedRotation.x, normalizedRotation.y, normalizedRotation.z, normalizedRotation.w];
16
- this.scale = scale instanceof Vector3 ? scale.toArray() : [...scale];
17
- }
18
-
19
- toJSON(): { position: [number, number, number]; rotation: [number, number, number, number]; scale: [number, number, number] } {
20
- return {
21
- position: [...this.position],
22
- rotation: [...this.rotation],
23
- scale: [...this.scale]
24
- };
25
- }
26
-
27
- toMath(): { position: Vector3; rotation: Quaternion; scale: Vector3 } {
28
- return {
29
- position: new Vector3(this.position[0], this.position[1], this.position[2]),
30
- rotation: new Quaternion(this.rotation[0], this.rotation[1], this.rotation[2], this.rotation[3]),
31
- scale: new Vector3(this.scale[0], this.scale[1], this.scale[2])
32
- };
33
- }
34
- }
@@ -1,22 +0,0 @@
1
- /**
2
- * Computed world-space transform for an ECS entity.
3
- *
4
- * Written by {@link TransformSystem} each frame from the entity's
5
- * {@link TransformComponent} and {@link HierarchyComponent} parent chain.
6
- * The renderer bridge reads this directly — it never touches local transforms.
7
- */
8
- export class WorldTransformComponent {
9
- worldMatrix = new Float32Array([
10
- 1, 0, 0, 0,
11
- 0, 1, 0, 0,
12
- 0, 0, 1, 0,
13
- 0, 0, 0, 1
14
- ]);
15
-
16
- normalMatrix = new Float32Array([
17
- 1, 0, 0, 0,
18
- 0, 1, 0, 0,
19
- 0, 0, 1, 0,
20
- 0, 0, 0, 1
21
- ]);
22
- }
package/src/index.ts DELETED
@@ -1,25 +0,0 @@
1
- export * from "./Entity.js";
2
- export * from "./EntityManager.js";
3
- export * from "./Component.js";
4
- export * from "./ComponentRegistry.js";
5
- export * from "./SparseSet.js";
6
- export * from "./Bitset.js";
7
- export * from "./ComponentStore.js";
8
- export * from "./Archetype.js";
9
- export * from "./Query.js";
10
- export * from "./CommandBuffer.js";
11
- export * from "./System.js";
12
- export * from "./SystemScheduler.js";
13
- export * from "./World.js";
14
- export * from "./ECSSerializer.js";
15
- export * from "./ECSProfiler.js";
16
- export * from "./components/TransformComponent.js";
17
- export * from "./components/WorldTransformComponent.js";
18
- export * from "./components/CameraComponent.js";
19
- export * from "./components/LightComponent.js";
20
- export * from "./components/MeshComponent.js";
21
- export * from "./components/NameComponent.js";
22
- export * from "./components/TagComponent.js";
23
- export * from "./components/ActiveComponent.js";
24
- export * from "./components/HierarchyComponent.js";
25
- export * from "./systems/index.js";
@@ -1,64 +0,0 @@
1
- import { type Entity } from "../Entity.js";
2
- import { type System, type SystemContext } from "../System.js";
3
- import { type World } from "../World.js";
4
- import { ActiveComponent } from "../components/ActiveComponent.js";
5
- import { HierarchyComponent } from "../components/HierarchyComponent.js";
6
-
7
- export class ActiveSystem implements System {
8
- readonly name = "ActiveSystem";
9
- readonly phase = "update";
10
- readonly priority = -90;
11
- onActiveChanged?: (entity: Entity, activeInHierarchy: boolean) => void;
12
-
13
- update(world: World, _context: SystemContext): void {
14
- const memo = new Map<string, boolean>();
15
- const entities = world.query({ include: [ActiveComponent] }).toArray().sort((left, right) => {
16
- return (world.get(left, HierarchyComponent)?.depth ?? 0) - (world.get(right, HierarchyComponent)?.depth ?? 0);
17
- });
18
- for (const entity of entities) {
19
- const component = world.get(entity, ActiveComponent);
20
- if (!component) continue;
21
- const previous = component.activeInHierarchy;
22
- component.activeInHierarchy = computeActiveInHierarchy(world, entity, memo);
23
- if (previous !== component.activeInHierarchy) this.onActiveChanged?.(entity, component.activeInHierarchy);
24
- }
25
- }
26
-
27
- setActive(world: World, entity: Entity, active: boolean): void {
28
- let component = world.get(entity, ActiveComponent);
29
- if (!component) {
30
- component = new ActiveComponent(active);
31
- world.add(entity, ActiveComponent, component);
32
- } else {
33
- component.setActive(active);
34
- }
35
- this.update(world, { deltaTime: 0, elapsedTime: 0, frame: 0 });
36
- }
37
-
38
- isActive(world: World, entity: Entity): boolean {
39
- return world.get(entity, ActiveComponent)?.activeSelf ?? true;
40
- }
41
-
42
- isActiveInHierarchy(world: World, entity: Entity): boolean {
43
- return world.get(entity, ActiveComponent)?.activeInHierarchy ?? true;
44
- }
45
-
46
- getActiveEntities(world: World): Entity[] {
47
- return world.query({ include: [ActiveComponent] }).toArray().filter((entity) => this.isActiveInHierarchy(world, entity));
48
- }
49
-
50
- getInactiveEntities(world: World): Entity[] {
51
- return world.query({ include: [ActiveComponent] }).toArray().filter((entity) => !this.isActiveInHierarchy(world, entity));
52
- }
53
- }
54
-
55
- function computeActiveInHierarchy(world: World, entity: Entity, memo: Map<string, boolean>): boolean {
56
- const key = `${entity.id}:${entity.generation}`;
57
- const cached = memo.get(key);
58
- if (cached !== undefined) return cached;
59
- const activeSelf = world.get(entity, ActiveComponent)?.activeSelf ?? true;
60
- const parent = world.get(entity, HierarchyComponent)?.parent ?? null;
61
- const active = activeSelf && (parent ? computeActiveInHierarchy(world, parent, memo) : true);
62
- memo.set(key, active);
63
- return active;
64
- }
@@ -1,50 +0,0 @@
1
- import { Matrix4 } from "@aura3d/math";
2
- import { type System, type SystemContext } from "../System.js";
3
- import { type World } from "../World.js";
4
- import { CameraComponent } from "../components/CameraComponent.js";
5
- import { WorldTransformComponent } from "../components/WorldTransformComponent.js";
6
-
7
- /**
8
- * Computes camera projection and view matrices for entities that have
9
- * both {@link CameraComponent} and {@link WorldTransformComponent}.
10
- *
11
- * Runs after {@link TransformSystem} so the camera entity's world matrix
12
- * is already computed.
13
- */
14
- export class CameraSystem implements System {
15
- readonly name = "CameraSystem";
16
- readonly phase = "update";
17
- readonly priority = -70;
18
- // Soft ordering constraint: the scheduler skips this edge when
19
- // TransformSystem is not registered, so CameraSystem can run standalone.
20
- readonly after = ["TransformSystem"];
21
-
22
- update(world: World, _context: SystemContext): void {
23
- for (const entity of world
24
- .query({ include: [CameraComponent, WorldTransformComponent] })
25
- .toArray()) {
26
- const cam = world.get(entity, CameraComponent)!;
27
- const wt = world.get(entity, WorldTransformComponent)!;
28
-
29
- const projection =
30
- cam.kind === "perspective"
31
- ? Matrix4.perspective(cam.fovYRadians, cam.aspect, cam.near, cam.far)
32
- : Matrix4.orthographic(
33
- cam.left / cam.zoom,
34
- cam.right / cam.zoom,
35
- cam.bottom / cam.zoom,
36
- cam.top / cam.zoom,
37
- cam.near,
38
- cam.far
39
- );
40
-
41
- // viewMatrix = inverse(worldMatrix)
42
- const worldMat = new Matrix4(wt.worldMatrix as unknown as Matrix4["elements"]);
43
- const view = worldMat.inverse();
44
-
45
- (cam.projectionMatrix as Float32Array).set(projection.elements);
46
- (cam.viewMatrix as Float32Array).set(view.elements);
47
- (cam.viewProjectionMatrix as Float32Array).set(projection.multiply(view).elements);
48
- }
49
- }
50
- }