Skip to content

Catenary chain

This (somewhat heavy) sketch was made for Mathober 2025, day 1: "Link".

My first real use of Verlet Integration!

Move your mouse to interact with (and maybe break) the chain.

A hanging chain forms a catenary curve, as it casts a shadow on the green wall behind

let canvas
function setup() {
  canvas = createCanvas(500, 500);
  sliders = {
    k: label("Spring K", createSlider(0, 22, 19, 0.2)),
    l: label("Spring rest length", createSlider(0.04, 4, 2, 0.01)),
    b: label("Break distance", createSlider(0, 60, 12)),
    m: label("Mouse force", createSlider(0, 250, 100)),
    g: label("Gravity", createSlider(0, 5, 2, 0.1)),
    e1: label("First Loop", createSlider(0, 200, 70)),
    e2: label("Second Loop", createSlider(0, 200, 50)),
    f1: label("Fixed pos", createSlider(50, 450, 200)),
  }
  let r = createButton("Reset")
  r.parent(canvas.parent())
  r.mouseClicked(reset)
  sliders.e1.input(() => nb = 10)
  sliders.e2.input(() => nb = 10)
  sliders.f1.input(() => nb = 10)
  reset()
}

function label(txt, forFn) {
  let div = createDiv()
  div.parent(canvas.parent())
  div.class("field")
  let label = createElement("label", txt)
  let el = (typeof forFn == "function" ? forFn(label, div) : forFn)
  div.child(label)
  div.child(el)
  el.id(el.id() || Math.random().toString(36).substring(2))
  label.attribute("for", el.id())
  return el
}

let c
let br = {}
let nb = 0
let sliders = {}


function reset() {
  c = Array(200).fill().map((_, i) => Array(2).fill([250-Math.abs(i-100)*2, 100]))
  br = {}
  nb = 10
}

function draw() {
  let conf = Object.fromEntries(Object.entries(sliders).map((x) => [x[0], x[1].value()]))
  let e = {
    [-1]: [50, 100],
    [conf.e1]: [conf.f1, 100],
    [conf.e1 + conf.e2]: [450, 100],
    [200]: [50, 100],
  }
  let m = [[mouseX, mouseY], [pmouseX, pmouseY]]
  background(230, 245, 240);
  strokeWeight(1.75)
  for (let s = 0; s < 90; s ++) {
    let dt = 0.15
    for (let p of c) {
      for (let j = 0; j < 2; j++) {
        p[0][j] = p[0][j] * 2 - p[1][j]
      }
      p[0][1] += conf.g * dt * dt
    }
    for (let i = 0; i < c.length; i ++) {
      for (let k of [-1, 1]) {
        if (!br[i * 2 + k]) {
          let next = e[i+k] || c[i + k][1]
          let d = dist(...c[i][1], ...next)
          d = max(d, 0.001)
          let f = (conf.l - d) * conf.k
          if (d > conf.b && nb < 0) {
            br[i * 2 + k] = true
          }
          for (let j = 0; j < 2; j++) {
            c[i][0][j] += (c[i][1][j] - next[j]) / d * f * dt * dt
          }
        }
      }
      {
        let dSq = distSq(...c[i][1], ...m[0])
        dSq = max(dSq, 64)
        for (let j = 0; j < 2; j++) {
          let f = (m[0][j] - m[1][j]) * conf.m / dSq
          c[i][0][j] += f * dt * dt
        }
      }
    }
    for (let p of c) {
      p[1] = p[0].slice()
    }
  }
  let cf = Array(c.length).fill(20)
  let b = false
  for (let i = 0; i < c.length; i ++) {
    if (br[i * 2 - 1]) {
      b = true
    }
    if (e[i]) {
      b = false
      cf[i] = 0
    }
    if (!b) {
      cf[i] = min(cf[i], (cf[i - 1] || 0) + 1)
    }
  }
  for (let i = c.length - 1; i >= 0; i --) {
    if (br[i * 2 + 1]) {
      b = true
    }
    if (e[i]) {
      b = false
      cf[i] = 0
    }
    if (!b) {
      cf[i] = min(cf[i], (cf[i + 1] || 0) + 1)
    }
  }
  for (let i = -1; i < c.length; i ++) {
    let curr = e[i] || c[i][1]
    let next = e[i + 1] || c[i + 1][1]
    if (!br[i * 2 + 1]) {
      let s = sqrt(cf[i] || 0)
      stroke(100)
      line(curr[0] + s, curr[1] + s, next[0] + s, next[1] + s)
    }
  }
  for (let i = -1; i < c.length; i ++) {
    let curr = e[i] || c[i][1]
    let next = e[i + 1] || c[i + 1][1]
    if (!br[i * 2 + 1]) {
      let d = max(dist(...curr, ...next), 0.1)
      stroke(map(abs(conf.l - d) ** 0.5, 0, conf.l + conf.b ** 0.5, 0, 280), 0, 0)
      line(...curr, ...next)
    } else {
      point(...curr)
      point(...next)
    }
  }
  nb --
  
}
function distSq(a,b,c,d) {
  return (c-a) ** 2 + (d-b) ** 2
}

(Originally seen at https://editor.p5js.org/bojidar-bg/sketches/fD6DS2tR7 and https://mastodon.social/@bojidar_bg/115300919055436822)

Experiments tagged p5 (60/85)

Experiments tagged interactive (20/26)

|← Experiments tagged mathober (1/1) →|

Experiments on this site (60/85)