We are about to switch to a new forum software. Until then we have removed the registration on this forum.
Hi
I working on a code running particles to represent refugee data The code limits the number of particles to keep the frameRate but even after using a steady number of particles, it keeps getting slower with lower frameRate.
I'm really not sure what I can do to make this run faster / smoother. Why does it keep getting slower?
Here's the data folder for the Sketch: https://www.dropbox.com/sh/z69phco3tahqklu/AAB8PLMpBQMoFUX5NGSie-5fa?dl=0
PImage texture;
PGraphics pg;
PShape worldMap;
import java.util.Map;
int filterPSystemsNum;
PGraphics sumPG;
// ========== Table Data Stuff
Table immigrationTable;
Table conflictTable;
Table countryLocations;
int k = 0;
String[] destCountryArray = new String[0];
String[] sourceCountryArray = new String[0];
String destCountry = "";
String prevDestCountry = "";
String sourceCountry;
float mapMultiplyX = 4;
float mapOffsetX = -100;
float mapMultiplyY = 4;
float mapOffsetY = 50;
// ========
int amountSum = 1;
String displayType = "Circular";
int maxParticles = 9000;
ParticleSystem ps;
ParticleSystem ps2;
int n = 0, n2=0;
int emmitMultiplyer = 1;
int emmitFreq = 1;
float particleSpeed = 1;
float locationX = 250;
float locationY = 450;
int[] sourceX = {10, 40, 200, 400, 700};
int[] destX = {300, 600, 300, 600, 600};
int[] amount = {10, 100, 500, 800, 1000};
int highestAmount;
float radius = 300;
float tempLong;
float tempLat;
int yearFilterMin = 1996;
int yearFilterMax = 2016;
ParticleSystem[] PSystems;
HashMap<String, Country> countries = new HashMap<String, Country>();
void setup() {
size(1200, 800, P2D);
((PGraphicsOpenGL)g).textureSampling(3);
texture = loadImage("paper_texture.jpg");
//worldMap = loadShape("worldLow.svg");
//=============== load immigrationTable and create an array of countries
immigrationTable = loadTable("asylum_seekers_all.csv", "header");
countryLocations = loadTable("LatLonCountries2.csv", "header");
conflictTable = loadTable("ged171.csv", "header");
destCountryArray = (String[]) append(destCountryArray, "Israel");
sumPG = createGraphics(width, height);
pg = createGraphics(width, height);
pg.beginDraw();
pg.background(177);
pg.endDraw();
image(pg, 0, 0);
for (TableRow row : immigrationTable.rows()) {
if (row.getInt("Year") <= yearFilterMax && row.getInt("Year") >= yearFilterMin) {
String tempCountryHolder = row.getString("Country / territory of asylum/residence");
boolean exists = countryExists(tempCountryHolder);
if (exists==true) {
continue;
}
destCountryArray = (String[]) append(destCountryArray, tempCountryHolder);
}
}
for (TableRow row : immigrationTable.rows()) {
if (row.getInt("Year") <= yearFilterMax && row.getInt("Year") >= yearFilterMin) {
String tempCountryHolder = row.getString("Origin");
boolean exists = countryExists(tempCountryHolder);
if (exists==true) { //println("exists, skipping");
continue;
}
destCountryArray = (String[]) append(destCountryArray, tempCountryHolder);
}
}
destCountryArray = sort(destCountryArray);
//============================ country hashmaps ======================================
for (int i = 0; i < destCountryArray.length; i++) {
String name = destCountryArray[i];
for (TableRow row : countryLocations.rows()) {
if (name.equals(row.getString("name"))) {
tempLong = row.getFloat("longitude");
tempLat = row.getFloat("latitude");
}
}
countries.put(name, new Country(name, width/2+(tempLong*mapMultiplyX)+mapOffsetX, height/2-(tempLat*mapMultiplyY)+mapOffsetY));
}
for (String key : countries.keySet()) {
Country c = countries.get(key);
}
//============================ PSystems =============
PSystems = new ParticleSystem[immigrationTable.getRowCount()];
int j = 0;
highestAmount = 291664;
println(amountSum);
for (TableRow row : immigrationTable.rows()) {
if (row.getInt("Year") <= yearFilterMax && row.getInt("Year") >= yearFilterMin) {
int tempAmount = row.getInt("decisions_recognized");
String tempDestCountry = row.getString("Country / territory of asylum/residence");
String tempSourceCountry = row.getString("Origin");
int tempYear = row.getInt("Year");
float sourceEmmiterX = countries.get(tempSourceCountry).x;
float sourceEmmiterY = countries.get(tempSourceCountry).y;
float destEmmiterX = countries.get(tempDestCountry).x;
float destEmitterY = countries.get(tempDestCountry).y;
PSystems[j] = new ParticleSystem(1, new Vector3D(sourceEmmiterX, sourceEmmiterY, 0), new Vector3D(destEmmiterX, destEmitterY, 0), tempAmount, tempYear);
println("----");
filterPSystemsNum++;
println("tempAmount "+tempAmount);
println("maxParticles "+maxParticles);
println("amountSum "+amountSum);
println("tempAmount*maxParticles "+tempAmount*maxParticles);
println("tempAmount*(maxParticles/amountSum) "+tempAmount*maxParticles/amountSum);
//println("PSystems " + i + " is " +PSystems[i]);
j++;
}
}
smooth();
}
int currentYear = 2000;
void draw() {
blendMode(MULTIPLY);
background(247, 245, 246, 1);
fill(1, 1, 1, 1);
tint(255, 1);
image(pg, 0, 0);
for (int i = 0; i<filterPSystemsNum; i++) {
boolean overCountryOrigin = overCountry(int(PSystems[i].origin.x), int(PSystems[i].origin.y), 10);
boolean overCountryDest = overCountry(int(PSystems[i].dest.x), int(PSystems[i].dest.y), 10);
boolean isOver = (overCountryOrigin || overCountryDest);
PSystems[i].run(isOver);
}
// =======
int allPCount = 0;
for (int k = 0; k<filterPSystemsNum; k++) {
Vector3D b=new Vector3D(PSystems[k].origin.x, PSystems[k].origin.y+abs(PSystems[k].origin.x-PSystems[k].dest.x)/2, 0);
Vector3D c=new Vector3D(PSystems[k].dest.x, PSystems[k].dest.y+abs(PSystems[k].origin.x-PSystems[k].dest.x)/2, 0);
boolean isTherePLimit = PSystems[k].PLimit != 0;
if (isTherePLimit){
boolean isThisAPlayingFrame = frameCount % int(sqrt(highestAmount/PSystems[k].PLimit)) == 0;
boolean isThisTheRightYear = PSystems[k].year == currentYear;
boolean areThereAnyMoreParticles = PSystems[k].PCount<PSystems[k].PLimit;
if (isThisAPlayingFrame && isThisTheRightYear && areThereAnyMoreParticles) {
for(int s = 0; s<emmitMultiplyer; s++){
PSystems[k].addParticle();
PSystems[k].PCount++;
n++;
}
}
}
}
// =========================================================== country labels
for (int i = 0; i<destCountryArray.length; i++) {
float tempX = countries.get(destCountryArray[i]).x;
float tempY = countries.get(destCountryArray[i]).y;
float tempAng = atan((tempY-height/2)/(tempX-width/2));
float labelMargin;
translate(tempX, tempY);
textSize(8);
rotate(tempAng);
fill(0);
if (tempX<width/2) {
textAlign(RIGHT);
labelMargin = -7;
} else {
textAlign(LEFT);
labelMargin = 7;
}
if (overCountry(int(tempX), int(tempY), 9)==true) {
fill(50, 100, 250);
ellipse(0, 0, 5, 5);
fill(0, 0, 255);
text(countries.get(destCountryArray[i]).name, labelMargin, 2);
} else {
fill (200);
fill(30, 100-sq(dist(mouseX, mouseY, tempX, tempY)));
text(countries.get(destCountryArray[i]).name, labelMargin, 2);
}
if (countries.get(destCountryArray[i]).name == "Israel") {
textAlign(RIGHT);
textSize(10);
fill(255);
rotate(-tempAng);
translate(-tempX, -tempY);
rect(-6, -8, -textWidth("you are here")-7, 14);
fill(200, 40, 40);
text ("you are here", mouseX, mouseY);
noFill();
strokeWeight(0.5);
stroke(255, 0, 0);
bezier(mouseX, mouseY, (mouseX+tempX)/2+100, mouseY, tempX, (mouseY+tempY)/2, tempX, tempY);
noStroke();
translate(tempX, tempY);
rotate(tempAng);
ellipse(0, 0, 4, 4);
}
rotate(-tempAng);
translate(-tempX, -tempY);
}
fill(0);
text("Frame rate: "
+ int(frameRate), 10, 20);
text("Each point: " + int(amountSum/maxParticles) +" people", 10, 30);
text("number of refugees: " + n*int(amountSum/maxParticles), 10, 40);
text("number of particles is: " +n, 10, 50);
text("max particles is: " +maxParticles, 10, 60);
textSize(80);
blendMode(BLEND);
fill(128, 130, 131);
text(round(float(n)*float(amountSum)/float(maxParticles)), 10, 160);
textSize(20);
text("RECOGNIZED REFUGEES (" +yearFilterMin+ " - " + yearFilterMax+")", 10, 180);
println("n = "+n);
println("amountSum = "+amountSum);
println("maxParticles = "+maxParticles);
}
// ==============================// A simple Particle class // ===============================================//
class Particle {
Vector3D loc;
Vector3D des;
Vector3D vel;
Vector3D acc;
Vector3D locHome, b, c;
float relativeSpeed;
float r;
float timer;
float t=0.0;
float a = random(TWO_PI);
Particle(Vector3D l, Vector3D m, int accum) {
acc = new Vector3D(0, 0, 0); // new Vector3D(random(-0.1, 0.1), random(-0.02, 0), 0);
loc = l.copy();
des = m.copy();
locHome = l.copy();
float rescale = 0.5;
locHome.x = locHome.x+ cos(a)*random(0, sqrt(accum)*rescale);
locHome.y = locHome.y+ sin(a)*random(0, sqrt(accum)*rescale);
des.x = des.x+ cos(a)*random(0, sqrt(accum)*rescale);
des.y = des.y+ sin(a)*random(0, sqrt(accum)*rescale);
relativeSpeed = random(0.1, 0.02);
r = random(0.9, 3); // particle radius
timer = 10000.0; // particles lifespan
b=new Vector3D(l.copy().x, l.copy().y+abs(l.copy().x-m.copy().x)/2, 0);
c=new Vector3D(m.copy().x, m.copy().y+abs(l.copy().x-m.copy().x)/2, 0);
}
void run(boolean onMouseOverPSystem) {
update();
render(onMouseOverPSystem);
}
// Method to update location
void update() {
if (t>=1)
return;
loc.x = bezierPoint(locHome.x, b.x, c.x, des.x, t);
loc.y = bezierPoint(locHome.y, b.y, c.y, des.y, t);
t = lerp(t, 1, particleSpeed*relativeSpeed/2);
}
void render(boolean isSelected) {
ellipseMode(CENTER);
noStroke();
blendMode(BLEND);
if (isSelected == false) {
fill(128, 129, 129, 150);
}
if (t==lerp(0, 1, particleSpeed*relativeSpeed/2)) {
pg.beginDraw();
pg.blendMode(BLEND);
pg.fill(0, 0, 0);
pg.noStroke();
pg.ellipse(loc.x, loc.y, r, r);
pg.endDraw();
} else {
fill(4*255*t-3*255);
}
ellipse(loc.x, loc.y, r, r);
}
// Is the particle still useful?
boolean dead() {
if (t==1) {
pg.beginDraw();
pg.blendMode(BLEND);
pg.fill(255, 255, 255, 255);
pg.noStroke();
pg.ellipse(loc.x, loc.y, r, r);
pg.endDraw();
return true;
} else {
return false;
}
}
}
// ==============================// A ParticleSystem // ===============================================//
// A class to describe a group of Particles
// An ArrayList is used to manage the list of Particles
class ParticleSystem {
ArrayList particles; // An arraylist for all the particles
Vector3D origin; // An origin point for where particles are birthed
Vector3D dest;
int PLimit;
int PCount;
int year;
float ratio = PLimit/highestAmount;
//ParticleSystem( number of particles / frame, source, destination, frequency);
ParticleSystem(int num, Vector3D v, Vector3D d, int f, int y) {
particles = new ArrayList(); // Initialize the arraylist
origin = v.copy(); // Store the origin point
dest = d.copy();
year = y;
PLimit = f;
//if (frameCount % (1/f) == 0){
for (int i = 0; i < num; i++) {
// particles.add(new Particle(origin, dest)); // Add "num" amount of particles to the arraylist
}
//}
}
void run(boolean onMouseOverPSystem) {
for (int i = particles.size()-1; i >= 0; i--) {
Particle p = (Particle) particles.get(i);
p.run(onMouseOverPSystem);
if (p.dead()) {
particles.remove(i);
//PCount--;
//n--;
}
}
}
void addParticle() {
particles.add(new Particle(origin, dest, PCount));
}
// A method to test if the particle system still has particles
boolean dead() {
if (particles.isEmpty()) {
return true;
} else {
return false;
}
}
}
//=================================================== Class Country
class Country {
String name;
float x, y, lon, lat, migrationIndex, incoming, outgoing;
// more things here?
Country(String name, float x, float y) {
this.name = name;
this.x = x;
this.y = y;
}
}
// ================================================ Simple Vector3D Class
public class Vector3D {
public float x;
public float y;
public float z;
Vector3D(float x_, float y_, float z_) {
x = x_;
y = y_;
z = z_;
}
Vector3D(float x_, float y_) {
x = x_;
y = y_;
z = 0f;
}
Vector3D() {
x = 0f;
y = 0f;
z = 0f;
}
void setX(float x_) {
x = x_;
}
void setY(float y_) {
y = y_;
}
void setZ(float z_) {
z = z_;
}
void setXY(float x_, float y_) {
x = x_;
y = y_;
}
void setXYZ(float x_, float y_, float z_) {
x = x_;
y = y_;
z = z_;
}
void setXYZ(Vector3D v) {
x = v.x;
y = v.y;
z = v.z;
}
public float magnitude() {
return (float) Math.sqrt(x*x + y*y + z*z);
}
public Vector3D copy() {
return new Vector3D(x, y, z);
}
public Vector3D copy(Vector3D v) {
return new Vector3D(v.x, v.y, v.z);
}
public void add(Vector3D v) {
x += v.x;
y += v.y;
z += v.z;
}
public void sub(Vector3D v) {
x -= v.x;
y -= v.y;
z -= v.z;
}
public void mult(float n) {
x *= n;
y *= n;
z *= n;
}
public void div(float n) {
x /= n;
y /= n;
z /= n;
}
public void normalize() {
float m = magnitude();
if (m > 0) {
div(m);
}
}
public void limit(float max) {
if (magnitude() > max) {
normalize();
mult(max);
}
}
public float heading2D() {
float angle = (float) Math.atan2(-y, x);
return -1*angle;
}
public Vector3D add(Vector3D v1, Vector3D v2) {
Vector3D v = new Vector3D(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z);
return v;
}
public Vector3D sub(Vector3D v1, Vector3D v2) {
Vector3D v = new Vector3D(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z);
return v;
}
public Vector3D div(Vector3D v1, float n) {
Vector3D v = new Vector3D(v1.x/n, v1.y/n, v1.z/n);
return v;
}
public Vector3D mult(Vector3D v1, float n) {
Vector3D v = new Vector3D(v1.x*n, v1.y*n, v1.z*n);
return v;
}
public float distance (Vector3D v1, Vector3D v2) {
float dx = v1.x - v2.x;
float dy = v1.y - v2.y;
float dz = v1.z - v2.z;
return (float) Math.sqrt(dx*dx + dy*dy + dz*dz);
}
}
boolean countryExists(String tempCountryHolder) {
for (int i = 0; i < destCountryArray.length; i++) {
if (tempCountryHolder.equals(destCountryArray[i]) == true) {
return true; // it exists
} //if
} //for
return false ; // not found
}//func
//============================================= onMouseOver Bolean Class
boolean overParticle(int x, int y, int diameter) {
float disX = x - mouseX;
float disY = y - mouseY;
if (sqrt(sq(disX) + sq(disY)) < diameter/2 ) {
return true;
} else {
return false;
}
}
boolean overCountry(int x, int y, int diameter) {
float disX = x - mouseX;
float disY = y - mouseY;
if (sqrt(sq(disX) + sq(disY)) < diameter/2 ) {
return true;
} else {
return false;
}
}
Answers
I figured out the problem
Since particle location is determined by Lerp() function, it never reaches t=1. A fix was to change the if() to kill the particles from
if(t==1)
to
if (dist(loc.x, loc.y, des.x, des.y)<3)