/**
* Copyright marmph ( http://wonderfl.net/user/marmph )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/jY0r
*/
package {
//cam is the origin
import flash.display.Sprite;
import flash.display.BitmapData;
import flash.display.Bitmap;
import flash.display.BitmapDataChannel;
import flash.events.Event;
import flash.filters.BlurFilter;
import flash.geom.Point;
import flash.geom.ColorTransform;
import flash.events.MouseEvent;
[SWF(backgroundColor="0", width="400", height="400", frameRate="30")]
public class RayTracer extends Sprite {
//not quite accurate
private const WIDTH:Number = 100;
private const HEIGHT:Number = 100;
private const IMAGE_WIDTH:Number = 150;
private const IMAGE_HEIGHT:Number = 150;
private const SCALE_X:Number = WIDTH/IMAGE_WIDTH;
private const SCALE_Y:Number = HEIGHT/IMAGE_HEIGHT;
private const CENTER_X:int = IMAGE_WIDTH/2;//image-wise
private const CENTER_Y:int = IMAGE_HEIGHT/2;
private const DRAW_DISTANCE:int = 500;
private const RED_AMBIENT:Number = 0//0.05;
private const BLUE_AMBIENT:Number = 0;
private const GREEN_AMBIENT:Number = 0//0.02;
private var backgroundColor:Color;
private var bufferSwitch:int = 0; //0 composite, 1 AO, 2 Bloom
private var buffer:BitmapData; //main color/diffuse buffer
private var bitmap:Bitmap;
private var redBuffer:BitmapData;
private var greenBuffer:BitmapData;
private var blueBuffer:BitmapData;
private var AOBuffer:BitmapData;
private var glowBuffer:BitmapData;
private var cur:Vector3D;
private var stop:Boolean = false;
///////////////////////////////////TEMP
private var spheres:Array = [];
private var lights:Array = [];
///////////////////////////////////
private var cam:Vector3D;
public function RayTracer() {
buffer = new BitmapData(IMAGE_WIDTH, IMAGE_HEIGHT, false, 0);
bitmap = new Bitmap(buffer);
bitmap.scaleY = stage.stageHeight/IMAGE_HEIGHT;
bitmap.scaleX = stage.stageWidth/IMAGE_WIDTH;
redBuffer = new BitmapData(IMAGE_WIDTH, IMAGE_HEIGHT, false, 0);
greenBuffer = new BitmapData(IMAGE_WIDTH, IMAGE_HEIGHT, false, 0);
blueBuffer = new BitmapData(IMAGE_WIDTH, IMAGE_HEIGHT, false, 0);
AOBuffer = new BitmapData(IMAGE_WIDTH, IMAGE_HEIGHT, false, 0xffffff);
addChild(bitmap);
backgroundColor = new Color(0, 0, 0);
cur = new Vector3D(0, 0, 0);
cam = new Vector3D(0, 0, -80);
var specularColor:Color = new Color(1, 1, 1);
var reddish:Material = new Material();
reddish.diffuseColor = new Color(1, 0, 0);
reddish.specularColor = specularColor;
reddish.specularSize = 0;
reddish.specularPower = 0;
reddish.reflectivity = 0;
var blueish:Material = new Material();
blueish.diffuseColor = new Color(0, .5, 1);
blueish.specularColor = specularColor;
blueish.specularSize = .05;
blueish.specularPower = 0;
blueish.reflectivity = 0;
var greenish:Material = new Material();
greenish.diffuseColor = new Color(0, 1, 0);
greenish.specularColor = specularColor;
greenish.specularSize = .05;
greenish.specularPower = 0;
greenish.reflectivity = 0;
var grey:Material = new Material();
grey.diffuseColor = new Color(.3, .3, .3);
grey.specularColor = specularColor;
grey.specularSize = .05;
grey.specularPower = 0;
grey.reflectivity = 0;
var reflectyGrey:Material = new Material();
reflectyGrey.diffuseColor = new Color(1, 1, 1);
reflectyGrey.specularColor = specularColor;
reflectyGrey.specularSize = 0;
reflectyGrey.specularPower = 0;
reflectyGrey.reflectivity = .8;
spheres = [new Sphere(new Vector3D(0, 0, 100), 25, reflectyGrey),
new Sphere(new Vector3D(-60, 40, 130), 40, reflectyGrey),
new Sphere(new Vector3D(50, -40, 100), 20, greenish),
new Sphere(new Vector3D(50, 15, 80), 25, reddish),
new Sphere(new Vector3D(-25, -30, 50), 20, blueish),
new Plane(new Vector3D(0, 40, 100), new Vector3D(0, -1, 0), grey),
new Plane(new Vector3D(0, 40, 130), new Vector3D(0, 0, -1), grey),
];
lights = [new Light(new Vector3D(-50, -50, 80), new Color(1, 1, 1), 3000),
new Light(new Vector3D(50, 0, 20), new Color(.8, .8, .8), 1000),
new Light(new Vector3D(-50, 0, 0), new Color(.8, .8, .8), 2000),
new Light(new Vector3D(60, 0, 110), new Color(1, 1, 5), 500),
new Light(new Vector3D(0, -150, 50), new Color(.5, .5, .5), 8000),
new Light(new Vector3D(-50, -50, -40), new Color(.5, .5, .5), 8000)
];
/*lights = [new Light(new Vector3D(50, -50, 0), new Color(1, 1, 1), 8000),
new Light(new Vector3D(-50, -50, 80), new Color(1, 1, 1), 8000)];*/
addEventListener(Event.ENTER_FRAME, draw);
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
}
public function onMouseDown(e:MouseEvent):void {
stop = false;
bufferSwitch++;
if(bufferSwitch > 2) bufferSwitch = 0;
switch(bufferSwitch) {
case 0:
bitmap.bitmapData = buffer;
break;
case 1:
bitmap.bitmapData = AOBuffer;
break;
case 2:
bitmap.bitmapData = glowBuffer;
break;
}
}
public function draw(e:Event):void {
if(stop) return;
while(cur.x < IMAGE_WIDTH) {
var ray:Vector3D = toRealSpace(cur).minus(cam).normalized();
var color:Color = getColorFromCastRay(ray, cam, 4);
var pixelColor:int = color.hex();
buffer.setPixel(cur.x, cur.y, pixelColor);
// buffer.setPixel(cur.x, cur.y, 0xffffff*cam.dist(ray)*.02); //for trippiness
cur.x++;
}
cur.x = 0;
cur.y++;;
if(cur.y == IMAGE_HEIGHT) {
var p:Point = new Point(0, 0);
redBuffer.copyChannel(buffer, buffer.rect, p, BitmapDataChannel.RED, BitmapDataChannel.RED);
greenBuffer.copyChannel(buffer, buffer.rect, p, BitmapDataChannel.GREEN, BitmapDataChannel.GREEN);
blueBuffer.copyChannel(buffer, buffer.rect, p, BitmapDataChannel.BLUE, BitmapDataChannel.BLUE);
redBuffer.threshold(redBuffer, buffer.rect, p, "<", 0x880000, 0, 0xff0000, true);
greenBuffer.threshold(greenBuffer, buffer.rect, p, "<", 0x8800, 0, 0xff00, true);
blueBuffer.threshold(blueBuffer, buffer.rect, p, "<", 0x88, 0, 0xff, true);
glowBuffer = new BitmapData(IMAGE_WIDTH, IMAGE_HEIGHT, false, 0);
glowBuffer.copyChannel(redBuffer, buffer.rect, p, BitmapDataChannel.RED, BitmapDataChannel.RED);
glowBuffer.copyChannel(greenBuffer, buffer.rect, p, BitmapDataChannel.GREEN, BitmapDataChannel.GREEN);
glowBuffer.copyChannel(blueBuffer, buffer.rect, p, BitmapDataChannel.BLUE, BitmapDataChannel.BLUE);
glowBuffer.applyFilter(glowBuffer, glowBuffer.rect, p, new BlurFilter(24, 24, 2));
// buffer.fillRect(buffer.rect, 0xffffff);
AOBuffer.applyFilter(AOBuffer, AOBuffer.rect, p, new BlurFilter(4, 4, 2));
buffer.draw(AOBuffer, null, new ColorTransform(1.2, 1.2, 1.2), "multiply");
buffer.draw(glowBuffer, null, new ColorTransform(.6, .6, .6), "add");
removeEventListener(Event.ENTER_FRAME, draw);
}
}
///////////////////////////////////////////////////////////////////-----raydepth/////
public function getColorFromCastRay(ray:Vector3D, origin:Vector3D, rays:int=1):Color { //returns color of material/object this ray intersects
var minDist:Number = DRAW_DISTANCE;
var closestObject:RenderObject;
for each(var s:RenderObject in spheres) { //no good occlusion yet
var dist:Number = s.getIntersectionDistance(ray, origin);
if(dist <= 0) continue;
if(isNaN(dist)) continue;
if(dist < minDist) {
minDist = dist;
closestObject = s;
}
}
var color:Color = new Color(RED_AMBIENT, GREEN_AMBIENT, BLUE_AMBIENT);
if(closestObject != null) {
var objectMaterial:Material = closestObject.material();
var pixelPosition:Vector3D = origin.plus(ray.normalized().multiply(minDist));
var normal:Vector3D = closestObject.normalAtPoint(pixelPosition);
//lighting
for each(var light:Light in lights) {
var lightPosition:Vector3D = light.position;
var lightDir:Vector3D = pixelPosition.minus(lightPosition).normalized();
var lightMultiplier:Number = 1;
//shadows
var shadowSamples:Number = 20;
var shadowHits:Number = 0;
var lightSize:Number = 5;
for(var z:int = 0; z < shadowSamples; z++) {
var sampleRad:Vector3D = new Vector3D(Math.random()*2-1, Math.random()*2-1, Math.random()*2-1).normalized().multiply(lightSize);
if(sampleRad.dot(lightDir) < 0) sampleRad.multiply(-1);
var samplePoint:Vector3D = lightPosition.plus(sampleRad);
for each(var obj:RenderObject in spheres) {
if(obj == closestObject) continue;
var diff:Number = obj.getIntersectionDistance(samplePoint.minus(pixelPosition).normalized(), pixelPosition);
if(diff <= 0) continue;
if(isNaN(diff)) continue;
if(diff < pixelPosition.dist(lightPosition)) {
shadowHits++;
break;
}
}
}
lightMultiplier = 1-shadowHits/shadowSamples;
var normalDifference:Number = lightDir.dot(normal)*-1;
if(normalDifference > 0) { //diffuse
var diffuseColor:Color = objectMaterial.diffuseColor;
var intensity:Number = light.distance/(lightPosition.dist(pixelPosition)*lightPosition.dist(pixelPosition));
color.red += light.color.red*normalDifference*intensity*diffuseColor.red*lightMultiplier;
color.green += light.color.green*normalDifference*intensity*diffuseColor.green*lightMultiplier;
color.blue += light.color.blue*normalDifference*intensity*diffuseColor.blue*lightMultiplier;
}
/*
var reflectedRay:Vector3D = lightDir.reflectAbout(normal).normalized();
var reflectedRayDifference:Number = reflectedRay.dot(ray)*-1;
var minusSpecularSize:Number = 1-objectMaterial.specularSize;
if(reflectedRayDifference > minusSpecularSize) { //specularish, with set parameters
//crap specular
color.red += lightMultiplier*objectMaterial.specularPower*(reflectedRayDifference-minusSpecularSize);
color.green += lightMultiplier*objectMaterial.specularPower*(reflectedRayDifference-minusSpecularSize);
color.blue += lightMultiplier*objectMaterial.specularPower*(reflectedRayDifference-minusSpecularSize);
}
*/
}
//ambient occlusion (even works through reflections!)
var hits:Number = 0;
var casts:Number = 20; //samples
var distanceThreshold:Number = 30;
var AOColor:Color = new Color(1, 1, 1);
for(var i:int = 0; i < casts; i++) {
var subray:Vector3D = new Vector3D(Math.random()*2-1, Math.random()*2-1, Math.random()*2-1);
subray = subray.normalized();
if(subray.dot(normal) < 0) {
subray.multiply(-1);
}
for each(var bob:RenderObject in spheres) {
if(bob == closestObject) continue;
var d:Number = bob.getIntersectionDistance(subray, pixelPosition)
if(isNaN(d)) continue;
if(d <= 0) continue;
if(d < distanceThreshold) {
hits++;
break;
}
}
}
if(hits > 0) {
var aohits:Number = 1-hits/casts;
AOColor.red *= aohits;
AOColor.green *= aohits;
AOColor.blue *= aohits;
AOBuffer.setPixel(cur.x, cur.y, AOColor.hex());
}
} else {
//doesn't really work
color.red += backgroundColor.red;
color.green += backgroundColor.green;
color.blue += backgroundColor.blue;
}
if(rays > 1 && closestObject != null && objectMaterial.reflectivity > 0) {
var john:Color = getColorFromCastRay(ray.reflectAbout(normal).normalized(), pixelPosition, rays-1);
var minusReflectivity:Number = 1-objectMaterial.reflectivity;
john.red = john.red*objectMaterial.reflectivity+color.red*minusReflectivity;
john.green = john.green*objectMaterial.reflectivity+color.green*minusReflectivity;
john.blue = john.blue*objectMaterial.reflectivity+color.blue*minusReflectivity;
return john;
} else {
return color;
}
}
public function toRealSpace(v:Vector3D):Vector3D {
return new Vector3D((v.x-CENTER_X)*SCALE_X+cam.x, (v.y-CENTER_Y)*SCALE_Y+cam.y, v.z);
}
}
}
interface RenderObject {
function getIntersectionDistance(ray:Vector3D, origin:Vector3D):Number;
function normalAtPoint(p:Vector3D):Vector3D;
function material():Material;
}
class Plane implements RenderObject {
public var position:Vector3D; //any point really
public var normal:Vector3D;
public var _material:Material;
public function Plane(sposition:Vector3D, snormal:Vector3D, material:Material):void {
position = sposition;
normal = snormal.normalized();
_material = material;
}
public function getIntersectionDistance(ray:Vector3D, origin:Vector3D):Number {
var dist:Number = position.minus(origin).dot(normal)/ray.dot(normal);
return dist;
}
public function normalAtPoint(p:Vector3D):Vector3D {
return normal;
}
public function material():Material {
return _material;
}
}
class Sphere implements RenderObject {
public var position:Vector3D;
public var radius:Number;
public var _material:Material;
public function Sphere(pos:Vector3D, rad:Number, material:Material):void {
position = pos;
radius = rad;
_material = material;
}
public function normalAtPoint(p:Vector3D):Vector3D {
//not normalized
return p.minus(position).normalized();
}
//returns length along ray where intersection lies
public function getIntersectionDistance(ray:Vector3D, origin:Vector3D):Number {
var dist:Vector3D = position.minus(origin);
var dot:Number = ray.dot(dist);
var determinant:Number = dot*dot-dist.dot(dist)+radius*radius;
return ray.dot(dist)-Math.sqrt(determinant);
}
public function material():Material {
return _material;
}
}
class Light {
public var position:Vector3D;
public var color:Color;
public var distance:Number; //maximum distance at which intensity is maximum
public function Light(pos:Vector3D, scolor:Color, sdistance:Number):void {
position = pos;
color = scolor;
distance = sdistance;
}
}
class Material {
public var diffuseColor:Color;
public var specularColor:Color;
public var specularSize:Number;
public var specularPower:Number;
public var reflectivity:Number;
public function Material():void {}
}
class Color {
public var red:Number;
public var green:Number;
public var blue:Number;
public function Color(r:Number, g:Number, b:Number):void {
red = r;
green = g;
blue = b;
}
public function hex():Number { //clamped to [0, 255]
return (red>1?1:red)*255 << 16 | (green>1?1:green)*255 << 8 | (blue>1?1:blue)*255;
}
}
class Vector3D { //I know that there is a built in vector 3d class; I did this just for fun :D
public var x:Number;
public var y:Number;
public var z:Number;
public function Vector3D(sx:Number, sy:Number, sz:Number):void {
x = sx;
y = sy;
z = sz;
}
public function normalized():Vector3D {
var l:Number = Math.sqrt(x*x+y*y+z*z);
return new Vector3D(x/l, y/l, z/l);
}
public function magnitude():Number {
return Math.sqrt(x*x+y*y+z*z);
}
public function reflectAbout(v:Vector3D):Vector3D {
return minus(v.multiply(2*v.dot(this)));
}
public function dist(v:Vector3D):Number {
var dx:Number = x-v.x;
var dy:Number = y-v.y;
var dz:Number = z-v.z;
return Math.sqrt(dx*dx+dy*dy+dz*dz);
}
public function minus(v:Vector3D):Vector3D {
return new Vector3D(x-v.x, y-v.y, z-v.z);
}
public function plus(v:Vector3D):Vector3D {
return new Vector3D(x+v.x, y+v.y, z+v.z);
}
public function multiply(n:Number):Vector3D {
return new Vector3D(x*n, y*n, z*n);
}
public function dot(v:Vector3D):Number {
return x*v.x+y*v.y+z*v.z;
}
public function cross(v:Vector3D):Vector3D {
return new Vector3D(y*v.z-v.y*z, z*v.x-x*v.z, x*v.y-v.x*y);
}
}