In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

forked from: レンズフレア

レンズフレアのテスト
Get Adobe Flash player
by yonatan 09 Mar 2012
/**
 * Copyright yonatan ( http://wonderfl.net/user/yonatan )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/iheG
 */

// forked from tencho's レンズフレア
/**
 * レンズフレアのテスト
 */
package {
	import com.bit101.components.*;
	import flash.display.*;
	import flash.events.*;
	import flash.filters.*;
	import flash.geom.*;
	import flash.utils.*;
	import net.hires.debug.Stats;
	
	[SWF(width = "465", height = "465", frameRate = "60", backgroundColor = "#000000")]
	public class main extends Sprite {
		private const RADIAN:Number = Math.PI / 180;
		private var _bgColor:uint = 0x4DA7FA;
		private var _horizonColor:uint = 0x9CC8F5;
		
		private var _sky:Sprite;
		private var _lensEfx:LensEffect;
		private var _orchard:Orchard;
		private var _sun:Vector3D = new Vector3D(20000, 10500, -20000);
		private var _cameraPos:Vector3D = new Vector3D();
		private var _cameraTarget:Vector3D = new Vector3D();
		private var _horizonTarget:Vector3D = new Vector3D();
		
		private var _cameraMode:Boolean = true;
		private var _isDrag:Boolean = false;
		private var _clickPosition:Point = new Point();
		private var _saveRotation:Number;
		private var _saveAngle:Number;
		private var _cameraRotation:Number = 120;
		private var _cameraAngle:Number = 1;
		private var _cameraRadius:Number = 370;

		private var _blurArea:int = 3;
        private var _blurAreaBmd:BitmapData = new BitmapData(_blurArea*2+1, _blurArea*2+1, true, 0);
        private var _mtx:Matrix = new Matrix();
        private var _sigl:SiGLCore;
        private var _camera:SiGLMatrix = new SiGLMatrix();
        private var _lastFrameTime:int = 0;
        private var _targetFrameRate:int = 30;
        private var _mt:Label;
		
		public function main() {
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}
		
		private function init(e:Event = null):void {
			removeEventListener(Event.ADDED_TO_STAGE, init);
			
			stage.frameRate = 60;
			stage.quality = StageQuality.LOW;
			Display.setSize(stage.stageWidth, stage.stageHeight);

			_sigl = new SiGLCore(Display.width, Display.height);
            _sigl.fieldOfView = 78; // best resembles original pv3d camera
			_orchard = new Orchard(_sigl);
            _orchard.x = Display.center.x;
            _orchard.y = Display.center.y;
            _orchard.scaleY = _orchard.scaleX = -1;
			
			//マウス操作用
			stage.addEventListener(MouseEvent.MOUSE_DOWN, onMsDown);
			stage.addEventListener(MouseEvent.MOUSE_UP, onMsUp);
			stage.addEventListener(Event.MOUSE_LEAVE, onMsUp);
			stage.addEventListener(MouseEvent.MOUSE_MOVE, onMsMove);
			
			//背景
			_sky = new Sprite();
			_sky.addChild(Painter.createGradientRect(Display.width, Display.height, [_bgColor, _horizonColor], [1, 1], [], 90, 0, -Display.height));
			_sky.addChild(Painter.createGradientRect(Display.width, Display.height, [0x000000, 0x444444], [1, 1], [], 90));
			
			//レンズフレア
			_lensEfx = new LensEffect();
			_lensEfx.init(stage);
			_lensEfx.x = Display.center.x;
			_lensEfx.y = Display.center.y;
			
			addChild(Painter.createGradientRect(Display.width, Display.height, [_bgColor], [1]));
			addChild(_sky);
			addChild(_orchard)
			addChild(_lensEfx);
			addChild(new Stats());
			Style.BUTTON_FACE = 0;
            Style.BACKGROUND = 0x808080;
			Style.LABEL_TEXT = 0xffffff;
			new PushButton(this, 360, 5, "CAMERA: AUTO", onClickCamera);
            _mt = new Label(this, 5, Display.height - 60);
            new HUISlider(this, 5, Display.height - 40, "Target FPS", function(e:Event):void {_targetFrameRate = e.target.value;}).setSliderParams(6,60,30);
            new HUISlider(this, 5, Display.height - 20, "Focus", function(e:Event):void {_sigl.fieldOfView = e.target.value;}).setSliderParams(45,80,78);
			
			//レンダリング開始
			onEnter(null);
			addEventListener(Event.ENTER_FRAME, onEnter);
		}
		
		private function onClickCamera(e:MouseEvent):void{
			_cameraMode = !_cameraMode;
			PushButton(e.currentTarget).label = "CAMERA: " + ["DRAG", "AUTO"][int(_cameraMode)];
		}
		
		private function onMsDown(e:MouseEvent):void {
			_isDrag = true;
			_clickPosition.x = stage.mouseX;
            _clickPosition.y = stage.mouseY;
			_saveAngle = _cameraAngle;
			_saveRotation = _cameraRotation;
		}
		
		private function onMsUp(...arg):void {
			_isDrag = false;
		}
		
		private function onMsMove(e:MouseEvent):void {
			if (!_isDrag || _cameraMode) return;
			_cameraRotation = _saveRotation - (stage.mouseX - _clickPosition.x) / 10;
			_cameraAngle = Math.max(1, Math.min(40, _saveAngle + (stage.mouseY - _clickPosition.y) / 10));
		}

        private function toScreen(v:Vector3D):Vector3D {
            var ret:Vector3D = _sigl.projectionMatrix.transform(v.clone());
            ret.w = _sigl.focalLength/ret.z;
            ret.x *= -ret.w;
            ret.y *= -ret.w;
            return ret;
        }
		
		//毎フレーム処理
		private function onEnter(e:Event):void {
			var time:int = getTimer() + 88400;
            var renderTime:int = time - _lastFrameTime;
            _lastFrameTime = time;
            var timeDiff:Number = Math.abs(renderTime - 1000/_targetFrameRate);
            if(timeDiff > 300/_targetFrameRate) { // don't adjust unless we have to (reduces flicker)
                _orchard.minThickness += (renderTime > 1000/_targetFrameRate) ? 0.1 : -0.1;
                if(_orchard.minThickness < 1) _orchard.minThickness = 1;
            }
            _mt.text = "Min. thickness: " + _orchard.minThickness.toFixed(2);
			
			//カメラ移動
			if (_cameraMode) {
				_cameraTarget.x = Math.cos(RADIAN * (time / 24)) * 100;
				_cameraTarget.z = Math.sin(RADIAN * (time / 28)) * 100;
				_cameraTarget.y = 100;
				_cameraPos.setTo(Math.cos(RADIAN * (time / 40)) * 350, 15, Math.cos(RADIAN * (time / 50)) * 350);
			} else {
				_cameraTarget.x = 0;
				_cameraTarget.y = 100;
				_cameraTarget.z = 0;
				var perXZ:Number = Math.cos(RADIAN * _cameraAngle);
				_cameraPos.x = Math.cos(RADIAN * _cameraRotation) * _cameraRadius * perXZ;
				_cameraPos.z = Math.sin(RADIAN * _cameraRotation) * _cameraRadius * perXZ;
				_cameraPos.y = Math.sin(RADIAN * _cameraAngle) * _cameraRadius;
			}
            _camera.id().prependTranslation(_cameraPos.x, _cameraPos.y, _cameraPos.z);
            _camera.lookAt(_cameraPos.x, _cameraPos.y, _cameraPos.z, _cameraTarget.x, _cameraTarget.y, _cameraTarget.z, 0, 1, 0, 1);
            _sigl.setCameraMatrix(_camera);
			
			//地平線用オブジェクトをカメラの前方に移動
			var lookRadian:Number = Math.atan2(_cameraTarget.z - _cameraPos.z, _cameraTarget.x - _cameraPos.x);
			_horizonTarget.x = _cameraPos.x + Math.cos(lookRadian) * 100000;
			_horizonTarget.z = _cameraPos.z + Math.sin(lookRadian) * 100000;
			
			//レンダリング
			_orchard.render();
			
			//地平線の高さに背景を合わせる
			_sky.y = toScreen(_horizonTarget).y + Display.center.y;
			
            var sunOnScreen:Vector3D = toScreen(_sun);
			var sunPos:Point = new Point(sunOnScreen.x + Display.center.x, sunOnScreen.y + Display.center.y);
			//太陽が遮蔽物にどのくらいの割合で隠れているかの割合を計算
            _blurAreaBmd.fillRect(_blurAreaBmd.rect, 0);
            _mtx.identity();
            _mtx.tx = sunOnScreen.x + _blurArea;
            _mtx.ty = sunOnScreen.y + _blurArea;
            _blurAreaBmd.draw(_orchard, _mtx);
			var per1:Number = 0;
			for (var px:int = 0; px < _blurAreaBmd.width; px++) {
			    for (var py:int = 0; py < _blurAreaBmd.height; py++) {
					per1 += 1 - (_blurAreaBmd.getPixel32(px, py) >>> 24) / 0xFF;
				}
			}
			per1 /= (_blurArea * 2 + 1) * (_blurArea * 2 + 1);
			//太陽が画面中央にどれだけ近いかの割合を計算
			var per2:Number = Math.max(0, 1 - sunPos.subtract(Display.center).length / 600);
			var efxPower:Number = (sunOnScreen.z <= 0 )? 0 : per1 * per2;
			_lensEfx.rotateFlash();
			_lensEfx.setSunPosition(sunOnScreen.x, sunOnScreen.y);
			_lensEfx.setPower(efxPower);
		}
	}
}

