Star Power
Reach out into space. Your hands must be visible in the camera frame. ML models are loaded via a third party website.
This sketch needs permission to use your camera.
Source Code for this Sketch
class StepToHand {
x = 0
y = 0
static def(input, defaultValue) {
if (typeof input === "number") {
return input
}
return defaultValue
}
constructor(step, minX, minY, maxX, maxY) {
// step determines how quickly we orient towards
// the hand position. Each frame, we move at
// most this many units in any direction.
this.step = StepToHand.def(step, 3)
// Set the bounds for valid input values
this.minX = StepToHand.def(minX, 0)
this.minY = StepToHand.def(minY, 0)
this.maxX = StepToHand.def(maxX, width)
this.maxY = StepToHand.def(maxY, height)
}
update(x, y) {
let deltaX = x-this.x
let deltaY = y-this.y
// Calculate the hypotenuse of our delta triangle
// so we can limit angular movement by this.step
let c = sqrt(pow(deltaX, 2)+pow(deltaY, 2))
// Multiply our x & y movement by a ratio of their
// length to the hypotenuse. For lateral or
// horizonal movement we'll have 1:0 or 0:1.
let ratioX = abs(deltaX) / c || 0
let ratioY = abs(deltaY) / c || 0
this.x += max(min(deltaX, this.step), -this.step)*ratioX
this.y += max(min(deltaY, this.step), -this.step)*ratioY
// Constrain x and y to valid area
this.x = min(this.x, this.maxX)
this.x = max(this.x, this.minX)
this.y = min(this.y, this.maxY)
this.y = max(this.y, this.minY)
}
}
class Timer{
constructor(delay) {
this.count = 0
this.delta = 0
this.delay = delay
}
update() {
this.delta += deltaTime
if(this.delta > this.delay) {
this.count++
this.delta %= this.delay
}
}
}
class StepToMouse{
// x and y are measured from the canvas origin (top left)
x = 0
y = 0
// cx and cy are centered coordinates; i.e. 0,0 is
// at the midpoint in the canvas
cx = 0
cy = 0
static def(input, defaultValue) {
if (typeof input === "number") {
return input
}
return defaultValue
}
constructor(step, minX, minY, maxX, maxY) {
// step determines how quickly we orient towards
// the mouse position. Each frame, we move at
// most this many units in any direction.
this.step = StepToMouse.def(step, 3)
// Set the bounds for valid input values
this.minX = StepToMouse.def(minX, 0)
this.minY = StepToMouse.def(minY, 0)
this.maxX = StepToMouse.def(maxX, width)
this.maxY = StepToMouse.def(maxY, height)
}
update() {
let deltaX = mouseX-this.x
let deltaY = mouseY-this.y
// Calculate the hypotenuse of our delta triangle
// so we can limit angular movement by this.step
let c = sqrt(pow(deltaX, 2)+pow(deltaY, 2))
// Multiply our x & y movement by a ratio of their
// length to the hypotenuse. For lateral or
// horizonal movement we'll have 1:0 or 0:1.
let ratioX = abs(deltaX) / c || 0
let ratioY = abs(deltaY) / c || 0
this.x += max(min(deltaX, this.step), -this.step)*ratioX
this.y += max(min(deltaY, this.step), -this.step)*ratioY
// Constrain x and y to valid area
this.x = min(this.x, this.maxX)
this.x = max(this.x, this.minX)
this.y = min(this.y, this.maxY)
this.y = max(this.y, this.minY)
this.cx = this.x - width/2
this.cy = this.y - height/2
}
}
class Star {
static minSize = 3
static maxSize = 6
// speedFactor controls how quickly stars approach
static speedFactor = 1
constructor() {
this.reset()
// More evenly distribute stars in their initial
// starting positions
this.r = pow(random(0, sqrt(this.limit)), 1.8)
}
reset() {
this.r = 0
this.x = 0
this.y = 0
this.speed = sqrt(random(.1, 8)) * Star.speedFactor
this.size = random(Star.minSize, Star.maxSize)
this.deg = random(0, 360)
this.hue = random(0, 360)
this.limit = max(width, height)
}
// multiplier returns a ratio of this.r to this.limit
// with the given minimum value. min should be between
// 0 and 1
multiplier(min) {
if (typeof min !== 'number') {
min = 0
}
return min + (this.r / this.limit * (1 - min))
}
draw() {
// Position from center
this.r += this.speed * this.multiplier(.2)
fill(this.hue, 70, 90)
this.x = cos(this.deg - 90) * this.r * width / this.limit
this.y = sin(this.deg - 90) * this.r * height / this.limit
circle(this.x, this.y, this.size * this.multiplier())
// detect when stars are outside the visible area and
// reset. note: assumes 0,0 coordinates are centered
if (this.x < -(width + this.size) ||
this.x > (width + this.size)) {
this.reset()
}
if (this.y < -(height + this.size) *.75 ||
this.y > (height + this.size) *.75) {
this.reset()
}
}
}
class Alien{
constructor() {
this.lights = new Timer(250)
}
radX(deg, r) {
return cos(deg-90)*r
}
radY(deg, r) {
return sin(deg-90)*r
}
eye(x, y) {
let deg = round(abs(atan2(mouseX-width/2, mouseY-height/2+30)-180))
// Stalks
stroke(80, 100, 50)
noFill()
bezier(x, 0, x+this.radX(deg, 5), this.radY(deg, 10), x-this.radX(deg, 15), y-this.radY(deg, 20), x, y)
// Eyeballs
noStroke()
fill(80, 100, 50)
circle(x+this.radX(deg, 2), y+this.radY(deg, 2), 10)
fill( 100)
circle(x+this.radX(deg, 3), y+this.radY(deg, 3), 6)
fill( 0)
circle(x+this.radX(deg, 4), y+this.radY(deg, 4), 3)
}
draw() {
this.lights.update()
push()
rectMode(CENTER)
// Position the ship
translate(width/2, height*.9)
// Cockpit
strokeWeight(1)
stroke(200, 100, 50, .7)
fill(200, 100, 50, .3)
arc(0, -10, 100, 100, 170, 10)
// Pilot
let alienElevation = 16
noStroke()
fill(80, 100, 50)
arc(0, -alienElevation, 40, 20, 180, 0)
rect(0, -alienElevation, 40, 5, 0, 0, 5, 5)
this.eye(-10, -45)
this.eye(10, -45)
// Hull
strokeWeight(1)
stroke(20)
fill(40)
for(let i = 0; i < 6; i++) {
ellipse(0, 0, 120-i*20, 30)
}
rect(0, 0, 120, 10, 5, 5, 5, 5)
// Hull lights
for(let i = 0; i < 11; i++) {
if(i%4 === this.lights.count) {
fill(65, 100, 50)
} else if ((i+1)%4 === this.lights.count) {
fill(310, 100, 50)
} else {
fill(200, 100, 50)
}
circle(-50+i*10, 0, 5)
if(this.lights.count >=4) {
this.lights.count = 0
}
}
pop()
}
}
let stars = []
let alien
let handpose
let video
let hands = []
let leftHand
let rightHand
function preload() {
handpose = ml5.handPose()
}
function setup() {
createCanvas(640, 400, P2D, canvas)
colorMode(HSL)
angleMode(DEGREES)
ellipseMode(CENTER)
noStroke()
for (const x of Array(100).keys()) {
stars.push(new Star())
}
alien = new Alien()
video = createCapture(VIDEO)
// mirror video for selfie purposes
video.flipped = true
video.size(640, 480)
// video.hide()
handpose.detectStart(video, (e) => {
hands = e
})
fill(0, 100, 50);
noStroke();
leftHand = new StepToHand(30)
leftHand.x = width*.25
leftHand.y = height/2
rightHand = new StepToHand(30)
rightHand.x = width*.75
rightHand.y = height/2
}
function draw() {
background(0)
push()
translate(width/2, height/2)
noStroke()
stars.map((star) => { star.draw() })
pop()
noStroke()
for (let i = 0; i < hands.length; i++) {
let c = color(0)
// Note: handedness seems to be flipped in the model.
if (hands[i].handedness === "Left") {
c = color(0, 100, 50)
}
if (hands[i].handedness === "Right") {
c = color(240, 100, 50)
}
fill(c)
let averageX = 0
let countX = 0
let averageY = 0
let countY = 0
for(let j = 0; j < hands[i].keypoints.length; j++) {
let keypoint = hands[i].keypoints[j]
// ignore the wrist and thumb to make gestures
// a bit more responsive
if (keypoint.name.indexOf("wrist") >= 0 ||
keypoint.name.indexOf("thumb") >= 0) {
continue
}
averageX += (width-keypoint.x)
averageY += (keypoint.y)
countX ++
countY ++
}
// Note: handedness seems to be flipped in the model.
if (hands[i].handedness === "Left") {
rightHand.update(averageX/countX, averageY/countY)
}
if (hands[i].handedness === "Right") {
leftHand.update(averageX/countX, averageY/countY)
}
}
noFill()
strokeWeight(8)
stroke(80, 100, 50)
bezier(width/2, height*.9, width/2-150, height*.9, leftHand.x, leftHand.y+150, leftHand.x+24, leftHand.y +10)
bezier(width/2, height*.9, width/2-150, height*.9, leftHand.x, leftHand.y+150, leftHand.x+12, leftHand.y +2)
bezier(width/2, height*.9, width/2-150, height*.9, leftHand.x, leftHand.y+150, leftHand.x, leftHand.y)
bezier(width/2, height*.9, width/2-150, height*.9, leftHand.x, leftHand.y+150, leftHand.x-12, leftHand.y +3)
bezier(width/2, height*.9, width/2-150, height*.9, leftHand.x, leftHand.y+150, leftHand.x-24, leftHand.y+6)
bezier(width/2, height*.9, width/2+150, height*.9, rightHand.x, rightHand.y+150, rightHand.x+24, rightHand.y+6)
bezier(width/2, height*.9, width/2+150, height*.9, rightHand.x, rightHand.y+150, rightHand.x+12, rightHand.y+3)
bezier(width/2, height*.9, width/2+150, height*.9, rightHand.x, rightHand.y+150, rightHand.x, rightHand.y)
bezier(width/2, height*.9, width/2+150, height*.9, rightHand.x, rightHand.y+150, rightHand.x-12, rightHand.y +2)
bezier(width/2, height*.9, width/2+150, height*.9, rightHand.x, rightHand.y+150, rightHand.x-24, rightHand.y+10)
alien.draw(width/2, height/5)
}
You may use all or part of the p5 code seen on this page for your own creations, without restriction. Attribution is appreciated but not required.
This sketch also includes the following sources: p5common.js ml5.js . Sources are available under their respective licenses.