@multiplekex/shallot 0.1.0

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 (196) hide show
  1. package/dist/core/builder.d.ts +25 -0
  2. package/dist/core/builder.d.ts.map +1 -0
  3. package/dist/core/builder.js +88 -0
  4. package/dist/core/builder.js.map +1 -0
  5. package/dist/core/component.d.ts +29 -0
  6. package/dist/core/component.d.ts.map +1 -0
  7. package/dist/core/component.js +36 -0
  8. package/dist/core/component.js.map +1 -0
  9. package/dist/core/index.d.ts +13 -0
  10. package/dist/core/index.d.ts.map +1 -0
  11. package/dist/core/math.d.ts +32 -0
  12. package/dist/core/math.d.ts.map +1 -0
  13. package/dist/core/math.js +39 -0
  14. package/dist/core/math.js.map +1 -0
  15. package/dist/core/relation.d.ts +16 -0
  16. package/dist/core/relation.d.ts.map +1 -0
  17. package/dist/core/relation.js +32 -0
  18. package/dist/core/relation.js.map +1 -0
  19. package/dist/core/resource.d.ts +9 -0
  20. package/dist/core/resource.d.ts.map +1 -0
  21. package/dist/core/resource.js +12 -0
  22. package/dist/core/resource.js.map +1 -0
  23. package/dist/core/runtime.d.ts +13 -0
  24. package/dist/core/runtime.d.ts.map +1 -0
  25. package/dist/core/runtime.js +118 -0
  26. package/dist/core/runtime.js.map +1 -0
  27. package/dist/core/scheduler.d.ts +47 -0
  28. package/dist/core/scheduler.d.ts.map +1 -0
  29. package/dist/core/scheduler.js +138 -0
  30. package/dist/core/scheduler.js.map +1 -0
  31. package/dist/core/state.d.ts +62 -0
  32. package/dist/core/state.d.ts.map +1 -0
  33. package/dist/core/state.js +185 -0
  34. package/dist/core/state.js.map +1 -0
  35. package/dist/core/strings.d.ts +3 -0
  36. package/dist/core/strings.d.ts.map +1 -0
  37. package/dist/core/strings.js +11 -0
  38. package/dist/core/strings.js.map +1 -0
  39. package/dist/core/types.d.ts +33 -0
  40. package/dist/core/types.d.ts.map +1 -0
  41. package/dist/core/xml.d.ts +42 -0
  42. package/dist/core/xml.d.ts.map +1 -0
  43. package/dist/core/xml.js +349 -0
  44. package/dist/core/xml.js.map +1 -0
  45. package/dist/extras/arrows/index.d.ts +33 -0
  46. package/dist/extras/arrows/index.d.ts.map +1 -0
  47. package/dist/extras/arrows/index.js +288 -0
  48. package/dist/extras/arrows/index.js.map +1 -0
  49. package/dist/extras/index.d.ts +5 -0
  50. package/dist/extras/index.d.ts.map +1 -0
  51. package/dist/extras/index.js +31 -0
  52. package/dist/extras/index.js.map +1 -0
  53. package/dist/extras/lines/index.d.ts +36 -0
  54. package/dist/extras/lines/index.d.ts.map +1 -0
  55. package/dist/extras/lines/index.js +288 -0
  56. package/dist/extras/lines/index.js.map +1 -0
  57. package/dist/extras/orbit/index.d.ts +20 -0
  58. package/dist/extras/orbit/index.d.ts.map +1 -0
  59. package/dist/extras/orbit/index.js +93 -0
  60. package/dist/extras/orbit/index.js.map +1 -0
  61. package/dist/extras/text/index.d.ts +64 -0
  62. package/dist/extras/text/index.d.ts.map +1 -0
  63. package/dist/extras/text/index.js +423 -0
  64. package/dist/extras/text/index.js.map +1 -0
  65. package/dist/index.d.ts +4 -0
  66. package/dist/index.d.ts.map +1 -0
  67. package/dist/index.js +187 -0
  68. package/dist/index.js.map +1 -0
  69. package/dist/rust/transforms/pkg/shallot_transforms.js +107 -0
  70. package/dist/rust/transforms/pkg/shallot_transforms.js.map +1 -0
  71. package/dist/standard/compute/graph.d.ts +37 -0
  72. package/dist/standard/compute/graph.d.ts.map +1 -0
  73. package/dist/standard/compute/graph.js +85 -0
  74. package/dist/standard/compute/graph.js.map +1 -0
  75. package/dist/standard/compute/index.d.ts +21 -0
  76. package/dist/standard/compute/index.d.ts.map +1 -0
  77. package/dist/standard/compute/index.js +81 -0
  78. package/dist/standard/compute/index.js.map +1 -0
  79. package/dist/standard/defaults.d.ts +3 -0
  80. package/dist/standard/defaults.d.ts.map +1 -0
  81. package/dist/standard/defaults.js +18 -0
  82. package/dist/standard/defaults.js.map +1 -0
  83. package/dist/standard/index.d.ts +8 -0
  84. package/dist/standard/index.d.ts.map +1 -0
  85. package/dist/standard/input/index.d.ts +5 -0
  86. package/dist/standard/input/index.d.ts.map +1 -0
  87. package/dist/standard/input/index.js +70 -0
  88. package/dist/standard/input/index.js.map +1 -0
  89. package/dist/standard/loading/index.d.ts +7 -0
  90. package/dist/standard/loading/index.d.ts.map +1 -0
  91. package/dist/standard/loading/index.js +91 -0
  92. package/dist/standard/loading/index.js.map +1 -0
  93. package/dist/standard/render/camera.d.ts +36 -0
  94. package/dist/standard/render/camera.d.ts.map +1 -0
  95. package/dist/standard/render/camera.js +71 -0
  96. package/dist/standard/render/camera.js.map +1 -0
  97. package/dist/standard/render/forward.d.ts +30 -0
  98. package/dist/standard/render/forward.d.ts.map +1 -0
  99. package/dist/standard/render/forward.js +158 -0
  100. package/dist/standard/render/forward.js.map +1 -0
  101. package/dist/standard/render/index.d.ts +22 -0
  102. package/dist/standard/render/index.d.ts.map +1 -0
  103. package/dist/standard/render/index.js +153 -0
  104. package/dist/standard/render/index.js.map +1 -0
  105. package/dist/standard/render/light.d.ts +25 -0
  106. package/dist/standard/render/light.d.ts.map +1 -0
  107. package/dist/standard/render/light.js +48 -0
  108. package/dist/standard/render/light.js.map +1 -0
  109. package/dist/standard/render/mesh/box.d.ts +3 -0
  110. package/dist/standard/render/mesh/box.d.ts.map +1 -0
  111. package/dist/standard/render/mesh/box.js +190 -0
  112. package/dist/standard/render/mesh/box.js.map +1 -0
  113. package/dist/standard/render/mesh/index.d.ts +52 -0
  114. package/dist/standard/render/mesh/index.d.ts.map +1 -0
  115. package/dist/standard/render/mesh/index.js +158 -0
  116. package/dist/standard/render/mesh/index.js.map +1 -0
  117. package/dist/standard/render/mesh/plane.d.ts +3 -0
  118. package/dist/standard/render/mesh/plane.d.ts.map +1 -0
  119. package/dist/standard/render/mesh/plane.js +33 -0
  120. package/dist/standard/render/mesh/plane.js.map +1 -0
  121. package/dist/standard/render/mesh/sphere.d.ts +3 -0
  122. package/dist/standard/render/mesh/sphere.d.ts.map +1 -0
  123. package/dist/standard/render/mesh/sphere.js +25 -0
  124. package/dist/standard/render/mesh/sphere.js.map +1 -0
  125. package/dist/standard/render/postprocess.d.ts +11 -0
  126. package/dist/standard/render/postprocess.d.ts.map +1 -0
  127. package/dist/standard/render/postprocess.js +190 -0
  128. package/dist/standard/render/postprocess.js.map +1 -0
  129. package/dist/standard/render/scene.d.ts +8 -0
  130. package/dist/standard/render/scene.d.ts.map +1 -0
  131. package/dist/standard/render/scene.js +67 -0
  132. package/dist/standard/render/scene.js.map +1 -0
  133. package/dist/standard/transforms/index.d.ts +27 -0
  134. package/dist/standard/transforms/index.d.ts.map +1 -0
  135. package/dist/standard/transforms/index.js +122 -0
  136. package/dist/standard/transforms/index.js.map +1 -0
  137. package/dist/standard/transforms/wasm.d.ts +17 -0
  138. package/dist/standard/transforms/wasm.d.ts.map +1 -0
  139. package/dist/standard/transforms/wasm.js +31 -0
  140. package/dist/standard/transforms/wasm.js.map +1 -0
  141. package/dist/standard/tween/easing.d.ts +5 -0
  142. package/dist/standard/tween/easing.d.ts.map +1 -0
  143. package/dist/standard/tween/easing.js +80 -0
  144. package/dist/standard/tween/easing.js.map +1 -0
  145. package/dist/standard/tween/index.d.ts +4 -0
  146. package/dist/standard/tween/index.d.ts.map +1 -0
  147. package/dist/standard/tween/sequence.d.ts +20 -0
  148. package/dist/standard/tween/sequence.d.ts.map +1 -0
  149. package/dist/standard/tween/sequence.js +95 -0
  150. package/dist/standard/tween/sequence.js.map +1 -0
  151. package/dist/standard/tween/tween.d.ts +28 -0
  152. package/dist/standard/tween/tween.d.ts.map +1 -0
  153. package/dist/standard/tween/tween.js +136 -0
  154. package/dist/standard/tween/tween.js.map +1 -0
  155. package/package.json +63 -0
  156. package/src/core/builder.ts +148 -0
  157. package/src/core/component.ts +71 -0
  158. package/src/core/index.ts +92 -0
  159. package/src/core/math.ts +128 -0
  160. package/src/core/relation.ts +46 -0
  161. package/src/core/resource.ts +18 -0
  162. package/src/core/runtime.ts +185 -0
  163. package/src/core/scheduler.ts +238 -0
  164. package/src/core/state.ts +295 -0
  165. package/src/core/strings.ts +10 -0
  166. package/src/core/types.ts +37 -0
  167. package/src/core/xml.ts +676 -0
  168. package/src/extras/arrows/index.ts +363 -0
  169. package/src/extras/index.ts +4 -0
  170. package/src/extras/lines/index.ts +368 -0
  171. package/src/extras/orbit/index.ts +133 -0
  172. package/src/extras/text/index.ts +641 -0
  173. package/src/index.ts +3 -0
  174. package/src/standard/compute/graph.ts +165 -0
  175. package/src/standard/compute/index.ts +116 -0
  176. package/src/standard/defaults.ts +17 -0
  177. package/src/standard/index.ts +7 -0
  178. package/src/standard/input/index.ts +142 -0
  179. package/src/standard/loading/index.ts +136 -0
  180. package/src/standard/render/camera.ts +87 -0
  181. package/src/standard/render/forward.ts +212 -0
  182. package/src/standard/render/index.ts +175 -0
  183. package/src/standard/render/light.ts +81 -0
  184. package/src/standard/render/mesh/box.ts +20 -0
  185. package/src/standard/render/mesh/index.ts +227 -0
  186. package/src/standard/render/mesh/plane.ts +11 -0
  187. package/src/standard/render/mesh/sphere.ts +40 -0
  188. package/src/standard/render/postprocess.ts +235 -0
  189. package/src/standard/render/scene.ts +116 -0
  190. package/src/standard/transforms/index.ts +184 -0
  191. package/src/standard/transforms/wasm.ts +61 -0
  192. package/src/standard/tween/easing.ts +169 -0
  193. package/src/standard/tween/index.ts +13 -0
  194. package/src/standard/tween/sequence.ts +142 -0
  195. package/src/standard/tween/tween.ts +265 -0
  196. package/src/vite-env.d.ts +6 -0