import flash.display.*;
import flash.filters.*;
import flash.geom.*;
import flash.utils.*;

class Painter {
	/**
	 * グラデーションスプライト生成
	 */
	static public function createGradientRect(width:Number, height:Number, colors:Array, alphas:Array, ratios:Array = null, rotation:Number = 0, x:Number = 0, y:Number = 0):Sprite {
		var i:int;
		if (!ratios) ratios = [];
		if(!ratios.length) for (i = 0; i < colors.length; i++) ratios.push(int(255 * i / (colors.length - 1)));
		else for (i = 0; i < ratios.length; i++) ratios[i] = Math.round(ratios[i] * 255);
		var sp:Sprite = new Sprite();
		var mtx:Matrix = new Matrix();
		mtx.createGradientBox(width, height, Math.PI / 180 * rotation, 0, 0);
		if (colors.length == 1 && alphas.length == 1) sp.graphics.beginFill(colors[0], alphas[0]);
		else sp.graphics.beginGradientFill("linear", colors, alphas, ratios, mtx);
		sp.graphics.drawRect(0, 0, width, height);
		sp.graphics.endFill();
		sp.x = x;
		sp.y = y;
		return sp;
	}
	
	/**
	 * スプライトをキャプチャしてBitmap化する
	 * @param	target	キャプチャするSprite
	 * @param	smooth	Bitmapのsmoothに設定する値
	 * @param	stage	Stageクラスを渡すと最高画質でキャプチャします
	 */
	static public function captureSprite(target:Sprite, smooth:Boolean = true, stage:Stage = null):Sprite {
		var sp:Sprite = new Sprite();
		sp.blendMode = target.blendMode;
		
		if (stage) {
			//キャプチャする瞬間だけ最高画質にする
			var saveQuality:String = stage.quality;
			stage.quality = StageQuality.BEST;
		}
		
		var rect:Rectangle = getFilterBounds(target);
		var bmd:BitmapData = new BitmapData(rect.width, rect.height, true, 0);
		bmd.draw(target, new Matrix(1, 0, 0, 1, -rect.x, -rect.y));
		
		if(stage) stage.quality = saveQuality;
		var bmp:Bitmap = sp.addChild(new Bitmap(bmd, "auto", smooth)) as Bitmap;
		bmp.x = rect.x;
		bmp.y = rect.y;
		return sp;
	}
	
