/*
 * 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 double value;

    public int mode;
    public static final int NORMAL = 0;
    public static final int LEADER = 1;
    public static final int DEAD = 2;

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

    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(mode == DEAD){
            nx = x;
            ny = y;
        }

        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;
        if(mode == LEADER)
            speed = 2;

        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;
        initswarm();
    }

    public void initswarm(){

        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,
                Math.random(),
                MovingObject.NORMAL
            );
        }
        objects[0].mode = MovingObject.LEADER;
        objects[0].value = 0;
        objects[1].mode = MovingObject.LEADER;
        objects[1].value = 1;
    }

    /**
     * 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;

            if(objects[i].mode == MovingObject.DEAD)
                continue;

            /*
            {   // 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;
            double _avgvalue = 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;
                            }
                            if(objects[i].mode == MovingObject.LEADER && objects[j].mode == MovingObject.LEADER){

                                double dist = Math.sqrt(dd) - EPSILON;
                                double disp = (((diam*3) - dist)/2)/dist;

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

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

                        if(objects[j].mode != MovingObject.DEAD){
                            _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++;
                            if(objects[j].mode == MovingObject.LEADER && objects[i].mode != MovingObject.LEADER){
                                objects[i].value = objects[j].value;
                            }
                            _avgvalue += objects[j].value;
                        }
                    }

                }
            }

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

            // lean the color of your flock mates.
            if(objects[i].mode == MovingObject.NORMAL){
                
                if(Math.sqrt((_avgvalue - objects[i].value)*(_avgvalue - objects[i].value)) > 0.4){
                    objects[i].mode = MovingObject.DEAD;
                }else{            
                    objects[i].value = objects[i].value + 0.5*(_avgvalue - objects[i].value);
                }
            }            

            // 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;
                int color = (int)(0xFF * objects[i].value);

                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]);
                }

                if(objects[i].mode == MovingObject.NORMAL){
                    g.setColor(new Color(color,color,color));
                    //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);
                }else if(objects[i].mode == MovingObject.LEADER){
                    g.setColor(new Color(color,color,color));
                    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);
                }else if(objects[i].mode == MovingObject.DEAD){

                    g.setColor(new Color(color,color,color));
                    g.fillPolygon(modeltxs,modeltys,3);
                    g.setColor(Color.red);
                    g.drawPolygon(modeltxs,modeltys,3);

                    g.setColor(Color.black);
                    g.drawLine(x-RADIUS,y-RADIUS,x+RADIUS,y+RADIUS);
                    g.drawLine(x+RADIUS,y-RADIUS,x-RADIUS,y+RADIUS);
                    
                    //g.drawRect(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){
        initswarm();
        return true;        
    }    
    public boolean mouseDrag(Event evt, int x, int y){
        return true;
    }
}


