// Basic particle avoidance game int screen_width = 640; int screen_height = 480; int num_particles = 400; float[] performance; int PERFORMANCE_MEMORY_LENGTH = 10; float top_score = 0; boolean starting_up = false; float inter_flash_duration = 0; int which_is_flashing = 0; float ship_r = 5.0; float particles_r = 2.0; PVector ship_v = new PVector(0,0); int mouse_old_x; int mouse_old_y; int quad_size_x = 40; int quad_size_y = 40; class Particle { PVector p, v, c; float r; Particle(){} Particle(PVector _p, PVector _v, PVector _c) { p = new PVector(_p.x,_p.y); v = new PVector(_p.x,_p.y); c = new PVector(_c.x, _c.y, _c.z); } void SwapColor() { if (c.x == 200) { c.x = 0; c.z = 200; } else { c.x = 200; c.z = 0; } } }; Particle[] particles = new Particle[num_particles]; class QuadCell { float m_x, m_y; ArrayList my_particles; // indices into particles array QuadCell(){} void Draw() { fill(0,200,0); rect(m_x, m_y, quad_size_x, quad_size_y); } }; QuadCell[][] quadtree = new QuadCell[screen_width/quad_size_x][screen_height/quad_size_y]; void setup() { size(640, 480, JAVA2D); strokeWeight(2); smooth(); background(104); fill(200, 200, 200); noCursor(); // init quadtree for (int r = 0; r < quadtree.length; r++) { for (int c = 0; c < quadtree[r].length; c++) { quadtree[r][c] = new QuadCell(); quadtree[r][c].m_x = r*quad_size_x; quadtree[r][c].m_y = c*quad_size_y; quadtree[r][c].my_particles = new ArrayList(); } } // init particles for (int i = 0; i < particles.length; i++) { particles[i] = new Particle(); particles[i].p = new PVector(random(2, screen_width/2-2), random(2, screen_height-2)); particles[i].v = new PVector(random(-0.5,0.5), random(-0.5,0.5)); particles[i].r = int(random(particles_r/2,particles_r*2)); if (random(0,1)<0.5) particles[i].c = new PVector(200,0,0); else particles[i].c = new PVector(0,0,200); // add to quadtree int quadcell_x = int(particles[i].p.x/quad_size_x); int quadcell_y = int(particles[i].p.y/quad_size_y); quadtree[quadcell_x][quadcell_y].my_particles.add(i); } performance = new float[PERFORMANCE_MEMORY_LENGTH]; for (int i = 0; i < PERFORMANCE_MEMORY_LENGTH; i++) { performance[i] = 0.5; } mouse_old_x = mouseX; mouse_old_y = mouseY; } float hit_opportunities = 0; float was_hit = 0; void draw() { background(104); // draw middle divider stroke(0,0,0); rect(screen_width/2, 0, 4, screen_height/2-16); rect(screen_width/2, screen_height/2+16, 4, screen_height/2-16); if (!starting_up) { /***** update velocities *****/ for (int i = 0; i < num_particles; i++) { // collisions with walls if (particles[i].p.x + particles[i].v.x > screen_width-2) { particles[i].v.x *= -1 + random(-5,5)/10; } else if (particles[i].p.x + particles[i].v.x < 2) { particles[i].v.x *= -1 + random(-5,5)/10; } if (particles[i].p.y + particles[i].v.y > screen_height-2) { particles[i].v.y *= -1 + random(-5,5)/10; } else if (particles[i].p.y + particles[i].v.y < 2) { particles[i].v.y *= -1 + random(-5,5)/10; } // collisions with central divider if (particles[i].p.x + particles[i].v.x > screen_width/2-2 && particles[i].p.x + particles[i].v.x < screen_width/2+2 && (particles[i].p.y + particles[i].v.y > screen_height/2+16 || particles[i].p.y + particles[i].v.y < screen_height/2-16)) { particles[i].v.x *= -1 + random(-5,5)/10; } int quadcell_x_base = int(particles[i].p.x/quad_size_x); int quadcell_y_base = int(particles[i].p.y/quad_size_y); for (int ii = -1; ii <= 1; ii++) { for (int jj = -1; jj <= 1; jj++) { int quadcell_x = quadcell_x_base+ii; int quadcell_y = quadcell_y_base+jj; if (quadcell_x < 0 || quadcell_x >= quadtree.length || quadcell_y < 0 || quadcell_y >= quadtree[0].length) continue; //if (quadtree[quadcell_x][quadcell_y].my_particles.size() >= 2) { // quadtree[quadcell_x][quadcell_y].Draw(); //} for (int j = 0; j < quadtree[quadcell_x][quadcell_y].my_particles.size(); j++) { int which = ((Integer)(quadtree[quadcell_x][quadcell_y].my_particles.get(j))).intValue(); if (which != i) { float dx = particles[i].p.x - particles[which].p.x; float dy = particles[i].p.y - particles[which].p.y; if (dx*dx + dy*dy < pow(particles[i].r + particles[which].r, 2)) { // adapted from: http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=30 //quadtree[quadcell_x][quadcell_y].Draw(); // separate //PVector dp = new PVector(dx, dy); //float d = dp.mag(); //dp.mult((particles[i].r + particles[which].r-d)/d); //particles[i].p.add(dp); //particles[which].p.sub(dp); PVector pb1,pb2,xaxis,U1x,U1y,U2x,U2y,V1x,V1y,V2x,V2y; float a,b,m1,m2; pb1=PVector.add(particles[i].p,particles[i].v); // Find Position Of Ball1 pb2=PVector.add(particles[which].p,particles[which].v); // Find Position Of Ball2 m1=particles[i].r; m2=particles[which].r; xaxis=PVector.sub(pb2, pb1); // Find X-Axis float overlap_dist = (particles[i].r + particles[which].r) - xaxis.mag(); xaxis.normalize(); a=xaxis.dot(particles[i].v); // Find Projection U1x=PVector.mult(xaxis,a); // Find Projected Vectors U1y=PVector.sub(particles[i].v,U1x); xaxis=PVector.sub(pb1,pb2); // Do The Same As Above xaxis.normalize(); b=xaxis.dot(particles[which].v); // To Find Projection U2x=PVector.mult(xaxis,b); // Vectors For The Other Ball U2y=PVector.sub(particles[which].v,U2x); V1x=PVector.mult(PVector.add(PVector.mult(U1x,m1),PVector.sub(PVector.mult(U2x,m2),PVector.mult((PVector.sub(U1x,U2x)),m2))),1/(m1+m2)); // Now Find New Velocities V2x=PVector.mult(PVector.add(PVector.mult(U1x,m1),PVector.sub(PVector.mult(U2x,m2),PVector.mult((PVector.sub(U2x,U1x)),m1))),1/(m1+m2)); V1y=U1y; V2y=U2y; particles[i].v = PVector.add(V1x,V1y); // Set New Velocity Vectors particles[which].v = PVector.add(V2x,V2y); // To The Colliding Balls PVector offset = PVector.mult(xaxis,overlap_dist/2); particles[i].p.add(offset); particles[which].p.sub(offset); } } } } } // collisions with ship for (int ii = -1; ii <= 1; ii++) { for (int jj = -1; jj <= 1; jj++) { int quadcell_x = quadcell_x_base+ii; int quadcell_y = quadcell_y_base+jj; if (quadcell_x < 0 || quadcell_x >= quadtree.length || quadcell_y < 0 || quadcell_y >= quadtree[0].length) continue; //if (quadtree[quadcell_x][quadcell_y].my_particles.size() >= 2) { // quadtree[quadcell_x][quadcell_y].Draw(); //} float dx = particles[i].p.x - mouseX; float dy = particles[i].p.y - mouseY; if (dx*dx + dy*dy < pow(particles[i].r + ship_r, 2)) { // adapted from: http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=30 //quadtree[quadcell_x][quadcell_y].Draw(); // separate //PVector dp = new PVector(dx, dy); //float d = dp.mag(); //dp.mult((particles[i].r + particles[which].r-d)/d); //particles[i].p.add(dp); //particles[which].p.sub(dp); PVector pb1,pb2,xaxis,U1x,U1y,U2x,U2y,V1x,V1y,V2x,V2y; float a,b; pb1=PVector.add(particles[i].p,particles[i].v); // Find Position Of Ball1 pb2=PVector.add(new PVector(mouseX,mouseY),ship_v); // Find Position Of Ship xaxis=PVector.sub(pb2, pb1); // Find X-Axis float overlap_dist = (particles[i].r + ship_r) - xaxis.mag(); xaxis.normalize(); a=xaxis.dot(particles[i].v); // Find Projection U1x=PVector.mult(xaxis,a); // Find Projected Vectors U1y=PVector.sub(ship_v,U1x); xaxis=PVector.sub(pb1,pb2); // Do The Same As Above xaxis.normalize(); b=xaxis.dot(ship_v); // To Find Projection U2x=PVector.mult(xaxis,b); // Vectors For The Other Ball U2y=PVector.sub(ship_v,U2x); V1x=PVector.mult(PVector.add(U1x,PVector.sub(U2x,(PVector.sub(U1x,U2x)))),0.5); // Now Find New Velocities V2x=PVector.mult(PVector.add(U1x,PVector.sub(U2x,(PVector.sub(U2x,U1x)))),0.5); V1y=U1y; V2y=U2y; particles[i].v = PVector.add(V1x,V1y); // Set New Velocity Vectors PVector offset = PVector.mult(xaxis,overlap_dist/2); particles[i].p.add(offset); } } } // add gravity //particles[i].v.add(new PVector(0.0,0.1)); // cap velocities if (particles[i].v.x > 4) particles[i].v.x = 4; else if (particles[i].v.x < -4) particles[i].v.x = -4; if (particles[i].v.y > 4) particles[i].v.y = 4; else if (particles[i].v.y < -4) particles[i].v.y = -4; } /***** update positions *****/ // reset quadtree for (int r = 0; r < quadtree.length; r++) { for (int c = 0; c < quadtree[r].length; c++) { quadtree[r][c].my_particles = new ArrayList(); } } for (int i = 0; i < num_particles; i++) { particles[i].p.add(particles[i].v); if (particles[i].p.x < 0) particles[i].p.x = 0; if (particles[i].p.y < 0) particles[i].p.y = 0; if (particles[i].p.x > screen_width) particles[i].p.x = screen_width-1; if (particles[i].p.y > screen_height) particles[i].p.y = screen_height-1; // add to quadtree int quadcell_x = int(particles[i].p.x/quad_size_x); int quadcell_y = int(particles[i].p.y/quad_size_y); if(quadcell_x > 0 && quadcell_x < quadtree.length && quadcell_y > 0 && quadcell_y < quadtree[0].length) { quadtree[quadcell_x][quadcell_y].my_particles.add(i); } } // swap colors if (inter_flash_duration > 1) { int which_x = -1; int which_y = -1; int which = -1; int n = 0; while (which < 0 && n < 20) { which_x = int(random(0,quadtree.length-1)); which_y = int(random(0,quadtree[0].length-1)); which = int(random(0,quadtree[which_x][which_y].my_particles.size()-2)); n++; } if (which > 0) { particles[((Integer)(quadtree[which_x][which_y].my_particles.get(which))).intValue()].SwapColor(); particles[((Integer)(quadtree[which_x][which_y].my_particles.get(which+1))).intValue()].SwapColor(); } inter_flash_duration = 0; } inter_flash_duration++; // draw particles for (int i = 0; i < num_particles; i++) { stroke(particles[i].c.x, particles[i].c.y, particles[i].c.z); fill(particles[i].c.x, particles[i].c.y, particles[i].c.z); ellipse(particles[i].p.x, particles[i].p.y, (particles[i].r-1)*2, (particles[i].r-1)*2); } } else { for (int i = 0; i < num_particles; i++) { stroke(particles[i].c.x, particles[i].c.y, particles[i].c.z); fill(particles[i].c.x, particles[i].c.y, particles[i].c.z); ellipse(particles[i].p.x, particles[i].p.y, (particles[i].r-1)*2, (particles[i].r-1)*2); } } // draw ship stroke(0, 0, 0); fill(200,200,200); ellipse(mouseX,mouseY,ship_r*2,ship_r*2); // update ship velocity ship_v.x = (mouseX-mouse_old_x)/2; ship_v.y = (mouseY-mouse_old_y)/2; mouse_old_x = mouseX; mouse_old_y = mouseY; fill(200, 200, 200); } void mouseClicked() { //starting_up = !starting_up; }