	/**
	 * フィルターも含めたSpriteの矩形範囲を返す
	 * @param	target	サイズを調べるSprite
	 * @return
	 */
	static public function getFilterBounds(target:Sprite):Rectangle {
		var rect:Rectangle = target.getBounds(target);
		if (!rect.width) rect.width = 1;
		if (!rect.height) rect.height = 1;
		var bmd:BitmapData;
		var basePos:Point = rect.topLeft;
		rect.x = rect.y = 0;
		for each (var filter:* in target.filters) {
			bmd = new BitmapData(rect.width, rect.height, true, 0);
			rect = bmd.generateFilterRect(rect, filter);
			basePos.offset(rect.x, rect.y);
			rect.x = rect.y = 0;
		}
		rect.offsetPoint(basePos);
		return rect;
	}
}

/**
 * レンズフレアクラス
 * このスプライトを画面中央に配置して使う
 */
class LensEffect extends Sprite {
	private const ORB_COLOR:uint = 0xC7C677;
	private var _ratios:Dictionary = new Dictionary();
	private var _ghosts:Vector.<Sprite> = new Vector.<Sprite>();
	private var _sun:Sprite;
	private var _sunRing:Sprite;
	private var _sunStar:Sprite;
	private var _sunGlow:Sprite;
	private var _sunGlow2:Sprite;
	private var _sunLine:Sprite;
	private var _sunFlash1:Sprite;
	private var _sunFlash2:Sprite;
	private var _stage:Stage;
	
