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

KD-Image City (WIP: Alternativa3D-powered)

W-S-A-D to fly around with mouse drag look. Hold Shift to fly faster. 

Real-time processed image using Alternativa3D's KDContainer to generate a "growing/evolving" city layout.

This is still a WIP, intend to add optional road cuts per KD-Node/Building split,  flying/walking collision detection and other stuffs.

Toggle TAB to preview KD tree of image/3d world.  Toggle Backspace to pause city kd generation.
Get Adobe Flash player
by Glidias 23 Oct 2012
/**
 * Copyright Glidias ( http://wonderfl.net/user/Glidias )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/fbYv
 */

/**
フラクタルで画像を描画

パクリ元ネタ:
fladdict » コンピューターに絵画を描かせる
http://fladdict.net/blog/2009/05/computer-painting.html

標準偏差:
http://www.cap.or.jp/~toukei/kandokoro/html/14/14_2migi.htm

画像の読み込み処理:
http://wonderfl.kayac.com/code/3fb2258386320fe6d2b0fe17d6861e7da700706a

RGB->HSB変換:
http://d.hatena.ne.jp/flashrod/20060930#1159622027

**/
package 
{
    import alternativ7.engine3d.containers.DistanceSortContainer;
    import alternativ7.engine3d.containers.KDContainer;
    import alternativ7.engine3d.controllers.SimpleObjectController;
    import alternativ7.engine3d.core.Debug;
    import alternativ7.engine3d.core.EllipsoidCollider;
    import alternativ7.engine3d.core.Face;
    import alternativ7.engine3d.core.Object3D;
    import alternativ7.engine3d.core.Object3DContainer;
    import alternativ7.engine3d.core.Vertex;
    import alternativ7.engine3d.core.Wrapper;
    import alternativ7.engine3d.materials.FillMaterial;
    import alternativ7.engine3d.materials.Material;
    import alternativ7.engine3d.objects.Mesh;
    import alternativ7.engine3d.objects.SkyBox;
    import alternativ7.engine3d.primitives.Box;
    import alternativ7.engine3d.primitives.Plane;
    import com.greensock.easing.Cubic;
    import com.greensock.easing.Elastic;
    import com.greensock.easing.Linear;
    import com.greensock.plugins.HexColorsPlugin;
    import com.greensock.plugins.TweenPlugin;
    import com.greensock.TweenLite;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Loader;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.geom.Vector3D;
    import flash.net.URLRequest;
    import flash.system.LoaderContext;
    import flash.text.TextField;
    import flash.ui.Keyboard;
    //import portal.controllers.SimpleFlyController;

    import alternativ7.engine3d.alternativa3d;
    import alternativ7.engine3d.core.Camera3D;
    import alternativ7.engine3d.core.View;
    

    use namespace alternativa3d;

    /**
     * WIP: Real-time processed image using Alternativa3D's KDContainer to generate a "growing"  *  city.
     * 
     * Since image is used, can consider using perlin noise or other procedural means to 
     * generate city layout.
     * 
     * I was thinking a Blade runner style city would be nice.
     * Would be good to add in geometry/splitting animations (buildings pushing in/out) as part 
     * of the "growing" effect.
     
     *  W-S-A-D to fly around with mouse drag look.
     * 
     * @author Glidias
     */
    [SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#ffffff")]
    
    public class KDTreeImage extends Sprite 
    {
        static public const FLOOR_COLOR:uint = 0xAAAAAA;
        static public const SPEC_RADIUS:Number = 32;
        
        private const IMAGE_URL:String = "http://farm4.static.flickr.com/3639/3538831894_cca4aabd68.jpg";
        //標準偏差の閾値。小さくすると細かくなるけど、小さすぎるとただのモザイクみたくなる。
        private const THRESHOLD:Number = 0.1;

        private var fillRectangleArray:Array;
        private var image:Bitmap;
        private var imageData:BitmapData;
        private var _canvas:Sprite;
        
        public static const DUMMY_VECTOR1:Vector3D = new Vector3D();
        public static const DUMMY_VECTOR2:Vector3D = new Vector3D();
        public static const DUMMY_DISPLACE:Vector3D = new Vector3D(0,0,0.1);
        
        private var camera:Camera3D;
        private var kdContainer:KDContainer;
        private var cameraController:SimpleFlyController;
        private var KD_NODE:Class;
        private var _lastKDNode:*;
        private static const WORLD_SCALE:Number = 128;
        private static const INV_255:Number = 1 / 255;
        private static const MAX_HEIGHT:Number = 12000;
        private var rootContainer:Object3DContainer;
        
        private var _tint:Number;

        
        public function KDTreeImage():void 
        {
            TweenPlugin.activate( [HexColorsPlugin] );
            
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
            
            
            

        }
        
        private function init(e:Event = null):void 
        {
            
            initA3D(); 
            removeEventListener(Event.ADDED_TO_STAGE, init);
            //画像の読み込み
                        var req:URLRequest = new URLRequest(IMAGE_URL);
                        var loader:Loader = new Loader();
                        loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadComplete);    
                        loader.load( req, new LoaderContext(true));
                       
        }
        
        private function initA3D():void 
        {
            
            fillA3DBuffers();  // may improve performance for initial run
            
            camera = new Camera3D();
            camera.view = new View(stage.stageWidth, stage.stageHeight);
            stage.addEventListener(Event.RESIZE, onStageResize);
            
            addChild(camera.view);
            
            kdContainer = new KDContainer();
            KD_NODE = getKDNodeClass();
            
        
       
            
            rootContainer = new Object3DContainer();
            rootContainer.addChild(camera);
            
            
             cameraController = new SimpleFlyController(new MyEllipsoidCollider(SPEC_RADIUS, SPEC_RADIUS, SPEC_RADIUS), rootContainer, stage, camera, 800, 8);
            
            //camera.addToDebug(Debug.BOUNDS, Box);
            camera.addToDebug(Debug.NODES, KDContainer);
            //camera.addToDebug(Debug.BOUNDS, KDContainer);
    
            stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
            
        }
        
        private function fillA3DBuffers():void {
            Vertex.collector = Vertex.createList(300);
            Face.collector = createFaceList(100);
            Wrapper.collector = createWrapperList(300);
        }
        
        private function createFaceList(i:int):Face {
            var f:Face = new Face();
            while ( --i > -1) {
                f = f.next = new Face();
            }
            return f;
        }
        
        private function createWrapperList(i:int):Wrapper {
            var f:Wrapper = new Wrapper();
            while ( --i > -1) {
                f = f.next = new Wrapper();
            }
            return f;
        }
        
        private var _doSpawn:Boolean = true;
        
        private function onKeyDown(e:KeyboardEvent):void 
        {
            if (e.keyCode === Keyboard.TAB) {
                camera.debug = !camera.debug;
            }
            if (e.keyCode === Keyboard.BACKSPACE) {
                _doSpawn = !_doSpawn;
            }
        }
        
        private function getKDNodeClass():Class {
            var dummy:KDContainer = new KDContainer();
            dummy.createTree(new <Object3D>[new Box(8,8,8)]);
            return Object(dummy.root).constructor;
        }
        
        private function onStageResize(e:Event):void 
        {
            camera.view.width = stage.stageWidth;
            camera.view.height = stage.stageHeight;
        }
        
        //画像読み込み後の処理
        public function loadComplete(e:Event = null):void 
        {
            e.target.removeEventListener(Event.COMPLETE, loadComplete);
            
            image = e.target.loader.content as Bitmap;
            imageData = image.bitmapData;

            //キャンバス用スプライト
            _canvas = new Sprite;
            
            var p:RectanglePiece = new RectanglePiece();
            var node:*;
            var threshold:Number = kdContainer.threshold;
            
            p.x0 = 0;
            p.y0 = 0;
            p.x1 = imageData.width;
            p.y1 = imageData.height;
            p.c = 0;
            
            // Setup root starting
            kdContainer.root = setupNode(p);
            kdContainer.boundMinX = 0;
            kdContainer.boundMinY = 0;
            kdContainer.boundMaxX = p.x1 * WORLD_SCALE;
            kdContainer.boundMaxY = p.y1 * WORLD_SCALE;
            kdContainer.boundMinZ = 0;
            kdContainer.boundMaxZ = MAX_HEIGHT;
            
            
            camera.x = kdContainer.boundMaxX * .5;
            camera.y = kdContainer.boundMaxY * .5;
            camera.z =  MAX_HEIGHT + 63400;
            cameraController.updateObjectTransform();
            cameraController.lookAtXYZ(camera.x, camera.y, 0);
            
            var skybox:SkyBox = new SkyBox(99999999);
            skybox.setMaterialToAllFaces( new FillMaterial(0xEEFEFF) );
            rootContainer.addChild(skybox);
            
            
            var floor:Plane = new Plane(kdContainer.boundMaxX, kdContainer.boundMaxY,1,1,false,false,false,null, new FillMaterial(FLOOR_COLOR) );
            floor.clipping = 2;
            rootContainer.addChild(floor);
            floor.x = kdContainer.boundMaxX * .5;
            floor.y = kdContainer.boundMaxY * .5;
            
            rootContainer.addChild(kdContainer);
            
            //フラクタルデータ保持用配列に初期値挿入
            fillRectangleArray = new Array(p);
            
            
            
            addChild(_canvas);
            addEventListener(Event.ENTER_FRAME, onEnterFrame);
        }
        
        private function setupNode(p:RectanglePiece):* {
            var node:*;
            p.node = node =  new KD_NODE();
            node.boundMinX = p.x0 * WORLD_SCALE;
            node.boundMinY = p.y0 * WORLD_SCALE;
            node.boundMinZ = 0;
            node.boundMaxX = p.x1 * WORLD_SCALE;
            node.boundMaxY = p.y1 * WORLD_SCALE;
            node.boundMaxZ = MAX_HEIGHT;
            return node;
        }
        

    
        // todo: optimization and pooling?
        private function createBuilding(p:RectanglePiece, color:uint, fromHeight:Number, fromColor:uint):void
        {
            ///*
            
            //node.boundMaxZ = 0;
            //*/
            var node:* = p.node;
            
            var height:Number = 1 + _tint * MAX_HEIGHT;
            var w:Number = (p.x1 - p.x0) * WORLD_SCALE;
            var h:Number = (p.y1 - p.y0) * WORLD_SCALE;
            var building:KDBuilding = new KDBuilding(p.x0 * WORLD_SCALE, p.y0 * WORLD_SCALE, w, h, height, color);
            
            building.height = fromHeight;
            TweenLite.to(building, 1.6, { height:height, ease:Cubic.easeInOut } );
                
            (building.faceList.material as FillMaterial).color = fromColor;
            TweenLite.to(building.faceList.material, .4, { hexColors: {color:color}, ease:Linear.easeNone } );
            
            node.objectList = building;
            node.objectBoundList = building;
        }
        

        
        //ループ
        private function onEnterFrame(e:Event):void 
        {
            var node:*;
            
            if (!_doSpawn) {
                cameraController.update();
                camera.transformId++;
                camera.render();
                return;
            }
            
            
            //フラクタル処理終了
            if (fillRectangleArray.length < 1) {
                removeEventListener(Event.ENTER_FRAME, onEnterFrame);
                var tx:TextField = new TextField();
                tx.text = '終了';
                tx.textColor = 0xFFFFFF;
                addChild(tx);
            }else {
                //フラクタルデータ保持用配列から1つ取り出す
                var rect:RectanglePiece = fillRectangleArray.shift();
                var cArray:Array = deviationLogic(rect.x0, rect.y0, rect.x1, rect.y1);
                rect.c = cArray[0];
                rect.color = cArray[1];
                
                var halfWidth:Number = (rect.x1 - rect.x0) * .5;
                var halfHeight:Number = (rect.y1 - rect.y0) * .5;

                // 指定した矩形内の輝度の標準偏差値が閾値以上なら2分木して処理続行
                if (rect.c > THRESHOLD && (halfWidth > 2 || halfHeight > 2)) {
                    //矩形を書くよ
                    /*
                    _canvas.graphics.lineStyle(0, 0xAAAAAA);
                    _canvas.graphics.beginFill(cArray[1]);
                    _canvas.graphics.drawRect(rect.x0, rect.y0, (rect.x1 - rect.x0), (rect.y1 - rect.y0));
                    */
                    
                    node  = rect.node;
                    var removeObj:KDBuilding;
                    if (rect.parent != null ) {
                        if ((removeObj=rect.parent.node.objectList) != null) {
                            
                            rect.parent.node.objectList = null;
                            // todo: cut road along parent splitter.
                            
                            if (rect.positive) {
                                rect.parent.node.positive = node;
                                rect.parent.node.negative = rect.sibling.node;
                            }
                            else {
                                rect.parent.node.negative = node;
                                rect.parent.node.positive = rect.sibling.node;
                            }
                            TweenLite.killTweensOf(removeObj);
                            createBuilding(rect, rect.color, removeObj.height, rect.parent.color);
                            createBuilding(rect.sibling, rect.parent.color, removeObj.height, rect.parent.color);
                            
                        }
                        else {  // fill up remaining branch (either negative or postiive)
                            if (rect.positive) {
                                rect.parent.node.positive = node;
                                removeObj = rect.node.objectList;
                            }
                            else {
                                rect.parent.node.negative = node;
                                removeObj = rect.node.objectList;
                            }
                            createBuilding(rect, rect.color, removeObj.height, rect.parent.color);
                        }
                    }
                    else { // Root node case!
                        createBuilding(rect, rect.color, 0, FLOOR_COLOR);
                    }
                    
                    
                    //矩形を2分割してフラクタルデータ保持用配列に突っ込む
                    
                    var rect0:RectanglePiece = new RectanglePiece();
                    var rect1:RectanglePiece = new RectanglePiece();
                   var randomX:Number = Math.floor(Math.random()*(halfWidth-4)+4); 
                    var randomY:Number = Math.floor(Math.random()*(halfHeight-4)+4);
                  
                    
                    // Rather hackish pointers here!
                    rect0.positive = false;
                    rect1.positive = true;
                    rect0.sibling = rect1;
                    rect1.sibling  = rect0;
                    rect0.parent = rect;
                    rect1.parent = rect;
                    
                    if (halfWidth > halfHeight) {
                        
                        node.axis = 0;
                        node.coord = (rect.x0 + randomX) * WORLD_SCALE;
                        node.minCoord = node.coord - kdContainer.threshold;
                        node.maxCoord = node.coord + kdContainer.threshold;
                        
                        rect0.x0 = rect.x0;   // negative x
                        rect0.y0 = rect.y0;
                        rect0.x1 = rect.x0+randomX;
                        rect0.y1 = rect.y1;
                        fillRectangleArray.push(rect0);
                        
                        rect.node.negative
                        setupNode(rect0);
                        

                        rect1.x0 = rect.x0+randomX;  // postive x
                        rect1.y0 = rect.y0;
                        rect1.x1 = rect.x1;
                        rect1.y1 = rect.y1;
                        fillRectangleArray.push(rect1);
                        
                        
                        rect.node.positive;
                        setupNode(rect1);
                    

                    }else {
                        node.axis = 1;
                        node.coord = (rect.y0 + randomY) * WORLD_SCALE;
                        node.minCoord = node.coord - kdContainer.threshold;
                        node.maxCoord = node.coord + kdContainer.threshold;

                        rect0.x0 = rect.x0;  // negative y
                        rect0.y0 = rect.y0;
                        rect0.x1 = rect.x1;
                        rect0.y1 = rect.y0+randomY;
                        fillRectangleArray.push(rect0);
                        
                        rect.node.negative;
                        setupNode(rect0);
                        

                        rect1.x0 = rect.x0;  //postive y
                        rect1.y0 = rect.y0+randomY;
                        rect1.x1 = rect.x1;
                        rect1.y1 = rect.y1;
                        fillRectangleArray.push(rect1);
                        
                        
                        rect.node.positive
                        setupNode(rect1);
                        
                    }
                }
            }
            
        //cameraController.setObjectPosXYZ(camera.x, camera.y, camera.z);
            cameraController.update();
            var sphere:Vector3D = new Vector3D(camera.x, camera.y, camera.z, SPEC_RADIUS);
            var len:int = KDBuilding.numCollidables;
            var collidables:Vector.<KDBuilding> = KDBuilding.collidables;
            var collidable:KDBuilding;
            var highestZ:Number = 0;
            if (!cameraController.hasMoved) {  
                // do a dummy collision test
                cameraController.collider.getCollision( new Vector3D(camera.x, camera.y, camera.z), DUMMY_DISPLACE ,DUMMY_VECTOR1, DUMMY_VECTOR2, rootContainer);
            }
            len = KDBuilding.numCollidables;
            for (var i:int = 0; i < len; i++) {
                collidable = collidables[i];
                if (kdContainer.boundIntersectSphere(sphere,collidable.boundMinX, collidable.boundMinY, collidable.boundMinZ, collidable.boundMaxX, collidable.boundMaxY, collidable.boundMaxZ)  && collidable.boundMaxZ > highestZ) {
                    highestZ = collidable.boundMaxZ;
                }
            }
            
                if ( camera.z < highestZ + SPEC_RADIUS) {

                    cameraController.setObjectPosXYZ(camera.x, camera.y, highestZ + SPEC_RADIUS);
                    camera.z =  highestZ + SPEC_RADIUS;
                }
        
            camera.transformId++;
            camera.render();
        }
        

        /**
         * 指定した矩形間の輝度の標準偏差を求める
         * @param    x0    左上のx座標
         * @param    y0    左上のy座標
         * @param    x1    右下のx座標
         * @param    y1    右下のy座標
         * @return    標準偏差値とカラーの平均
         */
        private function deviationLogic(x0:Number,y0:Number,x1:Number,y1:Number):Array {
            var rgb:uint = 0;
            var r:uint = 0;
            var g:uint = 0;
            var b:uint = 0;
            var hsb:Array = new Array();
            var bArray:Array = new Array();
            var br:Number = 0;
            var av:Number = 0;

            //輝度の平均を計算
            for (var i:int = x0; i < x1;i++ ) {
                for (var j:int = y0; j < y1; j++ ) {
                    rgb = imageData.getPixel(i, j);
                    r += (rgb >> 16) & 255;
                    g += (rgb >> 8) & 255;
                    b += rgb & 255;
                    hsb = uintRGBtoHSB(rgb);
                    br += hsb[2];
                    bArray.push(hsb[2]);
                }
            }
            av = br / bArray.length;
            r = r / bArray.length;
            g = g / bArray.length;
            b = b / bArray.length;
            rgb = (r << 16) | (g << 8) | (b << 0);
            _tint = (255 - ( 0.21 * r + 0.71 * g + 0.07 * b )) * INV_255;
            //標準偏差を計算
            br = 0;
            for (i = 0; i < bArray.length; i++ ) {
                br += (bArray[i] - av) *(bArray[i] - av);
            }
            return [Math.sqrt(br / bArray.length),rgb];
            
        }
        /**
         * 
         * @param    rgb    RGB成分(uint)
         * @return HSB配列([0]=hue, [1]=saturation, [2]=brightness)
         */
        private function uintRGBtoHSB(rgb:uint):Array {
            var r:uint = (rgb >> 16) & 255;
            var g:uint = (rgb >> 8) & 255;
            var b:uint = rgb & 255;
            return RGBtoHSB(r, g, b);
        }
        /** RGBからHSBをつくる
         * @param r    色の赤色成分(0~255)
         * @param g 色の緑色成分(0~255)
         * @param b 色の青色成分(0~255)
         * @return HSB配列([0]=hue, [1]=saturation, [2]=brightness)
         */
        private function RGBtoHSB(r:int, g:int, b:int):Array {
            var cmax:Number = Math.max(r, g, b);
            var cmin:Number = Math.min(r, g, b);
            var brightness:Number = cmax / 255.0;
            var hue:Number = 0;
            var saturation:Number = (cmax != 0) ? (cmax - cmin) / cmax : 0;
            if (saturation != 0) {
                var redc:Number = (cmax - r) / (cmax - cmin);
                var greenc:Number = (cmax - g) / (cmax - cmin);
                var bluec:Number = (cmax - b) / (cmax - cmin);
                if (r == cmax) {
                    hue = bluec - greenc;
                } else if (g == cmax) {
                    hue = 2.0 + redc - bluec;
                } else {
                    hue = 4.0 + greenc - redc;
                }
                hue = hue / 6.0;
                if (hue < 0) {
                    hue = hue + 1.0;
                }
            }
            return [hue, saturation, brightness];
        }
    }    
}
import alternativ7.engine3d.containers.KDContainer;
import alternativ7.engine3d.core.Camera3D;
import alternativ7.engine3d.core.Canvas;
import alternativ7.engine3d.core.EllipsoidCollider;
import alternativ7.engine3d.core.Face;
import alternativ7.engine3d.core.Object3D;
import alternativ7.engine3d.core.Vertex;
import alternativ7.engine3d.core.VG;
import alternativ7.engine3d.core.Wrapper;
import alternativ7.engine3d.materials.FillMaterial;
import alternativ7.engine3d.objects.Mesh;
import alternativ7.engine3d.primitives.Box;
import alternativ7.engine3d.alternativa3d;
import flash.geom.Vector3D;
import flash.utils.Dictionary;
use namespace alternativa3d;

    /**
     * ...
     * @author DefaultUser (Tools -> Custom Arguments...)
     */
    class RectanglePiece 
    {
        public var x0:Number;
        public var y0:Number;
        public var x1:Number;
        public var y1:Number;
        public var c:Number;
        public var node:*;
        public var parent:RectanglePiece;
        public var positive:Boolean;
        public var color:uint;
        public var sibling:RectanglePiece;
        
        public function RectanglePiece() 
        {
             this.x0 = 0;
             this.y0 = 0;
             this.x1 = 0;
             this.x1 = 0;
             this.c = 0;            
        }
        
    }
    
    
