diff --git a/spider.js b/spider.js new file mode 100644 index 0000000..928cf03 --- /dev/null +++ b/spider.js @@ -0,0 +1,225 @@ +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); +}