Skip to content

Instantly share code, notes, and snippets.

@GMartigny
Created May 26, 2020 11:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save GMartigny/026f171a103c3662abbae4ccded7a5b4 to your computer and use it in GitHub Desktop.
Save GMartigny/026f171a103c3662abbae4ccded7a5b4 to your computer and use it in GitHub Desktop.
Walk cycle looping gif
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Walk cycle</title>
</head>
<body>
<script type="module">
import gif from "https://unpkg.com/@pencil.js/gif/dist/gif.esm.js";
import { OffScreenCanvas, Container, Spline, Particles, Triangle, Color,
Position, Circle, Rectangle, Math as M } from "https://unpkg.com/pencil.js/dist/pencil.esm.js";
const scene = new OffScreenCanvas(200, 480, {
fill: "#b5dcff",
});
const nbFrames = 160;
class Pine extends Container {
constructor (position) {
super(position);
const radius = 50;
const nbSegment = 5;
const color = new Color("#0c3e02");
const parts = [...new Array(nbSegment)].map((_, i) => new Triangle([0, -radius * i * 0.5], radius * ((nbSegment - i) ** 0.5), {
fill: color.lightness(0.515).clone(),
}));
this.add(...parts);
}
}
class Member {
constructor (length) {
const extremity = new Position(10, length);
const bend = extremity.clone().divide(2);
this.length = length;
this.extremity = extremity;
this.bend = bend;
}
walk (target) {
const { extremity, bend, length } = this;
extremity.set(target);
// Overkill but whatev'
bend.set(
// Tug by feet
bend.clone()
.subtract(extremity)
.divide(bend.distance(extremity))
.multiply(length / 2)
.add(extremity),
);
bend.set(
// Tug by hip
bend.clone()
.divide(bend.length)
.multiply(length / 2),
);
}
}
class Stick extends Container {
constructor (position) {
super(position);
const legLength = 200;
this.legs = [
new Member(legLength),
new Member(legLength),
];
const proportion = 2.5 / 3.5;
this.arms = [
new Member(legLength * proportion),
new Member(legLength * proportion),
];
this.shoulders = new Container();
const skin = "#e5a002";
const head = new Circle([0, -60], 40, {
fill: skin,
});
const hands = this.arms.map(arm => new Circle(arm.extremity, 12, {
fill: skin,
}));
const arms = this.arms.map((pos, i) => new Spline(undefined, [pos.bend, pos.extremity], undefined, {
stroke: i ? "#028963" : "#0b5844",
strokeWidth: 18,
}));
this.shoulders.add(
head,
arms[0], hands[0],
arms[1], hands[1],
);
const feet = this.legs.map(pos => new Circle(pos.extremity, 13));
const legs = this.legs.map((pos, i) => new Spline(undefined, [pos.bend, pos.extremity], undefined, {
stroke: i ? "#9c0404" : "#660909",
strokeWidth: 26,
}));
this.add(
this.shoulders,
legs[0], feet[0],
legs[1], feet[1],
);
}
update (ratio) {
this.shoulders.position.set(Math.cos((ratio + 0.25) * M.radianCircle * 2) * 8).add(0, -150);
this.arms.forEach((arm, i) => {
const diffX = Math.sin((ratio + (0.5 * i) - 0.01) * M.radianCircle) * 45;
const diffY = Math.cos((ratio + (0.5 * i)) * M.radianCircle) * 10;
const target = new Position(arm.length * 0.1, arm.length * 0.8)
.add(diffX, -diffY);
arm.walk(target);
});
this.legs.forEach((leg, i) => {
const target = new Position(leg.length * 0.35, 0).rotate(ratio + (0.5 * i) + 0.25);
target.add(0, leg.length);
target.y = Math.min(target.y, leg.length * 0.95);
leg.walk(target);
});
return this;
}
}
const base = new Circle(undefined, 20, {
fill: "#fff",
opacity: 0.5,
})
const cloud = new Particles(undefined, base, 10, (i) => ({
position: new Position((M.phi * i * 5) % 60 + base.radius, (M.phi * i * 22) % 20),
}));
const pines = [...new Array(6)]
.map((_, i) => new Pine())
.sort((a, b) => a.position.y - b.position.y);
const stick = new Stick(scene.center.add(0, 30));
// Hack the joint in place
for (let i = 0; i < 20; ++i) {
stick.update(0);
}
const ground = new Rectangle([0, scene.height * 0.6], scene.width, scene.height, {
fill: "#538e28",
});
scene
.add(
ground,
cloud,
...pines,
stick,
)
.on("draw", () => {
const { width, height } = scene;
const ratio = scene.frameCount / nbFrames;
stick.update(ratio * 2);
cloud.position.set((1 - ratio) * width * 1.5 - 100, height * 0.2);
pines.forEach(({ position }, i) => {
// OMG
position.set(
M.modulo((1 - ratio) * (width * 2) - (i * (width * 3) / pines.length), width * 2) - 100,
scene.height * 0.6 + (M.phi * i * 32) % 60,
);
});
}, true);
console.log("Working ...");
console.time("gif");
gif(scene, nbFrames).then((img) => {
console.timeEnd("gif");
document.body.appendChild(img);
});
</script>
</body>
</html>
@GMartigny
Copy link
Author

walkcycle

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment