@galfus/engine 0.0.0-stage → 0.31.0-alpha

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.
Files changed (80) hide show
  1. package/README.md +190 -2
  2. package/package.json +59 -4
  3. package/src/anchors/cache.ts +101 -0
  4. package/src/anchors/component.ts +123 -0
  5. package/src/anchors/hierarchy.ts +192 -0
  6. package/src/anchors/index.ts +8 -0
  7. package/src/anchors/solver.ts +247 -0
  8. package/src/anchors/strategy.ts +142 -0
  9. package/src/animation/blending.ts +163 -0
  10. package/src/animation/clip.ts +132 -0
  11. package/src/animation/coordinator.ts +568 -0
  12. package/src/animation/index.ts +7 -0
  13. package/src/animation/native-targets.ts +37 -0
  14. package/src/animation/player.ts +287 -0
  15. package/src/audio/index.ts +4 -0
  16. package/src/audio/manager.ts +371 -0
  17. package/src/collision/components.ts +211 -0
  18. package/src/collision/index.ts +16 -0
  19. package/src/collision/query2d.ts +167 -0
  20. package/src/collision/query3d.ts +170 -0
  21. package/src/ecs/component.ts +130 -0
  22. package/src/ecs/entity.ts +75 -0
  23. package/src/ecs/index.ts +9 -0
  24. package/src/ecs/intent.ts +162 -0
  25. package/src/ecs/metadata.ts +44 -0
  26. package/src/ecs/system.ts +224 -0
  27. package/src/ecs/world.ts +291 -0
  28. package/src/index.ts +19 -0
  29. package/src/interaction/focus.ts +327 -0
  30. package/src/interaction/index.ts +9 -0
  31. package/src/interaction/listener.ts +58 -0
  32. package/src/interaction/snapshot2d.ts +210 -0
  33. package/src/interaction/snapshot3d.ts +214 -0
  34. package/src/interaction/system.ts +544 -0
  35. package/src/interaction/types.ts +27 -0
  36. package/src/math/common.ts +67 -0
  37. package/src/math/index.ts +17 -0
  38. package/src/math/mat2d.ts +320 -0
  39. package/src/math/mat4.ts +576 -0
  40. package/src/math/quat.ts +438 -0
  41. package/src/math/vec2.ts +161 -0
  42. package/src/math/vec3.ts +242 -0
  43. package/src/math/vec4.ts +171 -0
  44. package/src/multiverse/coordinator.ts +540 -0
  45. package/src/multiverse/index.ts +13 -0
  46. package/src/multiverse/presentation-graph.ts +306 -0
  47. package/src/multiverse/presentation-links.ts +134 -0
  48. package/src/multiverse/realm.ts +49 -0
  49. package/src/platform/index.ts +2 -0
  50. package/src/platform/platform.ts +149 -0
  51. package/src/platform/window.ts +181 -0
  52. package/src/realm2d/components.ts +254 -0
  53. package/src/realm2d/index.ts +12 -0
  54. package/src/realm2d/presets/index.ts +36 -0
  55. package/src/realm2d/realm.ts +522 -0
  56. package/src/realm2d/sync.ts +284 -0
  57. package/src/realm2d/utils/index.ts +32 -0
  58. package/src/realm3d/components.ts +341 -0
  59. package/src/realm3d/geometry.ts +224 -0
  60. package/src/realm3d/index.ts +12 -0
  61. package/src/realm3d/presets/index.ts +67 -0
  62. package/src/realm3d/realm.ts +588 -0
  63. package/src/realm3d/resource-ids.ts +62 -0
  64. package/src/realm3d/skinning.ts +128 -0
  65. package/src/realm3d/sync.ts +442 -0
  66. package/src/realm3d/utils/index.ts +26 -0
  67. package/src/resources/index.ts +4 -0
  68. package/src/resources/manager.ts +92 -0
  69. package/src/runtime.ts +588 -0
  70. package/src/spatial/dirty_registry.ts +217 -0
  71. package/src/spatial/flags.ts +14 -0
  72. package/src/spatial/index.ts +6 -0
  73. package/src/spatial/metrics.ts +124 -0
  74. package/src/text/editor.ts +224 -0
  75. package/src/text/encoding.ts +38 -0
  76. package/src/text/index.ts +13 -0
  77. package/src/text/input.ts +160 -0
  78. package/src/text/keyboard.ts +50 -0
  79. package/src/text/svg-decorations.ts +147 -0
  80. package/src/types.ts +85 -0
@@ -0,0 +1,181 @@
1
+ /**
2
+ * WindowManager coordinates native OS and browser canvas display surfaces.
3
+ */
4
+
5
+ import type { GalfusEngine, WindowConfig, WindowState } from '@galfus/transport-types';
6
+ import { IdAllocator, type WindowId } from '../types';
7
+
8
+ export type CursorGrabMode = 'none' | 'confined' | 'locked';
9
+
10
+ export type CursorIconName =
11
+ 'default' | 'pointer' | 'grab' | 'grabbing' | 'crosshair' | 'text' | 'move' | 'wait';
12
+
13
+ const CURSOR_GRAB_MAP: Record<CursorGrabMode, number> = {
14
+ none: 0,
15
+ confined: 1,
16
+ locked: 2
17
+ };
18
+
19
+ const CURSOR_ICON_MAP: Record<CursorIconName, number> = {
20
+ default: 0,
21
+ pointer: 1,
22
+ grab: 2,
23
+ grabbing: 3,
24
+ crosshair: 4,
25
+ text: 5,
26
+ move: 6,
27
+ wait: 7
28
+ };
29
+
30
+ export interface WindowCreateOptions {
31
+ title?: string;
32
+ size?: [number, number];
33
+ position?: [number, number];
34
+ resizable?: boolean;
35
+ decorations?: boolean;
36
+ transparent?: boolean;
37
+ initialState?: WindowState;
38
+ canvasId?: string;
39
+ }
40
+
41
+ export class WindowManager {
42
+ private _engine: GalfusEngine | null = null;
43
+ private readonly _idAllocator = new IdAllocator<WindowId>(1);
44
+ private readonly _openWindows = new Set<WindowId>();
45
+ private readonly _lifecycleListeners = new Set<(windowId: WindowId, opened: boolean) => void>();
46
+
47
+ /** Observes actual window creation and closure, independent of close requests. */
48
+ onLifecycle(listener: (windowId: WindowId, opened: boolean) => void): () => void {
49
+ this._lifecycleListeners.add(listener);
50
+ return () => {
51
+ this._lifecycleListeners.delete(listener);
52
+ };
53
+ }
54
+
55
+ bindEngine(engine: GalfusEngine): void {
56
+ this._engine = engine;
57
+ }
58
+
59
+ /**
60
+ * Creates an OS window or binds a browser canvas, returning a strongly-typed WindowId.
61
+ */
62
+ create(options: WindowCreateOptions = {}): WindowId {
63
+ const windowId = this._idAllocator.allocate('WindowId');
64
+ if (this._engine) {
65
+ const config: WindowConfig = {
66
+ id: windowId,
67
+ title: options.title ?? 'Galfus Engine',
68
+ size: options.size,
69
+ position: options.position,
70
+ resizable: options.resizable ?? true,
71
+ decorations: options.decorations ?? true,
72
+ transparent: options.transparent ?? false,
73
+ initialState: options.initialState ?? 'windowed',
74
+ canvasId: options.canvasId
75
+ };
76
+ this._engine.windowCreate(config);
77
+ }
78
+ this._openWindows.add(windowId);
79
+ for (const listener of this._lifecycleListeners) listener(windowId, true);
80
+ return windowId;
81
+ }
82
+
83
+ /**
84
+ * Closes and destroys an open window surface.
85
+ */
86
+ close(windowId: WindowId): void {
87
+ if (!this._openWindows.has(windowId)) return;
88
+ if (this._engine) {
89
+ this._engine.windowClose(windowId);
90
+ }
91
+ this._openWindows.delete(windowId);
92
+ for (const listener of this._lifecycleListeners) listener(windowId, false);
93
+ }
94
+
95
+ /**
96
+ * Updates the window title bar.
97
+ */
98
+ setTitle(windowId: WindowId, title: string): void {
99
+ if (this._engine) {
100
+ this._engine.windowSetTitle(windowId, title);
101
+ }
102
+ }
103
+
104
+ /** Enables or disables input method composition for a window. */
105
+ setImeAllowed(windowId: WindowId, allowed: boolean): void {
106
+ if (this._openWindows.has(windowId))
107
+ this._engine?.windowSetImeAllowed(windowId, allowed ? 1 : 0);
108
+ }
109
+
110
+ /** @internal Controls whether the platform captures Tab navigation for this window. */
111
+ setTabCapture(windowId: WindowId, enabled: boolean): void {
112
+ this._engine?.windowSetTabCapture?.(windowId, enabled ? 1 : 0);
113
+ }
114
+
115
+ /** @internal Requests keyboard focus for the platform input surface. */
116
+ requestInputFocus(windowId: WindowId): void {
117
+ this._engine?.windowRequestInputFocus?.(windowId);
118
+ }
119
+
120
+ /** Sets the input method candidate area in physical window pixels. */
121
+ setImeCursorArea(windowId: WindowId, x: number, y: number, width: number, height: number): void {
122
+ this._engine?.windowSetImeCursorArea(windowId, x, y, width, height);
123
+ }
124
+
125
+ /**
126
+ * Retrieves the physical inner surface dimensions [width, height].
127
+ */
128
+ getSize(windowId: WindowId): [number, number] {
129
+ if (this._engine && this._engine.platformGetWindowSize) {
130
+ return this._engine.platformGetWindowSize(windowId);
131
+ }
132
+ return [0, 0];
133
+ }
134
+
135
+ /**
136
+ * Explicitly updates the window / swapchain surface size in the Multiverse.
137
+ */
138
+ updateSize(windowId: WindowId, width: number, height: number): void {
139
+ if (this._engine) {
140
+ this._engine.multiverseUpdateWindowSize(windowId, width, height);
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Updates cursor grab mode ('none' | 'confined' | 'locked').
146
+ * 'locked' enables Pointer Lock for FPS and orbit cameras.
147
+ */
148
+ setCursorGrab(windowId: WindowId, mode: CursorGrabMode | number): void {
149
+ const numericMode = typeof mode === 'string' ? (CURSOR_GRAB_MAP[mode] ?? 0) : mode;
150
+ if (this._engine) {
151
+ this._engine.windowSetCursorGrab(windowId, numericMode);
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Updates cursor visual appearance icon ('default' | 'pointer' | 'grab' | 'grabbing' | 'crosshair' | 'text' | 'move' | 'wait').
157
+ */
158
+ setCursorIcon(windowId: WindowId, icon: CursorIconName | number): void {
159
+ const numericIcon = typeof icon === 'string' ? (CURSOR_ICON_MAP[icon] ?? 0) : icon;
160
+ if (this._engine) {
161
+ this._engine.windowSetCursorIcon(windowId, numericIcon);
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Sets the visual application icon of this window using raw image bytes (ICO, PNG, WebP, JPEG).
167
+ * Follows payload-first convention: (windowId, data).
168
+ */
169
+ setIcon(windowId: WindowId, data: Uint8Array): void {
170
+ if (this._engine && this._engine.windowSetIcon) {
171
+ this._engine.windowSetIcon(windowId, data, data.byteLength);
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Disposes and closes all active windows.
177
+ */
178
+ dispose(): void {
179
+ for (const windowId of [...this._openWindows]) this.close(windowId);
180
+ }
181
+ }
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Realm 2D component declarations.
3
+ * Incorporates spatial Model2D with contiguous local/world matrices and SpatialDirtyFlags.
4
+ */
5
+
6
+ import type {
7
+ Camera2DDesc,
8
+ Light2DDesc,
9
+ ParticleEmitter2DDesc,
10
+ Shape2DDesc,
11
+ Sprite2DDesc,
12
+ Svg2DDesc
13
+ } from '@galfus/transport-types';
14
+ import type { Component } from '../ecs/component';
15
+ import { mat2d, type Mat2d, vec2, type Vec2 } from '../math/index';
16
+ import { SpatialDirtyFlags } from '../spatial/flags';
17
+
18
+ const SCRATCH_P2D = vec2.create();
19
+ const SCRATCH_S2D = vec2.create();
20
+
21
+ /**
22
+ * 2D Spatial node component holding dual 2D affine matrices (local and resolved world)
23
+ * and a two-stage dirty flag state machine (LocalDirty -> WorldDirty -> Clean).
24
+ */
25
+ export class Model2DComponent implements Component {
26
+ readonly type = 'Model2D' as const;
27
+ readonly modelId: number;
28
+ readonly localMatrix: Mat2d;
29
+ readonly worldMatrix: Mat2d;
30
+ private _zLayer = 0;
31
+ private _drawOrder = 0;
32
+ flags: SpatialDirtyFlags;
33
+
34
+ constructor(modelId: number, initialLocal?: Float32Array | number[]) {
35
+ this.modelId = modelId;
36
+ this.localMatrix = mat2d.create();
37
+ this.worldMatrix = mat2d.create();
38
+ if (initialLocal) {
39
+ this.localMatrix.set(initialLocal);
40
+ this.worldMatrix.set(initialLocal);
41
+ }
42
+ this.flags = SpatialDirtyFlags.LocalDirty | SpatialDirtyFlags.WorldDirty;
43
+ }
44
+
45
+ get zLayer(): number {
46
+ return this._zLayer;
47
+ }
48
+ set zLayer(value: number) {
49
+ if (value !== this._zLayer) {
50
+ this._zLayer = value;
51
+ this.flags |= SpatialDirtyFlags.WorldDirty;
52
+ }
53
+ }
54
+ get drawOrder(): number {
55
+ return this._drawOrder;
56
+ }
57
+ set drawOrder(value: number) {
58
+ if (value !== this._drawOrder) {
59
+ this._drawOrder = value;
60
+ this.flags |= SpatialDirtyFlags.WorldDirty;
61
+ }
62
+ }
63
+
64
+ setPosition(x: number, y: number): this;
65
+ setPosition(position: [number, number] | Vec2): this;
66
+ setPosition(xOrPos: number | [number, number] | Vec2, y?: number): this {
67
+ if (typeof xOrPos === 'number') {
68
+ this.localMatrix[4] = xOrPos;
69
+ this.localMatrix[5] = y ?? 0;
70
+ } else {
71
+ this.localMatrix[4] = xOrPos[0];
72
+ this.localMatrix[5] = xOrPos[1];
73
+ }
74
+ this.flags |= SpatialDirtyFlags.LocalDirty;
75
+ return this;
76
+ }
77
+
78
+ setRotation(rad: number): this {
79
+ vec2.set(SCRATCH_P2D, this.localMatrix[4], this.localMatrix[5]);
80
+ const sx = Math.hypot(this.localMatrix[0], this.localMatrix[1]);
81
+ const sy =
82
+ Math.hypot(this.localMatrix[2], this.localMatrix[3]) *
83
+ (this.localMatrix[0] * this.localMatrix[3] - this.localMatrix[1] * this.localMatrix[2] < 0
84
+ ? -1
85
+ : 1);
86
+ vec2.set(SCRATCH_S2D, sx, sy);
87
+ mat2d.fromRotationTranslationScale(this.localMatrix, SCRATCH_P2D, rad, SCRATCH_S2D);
88
+ this.flags |= SpatialDirtyFlags.LocalDirty;
89
+ return this;
90
+ }
91
+
92
+ setScale(x: number, y: number): this;
93
+ setScale(scale: [number, number] | Vec2): this;
94
+ setScale(xOrScale: number | [number, number] | Vec2, y?: number): this {
95
+ const x = typeof xOrScale === 'number' ? xOrScale : xOrScale[0];
96
+ const sy = typeof xOrScale === 'number' ? (y ?? 1) : xOrScale[1];
97
+ const rotation = Math.atan2(this.localMatrix[1], this.localMatrix[0]);
98
+ vec2.set(SCRATCH_P2D, this.localMatrix[4], this.localMatrix[5]);
99
+ vec2.set(SCRATCH_S2D, x, sy);
100
+ mat2d.fromRotationTranslationScale(this.localMatrix, SCRATCH_P2D, rotation, SCRATCH_S2D);
101
+ this.flags |= SpatialDirtyFlags.LocalDirty;
102
+ return this;
103
+ }
104
+
105
+ setTRS(
106
+ position?: [number, number] | Vec2,
107
+ rotationRad = 0,
108
+ scale?: [number, number] | Vec2
109
+ ): this {
110
+ const p = (position ?? [0, 0]) as unknown as Vec2;
111
+ const s = (scale ?? [1, 1]) as unknown as Vec2;
112
+ mat2d.fromRotationTranslationScale(this.localMatrix, p, rotationRad, s);
113
+ this.flags |= SpatialDirtyFlags.LocalDirty;
114
+ return this;
115
+ }
116
+
117
+ setLocalMatrix(mat: Float32Array | number[]): this {
118
+ this.localMatrix.set(mat);
119
+ this.flags |= SpatialDirtyFlags.LocalDirty;
120
+ return this;
121
+ }
122
+
123
+ setWorldMatrix(mat: Float32Array | number[]): this {
124
+ this.worldMatrix.set(mat);
125
+ this.flags |= SpatialDirtyFlags.WorldDirty;
126
+ return this;
127
+ }
128
+
129
+ markLocalDirty(): void {
130
+ this.flags |= SpatialDirtyFlags.LocalDirty;
131
+ }
132
+
133
+ markWorldDirty(): void {
134
+ this.flags |= SpatialDirtyFlags.WorldDirty;
135
+ }
136
+
137
+ get isDirty(): boolean {
138
+ return this.flags !== SpatialDirtyFlags.Clean;
139
+ }
140
+
141
+ set isDirty(val: boolean) {
142
+ if (val) {
143
+ this.flags |= SpatialDirtyFlags.WorldDirty;
144
+ } else {
145
+ this.flags = SpatialDirtyFlags.Clean;
146
+ }
147
+ }
148
+ }
149
+
150
+ export function createModel2D(
151
+ modelId: number,
152
+ initialLocal?: Float32Array | number[]
153
+ ): Model2DComponent {
154
+ return new Model2DComponent(modelId, initialLocal);
155
+ }
156
+
157
+ export interface Sprite2DComponent extends Component {
158
+ readonly type: 'Sprite2D';
159
+ readonly spriteId: number;
160
+ desc: Sprite2DDesc;
161
+ isDirty: boolean;
162
+ }
163
+
164
+ export function createSprite2D(spriteId: number, desc: Sprite2DDesc): Sprite2DComponent {
165
+ return {
166
+ type: 'Sprite2D',
167
+ spriteId,
168
+ desc,
169
+ isDirty: true
170
+ };
171
+ }
172
+
173
+ export interface Shape2DComponent extends Component {
174
+ readonly type: 'Shape2D';
175
+ readonly shapeId: number;
176
+ desc: Shape2DDesc;
177
+ isDirty: boolean;
178
+ }
179
+
180
+ export function createShape2D(shapeId: number, desc: Shape2DDesc): Shape2DComponent {
181
+ return {
182
+ type: 'Shape2D',
183
+ shapeId,
184
+ desc,
185
+ isDirty: true
186
+ };
187
+ }
188
+
189
+ export interface Light2DComponent extends Component {
190
+ readonly type: 'Light2D';
191
+ readonly lightId: number;
192
+ desc: Light2DDesc;
193
+ isDirty: boolean;
194
+ }
195
+
196
+ export function createLight2D(lightId: number, desc: Light2DDesc): Light2DComponent {
197
+ return {
198
+ type: 'Light2D',
199
+ lightId,
200
+ desc,
201
+ isDirty: true
202
+ };
203
+ }
204
+
205
+ export interface Camera2DComponent extends Component {
206
+ readonly type: 'Camera2D';
207
+ readonly cameraId: number;
208
+ desc: Camera2DDesc;
209
+ isDirty: boolean;
210
+ }
211
+
212
+ export function createCamera2D(cameraId: number, desc: Camera2DDesc): Camera2DComponent {
213
+ return {
214
+ type: 'Camera2D',
215
+ cameraId,
216
+ desc,
217
+ isDirty: true
218
+ };
219
+ }
220
+
221
+ export interface Svg2DComponent extends Component {
222
+ readonly type: 'Svg2D';
223
+ readonly svgId: number;
224
+ desc: Svg2DDesc;
225
+ isDirty: boolean;
226
+ }
227
+
228
+ export function createSvg2D(svgId: number, desc: Svg2DDesc): Svg2DComponent {
229
+ return {
230
+ type: 'Svg2D',
231
+ svgId,
232
+ desc,
233
+ isDirty: true
234
+ };
235
+ }
236
+
237
+ export interface ParticleEmitter2DComponent extends Component {
238
+ readonly type: 'ParticleEmitter2D';
239
+ readonly emitterId: number;
240
+ desc: ParticleEmitter2DDesc;
241
+ isDirty: boolean;
242
+ }
243
+
244
+ export function createParticleEmitter2D(
245
+ emitterId: number,
246
+ desc: ParticleEmitter2DDesc
247
+ ): ParticleEmitter2DComponent {
248
+ return {
249
+ type: 'ParticleEmitter2D',
250
+ emitterId,
251
+ desc,
252
+ isDirty: true
253
+ };
254
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Realm 2D module.
3
+ */
4
+ export * from './components';
5
+ export * from './presets/index';
6
+ export * as presets2D from './presets/index';
7
+ export * from './realm';
8
+ export * from './sync';
9
+ export * from './utils/index';
10
+ export * as utils2D from './utils/index';
11
+
12
+ export { Collider2DComponent, createCollider2D } from '../collision/components';
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Realm 2D presets for quick entity and camera setup.
3
+ */
4
+
5
+ import type { Camera2DDesc, Light2DDesc } from '@galfus/transport-types';
6
+
7
+ export function createOrthoCamera2DPreset(
8
+ options: {
9
+ orthoScale?: number;
10
+ near?: number;
11
+ far?: number;
12
+ cameraSlot?: number;
13
+ } = {}
14
+ ): Camera2DDesc {
15
+ return {
16
+ orthoScale: options.orthoScale ?? 1.0,
17
+ near: options.near ?? 0.0,
18
+ far: options.far ?? 1000.0,
19
+ cameraSlot: options.cameraSlot ?? 0,
20
+ active: true
21
+ };
22
+ }
23
+
24
+ export function createPointLight2DPreset(
25
+ options: {
26
+ color?: [number, number, number];
27
+ intensity?: number;
28
+ range?: number;
29
+ } = {}
30
+ ): Light2DDesc {
31
+ return {
32
+ color: options.color ?? [1.0, 1.0, 1.0],
33
+ intensity: options.intensity ?? 1.0,
34
+ range: options.range ?? 200.0
35
+ };
36
+ }