
/**
 * Particle - DESCRIPTION
 * <p/>
 * An extension to the Processing project - http://Proce55ing.net
 * <p/>
 * Copyright (c) 2004 Simon Greenwold
 * Created: Feb 11, 2004
 * <p/>
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * <p/>
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * <p/>
 * You should have received a copy of the GNU Lesser General
 * Public License along with the Processing project; if not,
 * write to the Free Software Foundation, Inc., 59 Temple Place,
 * Suite 330, Boston, MA  02111-1307  USA
 */

public class Particle implements MPConstants {

  public boolean fixed[] = new boolean[3];
  public float fixedPos[] = new float[3];
  public boolean visible = true;
  public int index;

  public boolean fixed() {
    return fixed[X] && fixed[Y] && fixed[Z];
  }

  public void fix() {
    fix(pos[X], pos[Y], pos[Z]);
  }

  public void fix(boolean fixed) {
    if (fixed) {
      fix();
    }
    else {
      unfix();
    }
  }

  public void fix(float x, float y, float z) {
    fix(X, x);
    fix(Y, y);
    fix(Z, z);
  }

  public void fix(int axis, float value) {
    if (axis > Z) return;
    fixedPos[axis] = value;
    fixed[axis] = true;
  }

  public void unfix() {
    fixed[X] = false;
    fixed[Y] = false;
    fixed[Z] = false;
  }

  public void unfix(int axis) {
    if (axis >= Z) return;
    fixed[axis] = false;
  }

//  CollisionForce collisionForce = null;

  float mass;
  float collisionRadius;
  float minCollisionForce;
  float maxCollisionForce;
  float pos[] = new float[DIMENSIONS];		// x
  float velocity[] = new float[DIMENSIONS];		// v
  float force[] = new float[DIMENSIONS];			// f

  float nextpos[] = new float[DIMENSIONS];
  float nextVelocity[] = new float[DIMENSIONS];

  float k1[] = new float[DIMENSIONS];
  float k2[] = new float[DIMENSIONS];
  float k3[] = new float[DIMENSIONS];
  float k4[] = new float[DIMENSIONS];
  float vsave[] = new float[DIMENSIONS];

  Particle(float x, float y, float z) {
    commonConstruct(x, y, z);
  }
  
  Particle() {
    commonConstruct(0, 0, 0);
  }
  
  private void commonConstruct(float x, float y, float z) {
    unfix();

    pSystem = null;

    for (int i = 0; i < DIMENSIONS; i++) {
      velocity[i] = 0.0f;
      force[i] = 0.0f;
      nextpos[i] = 0.0f;
      nextVelocity[i] = 0.0f;
    }  
    pos[0] = x;
    pos[1] = y;
    pos[2] = z;
  }

  public void init() {
    if (pSystem == null) return;

    mass = pSystem.defaultMass;
    if (collisionRadius == 0)
      collisionRadius = pSystem.defaultCollisionRadius;
    if (minCollisionForce == 0)
      minCollisionForce = pSystem.defaultMinCollisionForce;
    if (maxCollisionForce == 0)
      maxCollisionForce = pSystem.defaultMaxCollisionForce;
    age = 0;
  }

  void draw() {
    if (pSystem == null) return;
    
    BApplet applet = pSystem.getBApplet();
    
    applet.push();
    applet.translate(pos[0], pos[1], pos[2]);
    applet.box(5, 5, 5);
    applet.pop();    
  }

  void die() {
    pSystem.removeParticle(this);
  }

  Particle[] particlesWithin(float radius) {
    float radSq = sq(radius);
    int count = 0;
    Particle particles[] = pSystem.particles;
    for (int i = 0; i < pSystem.nParticles; i++) {
      if (particles[i] != this) {
        if (distSq(pos, particles[i].pos) <= radSq) {
          count++;
        }
      }
    }
    Particle result[] = new Particle[count];
    count = 0;
    for (int i = 0; i < pSystem.nParticles; i++) {
      if (particles[i] != this) {
        if (distSq(pos, particles[i].pos) <= radSq) {
          result[count] = particles[i];
          count++;
        }
      }
    }
    return result;
  }

  void enableCollision() {
//    if (collisionForce == null) {
//      collisionForce = new CollisionForce(this);
//      if (pSystem != null)
//        pSystem.addForce(collisionForce);
//    }
  }

  void enableCollision(float radius) {
    collisionRadius = radius;
    enableCollision();
  }

  void disableCollision() {
//    if (collisionForce != null) {
//      collisionForce.active = false;
//    }
  }

  void setPos(float x, float y, float z) {
    pos[0] = max(min(x, SYS_X_MAX - 1), SYS_X_MIN);
    pos[1] = max(min(y, SYS_Y_MAX - 1), SYS_Y_MIN);
    pos[2] = max(min(z, SYS_Z_MAX - 1), SYS_Z_MIN);
  }

  void setVelocity(float dx, float dy, float dz) {
    velocity[0] = dx;
    velocity[1] = dy;
    velocity[2] = dz;
  }

  void setForce(float fx, float fy, float fz) {
    force[0] = fx;
    force[1] = fy;
    force[2] = fz;
  }

  void addForce(float fx, float fy, float fz) {
    force[0] += fx;
    force[1] += fy;
    force[2] += fz;
  }

  void clearForce() {
    force[0] = 0.0f;
    force[1] = 0.0f;
    force[2] = 0.0f;
  }

