LRay.java
Java source for LRay - Java Raytracer, 871 lines. Raw file
import java.awt.image.*;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Event;
import java.awt.Image;
import java.awt.Font;
//---- MAIN APPLET -----------------------------------------------------------//
public class LRay extends java.applet.Applet implements Runnable {
Thread runThread;
Image im = null;
public void init() {
System.out.println("LKS Raytrace applet");
}
public void start() {
if(runThread == null) {
runThread = new Thread(this);
runThread.start();
}
}
public void stop() {
if(runThread != null) {
runThread.stop();
runThread = null;
}
}
public void run() {
int w = this.getBounds().width;
int h = this.getBounds().height;
im = createImage(w, h);
Texture texOrange = new Texture(1.0, 0.7, 0.0, 0.0, 0.5);
Texture texPurple = new Texture(0.7, 0.0, 1.0, 0.0, 0.5);
Texture texWhite = new Texture(1.0, 1.0, 1.0, 0.0, 0.7);
Texture texBlack = new Texture(0.0, 0.0, 0.0, 0.0, 0.0);
Texture texMirror = new Texture(0.45, 0.45, 0.45, 0.0, 0.7);
Texture texChecks = new Texture(new ChecksColourMap(0.6, 0.6, 0.6, 0.3, 0.3, 0.3), 0.7);
Texture texSky = new Texture(new ChecksColourMap(0.6, 0.8, 1.0, 0.2, 0.3, 0.5), 0.3);
Scene s = new Scene();
s.addLight(new Light(1.0, 5.0, 2.0, 1.0, 1.0, 1.0));
s.addLight(new Light(-1.0, 7.0, 3.0, 0.7, 0.7, 0.7));
// s.addEntity(new Sphere(0.0, 0.0, 0.0, 20000.0, texBlack));
s.addEntity(new Plane( 0.0, 1.0, 0.0, -1.5, texChecks));
s.addEntity(new Sphere( 1.0, -1.0, 6.0, 1.0, texOrange));
s.addEntity(new Sphere( 1.0, 3.0, 6.0, 1.5, texPurple));
s.addEntity(new Sphere(-1.0, 2.0, 5.0, 1.2, texSky));
s.addEntity(new Sphere(-3.0, 1.0, 6.0, 1.5, texWhite ));
s.addEntity(new Sphere(-3.0, -1.0, 6.0, 1.5, texPurple));
s.addEntity(new Sphere( 4.0, 3.5, 4.0, 0.7, texWhite));
s.addEntity(new Sphere( 3.0, -1.5, 2.0, 0.7, texWhite));
s.addEntity(new Sphere( 4.0, -1.5, 5.0, 0.7, texPurple));
s.addEntity(new Sphere( 5.0, -1.5, 8.0, 0.7, texWhite));
s.addEntity(new Sphere( 6.0, -1.5, 11.0, 0.7, texPurple));
s.addEntity(new Sphere( 7.0, -1.5, 14.0, 0.7, texWhite));
s.addEntity(new Sphere( 8.0, -1.5, 17.0, 0.7, texPurple));
Graphics img = im.getGraphics();
Color c;
for(int j=0; j<h; j+=32) {
for(int i=0; i<w; i+=32) {
img.setColor(s.getColor((i-(w*0.5))/(h*0.5), ((h*0.5)-j)/(h*0.5)));
img.fillRect(i, j, 32, 32);
}
repaint();
}
repaint();
for(int j=0; j<h; j+=4) {
for(int i=0; i<w; i+=4) {
img.setColor(s.getColor((i-(w*0.5))/(h*0.5), ((h*0.5)-j)/(h*0.5)));
img.fillRect(i, j, 4, 4);
}
if((j&31) == 0) repaint();
}
repaint();
for(int j=0; j<h; j+=4) {
for(int i=0; i<w; i+=4) {
img.setColor(s.getColor((i+2-(w*0.5))/(h*0.5), ((h*0.5)-j)/(h*0.5)));
img.fillRect(i+2, j, 2, 2);
img.setColor(s.getColor((i-(w*0.5))/(h*0.5), ((h*0.5)-j-2)/(h*0.5)));
img.fillRect(i, j+2, 2, 2);
img.setColor(s.getColor((i+2-(w*0.5))/(h*0.5), ((h*0.5)-j-2)/(h*0.5)));
img.fillRect(i+2, j+2, 2, 2);
}
if((j&15) == 0) repaint();
}
repaint();
for(int j=0; j<h; j+=2) {
for(int i=0; i<w; i+=2) {
img.setColor(s.getColor((i+1-(w*0.5))/(h*0.5), ((h*0.5)-j)/(h*0.5)));
img.fillRect(i+1, j, 1, 1);
img.setColor(s.getColor((i-(w*0.5))/(h*0.5), ((h*0.5)-j-1)/(h*0.5)));
img.fillRect(i, j+1, 1, 1);
img.setColor(s.getColor((i+1-(w*0.5))/(h*0.5), ((h*0.5)-j-1)/(h*0.5)));
img.fillRect(i+1, j+1, 1, 1);
}
if((j&7) == 0) repaint();
}
repaint();
double r, g, b;
for(int j=0; j<h; j++) {
for(int i=0; i<w; i++) {
r = 0; g = 0; b = 0;
for(int y=0; y<3; y++) for(int x=0; x<3; x++) {
c = s.getColor((i+x*(1.0/3.0)-(w*0.5))/(h*0.5), ((h*0.5)-j-y*(1.0/3.0))/(h*0.5));
r += c.getRed();
g += c.getGreen();
b += c.getBlue();
}
img.setColor(new Color((int)(r/9.0), (int)(g/9.0), (int)(b/9.0)));
img.fillRect(i, j, 1, 1);
}
if((j&3) == 0) repaint();
}
repaint();
}
public void update(Graphics g) {
paint(g);
}
public void paint(Graphics g) {
if(im != null) g.drawImage(im, 0, 0, this);
}
}
//---- Scene ---------------------------------------------------------------//
class Scene {
Entity[] entity;
Light[] light;
int numEntities = 0;
int numLights = 0;
public Scene() {
entity = new Entity[32];
light = new Light[32];
}
public void addEntity(Entity e) {
entity[numEntities++] = e;
}
public void addLight(Light l) {
light[numLights++] = l;
}
public Color getColor(double x, double y) {
return getPigmentRecursive(new Ray(0, 0, 0, x, y, 1.0), 5).getColor();
}
private Pigment getPigmentRecursive(Ray r, int level) {
Pigment p = new Pigment(0, 0, 0);
//- Find the first object that this that this ray intersects
Intersection in = null;
for(int i=0; i<numEntities; i++) {
in = Intersection.getNearest(in, entity[i].intersectRay(r));
}
if(in == null) return p;
//- Get the colour at this point
Texture t = in.e.getTexture();
if(t.refl == 0.0 || level == 0) {
p = t.getPigment(in.getPoint());
} else if(t.fuzzy == 0.0) {
p = getPigmentRecursive(in.reflectionRay(), level-1);
p.blend(t.getPigment(in.getPoint()), 1.0-t.refl);
} else {
int i, j, k;
for(k=-1; k<=1; k++) for(j=-1; j<=1; j++) for(i=-1; i<=1; i++)
p.add( getPigmentRecursive(in.reflectionRay().distort(i*t.fuzzy, j*t.fuzzy, k*t.fuzzy), level-1) );
p.scale(1.0/(27.0));
p.blend(t.getPigment(in.getPoint()), 1.0-t.refl);
}
//- Perform lighting against each light
Intersection sin;
Ray sray;
double diffuse = 0, phong = 0;
for(int i=0; i<numLights; i++) {
// Make a ray to this light
Vector u = light[i].getDirection(in.getPoint());
sray = new Ray(in.getPoint(), u);
// See if this intersects any objects
sin = null;
for(int j=0; j<numEntities; j++) {
sin = Intersection.getNearest(sin, entity[j].intersectRay(sray));
}
// No objects intersected before reaching light source so calculate lighting value
if(sin == null || sin.t >= 1) {
Vector v = in.getReflection();
u.setLength(1.0);
v.setLength(1.0);
double d = u.x*v.x + u.y*v.y + u.z*v.z;
if(d > 0) {
d *= d*d;
d *= d*d;
d *= d*d;
phong += d;
}
v = in.getNormal();
d = u.x*v.x + u.y*v.y + u.z*v.z;
if(d > 0) {
diffuse += d;
}
}
}
if(diffuse < 0.3) diffuse = 0.3;
p.lightDiffuse(diffuse);
p.lightPhong(phong);
//- Done
return p;
}
}
//---- Vector --------------------------------------------------------------//
class Vector {
public double x, y, z;
public Vector(double x, double y, double z) {
this.x = x; this.y = y; this.z = z;
}
public Vector(Vector v) {
this.x = v.x; this.y = v.y; this.z = v.z;
}
public double getX() { return x; }
public double getY() { return y; }
public double getZ() { return z; }
public void setX() { this.x = x; }
public void setY() { this.y = y; }
public void setZ() { this.z = z; }
public double getLength() {
return Math.sqrt(x*x+y*y+z*z);
}
public void setLength(double d) {
double dd = d/Math.sqrt(x*x+y*y+z*z);
x *= dd; y *= dd; z *= dd;
}
public double dotProduct(Vector v) {
return x*v.x + y*v.y + z*v.z;
}
public static double dotProduct(Vector u, Vector v) {
return u.x*v.x + u.y*v.y + u.z*v.z;
}
public Vector crossProduct(Vector v) {
return new Vector(y*v.z-z*v.y, z*v.x-x*v.z, x*v.y-y*v.x);
}
public static Vector crossProduct(Vector u, Vector v) {
return new Vector(u.y*v.z-u.z*v.y, u.z*v.x-u.x*v.z, u.x*v.y-u.y*v.x);
}
}
//---- Ray -----------------------------------------------------------------//
class Ray {
public double x, y, z;
public double dx, dy, dz;
public Ray(Vector origin, Vector direction) {
x = origin.x; y = origin.y; z = origin.z;
dx = direction.x; dy = direction.y; dz = direction.z;
}
public Ray(double x, double y, double z, double dx, double dy, double dz) {
this.x = x; this.y = y; this.z = z;
this.dx = dx; this.dy = dy; this.dz = dz;
}
public Ray fuzzy(double f) {
double d = Math.sqrt(dx*dx+dy*dy+dz*dz);
return new Ray(x, y, z, dx + d*(Math.random()*f*2-f), dy + d*(Math.random()*f*2-f), dz + d*(Math.random()*f*2-f));
}
public Ray distort(double fx, double fy, double fz) {
double d = Math.sqrt(dx*dx+dy*dy+dz*dz);
return new Ray(x, y, z, dx + d*fz, dy + d*fy, dz + d*fz);
}
}
//---- Intersection --------------------------------------------------------//
class Intersection {
public double x, y, z;
public double nx, ny, nz;
public double reflx, refly, reflz;
public double t;
public Entity e;
public Intersection(Entity e, double t) {
this.e = e; this.t = t;
}
public boolean isNearer(Intersection inter) {
return inter == null || (t > 0 && t < inter.t);
}
public static Intersection getNearest(Intersection in1, Intersection in2) {
if(in1 == null) return in2;
if(in2 == null) return in1;
if(in1.t < 0.000001 && in2.t < 0.000001) return null;
if(in1.t < 0.000001) return in2;
if(in2.t < 0.000001) return in1;
if(in1.t < in2.t) return in1;
return in2;
}
public void setPoint(double x, double y, double z) {
this.x = x; this.y = y; this.z = z;
}
public Vector getPoint() {
return new Vector(x, y, z);
}
public void setNormal(double x, double y, double z) {
this.nx = x; this.ny = y; this.nz = z;
}
public Vector getNormal() {
return new Vector(nx, ny, nz);
}
public void setReflection(double x, double y, double z) {
this.reflx = x; this.refly = y; this.reflz = z;
}
public Vector getReflection() {
return new Vector(reflx, refly, reflz);
}
public Ray reflectionRay() {
return new Ray(x, y, z, reflx, refly, reflz);
}
}
//---- Pigment -------------------------------------------------------------//
class Pigment {
public static Pigment red = new Pigment(1.0, 0.0, 0.0);
public static Pigment green = new Pigment(0.0, 1.0, 0.0);
public static Pigment blue = new Pigment(0.0, 0.0, 1.0);
public static Pigment cyan = new Pigment(0.0, 1.0, 1.0);
public static Pigment yellow = new Pigment(1.0, 1.0, 0.0);
public static Pigment magenta = new Pigment(0.0, 1.0, 1.0);
public static Pigment orange = new Pigment(1.0, 0.7, 0.0);
public static Pigment purple = new Pigment(0.7, 0.0, 1.0);
public static Pigment black = new Pigment(0.0, 0.0, 0.0);
public static Pigment grey = new Pigment(0.5, 0.5, 0.5);
public static Pigment white = new Pigment(1.0, 1.0, 1.0);
public double r, g, b, f;
public Pigment() {
r = 0.0; g = 0.0; b = 0.0; f = 0.0;
}
public Pigment(Pigment p) {
r = p.r; g = p.g; b = p.b; f = p.f;
}
public Pigment(double r, double g, double b) {
this.r = r; this.g = g; this.b = b; this.f = 0.0;
}
public Pigment(double r, double g, double b, double f) {
this.r = r; this.g = g; this.b = b; this.f = f;
}
public void blend(Pigment p, double f) {
r = p.r*f + r*(1-f);
g = p.g*f + g*(1-f);
b = p.b*f + b*(1-f);
}
public void scale(double f) {
r *= f; g *= f; b *= f;
}
public void add(Pigment p) {
r += p.r; g += p.g; b += p.b;
}
public void lightDiffuse(double f) {
r *= f; g *= f; b *= f;
}
public void lightPhong(double f) {
r = r*(1-f)+f; g = g*(1-f)+f; b = b*(1-f)+f;
}
public Color getColor() {
int cr = (int)(255.0*r), cg = (int)(255.0*g), cb = (int)(255.0*b);
if(cr < 0) cr = 0; if(cg < 0) cg = 0; if(cb < 0) cb = 0;
if(cr > 255) cr = 255; if(cg > 255) cg = 255; if(cb > 255) cb = 255;
return new Color(cr, cg, cb);
}
}
//---- ColourMap -----------------------------------------------------------//
abstract class ColourMap {
abstract public Pigment getPigment(double x, double y, double z);
abstract public Pigment getPigment(Vector v);
}
//---- ColourMap/SolidColourMap --------------------------------------------//
abstract class SolidColourMap extends ColourMap {
Pigment p;
public SolidColourMap(Pigment p) {
this.p = p;
}
public Pigment getPigment(double x, double y, double z) {
return new Pigment(p);
}
public Pigment getPigment(Vector v) {
return new Pigment(p);
}
}
//---- ColourMap/ChecksColourMap -------------------------------------------//
class ChecksColourMap extends ColourMap {
Pigment p1, p2;
public ChecksColourMap(double r1, double g1, double b1, double r2, double g2, double b2) {
p1 = new Pigment(r1, g1, b1); p2 = new Pigment(r2, g2, b2);
}
public ChecksColourMap(Pigment p1, Pigment p2) {
this.p1 = p1; this.p2 = p2;
}
public Pigment getPigment(double x, double y, double z) {
int i = (int)x, j = (int)y, k = (int)z;
if(x<0) i--;
if(y<0) j--;
if(z<0) k--;
return new Pigment( ((i^j^k)&1) == 1 ? p2 : p1 );
}
public Pigment getPigment(Vector v) {
return getPigment(v.x, v.y, v.z);
}
}
//---- ColourMap/SkyColourMap ----------------------------------------------//
// Doesn't work properly yet!
class SkyColourMap extends ColourMap {
double[][][] d;
Pigment p1, p2;
public SkyColourMap(double r1, double g1, double b1, double r2, double g2, double b2) {
p1 = new Pigment(r1, g1, b1); p2 = new Pigment(r2, g2, b2);
createNoise();
}
public SkyColourMap(Pigment p1, Pigment p2) {
this.p1 = p1; this.p2 = p2;
createNoise();
}
private void createNoise() {
d = new double[8][8][8];
for(int k=0; k<8; k++) for(int j=0; j<8; j++) for(int i=0; i<8; i++) d[k][j][i] = Math.random();
}
public Pigment getPigment(double x, double y, double z) {
int i = (int)(x*5), j = (int)(y*5), k = (int)(z*5);
if(x<0) i--;
if(y<0) j--;
if(z<0) k--;
i &= 7; j &= 7; k &= 7;
int ii = (i+1)&7, jj = (j+1)&7, kk = (k+1)&7;
double dx = x*5-i, dy = y*5-j, dz = z*5-k;
double dd = d[k ][j ][i ]*(1.0-dx)*(1.0-dy)*(1.0-dz) +
d[k ][j ][ii]*( dx)*(1.0-dy)*(1.0-dz) +
d[k ][jj][i ]*(1.0-dx)*( dy)*(1.0-dz) +
d[k ][jj][ii]*( dx)*( dy)*(1.0-dz) +
d[kk][j ][i ]*(1.0-dx)*(1.0-dy)*( dz) +
d[kk][j ][ii]*( dx)*(1.0-dy)*( dz) +
d[kk][jj][i ]*(1.0-dx)*( dy)*( dz) +
d[kk][jj][ii]*( dx)*( dy)*( dz);
dd = dx;
return new Pigment(p1.r*dd+p2.r*(1-dd), p1.g*dd+p2.g*(1-dd), p1.b*dd+p2.b*(1-dd));
}
public Pigment getPigment(Vector v) {
return getPigment(v.x, v.y, v.z);
}
}
//---- BumpMap -------------------------------------------------------------//
class BumpMap {
public BumpMap() {
}
public Vector getBump(Vector v, Vector n) {
return n;
}
}
//---- SurfaceMap ----------------------------------------------------------//
class SurfaceMap {
public SurfaceMap() {
}
}
//---- Texture -------------------------------------------------------------//
class Texture {
public ColourMap colour = null;
public double colr=0.0, colg=0.0, colb=0.0, colf=0.0;
public Pigment pigment;
public SurfaceMap surface = null;
public double refl=0.0;
public double fuzzy=0.0;
public BumpMap bump = null;
public Texture() { }
public Texture(ColourMap c) {
colour = c;
}
public Texture(ColourMap c, double refl) {
colour = c; this.refl = refl;
}
public Texture(ColourMap c, SurfaceMap s) {
colour = c; surface = s;
}
public Texture(ColourMap c, SurfaceMap s, BumpMap b) {
colour = c; surface = s; bump = b;
}
public Texture(double r, double g, double b) {
colr = r; colg = g; colb = b;
pigment = new Pigment(r, g, b);
}
public Texture(double r, double g, double b, double f) {
colr = r; colg = g; colb = b; colf = f;
pigment = new Pigment(r, g, b, f);
}
public Texture(double r, double g, double b, double f, double refl) {
colr = r; colg = g; colb = b; colf = f; this.refl=refl;
pigment = new Pigment(r, g, b, f);
}
public Pigment getPigment(Vector v) {
if(colour == null)
return new Pigment(colr, colg, colb);
else
return colour.getPigment(v);
}
}
//---- Light ---------------------------------------------------------------//
class Light {
double x, y, z;
double r, g, b;
public Light(Vector v, double r, double g, double b) {
x = v.x; y = v.y; z = v.z; this.r = r; this.g = g; this.b = b;
}
public Light(double x, double y, double z, double r, double g, double b) {
this.x = x; this.y = y; this.z = z; this.r = r; this.g = g; this.b = b;
}
public Vector getDirection(double vx, double vy, double vz) {
Vector vv = new Vector(x-vx, y-vy, z-vz);
return vv;
}
public Vector getDirection(Vector v) {
Vector vv = new Vector(x-v.x, y-v.y, z-v.z);
return vv;
}
}
//---- Entity --------------------------------------------------------------//
abstract class Entity {
private Texture t;
public void setTexture(Texture t) {
this.t = t;
}
public Texture getTexture() {
return this.t;
}
abstract public Intersection intersectRay(Ray r);
}
//---- Entity/Sphere -------------------------------------------------------//
class Sphere extends Entity {
double x, y, z;
double r;
public Sphere(Vector v, double r) {
x = v.x; y = v.y; z = v.z; this.r = r;
}
public Sphere(double x, double y, double z, double r) {
this.x = x; this.y = y; this.z = z; this.r = r;
}
public Sphere(double x, double y, double z, double r, Texture t) {
this.x = x; this.y = y; this.z = z; this.r = r; super.setTexture(t);
}
public Intersection intersectRay(Ray ray) {
double a, b, c, d, t1, t2;
Intersection in = null;
a = ray.dx*ray.dx + ray.dy*ray.dy + ray.dz*ray.dz;
b = 2*(ray.dx*(ray.x-x) + ray.dy*(ray.y-y) + ray.dz*(ray.z-z));
c = (ray.x-x)*(ray.x-x) + (ray.y-y)*(ray.y-y) + (ray.z-z)*(ray.z-z) - r*r;
d = b*b-4*a*c;
if(d > 0) {
d = Math.sqrt(d);
t2 = (-b + d) / (2*a);
t1 = (-b - d) / (2*a);
in = Intersection.getNearest(new Intersection((Entity)this, t1), new Intersection((Entity)this, t2));
if(in != null) {
in.x = ray.x + in.t*ray.dx;
in.y = ray.y + in.t*ray.dy;
in.z = ray.z + in.t*ray.dz;
a = 1.0/r;
in.nx = a*(in.x - x);
in.ny = a*(in.y - y);
in.nz = a*(in.z - z);
a = - (ray.dx*in.nx + ray.dy*in.ny + ray.dz*in.nz);
in.reflx = ray.dx + 2*in.nx*a;
in.refly = ray.dy + 2*in.ny*a;
in.reflz = ray.dz + 2*in.nz*a;
}
}
return in;
}
}
//---- Entity/Plane --------------------------------------------------------//
class Plane extends Entity {
double x, y, z;
double nx, ny, nz;
public Plane(Vector n, Vector v, Texture t) {
x = v.x; y = v.y; z = v.z; nx = n.x; ny = n.y; nz = n.z; super.setTexture(t);
normalise();
}
public Plane(Vector n, double d, Texture t) {
x = d*n.x; y = d*n.y; z = d*n.z; nx = n.x; ny = n.y; nz = n.z; super.setTexture(t);
normalise();
}
public Plane(double nx, double ny, double nz, double d, Texture t) {
x = d*nx; y = d*ny; z = d*nz; this.nx = nx; this.ny = ny; this.nz = nz; super.setTexture(t);
normalise();
}
private void normalise() {
double n = 1.0/Math.sqrt(nx*nx+ny*ny+nz*nz);
nx *= n; ny *= n; nz *= n;
}
public Intersection intersectRay(Ray ray) {
Intersection in = null;
double t1, t2, t, a;
t1 = ray.dx*nx + ray.dy*ny + ray.dz*nz;
t2 = (ray.x-x)*nx + (ray.y-y)*ny + (ray.z-z)*nz;
t = -t2/t1;
if(t > 0.000001) {
in = new Intersection(this, t);
in.x = ray.x + t*ray.dx;
in.y = ray.y + t*ray.dy;
in.z = ray.z + t*ray.dz;
in.setNormal(nx, ny, nz);
a = - (ray.dx*in.nx + ray.dy*in.ny + ray.dz*in.nz);
in.reflx = ray.dx + 2*in.nx*a;
in.refly = ray.dy + 2*in.ny*a;
in.reflz = ray.dz + 2*in.nz*a;
}
return in;
}
}
//---- END OF CODE ---------------------------------------------------------//