nodality 1.0.181 → 1.0.182
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.
- package/dist/animator.cjs.js +1 -1
- package/dist/animator.esm.js +1 -1
- package/dist/audionew.cjs.js +1 -1
- package/dist/audionew.esm.js +1 -1
- package/dist/base.cjs.js +1 -1
- package/dist/base.esm.js +1 -1
- package/dist/beta-desktop-bar.cjs.js +1 -1
- package/dist/beta-desktop-bar.esm.js +1 -1
- package/dist/beta-mobile-bar.cjs.js +1 -1
- package/dist/beta-mobile-bar.esm.js +1 -1
- package/dist/bundle.umd.js +1 -1
- package/dist/button.cjs.js +1 -1
- package/dist/button.esm.js +1 -1
- package/dist/center.cjs.js +1 -1
- package/dist/center.esm.js +1 -1
- package/dist/checkbox.cjs.js +1 -1
- package/dist/checkbox.esm.js +1 -1
- package/dist/code.cjs.js +1 -1
- package/dist/code.esm.js +1 -1
- package/dist/container.cjs.js +1 -1
- package/dist/container.esm.js +1 -1
- package/dist/data-list.cjs.js +1 -1
- package/dist/data-list.esm.js +1 -1
- package/dist/designer.cjs.js +1 -1
- package/dist/designer.esm.js +1 -1
- package/dist/dropdown.cjs.js +1 -1
- package/dist/dropdown.esm.js +1 -1
- package/dist/element-mapper.cjs.js +1 -1
- package/dist/element-mapper.esm.js +1 -1
- package/dist/finalresult.esm.js +1 -1
- package/dist/flex-card.cjs.js +1 -1
- package/dist/flex-card.esm.js +1 -1
- package/dist/flex-grid.cjs.js +1 -1
- package/dist/flex-grid.esm.js +1 -1
- package/dist/flex-row.cjs.js +1 -1
- package/dist/flex-row.esm.js +1 -1
- package/dist/floating-input.cjs.js +1 -1
- package/dist/floating-input.esm.js +1 -1
- package/dist/free.cjs.js +1 -1
- package/dist/free.esm.js +1 -1
- package/dist/horizontal-scroller.cjs.js +1 -1
- package/dist/horizontal-scroller.esm.js +1 -1
- package/dist/image-picker.cjs.js +1 -1
- package/dist/image-picker.esm.js +1 -1
- package/dist/image.cjs.js +1 -1
- package/dist/image.esm.js +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/link-getter.cjs.js +1 -1
- package/dist/link-getter.esm.js +1 -1
- package/dist/link.cjs.js +1 -1
- package/dist/link.esm.js +1 -1
- package/dist/meta-adder.cjs.js +1 -1
- package/dist/meta-adder.esm.js +1 -1
- package/dist/modal-2025.cjs.js +1 -1
- package/dist/modal-2025.esm.js +1 -1
- package/dist/multiswitcher.cjs.js +1 -1
- package/dist/multiswitcher.esm.js +1 -1
- package/dist/new-nav-bar.cjs.js +1 -1
- package/dist/new-nav-bar.esm.js +1 -1
- package/dist/picker.cjs.js +1 -1
- package/dist/picker.esm.js +1 -1
- package/dist/progress.cjs.js +1 -1
- package/dist/progress.esm.js +1 -1
- package/dist/radio.cjs.js +1 -1
- package/dist/radio.esm.js +1 -1
- package/dist/range.cjs.js +1 -1
- package/dist/range.esm.js +1 -1
- package/dist/side-bar.cjs.js +1 -1
- package/dist/side-bar.esm.js +1 -1
- package/dist/side-nav-bar.cjs.js +1 -1
- package/dist/side-nav-bar.esm.js +1 -1
- package/dist/simple-bar.cjs.js +1 -1
- package/dist/simple-bar.esm.js +1 -1
- package/dist/slider-2025.cjs.js +1 -1
- package/dist/slider-2025.esm.js +1 -1
- package/dist/spacer.cjs.js +1 -1
- package/dist/spacer.esm.js +1 -1
- package/dist/stack.cjs.js +1 -1
- package/dist/stack.esm.js +1 -1
- package/dist/table.cjs.js +1 -1
- package/dist/table.esm.js +1 -1
- package/dist/text-field.cjs.js +1 -1
- package/dist/text-field.esm.js +1 -1
- package/dist/text.cjs.js +1 -1
- package/dist/text.esm.js +1 -1
- package/dist/ulist.cjs.js +1 -1
- package/dist/ulist.esm.js +1 -1
- package/dist/video.cjs.js +1 -1
- package/dist/video.esm.js +1 -1
- package/dist/wrap.cjs.js +1 -1
- package/dist/wrap.esm.js +1 -1
- package/dist/zoom-card.cjs.js +1 -1
- package/dist/zoom-card.esm.js +1 -1
- package/layout/animator.js +1 -1
- package/layout/audio.js +1 -1
- package/layout/audionew.js +1 -1
- package/layout/base-2.js +1 -1
- package/layout/base.js +1 -1
- package/layout/beta-desktop-bar.js +1 -1
- package/layout/beta-mobile-bar.js +1 -1
- package/layout/box.js +1 -1
- package/layout/button.js +1 -1
- package/layout/cards.js +1 -1
- package/layout/center.js +1 -1
- package/layout/checkbox.js +1 -1
- package/layout/circle.js +1 -1
- package/layout/clean-row.js +1 -1
- package/layout/code.js +1 -1
- package/layout/container.js +1 -1
- package/layout/custom.js +1 -1
- package/layout/div-image.js +1 -1
- package/layout/dropdown-2025.js +1 -1
- package/layout/dropdown.js +1 -1
- package/layout/empty-element.js +1 -1
- package/layout/external-stylesheet.js +1 -1
- package/layout/flex-card.js +1 -1
- package/layout/flex-grid.js +1 -1
- package/layout/flex-row.js +1 -1
- package/layout/footer.js +1 -1
- package/layout/form-components/custom.js +1 -1
- package/layout/form-components/data-list.js +1 -1
- package/layout/form-components/floating-input.js +1 -1
- package/layout/form-components/form-all.js +1 -1
- package/layout/form-components/form.js +1 -1
- package/layout/form-components/image-picker.js +1 -1
- package/layout/form-components/picker.js +1 -1
- package/layout/form-components/radio.js +1 -1
- package/layout/form-components/radiogroup.js +1 -1
- package/layout/form-components/range.js +1 -1
- package/layout/free.js +1 -1
- package/layout/grid-new.js +1 -1
- package/layout/grid-switcher.js +1 -1
- package/layout/grid.js +1 -1
- package/layout/group.js +1 -1
- package/layout/header.js +1 -1
- package/layout/horizontal-scroller.js +1 -1
- package/layout/image-old.js +1 -1
- package/layout/image.js +1 -1
- package/layout/index.js +1 -1
- package/layout/label.js +1 -1
- package/layout/link.js +1 -1
- package/layout/list-OLD.js +1 -1
- package/layout/list.js +1 -1
- package/layout/meta-adder.js +1 -1
- package/layout/modal-2025.js +1 -1
- package/layout/modernwrap.js +1 -1
- package/layout/multiswitcher.js +1 -1
- package/layout/multiswitcherBeta.js +1 -1
- package/layout/nav-bar.js +1 -1
- package/layout/nav-factor/custom-div.js +1 -1
- package/layout/navBar-OLD.js +1 -1
- package/layout/new-flat-adder.js +1 -1
- package/layout/new-nav-bar.js +1 -1
- package/layout/offset-container.js +1 -1
- package/layout/polygon.js +1 -1
- package/layout/prerender-site.js +1 -1
- package/layout/prerender.js +1 -1
- package/layout/progress.js +1 -1
- package/layout/row.js +1 -1
- package/layout/saved-new-nav-bar.js +1 -1
- package/layout/scroll-video.js +1 -1
- package/layout/side-bar.js +1 -1
- package/layout/side-nav-bar.js +1 -1
- package/layout/simple-bar.js +1 -1
- package/layout/slider-2025.js +1 -1
- package/layout/spacer.js +1 -1
- package/layout/stack.js +1 -1
- package/layout/styler.js +1 -1
- package/layout/svg.js +1 -1
- package/layout/switcher.js +1 -1
- package/layout/table.js +1 -1
- package/layout/text-field.js +1 -1
- package/layout/text.js +1 -1
- package/layout/ulist.js +1 -1
- package/layout/video.js +1 -1
- package/layout/without-new.js +1 -1
- package/layout/wrap.js +1 -1
- package/layout/zoom-card.js +1 -1
- package/lib/card-getter.js +1 -1
- package/lib/data.js +1 -1
- package/lib/designer.js +1 -1
- package/lib/element-mapper.js +1 -1
- package/lib/keyframe-animation.js +1 -1
- package/lib/link-getter.js +1 -1
- package/lib/raster-ops.js +484 -221
- package/lib/scroll-video.js +1 -1
- package/lib/seo.js +1 -1
- package/lib/stacker.js +1 -1
- package/lib/theme.js +1 -1
- package/lib/transform-anim.js +1 -1
- package/package.json +1 -1
package/lib/raster-ops.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* nodality v1.0.
|
|
2
|
+
* nodality v1.0.182
|
|
3
3
|
* (c) 2026 Filip Vabrousek
|
|
4
4
|
* License: MIT
|
|
5
5
|
*/
|
|
@@ -146,126 +146,348 @@ const REGISTRY = {
|
|
|
146
146
|
// projection at all — the whole simulation is a single pass.
|
|
147
147
|
//
|
|
148
148
|
// { op: "stir", strength: 26, swirl: 2.4, decay: 0.985 }
|
|
149
|
+
// Stirred liquid: an incompressible fluid whose velocity field warps
|
|
150
|
+
// the content and whose dye field colours it.
|
|
151
|
+
//
|
|
152
|
+
// Implemented from the published algorithm, not from any particular
|
|
153
|
+
// existing codebase:
|
|
154
|
+
// J. Stam, "Stable Fluids", SIGGRAPH '99, 121-128 - the
|
|
155
|
+
// unconditionally stable operator splitting used here: add
|
|
156
|
+
// forces, advect by a backward particle trace, then project the
|
|
157
|
+
// velocity onto its divergence-free part via a Poisson solve.
|
|
158
|
+
// M. Harris, "Fast Fluid Dynamics Simulation on the GPU", GPU Gems
|
|
159
|
+
// ch. 38 (2003) - mapping each operator onto a fragment program
|
|
160
|
+
// over ping-ponged textures, Jacobi iterations for the pressure.
|
|
161
|
+
// R. Fedkiw, J. Stam, H. Jensen, "Visual Simulation of Smoke",
|
|
162
|
+
// SIGGRAPH '01 - vorticity confinement, which restores the
|
|
163
|
+
// small-scale swirl that a coarse grid numerically damps away.
|
|
164
|
+
//
|
|
165
|
+
// Per frame: splat -> curl -> confine -> divergence -> pressure
|
|
166
|
+
// (Jacobi) -> subtract gradient -> advect velocity -> advect dye.
|
|
167
|
+
// The projection step is what distinguishes this from a plain
|
|
168
|
+
// velocity-texture smear: enforcing div(v)=0 is what makes the flow
|
|
169
|
+
// roll into persistent vortices instead of piling up and dissipating.
|
|
170
|
+
//
|
|
171
|
+
// Needs WebGL2 with float render targets; without them the op
|
|
172
|
+
// disables itself and the rest of the chain still renders.
|
|
149
173
|
stir: {
|
|
150
|
-
// Warp: swirl the coordinate space (so a cell op downstream tiles
|
|
151
|
-
// the ALREADY-stirred content). Color: a faint moving sheen.
|
|
152
174
|
stage: ["warp", "color"],
|
|
153
175
|
decl: (p) => `
|
|
154
|
-
uniform sampler2D ${p}
|
|
176
|
+
uniform sampler2D ${p}vel;
|
|
177
|
+
uniform sampler2D ${p}dye;
|
|
155
178
|
uniform float ${p}strength;
|
|
179
|
+
uniform float ${p}dyemix;
|
|
180
|
+
uniform float ${p}intensity;
|
|
156
181
|
uniform float ${p}sheen;
|
|
157
|
-
uniform vec3 ${p}tint
|
|
158
|
-
uniform float ${p}enc;
|
|
159
|
-
uniform float ${p}range;
|
|
160
|
-
vec2 ${p}flow(vec2 uvp) {
|
|
161
|
-
vec4 t = texture2D(${p}field, clamp(uvp, 0.001, 0.999));
|
|
162
|
-
return ${p}enc > 0.5 ? (t.xy * 2.0 - 1.0) * ${p}range : t.xy;
|
|
163
|
-
}`,
|
|
182
|
+
uniform vec3 ${p}tint;`,
|
|
164
183
|
code: (p, node, stage) => stage === "warp" ? `
|
|
165
184
|
{
|
|
166
|
-
// frag/warped are top-down pixels
|
|
167
|
-
// [0,1]
|
|
168
|
-
//
|
|
185
|
+
// frag/warped are top-down pixels, the field is bottom-up
|
|
186
|
+
// [0,1]: flip y going in, and flip the y of the vector coming
|
|
187
|
+
// out so the content drags WITH the pointer.
|
|
169
188
|
vec2 fuv = vec2(warped.x / u_res.x, 1.0 - warped.y / u_res.y);
|
|
170
|
-
vec2
|
|
171
|
-
warped += vec2(
|
|
189
|
+
vec2 v = texture2D(${p}vel, clamp(fuv, 0.001, 0.999)).xy;
|
|
190
|
+
warped += vec2(v.x, -v.y) * ${p}strength;
|
|
172
191
|
}` : `
|
|
173
192
|
{
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
193
|
+
// Sampled at the CONTINUOUS warped position, never at the
|
|
194
|
+
// cell centre: a cell op upstream (hexalize) snaps the cell
|
|
195
|
+
// centre to one point per tile, which would quantise the
|
|
196
|
+
// liquid into flat per-hex patches instead of smooth swirls.
|
|
197
|
+
vec2 suv = vec2(warped.x / u_res.x, 1.0 - warped.y / u_res.y);
|
|
198
|
+
vec3 dc = texture2D(${p}dye, clamp(suv, 0.001, 0.999)).rgb;
|
|
199
|
+
float mag = length(dc);
|
|
200
|
+
// Colour is carried IN the dye field and advected with it, so
|
|
201
|
+
// a ribbon keeps its hue while the flow folds it. Deriving
|
|
202
|
+
// colour from the local flow direction instead would make
|
|
203
|
+
// neighbouring pixels jump hue and read as confetti.
|
|
204
|
+
vec3 liquid = mag > 1e-4 ? clamp(dc / mag * 1.25, 0.0, 1.0) : ${p}tint;
|
|
205
|
+
float amt = clamp((1.0 - exp(-mag * ${p}intensity)) * ${p}dyemix, 0.0, 1.0);
|
|
206
|
+
// Straight-alpha OVER so a later overlay op (blobs) still
|
|
207
|
+
// composites correctly on top of the liquid.
|
|
208
|
+
float na = amt + ovA * (1.0 - amt);
|
|
209
|
+
ovCol = (liquid * amt + ovCol * ovA * (1.0 - amt)) / max(na, 1e-4);
|
|
210
|
+
ovA = na;
|
|
211
|
+
|
|
212
|
+
// Faint sheen on the content itself where the flow is fast.
|
|
213
|
+
float sp = length(texture2D(${p}vel, clamp(suv, 0.001, 0.999)).xy);
|
|
214
|
+
col = mix(col, ${p}tint, (1.0 - exp(-sp * 0.7)) * ${p}sheen);
|
|
180
215
|
}`,
|
|
181
216
|
uniforms: (node, dpr) => ({
|
|
182
217
|
strength: ["1f", (node.strength != null ? node.strength : 26) * dpr],
|
|
183
|
-
sheen: ["1f", node.sheen != null ? node.sheen : 0.
|
|
184
|
-
tint: ["3fv", hexToRgb(node.tint || "#
|
|
218
|
+
sheen: ["1f", node.sheen != null ? node.sheen : 0.15],
|
|
219
|
+
tint: ["3fv", hexToRgb(node.tint || "#7FD4FF")],
|
|
220
|
+
dyemix: ["1f", node.dye != null ? node.dye : 0.85],
|
|
221
|
+
intensity: ["1f", node.intensity != null ? node.intensity : 1.6],
|
|
185
222
|
}),
|
|
186
|
-
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
223
|
+
|
|
224
|
+
// ── The simulation ────────────────────────────────────────────
|
|
225
|
+
// Declared as data: the pipeline allocates the targets, compiles
|
|
226
|
+
// the programs, and calls step() once per frame.
|
|
227
|
+
solver: {
|
|
228
|
+
resolutions: (node) => ({
|
|
229
|
+
sim: Math.max(32, Math.min(node.resolution || 128, 512)),
|
|
230
|
+
dye: Math.max(64, Math.min(node.dyeResolution || 512, 1024)),
|
|
231
|
+
}),
|
|
232
|
+
// read/write pairs ping-pong; single targets are scratch.
|
|
233
|
+
targets: {
|
|
234
|
+
velocity: { double: true, res: "sim", fmt: "rg", smooth: true },
|
|
235
|
+
dye: { double: true, res: "dye", fmt: "rgba", smooth: true },
|
|
236
|
+
pressure: { double: true, res: "sim", fmt: "r", smooth: false },
|
|
237
|
+
divergence: { double: false, res: "sim", fmt: "r", smooth: false },
|
|
238
|
+
vorticity: { double: false, res: "sim", fmt: "r", smooth: false },
|
|
239
|
+
},
|
|
240
|
+
// Which target each main-pass sampler reads.
|
|
241
|
+
samplers: { vel: "velocity", dye: "dye" },
|
|
242
|
+
|
|
243
|
+
programs: {
|
|
244
|
+
// Gaussian source term. Distance is measured as a
|
|
245
|
+
// fraction of the element's LONGER side so the radius
|
|
246
|
+
// means the same thing whatever the element's shape.
|
|
247
|
+
splat: `
|
|
191
248
|
precision highp float;
|
|
192
|
-
uniform sampler2D
|
|
193
|
-
uniform vec2
|
|
194
|
-
uniform
|
|
195
|
-
uniform
|
|
196
|
-
uniform float
|
|
197
|
-
uniform float
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
uniform float s_range;
|
|
206
|
-
|
|
207
|
-
vec2 rd(vec2 uvp) {
|
|
208
|
-
vec4 t = texture2D(s_field, clamp(uvp, 0.0, 1.0));
|
|
209
|
-
return s_enc > 0.5 ? (t.xy * 2.0 - 1.0) * s_range : t.xy;
|
|
210
|
-
}
|
|
249
|
+
uniform sampler2D u_src;
|
|
250
|
+
uniform vec2 u_texel;
|
|
251
|
+
uniform vec2 u_point;
|
|
252
|
+
uniform vec3 u_amount;
|
|
253
|
+
uniform float u_radius;
|
|
254
|
+
uniform float u_aspect;
|
|
255
|
+
void main() {
|
|
256
|
+
vec2 uv = gl_FragCoord.xy * u_texel;
|
|
257
|
+
vec2 d = uv - u_point;
|
|
258
|
+
if (u_aspect >= 1.0) d.y /= u_aspect; else d.x *= u_aspect;
|
|
259
|
+
float g = exp(-dot(d, d) / max(u_radius * u_radius, 1e-6));
|
|
260
|
+
gl_FragColor = vec4(texture2D(u_src, uv).xyz + u_amount * g, 1.0);
|
|
261
|
+
}`,
|
|
211
262
|
|
|
263
|
+
// Semi-Lagrangian advection (Stam 1999): trace the
|
|
264
|
+
// characteristic backwards and resample. Unconditionally
|
|
265
|
+
// stable for any timestep, which is the whole point.
|
|
266
|
+
advect: `
|
|
267
|
+
precision highp float;
|
|
268
|
+
uniform sampler2D u_vel;
|
|
269
|
+
uniform sampler2D u_src;
|
|
270
|
+
uniform vec2 u_texel;
|
|
271
|
+
uniform float u_dt;
|
|
272
|
+
uniform float u_diss;
|
|
212
273
|
void main() {
|
|
213
|
-
vec2 uv = gl_FragCoord.xy *
|
|
214
|
-
vec2
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
vec2 a = rd(uv - m * s_advect * s_dt);
|
|
218
|
-
|
|
219
|
-
// 2. Curl of the field, normalised so the swirl setting is
|
|
220
|
-
// resolution independent (central differences over two texels).
|
|
221
|
-
float r = rd(uv + vec2(s_texel.x, 0.0)).y;
|
|
222
|
-
float l = rd(uv - vec2(s_texel.x, 0.0)).y;
|
|
223
|
-
float t = rd(uv + vec2(0.0, s_texel.y)).x;
|
|
224
|
-
float b = rd(uv - vec2(0.0, s_texel.y)).x;
|
|
225
|
-
float curl = ((r - l) - (t - b)) * 0.5 / s_texel.y;
|
|
226
|
-
|
|
227
|
-
// 3. Rotate momentum by the local curl angle. Norm preserving, so
|
|
228
|
-
// eddies spin and survive rather than smearing out — this is
|
|
229
|
-
// what replaces vorticity confinement + pressure projection.
|
|
230
|
-
float th = curl * s_swirl * s_dt;
|
|
231
|
-
float cs = cos(th), sn = sin(th);
|
|
232
|
-
a = mat2(cs, sn, -sn, cs) * a;
|
|
233
|
-
|
|
234
|
-
// 4. Viscous decay (frame-rate independent).
|
|
235
|
-
a *= pow(s_decay, s_dt * 60.0);
|
|
236
|
-
|
|
237
|
-
// 5. The spoon: a gaussian impulse of pointer velocity.
|
|
238
|
-
vec2 d = uv - s_point;
|
|
239
|
-
d.x *= s_aspect;
|
|
240
|
-
a += s_impulse * exp(-dot(d, d) / max(s_radius * s_radius, 1e-6));
|
|
241
|
-
|
|
242
|
-
// 6. Optional hands-free stir: a source orbiting the centre.
|
|
243
|
-
if (s_idle > 0.0) {
|
|
244
|
-
float ia = s_time * 0.6;
|
|
245
|
-
vec2 ic = vec2(0.5 + 0.22 * cos(ia), 0.5 + 0.22 * sin(ia));
|
|
246
|
-
vec2 id = uv - ic;
|
|
247
|
-
id.x *= s_aspect;
|
|
248
|
-
a += vec2(-sin(ia), cos(ia)) * s_idle *
|
|
249
|
-
exp(-dot(id, id) / max(s_radius * s_radius, 1e-6)) * s_dt * 60.0;
|
|
250
|
-
}
|
|
274
|
+
vec2 uv = gl_FragCoord.xy * u_texel;
|
|
275
|
+
vec2 back = uv - texture2D(u_vel, uv).xy * u_dt;
|
|
276
|
+
gl_FragColor = vec4(texture2D(u_src, clamp(back, 0.0, 1.0)).xyz * u_diss, 1.0);
|
|
277
|
+
}`,
|
|
251
278
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
279
|
+
// Scalar vorticity of a 2D field: d(vy)/dx - d(vx)/dy.
|
|
280
|
+
curl: `
|
|
281
|
+
precision highp float;
|
|
282
|
+
uniform sampler2D u_vel;
|
|
283
|
+
uniform vec2 u_texel;
|
|
284
|
+
void main() {
|
|
285
|
+
vec2 uv = gl_FragCoord.xy * u_texel;
|
|
286
|
+
float r = texture2D(u_vel, uv + vec2(u_texel.x, 0.0)).y;
|
|
287
|
+
float l = texture2D(u_vel, uv - vec2(u_texel.x, 0.0)).y;
|
|
288
|
+
float t = texture2D(u_vel, uv + vec2(0.0, u_texel.y)).x;
|
|
289
|
+
float b = texture2D(u_vel, uv - vec2(0.0, u_texel.y)).x;
|
|
290
|
+
gl_FragColor = vec4(0.5 * ((r - l) - (t - b)), 0.0, 0.0, 1.0);
|
|
291
|
+
}`,
|
|
255
292
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
293
|
+
// Vorticity confinement (Fedkiw et al. 2001): push the
|
|
294
|
+
// velocity along N x omega, where N is the unit gradient
|
|
295
|
+
// of |omega|, i.e. toward the centre of each eddy. This
|
|
296
|
+
// feeds energy back into swirl the grid would otherwise
|
|
297
|
+
// smooth out.
|
|
298
|
+
confine: `
|
|
299
|
+
precision highp float;
|
|
300
|
+
uniform sampler2D u_vel;
|
|
301
|
+
uniform sampler2D u_curl;
|
|
302
|
+
uniform vec2 u_texel;
|
|
303
|
+
uniform float u_eps;
|
|
304
|
+
uniform float u_dt;
|
|
305
|
+
void main() {
|
|
306
|
+
vec2 uv = gl_FragCoord.xy * u_texel;
|
|
307
|
+
float r = texture2D(u_curl, uv + vec2(u_texel.x, 0.0)).x;
|
|
308
|
+
float l = texture2D(u_curl, uv - vec2(u_texel.x, 0.0)).x;
|
|
309
|
+
float t = texture2D(u_curl, uv + vec2(0.0, u_texel.y)).x;
|
|
310
|
+
float b = texture2D(u_curl, uv - vec2(0.0, u_texel.y)).x;
|
|
311
|
+
float c = texture2D(u_curl, uv).x;
|
|
312
|
+
vec2 grad = 0.5 * vec2(abs(r) - abs(l), abs(t) - abs(b));
|
|
313
|
+
vec2 n = grad / (length(grad) + 1e-5);
|
|
314
|
+
vec2 force = u_eps * c * vec2(n.y, -n.x);
|
|
315
|
+
vec2 v = texture2D(u_vel, uv).xy + force * u_dt;
|
|
316
|
+
gl_FragColor = vec4(clamp(v, -64.0, 64.0), 0.0, 1.0);
|
|
259
317
|
}`,
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
318
|
+
|
|
319
|
+
// div(v). The walls reflect, so the fluid stays in the box.
|
|
320
|
+
divergence: `
|
|
321
|
+
precision highp float;
|
|
322
|
+
uniform sampler2D u_vel;
|
|
323
|
+
uniform vec2 u_texel;
|
|
324
|
+
void main() {
|
|
325
|
+
vec2 uv = gl_FragCoord.xy * u_texel;
|
|
326
|
+
float r = texture2D(u_vel, uv + vec2(u_texel.x, 0.0)).x;
|
|
327
|
+
float l = texture2D(u_vel, uv - vec2(u_texel.x, 0.0)).x;
|
|
328
|
+
float t = texture2D(u_vel, uv + vec2(0.0, u_texel.y)).y;
|
|
329
|
+
float b = texture2D(u_vel, uv - vec2(0.0, u_texel.y)).y;
|
|
330
|
+
vec2 c = texture2D(u_vel, uv).xy;
|
|
331
|
+
if (uv.x - u_texel.x < 0.0) l = -c.x;
|
|
332
|
+
if (uv.x + u_texel.x > 1.0) r = -c.x;
|
|
333
|
+
if (uv.y - u_texel.y < 0.0) b = -c.y;
|
|
334
|
+
if (uv.y + u_texel.y > 1.0) t = -c.y;
|
|
335
|
+
gl_FragColor = vec4(0.5 * ((r - l) + (t - b)), 0.0, 0.0, 1.0);
|
|
336
|
+
}`,
|
|
337
|
+
|
|
338
|
+
// One Jacobi sweep of the Poisson equation lap(p) = div(v)
|
|
339
|
+
// (Harris 2003). Run a handful of these per frame.
|
|
340
|
+
jacobi: `
|
|
341
|
+
precision highp float;
|
|
342
|
+
uniform sampler2D u_p;
|
|
343
|
+
uniform sampler2D u_div;
|
|
344
|
+
uniform vec2 u_texel;
|
|
345
|
+
void main() {
|
|
346
|
+
vec2 uv = gl_FragCoord.xy * u_texel;
|
|
347
|
+
float r = texture2D(u_p, uv + vec2(u_texel.x, 0.0)).x;
|
|
348
|
+
float l = texture2D(u_p, uv - vec2(u_texel.x, 0.0)).x;
|
|
349
|
+
float t = texture2D(u_p, uv + vec2(0.0, u_texel.y)).x;
|
|
350
|
+
float b = texture2D(u_p, uv - vec2(0.0, u_texel.y)).x;
|
|
351
|
+
float d = texture2D(u_div, uv).x;
|
|
352
|
+
gl_FragColor = vec4((r + l + t + b - d) * 0.25, 0.0, 0.0, 1.0);
|
|
353
|
+
}`,
|
|
354
|
+
|
|
355
|
+
// v <- v - grad(p): the projection itself. After this the
|
|
356
|
+
// velocity is (discretely) divergence free.
|
|
357
|
+
subtract: `
|
|
358
|
+
precision highp float;
|
|
359
|
+
uniform sampler2D u_p;
|
|
360
|
+
uniform sampler2D u_vel;
|
|
361
|
+
uniform vec2 u_texel;
|
|
362
|
+
void main() {
|
|
363
|
+
vec2 uv = gl_FragCoord.xy * u_texel;
|
|
364
|
+
float r = texture2D(u_p, uv + vec2(u_texel.x, 0.0)).x;
|
|
365
|
+
float l = texture2D(u_p, uv - vec2(u_texel.x, 0.0)).x;
|
|
366
|
+
float t = texture2D(u_p, uv + vec2(0.0, u_texel.y)).x;
|
|
367
|
+
float b = texture2D(u_p, uv - vec2(0.0, u_texel.y)).x;
|
|
368
|
+
vec2 v = texture2D(u_vel, uv).xy - 0.5 * vec2(r - l, t - b);
|
|
369
|
+
gl_FragColor = vec4(v, 0.0, 1.0);
|
|
370
|
+
}`,
|
|
371
|
+
|
|
372
|
+
// Scale a target in place (used to carry a fraction of
|
|
373
|
+
// the previous pressure into the next solve).
|
|
374
|
+
decay: `
|
|
375
|
+
precision highp float;
|
|
376
|
+
uniform sampler2D u_tex;
|
|
377
|
+
uniform vec2 u_texel;
|
|
378
|
+
uniform float u_value;
|
|
379
|
+
void main() {
|
|
380
|
+
gl_FragColor = texture2D(u_tex, gl_FragCoord.xy * u_texel) * u_value;
|
|
381
|
+
}`,
|
|
382
|
+
},
|
|
383
|
+
|
|
384
|
+
// Colour dropped in by the spoon. With rainbow on, the hue
|
|
385
|
+
// comes from the stroke direction at the moment of injection
|
|
386
|
+
// and is then carried by the dye, so it stays coherent as the
|
|
387
|
+
// flow folds it.
|
|
388
|
+
splatColor: (node, dx, dy) => {
|
|
389
|
+
const amt = node.dyeAmount != null ? node.dyeAmount : 0.6;
|
|
390
|
+
if (!node.rainbow) {
|
|
391
|
+
const c = hexToRgb(node.tint || "#7FD4FF");
|
|
392
|
+
return [c[0] * amt, c[1] * amt, c[2] * amt];
|
|
393
|
+
}
|
|
394
|
+
const a = Math.atan2(dy, dx);
|
|
395
|
+
return [
|
|
396
|
+
(0.5 + 0.5 * Math.cos(a)) * amt,
|
|
397
|
+
(0.5 + 0.5 * Math.cos(a + 2.0944)) * amt,
|
|
398
|
+
(0.5 + 0.5 * Math.cos(a + 4.1888)) * amt,
|
|
399
|
+
];
|
|
400
|
+
},
|
|
401
|
+
|
|
402
|
+
step: (S, node, ctx) => {
|
|
403
|
+
const dt = Math.min(ctx.dt, 1 / 30);
|
|
404
|
+
const simT = 1 / S.res.sim;
|
|
405
|
+
const dyeT = 1 / S.res.dye;
|
|
406
|
+
const pow = (base, d) => Math.pow(base, d * 60);
|
|
407
|
+
|
|
408
|
+
// 1. Forces: splat momentum and dye at the pointer.
|
|
409
|
+
if (ctx.moving) {
|
|
410
|
+
const f = node.force != null ? node.force : 1.0;
|
|
411
|
+
S.use("splat");
|
|
412
|
+
S.f1("u_radius", node.radius != null ? node.radius : 0.1);
|
|
413
|
+
S.f1("u_aspect", ctx.aspect);
|
|
414
|
+
S.f2("u_point", ctx.px, ctx.py);
|
|
415
|
+
|
|
416
|
+
S.f2("u_texel", simT, simT);
|
|
417
|
+
S.smp("u_src", S.read("velocity"), 0);
|
|
418
|
+
S.f3("u_amount", ctx.dx * f, ctx.dy * f, 0);
|
|
419
|
+
S.blit(S.write("velocity"));
|
|
420
|
+
S.swap("velocity");
|
|
421
|
+
|
|
422
|
+
S.f2("u_texel", dyeT, dyeT);
|
|
423
|
+
S.smp("u_src", S.read("dye"), 0);
|
|
424
|
+
S.f3("u_amount", ctx.col[0], ctx.col[1], ctx.col[2]);
|
|
425
|
+
S.blit(S.write("dye"));
|
|
426
|
+
S.swap("dye");
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// 2. Vorticity confinement.
|
|
430
|
+
S.use("curl");
|
|
431
|
+
S.f2("u_texel", simT, simT);
|
|
432
|
+
S.smp("u_vel", S.read("velocity"), 0);
|
|
433
|
+
S.blit(S.single("vorticity"));
|
|
434
|
+
|
|
435
|
+
S.use("confine");
|
|
436
|
+
S.f2("u_texel", simT, simT);
|
|
437
|
+
S.f1("u_eps", node.curl != null ? node.curl : 2.2);
|
|
438
|
+
S.f1("u_dt", dt);
|
|
439
|
+
S.smp("u_vel", S.read("velocity"), 0);
|
|
440
|
+
S.smp("u_curl", S.single("vorticity"), 1);
|
|
441
|
+
S.blit(S.write("velocity"));
|
|
442
|
+
S.swap("velocity");
|
|
443
|
+
|
|
444
|
+
// 3. Projection: divergence -> pressure -> subtract.
|
|
445
|
+
S.use("divergence");
|
|
446
|
+
S.f2("u_texel", simT, simT);
|
|
447
|
+
S.smp("u_vel", S.read("velocity"), 0);
|
|
448
|
+
S.blit(S.single("divergence"));
|
|
449
|
+
|
|
450
|
+
S.use("decay");
|
|
451
|
+
S.f2("u_texel", simT, simT);
|
|
452
|
+
S.f1("u_value", pow(node.pressure != null ? node.pressure : 0.8, dt));
|
|
453
|
+
S.smp("u_tex", S.read("pressure"), 0);
|
|
454
|
+
S.blit(S.write("pressure"));
|
|
455
|
+
S.swap("pressure");
|
|
456
|
+
|
|
457
|
+
S.use("jacobi");
|
|
458
|
+
S.f2("u_texel", simT, simT);
|
|
459
|
+
S.smp("u_div", S.single("divergence"), 1);
|
|
460
|
+
const iters = Math.max(1, Math.min(node.pressureIterations || 6, 40));
|
|
461
|
+
for (let k = 0; k < iters; k++) {
|
|
462
|
+
S.smp("u_p", S.read("pressure"), 0);
|
|
463
|
+
S.blit(S.write("pressure"));
|
|
464
|
+
S.swap("pressure");
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
S.use("subtract");
|
|
468
|
+
S.f2("u_texel", simT, simT);
|
|
469
|
+
S.smp("u_p", S.read("pressure"), 0);
|
|
470
|
+
S.smp("u_vel", S.read("velocity"), 1);
|
|
471
|
+
S.blit(S.write("velocity"));
|
|
472
|
+
S.swap("velocity");
|
|
473
|
+
|
|
474
|
+
// 4. Advection: velocity by itself, then dye by velocity.
|
|
475
|
+
S.use("advect");
|
|
476
|
+
S.f1("u_dt", dt);
|
|
477
|
+
S.f2("u_texel", simT, simT);
|
|
478
|
+
S.smp("u_vel", S.read("velocity"), 0);
|
|
479
|
+
S.smp("u_src", S.read("velocity"), 0);
|
|
480
|
+
S.f1("u_diss", pow(node.decay != null ? node.decay : 0.985, dt));
|
|
481
|
+
S.blit(S.write("velocity"));
|
|
482
|
+
S.swap("velocity");
|
|
483
|
+
|
|
484
|
+
S.f2("u_texel", dyeT, dyeT);
|
|
485
|
+
S.smp("u_vel", S.read("velocity"), 0);
|
|
486
|
+
S.smp("u_src", S.read("dye"), 1);
|
|
487
|
+
S.f1("u_diss", pow(node.dyeFade != null ? node.dyeFade : 0.97, dt));
|
|
488
|
+
S.blit(S.write("dye"));
|
|
489
|
+
S.swap("dye");
|
|
490
|
+
},
|
|
269
491
|
},
|
|
270
492
|
},
|
|
271
493
|
|
|
@@ -795,133 +1017,171 @@ function applyRasterPipeline(el, rasterNodes) {
|
|
|
795
1017
|
});
|
|
796
1018
|
let lastFrame = (typeof performance !== "undefined") ? performance.now() : 0;
|
|
797
1019
|
|
|
798
|
-
// ──
|
|
799
|
-
//
|
|
800
|
-
//
|
|
801
|
-
//
|
|
802
|
-
//
|
|
803
|
-
//
|
|
804
|
-
|
|
805
|
-
|
|
1020
|
+
// ── Fluid solvers (ops with a `solver` descriptor, e.g. stir) ─────
|
|
1021
|
+
// An op may declare a set of render targets and fragment programs
|
|
1022
|
+
// plus a step() that sequences the passes. The pipeline allocates
|
|
1023
|
+
// everything here, runs step() once per frame, and binds the named
|
|
1024
|
+
// result textures into the main composite pass.
|
|
1025
|
+
//
|
|
1026
|
+
// Float render targets are required (velocity and pressure are
|
|
1027
|
+
// signed and need range well outside [0,1]). Without them the op
|
|
1028
|
+
// disables itself rather than rendering something wrong.
|
|
1029
|
+
const solverOps = [];
|
|
1030
|
+
const makeSolver = (node, i) => {
|
|
806
1031
|
const def = REGISTRY[node.op];
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
819
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
820
|
-
if (enc) {
|
|
821
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, res, res, 0,
|
|
822
|
-
gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
823
|
-
} else {
|
|
824
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, res, res, 0,
|
|
825
|
-
gl.RGBA, gl.HALF_FLOAT, null);
|
|
826
|
-
}
|
|
827
|
-
const f = gl.createFramebuffer();
|
|
828
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, f);
|
|
829
|
-
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
|
|
830
|
-
gl.TEXTURE_2D, t, 0);
|
|
831
|
-
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
|
|
832
|
-
targets.forEach((x) => { gl.deleteTexture(x.tex); gl.deleteFramebuffer(x.fbo); });
|
|
833
|
-
gl.deleteTexture(t);
|
|
834
|
-
gl.deleteFramebuffer(f);
|
|
835
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
836
|
-
return null;
|
|
837
|
-
}
|
|
838
|
-
// Zero field == the encoded midpoint in the 8-bit path.
|
|
839
|
-
gl.viewport(0, 0, res, res);
|
|
840
|
-
gl.clearColor(enc ? 0.5 : 0, enc ? 0.5 : 0, 0, 1);
|
|
841
|
-
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
842
|
-
targets.push({ tex: t, fbo: f });
|
|
843
|
-
}
|
|
844
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
845
|
-
return targets;
|
|
1032
|
+
if (!isGL2 || !gl.getExtension("EXT_color_buffer_float")) {
|
|
1033
|
+
console.info("[nodality] '" + node.op +
|
|
1034
|
+
"' needs WebGL2 float render targets - op skipped.");
|
|
1035
|
+
return null;
|
|
1036
|
+
}
|
|
1037
|
+
gl.getExtension("OES_texture_float_linear");
|
|
1038
|
+
const res = def.solver.resolutions(node);
|
|
1039
|
+
const FMT = {
|
|
1040
|
+
r: [gl.R16F, gl.RED],
|
|
1041
|
+
rg: [gl.RG16F, gl.RG],
|
|
1042
|
+
rgba: [gl.RGBA16F, gl.RGBA],
|
|
846
1043
|
};
|
|
847
1044
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1045
|
+
const made = [];
|
|
1046
|
+
const mkTarget = (size, fmt, smooth) => {
|
|
1047
|
+
const [internal, format] = FMT[fmt];
|
|
1048
|
+
const t = gl.createTexture();
|
|
1049
|
+
gl.bindTexture(gl.TEXTURE_2D, t);
|
|
1050
|
+
const flt = smooth ? gl.LINEAR : gl.NEAREST;
|
|
1051
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, flt);
|
|
1052
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, flt);
|
|
1053
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
1054
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
1055
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, internal, size, size, 0,
|
|
1056
|
+
format, gl.HALF_FLOAT, null);
|
|
1057
|
+
const fbo = gl.createFramebuffer();
|
|
1058
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
|
|
1059
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
|
|
1060
|
+
gl.TEXTURE_2D, t, 0);
|
|
1061
|
+
const ok = gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE;
|
|
1062
|
+
gl.viewport(0, 0, size, size);
|
|
1063
|
+
gl.clearColor(0, 0, 0, 1);
|
|
1064
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
1065
|
+
const rec = { tex: t, fbo, size, ok };
|
|
1066
|
+
made.push(rec);
|
|
1067
|
+
return rec;
|
|
1068
|
+
};
|
|
1069
|
+
|
|
1070
|
+
const targets = {};
|
|
1071
|
+
let allOk = true;
|
|
1072
|
+
for (const name in def.solver.targets) {
|
|
1073
|
+
const spec = def.solver.targets[name];
|
|
1074
|
+
const size = res[spec.res];
|
|
1075
|
+
if (spec.double) {
|
|
1076
|
+
targets[name] = {
|
|
1077
|
+
a: mkTarget(size, spec.fmt, spec.smooth),
|
|
1078
|
+
b: mkTarget(size, spec.fmt, spec.smooth),
|
|
1079
|
+
};
|
|
1080
|
+
allOk = allOk && targets[name].a.ok && targets[name].b.ok;
|
|
1081
|
+
} else {
|
|
1082
|
+
targets[name] = { a: mkTarget(size, spec.fmt, spec.smooth) };
|
|
1083
|
+
allOk = allOk && targets[name].a.ok;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
1087
|
+
|
|
1088
|
+
const cleanup = () => made.forEach((t) => {
|
|
1089
|
+
gl.deleteFramebuffer(t.fbo);
|
|
1090
|
+
gl.deleteTexture(t.tex);
|
|
1091
|
+
});
|
|
852
1092
|
|
|
853
|
-
|
|
1093
|
+
if (!allOk) {
|
|
1094
|
+
console.info("[nodality] '" + node.op +
|
|
1095
|
+
"' float targets incomplete - op skipped.");
|
|
1096
|
+
cleanup();
|
|
1097
|
+
return null;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
const progs = {};
|
|
854
1101
|
try {
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1102
|
+
for (const name in def.solver.programs) {
|
|
1103
|
+
const pr = gl.createProgram();
|
|
1104
|
+
gl.attachShader(pr, compile(gl.VERTEX_SHADER, VS));
|
|
1105
|
+
gl.attachShader(pr, compile(gl.FRAGMENT_SHADER, def.solver.programs[name]));
|
|
1106
|
+
gl.bindAttribLocation(pr, 0, "a");
|
|
1107
|
+
gl.linkProgram(pr);
|
|
1108
|
+
if (!gl.getProgramParameter(pr, gl.LINK_STATUS)) {
|
|
1109
|
+
throw new Error(name + ": " + gl.getProgramInfoLog(pr));
|
|
1110
|
+
}
|
|
1111
|
+
progs[name] = { prog: pr, loc: {} };
|
|
862
1112
|
}
|
|
863
1113
|
} catch (e) {
|
|
864
|
-
console.warn("[nodality]
|
|
865
|
-
|
|
1114
|
+
console.warn("[nodality] '" + node.op + "' solver programs failed:", e);
|
|
1115
|
+
cleanup();
|
|
1116
|
+
for (const n in progs) gl.deleteProgram(progs[n].prog);
|
|
866
1117
|
return null;
|
|
867
1118
|
}
|
|
868
1119
|
|
|
869
|
-
// Static sim uniforms (set once).
|
|
870
|
-
gl.useProgram(simProg);
|
|
871
|
-
const SU = (n) => gl.getUniformLocation(simProg, n);
|
|
872
|
-
const statics = def.field.uniforms(node);
|
|
873
|
-
for (const key in statics) setUniform(SU(key), statics[key][0], statics[key][1]);
|
|
874
|
-
gl.uniform2f(SU("s_texel"), 1 / res, 1 / res);
|
|
875
|
-
gl.uniform1f(SU("s_enc"), enc);
|
|
876
|
-
gl.uniform1f(SU("s_range"), RANGE);
|
|
877
|
-
|
|
878
|
-
// The main pass needs to know how to decode what it samples.
|
|
879
1120
|
gl.useProgram(prog);
|
|
880
|
-
gl.uniform1f(U(`u${i}_enc`), enc);
|
|
881
|
-
gl.uniform1f(U(`u${i}_range`), RANGE);
|
|
882
|
-
|
|
883
1121
|
return {
|
|
884
|
-
i, res,
|
|
885
|
-
src: pair[0], dst: pair[1],
|
|
886
|
-
force: def.field.force(node),
|
|
1122
|
+
i, node, def, res, targets, progs, cleanup,
|
|
887
1123
|
prevX: 0.5, prevY: 0.5, primed: false,
|
|
888
1124
|
};
|
|
889
1125
|
};
|
|
890
1126
|
rasterNodes.forEach((node, i) => {
|
|
891
1127
|
const def = REGISTRY[node.op];
|
|
892
|
-
if (!def || !def.
|
|
893
|
-
const
|
|
894
|
-
if (
|
|
1128
|
+
if (!def || !def.solver) return;
|
|
1129
|
+
const s = makeSolver(node, i);
|
|
1130
|
+
if (s) solverOps.push(s);
|
|
895
1131
|
});
|
|
896
1132
|
|
|
897
|
-
//
|
|
898
|
-
const
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1133
|
+
// One frame of a solver. `S` is the small API the op's step() drives.
|
|
1134
|
+
const stepSolver = (s, dt) => {
|
|
1135
|
+
let cur = null;
|
|
1136
|
+
const S = {
|
|
1137
|
+
res: s.res,
|
|
1138
|
+
use(name) {
|
|
1139
|
+
cur = s.progs[name];
|
|
1140
|
+
gl.useProgram(cur.prog);
|
|
1141
|
+
},
|
|
1142
|
+
u(name) {
|
|
1143
|
+
if (!(name in cur.loc)) cur.loc[name] = gl.getUniformLocation(cur.prog, name);
|
|
1144
|
+
return cur.loc[name];
|
|
1145
|
+
},
|
|
1146
|
+
f1(n, v) { gl.uniform1f(S.u(n), v); },
|
|
1147
|
+
f2(n, x, y) { gl.uniform2f(S.u(n), x, y); },
|
|
1148
|
+
f3(n, x, y, z) { gl.uniform3f(S.u(n), x, y, z); },
|
|
1149
|
+
smp(n, target, unit) {
|
|
1150
|
+
gl.activeTexture(gl.TEXTURE0 + unit);
|
|
1151
|
+
gl.bindTexture(gl.TEXTURE_2D, target.tex);
|
|
1152
|
+
gl.uniform1i(S.u(n), unit);
|
|
1153
|
+
},
|
|
1154
|
+
read: (name) => s.targets[name].a,
|
|
1155
|
+
write: (name) => s.targets[name].b,
|
|
1156
|
+
single: (name) => s.targets[name].a,
|
|
1157
|
+
swap(name) {
|
|
1158
|
+
const t = s.targets[name];
|
|
1159
|
+
const tmp = t.a; t.a = t.b; t.b = tmp;
|
|
1160
|
+
},
|
|
1161
|
+
blit(target) {
|
|
1162
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, target.fbo);
|
|
1163
|
+
gl.viewport(0, 0, target.size, target.size);
|
|
1164
|
+
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
1165
|
+
},
|
|
1166
|
+
};
|
|
1167
|
+
|
|
1168
|
+
// Pointer in field space (bottom-up [0,1]) and the velocity it
|
|
1169
|
+
// picked up since the previous frame - the spoon.
|
|
908
1170
|
const px = mouse[0] / canvas.width;
|
|
909
1171
|
const py = 1 - mouse[1] / canvas.height;
|
|
910
|
-
let
|
|
911
|
-
if (
|
|
912
|
-
|
|
913
|
-
|
|
1172
|
+
let dx = 0, dy = 0;
|
|
1173
|
+
if (s.primed) {
|
|
1174
|
+
dx = (px - s.prevX) / Math.max(dt, 1e-3);
|
|
1175
|
+
dy = (py - s.prevY) / Math.max(dt, 1e-3);
|
|
914
1176
|
}
|
|
915
|
-
|
|
1177
|
+
s.prevX = px; s.prevY = py; s.primed = true;
|
|
1178
|
+
const moving = Math.abs(dx) + Math.abs(dy) > 1e-3;
|
|
916
1179
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
923
|
-
|
|
924
|
-
const swap = f.src; f.src = f.dst; f.dst = swap;
|
|
1180
|
+
s.def.solver.step(S, s.node, {
|
|
1181
|
+
dt, px, py, dx, dy, moving,
|
|
1182
|
+
aspect: canvas.width / Math.max(canvas.height, 1),
|
|
1183
|
+
col: s.def.solver.splatColor(s.node, dx, dy),
|
|
1184
|
+
});
|
|
925
1185
|
};
|
|
926
1186
|
|
|
927
1187
|
const tex = gl.createTexture();
|
|
@@ -1107,9 +1367,9 @@ function applyRasterPipeline(el, rasterNodes) {
|
|
|
1107
1367
|
const now = (typeof performance !== "undefined") ? performance.now() : lastFrame + 16;
|
|
1108
1368
|
const dt = Math.max(0, (now - lastFrame) / 1000);
|
|
1109
1369
|
|
|
1110
|
-
// 1. Advance stateful
|
|
1111
|
-
//
|
|
1112
|
-
for (const
|
|
1370
|
+
// 1. Advance stateful fluid solvers into their own off-screen
|
|
1371
|
+
// targets (this rebinds framebuffer/program/textures).
|
|
1372
|
+
for (const s of solverOps) stepSolver(s, dt);
|
|
1113
1373
|
|
|
1114
1374
|
// 2. Composite pass, back on the visible framebuffer.
|
|
1115
1375
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
@@ -1128,15 +1388,21 @@ function applyRasterPipeline(el, rasterNodes) {
|
|
|
1128
1388
|
for (const u of ups) setUniform(U(`u${d.i}_${u.name}`), u.kind, u.value);
|
|
1129
1389
|
}
|
|
1130
1390
|
}
|
|
1131
|
-
// Content on unit 0, each
|
|
1391
|
+
// Content on unit 0, then each solver's named result
|
|
1392
|
+
// textures (velocity, dye, ...) on units above it.
|
|
1132
1393
|
gl.activeTexture(gl.TEXTURE0);
|
|
1133
1394
|
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
1134
1395
|
gl.uniform1i(U("u_tex"), 0);
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1396
|
+
let unit = 1;
|
|
1397
|
+
for (const s of solverOps) {
|
|
1398
|
+
const smps = s.def.solver.samplers;
|
|
1399
|
+
for (const key in smps) {
|
|
1400
|
+
gl.activeTexture(gl.TEXTURE0 + unit);
|
|
1401
|
+
gl.bindTexture(gl.TEXTURE_2D, s.targets[smps[key]].a.tex);
|
|
1402
|
+
gl.uniform1i(U(`u${s.i}_${key}`), unit);
|
|
1403
|
+
unit++;
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1140
1406
|
gl.activeTexture(gl.TEXTURE0); // texture uploads assume unit 0
|
|
1141
1407
|
lastFrame = now;
|
|
1142
1408
|
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
@@ -1233,14 +1499,11 @@ function applyRasterPipeline(el, rasterNodes) {
|
|
|
1233
1499
|
el.removeEventListener("mousemove", onMove);
|
|
1234
1500
|
el.removeEventListener("touchmove", onMove);
|
|
1235
1501
|
canvas.removeEventListener("paint", onPaint);
|
|
1236
|
-
for (const
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
gl.deleteTexture(t.tex);
|
|
1240
|
-
});
|
|
1241
|
-
gl.deleteProgram(f.prog);
|
|
1502
|
+
for (const s of solverOps) {
|
|
1503
|
+
s.cleanup();
|
|
1504
|
+
for (const n in s.progs) gl.deleteProgram(s.progs[n].prog);
|
|
1242
1505
|
}
|
|
1243
|
-
|
|
1506
|
+
solverOps.length = 0;
|
|
1244
1507
|
while (sourceEl !== el && sourceEl.firstChild) el.appendChild(sourceEl.firstChild);
|
|
1245
1508
|
el.style.visibility = "";
|
|
1246
1509
|
canvas.remove();
|