	public function LensEffect() {
	}
	
	public function init(stage:Stage):void {
		_stage = stage;
		mouseChildren = mouseEnabled = false;
		_sun = addChild(createSun()) as Sprite;
		//リングを生成
		var ringSizes:Array = [60, 120];
		var ringRatios:Array = [-0.3, -0.7];
		for (var i:int = 0; i < ringSizes.length; i++) {
			var ring:Sprite = addChild(createFlare(ringSizes[i])) as Sprite;
			_ghosts.push(ring);
			_ratios[ring] = ringRatios[i];
		}
		//オーブを生成
		var orbSizes:Array = [15, 5, 10, 6, 60];
		for (var n:int = 0; n < orbSizes.length; n++) {
			var orb:Sprite = addChild(createOrb(orbSizes[n], ORB_COLOR)) as Sprite;
			_ghosts.push(orb);
			_ratios[orb] = 0.7 - n * 0.4;
		}
	}
	
	/**
	 * 太陽の放射エフェクトを回転
	 */
	public function rotateFlash():void {
		var rot:Number = getTimer() / 200 % 360;
		_sunFlash1.rotation = -rot;
		_sunFlash2.rotation = rot;
	}
	
	/**
	 * レンズフレアの強さ(0~1)を設定
	 * @param	per
	 */
	public function setPower(per:Number):void {
		visible = !!per;
		if (!per) return;
		_sunRing.alpha = 0.1 * per;
		_sunStar.scaleX = _sunStar.scaleY = per;
		_sunFlash1.alpha = 0.2 * per;
		_sunFlash2.alpha = 0.1 * per;
		_sunLine.width = 500 * per * 2;
		_sunLine.height = 15 * per * 2;
		_sunLine.alpha = 0.2 * per;
		_sunGlow.scaleX = _sunGlow.scaleY = per;
		_sunGlow2.alpha = per * 0.5 - 0.2;
		for each(var orb:Sprite in _ghosts) orb.alpha = per;
	}
	
	/**
	 * 太陽の位置を画面の中心からのオフセットで指定
	 * @param	px
	 * @param	py
	 */
	public function setSunPosition(px:Number, py:Number):void {
		_sun.x = int(px);
		_sun.y = int(py);
		for each(var orb:Sprite in _ghosts) {
			orb.x = int(px * _ratios[orb]);
			orb.y = int(py * _ratios[orb]);
			//虹リングだった場合
			if (orb.numChildren >= 2) {
				var ringRay:Sprite = orb.getChildAt(1) as Sprite;
				ringRay.x = int(px / 5);
				ringRay.y = int(py / 5);
			}
		}
	}
	
	//太陽セットを生成
	private function createSun():Sprite {
		var sp:Sprite = new Sprite();
		_sunRing = sp.addChild(createRing(100)) as Sprite;
		_sunFlash1 = sp.addChild(createFlash(150, 20, 0x5977AD)) as Sprite;
		_sunFlash2 = sp.addChild(createFlash(120, 50, 0xFFFFFF)) as Sprite;
		_sunStar = sp.addChild(createStar(100, 6, 0.03, 0xFFFFFF, 4)) as Sprite;
		_sunStar.rotation = 15;
		_sunLine = sp.addChild(createGlow(500, 0xFFFFFF)) as Sprite;
		_sunGlow = sp.addChild(createGlow(30, 0xFFFFFF)) as Sprite;
		_sunGlow2 = sp.addChild(createGlow(150, 0xFFDD88)) as Sprite;
		sp.blendMode = BlendMode.ADD;
		return sp;
	}
	
	//虹リングセットを生成
	private function createFlare(size:Number = 100):Sprite {
		var ring:Sprite = createRing(size);
		ring.alpha = 0.5;
		var ray:Sprite = new Sprite();
		ray.addChild(createStar(size*2, 100, 0.2));
		ray.addChild(createStar(size*2.5, 175, 0.15));
		ray.addChild(createGlow(size, 0x000000));
		var sp:Sprite = new Sprite();
		sp.addChild(ring);
		sp.addChild(Painter.captureSprite(ray, false, _stage));
		sp.blendMode = BlendMode.ADD;
		return sp;
	}
	