class MyEllipsoidCollider extends EllipsoidCollider {
    public function MyEllipsoidCollider(radiusX:Number, radiusY:Number, radiusZ:Number) {
        super(radiusX, radiusY, radiusZ);
    }
    
        override public function calculateDestination (source:Vector3D, displacement:Vector3D, object:Object3D, excludedObjects:Dictionary = null) : Vector3D {
            KDBuilding.numCollidables = 0;
            return super.calculateDestination(source, displacement, object, excludedObjects);
        }


        override public function getCollision (source:Vector3D, displacement:Vector3D, resCollisionPoint:Vector3D, resCollisionPlane:Vector3D, object:Object3D, excludedObjects:Dictionary = null) : Boolean {
            KDBuilding.numCollidables = 0;
            return super.getCollision(source, displacement, resCollisionPoint, resCollisionPlane, object, excludedObjects);
        }
        
        public function $calculateDestination(source:Vector3D, displacement:Vector3D, object:Object3D, excludedObjects:Dictionary = null) : Vector3D {
            return super.calculateDestination(source, displacement, object, excludedObjects);
        }
        
        public function $getCollision (source:Vector3D, displacement:Vector3D, resCollisionPoint:Vector3D, resCollisionPlane:Vector3D, object:Object3D, excludedObjects:Dictionary = null) : Boolean {
            return super.getCollision(source, displacement, resCollisionPoint, resCollisionPlane, object, excludedObjects);
        }
    
}
    
    class KDBuilding extends Mesh
    {
        // TODO: Can consider recycling of KDBuildings
        
        public static var collidables:Vector.<KDBuilding> = new Vector.<KDBuilding>();
        public static var numCollidables:int = 0;
        
        public function KDBuilding(xPos:Number, yPos:Number, width:Number, length:Number, height:Number, color:uint) 
        {
            clipping = 2;
            sorting = 0;
            
            var v1:Vertex;
            var v2:Vertex;
            var v3:Vertex;
            var v4:Vertex;
                
            var mat:FillMaterial = new FillMaterial(color);
            var v:Vertex;
            var f:Face;
            var w:Wrapper;

            // Define top roof vertices
            vertexList = v = Vertex.collector || new Vertex(); v.u = 0; v.v = 0;
            Vertex.collector = v.next;
            v.x = xPos;
            v.y = yPos;
            v.z = height;
            v1 = v;
            
            v.next =  v = v.create(); v.u = 0; v.v = 0;
            v.x = xPos + width;
            v.y = yPos;
            v.z = height;
            v2 = v;
            
            v.next =  v = v.create(); v.u = 0; v.v = 0;
            v.x = xPos + width;
            v.y = yPos + length;
            v.z = height;
            v3 = v;
            
            v.next = v =  v.create(); v.u = 0; v.v = 0;
            v.x = xPos;
            v.y = yPos + length;
            v.z = height;
            v4 = v;
            
            var v5:Vertex;
            var v6:Vertex;
            var v7:Vertex;
            var v8:Vertex;
            
            // Define bottom vertices
            v.next = v = v.create(); v.u = 0; v.v = 0;
            v.x = xPos;
            v.y = yPos;
            v.z = 0;
            v5 = v;
            
            v.next =  v = v.create(); v.u = 0; v.v = 0;
            v.x = xPos + width;
            v.y = yPos;
            v.z = 0;
            v6 = v;
            
            v.next =  v = v.create(); v.u = 0; v.v = 0;
            v.x = xPos + width;
            v.y = yPos + length;
            v.z = 0;
            v7 = v;
            
            v.next = v =  v.create(); v.u = 0; v.v = 0;
            v.x = xPos;
            v.y = yPos + length;
            v.z = 0;
            v8 = v;
            
            // top face
            faceList = f = Face.collector || new Face();
            Face.collector = f.next;
            f.material = mat;
            f.wrapper = w = Wrapper.collector || new Wrapper();
            Wrapper.collector = w.next; 
            w.vertex = v1;
            w.next = w = w.create();
            w.vertex = v2;
            w.next = w = w.create();
            w.vertex = v3;
            w.next = w = w.create();
            w.vertex = v4;
            f.normalX = 0;
            f.normalY = 0;
            f.normalZ = 1;
            f.offset = height;
            //f.calculateBestSequenceAndNormal();
            //if (!f.normal.nearEquals(new Vector3D(0,0,1), 0.001)) throw new Error("MISMATCH top!"+f.normal);
            
            // South face
            f.next = f = f.create();
            f.material = mat;
            f.wrapper = w = w.create();
            w.vertex = v5;
            w.next = w = w.create();
            w.vertex = v6;
            w.next = w = w.create();
            w.vertex = v2;
            w.next = w = w.create();
            w.vertex = v1;
            f.normalX = 0;
            f.normalY = -1;
            f.normalZ = 0;
            f.offset = -yPos;
            //f.calculateBestSequenceAndNormal();
            //if (!f.normal.nearEquals(new Vector3D(0,-1,0), 0.001)) throw new Error("MISMATCH south!"+f.normal);
            
            // East Face
            f.next = f = f.create();
            f.material = mat;
            f.wrapper = w = w.create();
            w.vertex = v6;
            w.next = w = w.create();
            w.vertex = v7;
            w.next = w = w.create();
            w.vertex = v3;
            w.next = w = w.create();
            w.vertex = v2;
            f.normalX = 1;
            f.normalY = 0;
            f.normalZ = 0;
            f.offset = xPos + width;
            //f.calculateBestSequenceAndNormal();
            //if (!f.normal.nearEquals(new Vector3D(1,0,0), 0.001)) throw new Error("MISMATCH east!"+f.normal);
                
            // North Face
            f.next = f = f.create();
            f.material = mat;
            f.wrapper = w = w.create();
            w.vertex = v7;
            w.next = w = w.create();
            w.vertex = v8;
            w.next = w = w.create();
            w.vertex = v4;
            w.next = w = w.create();
            w.vertex = v3;
            f.normalX = 0;
            f.normalY = 1;
            f.normalZ = 0;
            f.offset = yPos + length;
            //f.calculateBestSequenceAndNormal();
            //if (!f.normal.nearEquals(new Vector3D(0,1,0), 0.001)) throw new Error("MISMATCH north!"+f.normal);
            
            // West Face
            f.next = f = f.create();
            f.material = mat;
            f.wrapper = w = w.create();
            w.vertex = v8;
            w.next = w = w.create();
            w.vertex = v5;
            w.next = w = w.create();
            w.vertex = v1;
            w.next = w = w.create();
            w.vertex = v4;
            f.normalX = -1;
            f.normalY = 0;
            f.normalZ = 0;
            f.offset = -xPos;
            //f.calculateBestSequenceAndNormal();
            //if (!f.normal.nearEquals(new Vector3D(-1,0,0), 0.001)) throw new Error("MISMATCH west!"+f.normal);
            
            
            // ^^^ note vertex normals not coded in!    
            
            // calculate bounds
            boundMinX = xPos;
            boundMinY = yPos;
            boundMinZ = 0;
            boundMaxX = xPos + width;
            boundMaxY = yPos + length;
            boundMaxZ = height;
        
            // for debugging (checking) purposes
            //calculateFacesNormals();
        }
        
        // Boiler-plate mesh draw implementation to prevent errors with camera.debug mode due to
        // private classes.
        override alternativa3d function draw(camera:Camera3D, parentCanvas:Canvas):void {

            calculateInverseMatrix();
            // either transformId++; or if camera.transformId used as an incrementing timestamp
            transformId = camera.transformId;  
            var f:Face = prepareFaces(camera);
            if (f == null) return;
            if (culling > 0) {
                f = camera.clip(f, culling);
                if (f == null) return;
            }
            drawFaces(camera, parentCanvas.getChildCanvas(true, false, this, 1, blendMode, colorTransform, filters), f);
            
        }
        
        override alternativa3d function getVG(camera:Camera3D):VG {
            
            calculateInverseMatrix();
            // either transformId++; or if camera.transformId used as an incrementing timestamp
            transformId = camera.transformId;
            var f:Face = prepareFaces(camera);
            if (f == null) return null;
            if (culling > 0) {
                camera.clip(f, culling);
                if (f == null) return null;
            }
            return VG.create(this, f, sorting, 0, false);
            
        }
        
        override alternativa3d function collectPlanes(center:Vector3D, a:Vector3D, b:Vector3D, c:Vector3D, d:Vector3D, collector:Vector.<Face>, excludedObjects:Dictionary=null) : void {
transformId++;
            collidables[numCollidables++] = this;
            super.collectPlanes(center, a, b, c, d, collector, excludedObjects);
        }

        
        public function get height():Number { return vertexList.z; }
        
        // TODO: Adjust height of related KD nodes as well once chars are put in
        public function set height(value:Number):void 
        {
            var v:Vertex = vertexList;
            v.z = value; v = v.next;
            v.z = value; v = v.next;
            v.z = value; v = v.next;
            v.z = value;
            faceList.offset = value;
            boundMaxZ = value;
        }
        
        
        
    }
    

    
    

    import alternativ7.engine3d.controllers.SimpleObjectController;
    import alternativ7.engine3d.core.Camera3D;
    import alternativ7.engine3d.core.EllipsoidCollider;
    import alternativ7.engine3d.core.Object3D;
    import flash.display.InteractiveObject;
    import flash.geom.Vector3D;
    import flash.utils.Dictionary;
    /**
     * ...
     * @author Glenn Ko
     */
    class SimpleFlyController extends SimpleObjectController 
    {
        public var currentPosition:Vector3D;
        public var collider:EllipsoidCollider;
        public var collidable:Object3D;
        
        public var displacement:Vector3D = new Vector3D();
        public var collisionPoint:Vector3D = new Vector3D();
        public var lastPosition:Vector3D  = new Vector3D();
        public var excludedObjects:Dictionary = null;
        public var gotCollision:Boolean;
        public var collisionPlane:Vector3D = new Vector3D();
        public var hasMoved:Boolean = false;
        
        public function SimpleFlyController(collider:EllipsoidCollider, collidable:Object3D, eventSource:InteractiveObject, object:Object3D, speed:Number, speedMultiplier:Number = 3, mouseSensitivity:Number = 1) 
        {
            super(eventSource, object, speed, speedMultiplier, mouseSensitivity);
            this.collider = collider;
            this.collidable = collidable;
        }
        

        override public function update():void {
            var object:Object3D = this.object;
            
            if (object == null) return;
            if (collider && collidable) {
                lastPosition.x = object.x;
                lastPosition.y = object.y;
                lastPosition.z = object.z;
                super.update();
                displacement.x = object.x -  lastPosition.x;
                displacement.y = object.y  - lastPosition.y;
                displacement.z = object.z  - lastPosition.z;
                if (displacement.x * displacement.x + displacement.y * displacement.y + displacement.z * displacement.z == 0) {
                    gotCollision = false;
                    hasMoved = false;
                    return;
                }
                hasMoved = true;
                gotCollision = collider.getCollision(lastPosition, displacement, collisionPoint, collisionPlane, collidable);
                var dest:Vector3D = collider.calculateDestination(lastPosition, displacement, collidable, excludedObjects);
                 // set back frame coherant transform values
                setObjectPosXYZ(dest.x, dest.y, dest.z);
                // refresh values immediately
                object.x = dest.x; 
                object.y = dest.y;
                object.z = dest.z;
                
                displacement.x = dest.x -  lastPosition.x;
                displacement.y = dest.y  - lastPosition.y;
                displacement.z = dest.z  - lastPosition.z;
                
                currentPosition = dest;
                
    
            }
            else {
                super.update();
            }
        }
        
    }