82 lines
2.7 KiB
JavaScript
82 lines
2.7 KiB
JavaScript
const GLSL = `
|
|
float sdSegment(vec3 p, vec3 a, vec3 b) {
|
|
vec3 pa = p - a;
|
|
vec3 ba = b - a;
|
|
float h = clamp(dot(pa, ba) / max(dot(ba, ba), 0.0001), 0.0, 1.0);
|
|
return length(pa - ba * h);
|
|
}
|
|
|
|
float smoothMin(float a, float b, float k) {
|
|
float h = clamp(0.5 + 0.5 * (b - a) / max(k, 0.0001), 0.0, 1.0);
|
|
return mix(b, a, h) - k * h * (1.0 - h);
|
|
}
|
|
|
|
vec3 moorePoint(float i) {
|
|
float row = floor(i / 6.0);
|
|
float col = mod(i, 6.0);
|
|
if (mod(row, 2.0) > 0.5) {
|
|
col = 5.0 - col;
|
|
}
|
|
vec2 planar = (vec2(col, row) - vec2(2.5)) * spacing;
|
|
float z = sin(i * 1.57079632679) * verticalWave;
|
|
return vec3(planar, z);
|
|
}
|
|
|
|
float sdf(vec3 p) {
|
|
float d = 100000.0;
|
|
for (int i = 0; i < 35; i += 1) {
|
|
float fi = float(i);
|
|
d = smoothMin(d, sdSegment(p, moorePoint(fi), moorePoint(fi + 1.0)), bendBlend);
|
|
}
|
|
d = smoothMin(d, sdSegment(p, moorePoint(35.0), moorePoint(0.0)), bendBlend);
|
|
return d - tubeRadius;
|
|
}
|
|
|
|
vec3 color(vec3 p, vec3 normal) {
|
|
float ring = atan(p.y, p.x) * 0.15915494309 + length(p.xy) * 0.018;
|
|
float band = 0.5 + 0.5 * sin(ring * 6.28318530718 + p.z * 0.25);
|
|
vec3 c = mix(baseColor, accentColor, band);
|
|
return c * (0.58 + 0.42 * smoothstep(-0.4, 0.9, normal.z));
|
|
}
|
|
`;
|
|
|
|
export default {
|
|
schema: "implicit.js/0.1.0",
|
|
name: "Moore curve loop",
|
|
description: "A closed Moore space-filling loop rendered as a continuous rounded tube.",
|
|
units: "mm",
|
|
params: {
|
|
spacing: { type: "number", label: "Cell spacing", min: 4, max: 9, step: 0.1, default: 6.2, unit: "mm" },
|
|
tubeRadius: { type: "number", label: "Tube radius", min: 0.4, max: 2.5, step: 0.05, default: 1.05, unit: "mm" },
|
|
bendBlend: { type: "number", label: "Bend blend", min: 0.1, max: 2.4, step: 0.05, default: 0.72, unit: "mm" },
|
|
verticalWave: { type: "number", label: "Vertical wave", min: 0, max: 5, step: 0.1, default: 1.4, unit: "mm" },
|
|
baseColor: { type: "color", label: "Cool color", default: "#38d9ff" },
|
|
accentColor: { type: "color", label: "Hot color", default: "#ff7ab6" }
|
|
},
|
|
animations: {
|
|
loopWave: {
|
|
label: "Loop wave",
|
|
duration: 5.2,
|
|
loop: true,
|
|
update({ progress, set }) {
|
|
const wave = Math.sin(progress * Math.PI * 2);
|
|
set("verticalWave", 1.4 + (wave * 0.5 + 0.5) * 3.2);
|
|
set("tubeRadius", 1.05 + wave * 0.26);
|
|
}
|
|
}
|
|
},
|
|
bounds: ({ params }) => {
|
|
const extent = params.spacing * 3.2 + params.tubeRadius + 5;
|
|
const z = params.verticalWave + params.tubeRadius + 5;
|
|
return [[-extent, -extent, -z], [extent, extent, z]];
|
|
},
|
|
render: ({ params }) => ({
|
|
steps: 210,
|
|
stepScale: 0.66,
|
|
maxStep: Math.max(params.tubeRadius * 3.4, 1.2),
|
|
epsilon: Math.max(params.tubeRadius * 0.013, 0.005),
|
|
normalEpsilon: Math.max(params.tubeRadius * 0.075, 0.035)
|
|
}),
|
|
glsl: GLSL
|
|
};
|