gloop-sim 0.0.0-stage → 1.0.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 (166) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -2
  3. package/dist/assert.d.ts +1 -0
  4. package/dist/assert.js +6 -0
  5. package/dist/assert.js.map +1 -0
  6. package/dist/backend/cpu.d.ts +2 -0
  7. package/dist/backend/cpu.js +23 -0
  8. package/dist/backend/cpu.js.map +1 -0
  9. package/dist/backend/gpu.d.ts +2 -0
  10. package/dist/backend/gpu.js +145 -0
  11. package/dist/backend/gpu.js.map +1 -0
  12. package/dist/backend/index.d.ts +14 -0
  13. package/dist/backend/index.js +23 -0
  14. package/dist/backend/index.js.map +1 -0
  15. package/dist/device.d.ts +11 -0
  16. package/dist/device.js +66 -0
  17. package/dist/device.js.map +1 -0
  18. package/dist/index.d.ts +18 -0
  19. package/dist/index.js +18 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/initializer/fixed.d.ts +2 -0
  22. package/dist/initializer/fixed.js +12 -0
  23. package/dist/initializer/fixed.js.map +1 -0
  24. package/dist/initializer/index.d.ts +14 -0
  25. package/dist/initializer/index.js +23 -0
  26. package/dist/initializer/index.js.map +1 -0
  27. package/dist/initializer/random.d.ts +2 -0
  28. package/dist/initializer/random.js +16 -0
  29. package/dist/initializer/random.js.map +1 -0
  30. package/dist/integrator/euler.d.ts +2 -0
  31. package/dist/integrator/euler.js +34 -0
  32. package/dist/integrator/euler.js.map +1 -0
  33. package/dist/integrator/index.d.ts +14 -0
  34. package/dist/integrator/index.js +23 -0
  35. package/dist/integrator/index.js.map +1 -0
  36. package/dist/integrator/semi-implicit-euler.d.ts +2 -0
  37. package/dist/integrator/semi-implicit-euler.js +36 -0
  38. package/dist/integrator/semi-implicit-euler.js.map +1 -0
  39. package/dist/model/elastic-network/cpu.d.ts +2 -0
  40. package/dist/model/elastic-network/cpu.js +90 -0
  41. package/dist/model/elastic-network/cpu.js.map +1 -0
  42. package/dist/model/elastic-network/gpu.d.ts +2 -0
  43. package/dist/model/elastic-network/gpu.js +114 -0
  44. package/dist/model/elastic-network/gpu.js.map +1 -0
  45. package/dist/model/elastic-network/index.d.ts +2 -0
  46. package/dist/model/elastic-network/index.js +69 -0
  47. package/dist/model/elastic-network/index.js.map +1 -0
  48. package/dist/model/index.d.ts +15 -0
  49. package/dist/model/index.js +24 -0
  50. package/dist/model/index.js.map +1 -0
  51. package/dist/model/inertial.d.ts +2 -0
  52. package/dist/model/inertial.js +17 -0
  53. package/dist/model/inertial.js.map +1 -0
  54. package/dist/render/backend/channels.d.ts +17 -0
  55. package/dist/render/backend/channels.js +35 -0
  56. package/dist/render/backend/channels.js.map +1 -0
  57. package/dist/render/backend/cloud.d.ts +77 -0
  58. package/dist/render/backend/cloud.js +29 -0
  59. package/dist/render/backend/cloud.js.map +1 -0
  60. package/dist/render/backend/compile.d.ts +31 -0
  61. package/dist/render/backend/compile.js +73 -0
  62. package/dist/render/backend/compile.js.map +1 -0
  63. package/dist/render/backend/glyph.d.ts +8 -0
  64. package/dist/render/backend/glyph.js +38 -0
  65. package/dist/render/backend/glyph.js.map +1 -0
  66. package/dist/render/backend/glyphs.d.ts +6 -0
  67. package/dist/render/backend/glyphs.js +146 -0
  68. package/dist/render/backend/glyphs.js.map +1 -0
  69. package/dist/render/backend/gpu.d.ts +12 -0
  70. package/dist/render/backend/gpu.js +278 -0
  71. package/dist/render/backend/gpu.js.map +1 -0
  72. package/dist/render/backend/material.d.ts +30 -0
  73. package/dist/render/backend/material.js +57 -0
  74. package/dist/render/backend/material.js.map +1 -0
  75. package/dist/render/backend/particles.d.ts +5 -0
  76. package/dist/render/backend/particles.js +22 -0
  77. package/dist/render/backend/particles.js.map +1 -0
  78. package/dist/render/backend/post.d.ts +29 -0
  79. package/dist/render/backend/post.js +77 -0
  80. package/dist/render/backend/post.js.map +1 -0
  81. package/dist/render/backend/solids.d.ts +10 -0
  82. package/dist/render/backend/solids.js +134 -0
  83. package/dist/render/backend/solids.js.map +1 -0
  84. package/dist/render/camera.d.ts +73 -0
  85. package/dist/render/camera.js +32 -0
  86. package/dist/render/camera.js.map +1 -0
  87. package/dist/render/channel.d.ts +25 -0
  88. package/dist/render/channel.js +9 -0
  89. package/dist/render/channel.js.map +1 -0
  90. package/dist/render/color.d.ts +34 -0
  91. package/dist/render/color.js +56 -0
  92. package/dist/render/color.js.map +1 -0
  93. package/dist/render/effect.d.ts +81 -0
  94. package/dist/render/effect.js +30 -0
  95. package/dist/render/effect.js.map +1 -0
  96. package/dist/render/frame.d.ts +60 -0
  97. package/dist/render/frame.js +52 -0
  98. package/dist/render/frame.js.map +1 -0
  99. package/dist/render/index.d.ts +12 -0
  100. package/dist/render/index.js +2 -0
  101. package/dist/render/index.js.map +1 -0
  102. package/dist/render/layer.d.ts +90 -0
  103. package/dist/render/layer.js +54 -0
  104. package/dist/render/layer.js.map +1 -0
  105. package/dist/render/ramp.d.ts +93 -0
  106. package/dist/render/ramp.js +120 -0
  107. package/dist/render/ramp.js.map +1 -0
  108. package/dist/render/sampler.d.ts +34 -0
  109. package/dist/render/sampler.js +53 -0
  110. package/dist/render/sampler.js.map +1 -0
  111. package/dist/render/signal.d.ts +66 -0
  112. package/dist/render/signal.js +51 -0
  113. package/dist/render/signal.js.map +1 -0
  114. package/dist/render/sources.d.ts +33 -0
  115. package/dist/render/sources.js +46 -0
  116. package/dist/render/sources.js.map +1 -0
  117. package/dist/render/springs/anchors.d.ts +37 -0
  118. package/dist/render/springs/anchors.js +34 -0
  119. package/dist/render/springs/anchors.js.map +1 -0
  120. package/dist/render/springs/construction.d.ts +30 -0
  121. package/dist/render/springs/construction.js +66 -0
  122. package/dist/render/springs/construction.js.map +1 -0
  123. package/dist/render/springs/impulses.d.ts +45 -0
  124. package/dist/render/springs/impulses.js +144 -0
  125. package/dist/render/springs/impulses.js.map +1 -0
  126. package/dist/render/springs/index.d.ts +9 -0
  127. package/dist/render/springs/index.js +2 -0
  128. package/dist/render/springs/index.js.map +1 -0
  129. package/dist/render/springs/link.d.ts +51 -0
  130. package/dist/render/springs/link.js +42 -0
  131. package/dist/render/springs/link.js.map +1 -0
  132. package/dist/render/springs/links.d.ts +70 -0
  133. package/dist/render/springs/links.js +210 -0
  134. package/dist/render/springs/links.js.map +1 -0
  135. package/dist/render/springs/palette.d.ts +36 -0
  136. package/dist/render/springs/palette.js +22 -0
  137. package/dist/render/springs/palette.js.map +1 -0
  138. package/dist/render/springs/preset.d.ts +65 -0
  139. package/dist/render/springs/preset.js +114 -0
  140. package/dist/render/springs/preset.js.map +1 -0
  141. package/dist/render/springs/segment.d.ts +40 -0
  142. package/dist/render/springs/segment.js +81 -0
  143. package/dist/render/springs/segment.js.map +1 -0
  144. package/dist/render/springs/source.d.ts +35 -0
  145. package/dist/render/springs/source.js +70 -0
  146. package/dist/render/springs/source.js.map +1 -0
  147. package/dist/render/springs/springs.d.ts +40 -0
  148. package/dist/render/springs/springs.js +34 -0
  149. package/dist/render/springs/springs.js.map +1 -0
  150. package/dist/render/springs/strain.d.ts +21 -0
  151. package/dist/render/springs/strain.js +39 -0
  152. package/dist/render/springs/strain.js.map +1 -0
  153. package/dist/render/stage.d.ts +96 -0
  154. package/dist/render/stage.js +2 -0
  155. package/dist/render/stage.js.map +1 -0
  156. package/dist/render/view.d.ts +63 -0
  157. package/dist/render/view.js +63 -0
  158. package/dist/render/view.js.map +1 -0
  159. package/dist/swarm.d.ts +5 -0
  160. package/dist/swarm.js +24 -0
  161. package/dist/swarm.js.map +1 -0
  162. package/dist/types.d.ts +290 -0
  163. package/dist/types.js +2 -0
  164. package/dist/types.js.map +1 -0
  165. package/docs/assets/logo.svg +112 -0
  166. package/package.json +102 -3
@@ -0,0 +1,77 @@
1
+ import type { Mesh } from "three/webgpu";
2
+ import { type Frame } from "../../render/frame.js";
3
+ import type { ParticleGlyph } from "../../render/layer.js";
4
+ import type { ColorValue, ScalarValue } from "../../render/ramp.js";
5
+ import type { Source } from "../../render/sources.js";
6
+ /**
7
+ * The position, size, and color of one glyph, in scene units and linear
8
+ * color.
9
+ */
10
+ export interface GlyphInstance {
11
+ halfSize: number;
12
+ center: {
13
+ x: number;
14
+ y: number;
15
+ z: number;
16
+ };
17
+ color: {
18
+ r: number;
19
+ g: number;
20
+ b: number;
21
+ a: number;
22
+ };
23
+ }
24
+ /**
25
+ * Reads the glyph drawn for one particle.
26
+ */
27
+ export type GlyphReader = (particle: number) => GlyphInstance;
28
+ export interface GlyphStyle {
29
+ /**
30
+ * The rendering data source from which the style values should be computed.
31
+ */
32
+ readonly source: Source;
33
+ /**
34
+ * The color of the glyphs.
35
+ */
36
+ readonly color: ColorValue;
37
+ /**
38
+ * The size of the glyphs.
39
+ */
40
+ readonly size: ScalarValue;
41
+ /**
42
+ * The opacity of the glyphs.
43
+ */
44
+ readonly opacity: ScalarValue;
45
+ }
46
+ export interface GlyphCloud {
47
+ /**
48
+ * The mesh used to render the glyphs.
49
+ */
50
+ readonly mesh: Mesh;
51
+ /**
52
+ * Updates the glyph cloud with the given frame data and index list.
53
+ */
54
+ update(frame: Frame, indices: readonly number[]): void;
55
+ /**
56
+ * Disposes the glyph cloud, freeing its resources.
57
+ */
58
+ dispose(): void;
59
+ }
60
+ export interface GlyphCloudOptions<G extends ParticleGlyph = ParticleGlyph> {
61
+ /**
62
+ * The shape of the glyphs.
63
+ */
64
+ shape: G;
65
+ /**
66
+ * Styling options for the glyphs.
67
+ */
68
+ style: GlyphStyle;
69
+ /**
70
+ * A scale factor for the size of the glyphs.
71
+ */
72
+ sizeScale: number;
73
+ }
74
+ /**
75
+ * Prepares a reader for the glyphs of `frame` in the given style.
76
+ */
77
+ export declare function prepareGlyphReader(style: GlyphStyle, sizeScale: number, frame: Frame): GlyphReader;
@@ -0,0 +1,29 @@
1
+ import { PackedPositions } from "../../render/frame.js";
2
+ const PARTICLE_HALF_SIZE_WORLD = 0.006;
3
+ /**
4
+ * Prepares a reader for the glyphs of `frame` in the given style.
5
+ */
6
+ export function prepareGlyphReader(style, sizeScale, frame) {
7
+ const sizeAt = style.size.prepare(style.source);
8
+ const colorAt = style.color.prepare(style.source);
9
+ const opacityAt = style.opacity.prepare(style.source);
10
+ const points = PackedPositions.of(frame.positions);
11
+ return (particle) => {
12
+ const rgba = colorAt(particle);
13
+ return {
14
+ halfSize: sizeAt(particle) * sizeScale * PARTICLE_HALF_SIZE_WORLD,
15
+ center: {
16
+ x: points.x(particle),
17
+ y: points.y(particle),
18
+ z: points.z(particle),
19
+ },
20
+ color: {
21
+ r: rgba.r,
22
+ g: rgba.g,
23
+ b: rgba.b,
24
+ a: rgba.a * opacityAt(particle),
25
+ },
26
+ };
27
+ };
28
+ }
29
+ //# sourceMappingURL=cloud.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloud.js","sourceRoot":"","sources":["../../../src/render/backend/cloud.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAKhE,MAAM,wBAAwB,GAAG,KAAK,CAAC;AAyEvC;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAiB,EAAE,SAAiB,EAAE,KAAY;IACnF,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,eAAe,CAAC,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAEnD,OAAO,CAAC,QAAgB,EAAE,EAAE;QAC1B,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC/B,OAAO;YACL,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,SAAS,GAAG,wBAAwB;YACjE,MAAM,EAAE;gBACN,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;gBACrB,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;gBACrB,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;aACtB;YACD,KAAK,EAAE;gBACL,CAAC,EAAE,IAAI,CAAC,CAAC;gBACT,CAAC,EAAE,IAAI,CAAC,CAAC;gBACT,CAAC,EAAE,IAAI,CAAC,CAAC;gBACT,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC,QAAQ,CAAC;aAChC;SACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type { Mesh } from \"three/webgpu\";\nimport { type Frame, PackedPositions } from \"@/render/frame.js\";\nimport type { ParticleGlyph } from \"@/render/layer.js\";\nimport type { ColorValue, ScalarValue } from \"@/render/ramp.js\";\nimport type { Source } from \"@/render/sources.js\";\n\nconst PARTICLE_HALF_SIZE_WORLD = 0.006;\n\n/**\n * The position, size, and color of one glyph, in scene units and linear\n * color.\n */\nexport interface GlyphInstance {\n halfSize: number;\n center: { x: number; y: number; z: number };\n color: { r: number; g: number; b: number; a: number };\n}\n\n/**\n * Reads the glyph drawn for one particle.\n */\nexport type GlyphReader = (particle: number) => GlyphInstance;\n\nexport interface GlyphStyle {\n /**\n * The rendering data source from which the style values should be computed.\n */\n readonly source: Source;\n\n /**\n * The color of the glyphs.\n */\n readonly color: ColorValue;\n\n /**\n * The size of the glyphs.\n */\n readonly size: ScalarValue;\n\n /**\n * The opacity of the glyphs.\n */\n readonly opacity: ScalarValue;\n}\n\nexport interface GlyphCloud {\n /**\n * The mesh used to render the glyphs.\n */\n readonly mesh: Mesh;\n\n /**\n * Updates the glyph cloud with the given frame data and index list.\n */\n update(frame: Frame, indices: readonly number[]): void;\n\n /**\n * Disposes the glyph cloud, freeing its resources.\n */\n dispose(): void;\n}\n\nexport interface GlyphCloudOptions<G extends ParticleGlyph = ParticleGlyph> {\n /**\n * The shape of the glyphs.\n */\n shape: G;\n\n /**\n * Styling options for the glyphs.\n */\n style: GlyphStyle;\n\n /**\n * A scale factor for the size of the glyphs.\n */\n sizeScale: number;\n}\n\n/**\n * Prepares a reader for the glyphs of `frame` in the given style.\n */\nexport function prepareGlyphReader(style: GlyphStyle, sizeScale: number, frame: Frame): GlyphReader {\n const sizeAt = style.size.prepare(style.source);\n const colorAt = style.color.prepare(style.source);\n const opacityAt = style.opacity.prepare(style.source);\n const points = PackedPositions.of(frame.positions);\n\n return (particle: number) => {\n const rgba = colorAt(particle);\n return {\n halfSize: sizeAt(particle) * sizeScale * PARTICLE_HALF_SIZE_WORLD,\n center: {\n x: points.x(particle),\n y: points.y(particle),\n z: points.z(particle),\n },\n color: {\n r: rgba.r,\n g: rgba.g,\n b: rgba.b,\n a: rgba.a * opacityAt(particle),\n },\n };\n };\n}\n"]}
@@ -0,0 +1,31 @@
1
+ import type { Node } from "three/webgpu";
2
+ import type { Channel } from "../../render/channel.js";
3
+ import { type ColorValue, type ScalarValue } from "../../render/ramp.js";
4
+ import type { Signal } from "../../render/signal.js";
5
+ import type { Source } from "../../render/sources.js";
6
+ /**
7
+ * A compiled scalar value in the shader node graph.
8
+ */
9
+ export type ScalarNode = Node<"float">;
10
+ /**
11
+ * A compiled color in the shader node graph, holding sRGB channels that are not
12
+ * scaled by opacity.
13
+ */
14
+ export type ColorNode = Node<"vec3">;
15
+ /**
16
+ * Returns the node holding a channel's raw per-element value.
17
+ */
18
+ export type ChannelNodes<S extends Source> = (channel: Channel<S>) => ScalarNode;
19
+ /**
20
+ * Maps a signal's channel from its domain into `0..1`. This is the GPU version
21
+ * of `normalizeSignal`.
22
+ */
23
+ export declare function compileSignal<S extends Source>(signal: Signal<S>, raw: ChannelNodes<S>): ScalarNode;
24
+ /**
25
+ * Compiles a dynamic scalar into a shader-graph node.
26
+ */
27
+ export declare function compileScalar<S extends Source>(value: ScalarValue<S>, raw: ChannelNodes<S>): ScalarNode;
28
+ /**
29
+ * Compiles a dynamic color into a `vec3` shader-graph node of sRGB channels.
30
+ */
31
+ export declare function compileColor<S extends Source>(value: ColorValue<S>, raw: ChannelNodes<S>): ColorNode;
@@ -0,0 +1,73 @@
1
+ import { clamp, float, mix, vec3 } from "three/tsl";
2
+ import { color as srgb } from "../../render/color.js";
3
+ import { ramp } from "../../render/ramp.js";
4
+ /**
5
+ * Maps a signal's channel from its domain into `0..1`. This is the GPU version
6
+ * of `normalizeSignal`.
7
+ */
8
+ export function compileSignal(signal, raw) {
9
+ const [low, high] = signal.domain;
10
+ const t = raw(signal.channel)
11
+ .sub(low)
12
+ .div(high - low);
13
+ return signal.clamp ? clamp(t, 0, 1) : t;
14
+ }
15
+ /**
16
+ * Compiles a dynamic scalar into a shader-graph node.
17
+ */
18
+ export function compileScalar(value, raw) {
19
+ if (!ramp.is(value)) {
20
+ return float(value.value);
21
+ }
22
+ return foldScalar(value.stops, compileSignal(value.by, raw));
23
+ }
24
+ /**
25
+ * Compiles a dynamic color into a `vec3` shader-graph node of sRGB channels.
26
+ */
27
+ export function compileColor(value, raw) {
28
+ if (!ramp.is(value)) {
29
+ return colorNode(value.value);
30
+ }
31
+ return foldColor(value.stops, compileSignal(value.by, raw));
32
+ }
33
+ function colorNode(value) {
34
+ const { r, g, b } = srgb.parse(value);
35
+ return vec3(r, g, b);
36
+ }
37
+ function localFraction(from, to, t) {
38
+ const span = to - from;
39
+ return span === 0 ? float(1) : clamp(t.sub(from).div(span), 0, 1);
40
+ }
41
+ function foldScalar(stops, t) {
42
+ const first = stops[0];
43
+ if (first === undefined) {
44
+ return float(0);
45
+ }
46
+ let value = float(first.value);
47
+ for (let index = 1; index < stops.length; index++) {
48
+ const from = stops[index - 1];
49
+ const to = stops[index];
50
+ if (from === undefined || to === undefined) {
51
+ continue;
52
+ }
53
+ value = mix(value, float(to.value), localFraction(from.at, to.at, t));
54
+ }
55
+ return value;
56
+ }
57
+ function foldColor(stops, t) {
58
+ const first = stops[0];
59
+ if (first === undefined) {
60
+ return vec3(0, 0, 0);
61
+ }
62
+ let value = colorNode(first.value);
63
+ for (let index = 1; index < stops.length; index++) {
64
+ const from = stops[index - 1];
65
+ const to = stops[index];
66
+ if (from === undefined || to === undefined) {
67
+ continue;
68
+ }
69
+ value = mix(value, colorNode(to.value), localFraction(from.at, to.at, t));
70
+ }
71
+ return value;
72
+ }
73
+ //# sourceMappingURL=compile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile.js","sourceRoot":"","sources":["../../../src/render/backend/compile.ts"],"names":[],"mappings":"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","sourcesContent":["import { clamp, float, mix, vec3 } from \"three/tsl\";\nimport type { Node } from \"three/webgpu\";\nimport type { Channel } from \"@/render/channel.js\";\nimport { type Color, color as srgb } from \"@/render/color.js\";\nimport { type ColorValue, ramp, type ScalarValue, type Stop } from \"@/render/ramp.js\";\nimport type { Signal } from \"@/render/signal.js\";\nimport type { Source } from \"@/render/sources.js\";\n\n/**\n * A compiled scalar value in the shader node graph.\n */\nexport type ScalarNode = Node<\"float\">;\n\n/**\n * A compiled color in the shader node graph, holding sRGB channels that are not\n * scaled by opacity.\n */\nexport type ColorNode = Node<\"vec3\">;\n\n/**\n * Returns the node holding a channel's raw per-element value.\n */\nexport type ChannelNodes<S extends Source> = (channel: Channel<S>) => ScalarNode;\n\n/**\n * Maps a signal's channel from its domain into `0..1`. This is the GPU version\n * of `normalizeSignal`.\n */\nexport function compileSignal<S extends Source>(signal: Signal<S>, raw: ChannelNodes<S>): ScalarNode {\n const [low, high] = signal.domain;\n const t = raw(signal.channel)\n .sub(low)\n .div(high - low);\n return signal.clamp ? clamp(t, 0, 1) : t;\n}\n\n/**\n * Compiles a dynamic scalar into a shader-graph node.\n */\nexport function compileScalar<S extends Source>(value: ScalarValue<S>, raw: ChannelNodes<S>): ScalarNode {\n if (!ramp.is(value)) {\n return float(value.value);\n }\n return foldScalar(value.stops, compileSignal(value.by, raw));\n}\n\n/**\n * Compiles a dynamic color into a `vec3` shader-graph node of sRGB channels.\n */\nexport function compileColor<S extends Source>(value: ColorValue<S>, raw: ChannelNodes<S>): ColorNode {\n if (!ramp.is(value)) {\n return colorNode(value.value);\n }\n return foldColor(value.stops, compileSignal(value.by, raw));\n}\n\nfunction colorNode(value: Color): ColorNode {\n const { r, g, b } = srgb.parse(value);\n return vec3(r, g, b);\n}\n\nfunction localFraction(from: number, to: number, t: ScalarNode): ScalarNode {\n const span = to - from;\n return span === 0 ? float(1) : clamp(t.sub(from).div(span), 0, 1);\n}\n\nfunction foldScalar(stops: readonly Stop<number>[], t: ScalarNode): ScalarNode {\n const first = stops[0];\n if (first === undefined) {\n return float(0);\n }\n let value: ScalarNode = float(first.value);\n for (let index = 1; index < stops.length; index++) {\n const from = stops[index - 1];\n const to = stops[index];\n if (from === undefined || to === undefined) {\n continue;\n }\n value = mix(value, float(to.value), localFraction(from.at, to.at, t));\n }\n return value;\n}\n\nfunction foldColor(stops: readonly Stop<Color>[], t: ScalarNode): ColorNode {\n const first = stops[0];\n if (first === undefined) {\n return vec3(0, 0, 0);\n }\n let value: ColorNode = colorNode(first.value);\n for (let index = 1; index < stops.length; index++) {\n const from = stops[index - 1];\n const to = stops[index];\n if (from === undefined || to === undefined) {\n continue;\n }\n value = mix(value, colorNode(to.value), localFraction(from.at, to.at, t));\n }\n return value;\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import type { Node } from "three/webgpu";
2
+ import type { FlatGlyph } from "../../render/layer.js";
3
+ /**
4
+ * Computes a particle glyph's opacity at one point inside its quad. `p` is the
5
+ * point's position from the glyph center: `-1` at one edge, `1` at the other, on
6
+ * each axis. The result is `0` for transparent and `1` for opaque.
7
+ */
8
+ export declare function glyphMask(shape: FlatGlyph, p: Node<"vec2">): Node<"float">;
@@ -0,0 +1,38 @@
1
+ import { abs, float, length, max, smoothstep } from "three/tsl";
2
+ const EDGE = 0.08;
3
+ function ring({ radius, width, distance }) {
4
+ return float(1).sub(smoothstep(width, width + EDGE, abs(distance.sub(radius))));
5
+ }
6
+ function bar({ across, along, width, extent }) {
7
+ const inWidth = float(1).sub(smoothstep(width, width + EDGE, abs(across)));
8
+ const inLength = float(1).sub(smoothstep(extent, extent + EDGE, abs(along)));
9
+ return inWidth.mul(inLength);
10
+ }
11
+ /**
12
+ * Computes a particle glyph's opacity at one point inside its quad. `p` is the
13
+ * point's position from the glyph center: `-1` at one edge, `1` at the other, on
14
+ * each axis. The result is `0` for transparent and `1` for opaque.
15
+ */
16
+ export function glyphMask(shape, p) {
17
+ const px = p.x;
18
+ const py = p.y;
19
+ const distance = length(p);
20
+ const diamond = abs(px).add(abs(py));
21
+ switch (shape) {
22
+ case "point":
23
+ return float(1).sub(smoothstep(0.45, 0.7, distance));
24
+ case "circle":
25
+ return ring({ radius: 0.6, width: 0.13, distance });
26
+ case "diamond":
27
+ return ring({ radius: 0.7, width: 0.14, distance: diamond });
28
+ case "cross":
29
+ return max(bar({ across: py, along: px, width: 0.13, extent: 0.85 }), bar({ across: px, along: py, width: 0.13, extent: 0.85 }));
30
+ case "reticle":
31
+ return max(ring({ radius: 0.58, width: 0.1, distance }), max(bar({ across: py, along: px, width: 0.07, extent: 0.9 }), bar({ across: px, along: py, width: 0.07, extent: 0.9 })));
32
+ case "gyroscope":
33
+ return max(max(ring({ radius: 0.85, width: 0.09, distance }), ring({ radius: 0.48, width: 0.09, distance })), max(bar({ across: py, along: px, width: 0.06, extent: 0.85 }), bar({ across: px, along: py, width: 0.06, extent: 0.85 })));
34
+ default:
35
+ return float(1);
36
+ }
37
+ }
38
+ //# sourceMappingURL=glyph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glyph.js","sourceRoot":"","sources":["../../../src/render/backend/glyph.ts"],"names":[],"mappings":"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","sourcesContent":["import { abs, float, length, max, smoothstep } from \"three/tsl\";\nimport type { Node } from \"three/webgpu\";\nimport type { FlatGlyph } from \"@/render/layer.js\";\n\nconst EDGE = 0.08;\n\ninterface RingOptions {\n radius: number;\n width: number;\n distance: Node<\"float\">;\n}\n\ninterface BarOptions {\n across: Node<\"float\">;\n along: Node<\"float\">;\n width: number;\n extent: number;\n}\n\nfunction ring({ radius, width, distance }: RingOptions): Node<\"float\"> {\n return float(1).sub(smoothstep(width, width + EDGE, abs(distance.sub(radius))));\n}\n\nfunction bar({ across, along, width, extent }: BarOptions): Node<\"float\"> {\n const inWidth = float(1).sub(smoothstep(width, width + EDGE, abs(across)));\n const inLength = float(1).sub(smoothstep(extent, extent + EDGE, abs(along)));\n return inWidth.mul(inLength);\n}\n\n/**\n * Computes a particle glyph's opacity at one point inside its quad. `p` is the\n * point's position from the glyph center: `-1` at one edge, `1` at the other, on\n * each axis. The result is `0` for transparent and `1` for opaque.\n */\nexport function glyphMask(shape: FlatGlyph, p: Node<\"vec2\">): Node<\"float\"> {\n const px = p.x;\n const py = p.y;\n const distance = length(p);\n const diamond = abs(px).add(abs(py));\n\n switch (shape) {\n case \"point\":\n return float(1).sub(smoothstep(0.45, 0.7, distance));\n case \"circle\":\n return ring({ radius: 0.6, width: 0.13, distance });\n case \"diamond\":\n return ring({ radius: 0.7, width: 0.14, distance: diamond });\n case \"cross\":\n return max(\n bar({ across: py, along: px, width: 0.13, extent: 0.85 }),\n bar({ across: px, along: py, width: 0.13, extent: 0.85 }),\n );\n case \"reticle\":\n return max(\n ring({ radius: 0.58, width: 0.1, distance }),\n max(\n bar({ across: py, along: px, width: 0.07, extent: 0.9 }),\n bar({ across: px, along: py, width: 0.07, extent: 0.9 }),\n ),\n );\n case \"gyroscope\":\n return max(\n max(ring({ radius: 0.85, width: 0.09, distance }), ring({ radius: 0.48, width: 0.09, distance })),\n max(\n bar({ across: py, along: px, width: 0.06, extent: 0.85 }),\n bar({ across: px, along: py, width: 0.06, extent: 0.85 }),\n ),\n );\n default:\n return float(1);\n }\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import { type GlyphCloud, type GlyphCloudOptions } from "../../render/backend/cloud.js";
2
+ /**
3
+ * Builds one glyph cloud: camera-facing quads for a flat glyph, or shaded
4
+ * solids for a solid one.
5
+ */
6
+ export declare function buildGlyphCloud(options: GlyphCloudOptions): GlyphCloud;
@@ -0,0 +1,146 @@
1
+ import { attribute, cameraProjectionMatrix, modelViewMatrix, positionGeometry, uv, vec4 } from "three/tsl";
2
+ import { BufferAttribute, BufferGeometry, DynamicDrawUsage, Mesh } from "three/webgpu";
3
+ import { assert } from "../../assert.js";
4
+ import { prepareGlyphReader, } from "../../render/backend/cloud.js";
5
+ import { glyphMask } from "../../render/backend/glyph.js";
6
+ import { material as materialFactory } from "../../render/backend/material.js";
7
+ import { buildSolidCloud, isSolid } from "../../render/backend/solids.js";
8
+ const VERTICES_PER_GLYPH = 6;
9
+ const FLOATS_PER_POSITION = 3;
10
+ const FLOATS_PER_OFFSET = 2;
11
+ const FLOATS_PER_COLOR = 4;
12
+ const FLOATS_PER_UV = 2;
13
+ const POSITION_ATTRIBUTE = "position";
14
+ const OFFSET_ATTRIBUTE = "aOffset";
15
+ const COLOR_ATTRIBUTE = "aColor";
16
+ const UV_ATTRIBUTE = "uv";
17
+ const GLYPH_CORNER_OFFSETS = [
18
+ [-1, -1],
19
+ [1, -1],
20
+ [1, 1],
21
+ [-1, -1],
22
+ [1, 1],
23
+ [-1, 1],
24
+ ];
25
+ function cornerAt(vertex) {
26
+ const corner = GLYPH_CORNER_OFFSETS[vertex];
27
+ assert(corner !== undefined, `missing glyph corner ${vertex}`);
28
+ return corner;
29
+ }
30
+ function writeGlyphTo(buffers, { at }, { halfSize, center, color }) {
31
+ for (let vertex = 0; vertex < VERTICES_PER_GLYPH; vertex++) {
32
+ const corner = cornerAt(vertex);
33
+ const slot = at * VERTICES_PER_GLYPH + vertex;
34
+ const p = slot * FLOATS_PER_POSITION;
35
+ buffers.positions[p] = center.x;
36
+ buffers.positions[p + 1] = center.y;
37
+ buffers.positions[p + 2] = center.z;
38
+ const o = slot * FLOATS_PER_OFFSET;
39
+ buffers.offsets[o] = corner[0] * halfSize;
40
+ buffers.offsets[o + 1] = corner[1] * halfSize;
41
+ const c = slot * FLOATS_PER_COLOR;
42
+ buffers.colors[c] = color.r;
43
+ buffers.colors[c + 1] = color.g;
44
+ buffers.colors[c + 2] = color.b;
45
+ buffers.colors[c + 3] = color.a;
46
+ }
47
+ }
48
+ function writeGlyphTextureCoordinatesTo(target, { at }) {
49
+ const base = at * VERTICES_PER_GLYPH * FLOATS_PER_UV;
50
+ for (let vertex = 0; vertex < VERTICES_PER_GLYPH; vertex++) {
51
+ const corner = cornerAt(vertex);
52
+ const offset = base + vertex * FLOATS_PER_UV;
53
+ target[offset] = (corner[0] + 1) / 2;
54
+ target[offset + 1] = (corner[1] + 1) / 2;
55
+ }
56
+ }
57
+ function fillGlyphTextureCoordinates(target, count) {
58
+ for (let slot = 0; slot < count; slot++) {
59
+ writeGlyphTextureCoordinatesTo(target, { at: slot });
60
+ }
61
+ }
62
+ function createGlyphMaterial(shape) {
63
+ const color = attribute(COLOR_ATTRIBUTE, "vec4");
64
+ const result = materialFactory.flat({
65
+ color: color.xyz,
66
+ opacity: color.w.mul(glyphMask(shape, uv().sub(0.5).mul(2))),
67
+ });
68
+ // The corner offset is added in view space, so the quad faces the camera
69
+ // from any direction.
70
+ const offset = attribute(OFFSET_ATTRIBUTE, "vec2");
71
+ result.vertexNode = cameraProjectionMatrix.mul(modelViewMatrix.mul(vec4(positionGeometry, 1)).add(vec4(offset, 0, 0)));
72
+ return result;
73
+ }
74
+ function dynamicAttribute(count, itemSize) {
75
+ const result = new BufferAttribute(new Float32Array(count * VERTICES_PER_GLYPH * itemSize), itemSize);
76
+ result.setUsage(DynamicDrawUsage);
77
+ return result;
78
+ }
79
+ function allocateGlyphAttributes(count) {
80
+ const textureCoordinates = new BufferAttribute(new Float32Array(count * VERTICES_PER_GLYPH * FLOATS_PER_UV), FLOATS_PER_UV);
81
+ fillGlyphTextureCoordinates(textureCoordinates.array, count);
82
+ return {
83
+ position: dynamicAttribute(count, FLOATS_PER_POSITION),
84
+ offsets: dynamicAttribute(count, FLOATS_PER_OFFSET),
85
+ colors: dynamicAttribute(count, FLOATS_PER_COLOR),
86
+ textureCoordinates,
87
+ };
88
+ }
89
+ function attachGlyphAttributes(geometry, attributes) {
90
+ geometry.setAttribute(POSITION_ATTRIBUTE, attributes.position);
91
+ geometry.setAttribute(OFFSET_ATTRIBUTE, attributes.offsets);
92
+ geometry.setAttribute(COLOR_ATTRIBUTE, attributes.colors);
93
+ geometry.setAttribute(UV_ATTRIBUTE, attributes.textureCoordinates);
94
+ }
95
+ function buffersOf(attributes) {
96
+ return {
97
+ positions: attributes.position.array,
98
+ offsets: attributes.offsets.array,
99
+ colors: attributes.colors.array,
100
+ };
101
+ }
102
+ function writeGlyphsTo(buffers, indices, read) {
103
+ for (let slot = 0; slot < indices.length; slot++) {
104
+ const particle = indices[slot];
105
+ assert(particle !== undefined, `glyph slot ${slot} has no source particle`);
106
+ writeGlyphTo(buffers, { at: slot }, read(particle));
107
+ }
108
+ }
109
+ /**
110
+ * Builds one glyph cloud: camera-facing quads for a flat glyph, or shaded
111
+ * solids for a solid one.
112
+ */
113
+ export function buildGlyphCloud(options) {
114
+ const { shape } = options;
115
+ return isSolid(shape) ? buildSolidCloud({ ...options, shape }) : buildFlatCloud({ ...options, shape });
116
+ }
117
+ function buildFlatCloud({ shape, style, sizeScale }) {
118
+ const geometry = new BufferGeometry();
119
+ const material = createGlyphMaterial(shape);
120
+ const mesh = new Mesh(geometry, material);
121
+ mesh.frustumCulled = false;
122
+ let count = -1;
123
+ let attributes = allocateGlyphAttributes(0);
124
+ function resize(next) {
125
+ count = next;
126
+ attributes = allocateGlyphAttributes(next);
127
+ attachGlyphAttributes(geometry, attributes);
128
+ }
129
+ return {
130
+ mesh,
131
+ update(frame, indices) {
132
+ if (indices.length !== count) {
133
+ resize(indices.length);
134
+ }
135
+ writeGlyphsTo(buffersOf(attributes), indices, prepareGlyphReader(style, sizeScale, frame));
136
+ attributes.position.needsUpdate = true;
137
+ attributes.offsets.needsUpdate = true;
138
+ attributes.colors.needsUpdate = true;
139
+ },
140
+ dispose() {
141
+ geometry.dispose();
142
+ material.dispose();
143
+ },
144
+ };
145
+ }
146
+ //# sourceMappingURL=glyphs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glyphs.js","sourceRoot":"","sources":["../../../src/render/backend/glyphs.ts"],"names":[],"mappings":"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","sourcesContent":["import { attribute, cameraProjectionMatrix, modelViewMatrix, positionGeometry, uv, vec4 } from \"three/tsl\";\nimport { BufferAttribute, BufferGeometry, DynamicDrawUsage, Mesh, type MeshBasicNodeMaterial } from \"three/webgpu\";\nimport { assert } from \"@/assert.js\";\nimport {\n type GlyphCloud,\n type GlyphCloudOptions,\n type GlyphInstance,\n type GlyphReader,\n prepareGlyphReader,\n} from \"@/render/backend/cloud.js\";\nimport { glyphMask } from \"@/render/backend/glyph.js\";\nimport { material as materialFactory } from \"@/render/backend/material.js\";\nimport { buildSolidCloud, isSolid } from \"@/render/backend/solids.js\";\nimport type { Frame } from \"@/render/frame.js\";\nimport type { FlatGlyph } from \"@/render/layer.js\";\n\nconst VERTICES_PER_GLYPH = 6;\nconst FLOATS_PER_POSITION = 3;\nconst FLOATS_PER_OFFSET = 2;\nconst FLOATS_PER_COLOR = 4;\nconst FLOATS_PER_UV = 2;\n\nconst POSITION_ATTRIBUTE = \"position\";\nconst OFFSET_ATTRIBUTE = \"aOffset\";\nconst COLOR_ATTRIBUTE = \"aColor\";\nconst UV_ATTRIBUTE = \"uv\";\n\nconst GLYPH_CORNER_OFFSETS: readonly [number, number][] = [\n [-1, -1],\n [1, -1],\n [1, 1],\n [-1, -1],\n [1, 1],\n [-1, 1],\n];\n\ninterface GlyphBuffers {\n positions: Float32Array;\n offsets: Float32Array;\n colors: Float32Array;\n}\n\ninterface GlyphAttributes {\n position: BufferAttribute;\n offsets: BufferAttribute;\n colors: BufferAttribute;\n textureCoordinates: BufferAttribute;\n}\n\nfunction cornerAt(vertex: number): [number, number] {\n const corner = GLYPH_CORNER_OFFSETS[vertex];\n assert(corner !== undefined, `missing glyph corner ${vertex}`);\n return corner;\n}\n\nfunction writeGlyphTo(buffers: GlyphBuffers, { at }: { at: number }, { halfSize, center, color }: GlyphInstance): void {\n for (let vertex = 0; vertex < VERTICES_PER_GLYPH; vertex++) {\n const corner = cornerAt(vertex);\n const slot = at * VERTICES_PER_GLYPH + vertex;\n\n const p = slot * FLOATS_PER_POSITION;\n buffers.positions[p] = center.x;\n buffers.positions[p + 1] = center.y;\n buffers.positions[p + 2] = center.z;\n\n const o = slot * FLOATS_PER_OFFSET;\n buffers.offsets[o] = corner[0] * halfSize;\n buffers.offsets[o + 1] = corner[1] * halfSize;\n\n const c = slot * FLOATS_PER_COLOR;\n buffers.colors[c] = color.r;\n buffers.colors[c + 1] = color.g;\n buffers.colors[c + 2] = color.b;\n buffers.colors[c + 3] = color.a;\n }\n}\n\nfunction writeGlyphTextureCoordinatesTo(target: Float32Array, { at }: { at: number }): void {\n const base = at * VERTICES_PER_GLYPH * FLOATS_PER_UV;\n\n for (let vertex = 0; vertex < VERTICES_PER_GLYPH; vertex++) {\n const corner = cornerAt(vertex);\n const offset = base + vertex * FLOATS_PER_UV;\n target[offset] = (corner[0] + 1) / 2;\n target[offset + 1] = (corner[1] + 1) / 2;\n }\n}\n\nfunction fillGlyphTextureCoordinates(target: Float32Array, count: number): void {\n for (let slot = 0; slot < count; slot++) {\n writeGlyphTextureCoordinatesTo(target, { at: slot });\n }\n}\n\nfunction createGlyphMaterial(shape: FlatGlyph): MeshBasicNodeMaterial {\n const color = attribute<\"vec4\">(COLOR_ATTRIBUTE, \"vec4\");\n const result = materialFactory.flat({\n color: color.xyz,\n opacity: color.w.mul(glyphMask(shape, uv().sub(0.5).mul(2))),\n });\n\n // The corner offset is added in view space, so the quad faces the camera\n // from any direction.\n const offset = attribute<\"vec2\">(OFFSET_ATTRIBUTE, \"vec2\");\n result.vertexNode = cameraProjectionMatrix.mul(\n modelViewMatrix.mul(vec4(positionGeometry, 1)).add(vec4(offset, 0, 0)),\n );\n return result;\n}\n\nfunction dynamicAttribute(count: number, itemSize: number): BufferAttribute {\n const result = new BufferAttribute(new Float32Array(count * VERTICES_PER_GLYPH * itemSize), itemSize);\n result.setUsage(DynamicDrawUsage);\n return result;\n}\n\nfunction allocateGlyphAttributes(count: number): GlyphAttributes {\n const textureCoordinates = new BufferAttribute(\n new Float32Array(count * VERTICES_PER_GLYPH * FLOATS_PER_UV),\n FLOATS_PER_UV,\n );\n fillGlyphTextureCoordinates(textureCoordinates.array as Float32Array, count);\n\n return {\n position: dynamicAttribute(count, FLOATS_PER_POSITION),\n offsets: dynamicAttribute(count, FLOATS_PER_OFFSET),\n colors: dynamicAttribute(count, FLOATS_PER_COLOR),\n textureCoordinates,\n };\n}\n\nfunction attachGlyphAttributes(geometry: BufferGeometry, attributes: GlyphAttributes): void {\n geometry.setAttribute(POSITION_ATTRIBUTE, attributes.position);\n geometry.setAttribute(OFFSET_ATTRIBUTE, attributes.offsets);\n geometry.setAttribute(COLOR_ATTRIBUTE, attributes.colors);\n geometry.setAttribute(UV_ATTRIBUTE, attributes.textureCoordinates);\n}\n\nfunction buffersOf(attributes: GlyphAttributes): GlyphBuffers {\n return {\n positions: attributes.position.array as Float32Array,\n offsets: attributes.offsets.array as Float32Array,\n colors: attributes.colors.array as Float32Array,\n };\n}\n\nfunction writeGlyphsTo(buffers: GlyphBuffers, indices: readonly number[], read: GlyphReader): void {\n for (let slot = 0; slot < indices.length; slot++) {\n const particle = indices[slot];\n assert(particle !== undefined, `glyph slot ${slot} has no source particle`);\n writeGlyphTo(buffers, { at: slot }, read(particle));\n }\n}\n\n/**\n * Builds one glyph cloud: camera-facing quads for a flat glyph, or shaded\n * solids for a solid one.\n */\nexport function buildGlyphCloud(options: GlyphCloudOptions): GlyphCloud {\n const { shape } = options;\n return isSolid(shape) ? buildSolidCloud({ ...options, shape }) : buildFlatCloud({ ...options, shape });\n}\n\nfunction buildFlatCloud({ shape, style, sizeScale }: GlyphCloudOptions<FlatGlyph>): GlyphCloud {\n const geometry = new BufferGeometry();\n const material = createGlyphMaterial(shape);\n const mesh = new Mesh(geometry, material);\n mesh.frustumCulled = false;\n\n let count = -1;\n let attributes = allocateGlyphAttributes(0);\n\n function resize(next: number): void {\n count = next;\n attributes = allocateGlyphAttributes(next);\n attachGlyphAttributes(geometry, attributes);\n }\n\n return {\n mesh,\n\n update(frame: Frame, indices: readonly number[]): void {\n if (indices.length !== count) {\n resize(indices.length);\n }\n\n writeGlyphsTo(buffersOf(attributes), indices, prepareGlyphReader(style, sizeScale, frame));\n\n attributes.position.needsUpdate = true;\n attributes.offsets.needsUpdate = true;\n attributes.colors.needsUpdate = true;\n },\n\n dispose(): void {\n geometry.dispose();\n material.dispose();\n },\n };\n}\n"]}
@@ -0,0 +1,12 @@
1
+ import type { RenderBackend, Stage, StageOptions } from "../../render/stage.js";
2
+ export declare namespace backend {
3
+ /**
4
+ * Selects the GPU rendering device.
5
+ */
6
+ function gpu(): RenderBackend;
7
+ }
8
+ /**
9
+ * Creates a GPU-backed stage on `canvas`. Waits for the device to initialize
10
+ * before returning.
11
+ */
12
+ export declare function createStage(options: StageOptions): Promise<Stage>;