	//光の放射イメージを生成
	private function createFlash(size:Number = 100, count:int = 50, color:uint = 0xffffff):Sprite {
		var sp:Sprite = new Sprite();
		sp.graphics.lineStyle();
		var mat:Matrix = new Matrix();
		mat.createGradientBox(size * 2, size * 2, 0, -size, -size);
		sp.graphics.beginGradientFill("radial", [color, color], [1, 0], [0, 255], mat);
		sp.graphics.drawCircle(0, 0, size);
		sp.graphics.endFill();
		sp.graphics.beginFill(0x000000, 1);
		var random:Array = new Array();
		var total:Number = 0;
		for (var n:int = 0; n < count*2; n ++) {
			total += Math.random() * 50 + 10;
			random.push(total);
		}
		for (var m:int = 0; m < random.length; m ++) {
			random[m] /= total / 360;
			var px:Number = Math.cos(Math.PI/180 * random[m]) * (size*1.2);
			var py:Number = Math.sin(Math.PI/180 * random[m]) * (size*1.2);
			if (m % 2) {
				sp.graphics.moveTo(px, py);
			}else {
				sp.graphics.lineTo(px, py);
				sp.graphics.lineTo(0, 0);
			}
		}
		sp.graphics.endFill();
		sp.filters = [new BlurFilter(2, 2, 3)];
		return Painter.captureSprite(sp, false, _stage);
	}
	
	//オーブイメージを生成
	private function createOrb(size:Number = 100, color:uint = 0xFFFFFF):Sprite {
		var sp:Sprite = new Sprite();
		sp.graphics.lineStyle();
		var mat:Matrix = new Matrix();
		mat.createGradientBox(size * 2, size * 2, 0, -size, -size);
		sp.graphics.beginGradientFill("radial", [color, color], [0.2, 1], [0, 255], mat);
		sp.graphics.drawCircle(0, 0, size);
		sp.graphics.endFill();
		sp.filters = [new GlowFilter(color, 1, 3, 3, 1, 3, false, true), new BlurFilter(2, 2, 3)];
		sp.blendMode = BlendMode.ADD;
		return Painter.captureSprite(sp, false, _stage);
	}
	
	//グローイメージを生成
	private function createGlow(size:Number = 100, color:uint = 0xFFFFFF):Sprite {
		var sp:Sprite = new Sprite();
		sp.graphics.lineStyle();
		var mat:Matrix = new Matrix();
		mat.createGradientBox(size * 2, size * 2, 0, -size, -size);
		sp.graphics.beginGradientFill("radial", [color, color], [1, 0], [0, 255], mat);
		sp.graphics.drawCircle(0, 0, size);
		sp.graphics.endFill();
		return Painter.captureSprite(sp, false, _stage);
	}
	
	//虹リングイメージを生成
	private function createRing(size:Number = 100):Sprite {
		var sp:Sprite = new Sprite();
		sp.graphics.lineStyle();
		var mat:Matrix = new Matrix();
		mat.createGradientBox(size * 2, size * 2, 0, -size, -size);
		sp.graphics.beginGradientFill("radial", [0xFF0000, 0xFFdd00, 0x00FF00, 0x0066FF, 0x0000FF], [0, 1, 1, 0.5, 0], [180, 205, 215, 235, 255], mat);
		sp.graphics.drawCircle(0, 0, size);
		sp.graphics.endFill();
		return Painter.captureSprite(sp, false, _stage);
	}
	
	//星イメージを生成
	private function createStar(size:Number = 100, count:uint = 8, per:Number = 0.05, color:uint = 0x000000, blur:Number = 0):Sprite {
		var sp:Sprite = new Sprite();
		sp.graphics.lineStyle();
		sp.graphics.beginFill(color, 1);
		var step:Number = 360 / (count * 2);
		for (var i:int = 0; i < count * 2; i ++) {
			var radius:Number = (i % 2)? size * per : size;
			var px:Number = Math.cos(Math.PI / 180 * i * step) * radius;
			var py:Number = Math.sin(Math.PI / 180 * i * step) * radius;
			if (i == 0) sp.graphics.moveTo(px, py);
			else sp.graphics.lineTo(px, py);
		}
		sp.graphics.endFill();
		sp.filters = (blur)? [new BlurFilter(blur, blur, 3)] : [];
		return Painter.captureSprite(sp, false, _stage);
	}
}

/**
 * 画面サイズ
 */
class Display {
	static public var width:Number;
	static public var height:Number;
	static public var size:Rectangle = new Rectangle();
	static public var center:Point = new Point();
	static public function setSize(width:Number, height:Number):void {
		Display.width = size.width = width;
		Display.height = size.height = height;
		center.x = width / 2;
		center.y = height / 2;
	}
}

