/*
 * SwarmApplet.java
 *
 * (C) 2005, Alex S.
 */
 
import java.awt.*;
import java.util.Vector;

class MovingObject {
    public double x,y;
    public double direction;
    public double speed;

    public MovingObject(double x,double y,double direction,double speed){
        this.x = x;
        this.y = y;
        this.direction = direction;
        this.speed = speed;
    }

    public void move(int w,int h){
        double sin = Math.sin(direction);
        double cos = Math.cos(direction);
        double nx = x + cos * speed;
        double ny = y + sin * speed;
        if(nx > w-1){
            nx = w-1;
            cos = -cos;
            speed += 0.5*(Math.random()-0.5);
        }
        if(nx < 0){
            nx = 0;
            cos = -cos;
            speed += 0.5*(Math.random()-0.5);
        }
        if(ny > h-1){
            ny = h-1;
            sin = -sin;
            speed += 0.5*(Math.random()-0.5);
        }
        if(ny < 0){
            ny = 0;
            sin = -sin;
            speed += 0.5*(Math.random()-0.5);
        }
        if(speed < 0.1)
            speed = 0.1;
        if(speed > 10)
            speed = 10;
        direction = Math.atan2(sin,cos);
        x = nx;
        y = ny;
    }
}

/**
 * very basic swarm applet
 */
public class SwarmApplet extends BufferedApplet
{
    private final static int NUM_OBJS = 200;
    private final static int GWIDTH = 40;
    private final static int GHEIGHT = 40;
    private final static int RADIUS = 5;

    private final static double EPSILON = 0.001;

    private int width,height;

    private int[] queue = new int[GWIDTH * GHEIGHT];
    private int[] queuelast = new int[GWIDTH * GHEIGHT];
    private int[] next = new int[NUM_OBJS];
    private int[] objx = new int[NUM_OBJS];
    private int[] objy = new int[NUM_OBJS];

    private MovingObject[] objects = new MovingObject[NUM_OBJS];

    /**
     * initialize things
     */
    public void init(){
        super.init();
        width = bounds().width;
        height = bounds().height;
        for(int i=0;i<NUM_OBJS;i++){
            objects[i] = new MovingObject(
                Math.random()*width,
                Math.random()*height,
                Math.random()*(2 * Math.PI),
                Math.random()*RADIUS/2
            );
        }
    }

