import processing.core.*;
import SGGUI.*;
import SGCamera.*;
import Raytracing.*;
 
 
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 ambientOcclusion extends PApplet {
 
SGCamera cam;
Raytracer tracer;
static final int NONE = 0;
static final int MOVE = 1;
static final int INTERACTIVE = 0;
static final int RENDERING = 1;
static final int RENDERED = 2;
int mouseMode = NONE;
SGGUI controller;
VerticalLayout settings;
SGSlider overSamples, hemiSamples;
SGSlider lightSize, hemiPower;
SGButton update;
int start, finish;
int mode = INTERACTIVE;
PImage rendered;
Vector buckets = new Vector();
Lamp lmp;
 
public void setup() {
 
size(550, 550, P3D);
 
 
controller = new SGGUI(this, loadFont("writing.vlw"));
controller.GUI_TEXT = color(255, 255, 255, 255);
settings = new VerticalLayout(controller, 10, 10, 150);
settings.title = "Settings";
overSamples = new SGSlider("Over Samples", 2, 1, 1, 5);
hemiSamples = new SGSlider("Light Samples", 2, 1, 1, 5);
lightSize = new SGSlider("Light Size", .04f, .01f, .01f, .25f);
hemiPower = new SGSlider("Ambient Light", .4f, .05f, 0, 1);
 
update = new SGButton("Render", this);
update.addGUIListener(this);
 
 
settings.add(lightSize);
settings.add(overSamples);
settings.add(hemiSamples);
settings.add(hemiPower);
settings.add(update);
controller.add(settings);
 
cam = new SGCamera(this, -50, -80, -100, 0, 0, 0, SGCamera.ORBIT, SGCamera.DOLLY);
tracer = new Raytracer(this);
tracer.setLighting(true);
 
frameRate(30);
tracer.openFile("simpleScene.mra", cam);
 
cam.lookAt(0, 0, 0);
lmp = (Lamp) tracer.lights.firstElement();
 
}
 
public void draw() {
 
if (mode == INTERACTIVE) {
background(220, 220, 255);
cam.feed();
tracer.draw();
}
else if (mode == RENDERED || mode == RENDERING) {
background(rendered);
if (buckets.size() == 0 && finish == 0) {
finish = millis();
println("time:" + (finish - start) / 1000.f);
mode = RENDERED;
}
}
 
}
 
public void GUIEventPerformed(GUIEvent e) {
if (mode != RENDERING) {
render();
}
}
 
public void mouseDragged() {
if (mode == RENDERING) {
//cancel all remaining renderbuckets
buckets = new Vector();
}
mode = INTERACTIVE;
 
if (mouseButton == CENTER || mouseMode == MOVE) {
 
lmp.origin.add(PVector.mult(cam.right, 5 * (mouseX - pmouseX)));
lmp.origin.add(PVector.mult(cam.up, 5 * (mouseY - pmouseY)));
}
 
}
 
public void render() {
mode = RENDERING;
finish = 0;
rendered = new PImage(width, height);
//create all the renderbuckets
int numB = 10;
for (int i = 0; i < numB; i++) {
for (int j = 0; j < numB; j++) {
buckets.add(new RenderBucket(i * width / numB, i * width / numB + width / numB, j * height / numB, j * height / numB + height / numB));
}
}
RenderThread thrd;
start = millis();
for (int i = 0; i < 2; i++) {
thrd = new RenderThread();
thrd.start();
}
}
 
public void keyPressed() {
mouseMode = MOVE;
cam.setMouseMode(LEFT, SGCamera.UNBOUND);
}
 
public void keyReleased() {
mouseMode = NONE;
cam.setMouseMode(LEFT, SGCamera.ORBIT);
}
 
int getColor(int x, int y) {
 
//keep track of how many samples have been cast
float samples = 0;
 
//get a reference to the lamp
Lamp lmp = (Lamp) tracer.lights.firstElement();
 
//color samples will be stored in this integrator
PVector accum = new PVector();
 
//jitter used for antialiasing
float dx = 0, dy = 0;
 
for (int i = 0; i < overSamples.getVal() * overSamples.getVal(); i++) {
dx = random(-.5f, .5f);
dy = random(-.5f, .5f);
PVector direction = cam.getRay(x + dx, y + dy);
 
//prepare the picking ray
Ray pick = new Ray(cam.pos, direction);
 
//intersect the camera ray with the scene
tracer.accelerator.findNearest(pick);
 
Triangle tritemp;
 
//if ray intersects anything, get the incident light
if (pick.occluder != null) {
 
 
float ao;
for (int s = 0; s < hemiSamples.getVal() * hemiSamples.getVal(); s++) {
 
//see if this ray can see a light
Ray shadow = new Ray();
shadow.origin = pick.project(pick.distance);
 
//pick whether to cast a light sample or an ao sample
if (random(0, 1) > hemiPower.getVal()) {
ao = lightSize.getVal();
lmp.getSample(shadow, null);
}
//ambient occlusion
else {
ao = 1;
shadow.direction = ((Triangle) pick.occluder).gNormal.get();
}
 
shadow.direction.x += random(-ao, ao);
shadow.direction.y += random(-ao, ao);
shadow.direction.z += random(-ao, ao);
shadow.direction.normalize();
shadow.minDist = .01f;
 
tracer.accelerator.findNearest(shadow);
samples++;
 
if (shadow.occluder == null) {
tritemp = (Triangle) pick.occluder;
float k = abs(shadow.direction.dot(tritemp.gNormal));
accum.add(PVector.mult(tritemp.shader.fillColor, 255 * k));
}
}
}
 
}
accum.mult(1.f / samples);
return doColor(accum.x, accum.y, accum.z);
}
 
synchronized int doColor(float x, float y, float z) {
return color(x, y, z);
}
 
class RenderThread extends Thread {
 
// We must implement run, this gets triggered by start()
public void run() {
 
//if more buckets, get one
while (buckets.size() > 0) {
//get the next bucket from the queque
RenderBucket bucket = (RenderBucket) buckets.elementAt((int) random(0, buckets.size()));
buckets.remove(bucket);
 
for (int i = bucket.minX; i < bucket.maxX; i++) {
for (int j = bucket.minY; j < bucket.maxY; j++) {
rendered.pixels[j * width + i] = getColor(i, j);
}
}
}
}
}
 
class RenderBucket {
 
int minX, maxX, minY, maxY;
 
RenderBucket(int minx, int maxx, int miny, int maxy) {
minX = minx;
minY = miny;
maxX = maxx;
maxY = maxy;
}
}
 
static public void main(String args[]) {
PApplet.main(new String[]{"AmbientOcclusion"});
}
}