let tapeBottomCorners = []
let baseTapeColor, shinyTapeColor
createCanvas(600, 600, WEBGL)
baseTapeColor = color('#111')
shinyTapeColor = color('#CCC')
return createVector(0, 80).mult(obj.mass);
const offAng = 0.0008 * obj.position.x + 5*cos(0.0009 * obj.position.y)
const offMag = sin(0.0005 * obj.position.x) + 10*cos(0.002 * obj.position.y)
return p5.Vector.fromAngle(
20 * (sin(millis() / 5000 + offAng) + 0.5 * sin(millis() / 8000 + 100 + offAng)),
150 * cos(millis() / 5000 + offMag) - 15
function springForce(kSpring, kDamper, target) {
const fSpring = target().copy().sub(obj.position).mult(kSpring)
const fDamper = obj.vel.copy().mult(-kDamper)
return fSpring.add(fDamper)
{ position: createVector(-100, -20) },
{ position: createVector(100, -20) },
{ position: createVector(-100, 20) },
{ position: createVector(100, 20) },
const addTape = (pins) => {
pins.sort((a, b) => a.position.x - b.position.x)
const xPositions = pins.map((p) => p.position.x)
const yPositions = pins.map((p) => p.position.y)
const tapeOffset = createVector(0, 0, 12)
const tapeTangent = tapeOffset.copy().normalize()
for (let i = 0; i <= xPositions.length - 1; i += 0.03) {
lerp(xPositions[a], xPositions[b], mix),
lerp(yPositions[a], yPositions[b], mix),
0.8*(cos(i*12) + 0.5*cos(pow(i*12,2))),
-abs(sin(i*8) + 0.5*sin(pow(i*8,2))),
const last = links[links.length - 1]
first.constraints.push(() => {
const target = pins[0].position
first.position.x = target.x
first.position.y = target.y
last.constraints.push(() => {
const target = pins[1].position
last.position.x = target.x
last.position.y = target.y
for (let i = 1; i < links.length; i++) {
const prev = links[i - 1]
const nextNext = links[i+1]
const dir = prev.position.copy().sub(next.position)
prev.forces.push(springForce(20*95, 5*25, () => {
const off = prev.position.copy().sub(next.position).normalize().mult(dist)
return next.position.copy().add(off)
next.forces.push(springForce(20*95, 5*25, () => {
const off = next.position.copy().sub(prev.position).normalize().mult(dist)
return prev.position.copy().add(off)
const tangent = next.position.copy().sub(prev.position).normalize()
specularMaterial(baseTapeColor)
let nextTangent = tangent
nextTangent = nextNext.position.copy().sub(next.position).normalize()
const normal1 = tangent.cross(tapeTangent).mult(-1)
const normal2 = nextTangent.cross(tapeTangent).mult(-1)
beginShape(TRIANGLE_STRIP)
normal(normal1.x, normal1.y, normal1.z)
vertex(prev.position.x, prev.position.y, prev.position.z)
vertex(prev.position.x+tapeOffset.x, prev.position.y+tapeOffset.y, prev.position.z+tapeOffset.z)
normal(normal2.x, normal2.y, normal2.z)
vertex(next.position.x, next.position.y, next.position.z)
vertex(next.position.x+tapeOffset.x, next.position.y+tapeOffset.y, next.position.z+tapeOffset.z)
addTape(tapeTopCorners.slice())
addTape(tapeBottomCorners.slice())
pointLight(100, 100, 100, -width/2, -200, -width/2)
pointLight(100, 100, 100, -width/2, 200, -width/2)
pointLight(100, 100, 100, width/3, -400, width/2)
translate(0, -height / 8)
-150 + 50*sin(millis()*0.001),
50*sin(millis()*0.0015+10)
150 + 50*sin(millis()*0.0012 + 20),
50*sin(millis()*0.0008+30)
sin(millis() * 0.002 - PI/2) * PI/4,
sin(millis() * 0.0022 + 40) * PI/4
tapeTopCorners.forEach((corner, i) => {
corner.position = positions[i].copy().add(
p5.Vector.fromAngle(rotations[i], -r)
tapeBottomCorners.forEach((corner, i) => {
corner.position = positions[i].copy().add(
p5.Vector.fromAngle(rotations[i], r)
for (let i = 0; i < stepsPerFrame; i++) {
for (const obj of objects) {
const accel = obj.forces.reduce((acc, next) => acc.add(next(obj)), createVector(0, 0))
obj.vel.add(accel.mult(dt))
for (const obj of objects) {
obj.position.add(obj.vel.copy().mult(dt));
obj.constraints.forEach((applyConstraint) => applyConstraint(obj))
for (const obj of objects) {
if (obj.draw) obj.draw(obj)
positions.forEach((position, i) => {
translate(position.x, position.y, position.z + 5)
translate(0, -1.5 * r, 0)
box(2.7 * r, 2.7 * r, 22)
translate(0, r * 0.25, 0)