// slightly modified useful stuff by keim_at_Si (probably from the boolean crystals demo)

/** SiGLCore provides basic matrix operations. */
class SiGLCore {
    // variables ----------------------------------------
    public var modelViewMatrix:SiGLMatrix = new SiGLMatrix(), projectionMatrix:SiGLMatrix = new SiGLMatrix();
    public var viewWidth:Number, viewHeight:Number, pointSpriteFieldScale:Point = new Point();
    public var defaultCameraMatrix:SiGLMatrix = new SiGLMatrix(), matrix:SiGLMatrix = modelViewMatrix;
    private var _mvpMatrix:SiGLMatrix = new SiGLMatrix(), _mvpdir:Boolean, _2d:Number, _2r:Number;
    private var _mag:Number, _zNear:Number, _zFar:Number, _fieldOfView:Number, _fl:Number, _alignTopLeft:Boolean = false;
    // properties ----------------------------------------
    public function get modelViewProjectionMatrix() : SiGLMatrix {
        if (_mvpdir) {
            _mvpMatrix.copyFrom(projectionMatrix);
            _mvpMatrix.prepend(modelViewMatrix);
            _mvpdir = false;
        }
        return _mvpMatrix;
    }
    public function get focalLength() : Number { return _fl; }
    public function get zNear() : Number { return _zNear; }
    public function get zFar() : Number { return _zFar; }
    public function get align() : String { return (_alignTopLeft) ? "topLeft" : "center"; }
    public function set align(mode:String) : void { _alignTopLeft = (mode == "topLeft"); _updateProjectionMatrix(); }
    public function get matrixMode() : String { return (matrix === projectionMatrix) ? "projection" : "modelView"; }
    public function set matrixMode(mode:String) : void { matrix = (mode == "projection") ? projectionMatrix : modelViewMatrix; }
    public function get angleMode() : String { return (_2r == 1) ? "radian" : "degree"; }
    public function set angleMode(mode:String) : void { _2d = (mode == "radian") ? 57.29577951308232 : 1; _2r = (mode == "radian") ? 1 : 0.017453292519943295; }
    public function get fieldOfView() : Number { return _fieldOfView / _2r; }
    public function set fieldOfView(fov:Number) : void { _fieldOfView = fov * _2r; _updateProjectionMatrix(); }
    public function get magnification() : Number { return _mag; }
    public function set magnification(mag:Number) : void { _mag = mag; _updateProjectionMatrix(); }
    // constructor ----------------------------------------
    function SiGLCore(width:Number=1, height:Number=1) {
        viewWidth = width; viewHeight = height;
        angleMode = "degree"; _mag = 1;
        _zNear = -1000; _zFar = 200;
        modelViewMatrix.identity();
        _mvpdir = true;
        this.fieldOfView = 60;
    }
    // matrix operations ----------------------------------------
    public function forceUpdateMatrix() : SiGLCore { _mvpdir = true; return this; }
    public function setZRange(zNear:Number=-100, zFar:Number=100) : SiGLCore { _zNear = zNear; _zFar = zFar; _updateProjectionMatrix(); return this; }
    public function clear() : SiGLCore { matrix.clear(); _mvpdir = true; return this; }
    public function id() : SiGLCore { matrix.id(); _mvpdir = true; return this; }
    public function push() : SiGLCore { matrix.push(); return this; }
    public function pop() : SiGLCore { matrix.pop(); _mvpdir = true; return this; }
    public function rem() : SiGLCore { matrix.rem(); _mvpdir = true; return this; }
    public function r(a:Number, axis:Vector3D, pivot:Vector3D = null) : SiGLCore { matrix.prependRotation(a*_2d, axis, pivot); matrix._invdir = _mvpdir = true; return this; }
    public function s(x:Number, y:Number, z:Number=1) : SiGLCore { matrix.prependScale(x, y, z); matrix._invdir = _mvpdir = true; return this; }
    public function t(x:Number, y:Number, z:Number=0) : SiGLCore { matrix.prependTranslation(x, y, z); matrix._invdir = _mvpdir = true; return this; }
    public function m(mat:Matrix3D) : SiGLCore { matrix.prepend(mat); matrix._invdir = _mvpdir = true; return this; }
    public function re(x:Number, y:Number, z:Number) : SiGLCore { matrix.prependRotationXYZ(x*_2r, y*_2r, z*_2r); _mvpdir = true; return this; }
    public function setCameraMatrix(mat:Matrix3D=null) : SiGLCore { projectionMatrix.rem().prepend(mat || defaultCameraMatrix); _mvpdir = true; return this; }
    private function _updateProjectionMatrix() : void {
        var wh:Number = viewWidth / viewHeight, rev:Number = (_alignTopLeft)?-1:1;
        _fl = (viewHeight * 0.5) / Math.tan(_fieldOfView * 0.5);
        if (_zNear <= -_fl) _zNear = -_fl + 0.001;
        projectionMatrix.clear().perspectiveFieldOfView(_fieldOfView, wh, _zNear+_fl, _zFar+_fl, -1);
        pointSpriteFieldScale.setTo(projectionMatrix.rawData[0] * _fl, projectionMatrix.rawData[5] * _fl);
        projectionMatrix.push();
        defaultCameraMatrix.identity();
        defaultCameraMatrix.prependTranslation(0, 0, -_fl);
        if (_alignTopLeft) defaultCameraMatrix.prependTranslation(viewWidth* 0.5, -viewHeight * 0.5, 0);
        defaultCameraMatrix.prependScale(_mag, _mag * rev, _mag * rev);
        setCameraMatrix();
    }
    public function screenCoords(vector:Vector3D, dest:Vector3D = null):Vector3D {
        var ret:Vector3D = projectionMatrix.transform(vector, dest);
        ret.w = _fl/ret.z;
        ret.x *= ret.w;
        ret.y *= ret.w;
        return ret;
    }
}

