/**
* Copyright Jarvis.weng ( http://wonderfl.net/user/Jarvis.weng )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/gbtz
*/
package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
public class RayTracing extends Sprite
{
private var bmpData:BitmapData = new BitmapData(256, 256);
private var bmp:Bitmap = new Bitmap(bmpData);
public function RayTracing()
{
bmp.x = (stage.stageWidth - bmp.width) / 2;
bmp.y = (stage.stageHeight - bmp.height) / 2
addChild(bmp);
var plane:Plane = new Plane(new Vector3(0, 1, 0), 0);
var sphere1:Sphere = new Sphere(new Vector3(-10, 10, -10), 10);
var sphere2:Sphere = new Sphere(new Vector3(10, 10, -10), 10);
plane.material = new CheckerMaterial(0.1, 0.5);
sphere1.material = new PhongMaterial(Color.red, Color.white, 16, 0.25);
sphere2.material = new PhongMaterial(Color.blue, Color.white, 16, 0.25);
rayTraceReflection(new Scene(new <IGeometries>[ plane, sphere1, sphere2 ]),
new PerspectiveCamera(new Vector3(0, 5, 15), new Vector3(0, 0, -1), new Vector3(0, 1, 0), 90), 3);
}
private function rayTraceReflection(scene:Scene, camera:PerspectiveCamera, maxReflect:int):void
{
bmpData.lock();
var h:Number = bmpData.height;
var w:Number = bmpData.width;
var i:int = 0;
for (var y:Number = 0; y < h; y++)
{
var sy:Number = 1 - y / h;
for (var x:Number = 0; x < w; x++)
{
var sx:Number = x / w;
var ray:Ray3 = camera.generateRay(sx, sy);
var color:Color = rayTraceRecursive(scene, ray, maxReflect);
bmpData.setPixel32(x, y, argb2uint(255, color.r * 255, color.g * 255, color.b * 255));
}
}
bmpData.unlock();
}
private function rayTraceRecursive(scene:Scene, ray:Ray3, maxReflect:int):Color
{
var result:IntersectResult = scene.intersect(ray);
if (result.geometry)
{
var reflectiveness:Number = result.geometry.material.reflectiveness;
var color:Color = result.geometry.material.sample(ray, result.position, result.normal);
color = color.multiply(1 - reflectiveness);
if (reflectiveness > 0 && maxReflect > 0)
{
var r:Vector3 = result.normal.multiply(-2 * result.normal.dot(ray.direction)).add(ray.direction);
ray = new Ray3(result.position, r);
var reflectedColor:Color = rayTraceRecursive(scene, ray, maxReflect - 1);
color = color.add(reflectedColor.multiply(reflectiveness));
}
return color;
}
else
return Color.black;
}
private function argb2uint(a:uint, r:uint, g:uint, b:uint):uint
{
return a << 24 | r << 16 | g << 8 | b;
}
}
}
class Vector3
{
public static const zero:Vector3 = new Vector3(0, 0, 0);
public var x:Number;
public var y:Number;
public var z:Number;
public function Vector3(x:Number = 0, y:Number = 0, z:Number = 0)
{
this.x = x;
this.y = y;
this.z = z;
}
public function copy():Vector3
{
return new Vector3(this.x, this.y, this.z);
}
public function length():Number
{
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
}
public function sqrLength():Number
{
return this.x * this.x + this.y * this.y + this.z * this.z;
}
public function normalize():Vector3
{
var inv:Number = 1 / this.length();
return new Vector3(this.x * inv, this.y * inv, this.z * inv);
}
public function negate():Vector3
{
return new Vector3(-this.x, -this.y, -this.z);
}
public function add(v:Vector3):Vector3
{
return new Vector3(this.x + v.x, this.y + v.y, this.z + v.z);
}
public function subtract(v:Vector3):Vector3
{
return new Vector3(this.x - v.x, this.y - v.y, this.z - v.z);
}
public function multiply(f:Number):Vector3
{
return new Vector3(this.x * f, this.y * f, this.z * f);
}
public function divide(f:Number):Vector3
{
var invf:Number = 1 / f;
return new Vector3(this.x * invf, this.y * invf, this.z * invf);
}
public function dot(v:Vector3):Number
{
return this.x * v.x + this.y * v.y + this.z * v.z;
}
public function cross(v:Vector3):Vector3
{
return new Vector3(-this.z * v.y + this.y * v.z, this.z * v.x - this.x * v.z, -this.y * v.x + this.x * v.y);
}
}
class Ray3
{
public var origin:Vector3;
public var direction:Vector3;
public function Ray3(origin:Vector3, direction:Vector3)
{
this.origin = origin;
this.direction = direction;
}
public function getPoint(t:Number):Vector3
{
return this.origin.add(this.direction.multiply(t));
}
}
interface IGeometries
{
function intersect(ray:Ray3):IntersectResult;
function get material():IMaterial;
}
class Sphere implements IGeometries
{
private var center:Vector3;
private var radius:Number;
private var sqrRadius:Number;
private var _material:IMaterial;
public function get material():IMaterial
{
return _material;
}
public function set material(value:IMaterial):void
{
_material = value;
}
public function Sphere(center:Vector3, radius:Number)
{
this.center = center;
this.radius = radius;
this.sqrRadius = this.radius * this.radius;
}
public function intersect(ray:Ray3):IntersectResult
{
var v:Vector3 = ray.origin.subtract(this.center);
var a0:Number = v.sqrLength() - this.sqrRadius;
var DdotV:Number = ray.direction.dot(v);
if (DdotV <= 0)
{
var discr:Number = DdotV * DdotV - a0;
if (discr >= 0)
{
var result:IntersectResult = new IntersectResult();
result.geometry = this;
result.distance = -DdotV - Math.sqrt(discr);
result.position = ray.getPoint(result.distance);
result.normal = result.position.subtract(this.center).normalize();
return result;
}
}
return IntersectResult.noHit;
}
}
class Plane implements IGeometries
{
private var position:Vector3;
private var normal:Vector3;
private var d:Number;
private var _material:IMaterial;
public function get material():IMaterial
{
return _material;
}
public function set material(value:IMaterial):void
{
_material = value;
}
public function Plane(normal, d:Number)
{
this.normal = normal;
this.d = d;
this.position = this.normal.multiply(this.d);
}
public function intersect(ray:Ray3):IntersectResult
{
var a:Number = ray.direction.dot(this.normal);
if (a >= 0)
return IntersectResult.noHit;
var b:Number = this.normal.dot(ray.origin.subtract(this.position));
var result:IntersectResult = new IntersectResult();
result.geometry = this;
result.distance = -b / a;
result.position = ray.getPoint(result.distance);
result.normal = this.normal;
return result;
}
}
class Scene
{
public var geometries:Vector.<IGeometries>;
public function Scene(geometries:Vector.<IGeometries>)
{
this.geometries = geometries;
}
public function intersect(ray):IntersectResult
{
var minDistance:Number = Number.MAX_VALUE;
var minResult:IntersectResult = IntersectResult.noHit;
for (var i:int = 0; i < this.geometries.length; ++i)
{
var result:IntersectResult = this.geometries[i].intersect(ray);
if (result.geometry && result.distance < minDistance)
{
minDistance = result.distance;
minResult = result;
}
}
return minResult;
}
}
class IntersectResult
{
public static const noHit:IntersectResult = new IntersectResult();
public var geometry:IGeometries;
public var distance:Number;
public var position:Vector3;
public var normal:Vector3;
public function IntersectResult()
{
this.geometry = null;
this.distance = 0;
this.position = Vector3.zero;
this.normal = Vector3.zero;
}
}
class PerspectiveCamera
{
public var eye:Vector3;
public var front:Vector3;
public var refUp:Vector3;
public var fov:Number;
public var right:Vector3;
public var up:Vector3;
public var fovScale:Number;
public function PerspectiveCamera(eye:Vector3, front:Vector3, up:Vector3, fov:Number)
{
this.eye = eye;
this.front = front;
this.refUp = up;
this.fov = fov;
initialize();
}
public function initialize():void
{
this.right = this.front.cross(this.refUp);
this.up = this.right.cross(this.front);
this.fovScale = Math.tan(this.fov * 0.5 * Math.PI / 180) * 2;
}
public function generateRay(x:Number, y:Number):Ray3
{
var r:Vector3 = this.right.multiply((x - 0.5) * this.fovScale);
var u:Vector3 = this.up.multiply((y - 0.5) * this.fovScale);
return new Ray3(this.eye, this.front.add(r).add(u).normalize());
}
}
class Color
{
public static const black:Color = new Color(0, 0, 0);
public static const white:Color = new Color(1, 1, 1);
public static const red:Color = new Color(1, 0, 0);
public static const green:Color = new Color(0, 1, 0);
public static const blue:Color = new Color(0, 0, 1);
public var r:Number;
public var g:Number;
public var b:Number;
public function Color(r, g, b)
{
this.r = Math.min(1, r);
this.g = Math.min(1, g);
this.b = Math.min(1, b);
}
public function copy():Color
{
return new Color(this.r, this.g, this.b);
}
public function add(c:Color):Color
{
return new Color(this.r + c.r, this.g + c.g, this.b + c.b);
}
public function multiply(s:Number):Color
{
return new Color(this.r * s, this.g * s, this.b * s);
}
public function modulate(c:Color):Color
{
return new Color(this.r * c.r, this.g * c.g, this.b * c.b);
}
}
interface IMaterial
{
function sample(ray:Ray3, position:Vector3, normal:Vector3):Color;
function get reflectiveness():Number;
}
class CheckerMaterial implements IMaterial
{
private var scale:Number;
private var _reflectiveness:Number;
public function get reflectiveness():Number
{
return _reflectiveness
}
public function CheckerMaterial(scale:Number, reflectiveness:Number)
{
this.scale = scale;
this._reflectiveness = reflectiveness;
}
public function sample(ray:Ray3, position:Vector3, normal:Vector3):Color
{
return Math.abs((Math.floor(position.x * 0.1) + Math.floor(position.z * this.scale)) % 2) < 1 ? Color.black : Color.white;
}
}
class PhongMaterial implements IMaterial
{
// global light
static private var lightDir:Vector3 = new Vector3(1, 1, 1).normalize();
static private var lightColor:Color = Color.white;
private var diffuse:Color;
private var specular:Color;
private var shininess:Number;
private var _reflectiveness:Number;
public function get reflectiveness():Number
{
return _reflectiveness
}
public function PhongMaterial(diffuse, specular, shininess, reflectiveness)
{
this.diffuse = diffuse;
this.specular = specular;
this.shininess = shininess;
this._reflectiveness = reflectiveness;
}
public function sample(ray:Ray3, position:Vector3, normal:Vector3):Color
{
var NdotL:Number = normal.dot(lightDir);
var H:Vector3 = (lightDir.subtract(ray.direction)).normalize();
var NdotH:Number = normal.dot(H);
var diffuseTerm:Color = this.diffuse.multiply(Math.max(NdotL, 0));
var specularTerm:Color = this.specular.multiply(Math.pow(Math.max(NdotH, 0), this.shininess));
return lightColor.modulate(diffuseTerm.add(specularTerm));
}
}