/*
* Terrain raycaster
* by Mr.doob (http://mrdoob.com)
*
* Click for a new terrain
*/
package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Rectangle;
[SWF(backgroundColor="#000000", frameRate="60")]
public class Main extends Sprite
{
private var seed : Number;
private var precision : int;
private var canvas : BitmapData;
private var canvas_width : int = 465;
private var canvas_height : int = 465;
private var canvas_width_half : int = canvas_width / 2;
private var canvas_height_half : int = canvas_height / 2;
private var light : Point3D;
private var diffuseColor : Color;
private var ambientColor : Color;
private var skyColor : Color;
private var pixelRect : Rectangle;
private var lineRect : Rectangle;
private var yy : int = 0;
public function Main()
{
addEventListener( Event.ADDED_TO_STAGE, onAddedToStage );
}
private function onAddedToStage( e : Event ) : void
{
stage.scaleMode = StageScaleMode.NO_SCALE;
canvas = new BitmapData( canvas_width, canvas_height, false, 0);
var sprite : Sprite = new Sprite();
addChild( sprite );
sprite.addChild( new Bitmap( canvas ) );
sprite.buttonMode = true;
stage.addEventListener(MouseEvent.CLICK, init);
lineRect = new Rectangle(0, 0, canvas_width, 1);
init();
addEventListener(Event.ENTER_FRAME, renderLine);
}
private function init( e : Event = null ) : void
{
yy = 0;
precision = 16;
seed = Math.random() * 2 + 0.1;
diffuseColor = new Color( 0x303030 * Math.random() );
ambientColor = new Color( 0x101010 * Math.random() );
skyColor = new Color( 0xffffff * Math.random() );
light = new Point3D( (Math.random() - 0.5) * 2, (Math.random() - 0.5) * 2, (Math.random() - 0.5) * 2 );
}
private function renderLine( e : Event ) : void
{
pixelRect = new Rectangle(0, 0, precision, precision);
canvas.lock();
for (var xx : int = 0; xx < canvas_width; xx += precision)
{
var ray : Ray = new Ray();
ray.origin = new Point3D( canvas_width_half, canvas_height_half, canvas_width_half);
ray.direction = Point3D.sub( new Point3D(xx, canvas_height - yy, 0 ), ray.origin);
ray.direction.normalise();
pixelRect.x = xx;
pixelRect.y = yy;
if (castRay(ray))
canvas.fillRect(pixelRect, terrainColor(ray) );
else
canvas.fillRect(pixelRect, skyColor.getHex() );
}
lineRect.y = yy + precision;
canvas.fillRect(lineRect, 0xffffff );
if ((yy += precision) > canvas_height)
{
if (precision > 1)
precision /= 2;
else
init();
yy = 0;
}
canvas.unlock();
}
private function f( x : Number, z : Number) : Number
{
var f : Number;
f = seed * Math.sin(seed * x) * Math.cos(seed * z);
f += seed * Math.sin(2 * seed * x) * Math.cos(2 * seed * z);
f += seed * 0.2 * Math.sin(5 * seed * x) * Math.cos(5 * seed * z);
f -= seed * 0.1 * Math.sin(10 * seed * x) * Math.cos(10 * seed * z);
return f - 1 + canvas_height_half;
}
private function castRay( ray : Ray ) : Boolean
{
var delt : Number = 0.01;
var mint : Number = 0.1;
var maxt : Number = 20.0;
for( var t : Number = mint; t < maxt; t += delt )
{
var p : Point3D = Point3D.add(ray.origin, Point3D.scalar(ray.direction, t));
if( p.y < f( p.x, p.z ) )
{
ray.t = t - 0.5 * delt;
return true;
}
delt = 0.01 * t;
}
return false;
}
private function terrainColor( ray : Ray ) : Number
{
var p : Point3D = Point3D.add(ray.origin, Point3D.scalar(ray.direction, ray.t));
var n : Point3D = getNormal( p );
var dot : Number = Point3D.dot(n, light);
var c : Color = new Color(0x000000);
c.addRGB( diffuseColor );
c.addNumber( dot * 0xFF );
c.addRGB( ambientColor );
c.mixRGB( skyColor, ray.t / 20);
return c.getHex();
}
private function getNormal( p : Point3D ) : Point3D
{
var eps : Number = 0.01;
var normal : Point3D = new Point3D( f(p.x-eps,p.z) - f(p.x+eps,p.z),
2 * eps,
f(p.x,p.z-eps) - f(p.x,p.z+eps) );
normal.normalise();
return normal;
}
}
}
class Ray
{
public var origin : Point3D;
public var direction : Point3D;
public var t : Number;
}
class Point3D
{
public var x : Number;
public var y : Number;
public var z : Number;
public function Point3D( x : Number = 0, y : Number = 0, z : Number = 0)
{
this.x = x;
this.y = y;
this.z = z;
}
public function normalise() : void
{
var dist : Number = Math.sqrt( (x * x)+(y * y)+(z * z) );
x = x * ( 1.0 / dist );
y = y * ( 1.0 / dist );
z = z * ( 1.0 / dist );
}
public static function add( p1 : Point3D, p2 : Point3D ) : Point3D
{
return new Point3D( p1.x + p2.x, p1.y + p2.y, p1.z + p2.z);
}
public static function sub( p1 : Point3D, p2 : Point3D ) : Point3D
{
return new Point3D( p1.x - p2.x, p1.y - p2.y, p1.z - p2.z);
}
public static function scalar( p1 : Point3D, val : Number ) : Point3D
{
return new Point3D( p1.x * val, p1.y * val, p1.z * val);
}
public static function dot( p1 : Point3D, p2 : Point3D ) : Number
{
return p1.x * p2.x + p1.y * p2.y + p1.z * p2.z;
}
public function clone() : Point3D
{
return new Point3D(x, y , z);
}
public function toString() : String
{
return "x: " + x + ", y: " + y + ", z: " + z;
}
}
class Color
{
public var r : int, g : int, b : int;
public function Color( hex : int )
{
r = ( (0xff0000 & hex) >> 16 );
g = ( (0x00ff00 & hex) >> 8 );
b = ( 0x0000ff & hex );
}
public function getHex() : int
{
r = ( r > 0xff ) ? 0xff : ( r < 0x00 ) ? 0 : r;
g = ( g > 0xff ) ? 0xff : ( g < 0x00 ) ? 0 : g;
b = ( b > 0xff ) ? 0xff : ( b < 0x00 ) ? 0 : b;
return r << 16 | g << 8 | b << 0;
}
public function addNumber( value : int ) : void
{
r += value;
g += value;
b += value;
}
public function addRGB( colour : Color ) : void
{
r += colour.r;
g += colour.g;
b += colour.b;
}
public function mixRGB( colour : Color, amount : Number ) : void
{
amount = (amount > 1) ? 1 : (amount < 0) ? 0 : amount;
r = (r * (1 - amount)) + (colour.r * amount);
g = (g * (1 - amount)) + (colour.g * amount);
b = (b * (1 - amount)) + (colour.b * amount);
}
public function multiplyNumber( value : Number ) : void
{
r *= value;
g *= value;
b *= value;
}
}