@multiplekex/shallot 0.2.4 → 0.3.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 (62) hide show
  1. package/package.json +1 -1
  2. package/src/core/component.ts +1 -1
  3. package/src/core/index.ts +1 -13
  4. package/src/core/math.ts +186 -0
  5. package/src/core/state.ts +1 -1
  6. package/src/core/xml.ts +56 -41
  7. package/src/extras/arrows/index.ts +3 -3
  8. package/src/extras/caustic.ts +37 -0
  9. package/src/extras/gradient/index.ts +63 -69
  10. package/src/extras/index.ts +3 -0
  11. package/src/extras/lines/index.ts +3 -3
  12. package/src/extras/orbit/index.ts +1 -1
  13. package/src/extras/skylab/index.ts +314 -0
  14. package/src/extras/text/font.ts +69 -14
  15. package/src/extras/text/index.ts +17 -69
  16. package/src/extras/text/sdf.ts +13 -2
  17. package/src/extras/water/index.ts +119 -0
  18. package/src/standard/defaults.ts +2 -0
  19. package/src/standard/index.ts +2 -0
  20. package/src/standard/raster/batch.ts +149 -0
  21. package/src/standard/raster/forward.ts +832 -0
  22. package/src/standard/raster/index.ts +191 -0
  23. package/src/standard/raster/shadow.ts +408 -0
  24. package/src/standard/{render → raytracing}/bvh/blas.ts +336 -88
  25. package/src/standard/raytracing/bvh/radix.ts +473 -0
  26. package/src/standard/raytracing/bvh/refit.ts +711 -0
  27. package/src/standard/{render → raytracing}/bvh/structs.ts +0 -55
  28. package/src/standard/{render → raytracing}/bvh/tlas.ts +155 -140
  29. package/src/standard/{render → raytracing}/bvh/traverse.ts +72 -64
  30. package/src/standard/{render → raytracing}/depth.ts +9 -9
  31. package/src/standard/raytracing/index.ts +409 -0
  32. package/src/standard/{render → raytracing}/instance.ts +31 -16
  33. package/src/standard/{render → raytracing}/ray.ts +1 -1
  34. package/src/standard/raytracing/shaders.ts +798 -0
  35. package/src/standard/{render → raytracing}/triangle.ts +1 -1
  36. package/src/standard/render/camera.ts +96 -106
  37. package/src/standard/render/data.ts +1 -1
  38. package/src/standard/render/index.ts +136 -220
  39. package/src/standard/render/indirect.ts +9 -10
  40. package/src/standard/render/light.ts +2 -2
  41. package/src/standard/render/mesh.ts +404 -0
  42. package/src/standard/render/overlay.ts +8 -5
  43. package/src/standard/render/pass.ts +1 -1
  44. package/src/standard/render/postprocess.ts +263 -242
  45. package/src/standard/render/scene.ts +28 -16
  46. package/src/standard/render/surface/index.ts +81 -12
  47. package/src/standard/render/surface/shaders.ts +511 -0
  48. package/src/standard/render/surface/structs.ts +23 -6
  49. package/src/standard/tween/tween.ts +44 -115
  50. package/src/standard/render/bvh/radix.ts +0 -476
  51. package/src/standard/render/forward/index.ts +0 -259
  52. package/src/standard/render/forward/raster.ts +0 -228
  53. package/src/standard/render/mesh/box.ts +0 -20
  54. package/src/standard/render/mesh/index.ts +0 -446
  55. package/src/standard/render/mesh/plane.ts +0 -11
  56. package/src/standard/render/mesh/sphere.ts +0 -40
  57. package/src/standard/render/mesh/unified.ts +0 -96
  58. package/src/standard/render/shaders.ts +0 -484
  59. package/src/standard/render/surface/compile.ts +0 -67
  60. package/src/standard/render/surface/noise.ts +0 -45
  61. package/src/standard/render/surface/wgsl.ts +0 -573
  62. /package/src/standard/{render → raytracing}/intersection.ts +0 -0
@@ -28,6 +28,15 @@ struct Scene {
28
28
  reflectionDepth: u32,
29
29
  refractionDepth: u32,
30
30
  instanceCount: u32,
31
+ time: f32,
32
+ _pad0: f32,
33
+ _pad1: f32,
34
+ _pad2: f32,
35
+ frustumPlanes: array<vec4<f32>, 6>,
36
+ }`;
37
+
38
+ export const SKY_STRUCT_WGSL = /* wgsl */ `
39
+ struct Sky {
31
40
  hazeDensity: f32,
32
41
  _pad2: f32,
33
42
  _pad3: f32,
@@ -35,7 +44,6 @@ struct Scene {
35
44
  hazeColor: vec4<f32>,
36
45
  skyZenith: vec4<f32>,
37
46
  skyHorizon: vec4<f32>,
38
- frustumPlanes: array<vec4<f32>, 6>,
39
47
  moonParams: vec4<f32>,
40
48
  moonDirection: vec4<f32>,
41
49
  starParams: vec4<f32>,
@@ -56,6 +64,16 @@ struct Data {
56
64
  _pad2: u32,
57
65
  }`;
58
66
 
67
+ export const SHADOW_STRUCT_WGSL = /* wgsl */ `
68
+ struct Shadow {
69
+ cascade0ViewProj: mat4x4<f32>,
70
+ cascade1ViewProj: mat4x4<f32>,
71
+ cascade2ViewProj: mat4x4<f32>,
72
+ cascade3ViewProj: mat4x4<f32>,
73
+ cascadeSplits: vec4<f32>,
74
+ cascadeTexelSizes: vec4<f32>,
75
+ }`;
76
+
59
77
  export const WGSL_STRUCTS = /* wgsl */ `
60
78
  struct VertexInput {
61
79
  @location(0) position: vec3<f32>,
@@ -80,12 +98,11 @@ struct FragmentOutput {
80
98
 
81
99
  ${SCENE_STRUCT_WGSL}
82
100
 
101
+ ${DATA_STRUCT_WGSL}
102
+
83
103
  @group(0) @binding(0) var<uniform> scene: Scene;
84
104
  @group(0) @binding(1) var<storage, read> entityIds: array<u32>;
85
105
  @group(0) @binding(2) var<storage, read> matrices: array<mat4x4<f32>>;
86
- @group(0) @binding(3) var<storage, read> colors: array<vec4<f32>>;
87
- @group(0) @binding(4) var<storage, read> sizes: array<vec4<f32>>;
88
- @group(0) @binding(5) var<storage, read> pbr: array<vec4<f32>>;
89
- @group(0) @binding(6) var<storage, read> emission: array<vec4<f32>>;
90
- @group(0) @binding(7) var<storage, read> shapes: array<u32>;
106
+ @group(0) @binding(3) var<storage, read> sizes: array<vec4<f32>>;
107
+ @group(0) @binding(4) var<storage, read> data: array<Data>;
91
108
  `;
@@ -1,12 +1,4 @@
1
- import {
2
- defineRelation,
3
- registerPostLoadHook,
4
- toCamelCase,
5
- type State,
6
- type System,
7
- type Plugin,
8
- type PostLoadContext,
9
- } from "../../core";
1
+ import { defineRelation, toCamelCase, type State, type System, type Plugin } from "../../core";
10
2
  import {
11
3
  setTraits,
12
4
  getRegisteredComponent,
@@ -63,23 +55,17 @@ function bindFieldAccessor(
63
55
  return accessor;
64
56
  }
65
57
 
66
- function getFieldAccessor(bindingId: number): FieldAccessor | undefined {
67
- return fieldAccessors.get(bindingId);
68
- }
58
+ function getOrBindAccessor(tweenEid: number): FieldAccessor | undefined {
59
+ const existing = fieldAccessors.get(tweenEid);
60
+ if (existing) return existing;
69
61
 
70
- function parseTweenAttrs(attrs: Record<string, string>): Record<string, string> {
71
- if (attrs._value) {
72
- const parsed: Record<string, string> = {};
73
- for (const part of attrs._value.split(";")) {
74
- const colonIdx = part.indexOf(":");
75
- if (colonIdx === -1) continue;
76
- const key = part.slice(0, colonIdx).trim();
77
- const value = part.slice(colonIdx + 1).trim();
78
- if (key && value) parsed[key] = value;
79
- }
80
- return parsed;
81
- }
82
- return attrs;
62
+ const path = Tween.field[tweenEid];
63
+ if (!path) return undefined;
64
+
65
+ const parsed = resolveFieldPath(path);
66
+ if (!parsed) return undefined;
67
+
68
+ return bindFieldAccessor(tweenEid, parsed.component, parsed.field) ?? undefined;
83
69
  }
84
70
 
85
71
  export const TweenState = {
@@ -88,6 +74,29 @@ export const TweenState = {
88
74
  COMPLETE: 2,
89
75
  } as const;
90
76
 
77
+ const fieldPaths = new Map<number, string>();
78
+
79
+ function fieldProxy(): Record<number, string | undefined> {
80
+ return new Proxy({} as Record<number, string | undefined>, {
81
+ get(_, prop) {
82
+ const eid = Number(prop);
83
+ if (Number.isNaN(eid)) return undefined;
84
+ return fieldPaths.get(eid);
85
+ },
86
+ set(_, prop, value) {
87
+ const eid = Number(prop);
88
+ if (Number.isNaN(eid)) return false;
89
+ if (value === undefined || value === null) {
90
+ fieldPaths.delete(eid);
91
+ } else {
92
+ fieldPaths.set(eid, value as string);
93
+ }
94
+ fieldAccessors.delete(eid);
95
+ return true;
96
+ },
97
+ });
98
+ }
99
+
91
100
  export const Tween = {
92
101
  state: [] as number[],
93
102
  from: [] as number[],
@@ -95,7 +104,8 @@ export const Tween = {
95
104
  duration: [] as number[],
96
105
  elapsed: [] as number[],
97
106
  delay: [] as number[],
98
- easingIndex: [] as number[],
107
+ easing: [] as number[],
108
+ field: fieldProxy(),
99
109
  };
100
110
 
101
111
  setTraits(Tween, {
@@ -106,96 +116,18 @@ setTraits(Tween, {
106
116
  duration: 1,
107
117
  elapsed: 0,
108
118
  delay: 0,
109
- easingIndex: 0,
119
+ easing: 0,
110
120
  }),
111
- adapter: (attrs: Record<string, string>, eid: number) => {
112
- const parsed = parseTweenAttrs(attrs);
113
- const result: Record<string, number> = {};
114
-
115
- if (parsed.duration) result.duration = parseFloat(parsed.duration);
116
- if (parsed.delay) result.delay = parseFloat(parsed.delay);
117
- if (parsed.easing) result.easingIndex = getEasingIndex(parsed.easing);
118
-
119
- if (parsed.target) {
120
- setupTweenFromXml(parsed, eid);
121
- }
122
-
123
- return result;
124
- },
121
+ parse: { easing: getEasingIndex },
125
122
  });
126
123
 
127
- export const TweenTarget = defineRelation("tween-target", {
124
+ export const TweenTarget = defineRelation("target", {
128
125
  exclusive: true,
129
126
  });
130
127
 
131
- interface ParsedTargetPath {
132
- readonly entity: string;
133
- readonly component: string;
134
- readonly field: string;
135
- }
136
-
137
- function parseTargetPath(path: string): ParsedTargetPath | null {
138
- if (!path.startsWith("@")) return null;
139
-
140
- const rest = path.slice(1);
141
- const firstDot = rest.indexOf(".");
142
- if (firstDot === -1) return null;
143
-
144
- const entity = rest.slice(0, firstDot);
145
- const fieldPath = rest.slice(firstDot + 1);
146
- const dotIndex = fieldPath.lastIndexOf(".");
147
- if (dotIndex === -1) return null;
148
-
149
- return {
150
- entity,
151
- component: fieldPath.slice(0, dotIndex),
152
- field: fieldPath.slice(dotIndex + 1),
153
- };
154
- }
155
-
156
- interface PendingTween {
157
- readonly tweenEid: number;
158
- readonly target: string;
159
- readonly to: string;
160
- }
161
-
162
- let pendingXmlTweens: PendingTween[] = [];
163
-
164
- function setupTweenFromXml(attrs: Record<string, string>, tweenEid: number): void {
165
- pendingXmlTweens.push({
166
- tweenEid,
167
- target: attrs.target,
168
- to: attrs.to,
169
- });
170
- }
171
-
172
- export function finalizePendingTweens(state: State, context: PostLoadContext): void {
173
- for (const pending of pendingXmlTweens) {
174
- const parsed = parseTargetPath(pending.target);
175
- if (!parsed) continue;
176
-
177
- const targetEid = context.getEntityByName(parsed.entity);
178
- if (targetEid === null) continue;
179
-
180
- const binding = bindFieldAccessor(pending.tweenEid, parsed.component, parsed.field);
181
- if (!binding) continue;
182
-
183
- state.addRelation(pending.tweenEid, TweenTarget, targetEid);
184
- const toValue =
185
- pending.to.startsWith("0x") || pending.to.startsWith("0X")
186
- ? parseInt(pending.to, 16)
187
- : parseFloat(pending.to);
188
- if (!Number.isFinite(toValue)) {
189
- throw new Error(`Tween has invalid 'to' value: "${pending.to}" (parsed as ${toValue})`);
190
- }
191
- Tween.to[pending.tweenEid] = toValue;
192
- }
193
- pendingXmlTweens = [];
194
- }
195
-
196
128
  export function captureFromValue(state: State, tweenEid: number): void {
197
129
  const targetEid = state.getFirstRelationTarget(tweenEid, TweenTarget);
198
- const binding = getFieldAccessor(tweenEid);
130
+ const binding = getOrBindAccessor(tweenEid);
199
131
 
200
132
  if (binding && targetEid >= 0) {
201
133
  Tween.from[tweenEid] = binding.get(targetEid) ?? 0;
@@ -209,7 +141,7 @@ export function ensureResolved(state: State, tweenEid: number): void {
209
141
  if (duration > 0 && elapsed >= duration) return;
210
142
 
211
143
  const targetEid = state.getFirstRelationTarget(tweenEid, TweenTarget);
212
- const binding = getFieldAccessor(tweenEid);
144
+ const binding = getOrBindAccessor(tweenEid);
213
145
 
214
146
  if (binding && targetEid >= 0) {
215
147
  const toValue = Tween.to[tweenEid];
@@ -235,7 +167,7 @@ function updateTweens(state: State, dt: number): void {
235
167
  if (tweenState !== TweenState.PLAYING) continue;
236
168
 
237
169
  const targetEid = state.getFirstRelationTarget(tweenEid, TweenTarget);
238
- const binding = getFieldAccessor(tweenEid);
170
+ const binding = getOrBindAccessor(tweenEid);
239
171
 
240
172
  if (Tween.elapsed[tweenEid] === 0 && binding && targetEid >= 0) {
241
173
  Tween.from[tweenEid] = binding.get(targetEid) ?? 0;
@@ -253,7 +185,7 @@ function updateTweens(state: State, dt: number): void {
253
185
  );
254
186
  }
255
187
 
256
- const easingFn = getEasing(Tween.easingIndex[tweenEid]);
188
+ const easingFn = getEasing(Tween.easing[tweenEid]);
257
189
  const easedProgress = easingFn(rawProgress);
258
190
 
259
191
  const from = Tween.from[tweenEid];
@@ -305,7 +237,7 @@ export function createTween(
305
237
  Tween.to[tweenEid] = options.to;
306
238
  Tween.duration[tweenEid] = options.duration ?? 1;
307
239
  Tween.elapsed[tweenEid] = 0;
308
- Tween.easingIndex[tweenEid] = getEasingIndex(options.easing ?? "linear");
240
+ Tween.easing[tweenEid] = getEasingIndex(options.easing ?? "linear");
309
241
 
310
242
  return tweenEid;
311
243
  }
@@ -327,7 +259,4 @@ export const TweenPlugin: Plugin = {
327
259
  systems: [TweenSystem],
328
260
  components: { Tween, Sequence, Pause },
329
261
  relations: [TweenTarget],
330
- initialize() {
331
- registerPostLoadHook(finalizePendingTweens);
332
- },
333
262
  };