/** SiGLMatrix is extention of Matrix3D with push/pop operation */
class SiGLMatrix extends Matrix3D {
    internal var _top:int = 0, _invdir:Boolean = false, _inv:Matrix3D = new Matrix3D(), _stac:Vector.<Matrix3D> = new Vector.<Matrix3D>();
    static private var _tv:Vector.<Number> = new Vector.<Number>(16, true), _tm:Matrix3D = new Matrix3D();
    static private var _in:Vector.<Number> = new Vector.<Number>(4, true), _out:Vector.<Number> = new Vector.<Number>(4, true);
    public function get inverted() : Matrix3D { if (_invdir) { _inv.copyFrom(this); _inv.invert(); _invdir = false; } return _inv; }
    public function forceUpdateInvertedMatrix() : SiGLMatrix { _invdir=true; return this; }
    public function clear() : SiGLMatrix { _top = 0; return id(); }
    public function id() : SiGLMatrix { identity(); _inv.identity(); return this; }
    public function push() : SiGLMatrix {
        if(_stac.length == _top) _stac.push(new Matrix3D());
        _stac[_top++].copyFrom(this);
        return this; 
    }
    public function pop() : SiGLMatrix { 
        this.copyFrom(_stac[--_top]);
        _invdir=true;
        return this;
    }
    public function rem() : SiGLMatrix { this.copyFrom(_stac[_top-1]); _invdir=true; return this; }
    public function prependRotationXYZ(rx:Number, ry:Number, rz:Number) : SiGLMatrix {
        var sx:Number = Math.sin(rx), sy:Number = Math.sin(ry), sz:Number = Math.sin(rz), 
        cx:Number = Math.cos(rx), cy:Number = Math.cos(ry), cz:Number = Math.cos(rz);
        _tv[0] = cz*cy; _tv[1] = sz*cy; _tv[2] = -sy; _tv[4] = -sz*cx+cz*sy*sx; _tv[5] = cz*cx+sz*sy*sx;
        _tv[6] = cy*sx; _tv[8] = sz*sx+cz*sy*cx; _tv[9] = -cz*sx+sz*sy*cx;
        _tv[10] = cy*cx; _tv[14] = _tv[13] = _tv[12] = _tv[11] = _tv[7] = _tv[3] = 0; _tv[15] = 1;
        _tm.copyRawDataFrom(_tv); prepend(_tm); _invdir=true;
        return this;
    }
    public function lookAt(cx:Number, cy:Number, cz:Number, tx:Number=0, ty:Number=0, tz:Number=0, ux:Number=0, uy:Number=1, uz:Number=0, w:Number=0) : SiGLMatrix {
        var dx:Number=tx-cx, dy:Number=ty-cy, dz:Number=tz-cz, dl:Number=-1/Math.sqrt(dx*dx+dy*dy+dz*dz), 
        rx:Number=dy*uz-dz*uy, ry:Number=dz*ux-dx*uz, rz:Number=dx*uy-dy*ux, rl:Number= 1/Math.sqrt(rx*rx+ry*ry+rz*rz);
        _tv[0]  = (rx*=rl); _tv[4]  = (ry*=rl); _tv[8]  = (rz*=rl); _tv[12] = -(cx*rx+cy*ry+cz*rz) * w;
        _tv[2]  = (dx*=dl); _tv[6]  = (dy*=dl); _tv[10] = (dz*=dl); _tv[14] = -(cx*dx+cy*dy+cz*dz) * w;
        _tv[1]  = (ux=dy*rz-dz*ry); _tv[5]  = (uy=dz*rx-dx*rz); _tv[9]  = (uz=dx*ry-dy*rx); _tv[13] = -(cx*ux+cy*uy+cz*uz) * w;
        _tv[3] = _tv[7] = _tv[11] = 0; _tv[15] = 1; copyRawDataFrom(_tv); _invdir=true;
        return this;
    }
    public function perspectiveFieldOfView(fieldOfViewY:Number, aspectRatio:Number, zNear:Number, zFar:Number, lh:Number=1.0) : void {
        var yScale:Number = 1.0 / Math.tan(fieldOfViewY * 0.5), xScale:Number = yScale / aspectRatio;
        this.copyRawDataFrom(Vector.<Number>([xScale,0,0,0,0,yScale,0,0,0,0,zFar/(zFar-zNear)*lh,lh,0,0,(zNear*zFar)/(zNear-zFar),0]));
    }
    public function transform(vector:Vector3D, dest:Vector3D = null) : Vector3D {
        if(!dest) dest = vector;
        _in[0] = vector.x; _in[1] = vector.y; _in[2] = vector.z; _in[3] = vector.w;
        transformVectors(_in, _out); dest.setTo(_out[0], _out[1], _out[2]); dest.w = _out[3];
        return dest;
    }
}