  void calculateNext(float step) {
    // Runge-Kutta solver
    vsave[0] = velocity[0];
    vsave[1] = velocity[1];
    vsave[2] = velocity[2];

    k1[0] = step * force[0] / mass;
    k1[1] = step * force[1] / mass;
    k1[2] = step * force[2] / mass;

    velocity[0] += k1[0]/2;
    velocity[1] += k1[1]/2;
    velocity[2] += k1[2]/2;
    clearForce();
    pSystem.applyForces(this);

    k2[0] = step * force[0] / mass;
    k2[1] = step * force[1] / mass;
    k2[2] = step * force[2] / mass;

    velocity[0] = vsave[0] + k2[0]/2;
    velocity[1] = vsave[1] + k2[1]/2;
    velocity[2] = vsave[2] + k2[2]/2;
    clearForce();
    pSystem.applyForces(this);

    k3[0] = step * force[0] / mass;
    k3[1] = step * force[1] / mass;
    k3[2] = step * force[2] / mass;

    velocity[0] = vsave[0] + k3[0];
    velocity[1] = vsave[1] + k3[1];
    velocity[2] = vsave[2] + k3[2];
    clearForce();
    pSystem.applyForces(this);

    k4[0] = step * force[0] / mass;
    k4[1] = step * force[1] / mass;
    k4[2] = step * force[2] / mass;

    velocity[0] = vsave[0];
    velocity[1] = vsave[1];
    velocity[2] = vsave[2];

    nextVelocity[0] = velocity[0] + (k1[0] + 2*k2[0] + 2*k3[0] + k4[0]) / 6;
    nextVelocity[1] = velocity[1] + (k1[1] + 2*k2[1] + 2*k3[1] + k4[1]) / 6;
    nextVelocity[2] = velocity[2] + (k1[2] + 2*k2[2] + 2*k3[2] + k4[2]) / 6;

    nextpos[0] = pos[0] + step * nextVelocity[0];
    nextpos[1] = pos[1] + step * nextVelocity[1];
    nextpos[2] = pos[2] + step * nextVelocity[2];
  }

  void updateNext() {
    setPos(nextpos[0], nextpos[1], nextpos[2]);
    velocity[0] = nextVelocity[0];
    velocity[1] = nextVelocity[1];
    velocity[2] = nextVelocity[2];
    
    if (fixed[0])
      pos[0] = fixedPos[0];
    if (fixed[1])
      pos[1] = fixedPos[1];
    if (fixed[2])
      pos[2] = fixedPos[2];
    age++;
  }

  // ------------------------------------------------------------

  // math stuff for convenience


  public final float abs(float n) {
    return (n < 0) ? -n : n;
  }

  public final int abs(int n) {
    return (n < 0) ? -n : n;
  }

  public final float sq(float a) {
    return a*a;
  }

  public final float sqrt(float a) {
    return (float)Math.sqrt(a);
  }

  public final float pow(float a, float b) {
    return (float)Math.pow(a, b);
  }


  public final float max(float a, float b) {
    return Math.max(a, b);
  }

  public final float max(float a, float b, float c) {
    return Math.max(a, Math.max(b, c));
  }

  public final float min(float a, float b) {
    return Math.min(a, b);
  }

  public final float min(float a, float b, float c) {
    return Math.min(a, Math.min(b, c));
  }

  public final float constrain(float amt, float low, float high) {
    return (amt < low) ? low : ((amt > high) ? high : amt);
  }


  public final int max(int a, int b) {
    return (a > b) ? a : b;
  }

  public final int max(int a, int b, int c) {
    return (a > b) ? ((a > c) ? a : c) : b;
  }

  public final int min(int a, int b) {
    return (a < b) ? a : b;
  }

  public final int min(int a, int b, int c) {
    return (a < b) ? ((a < c) ? a : c) : b;
  }

  public final int constrain(int amt, int low, int high) {
    return (amt < low) ? low : ((amt > high) ? high : amt);
  }


  public final float sin(float angle) {
    return (float)Math.sin(angle);
  }

  public final float cos(float angle) {
    return (float)Math.cos(angle);
  }

  public final float tan(float angle) {
    return (float)Math.tan(angle);
  }

  public final float atan2(float a, float b) {
    return (float)Math.atan2(a, b);
  }


  public final float ceil(float what) {
    return (float) Math.ceil(what);
  }

  public final float floor(float what) {
    return (float) Math.floor(what);
  }

  public final float round(float what) {
    return Math.round(what);
  }

  public final float distSq(float x1, float y1, float x2, float y2) {
    return sq(x2-x1) + sq(y2-y1);
  }

  public final float distSq(float p1[], float p2[]) {
    return sq(p2[X]-p1[X]) + sq(p2[Y]-p1[Y]) + sq(p2[Z]-p1[Z]);
  }

  public final float distSq(float x1, float y1, float z1,
                            float x2, float y2, float z2) {
    return sq(x2-x1) + sq(y2-y1) + sq(z2-z1);
  }

  public final float dist(float p1[], float p2[]) {
    return sqrt(sq(p2[X]-p1[X]) + sq(p2[Y]-p1[Y]) + sq(p2[Z]-p1[Z]));
  }

  public final float dist(float x1, float y1, float x2, float y2) {
    return sqrt(sq(x2-x1) + sq(y2-y1));
  }

  public final float dist(float x1, float y1, float z1,
                          float x2, float y2, float z2) {
    return sqrt(sq(x2-x1) + sq(y2-y1) + sq(z2-z1));
  }


  public final float random(float howbig) {
    return (float)Math.random() * howbig;
  }

  public final float random(float howsmall, float howbig) {
    float diff = howbig - howsmall;
    return howsmall + (float)Math.random() * diff;
  }

  public PSystem pSystem;
  int age;
}
