xxxxxxxxxx
208
// Created for the Barney Codes discord challenge
// Challenge #2: "Adaptation"
// Join the discord: https://discord.gg/Qnc3jWTeEW
// Testing out some Inverse Kinematics using the FABRIK technique
const DEBUG = false;
const numLegs = 4;
const numLimbs = 3;
const stepSize = 60;
const legs = [];
const targets = [];
const newTargets = [];
const lerpTargets = [];
let targetL;
let targetR;
let newTargetL;
let lerpTargetL;
let newTargetR;
let lerpTargetR;
let bodyPos;
const bodyWidth = 80;
const bodyHeight = 30;
function setup() {
createCanvas(640, 480);
createCreature();
}
function draw() {
updateCreature();
background("#2A584F");
drawTerrain(100);
drawCreature();
}
function updateCreature() {
const buff = 20;
const speed = 1.5;
if(bodyPos.x < mouseX - buff) {
bodyPos.x += speed;
} else if(bodyPos.x > mouseX + buff) {
bodyPos.x -= speed;
} else {
bodyPos.x = lerp(bodyPos.x, mouseX, 0.1);
}
bodyPos.x = constrain(bodyPos.x, 0, width);
bodyPos.y = constrain(bodyPos.y, height - numLimbs * 100, terrainHeightAt(bodyPos.x) - bodyHeight * 2);
for(let i = 0; i < numLegs; i ++) {
const stepLength = (i % 2 == 0 ? 1 : -1) * 10 + stepSize;
if(targets[i].dist(desired(i)) > stepLength) {
newTargets[i] = desired(i);
lerpTargets[i] = targets[i].copy();
}
lerpTargets[i] = p5.Vector.slerp(lerpTargets[i], newTargets[i], 0.1);
const d = lerpTargets[i].dist(newTargets[i]) / stepLength;
targets[i] = lerpTargets[i].copy().add(0, -stepHeight(d));
doIK(legs[i], anchor(i), targets[i]);
}
}
function drawCreature() {
push();
fill("#774448");
stroke("#2F142F");
strokeWeight(4);
rect(bodyPos.x - bodyWidth/2, bodyPos.y - bodyHeight/2, bodyWidth, bodyHeight);
for(let i = 0; i < numLegs; i ++) {
const leg = legs[i];
for(let j = 0; j < numLimbs; j ++) {
circle(leg[j].x, leg[j].y, 10);
if(j == 0) {
continue;
}
line(leg[j - 1].x, leg[j - 1].y, leg[j].x, leg[j].y);
}
if(DEBUG) {
push();
fill("#C6505A");
const d = desired(i);
circle(d.x, d.y, 10);
pop();
}
}
pop();
}
function drawTerrain(numPoints) {
push();
fill("#74A33F");
noStroke();
const stepSize = width/numPoints;
beginShape();
vertex(-stepSize, height);
vertex(-stepSize, terrainHeightAt(-stepSize));
let x = 0;
for(x; x <= width + stepSize; x += stepSize) {
const y = terrainHeightAt(x);
vertex(x, y);
}
vertex(x, height);
endShape(CLOSE);
pop();
}
function doIK(points, anchor, target) {
const MAX_IT = 10;
for(let i = 0; i < MAX_IT; i ++) {
if(i % 2 == 0) {
forwards(points, anchor, target);
} else {
backwards(points, anchor, target);
}
}
// always want to be connected to the anchor!
forwards(points, anchor, target);
}
function forwards(points, anchor, target) {
points[0] = anchor.copy();
for(let i = 0; i < numLimbs - 1; i ++) {
const curr = points[i];
const next = points[i + 1];
const diff = p5.Vector.sub(next, curr);
diff.setMag(limbLength(i));
points[i + 1] = p5.Vector.add(curr, diff);
}
}
function backwards(points, anchor, target) {
points[numLimbs - 1] = target.copy();
for(let i = numLimbs - 1; i > 0; i --) {
const curr = points[i];
const next = points[i - 1];
const diff = p5.Vector.sub(next, curr);
diff.setMag(limbLength(i-1));
points[i - 1] = p5.Vector.add(curr, diff);
}
}
function anchor(i) {
const offset = -bodyWidth/2 + (i * (bodyWidth/(numLegs - 1)));
return bodyPos.copy().add(offset, bodyHeight/2);
}
function desired(i) {
let spread = bodyWidth * 3;
let offset = i % 2 == 0 ? 43 : 0;
const x = bodyPos.x - spread/2 + (i * spread/(numLegs - 1)) + offset;
return createVector(x, terrainHeightAt(x));
}
function createCreature() {
bodyPos = createVector(width/2, height/2 + 20);
for(let i = 0; i < numLegs; i ++) {
targets.push(desired(i));
lerpTargets.push(desired(i));
newTargets.push(desired(i));
const leg = [];
for(let j = 0; j < numLimbs; j ++) {
leg.push(createVector(targets[i].x, bodyPos.y + j * limbLength(j)));
}
legs.push(leg);
}
}
function limbLength(i) {
return 100 - i * 20;//80 + i * 20;
}
function stepHeight(d) {
d = constrain(d, 0, 1);
return (1 - pow(2 * d - 1, 2)) * stepSize/4;
}
function terrainHeightAt(x) {
return (height * 1/32) * sin(x/50) + (height * 3/4);
}