xxxxxxxxxx
267
let video;
let handPose;
let hands = [];
let fingerX = 0;
let fingerY = 0;
// Boids and quadtree variables
let qtree;
let boids = [];
const BOID_COUNT = 1000;
const VIEW_RADIUS = 20;
const QUAD_CAPACITY = 2;
function preload() {
handPose = ml5.handPose({ flipped: true });
}
function gotHands(results) {
hands = results;
}
function setup() {
// Canvas height is 2x video height for equal split
createCanvas(640, 480 * 2);
// Setup video and hand tracking
video = createCapture(VIDEO, { flipped: true });
video.size(640, 480);
video.hide();
handPose.detectStart(video, gotHands);
// Setup boids in bottom half
for (let i = 0; i < BOID_COUNT; i++) {
// Initialize in the bottom half section
let x = random(width);
let y = random(height/2, height);
boids.push(new Boid(x, y));
}
}
function draw() {
// Calculate background opacity based on finger position
let bgOpacity = 10; // default opacity when no hand is detected
if (hands.length > 0) {
let hand = hands[0];
let index = hand.index_finger_tip;
fingerX = index.x;
fingerY = index.y;
// Map finger X position to opacity range (10-200)
bgOpacity = map(fingerX, 0, width, 10, 200);
}
// Set background with opacity
background(0, bgOpacity);
// Draw video in top third
image(video, 0, 0);
// Update finger position and draw green dot
if (hands.length > 0) {
// Draw green dot at index finger
push();
fill(0, 255, 0);
noStroke();
circle(fingerX, fingerY, 20);
pop();
}
// Draw boids sketch in bottom half
push();
// Create quadtree for bottom half
let boundary = new Rectangle(width/2, height * 3/4, width/2, height/4);
qtree = new QuadTree(boundary, QUAD_CAPACITY);
// Update and show boids
for (let boid of boids) {
let p = new Point(boid.pos.x, boid.pos.y, boid);
qtree.insert(p);
}
strokeWeight(3);
for (let boid of boids) {
let range = new Rectangle(boid.pos.x, boid.pos.y, VIEW_RADIUS, VIEW_RADIUS);
let neighbors = qtree.query(range);
// Use finger position for boid parameters if hand is detected
if (hands.length > 0) {
boid.update(neighbors, fingerX/width, fingerY/height);
} else {
boid.update(neighbors, 0.5, 0.5); // Default values if no hand detected
}
stroke(255);
point(boid.pos.x, boid.pos.y);
}
pop();
}
class Boid {
constructor(x, y) {
this.pos = createVector(x, y);
this.vel = p5.Vector.random2D();
this.vel.setMag(random(2, 4));
this.acc = createVector();
}
update(neighbors, normX, normY) {
let align = createVector();
let cohesion = createVector();
let separation = createVector();
let total = 0;
let alignStrength = map(normY, 0, 1, 2, 10);
let separationStrength = map(normX, 0, 1, 1.2, 3);
for (let point of neighbors) {
let other = point.userData;
if (other !== this) {
let d = dist(this.pos.x, this.pos.y, other.pos.x, other.pos.y);
if (d < VIEW_RADIUS) {
align.add(other.vel);
cohesion.add(other.pos);
let diff = p5.Vector.sub(this.pos, other.pos);
diff.div(d);
separation.add(diff);
total++;
}
}
}
if (total > 0) {
align.div(total).setMag(alignStrength);
cohesion.div(total).sub(this.pos).setMag(2);
separation.div(total).setMag(separationStrength);
this.acc.add(align);
this.acc.add(cohesion);
this.acc.add(separation);
}
this.vel.add(this.acc).limit(4);
this.pos.add(this.vel);
this.acc.set(0, 0);
this.edges();
}
edges() {
// Wrap horizontally
if (this.pos.x > width) this.pos.x = 0;
if (this.pos.x < 0) this.pos.x = width;
// Bounce vertically within bottom half with velocity reflection
if (this.pos.y > height) {
this.pos.y = height;
this.vel.y *= -1; // Reverse vertical velocity
}
if (this.pos.y < height/2) {
this.pos.y = height/2;
this.vel.y *= -1; // Reverse vertical velocity
}
}
}
class Point {
constructor(x, y, userData) {
this.x = x;
this.y = y;
this.userData = userData;
}
}
class Rectangle {
constructor(x, y, w, h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
contains(point) {
return (point.x >= this.x - this.w &&
point.x < this.x + this.w &&
point.y >= this.y - this.h &&
point.y < this.y + this.h);
}
intersects(range) {
return !(range.x - range.w > this.x + this.w ||
range.x + range.w < this.x - this.w ||
range.y - range.h > this.y + this.h ||
range.y + range.h < this.y - this.h);
}
}
class QuadTree {
constructor(boundary, n) {
this.boundary = boundary;
this.capacity = n;
this.points = [];
this.divided = false;
}
subdivide() {
let x = this.boundary.x;
let y = this.boundary.y;
let w = this.boundary.w / 2;
let h = this.boundary.h / 2;
let nw = new Rectangle(x - w, y - h, w, h);
let ne = new Rectangle(x + w, y - h, w, h);
let sw = new Rectangle(x - w, y + h, w, h);
let se = new Rectangle(x + w, y + h, w, h);
this.northwest = new QuadTree(nw, this.capacity);
this.northeast = new QuadTree(ne, this.capacity);
this.southwest = new QuadTree(sw, this.capacity);
this.southeast = new QuadTree(se, this.capacity);
this.divided = true;
}
insert(point) {
if (!this.boundary.contains(point)) return false;
if (this.points.length < this.capacity) {
this.points.push(point);
return true;
}
if (!this.divided) this.subdivide();
return (this.northwest.insert(point) ||
this.northeast.insert(point) ||
this.southwest.insert(point) ||
this.southeast.insert(point));
}
query(range, found = []) {
if (!this.boundary.intersects(range)) return found;
for (let p of this.points) {
if (range.contains(p)) found.push(p);
}
if (this.divided) {
this.northwest.query(range, found);
this.northeast.query(range, found);
this.southwest.query(range, found);
this.southeast.query(range, found);
}
return found;
}
show() {
if (this.divided) {
this.northwest.show();
this.northeast.show();
this.southwest.show();
this.southeast.show();
}
}
}