splat.ts
examples/fxedit/fx/nodes/splat.ts · raw · open the example · all examples · site home
Exports: splatNodes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
// The output node: writes the splat. Every input is optional; an absent input
// leaves that part of the splat as it was. Writes are plain statements on the
// splat var (a Fn whose result is unused is never built), in this order:
// center (set), offset (add), scale (multiply), size / sizeMix (blend toward
// a sphere), blur (add a size in quadrature, keeping total weight), rgb
// (set), alpha (set), opacity (multiply), keep (false: opacity 0, which the
// projection culls before the sort).
import { TSL, type TslNode } from "@sparkjsdev/radix";
import type { NodeDef, PortType } from "../types.js";
const { float, max, select, sqrt } = TSL;
const optional = (type: PortType, value: number | number[] | boolean, min?: number, max?: number) => ({ type, default: value, optional: true, min, max });
export const splatNodes: NodeDef[] = [
{
id: "splat.write",
label: "Splat output",
description: "Writes the splat: move it, recolor it, fade, resize or remove it",
category: "splat",
inputs: {
center: optional("vec3", [0, 0, 0], -1, 1),
offset: optional("vec3", [0, 0, 0], -1, 1),
scale: optional("float", 1, 0, 4),
size: optional("float", 0.01, 0, 0.2),
sizeMix: optional("float", 1, 0, 1),
blur: optional("float", 0, 0, 0.2),
rgb: optional("color", [1, 1, 1]),
alpha: optional("float", 1, 0, 1),
opacity: optional("float", 1, 0, 1),
keep: optional("bool", true),
},
outputs: {},
output: true,
build: ({ center, offset, scale, size, sizeMix, blur, rgb, alpha, opacity, keep }, { splat }) => {
if (center) {
splat.get("center").assign(center);
}
if (offset) {
splat.get("center").addAssign(offset);
}
if (scale) {
splat.get("scales").mulAssign(max(scale, 0.0));
}
if (sizeMix) {
// Blend toward a sphere of radius `size` in quadrature
const m = sizeMix.clamp(0, 1).toVar();
const keepOwn = float(1).sub(m).pow(2).toVar();
const target = (size ?? float(0.01)).max(1e-6).mul(m).pow(2).toVar();
const scales = splat.get("scales");
scales.assign(sqrt(scales.mul(scales).mul(keepOwn).add(target)));
}
let fade: TslNode | undefined;
if (blur) {
// Convolve with an isotropic gaussian of std-dev `blur`: variances
// add, and opacity keeps the footprint's total weight (the ratio of
// determinants over the two largest axes, as a 2D projection sees)
const b2 = max(blur, 0.0).pow(2).toVar();
const variance = splat.get("scales").mul(splat.get("scales")).toVar();
const ratio = variance.div(variance.add(b2).max(1e-12)).toVar();
fade = sqrt(ratio.x.mul(ratio.y).mul(ratio.z).div(ratio.x.min(ratio.y).min(ratio.z).max(1e-8))).toVar();
splat.get("scales").assign(sqrt(variance.add(b2)));
}
if (rgb) {
splat.get("rgb").assign(rgb);
}
if (alpha) {
splat.get("opacity").assign(alpha.clamp(0, 1));
}
if (opacity) {
splat.get("opacity").mulAssign(opacity);
}
if (fade) {
splat.get("opacity").mulAssign(fade);
}
if (keep) {
splat.get("opacity").assign(select(keep, splat.get("opacity"), float(0)));
}
return {};
},
},
];