// Algorithmic Mirror 1.1
// By Luke Cyca
// April 2005
// lcyca@nutok.com
// http://lukecyca.com

import java.applet.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.text.*;
import java.util.*;
import java.util.zip.*;

public class AlgMirror extends BApplet {

//______________________________________________
// GLOBAL VARIABLES
//______________________________________________

int[] mask;
Particle[] ptcls;
int kR, kG, kB;
boolean newFrame = false;

//______________________________________________
// INITIAL PARAMETERS
//______________________________________________

// number of particles
int n_ptcls = 1300;

// video gain
float gainSlope = 2.0f;

// probability that a particle will look for a darker neighbor pixel
float neigh_prob = 0.8f;

// the distances in each direction at which a particle will look for a darker pixel
int scope1 = 6;
int scope2 = 30;

// the amount darker a neighbor pixel must be for a particle to go to it
int diff = 65;

// The lightest the initial average colour can be
int avg_dark = 130;

// Maximum speed a particle can go
int max_speed = 5;

// show video by default?
boolean show_video = false;

//______________________________________________
// MAIN
//______________________________________________

static public void main(String args[])
{
	// this method creates the presentation mode in Mac OS X
	try {
		// get default screendevice and configuration
		GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
		GraphicsConfiguration gc = gd.getDefaultConfiguration();
		Rectangle bounds = gc.getBounds();

		Class c = Class.forName("AlgMirror");
		BApplet applet = (BApplet) c.newInstance();
		applet.init();
		applet.start();

		// frame to hide desktop and other windows ...
		Frame frame_hides_desktop = new Frame();
		frame_hides_desktop.setUndecorated(true);
		frame_hides_desktop.setBackground(Color.white);
		frame_hides_desktop.setBounds(bounds.x, bounds.y, bounds.width, bounds.height);
		frame_hides_desktop.show();
    
		// Hide the cursor
		int[] pixels = new int[16 * 16];
		Image image = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(16, 16, pixels, 0, 16));
		Cursor transparentCursor = Toolkit.getDefaultToolkit().createCustomCursor(image, new Point(0, 0), "invisiblecursor");
		//frame_hides_desktop.setCursor(transparentCursor);
  

		Frame frame = new Frame();
		frame.setUndecorated(true);
		frame.setBackground(Color.white);

		frame.setLayout(new GridBagLayout());
		frame.add(applet);
		frame.pack();
    

		// center applet on screen //
		frame.setLocation(bounds.x+(int)((bounds.width-applet.g.width)/2), bounds.y+(int)((bounds.height-applet.g.height)/2));
    
		frame.show();
		applet.requestFocus();

		frame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
			public void windowDeactivated(WindowEvent e) {
				e.getWindow().toFront();
			}
		});
  	} catch (Exception e) {
		e.printStackTrace();
		System.exit(1);
	}

	// Hide the menu bar and dock
	try {
		// Create a classloader to use this classpath
		StringBuffer strPath = new StringBuffer();
		strPath.append(File.separator);
		strPath.append("System");
		strPath.append(File.separator);
		strPath.append("Library");
		strPath.append(File.separator);
		strPath.append("Java");
		strPath.append(File.separator);
		File systemPath = new File(strPath.toString());

		java.net.URLClassLoader classLoader = new java.net.URLClassLoader( new java.net.URL[] {systemPath.toURL()} );

		// Create the NSMenu object
		Class appleMenuHelper = Class.forName("com.apple.cocoa.application.NSMenu", true, classLoader);

		// Get access to the method we need to hide the menu bar and dock
		Class defArgs[] = {Boolean.TYPE};
		java.lang.reflect.Method setMenuBarVisibleMethod = appleMenuHelper.getDeclaredMethod("setMenuBarVisible", defArgs);
		if (setMenuBarVisibleMethod != null) {
			// Make the menu bar and dock invisible
			Object nargs[] = {Boolean.FALSE};
			setMenuBarVisibleMethod.invoke(appleMenuHelper, nargs);
		}
    
		Class appleCursorHelper = Class.forName("com.apple.cocoa.application.NSCursor", true, classLoader);
    
		// Get access to the method we need to hide the cursor
		java.lang.reflect.Method setCursorVisibleMethod = appleCursorHelper.getDeclaredMethod("hide", null);
		if (setCursorVisibleMethod != null)
		{
			// Hide the cursor
			setCursorVisibleMethod.invoke(appleCursorHelper, null);
		}
    
	} catch (ClassNotFoundException e) {
		System.err.println("Could not find NSMenu");
		System.err.println(e.getMessage());
	} catch (Throwable t) {
		System.err.println("Exception while loading the appleMenuHelper: " +
		t.getMessage());
		t.printStackTrace();
	}
}



