Skip to content

Instantly share code, notes, and snippets.

@meganetaaan
Last active September 23, 2022 15:19
Show Gist options
  • Save meganetaaan/e2fd2bb77ef5210f0e4b9ab3606edb5f to your computer and use it in GitHub Desktop.
Save meganetaaan/e2fd2bb77ef5210f0e4b9ab3606edb5f to your computer and use it in GitHub Desktop.
Avatar drawing
import { Outline } from "commodetto/outline";
const useBlink = (openMin, openMax, closeMin, closeMax) => {
let eyeOpen = 1
let nextToggle = randomBetween(openMin, openMax)
return (tickMillis) => {
nextToggle -= tickMillis
if (nextToggle < 0) {
eyeOpen = Number(!eyeOpen)
nextToggle = eyeOpen ? randomBetween(openMin, openMax) : randomBetween(closeMin, closeMax)
}
return eyeOpen
}
}
const useDrawEye = (cx, cy, radius = 8) => (path, eyeContext) => {
let openRatio = eyeContext.open
let offsetX = eyeContext.gazeX ?? 0
let offsetY = eyeContext.gazeY ?? 0
if (openRatio < 0.3) {
// closed
let w = radius * 2
let h = Math.min(4, radius / 2)
let x = cx - w / 2
let y = cy - h / 2
path.rect(x, y, w, h);
} else {
// open
path.arc(cx + offsetX, cy + offsetY, radius, 0, 2 * Math.PI);
}
}
const useDrawMouth = (cx, cy, minWidth = 50, maxWidth = 90, minHeight = 4, maxHeight = 60) => (path, mouthContext) => {
let openRatio = mouthContext.open
let h = minHeight + (maxHeight - minHeight) * openRatio
let w = minWidth + (maxWidth - minWidth) * (1 - openRatio)
let x = cx - w / 2
let y = cy - h / 2
path.rect(x, y, w, h)
}
const Emotion = {
NEUTRAL: 'NEUTRAL'
}
function randomBetween(min, max) {
return min + (max - min) * Math.random();
}
export default class extends Behavior {
drawFace(faceContext) {
let fillPath = new Outline.CanvasPath;
let strokePath = new Outline.CanvasPath;
this.drawLeftEye(fillPath, faceContext.eyes.left);
this.drawRightEye(fillPath, faceContext.eyes.right);
this.drawMouth(fillPath, faceContext.mouth)
return [fillPath, strokePath]
}
onCreate(shape) {
shape.duration = 6000;
shape.interval = 33;
this.eyeOpen = 1;
this.cx = application.width >> 1;
this.cy = application.height >> 1;
this.drawLeftEye = useDrawEye(90, 93, 8)
this.drawRightEye = useDrawEye(230, 96, 8)
this.drawMouth = useDrawMouth(160, 148)
this.updateBlink = useBlink(400, 5000, 100, 400)
}
onFinished(shape) {
shape.time = 0;
shape.start();
}
onTimeChanged(shape) {
let f = shape.fraction * 4;
f = f * 2 * Math.PI;
const gain = Math.abs(Math.cos(f))
const gain2 = Math.sin(f)
let eyeOpen = this.updateBlink(shape.interval)
const faceContext = {
mouth: {
open: gain,
},
eyes: {
left: {
open: eyeOpen,
gazeX: gain2,
gazeY: gain2,
},
right: {
open: eyeOpen,
gazeX: gain2,
gazeY: gain2,
}
},
breath: gain,
emotion: Emotion.NEUTRAL,
theme: {
primary: 'white',
secondary: 'black'
}
}
const [fillPath, strokePath] = this.drawFace(faceContext)
shape.bubble("onLabel", `avatar face`);
shape.fillOutline = Outline.fill(fillPath)
.translate(gain2 * -8, gain * 4 + faceContext.breath * 3 ?? 0)
.rotate(gain2 * Math.PI / 16, this.cx, this.cy);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment