81 lines
2.5 KiB
JavaScript
81 lines
2.5 KiB
JavaScript
const GLSL = `
|
|
float sdSegment2(vec2 p, vec2 a, vec2 b) {
|
|
vec2 pa = p - a;
|
|
vec2 ba = b - a;
|
|
float h = clamp(dot(pa, ba) / max(dot(ba, ba), 0.0001), 0.0, 1.0);
|
|
return length(pa - ba * h);
|
|
}
|
|
|
|
vec2 dragonPoint(float i) {
|
|
float t = i / 72.0;
|
|
float a = t * 11.4 + sin(t * 31.0) * 0.28;
|
|
float r = size * (0.16 + 0.78 * sqrt(t));
|
|
vec2 q = vec2(cos(a), sin(a)) * r;
|
|
q += vec2(cos(a * 2.0 + 1.1), sin(a * 2.0 - 0.7)) * size * 0.10;
|
|
if (mod(floor(i / 6.0), 2.0) > 0.5) {
|
|
q = vec2(q.x - q.y, q.x + q.y) * 0.70710678118;
|
|
}
|
|
return q;
|
|
}
|
|
|
|
float dragonField(vec2 p) {
|
|
float d = 100000.0;
|
|
for (int i = 0; i < 72; i += 1) {
|
|
float fi = float(i);
|
|
d = min(d, sdSegment2(p, dragonPoint(fi), dragonPoint(fi + 1.0)));
|
|
}
|
|
return d;
|
|
}
|
|
|
|
float sdf(vec3 p) {
|
|
float ribbon = dragonField(p.xy) - ribbonWidth;
|
|
float slab = abs(p.z) - thickness * 0.5;
|
|
return max(ribbon, slab);
|
|
}
|
|
|
|
vec3 color(vec3 p, vec3 normal) {
|
|
float fold = 0.5 + 0.5 * sin(atan(p.y, p.x) * 5.0 + length(p.xy) * 0.16);
|
|
vec3 c = mix(baseColor, accentColor, fold);
|
|
return c * (0.58 + 0.42 * smoothstep(-0.2, 0.9, normal.z));
|
|
}
|
|
`;
|
|
|
|
export default {
|
|
schema: "implicit.js/0.1.0",
|
|
name: "Dragon curve ribbon",
|
|
description: "A recursively folded dragon-curve-inspired ribbon, thickened into a smooth printable strip.",
|
|
units: "mm",
|
|
params: {
|
|
size: { type: "number", label: "Fold size", min: 12, max: 34, step: 0.5, default: 24, unit: "mm" },
|
|
ribbonWidth: { type: "number", label: "Ribbon width", min: 0.45, max: 2.6, step: 0.05, default: 1.05, unit: "mm" },
|
|
thickness: { type: "number", label: "Thickness", min: 0.8, max: 4, step: 0.1, default: 1.55, unit: "mm" },
|
|
baseColor: { type: "color", label: "Inner color", default: "#5b8cff" },
|
|
accentColor: { type: "color", label: "Fold color", default: "#ff5c8a" }
|
|
},
|
|
animations: {
|
|
foldPulse: {
|
|
label: "Fold pulse",
|
|
duration: 5.8,
|
|
loop: true,
|
|
update({ progress, set }) {
|
|
const wave = Math.sin(progress * Math.PI * 2);
|
|
set("ribbonWidth", 1.05 + wave * 0.32);
|
|
set("thickness", 1.55 + (wave * 0.5 + 0.5) * 0.7);
|
|
}
|
|
}
|
|
},
|
|
bounds: ({ params }) => {
|
|
const extent = params.size * 1.25 + params.ribbonWidth + 6;
|
|
const z = params.thickness + 3;
|
|
return [[-extent, -extent, -z], [extent, extent, z]];
|
|
},
|
|
render: ({ params }) => ({
|
|
steps: 220,
|
|
stepScale: 0.64,
|
|
maxStep: Math.max(params.ribbonWidth * 3.2, 1.0),
|
|
epsilon: Math.max(params.ribbonWidth * 0.012, 0.004),
|
|
normalEpsilon: Math.max(params.ribbonWidth * 0.08, 0.032)
|
|
}),
|
|
glsl: GLSL
|
|
};
|