//______________________________________________
// SETUP()
// Initializes everything
//______________________________________________

void setup()  {        
	// Initialize environment
	size(800, 600);  
	framerate(80); // or as fast as possible
	noSmooth();
	noCursor();
	background(255);
	g.depthTest = false;
	noStroke();
   
	// Initialize mask array
	mask = new int[320*240];
	for (int i = 0; i < 320*240; i++) {
		mask[i] = 0;
	}
  
	// Begin video capture
	beginVideo(320, 240, 2); // 2 frames of video per second
  
	// Choose initial particle colour
	kR = (int)random(avg_dark);
	kG = (int)random(avg_dark);
	kB = (int)random(avg_dark);
	int pcolor = color(kR, kG, kB);
  
	// Create particles
	ptcls = new Particle[n_ptcls];
	for(int i = 0; i < n_ptcls; i++)
		ptcls[i] = new Particle(random(width), random(height), pcolor);
}


//______________________________________________
// LOOP()
// Runs continuously to generate frames
//______________________________________________
   
void loop()  {  
	// Update luminance mask
	if (newFrame) {
		for (int y = 0; y < 240; y++) {
			for (int x = 0; x < 320; x++) {
				int r = (video.pixels[y*320+(319-x)] >> 16) & 0x000000ff;
				int g = (video.pixels[y*320+(319-x)] >> 8) & 0x000000ff;
				int b = video.pixels[y*320+(319-x)] & 0x000000ff;
				mask[y*320+x] = (int)(0.299f * r + 0.587f * g + 0.114f * b);
			}
		}
		newFrame = false;
	}
  
	// debug flag for showing video frames on screen
	if (show_video) {
		for (int i = 0; i < pixels.length; i++) {
			int g = (int)min(255,getPix(i) * gainSlope);
			pixels[i] = ((g << 16) | (g << 8) | (g));
		}
	}
	else {
		fadescr();
		drawPtcls();
	}
}



//______________________________________________
// keyPressed()
// Handles key presses
//______________________________________________

void keyPressed() {
	if (key == 'g')
		show_video = !show_video;
	else if (key == 'q')
		System.exit(0);
}



//______________________________________________
// videoEvent()
// Sets a flag to indicate a new frame of video
//______________________________________________

void videoEvent() { 
	newFrame = true;
}




//______________________________________________
// fadescr()
// Fades all pixels to white
//______________________________________________

void fadescr() { 
	int red, green, blue;
	for (int i = 0; i < pixels.length; i++) {
		if (pixels[i] != 0x00ffffff) {
			red = (pixels[i] >> 16) & 0x000000ff; 
			green = (pixels[i] >> 8) & 0x000000ff; 
			blue = pixels[i] & 0x000000ff; 
    
			// bit shift by 5 and add it back to each value
			red = red+(((255-red) + 31) >> 5) & 0x000000ff; 
			green = green+(((255-green) + 31) >> 5) & 0x000000ff; 
			blue = blue+(((255-blue) + 31) >> 5) & 0x000000ff; 
    
			pixels[i] = ((red << 16) | (green << 8) | (blue));
		}
	} 
}
 

//______________________________________________
// getPix()
// Returns the luminance value for a given pixel
// also does the math to convert resultions
//______________________________________________

int getPix(int x, int y) {
	if (x < 80 || x >= 720 || y < 60 || y >= 540) {
		return 255;
	}
	else {
		int i = (y/2-30)*320+(x/2-40);
		return mask[i];
	}
}
 
int getPix(int i) {
	return getPix(i % width,(int)floor(i/width));
}
 

//______________________________________________
// drawPtcls()
// Draws all particles
//______________________________________________


void drawPtcls()
{
	for(int i = 0; i < n_ptcls; i++)
	{
		ptcls[i].Move();
		ptcls[i].Draw();
	}
}


 
 
//______________________________________________
// PARTICLE Class
//______________________________________________

class Particle
{
	float x, y, vx, vy, ax, ay;
	int kr, kg, kb;
	int corrections_count;
  