@@ -0,0 +1,165 @@
1
+ import { CycleError } from "../../core";
2
+
3
+ export type ResourceId = string;
4
+ export type NodeId = string;
5
+ export type Phase = "render" | "overlay" | "postprocess";
6
+
7
+ const PHASE_ORDER: Phase[] = ["render", "overlay", "postprocess"];
8
+
9
+ export interface ResourceRef {
10
+ id: ResourceId;
11
+ access: "read" | "write";
12
+ }
13
+
14
+ export interface ExecutionContext {
15
+ readonly device: GPUDevice;
16
+ readonly queue: GPUQueue;
17
+ readonly encoder: GPUCommandEncoder;
18
+ readonly context: GPUCanvasContext;
19
+ readonly format: GPUTextureFormat;
20
+ readonly canvasView: GPUTextureView;
21
+ getTexture(id: ResourceId): GPUTexture | null;
22
+ getTextureView(id: ResourceId): GPUTextureView | null;
23
+ getBuffer(id: ResourceId): GPUBuffer | null;
24
+ }
25
+
26
+ export interface ComputeNode {
27
+ readonly id: NodeId;
28
+ readonly phase?: Phase;
29
+ readonly inputs: readonly ResourceRef[];
30
+ readonly outputs: readonly ResourceRef[];
31
+ readonly execute: (ctx: ExecutionContext) => void;
32
+ }
33
+
34
+ export interface ExecutionPlan {
35
+ readonly sorted: readonly ComputeNode[];
36
+ }
37
+
38
+ function buildEdges(nodes: ComputeNode[]): [ComputeNode, ComputeNode][] {
39
+ const edges: [ComputeNode, ComputeNode][] = [];
40
+ const producers = new Map<ResourceId, ComputeNode>();
41
+
42
+ for (const node of nodes) {
43
+ for (const output of node.outputs) {
44
+ producers.set(output.id, node);
45
+ }
46
+ }
47
+
48
+ for (const node of nodes) {
49
+ for (const input of node.inputs) {
50
+ const producer = producers.get(input.id);
51
+ if (producer) {
52
+ edges.push([producer, node]);
53
+ }
54
+ }
55
+ }
56
+
57
+ return edges;
58
+ }
59
+
60
+ function topoSort(nodes: ComputeNode[]): ComputeNode[] {
61
+ if (nodes.length === 0) return [];
62
+
63
+ const edges = buildEdges(nodes);
64
+ const adjacency = new Map<ComputeNode, ComputeNode[]>();
65
+ const inDegree = new Map<ComputeNode, number>();
66
+
67
+ for (const node of nodes) {
68
+ adjacency.set(node, []);
69
+ inDegree.set(node, 0);
70
+ }
71
+
72
+ for (const [from, to] of edges) {
73
+ adjacency.get(from)!.push(to);
74
+ inDegree.set(to, inDegree.get(to)! + 1);
75
+ }
76
+
77
+ const queue: ComputeNode[] = [];
78
+ for (const node of nodes) {
79
+ if (inDegree.get(node) === 0) {
80
+ queue.push(node);
81
+ }
82
+ }
83
+
84
+ const sorted: ComputeNode[] = [];
85
+ let i = 0;
86
+
87
+ while (i < queue.length) {
88
+ const node = queue[i++];
89
+ sorted.push(node);
90
+
91
+ for (const dep of adjacency.get(node)!) {
92
+ const newDegree = inDegree.get(dep)! - 1;
93
+ inDegree.set(dep, newDegree);
94
+ if (newDegree === 0) {
95
+ queue.push(dep);
96
+ }
97
+ }
98
+ }
99
+
100
+ if (sorted.length !== nodes.length) {
101
+ throw new CycleError();
102
+ }
103
+
104
+ return sorted;
105
+ }
106
+
107
+ function compile(nodes: ComputeNode[]): ExecutionPlan {
108
+ if (nodes.length === 0) {
109
+ return { sorted: [] };
110
+ }
111
+
112
+ const byPhase = new Map<Phase, ComputeNode[]>();
113
+ for (const phase of PHASE_ORDER) {
114
+ byPhase.set(phase, []);
115
+ }
116
+
117
+ for (const node of nodes) {
118
+ const phase = node.phase ?? "render";
119
+ byPhase.get(phase)!.push(node);
120
+ }
121
+
122
+ const sorted: ComputeNode[] = [];
123
+ for (const phase of PHASE_ORDER) {
124
+ const phaseNodes = byPhase.get(phase)!;
125
+ sorted.push(...topoSort(phaseNodes));
126
+ }
127
+
128
+ return { sorted };
129
+ }
130
+
131
+ export class ComputeGraph {
132
+ readonly nodes = new Map<NodeId, ComputeNode>();
133
+ private _plan: ExecutionPlan | null = null;
134
+
135
+ add(node: ComputeNode): void {
136
+ if (this.nodes.has(node.id)) {
137
+ throw new Error(`Node '${node.id}' already exists`);
138
+ }
139
+ this.nodes.set(node.id, node);
140
+ this._plan = null;
141
+ }
142
+
143
+ set(id: NodeId, node: ComputeNode): void {
144
+ if (node.id !== id) {
145
+ throw new Error(`Node id '${node.id}' must match slot id '${id}'`);
146
+ }
147
+ this.nodes.set(id, node);
148
+ this._plan = null;
149
+ }
150
+
151
+ remove(id: NodeId): boolean {
152
+ const removed = this.nodes.delete(id);
153
+ if (removed) {
154
+ this._plan = null;
155
+ }
156
+ return removed;
157
+ }
158
+
159
+ compile(): ExecutionPlan {
160
+ if (!this._plan) {
161
+ this._plan = compile(Array.from(this.nodes.values()));
162
+ }
163
+ return this._plan;
164
+ }
165
+ }
@@ -0,0 +1,116 @@
1
+ import { resource, type Plugin, type State, type System } from "../../core";
2
+ import { ComputeGraph, type ExecutionContext, type ResourceId } from "./graph";
3
+
4
+ export * from "./graph";
5
+
6
+ export function createEntityIdBuffer(device: GPUDevice, maxInstances: number): GPUBuffer {
7
+ return device.createBuffer({
8
+ label: "entityIds",
9
+ size: maxInstances * 4,
10
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,
11
+ });
12
+ }
13
+
14
+ export async function requestGPU(): Promise<GPUDevice> {
15
+ if (!navigator.gpu) {
16
+ throw new Error("WebGPU not supported");
17
+ }
18
+
19
+ const adapter = await navigator.gpu.requestAdapter();
20
+ if (!adapter) {
21
+ throw new Error("No GPU adapter found");
22
+ }
23
+
24
+ return adapter.requestDevice();
25
+ }
26
+
27
+ export interface ComputeResources {
28
+ textures: Map<ResourceId, GPUTexture>;
29
+ textureViews: Map<ResourceId, GPUTextureView>;
30
+ buffers: Map<ResourceId, GPUBuffer>;
31
+ }
32
+
33
+ export interface ComputeState {
34
+ readonly device: GPUDevice;
35
+ readonly context: GPUCanvasContext;
36
+ readonly format: GPUTextureFormat;
37
+ readonly graph: ComputeGraph;
38
+ readonly resources: ComputeResources;
39
+ }
40
+
41
+ export const Compute = resource<ComputeState>("compute");
42
+
43
+ export const ComputeSystem: System = {
44
+ group: "draw",
45
+
46
+ update(state: State) {
47
+ const compute = Compute.from(state);
48
+ if (!compute) return;
49
+
50
+ const { device, context, format, graph, resources } = compute;
51
+ const plan = graph.compile();
52
+
53
+ if (plan.sorted.length === 0) return;
54
+
55
+ const canvasTexture = context.getCurrentTexture();
56
+ const canvasView = canvasTexture.createView();
57
+ const encoder = device.createCommandEncoder();
58
+
59
+ const ctx: ExecutionContext = {
60
+ device,
61
+ queue: device.queue,
62
+ encoder,
63
+ context,
64
+ format,
65
+ canvasView,
66
+ getTexture(id: ResourceId) {
67
+ return resources.textures.get(id) ?? null;
68
+ },
69
+ getTextureView(id: ResourceId) {
70
+ return resources.textureViews.get(id) ?? null;
71
+ },
72
+ getBuffer(id: ResourceId) {
73
+ return resources.buffers.get(id) ?? null;
74
+ },
75
+ };
76
+
77
+ for (const node of plan.sorted) {
78
+ node.execute(ctx);
79
+ }
80
+
81
+ device.queue.submit([encoder.finish()]);
82
+ },
83
+ };
84
+
85
+ export const ComputePlugin: Plugin = {
86
+ systems: [ComputeSystem],
87
+
88
+ async initialize(state: State) {
89
+ if (!state.canvas) {
90
+ throw new Error("ComputePlugin requires a canvas");
91
+ }
92
+
93
+ const device = await requestGPU();
94
+
95
+ const context = state.canvas.getContext("webgpu");
96
+ if (!context) {
97
+ throw new Error("Failed to get WebGPU context");
98
+ }
99
+
100
+ const format = navigator.gpu.getPreferredCanvasFormat();
101
+ context.configure({
102
+ device,
103
+ format,
104
+ alphaMode: "premultiplied",
105
+ });
106
+
107
+ const graph = new ComputeGraph();
108
+ const resources: ComputeResources = {
109
+ textures: new Map(),
110
+ textureViews: new Map(),
111
+ buffers: new Map(),
112
+ };
113
+
114
+ state.setResource(Compute, { device, context, format, graph, resources });
115
+ },
116
+ };
@@ -0,0 +1,17 @@
1
+ import type { Plugin } from "../core/types";
2
+ import { StateBuilder } from "../core/builder";
3
+ import { TransformsPlugin } from "./transforms";
4
+ import { InputPlugin } from "./input";
5
+ import { canvasLoading } from "./loading";
6
+ import { ComputePlugin } from "./compute";
7
+ import { RenderPlugin } from "./render";
8
+
9
+ export const DEFAULT_PLUGINS: readonly Plugin[] = [
10
+ TransformsPlugin,
11
+ InputPlugin,
12
+ ComputePlugin,
13
+ RenderPlugin,
14
+ ];
15
+
16
+ StateBuilder.defaultPlugins = DEFAULT_PLUGINS;
17
+ StateBuilder.loading = canvasLoading;
@@ -0,0 +1,7 @@
1
+ export * from "./compute";
2
+ export * from "./input";
3
+ export * from "./tween";
4
+ export * from "./transforms";
5
+ export * from "./render";
6
+ export * from "./loading";
7
+ export * from "./defaults";
@@ -0,0 +1,142 @@
1
+ import {
2
+ resource,
3
+ type State,
4
+ type System,
5
+ type Plugin,
6
+ type InputState,
7
+ type MouseState,
8
+ } from "../../core";
9
+
10
+ export const Input = resource<InputState>("input");
11
+
12
+ const keys = new Set<string>();
13
+ const keysPressed = new Set<string>();
14
+ const keysReleased = new Set<string>();
15
+
16
+ const mouse: MouseState = {
17
+ deltaX: 0,
18
+ deltaY: 0,
19
+ scrollDelta: 0,
20
+ left: false,
21
+ right: false,
22
+ middle: false,
23
+ };
24
+
25
+ const inputState: InputState = {
26
+ mouse,
27
+ isKeyDown: (code: string) => keys.has(code),
28
+ isKeyPressed: (code: string) => keysPressed.has(code),
29
+ isKeyReleased: (code: string) => keysReleased.has(code),
30
+ };
31
+
32
+ let canvas: HTMLCanvasElement | null = null;
33
+
34
+ function handleKeyDown(e: KeyboardEvent): void {
35
+ if (!keys.has(e.code)) {
36
+ keysPressed.add(e.code);
37
+ }
38
+ keys.add(e.code);
39
+ }
40
+
41
+ function handleKeyUp(e: KeyboardEvent): void {
42
+ keys.delete(e.code);
43
+ keysReleased.add(e.code);
44
+ }
45
+
46
+ function handleMouseDown(e: MouseEvent): void {
47
+ if (e.target !== canvas) return;
48
+ if (e.button === 0) mouse.left = true;
49
+ if (e.button === 1) mouse.middle = true;
50
+ if (e.button === 2) mouse.right = true;
51
+ }
52
+
53
+ function handleMouseUp(e: MouseEvent): void {
54
+ if (e.button === 0) mouse.left = false;
55
+ if (e.button === 1) mouse.middle = false;
56
+ if (e.button === 2) mouse.right = false;
57
+ }
58
+
59
+ function handleMouseMove(e: MouseEvent): void {
60
+ mouse.deltaX += e.movementX;
61
+ mouse.deltaY += e.movementY;
62
+ }
63
+
64
+ function handleWheel(e: WheelEvent): void {
65
+ if (e.target !== canvas) return;
66
+ mouse.scrollDelta += e.deltaY;
67
+ e.preventDefault();
68
+ }
69
+
70
+ function handleContextMenu(e: Event): void {
71
+ if (e.target === canvas) {
72
+ e.preventDefault();
73
+ }
74
+ }
75
+
76
+ function resetFrameState(): void {
77
+ keysPressed.clear();
78
+ keysReleased.clear();
79
+ mouse.deltaX = 0;
80
+ mouse.deltaY = 0;
81
+ mouse.scrollDelta = 0;
82
+ }
83
+
84
+ function clearAllState(): void {
85
+ keys.clear();
86
+ keysPressed.clear();
87
+ keysReleased.clear();
88
+ mouse.deltaX = 0;
89
+ mouse.deltaY = 0;
90
+ mouse.scrollDelta = 0;
91
+ mouse.left = false;
92
+ mouse.right = false;
93
+ mouse.middle = false;
94
+ }
95
+
96
+ export const InputSystem: System = {
97
+ group: "simulation",
98
+
99
+ setup(state: State) {
100
+ if (!state.canvas) return;
101
+ canvas = state.canvas;
102
+
103
+ window.addEventListener("keydown", handleKeyDown);
104
+ window.addEventListener("keyup", handleKeyUp);
105
+ canvas.addEventListener("mousedown", handleMouseDown);
106
+ window.addEventListener("mouseup", handleMouseUp);
107
+ canvas.addEventListener("mousemove", handleMouseMove);
108
+ canvas.addEventListener("wheel", handleWheel, { passive: false });
109
+ canvas.addEventListener("contextmenu", handleContextMenu);
110
+
111
+ state.setResource(Input, inputState);
112
+ },
113
+
114
+ dispose(state: State) {
115
+ if (canvas) {
116
+ window.removeEventListener("keydown", handleKeyDown);
117
+ window.removeEventListener("keyup", handleKeyUp);
118
+ canvas.removeEventListener("mousedown", handleMouseDown);
119
+ window.removeEventListener("mouseup", handleMouseUp);
120
+ canvas.removeEventListener("mousemove", handleMouseMove);
121
+ canvas.removeEventListener("wheel", handleWheel);
122
+ canvas.removeEventListener("contextmenu", handleContextMenu);
123
+ canvas = null;
124
+ }
125
+
126
+ clearAllState();
127
+ state.deleteResource(Input);
128
+ },
129
+ };
130
+
131
+ const InputResetSystem: System = {
132
+ group: "draw",
133
+ last: true,
134
+
135
+ update() {
136
+ resetFrameState();
137
+ },
138
+ };
139
+
140
+ export const InputPlugin: Plugin = {
141
+ systems: [InputSystem, InputResetSystem],
142
+ };
@@ -0,0 +1,136 @@
1
+ import type { Loading } from "../../core";
2
+
3
+ interface Theme {
4
+ bg: string;
5
+ track: string;
6
+ bar: string;
7
+ }
8
+
9
+ const dark: Theme = { bg: "#1a1a1a", track: "#333", bar: "#E8A86B" };
10
+ const light: Theme = { bg: "#f5f5f5", track: "#ddd", bar: "#B87654" };
11
+
12
+ const LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80">
13
+ <defs>
14
+ <radialGradient id="baseGradient" cx="35%" cy="30%" r="70%" fx="25%" fy="20%">
15
+ <stop offset="0%" stop-color="#F5D4B8"/>
16
+ <stop offset="45%" stop-color="#E8A86B"/>
17
+ <stop offset="100%" stop-color="#B87654"/>
18
+ </radialGradient>
19
+ </defs>
20
+ <g transform="rotate(35 40.0 40.0)">
21
+ <path d="M40.0,2 C44.0,10 66.0,28 66.0,46 C66.0,60 48.0,70 40.0,78 C32.0,70 14.0,60 14.0,46 C14.0,28 36.0,10 40.0,2 Z" fill="url(#baseGradient)"/>
22
+ <path d="M40.0,6 C37.0,14 22.0,28 20.0,44 C20.0,52 28.0,62 36.0,70 C34.0,58 26.0,46 26.0,38 C26.0,26 38.0,12 40.0,6 Z" fill="#B87654" opacity="0.45"/>
23
+ <path d="M40.0,6 C43.0,14 58.0,28 60.0,44 C60.0,52 52.0,62 44.0,70 C46.0,58 54.0,46 54.0,38 C54.0,26 42.0,12 40.0,6 Z" fill="#B87654" opacity="0.35"/>
24
+ <path d="M40.0,8 C40.0,20 40.0,50 40.0,72" stroke="#B87654" stroke-width="1" stroke-opacity="0.4" fill="none" stroke-linecap="round"/>
25
+ <path d="M40.0,78 C48.0,70 66.0,60 66.0,46 C61.0,58 44.0,70 40.0,73 Z" fill="#B87654"/>
26
+ <path d="M40.0,2 C36.0,10 14.0,28 14.0,46 C19.0,30 41.0,8 40.0,7 Z" fill="#F5D4B8" opacity="0.7"/>
27
+ <path d="M40.0,2 C44.0,10 66.0,28 66.0,46 C66.0,60 48.0,70 40.0,78 C32.0,70 14.0,60 14.0,46 C14.0,28 36.0,10 40.0,2 Z" fill="none" stroke="#6B4230" stroke-width="2"/>
28
+ </g>
29
+ </svg>`;
30
+
31
+ function createOverlay(canvas: HTMLCanvasElement, bg: string): HTMLDivElement {
32
+ const overlay = document.createElement("div");
33
+ overlay.style.cssText = `
34
+ position: absolute;
35
+ inset: 0;
36
+ display: flex;
37
+ flex-direction: column;
38
+ align-items: center;
39
+ justify-content: center;
40
+ background: ${bg};
41
+ z-index: 1000;
42
+ `;
43
+
44
+ const parent = canvas.parentElement;
45
+ if (parent) {
46
+ if (getComputedStyle(parent).position === "static") {
47
+ parent.style.position = "relative";
48
+ }
49
+ parent.appendChild(overlay);
50
+ }
51
+
52
+ return overlay;
53
+ }
54
+
55
+ function createProgressBar(theme: Theme): { track: HTMLDivElement; bar: HTMLDivElement } {
56
+ const track = document.createElement("div");
57
+ track.style.cssText = `
58
+ width: 200px;
59
+ height: 4px;
60
+ background: ${theme.track};
61
+ border-radius: 2px;
62
+ overflow: hidden;
63
+ `;
64
+
65
+ const bar = document.createElement("div");
66
+ bar.style.cssText = `
67
+ width: 0%;
68
+ height: 100%;
69
+ background: ${theme.bar};
70
+ transition: width 0.15s ease-out;
71
+ `;
72
+ track.appendChild(bar);
73
+
74
+ return { track, bar };
75
+ }
76
+
77
+ function shallotLoading(canvas: HTMLCanvasElement, theme: Theme): Loading {
78
+ let overlay: HTMLDivElement | null = null;
79
+ let bar: HTMLDivElement | null = null;
80
+
81
+ return {
82
+ show() {
83
+ overlay = createOverlay(canvas, theme.bg);
84
+
85
+ const logo = document.createElement("div");
86
+ logo.innerHTML = LOGO_SVG;
87
+ logo.style.cssText = "width: 64px; height: 64px; margin-bottom: 24px;";
88
+ overlay.appendChild(logo);
89
+
90
+ const progressBar = createProgressBar(theme);
91
+ bar = progressBar.bar;
92
+ overlay.appendChild(progressBar.track);
93
+
94
+ return () => {
95
+ overlay?.remove();
96
+ overlay = null;
97
+ bar = null;
98
+ };
99
+ },
100
+
101
+ update(progress) {
102
+ if (bar) bar.style.width = `${progress * 100}%`;
103
+ },
104
+ };
105
+ }
106
+
107
+ function minimalLoading(canvas: HTMLCanvasElement, theme: Theme): Loading {
108
+ let overlay: HTMLDivElement | null = null;
109
+ let bar: HTMLDivElement | null = null;
110
+
111
+ return {
112
+ show() {
113
+ overlay = createOverlay(canvas, theme.bg);
114
+ const progressBar = createProgressBar(theme);
115
+ bar = progressBar.bar;
116
+ overlay.appendChild(progressBar.track);
117
+
118
+ return () => {
119
+ overlay?.remove();
120
+ overlay = null;
121
+ bar = null;
122
+ };
123
+ },
124
+
125
+ update(progress) {
126
+ if (bar) bar.style.width = `${progress * 100}%`;
127
+ },
128
+ };
129
+ }
130
+
131
+ export const shallotDark = (canvas: HTMLCanvasElement): Loading => shallotLoading(canvas, dark);
132
+ export const shallotLight = (canvas: HTMLCanvasElement): Loading => shallotLoading(canvas, light);
133
+ export const minimalDark = (canvas: HTMLCanvasElement): Loading => minimalLoading(canvas, dark);
134
+ export const minimalLight = (canvas: HTMLCanvasElement): Loading => minimalLoading(canvas, light);
135
+
136
+ export { shallotDark as canvasLoading };
@@ -0,0 +1,87 @@
1
+ import { setTraits } from "../../core/component";
2
+ import { WorldTransform } from "../transforms";
3
+ import { clearColor } from "./forward";
4
+ import { perspective, multiply, invert } from "./scene";
5
+
6
+ export const RenderMode = {
7
+ Raster: 0,
8
+ Raytracing: 1,
9
+ } as const;
10
+
11
+ export const DebugMode = {
12
+ Color: 0,
13
+ Depth: 1,
14
+ Normal: 2,
15
+ Material: 3,
16
+ Hit: 4,
17
+ } as const;
18
+
19
+ export const Camera = {
20
+ fov: [] as number[],
21
+ near: [] as number[],
22
+ far: [] as number[],
23
+ active: [] as number[],
24
+ clearColor: [] as number[],
25
+ renderMode: [] as number[],
26
+ debugMode: [] as number[],
27
+ };
28
+
29
+ setTraits(Camera, {
30
+ defaults: () => ({
31
+ fov: 60,
32
+ near: 0.1,
33
+ far: 1000,
34
+ active: 1,
35
+ clearColor: 0x1a1a1a,
36
+ renderMode: RenderMode.Raster,
37
+ debugMode: DebugMode.Color,
38
+ }),
39
+ });
40
+
41
+ export const Tonemap = {
42
+ exposure: [] as number[],
43
+ };
44
+
45
+ setTraits(Tonemap, {
46
+ defaults: () => ({ exposure: 1.0 }),
47
+ });
48
+
49
+ export const FXAA = {};
50
+
51
+ export const Vignette = {
52
+ strength: [] as number[],
53
+ inner: [] as number[],
54
+ outer: [] as number[],
55
+ };
56
+
57
+ setTraits(Vignette, {
58
+ defaults: () => ({ strength: 0.5, inner: 0.4, outer: 0.8 }),
59
+ });
60
+
61
+ export function unpackColor(packed: number): { r: number; g: number; b: number } {
62
+ return {
63
+ r: ((packed >> 16) & 0xff) / 255,
64
+ g: ((packed >> 8) & 0xff) / 255,
65
+ b: (packed & 0xff) / 255,
66
+ };
67
+ }
68
+
69
+ export function uploadCamera(
70
+ device: GPUDevice,
71
+ buffer: GPUBuffer,
72
+ eid: number,
73
+ aspect: number
74
+ ): void {
75
+ const color = unpackColor(Camera.clearColor[eid]);
76
+ clearColor.r = color.r;
77
+ clearColor.g = color.g;
78
+ clearColor.b = color.b;
79
+
80
+ const proj = perspective(Camera.fov[eid], aspect, Camera.near[eid], Camera.far[eid]);
81
+ const world = WorldTransform.data.subarray(eid * 16, eid * 16 + 16);
82
+ const view = invert(world);
83
+ const viewProj = multiply(proj, view);
84
+
85
+ device.queue.writeBuffer(buffer, 0, viewProj as Float32Array<ArrayBuffer>);
86
+ device.queue.writeBuffer(buffer, 64, world);
87
+ }