Created
May 26, 2020 11:03
-
-
Save GMartigny/026f171a103c3662abbae4ccded7a5b4 to your computer and use it in GitHub Desktop.
Walk cycle looping gif
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
Author
GMartigny
commented
May 26, 2020
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment