@opsydyn/effect-three 0.3.0 → 0.6.4

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 (143) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +15 -1
  3. package/dist/{index-8cf8t6by.js → index-re3rrfwd.js} +2935 -104
  4. package/dist/index-re3rrfwd.js.map +56 -0
  5. package/dist/index-ttwy0znv.js +145 -0
  6. package/dist/index-ttwy0znv.js.map +12 -0
  7. package/dist/index.js +557 -147
  8. package/dist/index.js.map +8 -6
  9. package/dist/kernel/bodies.d.ts +20 -1
  10. package/dist/kernel/bodies.d.ts.map +1 -1
  11. package/dist/kernel/boxSelect.d.ts +22 -0
  12. package/dist/kernel/boxSelect.d.ts.map +1 -0
  13. package/dist/kernel/build.d.ts +165 -7
  14. package/dist/kernel/build.d.ts.map +1 -1
  15. package/dist/kernel/cameraShake.d.ts.map +1 -1
  16. package/dist/kernel/describeScene.d.ts +50 -0
  17. package/dist/kernel/describeScene.d.ts.map +1 -0
  18. package/dist/kernel/description.d.ts +223 -1
  19. package/dist/kernel/description.d.ts.map +1 -1
  20. package/dist/kernel/diff.d.ts +15 -0
  21. package/dist/kernel/diff.d.ts.map +1 -1
  22. package/dist/kernel/equality.d.ts +11 -1
  23. package/dist/kernel/equality.d.ts.map +1 -1
  24. package/dist/kernel/gizmo.d.ts +69 -0
  25. package/dist/kernel/gizmo.d.ts.map +1 -0
  26. package/dist/kernel/history.d.ts +61 -0
  27. package/dist/kernel/history.d.ts.map +1 -0
  28. package/dist/kernel/index.d.ts +19 -6
  29. package/dist/kernel/index.d.ts.map +1 -1
  30. package/dist/kernel/index.js +124 -3
  31. package/dist/kernel/index.js.map +1 -1
  32. package/dist/kernel/inspect.d.ts +85 -0
  33. package/dist/kernel/inspect.d.ts.map +1 -0
  34. package/dist/kernel/lineGeometry.d.ts +25 -0
  35. package/dist/kernel/lineGeometry.d.ts.map +1 -0
  36. package/dist/kernel/lockstep.d.ts +114 -0
  37. package/dist/kernel/lockstep.d.ts.map +1 -0
  38. package/dist/kernel/loop.d.ts.map +1 -1
  39. package/dist/kernel/materials.d.ts.map +1 -1
  40. package/dist/kernel/paintOrder.d.ts +53 -0
  41. package/dist/kernel/paintOrder.d.ts.map +1 -0
  42. package/dist/kernel/physics.d.ts +39 -0
  43. package/dist/kernel/physics.d.ts.map +1 -1
  44. package/dist/kernel/pivot.d.ts +54 -0
  45. package/dist/kernel/pivot.d.ts.map +1 -0
  46. package/dist/kernel/pointBuffers.d.ts +17 -0
  47. package/dist/kernel/pointBuffers.d.ts.map +1 -0
  48. package/dist/kernel/pointer.d.ts +31 -1
  49. package/dist/kernel/pointer.d.ts.map +1 -1
  50. package/dist/kernel/preview.d.ts +32 -0
  51. package/dist/kernel/preview.d.ts.map +1 -0
  52. package/dist/kernel/renderKit.d.ts +21 -1
  53. package/dist/kernel/renderKit.d.ts.map +1 -1
  54. package/dist/kernel/renderer.d.ts +6 -0
  55. package/dist/kernel/renderer.d.ts.map +1 -1
  56. package/dist/kernel/sceneChanges.d.ts +87 -0
  57. package/dist/kernel/sceneChanges.d.ts.map +1 -0
  58. package/dist/kernel/sceneGraph.d.ts +9 -1
  59. package/dist/kernel/sceneGraph.d.ts.map +1 -1
  60. package/dist/kernel/sceneIndex.d.ts +5 -0
  61. package/dist/kernel/sceneIndex.d.ts.map +1 -1
  62. package/dist/kernel/seeded.d.ts +9 -0
  63. package/dist/kernel/seeded.d.ts.map +1 -0
  64. package/dist/kernel/shots.d.ts +26 -0
  65. package/dist/kernel/shots.d.ts.map +1 -0
  66. package/dist/kernel/tape.d.ts +38 -0
  67. package/dist/kernel/tape.d.ts.map +1 -0
  68. package/dist/kernel/timeline.d.ts +60 -0
  69. package/dist/kernel/timeline.d.ts.map +1 -0
  70. package/dist/kernel/trail.d.ts +31 -0
  71. package/dist/kernel/trail.d.ts.map +1 -0
  72. package/dist/kernel/viewportGizmo.d.ts +25 -0
  73. package/dist/kernel/viewportGizmo.d.ts.map +1 -0
  74. package/dist/runtime/Inspector.d.ts +20 -0
  75. package/dist/runtime/Inspector.d.ts.map +1 -0
  76. package/dist/runtime/SceneFile.d.ts +462 -0
  77. package/dist/runtime/SceneFile.d.ts.map +1 -0
  78. package/dist/runtime/SceneSchema.d.ts +404 -0
  79. package/dist/runtime/SceneSchema.d.ts.map +1 -1
  80. package/dist/runtime/errors.d.ts +9 -0
  81. package/dist/runtime/errors.d.ts.map +1 -1
  82. package/dist/runtime/index.d.ts +3 -1
  83. package/dist/runtime/index.d.ts.map +1 -1
  84. package/dist/webgl/index.d.ts.map +1 -1
  85. package/dist/webgl/index.js +228 -3
  86. package/dist/webgl/index.js.map +6 -4
  87. package/dist/webgl/line.d.ts +4 -0
  88. package/dist/webgl/line.d.ts.map +1 -0
  89. package/dist/webgl/points.d.ts +4 -0
  90. package/dist/webgl/points.d.ts.map +1 -0
  91. package/dist/webgpu/index.d.ts.map +1 -1
  92. package/dist/webgpu/index.js +206 -26
  93. package/dist/webgpu/index.js.map +6 -4
  94. package/dist/webgpu/line.d.ts +4 -0
  95. package/dist/webgpu/line.d.ts.map +1 -0
  96. package/dist/webgpu/points.d.ts +4 -0
  97. package/dist/webgpu/points.d.ts.map +1 -0
  98. package/package.json +4 -2
  99. package/src/kernel/bodies.ts +299 -17
  100. package/src/kernel/boxSelect.ts +73 -0
  101. package/src/kernel/build.ts +374 -1
  102. package/src/kernel/cameraShake.ts +3 -13
  103. package/src/kernel/describeScene.ts +224 -0
  104. package/src/kernel/description.ts +243 -0
  105. package/src/kernel/diff.ts +39 -0
  106. package/src/kernel/equality.ts +123 -0
  107. package/src/kernel/gizmo.ts +478 -0
  108. package/src/kernel/history.ts +118 -0
  109. package/src/kernel/index.ts +110 -1
  110. package/src/kernel/inspect.ts +214 -0
  111. package/src/kernel/lineGeometry.ts +75 -0
  112. package/src/kernel/lockstep.ts +237 -0
  113. package/src/kernel/loop.ts +7 -0
  114. package/src/kernel/materials.ts +3 -0
  115. package/src/kernel/paintOrder.ts +274 -0
  116. package/src/kernel/physics.ts +86 -1
  117. package/src/kernel/pivot.ts +418 -0
  118. package/src/kernel/pointBuffers.ts +115 -0
  119. package/src/kernel/pointer.ts +98 -17
  120. package/src/kernel/preview.ts +467 -0
  121. package/src/kernel/renderKit.ts +23 -1
  122. package/src/kernel/renderer.ts +6 -0
  123. package/src/kernel/sceneChanges.ts +265 -0
  124. package/src/kernel/sceneGraph.ts +354 -11
  125. package/src/kernel/sceneIndex.ts +8 -0
  126. package/src/kernel/seeded.ts +18 -0
  127. package/src/kernel/shots.ts +167 -0
  128. package/src/kernel/tape.ts +124 -0
  129. package/src/kernel/timeline.ts +93 -0
  130. package/src/kernel/trail.ts +170 -0
  131. package/src/kernel/viewportGizmo.ts +194 -0
  132. package/src/runtime/Inspector.ts +43 -0
  133. package/src/runtime/SceneFile.ts +46 -0
  134. package/src/runtime/SceneSchema.ts +319 -0
  135. package/src/runtime/errors.ts +6 -0
  136. package/src/runtime/index.ts +3 -1
  137. package/src/webgl/index.ts +4 -0
  138. package/src/webgl/line.ts +66 -0
  139. package/src/webgl/points.ts +205 -0
  140. package/src/webgpu/index.ts +4 -0
  141. package/src/webgpu/line.ts +67 -0
  142. package/src/webgpu/points.ts +193 -0
  143. package/dist/index-8cf8t6by.js.map +0 -41
@@ -0,0 +1,205 @@
1
+ /**
2
+ * The WebGL kit's points: three's `Points` with the materials Drei draws them with
3
+ * (`bf6f4ad`): its `PointMaterial` (three's `PointsMaterial` with a round mask), and the
4
+ * shaders of its `Sparkles` and `Stars`, ported as they are. The buffers come from the
5
+ * kernel (`pointBuffers`), the same for every renderer.
6
+ *
7
+ * One change from Drei's `PointMaterial`: it applies tone mapping and the output colour
8
+ * space a second time after its mask, which brightens the dots; here the mask only sets
9
+ * the alpha, and fully masked corners are discarded so opaque dots are round too.
10
+ *
11
+ * @module
12
+ */
13
+ import {
14
+ AdditiveBlending,
15
+ BufferAttribute,
16
+ BufferGeometry,
17
+ Group,
18
+ type Material,
19
+ Points,
20
+ PointsMaterial,
21
+ ShaderMaterial,
22
+ } from 'three';
23
+
24
+ import type { PointLook, PointsNode } from '../kernel/description.ts';
25
+ import { type PointBuffers, pointBuffers } from '../kernel/pointBuffers.ts';
26
+ import type { HostedPoints } from '../kernel/renderKit.ts';
27
+
28
+ /** Drei's `PointMaterial` mask: 1 inside the circle, smoothed over a pixel at its edge. */
29
+ const roundMask = /* glsl */ `
30
+ vec2 cxy = 2.0 * gl_PointCoord - 1.0;
31
+ float r = dot(cxy, cxy);
32
+ float delta = fwidth(r);
33
+ float mask = 1.0 - smoothstep(1.0 - delta, 1.0 + delta, r);
34
+ gl_FragColor.a *= mask;
35
+ if (gl_FragColor.a < 0.01) discard;
36
+ `;
37
+
38
+ /** Dots: `PointsMaterial` with a size factor a point and, round, Drei's mask. */
39
+ const dotsMaterial = (look: Extract<PointLook, { readonly _tag: 'Dots' }>): Material => {
40
+ const material = new PointsMaterial({
41
+ color: 0xffffff,
42
+ vertexColors: true,
43
+ size: look.size,
44
+ sizeAttenuation: look.sizeAttenuation,
45
+ opacity: look.opacity,
46
+ transparent: look.transparent,
47
+ });
48
+ material.onBeforeCompile = (shader) => {
49
+ shader.vertexShader = shader.vertexShader
50
+ .replace('uniform float size;', 'uniform float size;\nattribute float pointSize;')
51
+ .replace('gl_PointSize = size;', 'gl_PointSize = size * pointSize;');
52
+ if (look.round) {
53
+ shader.fragmentShader = shader.fragmentShader.replace(
54
+ '#include <opaque_fragment>',
55
+ `#include <opaque_fragment>\n${roundMask}`,
56
+ );
57
+ }
58
+ };
59
+ material.customProgramCacheKey = () => `effect-three:dots:${look.round}`;
60
+ return material;
61
+ };
62
+
63
+ /** Drei's `SparklesImplMaterial`, its shaders as Drei writes them. */
64
+ const sparklesMaterial = (): ShaderMaterial =>
65
+ new ShaderMaterial({
66
+ uniforms: { time: { value: 0 }, pixelRatio: { value: 1 } },
67
+ vertexShader: /* glsl */ `
68
+ uniform float pixelRatio;
69
+ uniform float time;
70
+ attribute float size;
71
+ attribute float speed;
72
+ attribute float opacity;
73
+ attribute vec3 noise;
74
+ attribute vec3 color;
75
+ varying vec3 vColor;
76
+ varying float vOpacity;
77
+ void main() {
78
+ vec4 modelPosition = modelMatrix * vec4(position, 1.0);
79
+ modelPosition.y += sin(time * speed + modelPosition.x * noise.x * 100.0) * 0.2;
80
+ modelPosition.z += cos(time * speed + modelPosition.x * noise.y * 100.0) * 0.2;
81
+ modelPosition.x += cos(time * speed + modelPosition.x * noise.z * 100.0) * 0.2;
82
+ vec4 viewPosition = viewMatrix * modelPosition;
83
+ vec4 projectionPostion = projectionMatrix * viewPosition;
84
+ gl_Position = projectionPostion;
85
+ gl_PointSize = size * 25. * pixelRatio;
86
+ gl_PointSize *= (1.0 / - viewPosition.z);
87
+ vColor = color;
88
+ vOpacity = opacity;
89
+ }`,
90
+ fragmentShader: /* glsl */ `
91
+ varying vec3 vColor;
92
+ varying float vOpacity;
93
+ void main() {
94
+ float distanceToCenter = distance(gl_PointCoord, vec2(0.5));
95
+ float strength = 0.05 / distanceToCenter - 0.1;
96
+ gl_FragColor = vec4(vColor, strength * vOpacity);
97
+ #include <tonemapping_fragment>
98
+ #include <colorspace_fragment>
99
+ }`,
100
+ transparent: true,
101
+ depthWrite: false,
102
+ });
103
+
104
+ /** Drei's `StarfieldMaterial`, its shaders as Drei writes them. */
105
+ const starsMaterial = (fade: boolean): ShaderMaterial =>
106
+ new ShaderMaterial({
107
+ uniforms: { time: { value: 0 }, fade: { value: fade ? 1 : 0 } },
108
+ vertexShader: /* glsl */ `
109
+ uniform float time;
110
+ attribute float size;
111
+ varying vec3 vColor;
112
+ void main() {
113
+ vColor = color;
114
+ vec4 mvPosition = modelViewMatrix * vec4(position, 0.5);
115
+ gl_PointSize = size * (30.0 / -mvPosition.z) * (3.0 + sin(time + 100.0));
116
+ gl_Position = projectionMatrix * mvPosition;
117
+ }`,
118
+ fragmentShader: /* glsl */ `
119
+ uniform float fade;
120
+ varying vec3 vColor;
121
+ void main() {
122
+ float opacity = 1.0;
123
+ if (fade == 1.0) {
124
+ float d = distance(gl_PointCoord, vec2(0.5, 0.5));
125
+ opacity = 1.0 / (1.0 + exp(16.0 * (d - 0.25)));
126
+ }
127
+ gl_FragColor = vec4(vColor, opacity);
128
+ #include <tonemapping_fragment>
129
+ #include <colorspace_fragment>
130
+ }`,
131
+ blending: AdditiveBlending,
132
+ depthWrite: false,
133
+ transparent: true,
134
+ vertexColors: true,
135
+ });
136
+
137
+ /** The geometry from the kernel's buffers, with the attribute names each shader reads. */
138
+ const pointsGeometry = (buffers: PointBuffers, look: PointLook): BufferGeometry => {
139
+ const geometry = new BufferGeometry();
140
+ geometry.setAttribute('position', new BufferAttribute(buffers.positions, 3));
141
+ geometry.setAttribute('color', new BufferAttribute(buffers.colors, 3));
142
+ geometry.setAttribute(
143
+ look._tag === 'Dots' ? 'pointSize' : 'size',
144
+ new BufferAttribute(buffers.sizes, 1),
145
+ );
146
+ if (look._tag === 'Sparkles') {
147
+ geometry.setAttribute('speed', new BufferAttribute(buffers.speeds, 1));
148
+ geometry.setAttribute('opacity', new BufferAttribute(buffers.opacities, 1));
149
+ geometry.setAttribute('noise', new BufferAttribute(buffers.noise, 3));
150
+ }
151
+ return geometry;
152
+ };
153
+
154
+ const materialFor = (look: PointLook): Material => {
155
+ switch (look._tag) {
156
+ case 'Dots':
157
+ return dotsMaterial(look);
158
+ case 'Sparkles':
159
+ return sparklesMaterial();
160
+ case 'Stars':
161
+ return starsMaterial(look.fade);
162
+ }
163
+ };
164
+
165
+ const draw = (look: PointLook): Points<BufferGeometry, Material> => {
166
+ const buffers = pointBuffers(look);
167
+ const drawn = new Points(pointsGeometry(buffers, look), materialFor(look));
168
+ drawn.visible = buffers.count > 0;
169
+ // Sparkles size by the device's pixel ratio, as Drei's take the viewport's `dpr`.
170
+ drawn.onBeforeRender = (renderer) => {
171
+ const material = drawn.material;
172
+ if (material instanceof ShaderMaterial && material.uniforms['pixelRatio'] !== undefined) {
173
+ material.uniforms['pixelRatio'].value = renderer.getPixelRatio();
174
+ }
175
+ };
176
+ return drawn;
177
+ };
178
+
179
+ export const hostPoints = (first: PointsNode): HostedPoints => {
180
+ const object = new Group();
181
+ let look = first.look;
182
+ let drawn = draw(look);
183
+ object.add(drawn);
184
+ const free = (): void => {
185
+ drawn.geometry.dispose();
186
+ drawn.material.dispose();
187
+ };
188
+ return {
189
+ object,
190
+ update: (next) => {
191
+ look = next.look;
192
+ drawn.removeFromParent();
193
+ free();
194
+ drawn = draw(look);
195
+ object.add(drawn);
196
+ },
197
+ tick: (seconds) => {
198
+ const time =
199
+ drawn.material instanceof ShaderMaterial ? drawn.material.uniforms['time'] : undefined;
200
+ // Drei's stars twinkle at `speed`; its sparkles carry their speed a mote.
201
+ if (time !== undefined) time.value = look._tag === 'Stars' ? seconds * look.speed : seconds;
202
+ },
203
+ dispose: free,
204
+ };
205
+ };
@@ -7,6 +7,8 @@
7
7
  */
8
8
  import type { RenderKit } from '../kernel/renderKit.ts';
9
9
  import { createContactShadows } from './contactShadows.ts';
10
+ import { hostLine } from './line.ts';
11
+ import { hostPoints } from './points.ts';
10
12
  import { hostShaderGrid } from './shaderGrid.ts';
11
13
  import { hostSky } from './sky.ts';
12
14
 
@@ -15,5 +17,7 @@ export const webgpuKit: RenderKit = {
15
17
  shading: 'tsl',
16
18
  sky: hostSky,
17
19
  shaderGrid: hostShaderGrid,
20
+ line: hostLine,
21
+ points: hostPoints,
18
22
  contactShadows: createContactShadows,
19
23
  };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The WebGPU kit's lines: three's fat lines for WebGPU (`Line2`, `LineSegments2` and `Line2NodeMaterial`,
3
+ * the node-material twins of what Drei's `Line` uses), which size themselves to the viewport
4
+ * before each draw.
5
+ *
6
+ * @module
7
+ */
8
+ import { Group } from 'three';
9
+ import { LineGeometry } from 'three/addons/lines/LineGeometry.js';
10
+ import { LineSegmentsGeometry } from 'three/addons/lines/LineSegmentsGeometry.js';
11
+ import { Line2 } from 'three/addons/lines/webgpu/Line2.js';
12
+ import { LineSegments2 } from 'three/addons/lines/webgpu/LineSegments2.js';
13
+ import { Line2NodeMaterial } from 'three/webgpu';
14
+
15
+ import type { LineNode } from '../kernel/description.ts';
16
+ import { drawable, fillLineGeometry } from '../kernel/lineGeometry.ts';
17
+ import type { HostedLine } from '../kernel/renderKit.ts';
18
+
19
+ /** The material's look from the description; vertex colours want white, as Drei sets. */
20
+ const style = (material: Line2NodeMaterial, line: LineNode): void => {
21
+ material.color.setHex(line.vertexColors === null ? line.color : 0xffffff);
22
+ if (material.vertexColors !== (line.vertexColors !== null)) {
23
+ material.vertexColors = line.vertexColors !== null;
24
+ material.needsUpdate = true;
25
+ }
26
+ material.linewidth = line.width;
27
+ material.worldUnits = line.worldUnits;
28
+ material.dashed = line.dashed;
29
+ material.dashSize = line.dashSize;
30
+ material.gapSize = line.gapSize;
31
+ // The node material reads the dash scale as `scale`, as `LineDashedMaterial` names it.
32
+ material.scale = line.dashScale;
33
+ material.dashOffset = line.dashOffset;
34
+ material.opacity = line.opacity;
35
+ material.transparent = line.transparent;
36
+ };
37
+
38
+ const draw = (line: LineNode, material: Line2NodeMaterial): LineSegments2 => {
39
+ const drawn = line.segments
40
+ ? new LineSegments2(fillLineGeometry(new LineSegmentsGeometry(), line), material)
41
+ : new Line2(fillLineGeometry(new LineGeometry(), line), material);
42
+ drawn.computeLineDistances();
43
+ drawn.visible = drawable(line);
44
+ return drawn;
45
+ };
46
+
47
+ export const hostLine = (first: LineNode): HostedLine => {
48
+ const object = new Group();
49
+ const material = new Line2NodeMaterial();
50
+ style(material, first);
51
+ let drawn = draw(first, material);
52
+ object.add(drawn);
53
+ return {
54
+ object,
55
+ update: (next) => {
56
+ style(material, next);
57
+ drawn.removeFromParent();
58
+ drawn.geometry.dispose();
59
+ drawn = draw(next, material);
60
+ object.add(drawn);
61
+ },
62
+ dispose: () => {
63
+ drawn.geometry.dispose();
64
+ material.dispose();
65
+ },
66
+ };
67
+ };
@@ -0,0 +1,193 @@
1
+ /**
2
+ * The WebGPU kit's points. WebGPU draws point primitives one pixel across, so sized
3
+ * points are instanced sprites with three's `PointsNodeMaterial`, as three's WebGPU
4
+ * examples draw them: a quad a point, `sizeNode` pixels across. Dots, sparkles and stars
5
+ * port the WebGL kit's shaders (Drei's, `bf6f4ad`) to TSL, from the same kernel buffers:
6
+ * the sprite's `uv` stands in for `gl_PointCoord`.
7
+ *
8
+ * Sparkles drift in the node's own space here (Drei moves them in world space), which
9
+ * is the same unless the node is rotated or scaled.
10
+ *
11
+ * @module
12
+ */
13
+ import { AdditiveBlending, Group, InstancedBufferAttribute } from 'three';
14
+ import {
15
+ cos,
16
+ distance,
17
+ exp,
18
+ float,
19
+ fwidth,
20
+ instancedBufferAttribute,
21
+ modelWorldMatrix,
22
+ positionView,
23
+ sin,
24
+ smoothstep,
25
+ uniform,
26
+ uv,
27
+ vec2,
28
+ vec3,
29
+ vec4,
30
+ } from 'three/tsl';
31
+ import { PointsNodeMaterial, Sprite } from 'three/webgpu';
32
+
33
+ import type { PointLook, PointsNode } from '../kernel/description.ts';
34
+ import { type PointBuffers, pointBuffers } from '../kernel/pointBuffers.ts';
35
+ import type { HostedPoints } from '../kernel/renderKit.ts';
36
+
37
+ /** Scene time for sparkles and stars, a uniform the host sets each frame. */
38
+ const createTime = () => uniform(0);
39
+ type Time = ReturnType<typeof createTime>;
40
+
41
+ const vec3Attribute = (array: Float32Array) =>
42
+ instancedBufferAttribute<'vec3'>(new InstancedBufferAttribute(array, 3), 'vec3');
43
+ const floatAttribute = (array: Float32Array) =>
44
+ instancedBufferAttribute<'float'>(new InstancedBufferAttribute(array, 1), 'float');
45
+
46
+ type Attributes = {
47
+ readonly position: ReturnType<typeof vec3Attribute>;
48
+ readonly color: ReturnType<typeof vec3Attribute>;
49
+ readonly size: ReturnType<typeof floatAttribute>;
50
+ };
51
+
52
+ const attributes = (buffers: PointBuffers): Attributes => ({
53
+ position: vec3Attribute(buffers.positions),
54
+ color: vec3Attribute(buffers.colors),
55
+ size: floatAttribute(buffers.sizes),
56
+ });
57
+
58
+ /** Where the sprite's fragment is across its point: `gl_PointCoord`. */
59
+ const pointCoord = uv();
60
+
61
+ /** Dots: Drei's `PointMaterial`, round by its mask, a size factor a point. */
62
+ const dotsMaterial = (
63
+ look: Extract<PointLook, { readonly _tag: 'Dots' }>,
64
+ given: Attributes,
65
+ ): PointsNodeMaterial => {
66
+ const material = new PointsNodeMaterial({
67
+ transparent: look.transparent,
68
+ sizeAttenuation: look.sizeAttenuation,
69
+ });
70
+ material.positionNode = given.position;
71
+ material.colorNode = vec4(given.color, 1);
72
+ material.sizeNode = given.size.mul(look.size);
73
+ const cxy = pointCoord.mul(2).sub(1);
74
+ const r = cxy.dot(cxy);
75
+ const delta = fwidth(r);
76
+ const mask = float(1).sub(smoothstep(float(1).sub(delta), delta.add(1), r));
77
+ material.opacityNode = look.round ? mask.mul(look.opacity) : float(look.opacity);
78
+ // Corners left out entirely, opaque or not.
79
+ material.alphaTestNode = float(0.01);
80
+ return material;
81
+ };
82
+
83
+ /** Drei's sparkles: motes swaying about their places, glowing from their middles. */
84
+ const sparklesMaterial = (
85
+ buffers: PointBuffers,
86
+ given: Attributes,
87
+ time: Time,
88
+ ): PointsNodeMaterial => {
89
+ const speed = floatAttribute(buffers.speeds);
90
+ const opacity = floatAttribute(buffers.opacities);
91
+ const noise = vec3Attribute(buffers.noise);
92
+ const material = new PointsNodeMaterial({
93
+ transparent: true,
94
+ depthWrite: false,
95
+ sizeAttenuation: false,
96
+ });
97
+ const worldX = modelWorldMatrix.mul(vec4(given.position, 1)).x;
98
+ const phase = time.mul(speed);
99
+ material.positionNode = given.position.add(
100
+ vec3(
101
+ cos(phase.add(worldX.mul(noise.z).mul(100))).mul(0.2),
102
+ sin(phase.add(worldX.mul(noise.x).mul(100))).mul(0.2),
103
+ cos(phase.add(worldX.mul(noise.y).mul(100))).mul(0.2),
104
+ ),
105
+ );
106
+ material.colorNode = vec4(given.color, 1);
107
+ // Drei's `size * 25 * pixelRatio / -viewZ`; the material multiplies in the pixel ratio.
108
+ material.sizeNode = given.size.mul(25).div(positionView.z.negate());
109
+ const strength = float(0.05)
110
+ .div(distance(pointCoord, vec2(0.5, 0.5)))
111
+ .sub(0.1);
112
+ material.opacityNode = strength.mul(opacity).clamp(0, 1);
113
+ return material;
114
+ };
115
+
116
+ /**
117
+ * Drei's stars. Its shader places them at `vec4(position, 0.5)`: the same direction as
118
+ * twice the position at full weight, half as far in clip space, so here they are drawn at
119
+ * twice their positions, and sized by half the view depth.
120
+ */
121
+ const starsMaterial = (
122
+ look: Extract<PointLook, { readonly _tag: 'Stars' }>,
123
+ given: Attributes,
124
+ time: Time,
125
+ ): PointsNodeMaterial => {
126
+ const material = new PointsNodeMaterial({
127
+ transparent: true,
128
+ depthWrite: false,
129
+ blending: AdditiveBlending,
130
+ sizeAttenuation: false,
131
+ });
132
+ material.positionNode = given.position.mul(2);
133
+ material.colorNode = vec4(given.color, 1);
134
+ material.sizeNode = given.size
135
+ .mul(float(30).div(positionView.z.negate().mul(0.5)))
136
+ .mul(sin(time.add(100)).add(3));
137
+ const d = distance(pointCoord, vec2(0.5, 0.5));
138
+ const soft = float(1).div(exp(d.sub(0.25).mul(16)).add(1));
139
+ material.opacityNode = look.fade ? soft : float(1);
140
+ return material;
141
+ };
142
+
143
+ const materialFor = (
144
+ look: PointLook,
145
+ buffers: PointBuffers,
146
+ given: Attributes,
147
+ time: Time,
148
+ ): PointsNodeMaterial => {
149
+ switch (look._tag) {
150
+ case 'Dots':
151
+ return dotsMaterial(look, given);
152
+ case 'Sparkles':
153
+ return sparklesMaterial(buffers, given, time);
154
+ case 'Stars':
155
+ return starsMaterial(look, given, time);
156
+ }
157
+ };
158
+
159
+ const draw = (look: PointLook, time: Time): Sprite => {
160
+ const buffers = pointBuffers(look);
161
+ const material = materialFor(look, buffers, attributes(buffers), time);
162
+ const sprite = new Sprite(material);
163
+ sprite.count = buffers.count;
164
+ sprite.visible = buffers.count > 0;
165
+ sprite.frustumCulled = false;
166
+ return sprite;
167
+ };
168
+
169
+ export const hostPoints = (first: PointsNode): HostedPoints => {
170
+ const object = new Group();
171
+ const time = createTime();
172
+ let look = first.look;
173
+ let drawn = draw(look, time);
174
+ object.add(drawn);
175
+ // Sprites share three's one quad geometry: only the material is this node's.
176
+ const free = (): void => {
177
+ drawn.material.dispose();
178
+ };
179
+ return {
180
+ object,
181
+ update: (next) => {
182
+ look = next.look;
183
+ drawn.removeFromParent();
184
+ free();
185
+ drawn = draw(look, time);
186
+ object.add(drawn);
187
+ },
188
+ tick: (seconds) => {
189
+ time.value = look._tag === 'Stars' ? seconds * look.speed : seconds;
190
+ },
191
+ dispose: free,
192
+ };
193
+ };