xxxxxxxxxx
/*
"splat" - a somewhat mildly entertaining clicker
where each drone carries a payload of paint
two fliers at a time with wrap-around boundaries
the debris queue length is 50
optimized to work fairly well on a phone
design and engineering: jWilliamDunn.com
change log:
20221001 initial dev
20221006 simpleCam
20221007 propeller sweep
20221008 hud display, optimize for phone
20221012 hsl fork, attempt
20221224 add shadow rotation
20230105 fix debris rotation
issues:
p5.SimpleCam does not yet support touch pinch/zoom
future features:
additional fall rotation sequences
timed release of new drones
fast, curvy drones
for a close-up view of the drone model, see sketch:
https://openprocessing.org/sketch/1678350
inspired by the drones in "Egg, Inc."
dedicated to Sienna
*/
p5.disableFriendlyErrors = true;
function preload() {
f = loadFont(ttf);
}
let f=[],m=[],points=0,ground=[],debris=[],rate=0,rotor=0.7,cam,info,debrisMax=50,perfdrop=0,
ttf='https://cdnjs.cloudflare.com/ajax/libs/ink/3.1.10/fonts/Roboto/roboto-regular-webfont.ttf',
// this palette array is adjustable (lengthened without other changes)
// it is appended in setup()
pal=[[0,0,42],
[0,0,0]
];
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
// createEasyCam(); //for 3d model testing
addScreenPositionFunction(); // this allows for 3d world-to-screen coordinates conversion
ellipseMode(CENTER);
colorMode(HSL,360,100,100,255);
// HUD display init
info = createElement("p","ok");
info.style("user-select","none");
info.style("color","black");
info.position(20, 0);
noStroke();
textFont(f);
textSize(20);
for(let i=0;i<10;i++){ // build a perturbed ground array
ground[i]=[];
for(let j=0;j<10;j++)
ground[i][j]=2*random(-1,1);
}
/*
perspective(PI/3,width/height,0.1,5000);
cam = createSimpleCam();
cam.zoom = new Damp(-200,-5,0.07,[-400,600]); // adjust zoom
cam.rx = new Damp(PI/3-0.05,0.01,0.07,[PI/3-0.1,PI/3]); // set initial X rotation
cam.ry = new Damp(0,0.01,0.07,[-PI/2,PI/2]); // set initial Z rotation
camera(0,0,700, 0,0,0, 0,1,0);
*/
// build the palette
for(let i=0;i<12;i++){
// equations to approximate the reference color curve
let h=(i+1)**2*1.8949 + (i+1)*6.8984 - 16.227,
s=(i+1)**3*0.258 - (i+1)**2*5.0866 + 26.831*(i+1) + 54.739,
l=(i+1)**3*0.1543 - (i+1)**2*3.0148 + 13.885*(i+1) + 32.854;
pal.push([h-5.37,s,l]);
}
// finally, add a pair of drones
f[0] = new Flyer(-400,random(-400,400),150,pal[floor(random(0,pal.length))],[0.95,0.5],0.006);
f[1] = new Flyer(400,random(-400,400),155,pal[floor(random(0,pal.length))],[-0.8,-0.5],0.005);
//f[2] = new Flyer(random(-400,400),-400,145,[160,0,160],[-0.8,-0.7],0.004);
//f[3] = new Flyer(random(-400,400),-400,160,[80,80,80],[0.75,-0.55],0.005);
//f[4] = new Flyer(random(-400,400),400,190,[160,80,80],[0.5,0.95],0.004);
//f[4] = new Flyer(0,0,150,[160,80,80],[0,0],0.0); //test
}
function draw() {
let p;
//cam.apply();
orbitControl();
rotateX(PI/2);
background(0,0,50);
// HUD count and framerate
if(frameCount%30==0)rate=(rate*10+frameRate())/11;
info.html("splats: "+points+" fps: "+floor(rate), false);
perspective();
//lights();
pointLight(200,200,200, -1000,-1000,0);
ambientLight(63);
m[0]=mouseX-width/2;m[1]=mouseY-height/2;
// display the background grid
// 10x10 adds too much overhead for the phone, therefore 5x5
for(let i=0;i<5;i++)
for(let j=0;j<5;j++){
push();
translate(i*170-340,j*170-340,ground[i][j]);
fill(0,0,70+ground[i][j]); // color was 160 originally
plane(170,170);
pop();
}
// render the debris field
if(rate<50)
debrisMax=30; //optimize for phone
else {
if (perfdrop<60){
perfdrop++; // prevent oscillation
debrisMax=50;
}
}
for(let q=0;q<debris.length;q++){
push();
translate(debris[q][0],debris[q][1],5+q/debrisMax);
rotateZ(debris[q][3]);
fill(debris[q][2][0],debris[q][2][1],debris[q][2][2],
200-(debris.length-q)*(190/debrisMax)); //74 200-(debris.length-q)*4
//circle(0,0,20);
rect(-8,-8,16,16);
pop();
}
// render the drones
f[0].render();
f[1].render();
//f[2].render();
//f[3].render();
//f[4].render();
}
class Flyer {
constructor(x,y,z,c,v,w) {
this.x=x;
this.y=y;
this.z=z;
this.c=c; //color
this.v=v; //velocity
this.w=w; //wobble (drift off course)
this.h=false; //hit
this.r=0; //rotation
this.ix=x;
this.iy=y;
this.iz=z;
this.iv=[];
this.iv[0]=v[0];
this.iv[1]=v[1];
this.vz = 0;
this.a = -0.025;
this.rax = false; // 1=tumble XZ 0=tumble YZ
}
render(){
let p;
push();
translate(this.x,this.y,this.z);
if(this.v[0]!=0)rotateZ(atan(this.v[1]/this.v[0]));
if(this.rax) {rotateX(this.r);rotateZ(-this.r/2);}
else {rotateY(this.r);rotateZ(this.r/2);}
p = screenPosition(0,0,0);
if(dist(p.x,p.y, m[0], m[1])<30) {
if(!this.h && mouseIsPressed){
this.rax = random(1)>0.5;
this.h=true;
}
}
//scale(0.75);
fill(this.c[0],this.c[1],this.c[2]); // x-frame
push();
translate(0,0,-0.5);
rotateZ(PI/4);
box(1.414*10,1,1);
box(1,1.414*10,1);
rotateZ(-PI/4);
translate(0,0,-2.0); // payload box
fill(this.c[0],this.c[1]*0.8,this.c[2]*0.8);
box(6,6,3);
pop();
push(); // propellers
translate(-5,-5);
rotateZ(rotor*frameCount);
if(rate>50&&!this.h){
fill(0,31);
translate(0,0,-0.4);
ellipse(0,0,8,8,12);
translate(0,0,0.4);
}
fill(0,0,0);
rect( -0.4, -4, 0.8,8);
pop();
push();
translate(5,5);
rotateZ(rotor*frameCount);
if(rate>50&&!this.h){
fill(0,31);
translate(0,0,-0.4);
ellipse(0,0,8,8,12);
translate(0,0,0.4);
}
fill(0,0,0);
rect( -0.4, -4, 0.8,8);
pop();
push();
translate(5,-5);
rotateZ(-rotor*frameCount);
if(rate>50&&!this.h){
fill(0,31);
translate(0,0,-0.4);
ellipse(0,0,8,8,12);
translate(0,0,0.4);
}
fill(0,0,0);
rect( -0.4, -4, 0.8,8);
pop();
push();
translate(-5,5);
rotateZ(-rotor*frameCount*(this.h?0.14:1));
if(rate>50&&!this.h){
fill(0,31);
translate(0,0,-0.4);
ellipse(0,0,8,8,12);
translate(0,0,0.4);
}
fill(0,0,0);
rect( -0.4, -4, 0.8,8);
pop();
pop();
push(); // this represents the shadow (sort of)
translate(this.x,this.y,3);
rotateZ(atan(this.v[1]/this.v[0]));
fill(0,0,map(this.z, this.iz,0, 60,30), 200); // darken as it falls 157
scale(map(this.z, this.iz,0, 2,1.2)); // less diffused as it falls
rect(-3,-3, 6,6);
pop();
this.x += this.v[0];
this.y += this.v[1];
this.v[1] += this.w; // drift the y velocity
if(this.h){ // hit and falling
this.v[0]*=0.99;
this.v[1]*=0.99;
this.z+=this.vz;
this.r+=random(0.07,0.105);
this.vz+=this.a;
}
// handle boundaries
if(!(this.v[1]<2&&this.v[1]>-2))this.w=-this.w;
if(this.x>400)this.x=-400;
if(this.x<-400)this.x=400;
if(this.y>400)this.y=-400;
if(this.y<-400)this.y=400;
// handle crash
if(this.z<0){ //on impact...reset
debris.push([this.x,this.y,this.c,atan(this.v[1]/this.v[0])]); // enqueue to debris (append to the end)
while(debris.length>debrisMax)debris.shift(); // dequeue (remove front element)
this.v[0]=this.iv[0];
this.v[1]=this.iv[1];
this.z=this.iz+random(-15,15); // pick a new starting height (helps crash rotation)
this.x=this.ix;
this.y=random(-400,400); // pick a new y starting point
this.h=false;
this.c=pal[floor(random(0,pal.length))]; // pick a new color
this.r=0;
points+=1;
this.vz=0;
}
}
}