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.
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)
Browse more articles?
← Ninja circle Experiments tagged p5 (60/85) Dancing Cairo circles →
← Ninja circle Experiments tagged interactive (20/26) De Rahm curves →
|← Experiments tagged mathober (1/1) →|
← Ninja circle Experiments on this site (60/85) Dancing Cairo circles →