class Orchard extends Shape {
    private const LINE_WIDTH_FACTOR:Number = 0.5;
    private const BRANCH_SCALE:Number = 0.74;
    private const INIT_SIZE:Number = 120;
    private var _sigl:SiGLCore;
    public var minThickness:Number = 3;

    function Orchard(sigl:SiGLCore) { _sigl = sigl; }

    public function render():void {
        graphics.clear();
        for(var x:int = -4800; x <= 4800; x += 300) {
            for(var z:int = -4800; z <= 4800; z += 300) {
                _sigl.push().t(x, 0, z).r(x+z, Vector3D.Y_AXIS);
                tree(INIT_SIZE);
                _sigl.pop();
            }
        }
    }

    private var p1:Vector3D = new Vector3D, p2:Vector3D = new Vector3D;
    private function tree(size:Number):void {
        function projxy(v:Vector3D):void {
            _sigl.projectionMatrix.transform(_sigl.matrix.position, v);
            v.w = v.z/_sigl.focalLength;
            v.x /= v.w;
            v.y /= v.w;
            // v.project();
        }
        _sigl.push();
        projxy(p1);
        var limit:Number = size / (1 - BRANCH_SCALE);
        var xl:Number = _sigl.viewWidth*0.5 + limit*2/p1.w; // why *2 ???
        var yl:Number = _sigl.viewHeight*0.5 + limit*2/p1.w;
        if(p1.z > _sigl.zNear-limit
            && p1.x > -xl && p1.x < xl
            && p1.y > -yl && p1.y < yl
        ) {
            size *= BRANCH_SCALE;
            _sigl.t(0, size, 0);
            projxy(p2);
            var thickness:Number = size * LINE_WIDTH_FACTOR / ((p1.w+p2.w)*0.5);
            // crude clipping
            var cw:Number = (_sigl.viewWidth + thickness) * 0.5;
            var ch:Number = (_sigl.viewHeight + thickness) * 0.5;
            var isClipped:Boolean = (p1.x < -cw && p2.x < -cw) || (p1.x > cw && p2.x > cw) || (p1.y < -ch && p2.y < -ch) || (p1.y > ch && p2.y > ch) || p1.z < 0 || p2.z < 0;
            if(thickness > minThickness) {
                if(!isClipped) {
                    graphics.lineStyle(thickness, 0, (thickness-minThickness), false, "normal", "none");
                    graphics.moveTo(p1.x, p1.y);
                    graphics.lineTo(p2.x, p2.y);
                }
                _sigl.r(120, Vector3D.Y_AXIS).r(-30, Vector3D.X_AXIS);
                tree(size);
                _sigl.r(70, Vector3D.X_AXIS);
                tree(size);
            }
        }
        _sigl.pop();
    }
}