	public Particle(float _x, float _y, int pcolor)
	{
		x = _x;
		y = _y;
		vx = (1-random(2));
		vy = (1-random(2));
		ax = 0;
		ay = 0;
    
		corrections_count = 0;
    
		kr = (int)red(pcolor);
		kg = (int)green(pcolor);
		kb = (int)blue(pcolor);
    
		kr += (int)((1-random(2))*70);
		kg += (int)((1-random(2))*70);
		kb += (int)((1-random(2))*70);
	}
  
	public void Move()
	{
		// accel
		ax += (1-random(2))*0.05f;
		ay += (1-random(2))*0.05f;
    
		// velocity
		vx += ax;
		vy += ay;
    
		// position
		x += vx;    
		y += vy;
    
		// keep them in bounds, keep them from speeding
		boolean corrected = false;
		if ((x < 80 && vx < 0) || vx < -max_speed) {
			ax = 0.2f;
			vx *= 0.7f;
			corrected = true;
		}
		else if ((x > width - 80 && vx > 0) || vx > max_speed) {
			ax = -0.2f;
			vx *= 0.7f;
			corrected = true;
		}
		if ((y < 60 && vy < 0) || vy < -max_speed) {
			ay = 0.2f;
			vy *= 0.7f;
			corrected = true;
		}
		else if ((y > height - 60 && vy > 0) || vy > max_speed) {
			ay = -0.2f;
			vy *= 0.7f;
			corrected = true;
		}
    
		// algorithm to attract to dark areas of the picture
		if (corrected == false && random(1) < neigh_prob) {
			float curr = getPix((int)x,(int)y) * gainSlope;
			float up1   = getPix((int)x,(int)y-scope1) * gainSlope;
			float dn1   = getPix((int)x,(int)y+scope1) * gainSlope;
			float rt1   = getPix((int)x+scope1,(int)y) * gainSlope;
			float lt1   = getPix((int)x-scope1,(int)y) * gainSlope;
			float up2   = getPix((int)x,(int)y-scope2) * gainSlope;
			float dn2   = getPix((int)x,(int)y+scope2) * gainSlope;
			float rt2   = getPix((int)x+scope2,(int)y) * gainSlope;
			float lt2   = getPix((int)x-scope2,(int)y) * gainSlope;
      
			if (up1 < dn1 && up1 < rt1 && up1 < lt1 && up1 < dn2 && up1 < rt2 && up1 < lt2 && up1 < curr - diff) {
				ay = -0.2f;
				vy *= 0.7f;
			}
			else if (dn1 < up1 && dn1 < rt1 && dn1 < lt1 && dn1 < up2 && dn1 < rt2 && dn1 < lt2 && dn1 < curr - diff) {
				ay = 0.2f;
				vy *= 0.7f;
			}
			else if (rt1 < dn1 && rt1 < up1 && rt1 < lt1 && rt1 < dn2 && rt1 < up2 && rt1 < lt2 && rt1 < curr - diff) {
				ax = 0.2f;
				vx *= 0.7f;
			}
			else if (lt1 < dn1 && lt1 < rt1 && lt1 < up1 && lt1 < dn2 && lt1 < rt2 && lt1 < up2 && lt1 < curr - diff) {
				ax = -0.2f;
				vx *= 0.7f;
			}
      
			else if (up2 < dn1 && up2 < rt1 && up2 < lt1 && up2 < dn2 && up2 < rt2 && up2 < lt2 && up2 < curr - diff) {
				ay = -0.2f;
				vy *= 0.7f;
			}
			else if (dn2 < up1 && dn2 < rt1 && dn2 < lt1 && dn2 < up2 && dn2 < rt2 && dn2 < lt2 && dn2 < curr - diff) {
				ay = 0.2f;
				vy *= 0.7f;
			}
			else if (rt2 < dn1 && rt2 < up1 && rt2 < lt1 && rt2 < dn2 && rt2 < up2 && rt2 < lt2 && rt2 < curr - diff) {
				ax = 0.2f;
				vx *= 0.7f;
			}
			else if (lt2 < dn1 && lt2 < rt1 && lt2 < up1 && lt2 < dn2 && lt2 < rt2 && lt2 < up2 && lt2 < curr - diff) {
				ax = -0.2f;
				vx *= 0.7f;
			}
		}
	}
  
	public void Draw()
	{
		fill(kr, kg, kb, 50);
		ellipse(x, y, 5, 3);
	}
}


}