@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,368 @@
1
+ import { MAX_ENTITIES, resource, type Plugin, type State, type System } from "../../core";
2
+ import { setTraits, type FieldAccessor } from "../../core/component";
3
+ import {
4
+ Compute,
5
+ ComputePlugin,
6
+ createEntityIdBuffer,
7
+ type ComputeNode,
8
+ type ExecutionContext,
9
+ } from "../../standard/compute";
10
+ import { Render, RenderPlugin } from "../../standard/render";
11
+ import { Transform } from "../../standard/transforms";
12
+
13
+ export const LineData = {
14
+ data: new Float32Array(MAX_ENTITIES * 12),
15
+ };
16
+
17
+ interface LineProxy extends Array<number>, FieldAccessor {}
18
+
19
+ function lineProxy(offset: number): LineProxy {
20
+ const data = LineData.data;
21
+
22
+ function getValue(eid: number): number {
23
+ return data[eid * 12 + offset];
24
+ }
25
+
26
+ function setValue(eid: number, value: number): void {
27
+ data[eid * 12 + offset] = value;
28
+ }
29
+
30
+ return new Proxy([] as unknown as LineProxy, {
31
+ get(_, prop) {
32
+ if (prop === "get") return getValue;
33
+ if (prop === "set") return setValue;
34
+ const eid = Number(prop);
35
+ if (Number.isNaN(eid)) return undefined;
36
+ return getValue(eid);
37
+ },
38
+ set(_, prop, value) {
39
+ const eid = Number(prop);
40
+ if (Number.isNaN(eid)) return false;
41
+ setValue(eid, value);
42
+ return true;
43
+ },
44
+ });
45
+ }
46
+
47
+ function colorProxy(): LineProxy {
48
+ const data = LineData.data;
49
+
50
+ function getValue(eid: number): number {
51
+ const offset = eid * 12 + 8;
52
+ const r = Math.round(data[offset] * 255);
53
+ const g = Math.round(data[offset + 1] * 255);
54
+ const b = Math.round(data[offset + 2] * 255);
55
+ return (r << 16) | (g << 8) | b;
56
+ }
57
+
58
+ function setValue(eid: number, value: number): void {
59
+ const offset = eid * 12 + 8;
60
+ data[offset] = ((value >> 16) & 0xff) / 255;
61
+ data[offset + 1] = ((value >> 8) & 0xff) / 255;
62
+ data[offset + 2] = (value & 0xff) / 255;
63
+ data[offset + 3] = 1;
64
+ }
65
+
66
+ return new Proxy([] as unknown as LineProxy, {
67
+ get(_, prop) {
68
+ if (prop === "get") return getValue;
69
+ if (prop === "set") return setValue;
70
+ const eid = Number(prop);
71
+ if (Number.isNaN(eid)) return undefined;
72
+ return getValue(eid);
73
+ },
74
+ set(_, prop, value) {
75
+ const eid = Number(prop);
76
+ if (Number.isNaN(eid)) return false;
77
+ setValue(eid, value);
78
+ return true;
79
+ },
80
+ });
81
+ }
82
+
83
+ export const Line: {
84
+ offsetX: LineProxy;
85
+ offsetY: LineProxy;
86
+ offsetZ: LineProxy;
87
+ thickness: LineProxy;
88
+ visible: LineProxy;
89
+ opacity: LineProxy;
90
+ color: LineProxy;
91
+ } = {
92
+ offsetX: lineProxy(0),
93
+ offsetY: lineProxy(1),
94
+ offsetZ: lineProxy(2),
95
+ thickness: lineProxy(3),
96
+ visible: lineProxy(4),
97
+ opacity: lineProxy(7),
98
+ color: colorProxy(),
99
+ };
100
+
101
+ setTraits(Line, {
102
+ defaults: () => ({
103
+ offsetX: 1,
104
+ offsetY: 0,
105
+ offsetZ: 0,
106
+ thickness: 2,
107
+ visible: 1,
108
+ opacity: 1,
109
+ color: 0xffffff,
110
+ }),
111
+ accessors: {
112
+ offsetX: Line.offsetX,
113
+ offsetY: Line.offsetY,
114
+ offsetZ: Line.offsetZ,
115
+ thickness: Line.thickness,
116
+ visible: Line.visible,
117
+ opacity: Line.opacity,
118
+ color: Line.color,
119
+ },
120
+ });
121
+
122
+ export interface LinesConfig {
123
+ scene: GPUBuffer;
124
+ lines: GPUBuffer;
125
+ entityIds: GPUBuffer;
126
+ matrices: GPUBuffer;
127
+ getCount: () => number;
128
+ }
129
+
130
+ export const lineShader = /* wgsl */ `
131
+ struct VertexOutput {
132
+ @builtin(position) position: vec4<f32>,
133
+ @location(0) color: vec4<f32>,
134
+ }
135
+
136
+ struct Scene {
137
+ viewProj: mat4x4<f32>,
138
+ cameraWorld: mat4x4<f32>,
139
+ }
140
+
141
+ struct LineData {
142
+ offset: vec3<f32>,
143
+ thickness: f32,
144
+ visible: f32,
145
+ _pad1: f32,
146
+ _pad2: f32,
147
+ opacity: f32,
148
+ color: vec4<f32>,
149
+ }
150
+
151
+ @group(0) @binding(0) var<uniform> scene: Scene;
152
+ @group(0) @binding(1) var<storage, read> entityIds: array<u32>;
153
+ @group(0) @binding(2) var<storage, read> lines: array<LineData>;
154
+ @group(0) @binding(3) var<storage, read> matrices: array<mat4x4<f32>>;
155
+
156
+ @vertex
157
+ fn vs(@builtin(vertex_index) vid: u32, @builtin(instance_index) iid: u32) -> VertexOutput {
158
+ let eid = entityIds[iid];
159
+ let line = lines[eid];
160
+ let transform = matrices[eid];
161
+
162
+ let start = transform[3].xyz;
163
+ let rotation = mat3x3<f32>(transform[0].xyz, transform[1].xyz, transform[2].xyz);
164
+ let end = start + rotation * line.offset;
165
+
166
+ let startClip = scene.viewProj * vec4(start, 1.0);
167
+ let endClip = scene.viewProj * vec4(end, 1.0);
168
+
169
+ let startNDC = startClip.xy / startClip.w;
170
+ let endNDC = endClip.xy / endClip.w;
171
+
172
+ let dir = endNDC - startNDC;
173
+ let len = length(dir);
174
+ let normDir = select(vec2(1.0, 0.0), dir / len, len > 0.0001);
175
+
176
+ // Scale with world-space distance to camera (soft clamp prevents extreme thickness)
177
+ let midpoint = (start + end) * 0.5;
178
+ let cameraPos = scene.cameraWorld[3].xyz;
179
+ let distToCamera = length(cameraPos - midpoint);
180
+ let refDist = 15.0;
181
+ let rawScale = distToCamera / refDist;
182
+ let maxScale = 3.0;
183
+ let zoomScale = max(1.0, rawScale / (1.0 + rawScale / maxScale));
184
+ let perp = vec2(-normDir.y, normDir.x) * line.thickness * 0.002 * zoomScale;
185
+
186
+ var pos: vec2<f32>;
187
+ var t: f32;
188
+ switch vid {
189
+ case 0u: { pos = startNDC - perp; t = 0.0; }
190
+ case 1u: { pos = startNDC + perp; t = 0.0; }
191
+ case 2u: { pos = endNDC + perp; t = 1.0; }
192
+ case 3u: { pos = startNDC - perp; t = 0.0; }
193
+ case 4u: { pos = endNDC + perp; t = 1.0; }
194
+ case 5u: { pos = endNDC - perp; t = 1.0; }
195
+ default: { pos = startNDC; t = 0.0; }
196
+ }
197
+
198
+ let depth = mix(startClip.z / startClip.w, endClip.z / endClip.w, t);
199
+
200
+ var out: VertexOutput;
201
+ out.position = vec4(pos, depth, 1.0);
202
+ out.color = vec4(line.color.rgb, line.color.a * line.opacity);
203
+ return out;
204
+ }
205
+
206
+ @fragment
207
+ fn fs(input: VertexOutput) -> @location(0) vec4<f32> {
208
+ return input.color;
209
+ }
210
+ `;
211
+
212
+ export function createLinesPipeline(
213
+ device: GPUDevice,
214
+ format: GPUTextureFormat
215
+ ): GPURenderPipeline {
216
+ const module = device.createShaderModule({ code: lineShader });
217
+
218
+ return device.createRenderPipeline({
219
+ layout: "auto",
220
+ vertex: {
221
+ module,
222
+ entryPoint: "vs",
223
+ },
224
+ fragment: {
225
+ module,
226
+ entryPoint: "fs",
227
+ targets: [
228
+ {
229
+ format,
230
+ blend: {
231
+ color: {
232
+ srcFactor: "src-alpha",
233
+ dstFactor: "one-minus-src-alpha",
234
+ operation: "add",
235
+ },
236
+ alpha: {
237
+ srcFactor: "one",
238
+ dstFactor: "one-minus-src-alpha",
239
+ operation: "add",
240
+ },
241
+ },
242
+ },
243
+ ],
244
+ },
245
+ primitive: {
246
+ topology: "triangle-list",
247
+ },
248
+ });
249
+ }
250
+
251
+ export function createLinesNode(config: LinesConfig): ComputeNode {
252
+ let pipeline: GPURenderPipeline | null = null;
253
+ let bindGroup: GPUBindGroup | null = null;
254
+
255
+ return {
256
+ id: "lines",
257
+ phase: "overlay",
258
+ inputs: [],
259
+ outputs: [],
260
+
261
+ execute(ctx: ExecutionContext) {
262
+ const count = config.getCount();
263
+ if (count === 0) return;
264
+
265
+ const { device, encoder, format } = ctx;
266
+ const targetView = ctx.getTextureView("scene") ?? ctx.canvasView;
267
+
268
+ if (!pipeline) {
269
+ pipeline = createLinesPipeline(device, format);
270
+ }
271
+
272
+ if (!bindGroup) {
273
+ bindGroup = device.createBindGroup({
274
+ layout: pipeline.getBindGroupLayout(0),
275
+ entries: [
276
+ { binding: 0, resource: { buffer: config.scene } },
277
+ { binding: 1, resource: { buffer: config.entityIds } },
278
+ { binding: 2, resource: { buffer: config.lines } },
279
+ { binding: 3, resource: { buffer: config.matrices } },
280
+ ],
281
+ });
282
+ }
283
+
284
+ const pass = encoder.beginRenderPass({
285
+ colorAttachments: [
286
+ {
287
+ view: targetView,
288
+ loadOp: "load" as const,
289
+ storeOp: "store" as const,
290
+ },
291
+ ],
292
+ });
293
+
294
+ pass.setPipeline(pipeline);
295
+ pass.setBindGroup(0, bindGroup);
296
+ pass.draw(6, count);
297
+ pass.end();
298
+ },
299
+ };
300
+ }
301
+
302
+ export interface LinesState {
303
+ buffer: GPUBuffer;
304
+ entityIds: GPUBuffer;
305
+ count: number;
306
+ }
307
+
308
+ export const Lines = resource<LinesState>("lines");
309
+
310
+ const entityIdArray = new Uint32Array(MAX_ENTITIES);
311
+
312
+ const LinesSystem: System = {
313
+ group: "draw",
314
+
315
+ update(state: State) {
316
+ const compute = Compute.from(state);
317
+ const lines = Lines.from(state);
318
+ if (!compute || !lines) return;
319
+
320
+ const { device } = compute;
321
+
322
+ let count = 0;
323
+ for (const eid of state.query([Line, Transform])) {
324
+ if (!Line.visible[eid]) continue;
325
+ entityIdArray[count++] = eid;
326
+ }
327
+
328
+ device.queue.writeBuffer(lines.buffer, 0, LineData.data);
329
+ device.queue.writeBuffer(lines.entityIds, 0, entityIdArray, 0, count);
330
+ lines.count = count;
331
+ },
332
+ };
333
+
334
+ export const LinesPlugin: Plugin = {
335
+ systems: [LinesSystem],
336
+ components: { Line },
337
+ dependencies: [ComputePlugin, RenderPlugin],
338
+
339
+ initialize(state: State) {
340
+ const compute = Compute.from(state);
341
+ const render = Render.from(state);
342
+ if (!compute || !render) return;
343
+
344
+ const { device } = compute;
345
+
346
+ const linesState: LinesState = {
347
+ buffer: device.createBuffer({
348
+ label: "lines",
349
+ size: MAX_ENTITIES * 12 * 4,
350
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
351
+ }),
352
+ entityIds: createEntityIdBuffer(device, MAX_ENTITIES),
353
+ count: 0,
354
+ };
355
+
356
+ state.setResource(Lines, linesState);
357
+
358
+ compute.graph.add(
359
+ createLinesNode({
360
+ scene: render.scene,
361
+ lines: linesState.buffer,
362
+ entityIds: linesState.entityIds,
363
+ matrices: render.matrices,
364
+ getCount: () => linesState.count,
365
+ })
366
+ );
367
+ },
368
+ };
@@ -0,0 +1,133 @@
1
+ import { setTraits } from "../../core/component";
2
+ import { clamp, lookAt, type State, type System, type Plugin } from "../../core";
3
+ import { Transform } from "../../standard/transforms";
4
+ import { Input, InputPlugin } from "../../standard/input";
5
+
6
+ const Tau = Math.PI * 2;
7
+
8
+ export const Orbit = {
9
+ target: [] as number[],
10
+ yaw: [] as number[],
11
+ pitch: [] as number[],
12
+ distance: [] as number[],
13
+ targetYaw: [] as number[],
14
+ targetPitch: [] as number[],
15
+ targetDistance: [] as number[],
16
+ minPitch: [] as number[],
17
+ maxPitch: [] as number[],
18
+ minDistance: [] as number[],
19
+ maxDistance: [] as number[],
20
+ smoothness: [] as number[],
21
+ sensitivity: [] as number[],
22
+ zoomSpeed: [] as number[],
23
+ };
24
+
25
+ setTraits(Orbit, {
26
+ defaults: () => ({
27
+ target: 0,
28
+ yaw: 0,
29
+ pitch: Math.PI / 6,
30
+ distance: 8,
31
+ targetYaw: 0,
32
+ targetPitch: Math.PI / 6,
33
+ targetDistance: 10,
34
+ minPitch: -Math.PI / 2 + 0.01,
35
+ maxPitch: Math.PI / 2 - 0.01,
36
+ minDistance: 1,
37
+ maxDistance: 25,
38
+ smoothness: 0.3,
39
+ sensitivity: 0.005,
40
+ zoomSpeed: 0.025,
41
+ }),
42
+ });
43
+
44
+ function smoothLerp(smoothness: number, dt: number): number {
45
+ return 1 - Math.pow(1 - smoothness, dt * 60);
46
+ }
47
+
48
+ function normalizeAngle(a: number): number {
49
+ return ((a % Tau) + Tau) % Tau;
50
+ }
51
+
52
+ function angleDiff(from: number, to: number): number {
53
+ const diff = normalizeAngle(to - from);
54
+ return diff > Math.PI ? diff - Tau : diff;
55
+ }
56
+
57
+ export const OrbitSystem: System = {
58
+ group: "simulation",
59
+
60
+ update(state: State) {
61
+ const input = Input.from(state);
62
+ const dt = state.time.deltaTime;
63
+
64
+ for (const eid of state.query([Orbit, Transform])) {
65
+ const sensitivity = Orbit.sensitivity[eid];
66
+ const zoomSpeed = Orbit.zoomSpeed[eid];
67
+ const minPitch = Orbit.minPitch[eid];
68
+ const maxPitch = Orbit.maxPitch[eid];
69
+ const minDistance = Orbit.minDistance[eid];
70
+ const maxDistance = Orbit.maxDistance[eid];
71
+ const smoothness = Orbit.smoothness[eid];
72
+
73
+ if (input?.mouse.right) {
74
+ Orbit.targetYaw[eid] -= input.mouse.deltaX * sensitivity;
75
+ Orbit.targetPitch[eid] = clamp(
76
+ Orbit.targetPitch[eid] + input.mouse.deltaY * sensitivity,
77
+ minPitch,
78
+ maxPitch
79
+ );
80
+ }
81
+
82
+ if (input && input.mouse.scrollDelta !== 0) {
83
+ const currentDistance = Orbit.targetDistance[eid];
84
+ const distanceScale = Math.max(0.3, currentDistance * 0.08);
85
+ const zoomDelta = input.mouse.scrollDelta * zoomSpeed * distanceScale;
86
+ Orbit.targetDistance[eid] = clamp(
87
+ currentDistance + zoomDelta,
88
+ minDistance,
89
+ maxDistance
90
+ );
91
+ }
92
+
93
+ const t = smoothLerp(smoothness, dt);
94
+ Orbit.yaw[eid] += angleDiff(Orbit.yaw[eid], Orbit.targetYaw[eid]) * t;
95
+ Orbit.pitch[eid] += (Orbit.targetPitch[eid] - Orbit.pitch[eid]) * t;
96
+ Orbit.distance[eid] += (Orbit.targetDistance[eid] - Orbit.distance[eid]) * t;
97
+
98
+ const yaw = Orbit.yaw[eid];
99
+ const pitch = Orbit.pitch[eid];
100
+ const distance = Orbit.distance[eid];
101
+
102
+ let targetX = 0,
103
+ targetY = 0,
104
+ targetZ = 0;
105
+ const targetEid = Orbit.target[eid];
106
+ if (targetEid && state.hasComponent(targetEid, Transform)) {
107
+ targetX = Transform.posX[targetEid];
108
+ targetY = Transform.posY[targetEid];
109
+ targetZ = Transform.posZ[targetEid];
110
+ }
111
+
112
+ const camX = targetX + distance * Math.cos(pitch) * Math.sin(yaw);
113
+ const camY = targetY + distance * Math.sin(pitch);
114
+ const camZ = targetZ + distance * Math.cos(pitch) * Math.cos(yaw);
115
+
116
+ Transform.posX[eid] = camX;
117
+ Transform.posY[eid] = camY;
118
+ Transform.posZ[eid] = camZ;
119
+
120
+ const rotation = lookAt(camX, camY, camZ, targetX, targetY, targetZ);
121
+ Transform.quatX[eid] = rotation.x;
122
+ Transform.quatY[eid] = rotation.y;
123
+ Transform.quatZ[eid] = rotation.z;
124
+ Transform.quatW[eid] = rotation.w;
125
+ }
126
+ },
127
+ };
128
+
129
+ export const OrbitPlugin: Plugin = {
130
+ systems: [OrbitSystem],
131
+ components: { Orbit },
132
+ dependencies: [InputPlugin],
133
+ };