const vec = p5.Vector; class Leg { constructor() { this.base = new vec(0, 0); this.end = new vec(0, 0); this.interPos = new vec(0, 0); this.ikJoint = new IKJoint(4, 47); this.repositionDist = 90; this.moveSpeed = 0.4; } moveTo(startX, startY, endX, endY) { this.base = new vec(startX, startY); let target = new vec(endX, endY); if (vec.dist(target, this.end) > this.repositionDist) { this.end = new vec(endX, endY); } this.interPos.lerp(this.end, this.moveSpeed); this.ikJoint.moveTo(startX, startY, this.interPos.x, this.interPos.y); } getPoints() { return this.ikJoint.points; } } class IKJoint { constructor(numSegments, pointDist) { this.origin = new vec(0, 0); this.points = []; for (let i = 0; i < numSegments + 1; i++) { this.points.push(new ConstraintPoint(0, 0, pointDist, 7)); } this.pointDist = pointDist; this.iterations = 20; } moveStart(targetX, targetY) { let numPts = this.points.length; this.points[0].size = 1; this.points[0].follow(targetX, targetY); for (let i = 1; i < numPts; i++) { let prevPoint = this.points[i - 1]; this.points[i].follow(prevPoint.pos.x, prevPoint.pos.y, prevPoint.forward); } } moveEnd(targetX, targetY) { let numPts = this.points.length; this.points[numPts - 1].pos.x = targetX; this.points[numPts - 1].pos.y = targetY; for (let i = numPts - 2; i >= 0; i--) { let prevPoint = this.points[i + 1]; this.points[i].follow(prevPoint.pos.x, prevPoint.pos.y, null); } } moveTo(origX, origY, targetX, targetY) { for (let i = 0; i < this.iterations; i++) { this.moveEnd(targetX, targetY); this.moveStart(origX, origY); } } } class ConstraintPoint { constructor(x, y, size, angleConstraint = 0.2) { this.size = size; this.pos = new vec(x, y); this.forward = new vec(0, 0); this.angleConstraint = angleConstraint; } getRel(angleOffset, dist) { let dir = vec.rotate(this.forward, angleOffset); dir.setMag(dist); return vec.add(this.pos, dir); } follow(targetX, targetY, parentForward = null) { let target = new vec(targetX, targetY); let targetForward = vec.sub(target, this.pos); let targetAngle = targetForward.heading(); if (parentForward) { let parentAngle = parentForward.heading(); let diff = targetAngle - parentAngle; while (diff < -PI) diff += TWO_PI; while (diff > PI) diff -= TWO_PI; let maxAngle = this.angleConstraint; diff = constrain(diff, -maxAngle, maxAngle); targetAngle = parentAngle + diff; } this.forward = p5.Vector.fromAngle(targetAngle); let dir = p5.Vector.mult(this.forward, -this.size); this.pos = vec.add(target, dir); } } function drawPoints(points, sizes, turnSegments = 10) { beginShape(); let numPoints = points.length; let head = points[0]; let tail = points[numPoints - 1]; for (let angle = -HALF_PI; angle <= HALF_PI; angle += PI / turnSegments) { let p = head.getRel(angle, sizes[0]); curveVertex(p.x, p.y); } for (let i = 1; i < numPoints - 1; i++) { let p = points[i].getRel(HALF_PI, sizes[i]); curveVertex(p.x, p.y); } for (let angle = HALF_PI; angle <= HALF_PI + PI; angle += PI / turnSegments) { let p = tail.getRel(angle, sizes[numPoints - 1]); curveVertex(p.x, p.y); } for (let i = numPoints - 2; i >= 1; i--) { let p = points[i].getRel(-HALF_PI, sizes[i]); curveVertex(p.x, p.y); } endShape(CLOSE); } let numPoints = 0; const bodySizes = [18, 22, 25, 22, 14, 30, 35, 40, 35, 30]; const legSizes = [6, 5, 4, 3, 2, 2, 1, 1, 1]; const bodyPoints = []; const legs = []; let mouseInter = new vec(0, 0); function setup() { createCanvas(displayWidth, displayHeight); numPoints = bodySizes.length; for (let i = 0; i < numPoints; i++) { let pt = new ConstraintPoint(400, 400, 15); bodyPoints.push(pt); } for (let i = 0; i < 8; i++) { legs.push(new Leg()); } } function draw() { background(180); mouseInter.lerp(new vec(mouseX, mouseY), 0.05); let time = millis(); bodyPoints[0].follow(mouseInter.x, mouseInter.y); for (let i = 1; i < numPoints; i++) { let prevPoint = bodyPoints[i - 1]; bodyPoints[i].follow(prevPoint.pos.x, prevPoint.pos.y, prevPoint.forward); } let legAngles = [ -PI / 3, PI / 3, -PI / 1.8, PI / 1.8, -PI * 0.7, PI * 0.7, -PI * 0.85, PI * 0.85 ]; let legAttachmentIndices = [1, 1, 2, 2, 3, 3, 4, 4]; for (let i = 0; i < 8; i++) { let baseJoint = bodyPoints[legAttachmentIndices[i]]; let bodyRadius = bodySizes[legAttachmentIndices[i]]; let basePos = baseJoint.getRel(legAngles[i], bodyRadius * 0.6); let reach = 120; let endPos = baseJoint.getRel(legAngles[i], reach); legs[i].moveTo(basePos.x, basePos.y, endPos.x, endPos.y); } fill(30); stroke(10); strokeWeight(1); for (let i = 0; i < 8; i++) { drawPoints(legs[i].getPoints(), legSizes); } let currentBodySizes = [...bodySizes]; let breathSpeed = time * 0.005; for (let i = 0; i < numPoints; i++) { if (i > 4) { let breathExpansion = sin(breathSpeed - i * 0.2) * 2.5; currentBodySizes[i] += breathExpansion; } } drawPoints(bodyPoints, currentBodySizes); let eye1 = bodyPoints[0].getRel(PI / 5, 12); let eye2 = bodyPoints[0].getRel(-PI / 5, 12); let eye3 = bodyPoints[0].getRel(PI / 2.5, 15); let eye4 = bodyPoints[0].getRel(-PI / 2.5, 15); fill(220, 20, 20); noStroke(); circle(eye1.x, eye1.y, 8); circle(eye2.x, eye2.y, 8); circle(eye3.x, eye3.y, 4); circle(eye4.x, eye4.y, 4); }