    /**
     * function that gets called whenever something needs to be
     * rendered.
     */
    public void render(Graphics g) {
        for(int i=0;i<queue.length;i++){
            queue[i] = -1;
        }
        double avgx = 0;
        double avgy = 0;
        for(int i=0;i<NUM_OBJS;i++){
            objects[i].move(width,height);
            
            avgx += objects[i].x;
            avgy += objects[i].y;

            int x = objx[i] = (int)( GWIDTH*(objects[i].x / width) );
            int y = objy[i] = (int)( GHEIGHT*(objects[i].y / height) );            
            int bucket = y * GWIDTH + x;

            // is there anything in this bucket already
            if(queue[bucket]==-1){  //  no
                //the start/end of this bucket is set to index of element.
                queuelast[bucket] = queue[bucket] = i;
            }else{                  // yes
                // the last pointer in this bucket will be this index
                queuelast[bucket] = next[ queuelast[bucket] ] = i;
            }
            // initialize the next pointer for that element.
            next[i]=-1;
        }
        avgx /= NUM_OBJS;
        avgy /= NUM_OBJS;

        double diam = RADIUS + RADIUS;
        double distsquared = diam * diam;
        //double xcenterpull = 0.01;
        //double ycenterpull = 0.02;

        for(int i=0;i<NUM_OBJS;i++){
            double x = objects[i].x;
            double y = objects[i].y;

            /*
            {   // adjust direction and speed.
                double dx = avgx - x;
                double dy = avgy - y;
                double sin = Math.sin(objects[i].direction);
                double cos = Math.cos(objects[i].direction);
                double len = Math.sqrt(dx*dx + dy*dy);
                if(len > 0){
                    dx /= len;
                    dy /= len;
                    cos = (1-xcenterpull)*cos + xcenterpull*dx;
                    sin = (1-ycenterpull)*sin + ycenterpull*dy;
                    sin += 0.03; // downward bias.
                    objects[i].direction = Math.atan2(sin,cos);
                    objects[i].speed = len > RADIUS/2 ? RADIUS/2:len;
                }
            }
            */

            int bx = objx[i];
            int by = objy[i];
            
            int Ux = bx - 1 < 0 ? 0:bx-1;
            int Uy = by - 1 < 0 ? 0:by-1;
            int Lx = bx + 1 > GWIDTH-1 ? GWIDTH-1:bx+1;
            int Ly = by + 1 > GHEIGHT-1 ? GHEIGHT-1:by+1;

            //System.out.println("current bucket: "+bx+","+by+" RANGE: ("+Ux+","+Uy+")->("+Lx+","+Ly+")");

            // direction stats.
            double _avgsin = 0;
            double _avgcos = 0;
            double _avgx = 0;
            double _avgy = 0;
            double _avgspeed = 0;
            int _numobjects = 0;

            for(int cy=Uy;cy<=Ly;cy++){
                for(int cx=Ux;cx<=Lx;cx++){
                    //System.out.println("examining: "+cx+", "+cy);
                    int bucket = cy * GWIDTH + cx;
                    for(int j = queue[bucket];j != -1; j = next[j]){

                        if(i != j){
                            double ox = objects[j].x;
                            double oy = objects[j].y;
                            double dx = ox - x;
                            double dy = oy - y;
                            double dd = dx * dx + dy * dy;
                            
                            //System.out.print("comparing: "+i+" to "+j+" dd:"+dd+" distsqrt: "+distsquared);
                            if(dd < distsquared){
                                double dist = Math.sqrt(dd) - EPSILON;
                                double disp = ((diam - dist)/2)/dist;
                                //System.out.print(" dist: "+dist);

                                objects[i].x -= dx * disp;
                                objects[i].y -= dy * disp;

                                objects[j].x += dx * disp;
                                objects[j].y += dy * disp;


                                //objects[i].direction = Math.atan2(-dy,-dx);
                                //objects[j].direction = Math.atan2(dy,dx);

                                //double speed = objects[i].speed;
                                //objects[i].speed = objects[j].speed;
                                //objects[j].speed = speed;
                            }                            
                        }

                        _avgsin += Math.sin(objects[j].direction);
                        _avgcos += Math.cos(objects[j].direction);
                        _avgspeed += objects[j].speed;
                        _avgx += objects[j].x;
                        _avgy += objects[j].y;
                        _numobjects++;

                    }

                }
            }

            // adjust current object according to averages.
            _avgsin /= _numobjects;
            _avgcos /= _numobjects;
            _avgx /= _numobjects;
            _avgy /= _numobjects;
            _avgspeed /= _numobjects;

            // add some noise into the motion
            _avgsin += 0.2*(Math.random()-0.5);
            _avgcos += 0.2*(Math.random()-0.5);
            objects[i].speed += 0.2*(Math.random()-0.5);

            // find heading into center of local group.
            double _dx = _avgx - objects[i].x ;
            double _dy = _avgy - objects[i].y ;
            // normalize.
            double _ddist = Math.sqrt(_dx * _dx + _dy * _dy);

            // find heading into center of -global- group.
            double _dxall = avgx - objects[i].x;
            double _dyall = avgy - objects[i].y;
            double _ddistall = Math.sqrt(_dxall * _dxall + _dyall * _dyall);
            if(_ddistall == 0)
                _ddistall = EPSILON;
            _dxall /= _ddistall;
            _dyall /= _ddistall;

            // if it's getting a bit too crowded, get away from center.
            if(_numobjects > 8){   //adjust number for different flocks.             
                if(_ddist > 0){
                    _dx /= _ddist;
                    _dy /= _ddist;
                    objects[i].direction = Math.atan2(_avgsin*0.3 + -_dy*0.7,_avgcos*0.3 + -_dx*0.7);
                }else{
                    objects[i].direction = Math.atan2(_avgsin,_avgcos);
                }
            }else{
                if(_ddist > 0){
                    _dx /= _ddist;
                    _dy /= _ddist;
                    objects[i].direction = Math.atan2(
                        _avgsin*0.8 + _dy*0.195 + _dyall*0.005,
                        _avgcos*0.8 + _dx*0.195 + _dxall*0.005);
                }else{
                    objects[i].direction = Math.atan2(_avgsin,_avgcos);
                }
            }
            objects[i].speed += 0.01*(_avgspeed - objects[i].speed);
            
        }


        if (true || damage) {
            g.setColor(Color.white);
            g.fillRect(0, 0, bounds().width, bounds().height);
            
            /*
            // draw grid. 
            g.setColor(Color.gray);
            for(int i=0;i<GWIDTH;i++){
                int j = (int)(i * ((double)width/(double)GWIDTH));
                g.drawLine(j,0,j,height);
            }
            for(int i=0;i<GHEIGHT;i++){
                int j = (int)(i * ((double)height/(double)GHEIGHT));
                g.drawLine(0,j,width,j);
            }
            */
            
            
            int[] modelxs = { (int)(RADIUS*1.4), -(int)(RADIUS*0.6), -(int)(RADIUS*0.6) };
            int[] modelys = { 0, (int)(RADIUS*0.6), -(int)(RADIUS*0.6) };

            int[] modeltxs = {0,0,0};
            int[] modeltys = {0,0,0};

            for(int i=0;i<NUM_OBJS;i++){
                int x = (int)objects[i].x;
                int y = (int)objects[i].y;
               
                double direction = objects[i].direction;
                double sin = Math.sin(direction);
                double cos = Math.cos(direction);

                for(int tripoint = 0;tripoint < 3;tripoint++){
                    modeltxs[tripoint] = x + (int)(cos * modelxs[tripoint] + -sin * modelys[tripoint]);
                    modeltys[tripoint] = y + (int)(sin * modelxs[tripoint] + cos * modelys[tripoint]);
                }

                g.setColor(Color.lightGray);
                //g.fillOval(x-RADIUS,y-RADIUS,RADIUS+RADIUS,RADIUS+RADIUS);
                g.fillPolygon(modeltxs,modeltys,3);

                g.setColor(Color.darkGray);
                //g.drawOval(x-RADIUS,y-RADIUS,RADIUS+RADIUS,RADIUS+RADIUS);
                g.drawPolygon(modeltxs,modeltys,3);
                
            }
            
            g.setColor(Color.gray);
            g.drawRect(0,0, bounds().width-1,bounds().height-1);
        }
    }

    public boolean mouseDown(Event evt, int x, int y){
        return true;
    }
    public boolean mouseUp(Event evt, int x, int y){
        return true;
    }    
    public boolean mouseDrag(Event evt, int x, int y){
        return true;
    }
}


