← sketches

string art

Lines stretched between two hidden parametric curves.

p5.js

  import p5 from 'p5';
  import { hsvToRgb } from '../../../utils/color';

  new p5((p: p5) => {
    const c = controls({
      length: { label: 'length', min: 1, max: 100, value: 100, format: (v) => `${Math.round(v)}%` },
      offset: { label: 'offset', min: 1, max: 200, rawValue: 40, value: 0.02, parse: (r) => parseInt(r) / 1000, format: (v) => v.toFixed(3) },
      weight: { label: 'weight', min: 1, max: 50, rawValue: 8, value: 0.8, parse: (r) => parseInt(r) / 10, format: (v) => v.toFixed(1) },
      opacity: { label: 'opacity', min: 1, max: 255, value: 50 },
      speed: { label: 'speed', min: 1, max: 30, rawValue: 6, value: 0.006, parse: (r) => parseInt(r) / 1000, format: (v) => v.toFixed(3) },
      freqa: { label: 'curve A', min: 1, max: 8, value: 3 },
      freqb: { label: 'curve B', min: 1, max: 8, value: 5 },
      curvea: { type: 'checkbox', label: 'show curve A', checked: false },
      curveb: { type: 'checkbox', label: 'show curve B', checked: false },
    });

    function curveA(t: number, time: number): [number, number] {
      const cx = p.width / 2;
      const cy = p.height / 2;
      const r = Math.min(p.width, p.height) * 0.42;
      const x = cx + Math.sin(c.freqa * t + time) * r * Math.cos(t + time * 0.3);
      const y = cy + Math.cos(c.freqa * t - time * 0.7) * r * Math.sin(t - time * 0.2);
      return [x, y];
    }

    function curveB(t: number, time: number): [number, number] {
      const cx = p.width / 2;
      const cy = p.height / 2;
      const r = Math.min(p.width, p.height) * 0.38;
      const x = cx + Math.cos(c.freqb * t - time * 0.5) * r * Math.sin(t * 0.7 + time * 0.4);
      const y = cy + Math.sin(c.freqb * t + time * 0.3) * r * Math.cos(t * 0.7 - time * 0.6);
      return [x, y];
    }

    p.setup = () => {
      const el = document.getElementById('canvas')!.parentElement!;
      const canvas = p.createCanvas(el.clientWidth, el.clientHeight);
      canvas.parent(el);
      document.getElementById('canvas')?.remove();
    };

    p.draw = () => {
      p.background(0);
      const time = p.frameCount * c.speed;

      const arcLen = (c.length / 100) * Math.PI * 2;
      const lineCount = Math.max(1, Math.floor(arcLen / c.offset));

      for (let i = 0; i < lineCount; i++) {
        const t = i * c.offset;

        const [ax, ay] = curveA(t, time);
        const [bx, by] = curveB(t + c.offset, time);

        const h = ((i / lineCount) * 360 + p.frameCount * 0.5) % 360;
        const [r, g, b] = hsvToRgb(h / 360, 0.6, 0.85);
        p.stroke(r, g, b, c.opacity);
        p.strokeWeight(c.weight);
        p.line(ax, ay, bx, by);
      }

      const fullArc = Math.PI * 2;
      const steps = 200;

      if (c.curvea) {
        p.noFill();
        p.strokeWeight(1.5);
        p.stroke(255, 80, 80);
        p.beginShape();
        for (let i = 0; i <= steps; i++) {
          const [x, y] = curveA((i / steps) * fullArc, time);
          p.vertex(x, y);
        }
        p.endShape();
      }

      if (c.curveb) {
        p.noFill();
        p.strokeWeight(1.5);
        p.stroke(80, 140, 255);
        p.beginShape();
        for (let i = 0; i <= steps; i++) {
          const [x, y] = curveB((i / steps) * fullArc + c.offset, time);
          p.vertex(x, y);
        }
        p.endShape();
      }
    };


  });