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

TerrainQuadLODContainer with terrain generators/loaders

LOD terrain in Alternativa3D.  This version doesn't support tileset painting
 (only a single world terrain texture).

Saving functionality (to binary) to be added soon.

Here, there are various options to create infinite tilable or single terrain pieces, with flat plane water/floor level clipping. 

The TerrainQuadLODContainer class supports conflict geometry sorting as well that can be added directly to the container display list at the top the quadtree.

Will include biome terrain tileset painting support in another version. The current class without tileset support (per tile drawing/blitting) is called TerrainQuadLODContainerNT.as.
Get Adobe Flash player
by Glidias 23 Oct 2012

    Talk

    makc3d at 15 Oct 2011 18:03
    when on the surface, there are no extra details from new geometry, so it could equally be few LOD levels less
    Glidias at 15 Oct 2011 19:02
    Yup, fixed it. In an earlier implementation, I was doing per-tile-zone texturing which required a unique "face" (with a unique material/uv rotation coordinate set) being drawn to be at high-enough resolution if the world texture resolution mipmap at that distance didn't fit, so VertexTest() and BoxTest() in QuadSquare.as was replaced with BoxMipTest() and VertexMipTest() to account for camera distance resolution, forcing higher polygons upclose. In this case, since there's no per-tile zone individual face texturing, i reverted back to the original calls without the mip tests. What's missing (what i took out) from QuadSquare.as was the vertex lighting recalculation/storage code back into QuadSquare.as to support vertex lighting for lighted texture materials in a3d 7.7-8 onwards. Since all terrain geometry (QuadRenderData.as buffer and triBuffer) is generated/reused on the fly every frame based on the quad tree structure being visited..

    Tags

    Embed
/**
 * Copyright Glidias ( http://wonderfl.net/user/Glidias )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/zhVG
 */

package  {
    
    import alternativ7.engine3d.core.Camera3D;
    import alternativ7.engine3d.core.Debug;
    import alternativ7.engine3d.core.EllipsoidCollider;
    import alternativ7.engine3d.core.MouseEvent3D;
    import alternativ7.engine3d.core.Object3D;
    import alternativ7.engine3d.core.Object3DContainer;
    import alternativ7.engine3d.core.RayIntersectionData;
    import alternativ7.engine3d.core.Vertex;
    import alternativ7.engine3d.core.View;
    import alternativ7.engine3d.loaders.Parser3DS;
    import alternativ7.engine3d.materials.FillMaterial;
    import alternativ7.engine3d.materials.TextureMaterial;
    import alternativ7.engine3d.objects.Mesh;
    import alternativ7.engine3d.alternativa3d;
    import alternativ7.engine3d.primitives.Plane;
    import com.bit101.components.CheckBox;
    import com.bit101.components.HBox;
    import com.bit101.components.Label;
    import com.bit101.components.PushButton;
    import com.bit101.components.Slider;
    import com.bit101.components.Window;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.DisplayObject;
    import flash.display.InteractiveObject;
    import flash.display.Loader;
    import flash.display.LoaderInfo;
    import flash.display.Sprite;
    import flash.display.Stage;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.events.Event;
    import flash.events.IEventDispatcher;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.events.TextEvent;
    import flash.filters.ColorMatrixFilter;
    import flash.geom.Matrix;
    import flash.geom.Point;
    import flash.net.FileReference;
    import flash.net.URLLoader;
    import flash.net.URLLoaderDataFormat;
    import flash.net.URLRequest;
    import flash.system.ApplicationDomain;
    import flash.system.LoaderContext;
    import flash.text.TextField;
    import flash.ui.Keyboard;
    import flash.utils.setTimeout;

    use namespace alternativa3d;
    
    /**
     * TerrainQuadLODContainer with Terrain Generators/Loaders.
     * 
     * This version doesn't support tileset painting (only a single world terrain texture).
     */
     [SWF(width = 800, height = 600, frameRate = 40 , backgroundColor = "#EEEEFF")]
    public class QuadLODNTerrainTestFL extends Sprite {
        
        private var rootContainer:Object3DContainer = new Object3DContainer();

        public static const DEG_TO_RAD:Number = Math.PI / 180;
        public static const RAD_TO_DEG:Number = 180 / Math.PI;
        private var camera:Camera3D;
        private var controller:SimpleFlyController;
        
        private var _debugField:TextField =  new TextField();
        private var _heightMap:HeightMapInfo;
        
        /*
        private var _saveBtn:InteractiveObject
        private var saveLoad:SaveLoad = new SaveLoad();
        */
        private var _currentTerrain:TerrainQuadLODContainerNT;
        private var _terrainTileList:Vector.<TerrainQuadLODContainerNT>;
        private var _setup:SetupPanelNT;
        private var _worldModel:WorldModel;
        private var _rootData:QuadCornerData;
        private var _lastWorldMaterial:TextureMaterial;
        private var _lastFloorMaterial:TextureMaterial;  // actually this is the "water", but we call it a "floor" here since it's just a flat plane.
        private var _floor:Floor;
        private var _lodSlider:Slider;
        private var _lodSlideVal:Label;
        
        private var _world:OverworldTerrainContainer;
        
        
        
        public function QuadLODNTerrainTestFL() {

            stage.align = StageAlign.TOP_LEFT;
            stage.scaleMode = StageScaleMode.NO_SCALE;
            
            init();
        }
        

        private function init():void  {
            // Camera and view
            camera = new Camera3D();
            camera.view = new View(stage.stageWidth, stage.stageHeight);
            //camera.farClipping = 7000;
            addChild(camera.view);
        
        
            camera.y = -400;
            camera.z = 600;
            //camera.z = 14000;
            
            controller = new SimpleFlyController(new EllipsoidCollider(32, 32, 72), rootContainer, stage, camera, 1000, 3);
            
            
            addChild( _setup = new SetupPanelNT() );
            _setup.comp_updateTerrainRes.addEventListener(MouseEvent.CLICK, onUpdateTerrainResClick);
            _setup.comp_updateWaterRes.addEventListener(MouseEvent.CLICK, onUpdateWaterResClick);
            _setup.comp_startBtn.addEventListener(MouseEvent.CLICK, onStartClick);
            _setup.comp_mipmapTerrainStretch.addEventListener(MouseEvent.CLICK, onMipStretchBtnClick);
            _setup.comp_mipmapWaterStretch.addEventListener(MouseEvent.CLICK, onMipStretchBtnClick);
        }
        
        private function onMipStretchBtnClick(e:Event):void 
        {
            var worldSize:int = ((1 << _rootData.Level) << 1);
            if (e.currentTarget === _setup.comp_mipmapTerrainStretch) {
                _setup.comp_mipmapTerrainRes.value = worldSize / _lastWorldMaterial._texture.width;
            //    onUpdateTerrainResClick();
            }
            else {
                _setup.comp_mipmapWaterRes.value = worldSize / _lastFloorMaterial._texture.width;;
            //    onUpdateWaterResClick();
            }
            
        }
        
        private function onUpdateWaterResClick(e:MouseEvent=null):void 
        {
            var bmpData:BitmapData = _lastFloorMaterial.texture;
            var gotMipMapping:Boolean = _setup.waterMipMapResolution != 0 && _worldModel.isBase2(bmpData.width) && _worldModel.isBase2(bmpData.height) && bmpData.width == bmpData.height;
            _lastFloorMaterial.mipMapping = gotMipMapping ? 2 : 0;
            _lastFloorMaterial.resolution = _setup.waterMipMapResolution * _setup.waterMipMapResolutionScale;
            var worldSize:int = ((1 << _rootData.Level) << 1);
            _lastFloorMaterial.repeat = gotMipMapping && _setup.waterMipMapResolution *  bmpData.width < worldSize;
            
            
            adjustWaterUVs();
        }
        
        private function onUpdateTerrainResClick(e:MouseEvent=null):void 
        {
            var terrain:TerrainQuadLODContainerNT;
        //    _lastWorldMaterial.mipMapping = ;
        var bmpData:BitmapData = _lastWorldMaterial.texture;
            var gotMipMapping:Boolean = _setup.terrainMipMapResolution != 0 && _worldModel.isBase2(bmpData.width) && _worldModel.isBase2(bmpData.height) && bmpData.width == bmpData.height;
            _lastWorldMaterial.mipMapping = gotMipMapping ? 2 : 0;
            _lastWorldMaterial.resolution = _setup.terrainMipMapResolution * _setup.terrainMipMapResolutionScale;
            if (gotMipMapping) {
                
                _worldModel.worldTextureDistance = _setup.terrainMipMapResolution * bmpData.width;
                if (_currentTerrain != null) {
                    _currentTerrain.setupMaterials(_worldModel);
                }
                else {
                    for each( terrain in _terrainTileList) {
                        terrain.setupMaterials(_worldModel);
                    }
                }
            }
            else {
                _worldModel.resetWorldTextureDistance();
                if (_currentTerrain != null) {
                    _currentTerrain.setupMaterials(_worldModel);
                }
                else {
                    for each( terrain in _terrainTileList) {
                        terrain.setupMaterials(_worldModel);
                    }
                }
            }
            var worldSize:int = ((1 << _rootData.Level) << 1);
            _lastWorldMaterial.repeat = gotMipMapping && _setup.terrainMipMapResolution *  bmpData.width < worldSize;
        
        }
        
    
        
        private function onStartClick(e:MouseEvent):void 
        {
            var selectedItem:String = _setup.comp_method.selectedItem as String;
            if (selectedItem === SetupPanelNT.METHOD_STELLAR) {
                if (!_setup.comp_x3.selected) {
                    prepareStellarTerrain();
                }
                else {
                    prepareStellarTerrain3x3();
                }
            }
            else if (selectedItem === SetupPanelNT.METHOD_PERLIN) {
                if (!_setup.comp_x3.selected) {
                    preparePerlinTerrain();
                }
                else {
                    preparePerlinTerrain(true);
                }
            }
            else if (selectedItem === SetupPanelNT.METHOD_LOADED) {
                if (_setup.file_terrain.data == null) return;
                var ext:String = _setup.file_terrain.name.split(".").pop();
                var loader:Loader = new Loader();
                if (ext.toLowerCase() === "3ds") {
                    parseTerrain3DS(_setup.file_terrain.data);
                }
                else {  // assumed image
                    loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadTerrainImageComplete);
                    loader.loadBytes(_setup.file_terrain.data);
                    return;
                }
                // TODO:
            }
            else {
                throw new Error("COuld not resolve method type of setup!:"+selectedItem);
            }
            onLoadDone();
        }
        
    
        
        private function onLoadTerrainImageComplete(e:Event):void 
        {
            (e.currentTarget as IEventDispatcher).removeEventListener(e.type, onLoadTerrainImageComplete);
            prepareBmpDataTerrain( ((e.currentTarget as LoaderInfo).content as Bitmap).bitmapData, _setup.comp_x3.selected);
            onLoadDone();
        }
        

        

        
        private function onLoadDone():void {
            
            
            removeChild(_setup);
            startViewing();
        }
        

        
        private function startViewing():void {
            
        
            addChild(camera.diagram);

            ///*
            if (_currentTerrain != null) {
                var half:int = ((1 << _rootData.Level));
                try {
                    camera.z = _heightMap.Sample(camera.x - _rootData.xorg, camera.y - _rootData.zorg);
                    camera.z += 256;
                }
                catch (e:Error) {
                    camera.z = _currentTerrain.rootQuad.Square.MaxY + 256;
                }
                controller.setObjectPosXYZ(camera.x, camera.y, camera.z);
            controller.lookAtXYZ(_rootData.xorg + half, 
                                _rootData.zorg + half,
                                _rootData.Square.MinY + (_rootData.Square.MaxY - _rootData.Square.MinY) * .5);
            }
            else { // TODO: world camera placement
                
            }
        //    */
        
            
            // Listeners
            camera.render();  // !important! Need to get camera focalLength for world update
            startRendering();
            stage.addEventListener(Event.RESIZE, onResize);
            
            
            
            rootContainer.addChild(camera);
            
            setupUI();
            _debugField.autoSize = "left";
            addChild(_debugField);
        }
        
        private var _window:Window;
        private function setupUI():void 
        {
            var baseUI:BaseUI = new BaseUI();
            addChild(baseUI);
            var hBox:HBox = new HBox(baseUI);
            baseUI.y = 5;
            baseUI.x = 5;
            
            var windowBaseUI:BaseUI = new BaseUI();
            addChild(windowBaseUI);
            var window:Window = _window = new Window(windowBaseUI, 4, 28, "Options");
            
            
            window.setSize( _setup.panel_textureStuff.width + 5 * 2, _setup.panel_textureStuff.height + 5 * 2 + 25);
            
        _setup.panel_textureStuff.x = 5;
            _setup.panel_textureStuff.y = 25;
            window.addChild(_setup.panel_textureStuff);
    
            new CheckBox(hBox, 0, 0, "Preview LOD", onPReviewLODCheck);
            new Label(hBox, 0, 0, "adjust>>:");
            
            
            _lodSlider = new Slider(Slider.HORIZONTAL, hBox, 0, 0, onLODSlide);
            _lodSlider.minimum = 5;
            _lodSlider.value = 30;
            _lodSlider.maximum = 800;
            
            _lodSlideVal = new Label(hBox);
            onLODSlide();
            
            new Label(hBox, 0, 0, "    ");
            var cb:CheckBox = new CheckBox(hBox, 0, 0, "Hide Options", onHideOptionsCheck);
            cb.selected = true;
            
            _setup.file_terrainTexture.addEventListener(Event.COMPLETE, terrainTextureLoadedHandler);
            _setup.file_waterTexture.addEventListener(Event.COMPLETE, waterTextureLoadedHandler);
            _setup.addEventListener(SetupPanelNT.E_WATERCHANGE, updateWaterState);
            
            _setup.addEventListener(Event.SELECT, onFileOpen);
            _setup.addEventListener(Event.CLOSE, onFileClose);
            _setup.addEventListener(Event.CANCEL, onFileClose);
            
            hBox.draw();
        }
        
        private function onHideOptionsCheck(e:Event):void 
        {
            _window.visible = (e.currentTarget as CheckBox).selected;
        }
        
    
        
        
        private function onFileClose(e:Event):void 
        {
    
            startRendering();
        }
        
        private function onFileOpen(e:Event):void 
        {
        
            camera.view.graphics.clear();
            stopRendering();
        }
        
        
        
        private function onPReviewLODCheck(e:Event=null):void 
        {
            camera.debug  = (e.target as CheckBox).selected;    
        }
        
        private function onLODSlide(e:Event=null):void 
        {
            var val:Number = getDetailFromSlider();
            
            if (_currentTerrain != null) {
                _currentTerrain.detail = val;
                _lodSlideVal.text = String(val);
            }
            
            if (_terrainTileList != null) {
                /*  // Not needed, world does detail update
                var i:int = _terrainTileList.length;
                while (--i > -1) {
                    (_terrainTileList[i]).detail = val;
                }
                */
                _world.setDetail(val);
            }
        }
        
        private function getDetailFromSlider():Number 
        {
            return Math.round(_lodSlider.value)
        }
        
        
        /*
        private function onSaveClick(e:MouseEvent):void 
        {
            var bytes:ByteArray = saveLoad.save(_worldModel, _heightMaps, (currentTerrain as TerrainGridKDContainer).tileDefMap, _defaultTexture);
            saveLoad.load(bytes, tileSetViewer.model);
            (currentTerrain as TerrainGridKDContainer).tileDefMap = saveLoad.textureDefMap;
            (currentTerrain as TerrainGridKDContainer).setupMaterials(_worldModel);
            //throw new Error(bytes.bytesAvailable);
        }
        */
        
        

        private function loadModel(url:String):void 
        {
            var loader3ds:URLLoader = new URLLoader();
            loader3ds.dataFormat = URLLoaderDataFormat.BINARY;
            loader3ds.load(new URLRequest(url));
            loader3ds.addEventListener(Event.COMPLETE, on3dsLoad);
            
        }
        
        private function on3dsLoad(e:Event):void 
        {
            e.currentTarget.removeEventListener(e.type, on3dsLoad);
            directLoad ( (e.target as URLLoader).data );
        }
        
        
        private function directLoad(data:*):void {
            parseTerrain3DS(data);
        }
        
        private function prepareStellarTerrain3x3():void {
            var stellar:StellarTerrainGen = new StellarTerrainGen();
            var tilesAcross:int = _setup.numXTiles;
            if (tilesAcross < 128 ) tilesAcross  = 128;
            stellar.mapWidth = stellar.mapHeight = tilesAcross * 3;
            stellar.generate();    
        
            
            init3x3Terrain( stellar.getHeightBmpData()  );    
            
        }
        


        private function prepareStellarTerrain():void 
        {
            var stellar:StellarTerrainGen = new StellarTerrainGen();
            var tilesAcross:int = _setup.numXTiles;
            if (tilesAcross < 128 ) tilesAcross  = 128;
            stellar.mapWidth = tilesAcross;
            stellar.mapHeight = tilesAcross;
            stellar.generate();
            
            
            var worldModel:WorldModel =  _worldModel =  new WorldModel(0, 0, tilesAcross, tilesAcross); 

            
            var hm:HeightMapInfo =  HeightMapInfo.createFromBmpData(stellar.getHeightBmpData(false), 0, 0, _setup.bmpScaleHeight, _setup.bmpFromHeight);
            _heightMap = hm;
            _rootData = QuadCornerData.createRoot(0, 0, tilesAcross*256);
        
            initTerrain();
        }
        
        
        private function preparePerlinTerrain(for3x3:Boolean=false):void 
        {
            var tilesAcross:int = _setup.numXTiles;
            var worldModel:WorldModel =  _worldModel =  new WorldModel(0, 0, tilesAcross, tilesAcross); 
            var w:int = !for3x3 ? tilesAcross + 1  : tilesAcross *3 - 3;
     
            var bmpData:BitmapData = new BitmapData(w, w, false, 0);
            bmpData.perlinNoise(bmpData.width, bmpData.height, _setup.perlin_numOctaves, 
                                _setup.perlin_useRandomSeed ? _setup.perlin_randomSeed :  int( -int(int.MAX_VALUE * .5) + Math.random() * int.MAX_VALUE ),
                                _setup.perlin_stitch, _setup.perlin_useFractalNoise, 7, true);
                                
            
            if (!for3x3) {
                parseBmpDataTerrain(bmpData, tilesAcross);
                initTerrain()
            }
            else {
                init3x3Terrain(bmpData);
            }
        }
        
        private function prepareBmpDataTerrain(bitmapData:BitmapData, for3x3:Boolean=false):void 
        {
            var tilesAcross:int = _setup.numXTiles;
            var worldModel:WorldModel =  _worldModel =  new WorldModel(0, 0, tilesAcross, tilesAcross); 
            
            var w:int = !for3x3 ? tilesAcross + 1  : tilesAcross *3 - 3;
            var bmpData:BitmapData = new BitmapData(w, w, false, 0);
            var matrix:Matrix = new Matrix();
            
            matrix.scale(bmpData.width / bitmapData.width, bmpData.height / bitmapData.height);
            matrix.scale(1, -1);
            matrix.translate(1, bmpData.height);
            setGrayScale(bitmapData);
            bmpData.draw(bitmapData, matrix);
            bitmapData.dispose();
            
            if (!for3x3) {
                parseBmpDataTerrain(bmpData, tilesAcross);
                initTerrain();
            }
            else {
                init3x3Terrain(bitmapData);
            }
        }
        
        private function setGrayScale(bmpData:BitmapData):void {
            var matrix:Array = [0.3, 0.59, 0.11, 0, 0,

           0.3, 0.59, 0.11, 0, 0,

           0.3, 0.59, 0.11, 0, 0,

           0, 0, 0, 1, 0];
           var grayscaleFilter:ColorMatrixFilter = new ColorMatrixFilter(matrix);
        bmpData.applyFilter(bmpData, bmpData.rect, new Point(), grayscaleFilter);
        
        }
        
        private function parseBmpDataTerrain(bmpData:BitmapData, tilesAcross:int):void {
            
            

            var hm:HeightMapInfo =  HeightMapInfo.createFromBmpData(bmpData, 0, 0, _setup.bmpScaleHeight, _setup.bmpFromHeight);
            _heightMap = hm;
            _rootData = QuadCornerData.createRoot(0, 0, tilesAcross*256);

        }
    
        
        private function parseTerrain3DS(data:*):void {
            var i:int
            var parser:Parser3DS = new Parser3DS();
            parser.parse(data);
            
            // Setup terrain model
            var mesh:Mesh = (parser.objects[0] as Mesh);
            mesh.sorting = 1;
            mesh.calculateBounds();
            
    
            var tilesAcross:int = (mesh.boundMaxX - mesh.boundMinX) / 256;
            var worldModel:WorldModel =   _worldModel = new WorldModel(
            mesh.boundMinX, 
            mesh.boundMinY, tilesAcross, tilesAcross); 
            

            var hm:HeightMapInfo =  HeightMapInfo.createFromMesh(mesh);
            _heightMap = hm;
            _rootData = QuadCornerData.createRoot(0, 0, tilesAcross*256);
            
            initTerrain();

        }
        
        private function initTerrain():void {
            var terrain:TerrainQuadLODContainerNT = new TerrainQuadLODContainerNT();
            
            _currentTerrain = terrain;
            if (_setup.file_terrainTexture.data != null) {
                terrainTextureLoadedHandler();
            }
            if (_setup.file_waterTexture.data != null) {
                waterTextureLoadedHandler();
            }
            
            rootContainer.addChild(terrain);
            
            var rootSq:QuadSquare = _rootData.Square;
            rootSq.AddHeightMap(_rootData, _heightMap);
            rootSq.RecomputeErrorAndLighting(_rootData);
            _currentTerrain.setupMaterialsAndQuad(_worldModel, _rootData);
            rootContainer.addChild(terrain);
            
            updateWaterState();
        }
        
        private function init3x3Terrain(terrainBmpData:BitmapData):void {
            var tilesAcross:int = ((terrainBmpData.width / 3) + 1);
            var cellSize:int = tilesAcross * 256;
            _world = new OverworldTerrainContainer(cellSize);
            
                
            var worldModel:WorldModel =  _worldModel =  new WorldModel(0, 0, tilesAcross, tilesAcross); 
            
            var vec2:Vector.<Object3DContainer> = new Vector.<Object3DContainer>(9, true);// HuaTilingTerrainPiece
            var vec:Vector.<QuadCornerDataNeighbor> = QuadCornerDataNeighbor.create3x3WithBitmapData(terrainBmpData, _setup.bmpScaleHeight, _setup.bmpFromHeight );
            terrainBmpData.dispose();
            _terrainTileList = new Vector.<TerrainQuadLODContainerNT>(9, true);
            for (var i:int = 0; i < 9; i++) {
                var terrainPiece:TerrainQuadLODContainerNT = new TerrainQuadLODContainerNT();
                _terrainTileList[i] = terrainPiece;
                vec2[i] = terrainPiece;
                terrainPiece.autoUpdate = false;
                _rootData = vec[i];
                terrainPiece.setupMaterialsAndQuad(worldModel, vec[i]);
            }
            
            
            _world.setTileList(vec2); 
            rootContainer.addChild( _world);
            
            updateWaterState();
        }
        
        
        private function updateWaterState(e:Event=null):void 
        {
             var terrain:TerrainQuadLODContainerNT;
            var gotWater:Boolean = _setup.useWaterZClip;
            if (_currentTerrain != null) _currentTerrain.waterClipping = gotWater;
            else {
                _world.waterCulling = gotWater;
                for each( terrain in _terrainTileList) {
                    terrain.waterClipping = gotWater;
                }
            }
            if (gotWater) {
                ensureFloor();    
                var whole:int =  ((1 << _rootData.Level) << 1);
                whole *= _setup.comp_waterSizeXAmount.value;
                _floor.scaleX = whole;
                _floor.scaleY = whole;
                
                _floor.z =  _setup.comp_waterLevel.value;
                if (_currentTerrain != null) {
                    _currentTerrain.waterLevel = _floor.z;
                    
                }
                else {
                    _world.waterCullZ = _floor.z;
                    for each( terrain in _terrainTileList) {
                        terrain.waterLevel = _floor.z ;
                    }
                }
                
                
            }
            if (_floor) {
                _floor.visible = gotWater;
                
            }
            
        }
        
        private static const DUMMY_FLOOR_MATERIAL:FillMaterial = new FillMaterial(0x1133EE);
        private function ensureFloor():void {
            var verts:Vector.<Vertex> 
            if (_floor == null) {
                var half:int = (1 << _rootData.Level);
                _floor = new Floor(1, 1, 1, 1, false, false, false);
                
                _floor.x = _world != null ? 0 : _rootData.xorg + half;
                _floor.y = _world != null ? 0 : _rootData.zorg + half;
                if (_world == null) rootContainer.addChildAt(_floor, 0)
                else _world.floor = _floor;
                verts = _floor.faceList.vertices;
                for (var i:int = 0; i < verts.length; i++) {
                    verts[i].id = new Point(verts[i].u, verts[i].v);
                }
            }
            
            _floor.faceList.material = _lastFloorMaterial || DUMMY_FLOOR_MATERIAL;
            
            if (_lastFloorMaterial != null) {
                adjustWaterUVs();
            }
        
        }
        
        private function adjustWaterUVs():void 
        {
            var verts:Vector.<Vertex> 
            var i:int;
            var half:int = (1 << _rootData.Level);
            if (_floor!=null && _lastFloorMaterial.repeat) {  // assumed mipmapped resolution
                verts = _floor.faceList.vertices;
                var d:Number = _lastFloorMaterial.mipMapping != 0 ? _lastFloorMaterial.resolution * _lastFloorMaterial._texture.width : 1;
                if (_lastFloorMaterial._mipMapping != 0) d = half * 2 *_setup.comp_waterSizeXAmount.value/ d;
                
                /// half*2*_setup.comp_waterSizeXAmount.value
                for (i=0; i < verts.length; i++) {
                    var v:Vertex = verts[i];
                    var uvPt:Point = v.id as Point;
                    v.u = uvPt.x * d;
                    v.v = uvPt.y * d;
                }
            }        
        }
        
        
        
        
        private function terrainTextureLoadedHandler(e:Event=null):void 
        {
            var loader:Loader = new Loader();
            loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaderWorldTextueComplete);
            loader.loadBytes(_setup.file_terrainTexture.data);
        }
        
        private function waterTextureLoadedHandler(e:Event=null):void 
        {
            var loader:Loader = new Loader();
            loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaderWaterTextureComplete);
            loader.loadBytes(_setup.file_waterTexture.data);
        }
        
        private function onLoaderWaterTextureComplete(e:Event):void 
        {
            (e.currentTarget as IEventDispatcher).removeEventListener(e.type, onLoaderWaterTextureComplete);
            
            if (_lastFloorMaterial != null) {
                _lastFloorMaterial.disposeMipMaps();
                _lastFloorMaterial._texture.dispose();
            }
                
            var bmpData:BitmapData = (e.currentTarget.content as Bitmap).bitmapData;
            var gotMipMapping:Boolean = _setup.waterMipMapResolution != 0 && _worldModel.isBase2(bmpData.width) && _worldModel.isBase2(bmpData.height) && bmpData.width == bmpData.height;
            var worldSize:int = ((1 << _rootData.Level) << 1);
            var needRepeat:Boolean = gotMipMapping && _setup.waterMipMapResolution * bmpData.width < worldSize;
            _lastFloorMaterial = new TextureMaterial(bmpData, needRepeat, true, gotMipMapping ? 2 : 0, _setup.waterMipMapResolution * _setup.waterMipMapResolutionScale);
            ensureFloor();
            
            _setup.comp_updateWaterRes.visible = true;
            _setup.comp_mipmapWaterStretch.visible = true;
        }
        
        private function onLoaderWorldTextueComplete(e:Event):void 
        {
            (e.currentTarget as IEventDispatcher).removeEventListener(e.type, onLoaderWorldTextueComplete);
            if (_lastWorldMaterial != null) {
                
                _lastWorldMaterial.disposeMipMaps();
                _lastWorldMaterial._texture.dispose();
            }
            
            var bmpData:BitmapData = (e.currentTarget.content as Bitmap).bitmapData;
            var gotMipMapping:Boolean = _setup.terrainMipMapResolution != 0 && _worldModel.isBase2(bmpData.width) && _worldModel.isBase2(bmpData.height) && bmpData.width == bmpData.height;
        
            if (gotMipMapping) {
                _worldModel.worldTextureDistance = _setup.terrainMipMapResolution * bmpData.width;
            }
            var worldSize:int = ((1 << _rootData.Level) << 1);
            var needRepeat:Boolean = gotMipMapping && _setup.terrainMipMapResolution *  bmpData.width < worldSize;
    
            _worldModel.setWorldMaterialAtIndex(0, _lastWorldMaterial = new TextureMaterial(bmpData,needRepeat , true, gotMipMapping ? 2 : 0, _setup.terrainMipMapResolution*_setup.terrainMipMapResolutionScale) );
            if (_currentTerrain != null) {
                _currentTerrain.setupMaterials(_worldModel);
            }
            else {
                for each(var terrain:TerrainQuadLODContainerNT in _terrainTileList) {
                    terrain.setupMaterials(_worldModel);
                }
            }
            
            _setup.comp_updateTerrainRes.visible = true;
            _setup.comp_mipmapTerrainStretch.visible = true;
        }
        
        
        private var _isRendering:Boolean = false;
        private function stopRendering():void {
            _isRendering = false;
            stage.removeEventListener(Event.ENTER_FRAME, onEnterFrame);
        }
        
        private function startRendering():void {
            if (_isRendering) return;
            _isRendering = true;
            stage.addEventListener(Event.ENTER_FRAME, onEnterFrame);
        }
        
        
        private function onEnterFrame(e:Event):void {
        
            // IMPORTANT! Always reset required static buffer counters before any EllipsoidCollider collision/destination query! 
            //One should create an extended EllipsoidCollider class for this!
            QuadCornerData.BI = 0;  
            QuadRenderData.BI = 0; 
            controller.update();  

            if (_world != null) {
                _world.update(camera);
            }
                    
            // not needed for this case. But camera.transformId can act as a useful timestamp.
            //camera.transformId++; 
            camera.render();
        }
        
        
        private function onResize(e:Event = null):void {
            // Width and height of view
            camera.view.width = stage.stageWidth;
            camera.view.height = stage.stageHeight;
        }
        
        
        
    }
}



 interface ICuller 
    {
        /**
         * @param    culling A value higher than zero is reccomended for testing bounds against required frustum planes
         * @return    Resultant culling bitmask value. ( 1=near, 2=far, 4=left, 8=right, 16=top, 32=bottom) where bound intersection occurs.
         *             A value of -1 bounds completely outside camera view. A value of zero indicates bounds completely within camera view.
         */
        function cullingInFrustum(culling:int, boundMinX:Number, boundMinY:Number, boundMinZ:Number, boundMaxX:Number, boundMaxY:Number, boundMaxZ:Number):int;
    }
    
    interface INeighbourQuad 
    {

        function getQuad():QuadCornerDataNeighbor;
    }
    
    class QuadI 
    {
        
        public var cd:QuadCornerData;
        public var i:int = -1;
        public var rd:QuadRenderData;
        public var cullingResults:Vector.<int> = new Vector.<int>(4, true);
        public var orderedIndices:Vector.<int> = new Vector.<int>(4, true);
        
    }
    

    import flash.display.DisplayObject;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.events.TextEvent;
    import flash.geom.Matrix3D;

    /**
     * ...
     * @author Glenn Ko
     */
     class BaseUI extends Sprite
    {
        
        public function BaseUI() 
        {
        
            addEventListener(MouseEvent.CLICK, stopPropagation, false, 0, true);
            addEventListener(MouseEvent.MOUSE_DOWN, stopPropagation, false, 0, true);
            addEventListener(KeyboardEvent.KEY_DOWN, stopPropagation, false , 0, true);
            focusRect = false;
        }
        
        private function stopPropagation(e:Event):void 
        {
            e.stopPropagation();
        }
        
        protected function setAsWindow():void {
            addEventListener(MouseEvent.MOUSE_DOWN, setFocusHigher, false, 1, true);
            x = 120;
            y = 65;
        }
        
        private function setFocusHigher(e:MouseEvent):void 
        {
            if (parent) parent.addChild(this);
        }
        
        protected function setupHref(targ:DisplayObject):void {
            targ.addEventListener(MouseEvent.CLICK, hrefHandler, false, 0 , true);
        }
        protected function setupClosable(targ:DisplayObject):void {
            targ.addEventListener(MouseEvent.CLICK, closeHandler, false, 0 , true);
        }
        
        protected function closeHandler(e:MouseEvent):void 
        {
            visible = false;
            dispatchEvent( new Event(Event.CLOSE) );
        }
        
        private function hrefHandler(e:Event):void {
            dispatchEvent( new TextEvent(TextEvent.LINK, true, false, e.currentTarget.name) );
        }
        
    }
    
    
    class QuadCornerData
    {
    
        public var Parent:QuadCornerData;
        public var Square:QuadSquare; //    Square;
        public    var ChildIndex:int;
        public var    Level:int
        
        public    var xorg:int;
        public var zorg:int;
        
        public var Verts:Vector.<int> = new Vector.<int>(4, true);    //se,sw,nw,ne
        // ne, nw, sw, se [4]
        
        //public var vertexList:Vector.<Vertex>;
    
        public static var BUFFER:Vector.<QuadCornerData> = new Vector.<QuadCornerData>();
        public static var BI:int = 0;
        public static var BLEN:int = 0;
    
        public static function create():QuadCornerData {
            //return new QuadCornerData();
            var result:QuadCornerData;
            if (BI < BLEN) {
                result = BUFFER[BI];
            }
            else {
                result = new QuadCornerData();
                BUFFER[BLEN++] = result ;
            }

            
            BI++;
            return result;
        }
        
        public static function setFixedBufferSize(size:int):void {
            //return;
            BUFFER.length = size;
            BUFFER.fixed = true;
            BLEN = size;
        }

        
        public function clone():QuadCornerData {
        //    return this;
            var result:QuadCornerData = new QuadCornerData();
            result.Parent = Parent;
            result.Square = Square;
            result.xorg = xorg;
            result.zorg = zorg;
            result.Level = Level;
            result.ChildIndex = ChildIndex;
            result.Verts[0] = Verts[0];
            result.Verts[1] = Verts[1];
            result.Verts[2] = Verts[2];
            result.Verts[3] = Verts[3];
            return result;
        }
        
    
        
        

        public static function createRoot(x:int, y:int, size:int, neighborTilable:Boolean=false):QuadCornerData {
            var quadRoot:QuadCornerData = neighborTilable ? new QuadCornerDataNeighbor() : new QuadCornerData();
            quadRoot.xorg = x;
            quadRoot.zorg = y;
            if (!isBase2(size)) throw new Error("Size isn't base 2!" + size);
            size >>= 1;
            quadRoot.Level = Math.log(Number(size) + .01) * Math.LOG2E;
            
            var sq:QuadSquare = new QuadSquare(quadRoot);
            sq.Static = true;
            sq.Dirty  = true;
            return quadRoot;
        }
        
                
        private static function isBase2(val:int):Boolean {
            return Math.pow(2, int(Math.log(val + .01) * Math.LOG2E)) == val;
        }
        

        
    }
    

    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();
            }
        }
        
    }



    import flash.geom.Point;
    import flash.utils.IDataOutput;
    import alternativ7.engine3d.core.Vertex;
    import alternativ7.engine3d.objects.Mesh;
    import flash.display.BitmapData;
    import alternativ7.engine3d.alternativa3d;
    import flash.utils.ByteArray;
    import flash.utils.IDataInput;
    import flash.utils.IExternalizable;
    use namespace alternativa3d;
    
    /**
     * @author Thatcher Ulrich (tu@tulrich.com)
     * @author Glenn Ko
     */
    //public 
    class HeightMapInfo implements IExternalizable
    {
        
        public function HeightMapInfo() 
        {
            
        }
        
        //int16*    
        public var Data:Vector.<int>;
        public var    XOrigin:int;
        public var ZOrigin:int;
        public var    XSize:int, ZSize:int;
        public var    RowWidth:int;
        public var    Scale:int;
        
        

        public function    Sample( x:int,  z:int):int 

        // Returns the height (y-value) of a point in this heightmap.  The given (x,z) are in
        // world coordinates.  Heights outside this heightmap are considered to be 0.  Heights
        // between sample points are bilinearly interpolated from surrounding points.
        // xxx deal with edges: either force to 0 or over-size the query region....
        {
            // Break coordinates into grid-relative coords (ix,iz) and remainder (rx,rz).

            var    ix:int = (x - XOrigin) >> Scale;
            var    iz:int = (z - ZOrigin) >> Scale;
            
            
            var    mask:int = (1 << Scale) - 1;
            var    rx:int = (x - XOrigin) & mask;
            var    rz:int = (z - ZOrigin) & mask;
        
            

            if (ix < 0 || ix > XSize-1 || iz < 0 || iz > ZSize-1) {
                throw new Error("OUTSIDE!" + ix + "," + iz + ", " + XSize);
                if (ix < 0) ix  = 0;
                if (iz < 0 ) iz = 0;
                if (ix >= XSize ) ix = XSize - 1;
                if (iz >= ZSize) iz = ZSize -1;
                //return 0;    // Outside the grid.
            }
            
            

            var    fx:Number = rx / (mask + 1);
            var    fz:Number = rz / (mask + 1);

            var xSizeAdd:int = ix < XSize-1 ? 1 : 0;
            var zSizeAdd:int = iz < ZSize - 1 ? 1 : 0;  // clamp addition
            var    s00:Number = Data[ix + iz * RowWidth];
            var    s01:Number = Data[(ix+ xSizeAdd ) + iz * RowWidth];
            var    s10:Number = Data[ix + (iz+zSizeAdd) * RowWidth];
            var    s11:Number = Data[(ix+ xSizeAdd) + (iz+zSizeAdd) * RowWidth];

            return (s00 * (1-fx) + s01 * fx) * (1-fz) +
                (s10 * (1-fx) + s11 * fx) * fz;
        }
        
        public function clone():HeightMapInfo {
            var result:HeightMapInfo = new HeightMapInfo();
            
            result.XOrigin= XOrigin;
            result.ZOrigin= ZOrigin;
            result.XSize= XSize;
            result.ZSize= ZSize;
            result.RowWidth= RowWidth;
            result.Scale = Scale;
            result.Data = Data;
            return result;
        }
        
        public function flatten(val:int = 0):void {
            var len:int = Data.length;
            for (var i:int = 0; i < len; i++) {
                Data[i] = val;
            }
        }
        
        public function getHighestLowestBounds():Vector.<Number> {
            var vec:Vector.<Number>  = new Vector.<Number>(2, true);
            var min:Number = Number.MAX_VALUE;
            var max:Number = -Number.MAX_VALUE;
            var i:int = Data.length;
            var h:Number;
            while (--i > -1) {
                h = Data[i];
                if (h < min) min = h;
                if (h > max) max = h;
            }
            vec[0] = min;
            vec[1] = max;
            return vec;
        }

        public function setFromMesh(mesh:Mesh, heightScale:Number=1, heightOffset:Number=0):void {
            mesh.calculateBounds();
            Scale = 8;
            
            XOrigin = mesh.boundMinX;
            ZOrigin = mesh.boundMinY;
            var vertsX:int = (int(mesh.boundMaxX - mesh.boundMinX) >> 8) + 1;
            var vertsY:int = (int(mesh.boundMaxY - mesh.boundMinY) >> 8) + 1;
            
            RowWidth = vertsX;
            XSize = RowWidth;
            ZSize = vertsY;
            
            var data:Vector.<int> =  new Vector.<int>( (vertsX+1) * (vertsY+1), true);
            for (var v:Vertex = mesh.vertexList; v != null; v = v.next) {
                data[(int(v.y-mesh.boundMinY) >> 8)*vertsX  +   (int(v.x-mesh.boundMinX) >> 8)] = v.z * heightScale + heightOffset;
            }
            Data = data;
        }
        
        public function setFromBmpData(bmpData:BitmapData, heightMult:Number, heightMin:int=0):void 
        {
            Scale = 8;
            var bWidth:int; var bHeight:int;
            var vertsX:int = bWidth = bmpData.width;
            var vertsY:int = bHeight = bmpData.height;
            if ( (vertsX & 1) != 1) {
                throw new Error("Must be odd number of vertices along x dir!"); 
                //vertsX += 1;
            }
            if ( (vertsY & 1) != 1) {
                throw new Error("Must be odd number of vertices along y dir!"); 
                //vertsY += 1;
            }
            
            RowWidth = vertsX;
            XSize = RowWidth;
            ZSize = vertsY;
            
            var data:Vector.<int> =  new Vector.<int>( (vertsX+1) * (vertsY+1), true);
            var by:int = vertsY;
            var bx:int = vertsX;
            
            for (var y:int = 0; y <  by; y++ ) {
                for (var x:int = 0; x < bx; x++) {
                    var xer:int = x < bWidth ? x : bWidth - 1;
                    var yer:int = y < bHeight ? y : bHeight - 1;
                    data[y * by  +  x] = heightMin + (bmpData.getPixel(xer,yer) & 0x0000FF) * heightMult;
                }
            }
            Data = data;
        }
        
        
        
        
        public static function createFromMesh(mesh:Mesh, heightScale:Number=1, heightOffset:Number=0):HeightMapInfo {
            var result:HeightMapInfo = new HeightMapInfo();
            result.setFromMesh(mesh, heightScale, heightOffset);
            return result;
        }
        
        
        public static function createFromBmpData(bmpData:BitmapData, x:int = 0, y:int=0, heightMult:Number=16, heightMin:Number=0):HeightMapInfo {
            var result:HeightMapInfo = new HeightMapInfo();
            result.XOrigin = x;
            result.ZOrigin = y;
            result.setFromBmpData(bmpData, heightMult, heightMin);
            return result;
        }
        
        /* INTERFACE flash.utils.IExternalizable */
        
        public function readExternal(byte:IDataInput):void 
        {
            XOrigin = byte.readInt();
            ZOrigin = byte.readInt();
            XSize = byte.readInt();
            ZSize = byte.readInt();
            RowWidth = byte.readInt();
            Scale = byte.readInt();
            Data = byte.readObject();
        }
        
        public function writeExternal(byte:IDataOutput):void 
        {
            byte.writeInt(XOrigin);
            byte.writeInt(ZOrigin);
            byte.writeInt(XSize);
            byte.writeInt(ZSize);
            byte.writeInt(RowWidth);
            byte.writeInt(Scale);
            byte.writeObject(Data);
        }
        
    }
        



    import alternativ7.engine3d.core.RayIntersectionData;
    import alternativ7.engine3d.core.Object3DContainer;
    import alternativ7.engine3d.containers.ConflictContainer;
    import alternativ7.engine3d.core.Face;
    import alternativ7.engine3d.core.Wrapper;
    import alternativ7.engine3d.materials.FillMaterial;
    import alternativ7.engine3d.materials.Material;
    import alternativ7.engine3d.materials.TextureMaterial;
    import alternativ7.engine3d.objects.Mesh;
    import alternativ7.engine3d.core.Camera3D;
    import alternativ7.engine3d.core.Canvas;
    import alternativ7.engine3d.core.Debug;
    import alternativ7.engine3d.core.VG;
    import alternativ7.engine3d.core.Object3D;
    import alternativ7.engine3d.core.Vertex;

    
    import flash.display.BitmapData;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    import flash.geom.Vector3D;
    import flash.utils.Dictionary;

    
    import alternativ7.engine3d.alternativa3d;
    use namespace alternativa3d;
    
    /**
     * A quad-tree based terrain lod implementation for large environments. This container handles conflict geometry as well so long as you 
     * you directly add children to it. It can work under any IVGDelegater-based container (eg. ParentKDContainer) that can
     * also delegate leaf geometry to this container itself, allowing for a mix of differnent object types besides terrain.
     * 
     * There are 2 conditional compile-flags which you need to take note of (under #if/#endif) in order to comment off/on certain sections depending
     * on your needs.
     * - gotTileSet:  Whether a tileset is available from which individual tile uvs/rotation/tile materials are used per tile. (If no tileset is used, the class is duplicated and it's classname suffixed with 'NT' (ie. no tiling)).
     * - painterMode: If got tileSet, whether a TextureDefinition class ( used in Painter mode when dev/live tileset painting) is available. 
     *                   Else, it's assumed running in pure runtime (viewer/game) mode with baked compressed tile data information.
     * 
     * 
     *  Main method to begin: setupMaterialsAndQuad(...)
     */
     class TerrainQuadLODContainerNT extends ConflictContainer implements ICuller, INeighbourQuad {

        alternativa3d var rootQuad:QuadCornerData;
        public function setupQuad(qd:QuadCornerData):void {
            rootQuad = qd;
            var full:int = ((1 << qd.Level) << 1);
            boundMinX = qd.xorg;
            boundMinY = qd.zorg;
            boundMinZ = qd.Square.MinY - 99999999;
            boundMaxX = qd.xorg + full;
            boundMaxY = qd.zorg + full;
            boundMaxZ = qd.Square.MaxY + 99999999;
            
        
        //    calculateBounds();
            
        }
        
         // Keep this setting to avoid artifacts (i think clipping=0/1 is pointless here, but i still leave this as an option.
        public var clipping:int = 2; 
        
        public var detail:int = 30;                 // LOD threshold setting (higher values indicate more detail)
        public var autoUpdate:Boolean = true;   // Whether to automatically update LOD on every draw().
        
        // Calculated sphere values in container space during collectPlanes() for EllipsoidCollider
        private var _center:Vector3D;
        private var _a:Vector3D;
        private var _b:Vector3D;
        private var _c:Vector3D;
        private var _d:Vector3D;
        private var _sphere:Vector3D;
        
        // Water clip/cull settings
        public var waterClipping:Boolean = false;
        public var waterLevel:Number = 0; 
        alternativa3d var _waterPlane:Vector3D = new Vector3D();  // water clip plane in camera space
        alternativa3d var _waterOffset:Number; // water clip offset in camera space
    
        // Precalculated frustum in container space
        alternativa3d var nearX:Number; alternativa3d var nearXHigh:Boolean;
        alternativa3d var nearY:Number; alternativa3d var nearYHigh:Boolean;
        alternativa3d var nearZ:Number; alternativa3d var nearZHigh:Boolean;
        alternativa3d var nearOffset:Number;
        alternativa3d var farX:Number; alternativa3d var farXHigh:Boolean;
        alternativa3d var farY:Number; alternativa3d var farYHigh:Boolean;
        alternativa3d var farZ:Number; alternativa3d var farZHigh:Boolean;
        alternativa3d var farOffset:Number;
        alternativa3d var leftX:Number; alternativa3d var leftXHigh:Boolean;
        alternativa3d var leftY:Number; alternativa3d var leftYHigh:Boolean;
        alternativa3d var leftZ:Number; alternativa3d var leftZHigh:Boolean;
        alternativa3d var leftOffset:Number;
        alternativa3d var rightX:Number; alternativa3d var rightXHigh:Boolean;
        alternativa3d var rightY:Number; alternativa3d var rightYHigh:Boolean;
        alternativa3d var rightZ:Number; alternativa3d var rightZHigh:Boolean;
        alternativa3d var rightOffset:Number;
        alternativa3d var topX:Number;  alternativa3d var topXHigh:Boolean;
        alternativa3d var topY:Number;  alternativa3d var topYHigh:Boolean;
        alternativa3d var topZ:Number;  alternativa3d var topZHigh:Boolean;
        alternativa3d var topOffset:Number;
        alternativa3d var bottomX:Number; alternativa3d var bottomXHigh:Boolean;
        alternativa3d var bottomY:Number; alternativa3d var bottomYHigh:Boolean;
        alternativa3d var bottomZ:Number; alternativa3d var bottomZHigh:Boolean;
        alternativa3d var bottomOffset:Number;
        
        // Painter mode (to be injected manually)
        /* #if painterMode
        alternativa3d var tileDefMap:Vector.<TextureDefinition>;
         #endif */
        
        // Game/Viewer mode  (Baked Compressed uint values) and direct link to TileSet
        /* #if (!painterMode && gotTileSet)
        alternativa3d var tileSet:Vector.<TextureMaterial>; 
        alternativa3d var tileMap:Vector.<uint>; 
        alternativa3d var tileUVTransforms:Vector.<uint>; 
        #endif */
        
        
        
        
        // Expanded out values from worldModel to avoid dot pointer access. 
        private var _worldTexture:TextureMaterial;
    
        private var _worldTextureMinX:int;
        private var _worldTextureMinY:int;
        private var _invWorldTxDist:Number;
        alternativa3d var worldMinX:int;
        alternativa3d var worldMinY:int;
        alternativa3d var worldMats:Vector.<TextureMaterial>; // lookup table for world materials
        alternativa3d var xWorldShift:int;   
        alternativa3d var worldShift:int;
        
        /*#if gotTileSet
        alternativa3d var tileShift:int;
         #endif */
        
        /**
         *  Main key setup method most pple would use
         * @param    worldModel
         * @param    quad
         */
        public function setupMaterialsAndQuad(worldModel:WorldModel, quad:QuadCornerData):void {
            setupMaterials(worldModel);
            setupQuad(quad);
        }
        
    
        public function setupMaterials(worldModel:WorldModel):void {
            worldMinX = worldModel.minX;
            worldMinY = worldModel.minY;
            xWorldShift = worldModel.xWorldShift; 
            worldShift = worldModel.worldShift;
            worldMats = worldModel.worldMats;
            _invWorldTxDist = 1 / worldModel.worldTextureDistance;
            
            /*#if gotTileSet
            tileShift = worldModel.tilesShift;    
             #endif
            
            // #if (gotTileSet && !painterMode)
            // tileSet = worldModel.tileSet;
            // tileMap = worldModel.tileMap;
            // #endif */
        }
        
        
    
        private const _cameraPos:Vector3D = new Vector3D();
        
        
        // Collision detection
        override alternativa3d function collectPlanes(center:Vector3D, a:Vector3D, b:Vector3D, c:Vector3D, d:Vector3D, collector:Vector.<Face>, excludedObjects:Dictionary = null):void {
            
            if (rootQuad == null) {  // this can be omitted if dependency is ensured
                super.collectPlanes(center, a, b, c, d, collector, excludedObjects);
                return;
            }
            _d = d;
            _c = c;
            _b = b;
            _a = a;

            _center = center;
            collectPlanesQuad(rootQuad, calculateSphere(center, a, b, c, d), collector);
        }
        

        private const ORIGIN:Vector3D = new Vector3D();
        private const DIRECTION:Vector3D = new Vector3D();

        override public function intersectRay(origin:Vector3D, direction:Vector3D, excludedObjects:Dictionary = null, camera:Camera3D = null):RayIntersectionData {
            if (excludedObjects != null && excludedObjects[this]) return null;
            
            if (!boundIntersectRay(origin, direction, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ)) return null;
            
        
            var result:RayIntersectionData;
            var minT:Number = 1.79e+308;  // Number.MAX_VALUE
            var data:RayIntersectionData;
            
            if (rootQuad != null && boundIntersectRay(origin, direction, rootQuad.xorg, rootQuad.zorg, rootQuad.Square.MinY, rootQuad.xorg + ((1 << rootQuad.Level) << 1), rootQuad.zorg + ((1 << rootQuad.Level) << 1), rootQuad.Square.MaxY)) {
                data = intersectRayQuad(rootQuad, origin, direction);
                if (data != null && data.time < minT) {
                    minT = data.time;
                    result = data;
                }
            }
            
            const origin:Vector3D = ORIGIN;
            const direction:Vector3D = DIRECTION;
            
            for (var child:Object3D = childrenList; child != null; child = child.next) {
                child.composeMatrix();
                child.invertMatrix();
                
                origin.x = child.ma*origin.x + child.mb*origin.y + child.mc*origin.z + child.md;
                origin.y = child.me*origin.x + child.mf*origin.y + child.mg*origin.z + child.mh;
                origin.z = child.mi*origin.x + child.mj*origin.y + child.mk*origin.z + child.ml;
                direction.x = child.ma*direction.x + child.mb*direction.y + child.mc*direction.z;
                direction.y = child.me*direction.x + child.mf*direction.y + child.mg*direction.z;
                direction.z = child.mi*direction.x + child.mj*direction.y + child.mk*direction.z;
                data = child.intersectRay(origin, direction, excludedObjects, camera);
                if (data != null && data.time < minT) {
                    minT = data.time;
                    result = data;
                }
            }

            return result;
        }
        

        
    
        private static const QD_STACK:Vector.<QuadCornerData> = new Vector.<QuadCornerData>();
        
        protected function intersectRayQuad(cd:QuadCornerData, origin:Vector3D, direction:Vector3D):RayIntersectionData {
            
            var sq:QuadSquare;

            const stackStart:int =  QuadCornerData.BI;
            const rdStart:int = QuadRenderData.BI;
            
            var buffer:Vector.<QuadCornerData> =  QD_STACK;
            sq = cd.Square;
            var childList:Vector.<QuadSquare> = sq.Child;
            var child:QuadSquare;
            var half:int = 1 << cd.Level;
            var full:int = half << 1;
            var newCD:QuadCornerData;
            var rd:QuadRenderData;
            
            var faceList:Face;
            var f:Face;
            var lastFace:Face;
            
            var bi:int = 1;
            buffer[0] = cd;
                
            while (bi > 0) {
                cd = buffer[--bi];
    
                sq = cd.Square;
                childList = sq.Child;
                half =  1 << cd.Level;
                full = half << 1;
                
                if (cd.Level <= 8) {
                    rd =  QuadRenderData.create();
                    rd.setupVertices(cd); // could be inlined
                    var rdVertList:Vector.<Vertex> = rd.vertexList;
                    child = childList[0];
                    if ( boundIntersectRay(origin, direction,  cd.xorg + half, cd.zorg, child.MinY, cd.xorg + full, cd.zorg + half, child.MaxY)) {    
                        f =  tri(rdVertList, 0, 2, 1);
                        f.next = lastFace = tri(rdVertList, 0, 3, 2);
                        lastFace.next = faceList;
                        faceList = f;
                    }
                    child = childList[1];
                    if (boundIntersectRay(origin, direction, cd.xorg, cd.zorg, child.MinY, cd.xorg + half, cd.zorg + half, child.MaxY) ) {
                        f =  tri(rdVertList,0, 4, 3);
                        f.next = lastFace = tri(rdVertList, 0, 5, 4);
                        lastFace.next = faceList;
                        faceList = f;
                    }
                    child = childList[2];
                    if (boundIntersectRay(origin, direction, cd.xorg, cd.zorg + half, child.MinY, cd.xorg + half, cd.zorg + full, child.MaxY)) {
                        f =  tri(rdVertList, 0, 6, 5);
                        f.next = lastFace = tri(rdVertList, 0, 7, 6);
                        lastFace.next = faceList;
                        faceList = f;
        
                    }
                    child = childList[3];
                        if (boundIntersectRay(origin, direction, cd.xorg + half, cd.zorg + half, child.MinY, cd.xorg + full, cd.zorg + full, child.MaxY)) {
                        f =  tri(rdVertList, 0, 8, 7);
                        f.next = lastFace = tri(rdVertList, 0, 1, 8);
                        lastFace.next = faceList;
                        faceList = f;
                    }
                    continue;
                }
                
                
                child = childList[0];
                if ( boundIntersectRay(origin, direction,  cd.xorg + half, cd.zorg, child.MinY, cd.xorg + full, cd.zorg + half, child.MaxY)) {    
                    
                    sq.SetupCornerData( newCD = QuadCornerData.create(), cd, 0);
                    buffer[bi++] = newCD;
                }
                child = childList[1];
                if (boundIntersectRay(origin, direction, cd.xorg, cd.zorg, child.MinY, cd.xorg + half, cd.zorg + half, child.MaxY) ) {
                    sq.SetupCornerData( newCD = QuadCornerData.create(), cd, 1);
                    buffer[bi++] = newCD;
                }
                child = childList[2];
                if (boundIntersectRay(origin, direction, cd.xorg, cd.zorg + half, child.MinY, cd.xorg + half, cd.zorg + full, child.MaxY)) {
                        
                    sq.SetupCornerData( newCD = QuadCornerData.create(), cd, 2);
                    buffer[bi++] = newCD;
                }
                child = childList[3];
                    if (boundIntersectRay(origin, direction, cd.xorg + half, cd.zorg + half, child.MinY, cd.xorg + full, cd.zorg + full, child.MaxY)) {
            
                    sq.SetupCornerData( newCD = QuadCornerData.create(), cd, 3);
                    buffer[bi++] = newCD;
                }
            }

        
            QuadCornerData.BI = stackStart;
            QuadRenderData.BI = rdStart;
            
            if (faceList == null) return null;
            
            var minT:Number = 1e+22;
            var point:Vector3D = null;
            var ox:Number = origin.x;
            var oy:Number = origin.y;
            var oz:Number = origin.z;
            var dx:Number = direction.x;
            var dy:Number = direction.y;
            var dz:Number = direction.z;
            
            for (f = faceList; f != null; f = f.next) {
                var wrapper:Wrapper = f.wrapper;
                var a:Vertex = wrapper.vertex;
                var b:Vertex = wrapper.next.vertex;
                var c:Vertex = wrapper.next.next.vertex;
                var abx:Number = b.x - a.x;
                var aby:Number = b.y - a.y;
                var abz:Number = b.z - a.z;
                var acx:Number = c.x - a.x;
                var acy:Number = c.y - a.y;
                var acz:Number = c.z - a.z;
                var normalX:Number = acz*aby - acy*abz;
                var normalY:Number = acx*abz - acz*abx;
                var normalZ:Number = acy * abx - acx * aby;
                var dot:Number = dx * normalX + dy * normalY + dz * normalZ;
                f.offset = a.x*normalX + a.y*normalY + a.z*normalZ;
                if (dot < 0) {
                    var offset:Number = ox*normalX + oy*normalY + oz*normalZ - f.offset;
                    if (offset > 0) {
                        var time:Number = -offset/dot;
                        if (point == null || time < minT) {
                            var cx:Number = ox + dx*time;
                            var cy:Number = oy + dy*time;
                            var cz:Number = oz + dz*time;
                            for (wrapper = f.wrapper; wrapper != null; wrapper = wrapper.next) {
                                a = wrapper.vertex;
                                b = (wrapper.next != null) ? wrapper.next.vertex : f.wrapper.vertex;
                                abx = b.x - a.x;
                                aby = b.y - a.y;
                                abz = b.z - a.z;
                                acx = cx - a.x;
                                acy = cy - a.y;
                                acz = cz - a.z;
                                if ((acz * aby - acy * abz) * normalX + (acx * abz - acz * abx) * normalY + (acy * abx - acx * aby) * normalZ < 0) break;
                            }
                        }
                        if (wrapper == null) {
                            var data:RayIntersectionData = new RayIntersectionData();
                            data.object = this;
                            data.face = f;
                            data.point = new Vector3D(cx, cy, cz);
                            data.time = time;
                            
                            lastFace.next = triCollector;
                            triCollector = faceList;
                            return data;
                        }
                    }
                }
            }
            
            lastFace.next = triCollector;
            triCollector = faceList;
            return null;
        }
        
        
        private function collectPlanesQuad(cd:QuadCornerData, sphere:Vector3D, collector:Vector.<Face>):void {
            
            var sq:QuadSquare;

            
            const stackStart:int =  QuadCornerData.BI;
            const rdStart:int = QuadRenderData.BI;
            
            var buffer:Vector.<QuadCornerData> =  QD_STACK;
            sq = cd.Square;
            var childList:Vector.<QuadSquare> = sq.Child;
            var child:QuadSquare;
            var half:int = 1 << cd.Level;
            var full:int = half << 1;
            var newCD:QuadCornerData;
            var rd:QuadRenderData;
            
            var faceList:Face;
            var f:Face;
            var lastFace:Face;
            
            var bi:int = 1;
            buffer[0] = cd;
            
                
            while (bi > 0) {
                cd = buffer[--bi];
    
                sq = cd.Square;
                childList = sq.Child;
                half =  1 << cd.Level;
                full = half << 1;
                
                if (cd.Level <= 8) {
                    rd =  QuadRenderData.create();
                    rd.setupVerticesForCollision(cd, this); // could be inlined
                    var rdVertList:Vector.<Vertex> = rd.vertexList;
                    child = childList[0];
                    if ( boundIntersectSphere(sphere, cd.xorg + half, cd.zorg, child.MinY, cd.xorg + full, cd.zorg + half, child.MaxY)) {    
                        f =  tri(rdVertList, 0, 2, 1);
                        collector.push(f);
                        f.next = lastFace = tri(rdVertList, 0, 3, 2);
                        
                        collector.push(lastFace);
                        lastFace.next = faceList;
                        faceList = f;
                    }
                    child = childList[1];
                    if (boundIntersectSphere(sphere, cd.xorg, cd.zorg, child.MinY, cd.xorg + half, cd.zorg + half, child.MaxY) ) {
                        f =  tri(rdVertList, 0, 4, 3);
                        collector.push(f);
                        f.next = lastFace = tri(rdVertList, 0, 5, 4);
                        collector.push(lastFace)
                        lastFace.next = faceList;
                        faceList = f;
                    }
                    child = childList[2];
                    if (boundIntersectSphere(sphere, cd.xorg, cd.zorg + half, child.MinY, cd.xorg + half, cd.zorg + full, child.MaxY)) {
                        f =  tri(rdVertList, 0, 6, 5);
                        
                        collector.push(f);
                        f.next = lastFace = tri(rdVertList, 0, 7, 6);
                        collector.push(lastFace)
                        lastFace.next = faceList;
                        faceList = f;
        
                    }
                    child = childList[3];
                        if (boundIntersectSphere(sphere, cd.xorg + half, cd.zorg + half, child.MinY, cd.xorg + full, cd.zorg + full, child.MaxY)) {
                        f =  tri(rdVertList, 0, 8, 7);
                        collector.push(f);
                        f.next = lastFace = tri(rdVertList, 0, 1, 8);
                        collector.push(lastFace)
                        lastFace.next = faceList;
                        faceList = f;
                    }
                    continue;
                }
                
                
                child = childList[0];
                if ( boundIntersectSphere(sphere,  cd.xorg + half, cd.zorg, child.MinY, cd.xorg + full, cd.zorg + half, child.MaxY)) {    
                    
                    sq.SetupCornerData( newCD = QuadCornerData.create(), cd, 0);
                    buffer[bi++] = newCD;
                }
                child = childList[1];
                    if (boundIntersectSphere(sphere, cd.xorg, cd.zorg, child.MinY, cd.xorg + half, cd.zorg + half, child.MaxY) ) {
                    sq.SetupCornerData( newCD = QuadCornerData.create(), cd, 1);
                    buffer[bi++] = newCD;
                }
                child = childList[2];
                if (boundIntersectSphere(sphere, cd.xorg, cd.zorg + half, child.MinY, cd.xorg + half, cd.zorg + full, child.MaxY)) {
                        
                    sq.SetupCornerData( newCD = QuadCornerData.create(), cd, 2);
                    buffer[bi++] = newCD;
                }
                child = childList[3];
                if (boundIntersectSphere(sphere, cd.xorg + half, cd.zorg + half, child.MinY, cd.xorg + full, cd.zorg + full, child.MaxY)) {
            
                    sq.SetupCornerData( newCD = QuadCornerData.create(), cd, 3);
                    buffer[bi++] = newCD;
                }
            }
            

        
            QuadCornerData.BI = stackStart;
            QuadRenderData.BI = rdStart;
            
            if (faceList == null) return;
            
            // calculating face normals needed for other game physics cases. (not EllipsoidCollider)
            ///*
            for (f = faceList; f != null; f = f.next) {
            
                var a:Vertex = f.wrapper.vertex;
                var b:Vertex = f.wrapper.next.vertex;
                var c:Vertex = f.wrapper.next.next.vertex;

                var abx:Number = b.x - a.x;
                var aby:Number = b.y - a.y;
                var abz:Number = b.z - a.z;
                var acx:Number = c.x - a.x;
                var acy:Number = c.y - a.y;
                var acz:Number = c.z - a.z;
                var nx:Number = acz*aby - acy*abz;
                var ny:Number = acx*abz - acz*abx;
                var nz:Number = acy * abx - acx * aby;
                
                var length:Number = nx*nx + ny*ny + nz*nz;
                if (length > 0.001) {
                    length = 1/Math.sqrt(length);
                    nx *= length;
                    ny *= length;
                    nz *= length;
                }
                
                f.normalX = nx;
                f.normalY = ny;
                f.normalZ = nz;
                f.offset = a.x * nx + a.y * ny + a.z * nz;
                
                lastFace = f;
            }
            //*/
            
            lastFace.next = triCollector;
            triCollector = faceList;
            
        }
        
    

        override alternativa3d function draw(camera:Camera3D, parentCanvas:Canvas):void {
            var canvas:Canvas;
            
            if (rootQuad == null) {
                super.draw(camera, parentCanvas);
                return;
            }
            
            // get (projected camera -> local space ) transform matrix  from currently composed (local space -> projected camera) transform.
            calculateInverseMatrix();
                
            _cameraPos.x = imd;
            _cameraPos.y = imh;
            _cameraPos.z = iml;
            
            
            // determine if frustum culling checks are needed
            var rootCulling:int = 0;
            if (culling > 0) { 
                calculateFrustumPlanes(camera);
                rootCulling = myCullingInFrustum(culling, rootQuad.xorg, rootQuad.zorg, rootQuad.Square.MinY, rootQuad.xorg + ((1 << rootQuad.Level)<<1), rootQuad.zorg+ ((1 << rootQuad.Level)<<1), rootQuad.Square.MaxY);
            }
            if (rootCulling < 0) {
                super.draw(camera, parentCanvas);
                return;
            }
        
            canvas = parentCanvas.getChildCanvas(false, true, this, alpha, blendMode, colorTransform, filters);
            canvas.numDraws = 0;
            
            
            var geometry:VG = collectVG(camera);
            for (var vg:VG = geometry; vg != null; vg = vg.next) {
                vg.calculateAABB(ima, imb, imc, imd, ime, imf, img, imh, imi, imj, imk, iml);
            }
                    
            transformId++;
            
            if (waterClipping) calculateWaterPlane();    
            
            if (autoUpdate) rootQuad.Square.Update(rootQuad,  _cameraPos, camera.focalLength, detail, this, (waterClipping ? rootCulling | 64 : rootCulling  ) );
            
            QuadCornerData.BI = 0;
            QuadOrder.BI = 0;  
            QuadRenderData.BI = 0;
            drawQuad(rootQuad, culling, camera, canvas, geometry);
                    
            // important , cleanup canvas!
            if (canvas.numDraws > 0) {
                 canvas.alternativa3d::removeChildren(canvas.numDraws);
          
            }
            else  parentCanvas.numDraws--;        
        }
        
        private final function calculateWaterPlane():void 
        {
            /*
            var va:Vector3D = new Vector3D(ma, me, mi);  // forward x
            var vb:Vector3D = new Vector3D(mb, mf, mj);
            _waterPlane = va.crossProduct(vb);
            _waterPlane.normalize();
            */
            
            ///*
            var ax:Number = ma;
            var ay:Number = me;
            var az:Number = mi;
            
            var bx:Number = mb;
            var by:Number = mf;
            var bz:Number = mj;
            
            _waterPlane.x = bz*ay - by*az;
            _waterPlane.y = bx*az - bz*ax;
            _waterPlane.z = by * ax - bx * ay;
            
            ax = _waterPlane.x * _waterPlane.x + _waterPlane.y * _waterPlane.y + _waterPlane.z * _waterPlane.z;
            if (ax > 0.001) {
                bx = 1 / Math.sqrt(ax);
                _waterPlane.x *= bx;
                _waterPlane.y *= bx;
                _waterPlane.z *= bx;
            }
            //*/

            
            _waterOffset = (waterLevel*mc+md) * _waterPlane.x + (waterLevel*mg+mh) * _waterPlane.y + (waterLevel*mk + ml) * _waterPlane.z;

        }
        
        
        /**
         * Important note: No public getVG() supported at the moment, returns dummy geometry such that a specialized custom parent KD container
         * (implementing IVGDelegater) can delgate VG drawing to this container itself during drawConflictGeometry() situations! I might do up a proper getVG() implementation to collect terrain LOD geometry so that it can at least work under a parent conflict container, but I dont need it for my case, however. After all, I assume TerrainQuadLODContainer will hold large terrains, so  it's more effecient to delegate dynamic geometry (ie. VG geometry) to this container, (or manually addChild() various CroppedObject3D fragments or object3Ds) directly to this container, rather than use the entire terrain geometry set as a dynamic VG splitter.
         * @param    camera
         * @return Dummy non-drawable face or null if parent isn't marked as an IVGDelegater
         */
        override alternativa3d function getVG(camera:Camera3D):VG {
            var vgDelegater:IVGDelegater = _parent as IVGDelegater;
            if (vgDelegater != null) {
                vgDelegater.notifyVGSkip(this);
                return VG.create(this, DummyFace.getDummyFace(), 0, 0,false); 
            }
            return null;
        }
        
        /**
         * Important note: No cloning supported. Returns itself!
         * @return    self
         */
        override public function clone():Object3D {
            return this;
            //return null;
        }
    
        

        override alternativa3d function updateBounds(bounds:Object3D, transformation:Object3D = null):void {
            super.updateBounds(bounds, transformation);
            if (rootQuad != null) updateBoundsQuad(rootQuad, bounds, null);
        }
        
        private static const quadBoundList:Vertex = Vertex.createList(8);
    
        
        private final function updateBoundsQuad(cd:QuadCornerData, bounds:Object3D, transformation:Object3D):void {
            var half:int =  1 << cd.Level;
            var full:int = half << 1;
            var vertex:Vertex;
            var boundMinX:Number = cd.xorg;
            var boundMinY:Number = cd.zorg;
            
            var boundMaxX:Number = boundMinX + full;
            var boundMaxY:Number = boundMinY + full;
            
            var boundMinZ:Number = cd.Square.MinY;
            var boundMaxZ:Number = cd.Square.MaxY;
            
            
            vertex = quadBoundList;
            vertex.x = boundMinX;
            vertex.y = boundMinY;
            vertex.z = boundMinZ;
            vertex = vertex.next;
            vertex.x = boundMaxX;
            vertex.y = boundMinY;
            vertex.z = boundMinZ;
            vertex = vertex.next;
            vertex.x = boundMinX;
            vertex.y = boundMaxY;
            vertex.z = boundMinZ;
            vertex = vertex.next;
            vertex.x = boundMaxX;
            vertex.y = boundMaxY;
            vertex.z = boundMinZ;
            vertex = vertex.next;
            vertex.x = boundMinX;
            vertex.y = boundMinY;
            vertex.z = boundMaxZ;
            vertex = vertex.next;
            vertex.x = boundMaxX;
            vertex.y = boundMinY;
            vertex.z = boundMaxZ;
            vertex = vertex.next;
            vertex.x = boundMinX;
            vertex.y = boundMaxY;
            vertex.z = boundMaxZ;
            vertex = vertex.next;
            vertex.x = boundMaxX;
            vertex.y = boundMaxY;
            vertex.z = boundMaxZ;

            
            for (vertex = quadBoundList; vertex != null; vertex = vertex.next) {
            
                if (transformation != null) {
                    vertex.cameraX = transformation.ma*vertex.x + transformation.mb*vertex.y + transformation.mc*vertex.z + transformation.md;
                    vertex.cameraY = transformation.me*vertex.x + transformation.mf*vertex.y + transformation.mg*vertex.z + transformation.mh;
                    vertex.cameraZ = transformation.mi*vertex.x + transformation.mj*vertex.y + transformation.mk*vertex.z + transformation.ml;
                } else {
                    vertex.cameraX = vertex.x;
                    vertex.cameraY = vertex.y;
                    vertex.cameraZ = vertex.z;
                }
                if (vertex.cameraX < bounds.boundMinX) bounds.boundMinX = vertex.cameraX;
                if (vertex.cameraX > bounds.boundMaxX) bounds.boundMaxX = vertex.cameraX;
                if (vertex.cameraY < bounds.boundMinY) bounds.boundMinY = vertex.cameraY;
                if (vertex.cameraY > bounds.boundMaxY) bounds.boundMaxY = vertex.cameraY;
                if (vertex.cameraZ < bounds.boundMinZ) bounds.boundMinZ = vertex.cameraZ;
                if (vertex.cameraZ > bounds.boundMaxZ) bounds.boundMaxZ = vertex.cameraZ;
            }
        }


        private static const QUAD_ORDER:Vector.<Vector.<int>> = createQuadOrderIndiceTable(false);
        private static const QUAD_ORDER2:Vector.<Vector.<int>> = createQuadOrderIndiceTable(true);
        private static function createQuadOrderIndiceTable(reversed:Boolean):Vector.<Vector.<int>> {
            // quad z order
            var result:Vector.<Vector.<int>> = new Vector.<Vector.<int>>(4, true);
            result[0] = !reversed ? new <int>[3,2,0,1] : new <int>[1,0,2,3];
            result[0].fixed = true;
            result[1] = !reversed ? new <int>[2,3,1,0] : new <int>[0,1,3,2];
            result[1].fixed = true;
            result[2] = !reversed ? new <int>[0,1,3,2] : new <int>[2,3,1,0];
            result[2].fixed = true;
            result[3] = !reversed ? new <int>[1,2,0,3] : new <int>[3,0,2,1];
            result[3].fixed = true;
            return result;
        }
        

        
        public static var triCollector:Face;
        public  function tri(vertData:Vector.<Vertex>, a:int, b:int, c:int):Face {
            var result:Face;
            
            if (triCollector != null) {
                result = triCollector;
                triCollector = result.next;
                result.next = null; 
                result.processNext = null;
            }
            else {
                result = new Face();
                result.wrapper = w = new Wrapper();
                w.next = w = new Wrapper();
                w.next = new Wrapper();
            }
            
        
            result.material = _worldTexture;
            
        
            var w:Wrapper = result.wrapper;
            
            w.vertex = vertData[a]; w = w.next;
            w.vertex = vertData[b]; w = w.next;
            w.vertex = vertData[c];
            
            return result;
        }
        
    
        private function createTri():Face {
            var result:Face;

            var w:Wrapper;
            if (triCollector != null) {
                result = triCollector;
                triCollector = result.next;
                result.next = null; 
                result.processNext = null;
            }
            else {
                result = new Face();
                result.wrapper = w = new Wrapper();
                w.next = w = new Wrapper();
                w.next = new Wrapper();
            }
            return result;
        }
        
        public function triClone(vertData:Vector.<Vertex>, a:int, b:int, c:int, id:int):Face {
            /* #if gotTileSet
            var result:Face;
            
            if (triCollector != null) {
                result = triCollector;
                triCollector = result.next;
                result.next = null; 
                result.processNext = null;
            }
            else {
                result = new Face();
                result.wrapper = w = new Wrapper();
                w.next = w = new Wrapper();
                w.next = new Wrapper();
            }
            
            var u:Number;
            var v:Number;
            
            
            var sizeMod:int = vertData[1].x - vertData[0].x;
            sizeMod <<= 1;
            sizeMod--;
            
            var w:Wrapper = result.wrapper;
            var referVert:Vertex;
            var vert:Vertex;
            var value:Vertex  = vertData[id];
    
            var tileDef:TextureDefinition = tileDefMap[ ( ((value.y - worldMinY) >> 8) << tileShift) +  ((value.x - worldMinX) >> 8)  ];
            result.material = tileDef.material; 
        
            referVert = vertData[a];
            vert = referVert.create();
            vert.x  = referVert.x;
            vert.y = referVert.y;
            vert.z = referVert.z;
            vert.cameraX = referVert.cameraX;
            vert.cameraY = referVert.cameraY;
            vert.cameraZ = referVert.cameraZ;
            vert.drawId = 1;
            vert.transformId = tileDef.uvTransformId;
            vert.u = ((vert.x - value.x) & sizeMod)  ? 1 : 0;
            vert.v = ((vert.y - value.y) & sizeMod)  ? 1 : 0;
            
    
            w.vertex = vert; w = w.next;
            
            referVert = vertData[b];
            vert = referVert.create();
            vert.x  = referVert.x;
            vert.y = referVert.y;
            vert.z = referVert.z;
            vert.cameraX = referVert.cameraX;
            vert.cameraY = referVert.cameraY;
            vert.cameraZ = referVert.cameraZ;
            vert.transformId = transformId;
            vert.u = ((vert.x - value.x) & sizeMod)  ? 1 : 0;
            vert.v = ((vert.y - value.y) & sizeMod)  ? 1 : 0;
            
            

            w.vertex = vert; w = w.next;
            
            referVert = vertData[c];
            vert = referVert.create();
            vert.x  = referVert.x;
            vert.y = referVert.y;
            vert.z = referVert.z;
            vert.cameraX = referVert.cameraX;
            vert.cameraY = referVert.cameraY;
            vert.cameraZ = referVert.cameraZ;
            vert.transformId = transformId;
            vert.u = ((vert.x - value.x) & sizeMod)  ? 1 : 0;
            vert.v = ((vert.y - value.y) & sizeMod)  ? 1 : 0;
            

            w.vertex = vert; 
            
            return result;
             #endif */
            
            ///* #if !gotTileSet
            var result:Face;
            
            if (triCollector != null) {
                result = triCollector;
                triCollector = result.next;
                result.next = null; 
                result.processNext = null;
            }
            else {
                result = new Face();
                result.wrapper = w = new Wrapper();
                w.next = w = new Wrapper();
                w.next = new Wrapper();
            }
            
        
            result.material = _worldTexture;
            
        
            var w:Wrapper = result.wrapper;
            
            w.vertex = vertData[a]; w = w.next;
            w.vertex = vertData[b]; w = w.next;
            w.vertex = vertData[c];
            
            return result;
            //#endif */
        }
        
        /* #if gotTileSet
        private static const transformUVs:Vector.<UVTransform> = TextureDefinition.TRANSFORM_UVS;
         #endif */

        public function triClipClone(vertData:Vector.<Vertex>, a:int, b:int, c:int, d:int, index:int, id:int):Face {
            
            /* #if gotTileSet
            var result:Face;
            
            if (triCollector != null) {
                result = triCollector;
                triCollector = result.next;
                result.next = null; 
                result.processNext = null;
            }
            else {
                result = new Face();
                result.wrapper = w = new Wrapper();
                w.next = w = new Wrapper();
                w.next = new Wrapper();
            }
            var value:Vertex = vertData[id];

            // i hate this lazy repeated calculation per tri!
            //( ((value.y - worldMinY) >> 8) << tileShift) +  ((value.x - worldMinX) >> 8)
            var tileDef:TextureDefinition = tileDefMap[   ( ((value.y - worldMinY) >> 8) << tileShift) +  ((value.x - worldMinX) >> 8) ];
            result.material = tileDef.material;// TEST_TILE_MATERIAL;
            
            var transform:UVTransform = transformUVs[tileDef.uvTransformId];
            
            var replaceB:Boolean = ( ((d & 2) != 0? 1:0 ) ^ (index & 1) ) != 0;
            var sizeMod:int = vertData[1].x - vertData[0].x;
            sizeMod <<= 1;
            sizeMod--;
    
            var w:Wrapper = result.wrapper;
            var referVert:Vertex;
            var vert:Vertex;
            
            var u:Number, v:Number;
            
            referVert = vertData[a];
            vert = referVert.create();
            vert.x  = referVert.x;
            vert.y = referVert.y;
            vert.z = referVert.z;
            vert.cameraX = referVert.cameraX;
            vert.cameraY = referVert.cameraY;
            vert.cameraZ = referVert.cameraZ;
            vert.drawId = 1;
            vert.transformId = tileDef.uvTransformId;
            vert.u = ((vert.x - value.x) & sizeMod)  ? 1 : 0;
            vert.v = ((vert.y - value.y) & sizeMod)  ? 1 : 0;
            
            w.vertex = vert; w = w.next;
            
            referVert = replaceB ? vertData[d].value : vertData[b];
            vert = referVert.create();
            vert.x = referVert.x;
            vert.y = referVert.y;
            vert.z = referVert.z;
            vert.cameraX = referVert.cameraX;
            vert.cameraY = referVert.cameraY;
            vert.cameraZ = referVert.cameraZ;
            vert.transformId = transformId;
            vert.u = ((vert.x - value.x) & sizeMod)  ? 1 : 0;
            vert.v = ((vert.y - value.y) & sizeMod)  ? 1 : 0;
            
            w.vertex = vert; w = w.next;
            
            referVert = replaceB ? vertData[c] : vertData[d].value;
            vert = referVert.create();
            vert.x  = referVert.x;
            vert.y = referVert.y;
            vert.z = referVert.z;
            vert.cameraX = referVert.cameraX;
            vert.cameraY = referVert.cameraY;
            vert.cameraZ = referVert.cameraZ;
            vert.transformId = transformId;
            vert.u = ((vert.x - value.x) & sizeMod)  ? 1 : 0;
            vert.v = ((vert.y - value.y) & sizeMod)  ? 1 : 0;
            
            w.vertex = vert; 
            
            return result;
             #endif */
            
            ///* #if !gotTileSet
            // Duplicate of non cloned method
            var result:Face;
            
            if (triCollector != null) {
                result = triCollector;
                triCollector = result.next;
                result.next = null; 
                result.processNext = null;
            }
            else {
                result = new Face();
                result.wrapper = w = new Wrapper();
                w.next = w = new Wrapper();
                w.next = new Wrapper();
            }
        
            result.material = _worldTexture;
            
            
            var w:Wrapper = result.wrapper;

            var replaceB:Boolean = ( ((d & 2) != 0? 1:0 ) ^ (index & 1) ) != 0;
            w.vertex = vertData[a]; w = w.next;
            w.vertex = replaceB ? vertData[d].value : vertData[b]; w = w.next;
            w.vertex = replaceB ? vertData[c] : vertData[d].value;
            
            return result;
            //#endif */
        }
        
        public  function triclip(vertData:Vector.<Vertex>, a:int, b:int, c:int, d:int, index:int):Face {
            var result:Face;
            
            if (triCollector != null) {
                result = triCollector;
                triCollector = result.next;
                result.next = null; 
                result.processNext = null;
            }
            else {
                result = new Face();
                result.wrapper = w = new Wrapper();
                w.next = w = new Wrapper();
                w.next = new Wrapper();
            }
        
            result.material = _worldTexture;
            
            
            var w:Wrapper = result.wrapper;

            var replaceB:Boolean = ( ((d & 2) != 0? 1:0 ) ^ (index & 1) ) != 0;
            w.vertex = vertData[a]; w = w.next;
            w.vertex = replaceB ? vertData[d].value : vertData[b]; w = w.next;
            w.vertex = replaceB ? vertData[c] : vertData[d].value;
            
            return result;
        }
        
        public static var TEST_MATERIAL:Material = new FillMaterial(0x33DDBB, 1);
        public static var TEST_TILE_MATERIAL:Material = new FillMaterial(0x33DDBB, 1);
        public static var TEST_BMP:BitmapData = new BitmapData(128, 128, false, 0x33DDBB);
        public static var TEST_MATERIAL2:Material = new FillMaterial(0xFF0000, 1);
        
        private function prepareFaces(faceList:Face, camera:Camera3D):Face {
            var first:Face;
            var last:Face;
            var nextFace:Face;
            
            for (var face:Face = faceList; face != null; face = nextFace) {
                nextFace = face.next;
                var a:Vertex = face.wrapper.vertex;
                var b:Vertex = face.wrapper.next.vertex;
                var c:Vertex = face.wrapper.next.next.vertex;

                var abx:Number = b.x - a.x;
                var aby:Number = b.y - a.y;
                var abz:Number = b.z - a.z;
                var acx:Number = c.x - a.x;
                var acy:Number = c.y - a.y;
                var acz:Number = c.z - a.z;
                var nx:Number = acz*aby - acy*abz;
                var ny:Number = acx*abz - acz*abx;
                var nz:Number = acy * abx - acx * aby;
                
                        var length:Number = nx*nx + ny*ny + nz*nz;
                        if (length > 0.001) {
                            length = 1/Math.sqrt(length);
                            nx *= length;
                            ny *= length;
                            nz *= length;
                        }
                
                face.normalX = nx;
                face.normalY = ny;
                face.normalZ = nz;
                face.offset = a.x * nx + a.y * ny + a.z * nz;
                
                if (face.normalX * imd + face.normalY * imh + face.normalZ * iml > face.offset) {
                    //if (a.offset <= _waterOffset) throw new Error("YES"+a.offset);
                    if ( waterClipping && (a.offset <= _waterOffset || b.offset <= _waterOffset || c.offset <= _waterOffset) ) 
                    {
                        face.processNext = null;
        
                        var clipFace:Face = newPositiveClipFace(face, _waterPlane, _waterOffset);
                        if (clipFace.wrapper != null) {
                            
                            if (first != null) {
                                last.processNext = clipFace;
                            } else {
                                first = clipFace;
                            }
                            last = clipFace;
                            
                        }
                        else {
                            if (first != null) {
                                last.processNext = nextFace;
                            } 
                        }
                        clipFace.next = camera.lastFace;
                        camera.lastFace = clipFace;
                        continue;
                    }
                    if (first != null) {
                        last.processNext = face;
                    } else {
                        first = face;
                    }
                    last = face;
                }
            }
            if (last != null) last.processNext = null;
            return first;
        }
        
        private function drawFaces(camera:Camera3D, parentCanvas:Canvas, list:Face):void {
            if (camera.debug) {
                Debug.drawEdges(camera, parentCanvas.getChildCanvas(true, false), list, 0xFFFFFF);
            }
            var canvas:Canvas = parentCanvas.getChildCanvas(true, false, this);
            for (var face:Face = list; face != null; face = next) {
                var next:Face = face.processNext;
                if (next == null || next.material != list.material) {
                    face.processNext = null;
                    if (list.material != null) {
                        list.material.draw(camera, canvas, list, ml);
                    } else {
                        while (list != null) {
                            face = list.processNext;
                            list.processNext = null;
                            list = face;
                        }
                    }
                    list = next;
                }
            }
        }
        
        private final function drawTri4(rd:QuadRenderData, culling:int, camera:Camera3D, canvas:Canvas):void {
            var f:Face = tri(rd.vertexList, 0, 4, 2);
            var head:Face = f;
            f.next = f = tri(rd.vertexList, 0, 6, 4);
            f.next = f = tri(rd.vertexList, 0, 8, 6);
            f.next = f = tri(rd.vertexList, 0, 2, 8);
            
            var faceList:Face = prepareFaces(head, camera);
            if (culling > 0) {
                faceList = clipping == 2 ? camera.clip(faceList, culling) : camera.cull(faceList, culling);
            }
            if (faceList != null) drawFaces(camera, canvas, faceList);
            
            
            
            f.next = triCollector;
            triCollector = head;
        }
        
        
        
        private static function createQuadStackBuffer():Vector.<QuadI> {
            // starting resolution at 1024x1024 tile to 256x256 tile leaf resolution.
            var retStack: Vector.<QuadI> =  new Vector.<QuadI>(5, true);
            for (var  i:int = 0; i < 5; i++) {
                retStack[i] = new QuadI();
            }
            return retStack;
        }
        private static const QUAD_STACK:Vector.<QuadI> = createQuadStackBuffer();

    
        /**
         * Non-recursive iterative inline method to help collect faces in quad z-order in back-to-front order 
         * from a fixed starting resolution buffer size onwards.  This method is used from 1024x1024 leaf tiles onwards.
         * At this level of detail, there are certain texturing routines which are different from the "early-out"
         * lod traversals.
         * Make sure QUAD_STACK buffer is filled accordingly with the correct size.
         */
        private final function collectQuadFaces(collector:Face, cd:QuadCornerData, culling:int, camera:Camera3D):void {

            var child:QuadSquare;
            var q:QuadCornerData;
            var index:int;
            var i:int;
            var applyTexture:TextureMaterial;
            var tileTexture:BitmapData;
            var orderedIndices:Vector.<int>;
            var quadOrder2:Vector.<Vector.<int>> = QUAD_ORDER2;
            var rd:QuadRenderData;
            
            var quadStack:Vector.<QuadI> = QUAD_STACK;
            var drawPt:Point;
            
            var quadI:QuadI = quadStack[0];
            var len:int = 1;
            quadI.cd = cd; quadI.i = -1;
            
            
            while ( len > 0) {
                quadI = quadStack[len - 1];
                
                cd = quadI.cd;
                
                var sq:QuadSquare = cd.Square;    
                var Child:Vector.<QuadSquare> = sq.Child;    
                var EnabledFlags:int = sq.EnabledFlags;
                var half:int = 1 << cd.Level;
                var full:int = half << 1;
                
                if ( !(EnabledFlags & 255) ) {  // 4 leaf draw
                    
                    rd = QuadRenderData.create();
                    rd.cd = cd;
                    rd.setupForRendering(this, camera, _worldTextureMinX, _worldTextureMinY, _invWorldTxDist, _waterPlane);
                    
                    collector = collector.next = tri(rd.vertexList, 0, 4, 2);
                    collector = collector.next = tri(rd.vertexList, 0, 6, 4);
                    collector = collector.next = tri(rd.vertexList, 0, 8, 6);
                    collector = collector.next = tri(rd.vertexList, 0, 2, 8);

                    len--;
                    continue;
                }
                
                applyTexture = null;
                
                if (quadI.i == 3) {
                    len--;
                    continue;
                }
                
                // preprocess quad data if required
                if (quadI.i < 0) {
                    index = 0;
                    index |= camera.x < cd.xorg+half ? 1 : 0; 
                    index |= camera.y < cd.zorg+ half ? 2 : 0; 
                    quadI.orderedIndices = orderedIndices =  quadOrder2[index];
                
                    var CULLING_INDEX:Vector.<int> = quadI.cullingResults; 
                    child = Child[0]; 
                    CULLING_INDEX[0] = (waterClipping && child.MaxY <= boundMinZ) ? -1 :  culling == 0 ? 0 :  myCullingInFrustum(culling, cd.xorg+half, cd.zorg, child.MinY, cd.xorg+full, cd.zorg+half, child.MaxY);        
                    child = Child[1];
                    CULLING_INDEX[1] = (waterClipping && child.MaxY <= boundMinZ) ? -1 : culling == 0 ? 0 : myCullingInFrustum(culling, cd.xorg, cd.zorg, child.MinY, cd.xorg+half, cd.zorg+half, child.MaxY);        
                    child = Child[2];
                    CULLING_INDEX[2] = (waterClipping && child.MaxY <= boundMinZ) ? -1 : culling ==0 ? 0 : myCullingInFrustum(culling, cd.xorg, cd.zorg+half, child.MinY, cd.xorg+half, cd.zorg+full, child.MaxY);        
                    child = Child[3];
                    CULLING_INDEX[3] = (waterClipping && child.MaxY <= boundMinZ) ? -1 : culling == 0 ? 0 : myCullingInFrustum(culling, cd.xorg + half, cd.zorg + half, child.MinY, cd.xorg + full, cd.zorg + full, child.MaxY);    
                    
                    if ( (EnabledFlags>>4) ^ (EnabledFlags & 15)  ) {
                        rd = QuadRenderData.create();
                        rd.cd = cd;
                        rd.setupForRendering(this, camera, _worldTextureMinX, _worldTextureMinY, _invWorldTxDist, _waterPlane);
                        quadI.rd = rd;
                    }
                    else {
                        quadI.rd = null;
                    }
                }
                else { // retrieve back old quad data values
                    CULLING_INDEX = quadI.cullingResults;
                    orderedIndices = quadI.orderedIndices;
                    rd = quadI.rd;
                    //applyTexture = quadI.tiling;
                }
                
                if (cd.Level == 8) { // is final leaf lod level, so only test for drawing without considering recursion.
                // Spill repeat this process 4 times. At this level, all vertices per triangle are cloned with unique UVs to be
                // set later.
                    i = ++quadI.i; 
                    index = orderedIndices[i];
                    if ( (culling = CULLING_INDEX[index]) >= 0 ) {
                        collector = collector.next = index === 0 ? (EnabledFlags & 1) == 0 ? triClipClone(rd.vertexList, 0, 2, 8, 1, index, 3) : triClone(rd.vertexList, 0, 2, 1, 3) : index === 1 ? (EnabledFlags & 2) == 0 ?  triClipClone(rd.vertexList, 0, 4, 2, 3, index, 4) :  triClone(rd.vertexList, 0, 4, 3, 4) : index === 2 ? (EnabledFlags & 4) == 0 ? triClipClone(rd.vertexList, 0, 6, 4, 5, index, 5) :  triClone(rd.vertexList, 0, 6, 5, 5) :   (EnabledFlags & 8) == 0 ?  triClipClone(rd.vertexList, 0, 8, 6, 7, index, 0) : triClone(rd.vertexList, 0, 8, 7, 0);
                    
                        collector = collector.next = index === 0 ? (EnabledFlags & 2) == 0 ?  triClipClone(rd.vertexList,0, 4, 2, 3, index, 3) : triClone(rd.vertexList, 0, 3, 2, 3) : index === 1 ? (EnabledFlags & 4) == 0 ? triClipClone(rd.vertexList,0, 6, 4, 5, index, 4) : triClone(rd.vertexList,0, 5, 4, 4) : index === 2 ? (EnabledFlags & 8) == 0 ? triClipClone(rd.vertexList,0, 8, 6, 7, index, 5) : triClone(rd.vertexList,0, 7, 6, 5) : (EnabledFlags & 1) == 0 ?  triClipClone(rd.vertexList,0, 2, 8,1,index, 0) : triClone(rd.vertexList,0, 1, 8, 0); 
                    }
                    
                    i = ++quadI.i; 
                    index = orderedIndices[i];
                    if ( (culling = CULLING_INDEX[index]) >= 0 ) {
                        collector = collector.next = index === 0 ? (EnabledFlags & 1) == 0 ? triClipClone(rd.vertexList, 0, 2, 8, 1, index, 3) : triClone(rd.vertexList, 0, 2, 1, 3) : index === 1 ? (EnabledFlags & 2) == 0 ?  triClipClone(rd.vertexList, 0, 4, 2, 3, index, 4) :  triClone(rd.vertexList, 0, 4, 3, 4) : index === 2 ? (EnabledFlags & 4) == 0 ? triClipClone(rd.vertexList, 0, 6, 4, 5, index, 5) :  triClone(rd.vertexList, 0, 6, 5, 5) :   (EnabledFlags & 8) == 0 ?  triClipClone(rd.vertexList, 0, 8, 6, 7, index, 0) : triClone(rd.vertexList, 0, 8, 7, 0);
                        collector = collector.next = index === 0 ? (EnabledFlags & 2) == 0 ?  triClipClone(rd.vertexList,0, 4, 2, 3, index, 3) : triClone(rd.vertexList, 0, 3, 2, 3) : index === 1 ? (EnabledFlags & 4) == 0 ? triClipClone(rd.vertexList,0, 6, 4, 5, index, 4) : triClone(rd.vertexList,0, 5, 4, 4) : index === 2 ? (EnabledFlags & 8) == 0 ? triClipClone(rd.vertexList,0, 8, 6, 7, index, 5) : triClone(rd.vertexList,0, 7, 6, 5) : (EnabledFlags & 1) == 0 ?  triClipClone(rd.vertexList,0, 2, 8,1,index, 0) : triClone(rd.vertexList,0, 1, 8, 0); 
                    }
                    
                    i = ++quadI.i; 
                    index = orderedIndices[i];
                    if ( (culling = CULLING_INDEX[index]) >= 0 ) {
                        collector = collector.next = index === 0 ? (EnabledFlags & 1) == 0 ? triClipClone(rd.vertexList, 0, 2, 8, 1, index, 3) : triClone(rd.vertexList, 0, 2, 1, 3) : index === 1 ? (EnabledFlags & 2) == 0 ?  triClipClone(rd.vertexList, 0, 4, 2, 3, index, 4) :  triClone(rd.vertexList, 0, 4, 3, 4) : index === 2 ? (EnabledFlags & 4) == 0 ? triClipClone(rd.vertexList, 0, 6, 4, 5, index, 5) :  triClone(rd.vertexList, 0, 6, 5, 5) :   (EnabledFlags & 8) == 0 ?  triClipClone(rd.vertexList, 0, 8, 6, 7, index, 0) : triClone(rd.vertexList, 0, 8, 7, 0);
                        collector = collector.next = index === 0 ? (EnabledFlags & 2) == 0 ?  triClipClone(rd.vertexList,0, 4, 2, 3, index, 3) : triClone(rd.vertexList, 0, 3, 2, 3) : index === 1 ? (EnabledFlags & 4) == 0 ? triClipClone(rd.vertexList,0, 6, 4, 5, index, 4) : triClone(rd.vertexList,0, 5, 4, 4) : index === 2 ? (EnabledFlags & 8) == 0 ? triClipClone(rd.vertexList,0, 8, 6, 7, index, 5) : triClone(rd.vertexList,0, 7, 6, 5) : (EnabledFlags & 1) == 0 ?  triClipClone(rd.vertexList,0, 2, 8,1,index, 0) : triClone(rd.vertexList,0, 1, 8, 0);  
                    }
                    
                    i = ++quadI.i; 
                    index = orderedIndices[i];
                    if ( (culling = CULLING_INDEX[index]) >= 0 ) {
                        collector = collector.next = index === 0 ? (EnabledFlags & 1) == 0 ? triClipClone(rd.vertexList, 0, 2, 8, 1, index, 3) : triClone(rd.vertexList, 0, 2, 1, 3) : index === 1 ? (EnabledFlags & 2) == 0 ?  triClipClone(rd.vertexList, 0, 4, 2, 3, index, 4) :  triClone(rd.vertexList, 0, 4, 3, 4) : index === 2 ? (EnabledFlags & 4) == 0 ? triClipClone(rd.vertexList, 0, 6, 4, 5, index, 5) :  triClone(rd.vertexList, 0, 6, 5, 5) :   (EnabledFlags & 8) == 0 ?  triClipClone(rd.vertexList, 0, 8, 6, 7, index, 0) : triClone(rd.vertexList, 0, 8, 7, 0);
                        collector = collector.next = index === 0 ? (EnabledFlags & 2) == 0 ?  triClipClone(rd.vertexList,0, 4, 2, 3, index, 3) : triClone(rd.vertexList, 0, 3, 2, 3) : index === 1 ? (EnabledFlags & 4) == 0 ? triClipClone(rd.vertexList,0, 6, 4, 5, index, 4) : triClone(rd.vertexList,0, 5, 4, 4) : index === 2 ? (EnabledFlags & 8) == 0 ? triClipClone(rd.vertexList,0, 8, 6, 7, index, 5) : triClone(rd.vertexList,0, 7, 6, 5) : (EnabledFlags & 1) == 0 ?  triClipClone(rd.vertexList,0, 2, 8,1,index, 0) : triClone(rd.vertexList,0, 1, 8, 0); 
                    }
                     
                    len--;
                    continue;
                }
                
                
                // Repeat this old transitory process up to 4 times
                // 1st
                i = ++quadI.i; 
                index = orderedIndices[i];
                if ( (culling = CULLING_INDEX[index]) >= 0 ) {
                    if ( EnabledFlags & (16 << index) ) {
                        quadI = quadStack[len++]; quadI.i = -1;
                        sq.SetupCornerData(quadI.cd = QuadCornerData.create(), cd, index);
                        continue;
                    }
                    else {
                        collector = collector.next = index === 0 ? (EnabledFlags & 1) == 0 ? triclip(rd.vertexList, 0, 2, 8, 1, index) : tri(rd.vertexList, 0, 2, 1) : index === 1 ? (EnabledFlags & 2) == 0 ?  triclip(rd.vertexList, 0, 4, 2, 3, index) :  tri(rd.vertexList, 0, 4, 3) : index === 2 ? (EnabledFlags & 4) == 0 ? triclip(rd.vertexList, 0, 6, 4, 5, index) :  tri(rd.vertexList, 0, 6, 5) :   (EnabledFlags & 8) == 0 ?  triclip(rd.vertexList, 0, 8, 6, 7, index) : tri(rd.vertexList, 0, 8, 7);
                        collector = collector.next = index === 0 ? (EnabledFlags & 2) == 0 ?  triclip(rd.vertexList, 0, 4, 2, 3, index) : tri(rd.vertexList, 0, 3, 2) : index === 1 ? (EnabledFlags & 4) == 0 ? triclip(rd.vertexList, 0, 6, 4, 5, index) : tri(rd.vertexList, 0, 5, 4) : index === 2 ? (EnabledFlags & 8) == 0 ? triclip(rd.vertexList, 0, 8, 6, 7, index) : tri(rd.vertexList, 0, 7, 6) : (EnabledFlags & 1) == 0 ?  triclip(rd.vertexList, 0, 2, 8, 1, index) : tri(rd.vertexList, 0, 1, 8); 
                    }
                }
                if (i == 3) {
                    len--; continue;
                }
                
                // 2nd
                i = ++quadI.i; 
                index = orderedIndices[i];
                if ( (culling = CULLING_INDEX[index]) >= 0 ) {
                    if ( EnabledFlags & (16 << index) ) {
                        quadI = quadStack[len++];  quadI.i = -1;
                        sq.SetupCornerData(quadI.cd = QuadCornerData.create(), cd, index);
                        continue;
                    }
                    else {
                        collector = collector.next = index === 0 ? (EnabledFlags & 1) == 0 ? triclip(rd.vertexList, 0, 2, 8, 1, index) : tri(rd.vertexList, 0, 2, 1) : index === 1 ? (EnabledFlags & 2) == 0 ?  triclip(rd.vertexList, 0, 4, 2, 3, index) :  tri(rd.vertexList, 0, 4, 3) : index === 2 ? (EnabledFlags & 4) == 0 ? triclip(rd.vertexList, 0, 6, 4, 5, index) :  tri(rd.vertexList, 0, 6, 5) :   (EnabledFlags & 8) == 0 ?  triclip(rd.vertexList, 0, 8, 6, 7, index) : tri(rd.vertexList, 0, 8, 7);

                        collector = collector.next = index === 0 ? (EnabledFlags & 2) == 0 ?  triclip(rd.vertexList, 0, 4, 2, 3, index) : tri(rd.vertexList, 0, 3, 2) : index === 1 ? (EnabledFlags & 4) == 0 ? triclip(rd.vertexList, 0, 6, 4, 5, index) : tri(rd.vertexList, 0, 5, 4) : index === 2 ? (EnabledFlags & 8) == 0 ? triclip(rd.vertexList, 0, 8, 6, 7, index) : tri(rd.vertexList, 0, 7, 6) : (EnabledFlags & 1) == 0 ?  triclip(rd.vertexList, 0, 2, 8, 1, index) : tri(rd.vertexList, 0, 1, 8); 
                    }
                }
                if (i == 3) {
                    len--; continue;
                }
                
                // 3rd
                i = ++quadI.i; 
                index = orderedIndices[i];
                if ( (culling = CULLING_INDEX[index]) >= 0 ) {
                    if ( EnabledFlags & (16 << index) ) {
                        quadI = quadStack[len++];  quadI.i = -1;
                        sq.SetupCornerData(quadI.cd = QuadCornerData.create(), cd, index);
                        continue;
                    }
                    else {
                        collector = collector.next = index === 0 ? (EnabledFlags & 1) == 0 ? triclip(rd.vertexList, 0, 2, 8, 1, index) : tri(rd.vertexList, 0, 2, 1) : index === 1 ? (EnabledFlags & 2) == 0 ?  triclip(rd.vertexList, 0, 4, 2, 3, index) :  tri(rd.vertexList, 0, 4, 3) : index === 2 ? (EnabledFlags & 4) == 0 ? triclip(rd.vertexList, 0, 6, 4, 5, index) :  tri(rd.vertexList, 0, 6, 5) :   (EnabledFlags & 8) == 0 ?  triclip(rd.vertexList, 0, 8, 6, 7, index) : tri(rd.vertexList, 0, 8, 7);
                        collector = collector.next = index === 0 ? (EnabledFlags & 2) == 0 ?  triclip(rd.vertexList, 0, 4, 2, 3, index) : tri(rd.vertexList, 0, 3, 2) : index === 1 ? (EnabledFlags & 4) == 0 ? triclip(rd.vertexList, 0, 6, 4, 5, index) : tri(rd.vertexList, 0, 5, 4) : index === 2 ? (EnabledFlags & 8) == 0 ? triclip(rd.vertexList, 0, 8, 6, 7, index) : tri(rd.vertexList, 0, 7, 6) : (EnabledFlags & 1) == 0 ?  triclip(rd.vertexList, 0, 2, 8, 1, index) : tri(rd.vertexList, 0, 1, 8); 
                    }
                }
                if (i == 3) {
                    len--; continue;
                }
                
                // 4th (last)
                i = ++quadI.i; 
                index = orderedIndices[i];
                if ( (culling = CULLING_INDEX[index]) >= 0 ) {
                    if ( EnabledFlags & (16 << index) ) {
                        quadI = quadStack[len++];  quadI.i = -1;
                        sq.SetupCornerData(quadI.cd = QuadCornerData.create(), cd, index);
                        continue;
                    }
                    else {
                        collector = collector.next = index === 0 ? (EnabledFlags & 1) == 0 ? triclip(rd.vertexList, 0, 2, 8, 1, index) : tri(rd.vertexList, 0, 2, 1) : index === 1 ? (EnabledFlags & 2) == 0 ?  triclip(rd.vertexList, 0, 4, 2, 3, index) :  tri(rd.vertexList, 0, 4, 3) : index === 2 ? (EnabledFlags & 4) == 0 ? triclip(rd.vertexList, 0, 6, 4, 5, index) :  tri(rd.vertexList, 0, 6, 5) :   (EnabledFlags & 8) == 0 ?  triclip(rd.vertexList, 0, 8, 6, 7, index) : tri(rd.vertexList, 0, 8, 7);
                        collector = collector.next = index === 0 ? (EnabledFlags & 2) == 0 ?  triclip(rd.vertexList, 0, 4, 2, 3, index) : tri(rd.vertexList, 0, 3, 2) : index === 1 ? (EnabledFlags & 4) == 0 ? triclip(rd.vertexList, 0, 6, 4, 5, index) : tri(rd.vertexList, 0, 5, 4) : index === 2 ? (EnabledFlags & 8) == 0 ? triclip(rd.vertexList, 0, 8, 6, 7, index) : tri(rd.vertexList, 0, 7, 6) : (EnabledFlags & 1) == 0 ?  triclip(rd.vertexList, 0, 2, 8, 1, index) : tri(rd.vertexList, 0, 1, 8); 
                    }
                }
                len--;
            }
        }
    
        private final function drawQuadMesh(cd:QuadCornerData, culling:int, camera:Camera3D, canvas:Canvas):void {
            var collector:Face = Face.create();    
            collectQuadFaces(collector, cd, culling, camera);
            
            var head:Face = collector.next;
            collector.next = Face.collector;
            Face.collector = collector;
            if (head == null) return;
            
            var tail:Face;
            
            /* #if gotTileSet
            const transformUVs:Vector.<UVTransform> = transformUVs;
            var u:Number, v:Number;
            var uvt:UVTransform;
            #endif */
            
            // prepareFaces inline() with other specifics
            var first:Face;
            var last:Face;
            var nextFace:Face;
            
            for (var face:Face = head; face != null; face = nextFace) {
                nextFace = face.next;
                var a:Vertex = face.wrapper.vertex;
                
                var b:Vertex = face.wrapper.next.vertex;
                var c:Vertex = face.wrapper.next.next.vertex;
                
                /* #if gotTileSet
                if (a.drawId != 0) {   
                    uvt = transformUVs[a.transformId];
                    u = a.u; v = a.v;
                    a.u = uvt.a * u + uvt.c * v + uvt.tx;
                    a.v = uvt.b * u + uvt.d * v + uvt.ty;
                    a.u &= 1;
                    a.v *= 1;
                    a.v = 1 - a.v;
                    
                    u = b.u; v = b.v;
                    b.u = uvt.a * u + uvt.c * v + uvt.tx;
                    b.v = uvt.b * u + uvt.d * v + uvt.ty;
                    b.u &= 1;
                    b.v *= 1;
                    b.v = 1 - b.v;
                    
                    u = c.u; v = c.v;
                    c.u = uvt.a * u + uvt.c * v + uvt.tx;
                    c.v = uvt.b * u + uvt.d * v + uvt.ty;
                    c.u &= 1;
                    c.v *= 1;
                    c.v = 1 - c.v;
    
                    a.drawId = 0;    
                    // return cloned vertices back to defered pool
                    a.next = b;
                    b.next = c;
                    camera.lastVertex.next = a; 
                    camera.lastVertex = c;
                }
                 #endif */
                
                var abx:Number = b.x - a.x;
                var aby:Number = b.y - a.y;
                var abz:Number = b.z - a.z;
                var acx:Number = c.x - a.x;
                var acy:Number = c.y - a.y;
                var acz:Number = c.z - a.z;
                var nx:Number = acz*aby - acy*abz;
                var ny:Number = acx*abz - acz*abx;
                var nz:Number = acy * abx - acx * aby;
                
                        var length:Number = nx*nx + ny*ny + nz*nz;
                        if (length > 0.001) {
                            length = 1/Math.sqrt(length);
                            nx *= length;
                            ny *= length;
                            nz *= length;
                        }
                
                face.normalX = nx;
                face.normalY = ny;
                face.normalZ = nz;
                face.offset = a.x * nx + a.y * ny + a.z * nz;
                
                if (face.normalX * imd + face.normalY * imh + face.normalZ * iml > face.offset) {
                    if (waterClipping && (a.offset <= _waterOffset || b.offset <= _waterOffset || c.offset <= _waterOffset))  //
                    {
                        face.processNext = null;
        
                        var clipFace:Face = newPositiveClipFace(face, _waterPlane, _waterOffset);
                        if (clipFace.wrapper != null) {
                            if (first != null) {
                                last.processNext = clipFace;
                            } else {
                                first = clipFace;
                            }
                            last = clipFace;
                            
                        }
                        else {
                            if (first != null) {
                                last.processNext = nextFace;
                            } 
                            
                        }
                        tail  = face;
                        clipFace.next = camera.lastFace;
                        camera.lastFace = clipFace;
                        continue;
                    }
                    if (first != null) {
                        last.processNext = face;
                    } else {
                        first = face;
                    }
                    last = face;
                }
                tail = face;
            }
            if (last != null) last.processNext = null;
            
            
            
            if (first != null) {
                if (culling > 0) {
                    first = clipping == 2 ? camera.clip(first, culling) : camera.cull(first, culling);
                }
                if(first!=null) drawFaces(camera, canvas, first);
            }

    
            tail.next = triCollector;
            triCollector = head;
    
        }
        

        private final function drawVGQuadMesh(cd:QuadCornerData, culling:int, camera:Camera3D, canvas:Canvas, geometry:VG):void {
            var vg:VG;
            
            var collector:Face = Face.create();    
            collectQuadFaces(collector, cd, culling, camera);
            
            var head:Face = collector.next;
            collector.next = Face.collector;
            Face.collector = collector;
            if (head == null) {
                if (geometry.next != null) drawAABBGeometry(camera, canvas, geometry);
                else {
                    geometry.draw(camera, canvas, threshold, this);
                    geometry.destroy();
                }
                return;
            }
            
            var tail:Face;

            /* #if gotTileSet
            var uvt:UVTransform;
            const transformUVs:Vector.<UVTransform> = transformUVs;
            var u:Number;
            var v:Number;
             #endif */
            
            // prepareFaces inline() with other specifics
            var first:Face;
            var last:Face;
            var nextFace:Face;
            for (var face:Face = head; face != null; face = nextFace) {
                nextFace = face.next;
                var a:Vertex = face.wrapper.vertex;
                
                var b:Vertex = face.wrapper.next.vertex;
                var c:Vertex = face.wrapper.next.next.vertex;
                
                /* #if gotTileSet
                if (a.drawId != 0) {  
                    uvt = transformUVs[a.transformId];
            
                    u = a.u; v = a.v;
                    a.u = uvt.a * u + uvt.c * v + uvt.tx;
                    a.v = uvt.b * u + uvt.d * v + uvt.ty;
                    a.u &= 1;
                    a.v *= 1;
                    a.v = 1 - a.v;
                    
                    u = b.u; v = b.v;
                    b.u = uvt.a * u + uvt.c * v + uvt.tx;
                    b.v = uvt.b * u + uvt.d * v + uvt.ty;
                    b.u &= 1;
                    b.v *= 1;
                    b.v = 1 - b.v;
                    
                    u = c.u; v = c.v;
                    c.u = uvt.a * u + uvt.c * v + uvt.tx;
                    c.v = uvt.b * u + uvt.d * v + uvt.ty;
                    c.u &= 1;
                    c.v *= 1;
                    c.v = 1 - c.v;
                    //uvt.transformVertex(a);
                    //    uvt.transformVertex(b);
                    //uvt.transformVertex(c);
                    
                    
                    a.drawId = 0;    
                    // return cloned vertices back to defered pool (could collect immediatley actually for non-vg case)
                    a.next = b;
                    b.next = c;
                    
                    camera.lastVertex.next = a; 
                    camera.lastVertex = c;
                    
                    
                }
                 #endif */
                
                var abx:Number = b.x - a.x;
                var aby:Number = b.y - a.y;
                var abz:Number = b.z - a.z;
                var acx:Number = c.x - a.x;
                var acy:Number = c.y - a.y;
                var acz:Number = c.z - a.z;
                var nx:Number = acz*aby - acy*abz;
                var ny:Number = acx*abz - acz*abx;
                var nz:Number = acy * abx - acx * aby;
                
                var length:Number = nx*nx + ny*ny + nz*nz;
                if (length > 0.001) {
                    length = 1/Math.sqrt(length);
                    nx *= length;
                    ny *= length;
                    nz *= length;
                }
        
                face.normalX = nx;
                face.normalY = ny;
                face.normalZ = nz;
                face.offset = a.x * nx + a.y * ny + a.z * nz;
                
                if (face.normalX * imd + face.normalY * imh + face.normalZ * iml > face.offset) {
                        if (waterClipping && (a.offset <= _waterOffset || b.offset <= _waterOffset || c.offset <= _waterOffset))  //
                    {
                        face.processNext = null;
        
                        var clipFace:Face = newPositiveClipFace(face, _waterPlane, _waterOffset);
                        if (clipFace.wrapper != null) {
                            if (first != null) {
                                last.processNext = clipFace;
                            } else {
                                first = clipFace;
                            }
                            last = clipFace;
                            
                        }
                        else {
                            if (first != null) {
                                last.processNext = nextFace;
                            } 
                            
                        }
                        tail  = face;
                        clipFace.next = camera.lastFace;
                        camera.lastFace = clipFace;
                        continue;
                    }
                    if (first != null) {
                        last.processNext = face;
                    } else {
                        first = face;
                    }
                    last = face;
                }
                tail = face;
            }
            if (last != null) last.processNext = null;
            
            
            
            if (first != null) {
                if (culling > 0) {
                    first = clipping == 2 ? camera.clip(first, culling) : camera.cull(first, culling);
                }
            }
            if (first != null) {
                vg =  VG.create(this, first, 2, camera.debug ? camera.checkInDebug(this) : 0, false);
                //vg.calculateAABB(ima, imb, imc, imd, ime, imf, img, imh, imi, imj, imk, iml);
                /*
                vg.boundMinX = cd.xorg;
                vg.boundMinY = cd.zorg;
                vg.boundMinZ = cd.Square.MinY;
                vg.boundMaxX = cd.xorg + (1 << cd.Level);
                vg.boundMaxY = cd.zorg + (1 << cd.Level);
                vg.boundMaxZ = cd.Square.MaxY;
                */
                
                vg.next = geometry;
                geometry = vg;
                drawConflictGeometry(camera, canvas, geometry);
            }
            else {
                if (geometry.next != null) drawAABBGeometry(camera, canvas, geometry);
                else {
                    geometry.draw(camera, canvas, threshold, this);
                    geometry.destroy();
                }
            }
    

            ///*
            tail.next = triCollector;
            triCollector = head;
            //*/
            
        }
        //*/
        
        private final function drawQuad(cd:QuadCornerData, culling:int, camera:Camera3D, canvas:Canvas, geometry:VG):void {
        
            var sq:QuadSquare = cd.Square;    
            var Child:Vector.<QuadSquare> = sq.Child;    
            var EnabledFlags:int = sq.EnabledFlags;
            var pcd:QuadCornerData;
            var child:QuadSquare;
            var q:QuadCornerData;
            var index:int;
            var half:int = 1 << cd.Level;
            var full:int = half << 1;
            var orderedIndices:Vector.<int>;
            
            // get world texture tile index coordinates
            _worldTextureMinX = ((cd.xorg - worldMinX) >> xWorldShift);
            _worldTextureMinY = ((cd.zorg - worldMinY) >> xWorldShift); 
            _worldTexture = worldMats[(_worldTextureMinY << worldShift) + _worldTextureMinX];
            // save out starting x/y values of world texture
            _worldTextureMinX <<= xWorldShift; 
            _worldTextureMinY <<= xWorldShift;
            
//
            //_worldTexture = worldMats[0];// worldMats[ ( ((cd.zorg - worldMinY) >> xWorldShift) << worldShift) + ( ((cd.xorg - worldMinX) >> xWorldShift))];
            
        
             // case all disabled early out: 
             // all 4 children are disabled so this is a leaf to draw with any conflict geometry
            // Don't need to split conflict geometry anymore, if any.
            if ( !(EnabledFlags & 255) ) { 
                rd = QuadRenderData.create();
                rd.cd = cd;
                rd.setupForRendering(this, camera, boundMinX, boundMinY, _invWorldTxDist, _waterPlane);
                if (geometry == null) drawTri4(rd, culling, camera, canvas)
                else drawTri4Conflict(rd, culling, camera, canvas, geometry);
                
                QuadRenderData.BI--;
                rd.cd = null;
                return;
            }
                
            // case:  final top-down gather and draw with any conflict geometry
            // Don't need to split conflict geometry anymore, if any.
        //    /*
            if (cd.Level == 9) {  // 1024x1024 tile to handle 512x512 (2^9 child tiles)
                
               if (geometry == null) 
                drawQuadMesh(cd, culling, camera, canvas);
               else 
                drawVGQuadMesh(cd, culling, camera, canvas, geometry )
              
               return;
            }
        //    */
            

            
            // Else case 4: some children are enabled while some are not, above leaf level, so keep prepare
            //
            //determine z order
            var halfX:int = cd.xorg + half;
            var halfY:int = cd.zorg + half;

            index = 0;
            index |= imd < halfX ? 1 : 0; 
            index |= imh < halfY ? 2 : 0; 
            orderedIndices = QUAD_ORDER[index];
            
            var quadOrder:QuadOrder = QuadOrder.create();
            var vgIndex:Vector.<VG> = quadOrder.vg;
        
            var CULLING_INDEX:Vector.<int> = quadOrder.culling; 
            child = Child[0]; 
            CULLING_INDEX[0] = (waterClipping && child.MaxY <= boundMinZ) ? -1 : culling == 0 ? 0 : myCullingInFrustum(culling, cd.xorg+half, cd.zorg, child.MinY, cd.xorg+full, cd.zorg+half, child.MaxY);        
            child = Child[1];
            CULLING_INDEX[1] = (waterClipping && child.MaxY <= boundMinZ) ? -1 :culling == 0 ? 0 : myCullingInFrustum(culling, cd.xorg, cd.zorg, child.MinY, cd.xorg+half, cd.zorg+half, child.MaxY);        
            child = Child[2];
            CULLING_INDEX[2] =(waterClipping && child.MaxY <= boundMinZ) ?  -1 :culling ==0 ? 0 : myCullingInFrustum(culling, cd.xorg, cd.zorg+half, child.MinY, cd.xorg+half, cd.zorg+full, child.MaxY);        
            child = Child[3];
            CULLING_INDEX[3] = (waterClipping && child.MaxY <= boundMinZ) ? -1 :culling == 0 ? 0 : myCullingInFrustum(culling, cd.xorg + half, cd.zorg + half, child.MinY, cd.xorg + full, cd.zorg + full, child.MaxY);    
            
            
            // split geometry into all 4 sectors if any
            var geom2:VG; var geom3:VG;
            var geom1:VG; var geom0:VG;
            
            
            var nextGeom:VG;
            
            var transitGeom:VG;
                while ( geometry != null) {
                    nextGeom = geometry.next;
                    
                    // test along x-axis first
                    if (geometry.boundMaxX <= halfX + threshold) {  // left half only
                        if (geometry.boundMaxY <= halfY + threshold) { // left-bottom half left only
                            geometry.next = geom1;
                            geom1 = geometry;
                        }
                        else if (geometry.boundMinY >= halfY - threshold) { // left-top half left only
                            geometry.next = geom2;
                            geom2 = geometry;
                        }
                        else {  // to split at left-half along y

                            
                                
                                    geometry.split(camera, 0, 1, 0, halfY, threshold);
                                    if (geometry.next != null) {
                                        geometry.next.next = geom1;
                                        geom1 = geometry.next;
                                    }    
                                    if (geometry.faceStruct != null) {
                                        geometry.next = geom2;
                                        geom2 = geometry;
                                    } else {
                                        geometry.destroy();
                                    }
    
                        }
                    }
                    else if (geometry.boundMinX >= halfX - threshold) { // right half only
                        if (geometry.boundMaxY <= halfY + threshold) { // right-bottom half left only
                            geometry.next = geom0;
                            geom0 = geometry;
                        }
                        else if (geometry.boundMinY >= halfY - threshold) { // right-top half left only
                            geometry.next = geom3;
                            geom3 = geometry;
                        }
                        else { // to split crop at right-half along y
                            
                            
                                
                                    geometry.split(camera, 0, 1, 0, halfY, threshold);
                                    if (geometry.next != null) {
                                        geometry.next.next = geom0;
                                        geom0 = geometry.next;
                                    }    
                                    if (geometry.faceStruct != null) {
                                        geometry.next = geom3;
                                        geom3 = geometry;
                                    } else {
                                        geometry.destroy();
                                    }
                                
                                
                            }
                    } // definitely split/crop along x axis at least
                    else if (geometry.boundMaxY <= halfY + threshold) { // to split at bottom half
        
                        
                            
                                geometry.split(camera, 1, 0, 0, halfX, threshold);
                                if (geometry.next != null) {
                                    geometry.next.next = geom1;
                                    geom1 = geometry.next;
                                }    
                                if (geometry.faceStruct != null) {
                                    geometry.next = geom0;
                                    geom0 = geometry;
                                } else {
                                    geometry.destroy();
                                }
                            
                    }
                    else if (geometry.boundMinY >= halfY - threshold) { // to split at top half
                        
                        
                            
                                geometry.split(camera, 1, 0, 0, halfX, threshold);
                                if (geometry.next != null) {
                                    geometry.next.next = geom2;
                                    geom2 = geometry.next;
                                }    
                                if (geometry.faceStruct != null) {
                                    geometry.next = geom3;
                                    geom3 = geometry;
                                } else {
                                    geometry.destroy();
                                }
                        
                    }
                    else {  // to split/crop along all fours (stupid quad tree!)
                        ///*
                        // split along x first
                                geometry.split(camera, 1, 0, 0, halfX, threshold);
                                if ( (transitGeom = geometry.next) != null) {
                                    
                                    
                                        transitGeom.split(camera, 0, 1, 0, halfY, threshold);
                                        if (transitGeom.next != null) {
                                            transitGeom.next.next = geom1;
                                            geom1 = transitGeom.next;
                                        }    
                                        if (transitGeom.faceStruct != null) {
                                            transitGeom.next = geom2;
                                            geom2 = transitGeom;
                                        } else {
                                            transitGeom.destroy();
                                        }
                                    
                                    
                                }    
                                if ( (geometry.faceStruct) != null) {
                                    geometry.next = null;
                                
                                    
                                        geometry.split(camera, 0, 1, 0, halfY, threshold);
                                        if (geometry.next != null) {
                                            geometry.next.next = geom0;
                                            geom0 = geometry.next;
                                        }    
                                        if (geometry.faceStruct != null) {
                                            geometry.next = geom3;
                                            geom3 = geometry;
                                        } else {
                                            geometry.destroy();
                                        }
                                    
                                    
                                } else {
                                    geometry.destroy();
                                }
                            
                
                        
                        //*/
                    
                        
                    }
                    
                    
                    
                    geometry = nextGeom;
                }
            

            vgIndex[0] = geom0;
            vgIndex[1] = geom1;
            vgIndex[2] = geom2;
            vgIndex[3] = geom3;
            
            // if all 4 children aren't enabled, than clip leaves will be drawn
            if ( (EnabledFlags>>4) ^ (EnabledFlags & 15)  ) {
                var rd:QuadRenderData = QuadRenderData.create();
                rd.cd = cd;
                rd.setupForRendering(this, camera, boundMinX, boundMinY, _invWorldTxDist, _waterPlane);
            }
            
            index = orderedIndices[0];
            if ( (culling = CULLING_INDEX[index]) >= 0 ) {
                if ( EnabledFlags & (16<< index) ) {
                    sq.SetupCornerData(q = QuadCornerData.create(), cd, index);
                    drawQuad( q, culling, camera, canvas, vgIndex[index]);
                }
                else {
                    if ( (geometry = vgIndex[index]) == null) drawClipLeaf(rd, index, culling, camera, canvas);
                    else drawClipLeafConflict(rd, index, culling, camera, canvas, geometry);
                }
            }
            else if ( (geometry = vgIndex[index]) != null) {
                if (geometry.next != null) drawAABBGeometry(camera, canvas, geometry)
                else {
                    geometry.draw(camera, canvas, threshold, this);
                    geometry.destroy();
                }
            }
            
            index = orderedIndices[1];
            if ( (culling = CULLING_INDEX[index]) >= 0 ) {
                if ( EnabledFlags & (16<< index)) {
                    sq.SetupCornerData(q = QuadCornerData.create(), cd, index);
                    drawQuad( q, culling, camera, canvas, vgIndex[index]);
                }
                else {
                    if ( (geometry = vgIndex[index]) == null) drawClipLeaf(rd, index, culling, camera, canvas);
                    else drawClipLeafConflict(rd, index, culling, camera, canvas, geometry);
                }
            }
            else if ( (geometry = vgIndex[index]) != null) {
                if (geometry.next != null) drawAABBGeometry(camera, canvas, geometry)
                else {
                    geometry.draw(camera, canvas, threshold, this);
                    geometry.destroy();
                }
            }
            
            index = orderedIndices[2];
            if ( (culling = CULLING_INDEX[index]) >= 0 ) {
                if ( EnabledFlags & (16<< index) ) {
                    sq.SetupCornerData(q = QuadCornerData.create(), cd, index);
                    drawQuad( q, culling, camera, canvas, vgIndex[index]);
                }
                else {
                    if (   (geometry = vgIndex[index]) == null) drawClipLeaf(rd, index, culling, camera, canvas);
                    else drawClipLeafConflict(rd, index, culling, camera, canvas, geometry);
                }
            }
            else if ( (geometry = vgIndex[index]) != null) {
                if (geometry.next != null) drawAABBGeometry(camera, canvas, geometry)
                else {
                    geometry.draw(camera, canvas, threshold, this);
                    geometry.destroy();
                }
            }
            
            index = orderedIndices[3];
            if ( (culling = CULLING_INDEX[index]) >= 0 ) {
                if ( EnabledFlags & (16<< index) ) {
                    sq.SetupCornerData(q = QuadCornerData.create(), cd, index);
                    drawQuad( q, culling, camera, canvas, vgIndex[index]);
                }
                else {
                    if (   (geometry = vgIndex[index]) == null) drawClipLeaf(rd, index, culling, camera, canvas);
                    else drawClipLeafConflict(rd, index, culling, camera, canvas, geometry);
                }
            }
            else if ( (geometry = vgIndex[index]) != null) {
                if (geometry.next != null) drawAABBGeometry(camera, canvas, geometry)
                else {
                    geometry.draw(camera, canvas, threshold, this);
                    geometry.destroy();
                }
            }
            
            if (rd != null) {
                QuadRenderData.BI--;
                rd.cd = null;
            }
        }
        
        private final function drawTri4Conflict(rd:QuadRenderData, culling:int, camera:Camera3D, canvas:Canvas, geometry:VG):void 
        {
            var vg:VG;
    
            var f:Face = tri(rd.vertexList, 0, 4, 2);
            var head:Face = f;
            f.next = f = tri(rd.vertexList, 0, 6, 4);
            f.next = f = tri(rd.vertexList, 0, 8, 6);
            f.next = f = tri(rd.vertexList, 0, 2, 8);
            
            var faceList:Face = prepareFaces(head, camera);
            if (culling > 0) {
                faceList = clipping == 2 ? camera.clip(faceList, culling) : camera.cull(faceList, culling);
            }
            if (faceList != null) {
                vg = VG.create(this, faceList, 2, camera.debug ? camera.checkInDebug(this) : 0, false);
                vg.next = geometry;
                geometry = vg;
                drawConflictGeometry(camera, canvas, geometry);
            }
            else {
                if (geometry.next != null) drawConflictGeometry(camera, canvas, geometry);
                else {
                    geometry.draw(camera, canvas, threshold, this);
                    geometry.destroy();
                }
            }
            
            
            
            f.next = triCollector;
            triCollector = head;
        }
        
        private function collectClipLeaf(collector:Face, index:int, rd:QuadRenderData):Face {
            var EnabledFlags:int = rd.cd.Square.EnabledFlags;
        
            if (index == 0) {    // negative
                collector = collector.next =  (EnabledFlags & 1) == 0 ? triclip(rd.vertexList, 0, 2, 8, 1, index) : 
                                                 tri(rd.vertexList,0, 2, 1);
                
                
                collector = collector.next = (EnabledFlags & 2) == 0 ?  triclip(rd.vertexList,0, 4, 2, 3, index) :
                                                tri(rd.vertexList, 0, 3, 2);
            }
            else if (index == 1) {  // positive
                collector = collector.next = (EnabledFlags & 2) == 0 ?  triclip(rd.vertexList,0, 4, 2, 3, index) : 
                                        tri(rd.vertexList,0, 4, 3);
                
                collector = collector.next = (EnabledFlags & 4) == 0 ? triclip(rd.vertexList,0, 6, 4, 5, index) :
                                        tri(rd.vertexList,0, 5, 4);
            }
            else if (index == 2) {  //negative
                collector = collector.next =  (EnabledFlags & 4) == 0 ? triclip(rd.vertexList,0, 6, 4, 5, index) :  
                            tri(rd.vertexList, 0, 6, 5);
                    
                collector = collector.next =  (EnabledFlags & 8) == 0 ? triclip(rd.vertexList,0, 8, 6, 7, index) :
                            tri(rd.vertexList,0, 7, 6);
            
            }
            else   {  //positive  // index == 3
                collector = collector.next =  (EnabledFlags & 8) == 0 ?  triclip(rd.vertexList,0,8,6,7,index) :
                    tri(rd.vertexList,0, 8, 7);
                collector = collector.next =  (EnabledFlags & 1) == 0 ?  triclip(rd.vertexList,0, 2, 8,1,index) :
                    tri(rd.vertexList,0, 1, 8); 
            }
            
            return collector;

        }
        
        private function drawClipLeafConflict(rd:QuadRenderData, index:int, culling:int, camera:Camera3D, canvas:Canvas, geometry:VG):void {
var vg:VG;
        
            var EnabledFlags:int = rd.cd.Square.EnabledFlags;
            var f:Face;
            if (index == 0) {    // negative
                f =  (EnabledFlags & 1) == 0 ? triclip(rd.vertexList, 0, 2, 8, 1, index) : 
                                                 tri(rd.vertexList,0, 2, 1);
                
                
                f.next = (EnabledFlags & 2) == 0 ?  triclip(rd.vertexList,0, 4, 2, 3, index) :
                                                tri(rd.vertexList, 0, 3, 2);
            }
            else if (index == 1) {  // positive
                f = (EnabledFlags & 2) == 0 ?  triclip(rd.vertexList,0, 4, 2, 3, index) : 
                                        tri(rd.vertexList,0, 4, 3);
                
                f.next = (EnabledFlags & 4) == 0 ? triclip(rd.vertexList,0, 6, 4, 5, index) :
                                        tri(rd.vertexList,0, 5, 4);
            }
            else if (index == 2) {  //negative
                f =  (EnabledFlags & 4) == 0 ? triclip(rd.vertexList,0, 6, 4, 5, index) :  
                            tri(rd.vertexList, 0, 6, 5);
                    
                f.next =  (EnabledFlags & 8) == 0 ? triclip(rd.vertexList,0, 8, 6, 7, index) :
                            tri(rd.vertexList,0, 7, 6);
            
            }
            else   {  //positive  // index == 3
                f =  (EnabledFlags & 8) == 0 ?  triclip(rd.vertexList,0,8,6,7,index) :
                    tri(rd.vertexList,0, 8, 7);
                f.next =  (EnabledFlags & 1) == 0 ?  triclip(rd.vertexList,0, 2, 8,1,index) :
                    tri(rd.vertexList,0, 1, 8); 
            }
            
            // below methods are dumb for just drawing 2 faces, should only use for VG??
            var faceList:Face = prepareFaces(f, camera);
            if (culling > 0) {
                faceList = clipping == 2 ? camera.clip(faceList, culling) : camera.cull(faceList, culling);
            }
            if (faceList != null) {
                
                vg = VG.create(this, faceList, 2, camera.debug ? camera.checkInDebug(this) : 0, false);
                vg.next = geometry;
                geometry = vg;
                drawConflictGeometry(camera, canvas, geometry);
            }
            else {
                if (geometry.next != null) drawConflictGeometry(camera, canvas, geometry);
                else {
                    geometry.draw(camera, canvas, threshold, this);
                    geometry.destroy();
                }
            }
            
        
            
            f.next.next = triCollector;
            triCollector = f;
        }
        
        private function drawClipLeaf(rd:QuadRenderData, index:int, culling:int, camera:Camera3D, canvas:Canvas):void {

            var EnabledFlags:int = rd.cd.Square.EnabledFlags;
            var f:Face;
            if (index == 0) {    // negative
                f =  (EnabledFlags & 1) == 0 ? triclip(rd.vertexList, 0, 2, 8, 1, index) : 
                                                 tri(rd.vertexList,0, 2, 1);
                
                
                f.next = (EnabledFlags & 2) == 0 ?  triclip(rd.vertexList,0, 4, 2, 3, index) :
                                                tri(rd.vertexList, 0, 3, 2);
            }
            else if (index == 1) {  // positive
                f = (EnabledFlags & 2) == 0 ?  triclip(rd.vertexList,0, 4, 2, 3, index) : 
                                        tri(rd.vertexList,0, 4, 3);
                
                f.next = (EnabledFlags & 4) == 0 ? triclip(rd.vertexList,0, 6, 4, 5, index) :
                                        tri(rd.vertexList,0, 5, 4);
            }
            else if (index == 2) {  //negative
                f =  (EnabledFlags & 4) == 0 ? triclip(rd.vertexList,0, 6, 4, 5, index) :  
                            tri(rd.vertexList, 0, 6, 5);
                    
                f.next =  (EnabledFlags & 8) == 0 ? triclip(rd.vertexList,0, 8, 6, 7, index) :
                            tri(rd.vertexList,0, 7, 6);
            
            }
            else   {  //positive  // index == 3
                f =  (EnabledFlags & 8) == 0 ?  triclip(rd.vertexList,0,8,6,7,index) :
                    tri(rd.vertexList,0, 8, 7);
                f.next =  (EnabledFlags & 1) == 0 ?  triclip(rd.vertexList,0, 2, 8,1,index) :
                    tri(rd.vertexList,0, 1, 8); 
            }
            
            // below methods are dumb for just drawing 2 faces, should only use for VG??
            var faceList:Face = prepareFaces(f, camera);
            if (culling > 0) {
                faceList = clipping == 2 ? camera.clip(faceList, culling) : camera.cull(faceList, culling);
            }
            if (faceList != null) drawFaces(camera, canvas, faceList);
            
            f.next.next = triCollector;
            triCollector = f;
            
        }

        private function calculateFrustumPlanes(camera:Camera3D):void {
            /**
             * Calculates camera frustum planes in local coordinate space with current inverse matrix, which 
             * is the (projected camera -> local space) transform. 
             * 
             * Coeffecients -1/+1 before (ima, imb)  (ime, imf) (imi, imj)
             * are the standard normalized device coordinates of camera(X,Y) ratios respectively. 
             * 
             * For example, (-1,-1) is the top left corner, while (1,-1) is the top right corner. 
             * (0,-1) is the mid point of the top edge of the screen.
             * (0,0) is the center of the screen.
             * Coeffecient floating point values in between -1 to 1 represent the normalized position of a
             * particular 2d position on the camera screen.
             * 
             * This is actually the fast way to determine any world/local-space ray direction from any given on-screen point. 
             * (eg. camera.calculateRay())
             * 
             * For the frustum, by determining the ray directions towards the corners of the screen for each edge (top, right, bottom, left) 
             * (or in some cases, corners of a smaller screen AABB that can be used for coarse portal culling),
             * a cross product of the 2 rays towards the corners along each screen edge is simply used to get the camera plane normals,
             * and than a dot product of the current camera position in local space (imd, imh, iml), to get the plane offset.
             */
            
            // Near  
            nearX = imc;
            nearY = img;
            nearZ = imk;
            nearOffset = (imc*camera.nearClipping + imd)*nearX + (img*camera.nearClipping + imh)*nearY + (imk*camera.nearClipping + iml)*nearZ;
            nearXHigh = nearX >= 0; nearYHigh = nearY >= 0; nearZHigh = nearZ >= 0;
            
            // Far   
            farX = -imc;
            farY = -img;
            farZ = -imk;
            farOffset = (imc*camera.farClipping + imd)*farX + (img*camera.farClipping + imh)*farY + (imk*camera.farClipping + iml)*farZ;
            farXHigh = farX >= 0; farYHigh = farY >= 0; farZHigh = farZ >= 0;
            
            // Top
            var ax:Number = -ima - imb + imc;
            var ay:Number = -ime - imf + img;
            var az:Number = -imi - imj + imk;
            var bx:Number = ima - imb + imc;
            var by:Number = ime - imf + img;
            var bz:Number = imi - imj + imk;
            topX = bz*ay - by*az;
            topY = bx*az - bz*ax;
            topZ = by*ax - bx*ay;
            topOffset = imd * topX + imh * topY + iml * topZ;
            topXHigh = topX >= 0; topYHigh = topY >= 0; topZHigh = topZ >= 0;
            
            // Right
            ax = bx;
            ay = by;
            az = bz;
            bx = ima + imb + imc;
            by = ime + imf + img;
            bz = imi + imj + imk;
            rightX = bz*ay - by*az;
            rightY = bx*az - bz*ax;
            rightZ = by*ax - bx*ay;
            rightOffset = imd * rightX + imh * rightY + iml * rightZ;
            rightXHigh = rightX >= 0; rightYHigh = rightY >= 0; rightZHigh = rightZ >= 0;
            
            // Bottom
            ax = bx;
            ay = by;
            az = bz;
            bx = -ima + imb + imc;
            by = -ime + imf + img;
            bz = -imi + imj + imk;
            bottomX = bz*ay - by*az;
            bottomY = bx*az - bz*ax;
            bottomZ = by*ax - bx*ay;
            bottomOffset = imd*bottomX + imh*bottomY + iml*bottomZ;
            bottomXHigh = bottomX >= 0; bottomYHigh = bottomY >= 0; bottomZHigh = bottomZ >= 0;
            
            // Left
            ax = bx;
            ay = by;
            az = bz;
            bx = -ima - imb + imc;
            by = -ime - imf + img;
            bz = -imi - imj + imk;
            leftX = bz*ay - by*az;
            leftY = bx*az - bz*ax;
            leftZ = by*ax - bx*ay;
            leftOffset = imd * leftX + imh * leftY + iml * leftZ;
            leftXHigh = leftX >= 0; leftYHigh = leftY >= 0; leftZHigh = leftZ >= 0;
        }
        
        // For rendering of one's own quads
        private final function myCullingInFrustum(culling:int, boundMinX:Number, boundMinY:Number, boundMinZ:Number, boundMaxX:Number, boundMaxY:Number, boundMaxZ:Number):int {
            
            if (culling & 1) { // near
                if ( (nearXHigh ? boundMaxX : boundMinX )*nearX + (nearYHigh ? boundMaxY : boundMinY)*nearY + (nearZHigh ? boundMaxZ : boundMinZ)*nearZ <= nearOffset) return -1;
                if ( (nearXHigh ? boundMinX : boundMaxX )*nearX + (nearYHigh ? boundMinY : boundMaxY)*nearY + (nearZHigh ? boundMinZ : boundMaxZ)*nearZ > nearOffset) culling &= 62;
            }
                
            if (culling & 2) { // far
                if ( (farXHigh ? boundMaxX : boundMinX )*farX + (farYHigh ? boundMaxY : boundMinY)*farY + (farZHigh ? boundMaxZ : boundMinZ)*farZ <= farOffset) return -1;
                if ( (farXHigh ? boundMinX : boundMaxX )*farX + (farYHigh ? boundMinY : boundMaxY)*farY + (farZHigh ? boundMinZ : boundMaxZ)*farZ > farOffset) culling &= 61;
            }
                
            if (culling & 4) { // left
                if ( (leftXHigh ? boundMaxX : boundMinX )*leftX + (leftYHigh ? boundMaxY : boundMinY)*leftY + (leftZHigh ? boundMaxZ : boundMinZ)*leftZ <= leftOffset) return -1;
                if ( (leftXHigh ? boundMinX : boundMaxX )*leftX + (leftYHigh ? boundMinY : boundMaxY)*leftY + (leftZHigh ? boundMinZ : boundMaxZ)*leftZ > leftOffset) culling &= 59;
            }
            
            if (culling & 8) { // right
                if ( (rightXHigh ? boundMaxX : boundMinX )*rightX + (rightYHigh ? boundMaxY : boundMinY)*rightY + (rightZHigh ? boundMaxZ : boundMinZ)*rightZ <= rightOffset) return -1;
                if ( (rightXHigh ? boundMinX : boundMaxX )*rightX + (rightYHigh ? boundMinY : boundMaxY)*rightY + (rightZHigh ? boundMinZ : boundMaxZ)*rightZ > rightOffset) culling &= 55;
            }

            if (culling & 16) { // top
                if ( (topXHigh ? boundMaxX : boundMinX )*topX + (topYHigh ? boundMaxY : boundMinY)*topY + (topZHigh ? boundMaxZ : boundMinZ)*topZ <= topOffset) return -1;
                if ( (topXHigh ? boundMinX : boundMaxX )*topX + (topYHigh ? boundMinY : boundMaxY)*topY + (topZHigh ? boundMinZ : boundMaxZ)*topZ > topOffset) culling &= 47;
            }
            
            if (culling & 32) { // bottom
                if ( (bottomXHigh ? boundMaxX : boundMinX )*bottomX + (bottomYHigh ? boundMaxY : boundMinY)*bottomY + (bottomZHigh ? boundMaxZ : boundMinZ)*bottomZ <= bottomOffset) return -1;
                if ( (bottomXHigh ? boundMinX : boundMaxX )*bottomX + (bottomYHigh ? boundMinY : boundMaxY)*bottomY + (bottomZHigh ? boundMinZ : boundMaxZ)*bottomZ > bottomOffset) culling &= 31;
            }
            
            return culling;
        }
        
        /* INTERFACE utils.terrain.ICuller */
        
        // For updating of one's own quads
        public function cullingInFrustum(culling:int, boundMinX:Number, boundMinY:Number, boundMinZ:Number, boundMaxX:Number, boundMaxY:Number, boundMaxZ:Number):int {
            
            if (waterClipping ) {
                if (boundMaxZ <= waterLevel) return -1;
                if ( !(culling & 63)  ) return 64;   // early-out for "already inside frustum" case
            }
            
            if (culling & 1) { // near
                if ( (nearXHigh ? boundMaxX : boundMinX )*nearX + (nearYHigh ? boundMaxY : boundMinY)*nearY + (nearZHigh ? boundMaxZ : boundMinZ)*nearZ <= nearOffset) return -1;
                if ( (nearXHigh ? boundMinX : boundMaxX )*nearX + (nearYHigh ? boundMinY : boundMaxY)*nearY + (nearZHigh ? boundMinZ : boundMaxZ)*nearZ > nearOffset) culling &= 62;
            }
                
            if (culling & 2) { // far
                if ( (farXHigh ? boundMaxX : boundMinX )*farX + (farYHigh ? boundMaxY : boundMinY)*farY + (farZHigh ? boundMaxZ : boundMinZ)*farZ <= farOffset) return -1;
                if ( (farXHigh ? boundMinX : boundMaxX )*farX + (farYHigh ? boundMinY : boundMaxY)*farY + (farZHigh ? boundMinZ : boundMaxZ)*farZ > farOffset) culling &= 61;
            }
                
            if (culling & 4) { // left
                if ( (leftXHigh ? boundMaxX : boundMinX )*leftX + (leftYHigh ? boundMaxY : boundMinY)*leftY + (leftZHigh ? boundMaxZ : boundMinZ)*leftZ <= leftOffset) return -1;
                if ( (leftXHigh ? boundMinX : boundMaxX )*leftX + (leftYHigh ? boundMinY : boundMaxY)*leftY + (leftZHigh ? boundMinZ : boundMaxZ)*leftZ > leftOffset) culling &= 59;
            }
            
            if (culling & 8) { // right
                if ( (rightXHigh ? boundMaxX : boundMinX )*rightX + (rightYHigh ? boundMaxY : boundMinY)*rightY + (rightZHigh ? boundMaxZ : boundMinZ)*rightZ <= rightOffset) return -1;
                if ( (rightXHigh ? boundMinX : boundMaxX )*rightX + (rightYHigh ? boundMinY : boundMaxY)*rightY + (rightZHigh ? boundMinZ : boundMaxZ)*rightZ > rightOffset) culling &= 55;
            }

            if (culling & 16) { // top
                if ( (topXHigh ? boundMaxX : boundMinX )*topX + (topYHigh ? boundMaxY : boundMinY)*topY + (topZHigh ? boundMaxZ : boundMinZ)*topZ <= topOffset) return -1;
                if ( (topXHigh ? boundMinX : boundMaxX )*topX + (topYHigh ? boundMinY : boundMaxY)*topY + (topZHigh ? boundMinZ : boundMaxZ)*topZ > topOffset) culling &= 47;
            }
            
            if (culling & 32) { // bottom
                if ( (bottomXHigh ? boundMaxX : boundMinX )*bottomX + (bottomYHigh ? boundMaxY : boundMinY)*bottomY + (bottomZHigh ? boundMaxZ : boundMinZ)*bottomZ <= bottomOffset) return -1;
                if ( (bottomXHigh ? boundMinX : boundMaxX )*bottomX + (bottomYHigh ? boundMinY : boundMaxY)*bottomY + (bottomZHigh ? boundMinZ : boundMaxZ)*bottomZ > bottomOffset) culling &= 31;
            }
            
            return culling;
        }
        
        // -- Misc utility methods
        
        /**
         * Creates a new clip face at the positive side of a given clip plane in camera space.
         * @param    face    An existing face
         * @param    normal    The clip plane normal
         * @param    offset    The clip plane offset to test against
         * @return    A new positive clip face
         */
        private final function newPositiveClipFace(face:Face, normal:Vector3D, offset:Number):Face  
        {
            // Prepare cloned face
            var clipFace:Face = face.create();
            clipFace.material = face.material;
            clipFace.offset = face.offset;
            clipFace.normalX = face.normalX;
            clipFace.normalY = face.normalY;
            clipFace.normalZ = face.normalZ;

            
            // deepCloneWrapper() inline
            var wrapper:Wrapper = face.wrapper;
            var wrapperClone:Wrapper = wrapper.create();
            wrapperClone.vertex = wrapper.vertex;

            var w:Wrapper = wrapper.next;
            var tailWrapper:Wrapper = wrapperClone;
            var wClone:Wrapper;
            while (w != null) {
                wClone = w.create();
                wClone.vertex = w.vertex;
                tailWrapper.next = wClone;
                tailWrapper = wClone;
                w = w.next;
            }
            
            // get new wrapper of clipped vertices
            clipFace.wrapper =  getClippedVerticesForFace(face, normal, offset, tailWrapper, wrapperClone);
            return clipFace;
        }
        
        /**
         * Modifies wrapper vertex ordering according to clip plane in camera space
         * @param    face
         * @param    normal     
         * @param    offset                
         * @param    tailWrapper        Tail of vertex list to compare from at the beginning of iteration
         * @param    wrapperClone    Header of vertex list to start iterating from
         * @return  A wrapper list of vertices containing the clipped vertices
         */
        public static function getClippedVerticesForFace(face:Face, normal:Vector3D, offset:Number, tailWrapper:Wrapper, wrapperClone:Wrapper):Wrapper {
            // continue with clipping (iterate through all vertices)
            var nextWrapper:Wrapper;
            var headWrapper:Wrapper;     // the very first valid vertex wrapper found
            
            var w:Wrapper, wClone:Wrapper;
            
            var a:Vertex = tailWrapper.vertex;     
            var ao:Number = a.offset;              
            var bo:Number;                         
            var b:Vertex;                           
            var ratio:Number;
            var v:Vertex;                        // new created vertex
            
            for (w = wrapperClone; w != null; w = nextWrapper) {
                nextWrapper = w.next;
                b = w.vertex;
                bo = b.offset;
                
                if (bo > offset && ao <= offset || bo <= offset && ao > offset) { // diff plane sides
                    v = b.create();
                    //this.lastVertex.next = v;
                    //this.lastVertex = v;
                                
                    ratio = (offset - ao)/(bo - ao);
                    v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*ratio;
                    v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*ratio;
                    v.cameraZ = a.cameraZ + (b.cameraZ - a.cameraZ)*ratio; 
                    v.x = a.x + (b.x - a.x)*ratio;
                    v.y = a.y + (b.y - a.y)*ratio;
                    v.z = a.z + (b.z - a.z)*ratio;
                    v.u = a.u + (b.u - a.u)*ratio;
                    v.v = a.v + (b.v - a.v) * ratio;
                    //v.offset = a.offset + (b.offset - a.offset) * ratio;
                                
                    wClone = w.create();
                    wClone.vertex = v;
                    if (headWrapper != null) tailWrapper.next = wClone; 
                    else headWrapper = wClone;
                    tailWrapper = wClone;
                }
                
                if (bo > offset) { // current beta w should be appended as well as it's going from back to front
                    if (headWrapper != null) tailWrapper.next = w; 
                    else headWrapper = w;
                    tailWrapper = w;
                    w.next = null;
                } 
                else {        // current beta w should be culled as it's going from front to back.
                    w.vertex = null;    
                    w.next = Wrapper.collector;
                    Wrapper.collector = w;
                }
                
                a = b;        // set tail alpha to current beta before continuing
                ao = bo;
            }
            
            return headWrapper;
        }
        
        /* INTERFACE INeighbourQuad */
        
        public function getQuad():QuadCornerDataNeighbor 
        {
            return rootQuad as QuadCornerDataNeighbor;
        }
        
    }



    
    
    

    import alternativ7.engine3d.alternativa3d;
    import alternativ7.engine3d.materials.TextureMaterial;
    import flash.display.BitmapData;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    import flash.utils.Dictionary;
    use namespace alternativa3d;
    
    /**
     * A square blueprint of precomputed values and storage of world tileset/tiledata,
     * or just world textures. 
     * Also contains helper methods for both terrain texture editor and game.
     * 
     * @author Glenn Ko
     */
    class WorldModel
    {
        public static const DUMMY_TEXTURE_MATERIAL:TextureMaterial = new TextureMaterial( new BitmapData(1, 1, false, 0x33DDBB) );
        
        public static const TILESIZE:int = 256;
        public static const TILESIZE_SHIFT:int = Math.log(TILESIZE + .01) * Math.LOG2E;
        
        alternativa3d var minX:int;
        alternativa3d var minY:int;
        alternativa3d var size:int;
        alternativa3d var matSize:int;
        
        alternativa3d var tileSet:Vector.<TextureMaterial>;
        alternativa3d var worldMats:Vector.<TextureMaterial>;
        alternativa3d var tileMap:Vector.<uint>;
        alternativa3d var uvTileMap:Vector.<uint>;
        alternativa3d var matsAcross:int;
        
        alternativa3d var quadShift:int; 
        alternativa3d var quadMod:int;   

        alternativa3d var worldShift:int;
        alternativa3d var worldMod:int;
        alternativa3d var xWorldShift:int;
        alternativa3d var xWorldMod:int;

        alternativa3d var tilesShift:int;
        alternativa3d var tilesMod:int;
        
        alternativa3d var worldMatXTiles:int;
        alternativa3d var totalWorldXTiles:int;
        
        public var worldTextureDistance:int;
        
        private static const TILE_RECT_SML:Rectangle = new Rectangle(0, 0, 32, 32);  //*res
        private static const DRAW_PT:Point = new Point();
        
        /**
         * Both Painter & Game/Viewer mode parameters:
         * @param    minX    The starting world x position of the terrain to be textured
         * @param    minY    The starting world y position  of the terrain to be textured
         * @param    totalWorldXTiles The number of tiles across for the entire world in both x/y directions. Value be base2.
         * @param    worldMatXTiles    The number of tiles across each world texture tile in both x/y directions.
         *             Set this to a base2 divisible value of totalWorldXTiles in order to implicitly define the total number
         *             of world material slots provided for the entire world.
         * 
         * Game/Viewer mode parameters only:
         * @param    tileSet          Required for Game/Viewer mode, the tileset reference (if any), containing all used texture combinations.
         * @param    loadedTileMap  Required for Game/Viewer mode.  A loaded tileMap must be supplied as well.
         * @param    loadedUVTileMap  Required for Game/Viewer mode.  A loaded UV tileMap must be supplied as well.
         */
        public function WorldModel(minX:int, minY:int, totalWorldXTiles:int, worldMatXTiles:int, tileSet:Vector.<TextureMaterial>=null, loadedTileMap:Vector.<uint>=null, loadedUVTileMap:Vector.<uint>=null) {
        
            if (!isBase2(totalWorldXTiles) ) throw new Error("totalWorldXTiles is not a base2 number!");
            if (!isBase2(worldMatXTiles) ) throw new Error("worldMatXTiles is not a base2 number!");
            
            //if (worldMatXTiles > 64) throw new Error("World material x tiles exceed 64 tiles!");
            
            if (totalWorldXTiles < worldMatXTiles) throw new Error("worldMatXTiles lower than totalWorldXTiles");
            
            if ( (matsAcross = int(totalWorldXTiles / worldMatXTiles)) != Number(totalWorldXTiles / worldMatXTiles) ) {
                throw new Error("totalWorldXTiles not divisible by worldMatXTiles!");
            }
            if (!isBase2(matsAcross)) throw new Error("Number of worldMats across must be base 2!");
            

            this.minY = minY;
            this.minX = minX;
            this.totalWorldXTiles = totalWorldXTiles
            this.worldMatXTiles = worldMatXTiles;
            worldTextureDistance = worldMatXTiles * 256;
            
            size = totalWorldXTiles >> 1;
            matSize = size / matsAcross;
            
            
            worldMats = new Vector.<TextureMaterial>(matsAcross * matsAcross, true);
            
            quadShift = Math.log(size + .01) * Math.LOG2E;  
            quadMod = (1<<quadShift) - 1;
            
            
            worldShift = Math.log(matsAcross + .01) * Math.LOG2E; 
            worldMod = (1 << worldShift) - 1;
            xWorldShift = Math.log(worldMatXTiles * TILESIZE + .01) * Math.LOG2E;
            xWorldMod = (1 << xWorldShift) - 1;
        
            tilesShift = Math.log(totalWorldXTiles + .01) * Math.LOG2E; 
            tilesMod = (1<<tilesShift) - 1;
            
            this.tileSet = tileSet;
            if (tileSet != null) {    // Game mode
                if (loadedTileMap == null) throw new Error("loadedTileMap not provided! Required for game mode!");
                if (loadedUVTileMap == null) throw new Error("loadedUVTileMap not provided! Required for game mode!");
                tileMap = loadedTileMap;
                uvTileMap = loadedUVTileMap;
                // todo: world materials
            }
            else {  // Builder mode
                tileMap = new Vector.<uint>(size * size, true);
                var i:int = worldMats.length;
                while (--i > -1) {
                    worldMats[i] = DUMMY_TEXTURE_MATERIAL.clone() as TextureMaterial;
                }
            }
            
        }
        
        public function setWorldMaterialAtIndex(index:int, mat:TextureMaterial):void {
            worldMats[index] = mat;
        }
        
        
        
    
        
        public function blitToWorldMap(applyTexture:BitmapData, xPosOffset:int, yPosOffset:int):TextureMaterial {

            
            var index:int = ( (yPosOffset>>xWorldShift) << worldShift) +  ( (xPosOffset>>xWorldShift));
            var worldMat:TextureMaterial = worldMats[index];



            xPosOffset &= xWorldMod;
            yPosOffset &= xWorldMod;
            
            
            DRAW_PT.x = (xPosOffset >> 8) *32;  //*res
            DRAW_PT.y = worldMat._texture.height - (yPosOffset >> 8) * 32- 32;  //*res
            
            worldMat._texture.copyPixels(applyTexture, TILE_RECT_SML, DRAW_PT );
            return worldMat;
        }
        
        
        public function isBase2(val:int):Boolean {
            return Math.pow(2, int(Math.log(val + .01) * Math.LOG2E)) == val;
        }
        
        public function resetWorldTextureDistance():void 
        {
            worldTextureDistance = worldMatXTiles * 256;
        }
        
        
    }

 class QuadRenderData
    {
        public var cd:QuadCornerData; // this could be passed as a dependency!
        public var transformId:int; // this not required!
        
        public var vertexList:Vector.<Vertex>;  // All 9 vertices to setup during leaf rendering
    
        public static var BUFFER:Vector.<QuadRenderData> = new Vector.<QuadRenderData>();
        public static var BI:int = 0;
        public static var BLEN:int = 0;
        
        public var minCamZ:Number;
        public var maxCamZ:Number;
    
        public static function create():QuadRenderData {
            var result:QuadRenderData;
        
            if (BI < BLEN) {
                result = BUFFER[BI];
            }
            else {
                result = new QuadRenderData();
                BUFFER[BLEN++] = result ;
            }
            BI++;
            return result;
        }
        
        public static function setFixedBufferSize(size:int):void {
            BUFFER.length = size;
            BUFFER.fixed = true;
            BLEN = size;
        }
        
        public function QuadRenderData() 
        {    
            var list:Vertex = Vertex.createList(9);
            vertexList = new Vector.<Vertex>(9, true);
            vertexList[0] = list; list = list.next;
            vertexList[1] = list; list = list.next;
            vertexList[2] = list; list = list.next;
            vertexList[3] = list; list = list.next;
            vertexList[4] = list; list = list.next;
            vertexList[5] = list; list = list.next;
            vertexList[6] = list; list = list.next;
            vertexList[7] = list; list = list.next;
            vertexList[8] = list; list = list.next;
            
            list = vertexList[1];
            list.value = list.create();
            
            list = vertexList[3];
            list.value = list.create();
            
            list = vertexList[5];
            list.value = list.create();
            
            list = vertexList[7];
            list.value = list.create();
            
            transformId = 0;
        }
        
        public final function setupVertices(cd:QuadCornerData):void {
            var xorg:int = cd.xorg;
            var zorg:int = cd.zorg;
            var Verts:Vector.<int> = cd.Verts;
            
            var vertexHeights:Vector.<int> = cd.Square.Vertex;
            
            // init vertex list
            var v:Vertex = vertexList[0];
            var half:int = 1 << cd.Level;
            var whole:int = half << 1;

            v.x =  xorg + half;  v.z = vertexHeights[0];  v.y = zorg + half;
            v = v.next;
            
            v.x = xorg + whole; v.z = vertexHeights[1]; v.y = zorg + half;
            v = v.next;
            
            v.x = xorg + whole; v.z = Verts[0]; v.y =  zorg;
            v = v.next;
            
            v.x = xorg + half; v.z =  vertexHeights[2]; v.y =  zorg;
            v = v.next;
            
            v.x = xorg; v.z = Verts[1]; v.y = zorg;
            v = v.next;
            
            v.x = xorg; v.z = vertexHeights[3]; v.y = zorg + half;
            v = v.next;
            
            v.x = xorg; v.z = Verts[2]; v.y = zorg + whole;
            v = v.next;
        
            v.x = xorg + half; v.z = vertexHeights[4]; v.y = zorg + whole;
            v = v.next;
            
            v.x = xorg + whole; v.z = Verts[3]; v.y = zorg + whole;
        }
        
        public final function setupVerticesForCollision(cd:QuadCornerData, transform:Object3D):void {
            var xorg:int = cd.xorg;
            var zorg:int = cd.zorg;
            var Verts:Vector.<int> = cd.Verts;
            
            var vertexHeights:Vector.<int> = cd.Square.Vertex;
            
            // init vertex list
            var v:Vertex = vertexList[0];
            var half:int = 1 << cd.Level;
            var whole:int = half << 1;

            v.x =  xorg + half;  v.z = vertexHeights[0];  v.y = zorg + half;
            v.cameraX = v.x * transform.ma + v.y * transform.mb + v.z * transform.mc + transform.md; 
            v.cameraY = v.x * transform.me + v.y * transform.mf + v.z * transform.mg + transform.mh; 
            v.cameraZ = v.x * transform.mi + v.y * transform.mj + v.z * transform.mk + transform.ml; 
            v.transformId = transform.transformId;
            v = v.next;
            
            v.x = xorg + whole; v.z = vertexHeights[1]; v.y = zorg + half;
            v.cameraX = v.x * transform.ma + v.y * transform.mb + v.z * transform.mc + transform.md; 
            v.cameraY = v.x * transform.me + v.y * transform.mf + v.z * transform.mg + transform.mh; 
            v.cameraZ = v.x * transform.mi + v.y * transform.mj + v.z * transform.mk + transform.ml; 
            v.transformId = transform.transformId;
            v = v.next;
            
            v.x = xorg + whole; v.z = Verts[0]; v.y =  zorg;
            v.cameraX = v.x * transform.ma + v.y * transform.mb + v.z * transform.mc + transform.md; 
            v.cameraY = v.x * transform.me + v.y * transform.mf + v.z * transform.mg + transform.mh; 
            v.cameraZ = v.x * transform.mi + v.y * transform.mj + v.z * transform.mk + transform.ml; 
            v.transformId = transform.transformId;
            v = v.next;
            
            v.x = xorg + half; v.z =  vertexHeights[2]; v.y =  zorg;
            v.cameraX = v.x * transform.ma + v.y * transform.mb + v.z * transform.mc + transform.md; 
            v.cameraY = v.x * transform.me + v.y * transform.mf + v.z * transform.mg + transform.mh; 
            v.cameraZ = v.x * transform.mi + v.y * transform.mj + v.z * transform.mk + transform.ml; 
            v.transformId = transform.transformId;
            v = v.next;
            
            v.x = xorg; v.z = Verts[1]; v.y = zorg;
            v.cameraX = v.x * transform.ma + v.y * transform.mb + v.z * transform.mc + transform.md; 
            v.cameraY = v.x * transform.me + v.y * transform.mf + v.z * transform.mg + transform.mh; 
            v.cameraZ = v.x * transform.mi + v.y * transform.mj + v.z * transform.mk + transform.ml; 
            v.transformId = transform.transformId;
            v = v.next;
            
            v.x = xorg; v.z = vertexHeights[3]; v.y = zorg + half;
            v.cameraX = v.x * transform.ma + v.y * transform.mb + v.z * transform.mc + transform.md; 
            v.cameraY = v.x * transform.me + v.y * transform.mf + v.z * transform.mg + transform.mh; 
            v.cameraZ = v.x * transform.mi + v.y * transform.mj + v.z * transform.mk + transform.ml; 
            v.transformId = transform.transformId;
            v = v.next;
            
            v.x = xorg; v.z = Verts[2]; v.y = zorg + whole;
            v.cameraX = v.x * transform.ma + v.y * transform.mb + v.z * transform.mc + transform.md; 
            v.cameraY = v.x * transform.me + v.y * transform.mf + v.z * transform.mg + transform.mh; 
            v.cameraZ = v.x * transform.mi + v.y * transform.mj + v.z * transform.mk + transform.ml; 
            v.transformId = transform.transformId;
            v = v.next;
        
            v.x = xorg + half; v.z = vertexHeights[4]; v.y = zorg + whole;
            v.cameraX = v.x * transform.ma + v.y * transform.mb + v.z * transform.mc + transform.md; 
            v.cameraY = v.x * transform.me + v.y * transform.mf + v.z * transform.mg + transform.mh; 
            v.cameraZ = v.x * transform.mi + v.y * transform.mj + v.z * transform.mk + transform.ml; 
            v.transformId = transform.transformId;
            v = v.next;
            
            v.x = xorg + whole; v.z = Verts[3]; v.y = zorg + whole;
            v.cameraX = v.x * transform.ma + v.y * transform.mb + v.z * transform.mc + transform.md; 
            v.cameraY = v.x * transform.me + v.y * transform.mf + v.z * transform.mg + transform.mh; 
            v.cameraZ = v.x * transform.mi + v.y * transform.mj + v.z * transform.mk + transform.ml; 
            v.transformId = transform.transformId;
        }

        // setup for rendering using UV coordinates between 0 - 1 from minX/minY top-left reference point and quadMult divisor (inv dimension to multuply against).
        public final function setupForRendering(transform:Object3D, camera:Camera3D, minX:int, minY:int, quadMult:Number, waterPlane:Vector3D):void { 
            
            transformId = transform.transformId;
            
            var xorg:int = cd.xorg;
            var zorg:int = cd.zorg;
            var Verts:Vector.<int> = cd.Verts;
            
            var vertexHeights:Vector.<int> = cd.Square.Vertex;
            
            var x:Number, y:Number, z:Number;
            // init vertex list
            var v:Vertex = vertexList[0];
            var half:int = 1 << cd.Level;
            var whole:int = half << 1;
            
        

            
            v.x =  xorg + half;  v.z = vertexHeights[0];  v.y = zorg + half;
            x = v.x;
            y = v.y;
            z = v.z;
            v.u = (x-minX) * quadMult;
            v.v = 1 - (y-minY) * quadMult;
            
            v.cameraX = transform.ma*x + transform.mb*y + transform.mc*z + transform.md;
            v.cameraY = transform.me*x + transform.mf*y + transform.mg*z + transform.mh;
            v.cameraZ = transform.mi * x + transform.mj * y + transform.mk * z + transform.ml;
            v.offset = v.cameraX * waterPlane.x + v.cameraY * waterPlane.y + v.cameraZ * waterPlane.z;
            v.drawId = 0;
            v = v.next;
            
            v.x = xorg + whole; v.z = vertexHeights[1]; v.y = zorg + half;
            x = v.x;
            y = v.y;
            z = v.z;
            v.u = (x-minX) * quadMult;
            v.v = 1 - (y-minY) * quadMult;
            v.cameraX = transform.ma*x + transform.mb*y + transform.mc*z + transform.md;
            v.cameraY = transform.me*x + transform.mf*y + transform.mg*z + transform.mh;
            v.cameraZ = transform.mi * x + transform.mj * y + transform.mk * z + transform.ml;
            v.offset = v.cameraX * waterPlane.x + v.cameraY * waterPlane.y + v.cameraZ * waterPlane.z;
            v.drawId = 0;
            v = v.next;
            
            v.x = xorg + whole; v.z = Verts[0]; v.y =  zorg;
            x = v.x;
            y = v.y;
            z = v.z;
            v.u = (x-minX) * quadMult;
            v.v = 1 - (y-minY) * quadMult;
            v.cameraX = transform.ma*x + transform.mb*y + transform.mc*z + transform.md;
            v.cameraY = transform.me*x + transform.mf*y + transform.mg*z + transform.mh;
            v.cameraZ = transform.mi * x + transform.mj * y + transform.mk * z + transform.ml;
            v.offset = v.cameraX * waterPlane.x + v.cameraY * waterPlane.y + v.cameraZ * waterPlane.z;
            v.drawId = 0;
            v = v.next;
            
            v.x = xorg + half; v.z =  vertexHeights[2]; v.y =  zorg;
            x = v.x;
            y = v.y;
            z = v.z;
            v.u = (x-minX) * quadMult;
            v.v = 1- (y-minY) * quadMult;
            v.cameraX = transform.ma*x + transform.mb*y + transform.mc*z + transform.md;
            v.cameraY = transform.me*x + transform.mf*y + transform.mg*z + transform.mh;
            v.cameraZ = transform.mi * x + transform.mj * y + transform.mk * z + transform.ml;
            v.offset = v.cameraX * waterPlane.x + v.cameraY * waterPlane.y + v.cameraZ * waterPlane.z;
            v.drawId = 0;
            v = v.next;
            
            v.x = xorg; v.z = Verts[1]; v.y = zorg;
            x = v.x;
            y = v.y;
            z = v.z;
            v.u = (x-minX) * quadMult;
            v.v = 1 - (y-minY) * quadMult;
            v.cameraX = transform.ma*x + transform.mb*y + transform.mc*z + transform.md;
            v.cameraY = transform.me*x + transform.mf*y + transform.mg*z + transform.mh;
            v.cameraZ = transform.mi * x + transform.mj * y + transform.mk * z + transform.ml;
            v.offset = v.cameraX * waterPlane.x + v.cameraY * waterPlane.y + v.cameraZ * waterPlane.z;
            v.drawId = 0;
            v = v.next;
            
            v.x = xorg; v.z = vertexHeights[3]; v.y = zorg + half;
            x = v.x;
            y = v.y;
            z = v.z;
            v.u = (x-minX) * quadMult;
            v.v = 1- (y-minY) * quadMult;
            v.cameraX = transform.ma*x + transform.mb*y + transform.mc*z + transform.md;
            v.cameraY = transform.me*x + transform.mf*y + transform.mg*z + transform.mh;
            v.cameraZ = transform.mi * x + transform.mj * y + transform.mk * z + transform.ml;
            v.offset = v.cameraX * waterPlane.x + v.cameraY * waterPlane.y + v.cameraZ * waterPlane.z;
            v.drawId = 0;
            
            v = v.next;
            v.x = xorg; v.z = Verts[2]; v.y = zorg + whole;
            x = v.x;
            y = v.y;
            z = v.z;
            v.u = (x-minX) * quadMult;
            v.v = 1 - (y-minY) * quadMult;
            v.cameraX = transform.ma*x + transform.mb*y + transform.mc*z + transform.md;
            v.cameraY = transform.me*x + transform.mf*y + transform.mg*z + transform.mh;
            v.cameraZ = transform.mi * x + transform.mj * y + transform.mk * z + transform.ml;
            v.offset = v.cameraX * waterPlane.x + v.cameraY * waterPlane.y + v.cameraZ * waterPlane.z;
            v.drawId = 0;
            v = v.next;
        
            v.x = xorg + half; v.z = vertexHeights[4]; v.y = zorg + whole;
            x = v.x;
            y = v.y;
            z = v.z;
            v.u = (x-minX) * quadMult;
            v.v = 1 - (y-minY) * quadMult;;
            v.cameraX = transform.ma*x + transform.mb*y + transform.mc*z + transform.md;
            v.cameraY = transform.me*x + transform.mf*y + transform.mg*z + transform.mh;
            v.cameraZ = transform.mi * x + transform.mj * y + transform.mk * z + transform.ml;
            v.offset = v.cameraX * waterPlane.x + v.cameraY * waterPlane.y + v.cameraZ * waterPlane.z;
            v.drawId = 0;
            v = v.next;
            
            v.x = xorg + whole; v.z = Verts[3]; v.y = zorg + whole;
            x = v.x;
            y = v.y;
            z = v.z;
            v.u = (x-minX) * quadMult;
            v.v = 1  -(y-minY) * quadMult;
            v.cameraX = transform.ma*x + transform.mb*y + transform.mc*z + transform.md;
            v.cameraY = transform.me*x + transform.mf*y + transform.mg*z + transform.mh;
            v.cameraZ = transform.mi * x + transform.mj * y + transform.mk * z + transform.ml;
            v.offset = v.cameraX * waterPlane.x + v.cameraY * waterPlane.y + v.cameraZ * waterPlane.z;
            v.drawId = 0;

            
            // save out interplated clip average values
            var va:Vertex;
            var vb:Vertex;
            
            v = vertexList[1];
            v.value.x = v.x;
            v.value.y = v.y;
            v.value.u = v.u;
            v.value.v = v.v;
            v = v.value;
            va = vertexList[8];
            vb = vertexList[2];
            v.cameraX = va.cameraX + (vb.cameraX - va.cameraX)*.5;
            v.cameraY = va.cameraY + (vb.cameraY - va.cameraY)*.5;
            v.cameraZ = va.cameraZ + (vb.cameraZ - va.cameraZ) * .5; 
            v.offset = v.cameraX * waterPlane.x + v.cameraY * waterPlane.y + v.cameraZ * waterPlane.z;
            v.drawId = 0;
            v.z = va.z + (vb.z - va.z) * .5;
            
            v = vertexList[3];
            v.value.x = v.x;
            v.value.y = v.y;
            v.value.u = v.u;
            v.value.v = v.v;
            v = v.value;
            va = vertexList[2];
            vb = vertexList[4];
            v.cameraX = va.cameraX + (vb.cameraX - va.cameraX)*.5;
            v.cameraY = va.cameraY + (vb.cameraY - va.cameraY)*.5;
            v.cameraZ = va.cameraZ + (vb.cameraZ - va.cameraZ) * .5; 
            v.offset = v.cameraX * waterPlane.x + v.cameraY * waterPlane.y + v.cameraZ * waterPlane.z;
            v.drawId = 0;
            v.z = va.z + (vb.z - va.z) * .5;
            
            v = vertexList[5];
            v.value.x = v.x;
            v.value.y = v.y;
            v.value.u = v.u;
            v.value.v = v.v;
            v = v.value;
            va = vertexList[6];
            vb = vertexList[4];
            v.cameraX = va.cameraX + (vb.cameraX - va.cameraX)*.5;
            v.cameraY = va.cameraY + (vb.cameraY - va.cameraY)*.5;
            v.cameraZ = va.cameraZ + (vb.cameraZ - va.cameraZ) * .5; 
            v.offset = v.cameraX * waterPlane.x + v.cameraY * waterPlane.y + v.cameraZ * waterPlane.z;
            v.drawId = 0;
            v.z = va.z + (vb.z - va.z) * .5;
            
            v = vertexList[7];
            v.value.x = v.x;
            v.value.y = v.y;
            v.value.u = v.u;
            v.value.v = v.v;
            v = v.value;
            va = vertexList[8];
            vb = vertexList[6];                    
            v.cameraX = va.cameraX + (vb.cameraX - va.cameraX)*.5;
            v.cameraY = va.cameraY + (vb.cameraY - va.cameraY)*.5;
            v.cameraZ = va.cameraZ + (vb.cameraZ - va.cameraZ) * .5; 
            v.offset = v.cameraX * waterPlane.x + v.cameraY * waterPlane.y + v.cameraZ * waterPlane.z;
            v.drawId = 0;
            v.z = va.z + (vb.z - va.z) * .5;
            
        }
        
        
    }
    

    import com.bit101.components.CheckBox;
    import com.bit101.components.ComboBox;
    import com.bit101.components.Component;
    import com.bit101.components.HBox;
    import com.bit101.components.InputText;
    import com.bit101.components.Label;
    import com.bit101.components.NumericStepper;
    import com.bit101.components.Panel;
    import com.bit101.components.PushButton;
    import com.bit101.components.TextArea;
    import com.bit101.components.VBox;
    import com.bit101.components.Window;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.net.FileFilter;
    import flash.net.FileReference;

    /**
     * ...
     * @author Glidias
     */
     class SetupPanelNT extends BaseUI
    {
        static private const CONTENT_WIDTH:Number = 408;
        private var panel:Window;
        public var comp_tilesXShift:NumericStepper;
        public var comp_method:ComboBox;
        public var comp_x3:CheckBox;
        public var comp_totalW:InputText;
        public static const METHOD_STELLAR:String = "AdamAtomic's Stellar TerrainGen";
        public static const METHOD_PERLIN:String = "Perlin Noise BitmapData";
        public static const METHOD_LOADED:String = "Loaded File (image/3ds)";
        
        private var resultsBox:TextArea;
        private var comp_tilesX:InputText;
        
        private var methodHBoxes:Vector.<Sprite> = new Vector.<Sprite>();
        private var _mHBox:Panel;
        private var x3Spr:Sprite = new Sprite();
        private var _btnLoadTexture:PushButton;
        private var _btnLoadWaterTexture:PushButton;
        private var _perlin:HBoxPerlin;
        private var _hBoxLoad:HBoxLoad;
        
        public var comp_startBtn:PushButton;
        public var comp_useWaterZClip:CheckBox;
        public var comp_waterLevel:NumericStepper;
        //public var comp_waterLevelIsland:CheckBox;
        public var comp_scaleHeight:NumericStepper;
        public var comp_fromHeight:NumericStepper;
        public var comp_forMesh:CheckBox;
        public var comp_mipmapWaterRes:NumericStepper;
        public var comp_mipmapTerrainRes:NumericStepper;
        public var comp_mipmapTerrainResScale:InputText;
        public var comp_mipmapWaterResScale:InputText;
        public var comp_updateTerrainRes:PushButton;
        public var comp_updateWaterRes:PushButton;
        public var comp_waterSizeXAmount:NumericStepper;
        public var comp_waterInfinite:CheckBox;
        
        public var file_waterTexture:FileReference = new FileReference();
        public var file_terrainTexture:FileReference = new FileReference();
        public var file_terrain:FileReference = new FileReference();
        
        public var panel_textureStuff:Panel;
        
        public static const E_WATERCHANGE:String = "waterChanged";
        public var comp_mipmapTerrainStretch:PushButton;
        public var comp_mipmapWaterStretch:PushButton;
        
        


        private var fileFilter3DS:FileFilter = new FileFilter(".3ds file", "*.3ds");
        private var fileFilter3DSAndImage:FileFilter = new FileFilter("All formats (.3ds/image)", "*.jpg;*.gif;*.png;*.3ds");
        private var fileFilterImage:FileFilter = new FileFilter("Image (jpg/gif/png)", "*.jpg;*.gif;*.png");
        private var _btnLoadWaterTextureLabel:Label;
        private var _btnLoadTextureLabel:Label;
        private var comp_resultUnits:Label;
        
        public function get numXTiles():int {
            return (1 << int(comp_tilesXShift.value))
        }
        public function get tilesXShift():int {
            return comp_tilesXShift.value;
        }
        public function get bmpScaleHeight():int {
            return comp_scaleHeight.value;
        }
        public function get bmpFromHeight():int {
            return comp_fromHeight.value;
        }
        public function get useBmpValuesForMesh():Boolean {
            return comp_forMesh.selected;
        }
        public function get useWaterZClip():Boolean {
            return comp_useWaterZClip.selected;
        }
        public function get waterZClip():int {
            return comp_waterLevel.value;
        }
        //public function get waterAtEdges():Boolean {
            //return comp_waterLevelIsland.selected;
        //}
        public function get waterMipMapResolution():Number {
            return int(comp_mipmapWaterRes.value);
        }
        public function get waterMipMapResolutionScale():Number {
            return stringToPositiveNumber(comp_mipmapWaterResScale.text, 1);
        }
        public function get terrainMipMapResolution():Number {
            return int(comp_mipmapTerrainRes.value);
        }
        public function get terrainMipMapResolutionScale():Number {
            return stringToPositiveNumber(comp_mipmapTerrainResScale.text, 1);
        }
        private function stringToPositiveNumber(str:String, assertNoneZero:Number=0):Number {
            var num:Number = Number(str);
            return !isNaN(num)  && num >= 0 ?  assertNoneZero!=0 && num == 0 ? assertNoneZero : num : 0;
        }
        
        
        public function get perlin_useRandomSeed():Boolean {
            return _perlin.comp_useRandomSeed.selected;
        }
        public function get perlin_randomSeed():int {
            return _perlin.comp_randomSeed.value;
        }
        public function get perlin_numOctaves():int {
            return _perlin.comp_numOctaves.value;
        }
        public function get perlin_stitch():Boolean {
            return _perlin.comp_stitch.selected;
        }
        public function get perlin_useFractalNoise():Boolean {
            return _perlin.comp_fractalNoise.selected;
        }
        
        
        public function SetupPanelNT() 
        {
            panel = new Window(this, 0,0,"Terrain Creation");
            panel.draggable = false;
    
            var vLayout:VBox = new VBox(panel);
            vLayout.x = 5;
            vLayout.y = 25;
            addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
            addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage);
            
            var vBox:VBox;
            var hBox:HBox;
            var comp:Component;
        
        
            hBox = new HBox(vLayout); 
            new Label(hBox, 0, 0, "Method");
            comp_method = new ComboBox(hBox, 0, 0, "", [METHOD_STELLAR, METHOD_PERLIN, METHOD_LOADED]);
            comp_method.addEventListener(Event.SELECT, onMethodSelect);
            comp_method.width = 166;
            
            
            hBox.draw();
    
            _mHBox = new Panel(vLayout);
            _mHBox.width = CONTENT_WIDTH;
            _mHBox.height = 50;
            var spr:Sprite = new Sprite();
            spr.y = 2;
            _mHBox.addChild(spr);
            methodHBoxes.push ( new HBoxStellar(spr) );
            methodHBoxes.push ( _perlin  = new HBoxPerlin(spr) );
            methodHBoxes.push ( _hBoxLoad = new HBoxLoad(spr) );
            _hBoxLoad._btnLoad.addEventListener(MouseEvent.CLICK, onFileTerrainClick, false, 0, true);
            _mHBox.draw();
            hBox = new HBox(vLayout);
            new Label(hBox, 0, 0, "===Terrain size==="); 
            comp_resultUnits = new Label(hBox, 0, 0);
            hBox.draw();  hBox = new HBox(vLayout);
            new Label(hBox, 0,0,"NumTilesLevel: (1 <<");
            comp_tilesXShift = new NumericStepper(hBox, 0, 0, updateNumTiles);
            comp_tilesXShift.value = 7;
            comp_tilesXShift.minimum = 4;
            comp_tilesXShift.maximum = 10;
            comp_tilesXShift.width = 50;
            new Label(hBox, 0, 0, ") = ");
            comp_tilesX = new InputText(hBox, 0, 0);
            comp_tilesX.width = 50;
            
            hBox.addChild(x3Spr);hBox.draw();
            hBox = new HBox(x3Spr);
            comp_x3 = new CheckBox(hBox, 0, 0, "x3 tiling infinite", onX3Check);
            new Label(hBox, 0 , 0, " = ");
            comp_totalW = new InputText(hBox, 0 , 0);
            comp_totalW.enabled = false;
            comp_totalW.width = 80;
            hBox.draw();
            
            new Label(vLayout, 0, 0, "===Height scale options===");
            hBox = new HBox(vLayout);
            new Label(hBox, 0, 0, "Scale height");
            comp_scaleHeight = new NumericStepper(hBox);
            comp_scaleHeight.minimum = 1;
            comp_scaleHeight.value = 16;
            new Label(hBox, 0, 0, "From min height");
            comp_fromHeight = new NumericStepper(hBox);
            comp_forMesh = new CheckBox(hBox, 0, 0, "Include for 3ds");
            
            
            panel_textureStuff = new Panel(vLayout);
            panel_textureStuff.width = CONTENT_WIDTH;
            panel_textureStuff.height = 142;
            var panelVLayout:VBox = new VBox(panel_textureStuff);
        
            hBox.draw();
            hBox = new HBox(panelVLayout);
            new Label(hBox, 0, 0, "===Water options===");
            comp_useWaterZClip = new CheckBox(hBox, 0, 4, "Enable water", onWaterZCheck);
            
                hBox.draw();
            hBox = new HBox(panelVLayout);
            
            new Label(hBox, 0, 0, "water level:");
            comp_waterLevel = new NumericStepper(hBox, 0,0,dispatchWaterState);
            new Label(hBox, 0, 0, "waterXSizeOfTerrain:");
            comp_waterSizeXAmount = new NumericStepper(hBox, 0,0, onWaterSizeXAmountChange);
            comp_waterSizeXAmount.minimum = 1;
            comp_waterSizeXAmount.maximum = 999999999999999;
            
            comp_waterInfinite = new CheckBox(hBox, 0,0, "set infinite", onWaterZCheck);
            //comp_waterLevelIsland = new CheckBox(hBox, 0, 0, "Enforce water height at edges", onWaterZCheck)
            
            hBox.draw();
            
            hBox = new HBox(panelVLayout);
            new Label(hBox, 0, 0, "===Terrain texture image=== (Optional)");
            comp_updateTerrainRes = new PushButton(hBox, 0, 0, "Update");
            comp_mipmapTerrainStretch = new PushButton(hBox, 0, 0, "mipSetToFullRes");
            comp_mipmapTerrainStretch.visible = false
            comp_updateTerrainRes.width = 50; comp_updateTerrainRes.visible = false;
            hBox.draw();
            hBox = new HBox(panelVLayout);
            _btnLoadTexture = new PushButton(hBox, 0, 0, "Load file on computer..", onLoadTextureFileClick);
            _btnLoadTexture.width =100;
            new Label(hBox, 0, 0, "Mipmap resolution: ");
            comp_mipmapTerrainRes = new NumericStepper(hBox);
            comp_mipmapTerrainRes.minimum = 0;
            new Label(hBox, 0, 0, "scale:");
            comp_mipmapTerrainResScale = new InputText(hBox, 0, 0, "1");
            comp_mipmapTerrainResScale.width = 30;
            _btnLoadTextureLabel = new Label(hBox);
            
            hBox.draw();
            hBox = new HBox(panelVLayout);
            new Label(hBox, 0, 0, "===Water texture image=== (Optional)");
            comp_updateWaterRes = new PushButton(hBox, 0, 0, "Update");
            comp_mipmapWaterStretch = new PushButton(hBox, 0, 0, "mipSetToFullRes");
            comp_mipmapWaterStretch.visible = false
            comp_updateWaterRes.width = 50; comp_updateWaterRes.visible = false;
            hBox.draw();
            hBox = new HBox(panelVLayout);
            _btnLoadWaterTexture = new PushButton(hBox, 0, 0, "Load file on computer..", onLoadWaterTextureFileClick);
            _btnLoadWaterTexture.width = 100;
            
            new Label(hBox, 0, 0, "Mipmap resolution:");
            comp_mipmapWaterRes = new NumericStepper(hBox);
            comp_mipmapWaterRes.minimum = 0;
            new Label(hBox, 0, 0, "scale:");
            comp_mipmapWaterResScale = new InputText(hBox, 0, 0, "1");
            comp_mipmapWaterResScale.width = 30;
            _btnLoadWaterTextureLabel = new Label(hBox);
            
            
            comp = new TextArea(vLayout, 0, 0, "1. Only base2-sized square textures for world-texture mipmapping and tiling.\n2. Formula -> actual-length-covered / textureDimensionInPixels\n3. Mipmap resolution implicitly defines how many tiles are repeated across the entire terrain.\n The mipmap resolution scale value is used to modify the actual resolution used by the TextureMaterial, if you wish, allowing you to produce some depth of field effects (blurrer textures) depending on distance, or just to adjust the general appearance.");
            (comp as TextArea).editable = false;
            comp.setSize(CONTENT_WIDTH, 50);
            
            /*
            new Label(vLayout, 0, 0, "===Results===");
            resultsBox = new TextArea(vLayout);
            resultsBox.editable = false;
            */
            
            updateNumTiles();
            onMethodSelect();
            onX3Check();
            onWaterZCheck();
            
            comp_method.selectedIndex = 0;
            
        
            comp_startBtn = new  PushButton(vLayout, 0, 0, "Start!");
            
            file_terrain.addEventListener(Event.SELECT, function(e:Event):void { file_terrain.load() } );
            file_terrain.addEventListener(Event.COMPLETE, onTerrainLoaded);
            file_terrainTexture.addEventListener(Event.SELECT, function(e:Event):void {   file_terrainTexture.load() } );
            file_terrainTexture.addEventListener(Event.COMPLETE, onTerrainTextureLoaded);
            file_waterTexture.addEventListener(Event.SELECT, function(e:Event):void { file_waterTexture.load() } );
            file_waterTexture.addEventListener(Event.COMPLETE, onWaterTextureLoaded);
            
            file_terrain.addEventListener(Event.CANCEL, dispatchEvent);
            file_terrainTexture.addEventListener(Event.CANCEL, dispatchEvent);
            file_waterTexture.addEventListener(Event.CANCEL, dispatchEvent);
            
            
            vLayout.draw();
        }
        
    
        
        private function onWaterSizeXAmountChange(e:Event):void 
        {
            comp_waterInfinite.selected = false;
            dispatchWaterState();
        }
        
        private function dispatchWaterState(e:Event = null):void {
            dispatchEvent( new Event(E_WATERCHANGE) );
        }
        
        private function onWaterTextureLoaded(e:Event):void 
        {
            _btnLoadWaterTextureLabel.text = file_waterTexture.name;
            comp_mipmapWaterStretch.visible = true;
            dispatchEvent( new Event(Event.CLOSE));
        }
        
        private function onTerrainTextureLoaded(e:Event):void 
        {
            _btnLoadTextureLabel.text = file_terrainTexture.name;
            comp_mipmapTerrainStretch.visible = true;
            dispatchEvent( new Event(Event.CLOSE));
        }
        
        private function onTerrainLoaded(e:Event):void 
        {
            _hBoxLoad._loadedFileLabel.text = file_terrain.name;
            _hBoxLoad._loadedFileNote.visible = false;
        }
        
        
        private function onFileTerrainClick(e:MouseEvent):void 
        {
            
            file_terrain.browse([fileFilter3DSAndImage]);
        }
        
        private function onWaterZCheck(e:Event=null):void 
        {
            var valer:Boolean;
            comp_waterLevel.enabled = valer = comp_useWaterZClip.selected;
            comp_waterSizeXAmount.enabled  = valer;
            comp_waterInfinite.enabled  = valer;
            if (valer && comp_waterInfinite.selected) comp_waterSizeXAmount.value = comp_waterSizeXAmount.maximum;
            dispatchWaterState();
        }
        
        public function onLoadWaterTextureFileClick(e:Event=null):void 
        {
            dispatchEvent( new Event(Event.SELECT));
            file_waterTexture.browse([fileFilterImage]);
        }
        
        public function onLoadTextureFileClick(e:Event=null):void 
        {
            dispatchEvent( new Event(Event.SELECT));
            file_terrainTexture.browse([fileFilterImage]);
        }
        
        private function onX3Check(e:Event=null):void 
        {
            comp_totalW.text = comp_x3.selected ? String(numXTiles*3) : "";
        }
        
    
        
        private function onMethodSelect(e:Event=null):void 
        {
            var len:int = methodHBoxes.length;
            for (var i:int = 0; i < len; i++) {
                methodHBoxes[i].visible  = false;
            }
            if (comp_method.selectedIndex >=0)    methodHBoxes[comp_method.selectedIndex].visible = true;
        }
        
        
        private function updateNumTiles(e:Event = null):void {
            comp_tilesX.text = String(numXTiles);
            comp_resultUnits.text = String("" + (numXTiles*256) +" units");
            onX3Check();
        }
        

        
    
        
        private function onRemovedFromStage(e:Event):void 
        {
            stage.removeEventListener(Event.RESIZE, onStageResize);
        }
        
        private function onAddedToStage(e:Event):void 
        {
            onStageResize();
            stage.addEventListener(Event.RESIZE, onStageResize);
        }
        
        private function onStageResize(e:Event=null):void 
        {

            panel.setSize( stage.stageWidth, stage.stageHeight);
            panel.draw();
    
        }
        
    }


import com.bit101.components.CheckBox;
import com.bit101.components.Component;
import com.bit101.components.HBox;
import com.bit101.components.InputText;
import com.bit101.components.Label;
import com.bit101.components.NumericStepper;
import com.bit101.components.PushButton;
import com.bit101.components.VBox;
import flash.display.BitmapData;
import flash.display.DisplayObjectContainer;
import flash.display.Sprite;
import flash.events.Event;

 class HBoxPerlin extends HBox {
    public var comp_randomSeed:NumericStepper;
    
    public var comp_numOctaves:NumericStepper;
    
    public var comp_fractalNoise:CheckBox;
    public var comp_stitch:CheckBox;
    public var comp_useRandomSeed:CheckBox;

    
    public function HBoxPerlin(parenter:DisplayObjectContainer) {
        
        super(parenter);
    //    BitmapData().perlinNoise(0,0,
        new Label(this, 0, 0, "numOctaves");
        comp_numOctaves = new NumericStepper(this);
        comp_numOctaves.width = 60;
        comp_numOctaves.minimum = 1;
        comp_numOctaves.value = 1;
        comp_numOctaves.maximum = 16;
        comp_useRandomSeed = new CheckBox(this, 0, 0, "useRandomSeed", onRandomSeedCheck)
        comp_randomSeed = new NumericStepper(this);
        //comp_randomSeed.width = 50;

        comp_stitch = new CheckBox(this, 0, 0, "stitch");
        comp_fractalNoise = new CheckBox(this, 0, 0, "fractalNoise");
        
        onRandomSeedCheck();
    
    }
    
    private function onRandomSeedCheck(e:Event=null):void 
    {
        comp_randomSeed.alpha = comp_useRandomSeed.selected ? 1 : .5;
        comp_randomSeed.mouseChildren = comp_useRandomSeed.selected ? true : false;
    }
}

 class HBoxLoad extends Sprite {
    public var _btnLoad:PushButton;
    public var _loadedFileLabel:Label;
    public var _loadedFileNote:Label;
    
    public function HBoxLoad(parenter:DisplayObjectContainer) {
        parenter.addChild(this);
        
        //super(parenter);
        var vLayout:VBox = new VBox(this);

        var hBox:HBox = new HBox(vLayout);
        _btnLoad = new PushButton(hBox, 0, 0, "Load file on computer..", onLoadFileClick);
        _loadedFileLabel = new Label(hBox);
        hBox.draw();
        _loadedFileNote = new Label(vLayout, 0, 0, "You need to load a file before you can Start!");
        //new Label(this, 0, 0, "Load File (online URL):");
        //new F
        //new PushButton(this, 0, 0, "Load");
    }
    
    private function onLoadFileClick(e:Event=null):void 
    {
        
    }
    

 }

class HBoxStellar extends Sprite {
    public function HBoxStellar(parenter:DisplayObjectContainer) {
        parenter.addChild(this);
        
        //super(parenter);
        var vLayout:VBox = new VBox(this);
        new Label(vLayout, 0, 0, "No options");
        new Label(vLayout, 0, 0, "NOTE: Minimum terrain size should be at 128 tiles across at least for generation");
    }
}


    import alternativ7.engine3d.alternativa3d;
    import alternativ7.engine3d.core.Camera3D;
    import alternativ7.engine3d.materials.Material;
    import alternativ7.engine3d.primitives.Plane;
    use namespace alternativa3d;
    
    /**
     * ...
     * @author Glenn Ko
     */
     class Floor extends Plane
    {
        
        public function Floor(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, twoSided:Boolean = true, reverse:Boolean = false, triangulate:Boolean = false, bottom:Material = null, top:Material = null) 
        {
            super(width, length, widthSegments, lengthSegments, twoSided, reverse, triangulate, bottom, top);
            clipping = 2;
        }
        
        override alternativa3d function cullingInCamera(cam:Camera3D, culling:int):int {
            
            return super.cullingInCamera(cam, 47); // omit far-clipping plane
        }
        
    }
    
    
    
    


    import alternativ7.engine3d.containers.KDContainer;
    import alternativ7.engine3d.core.Camera3D;
    import alternativ7.engine3d.core.Canvas;
    import alternativ7.engine3d.core.Object3D;
    import alternativ7.engine3d.core.Object3DContainer;
    import alternativ7.engine3d.core.VG;
    import alternativ7.engine3d.materials.FillMaterial;
    import flash.geom.Vector3D;
    import flash.utils.Dictionary;

    
    import alternativ7.engine3d.alternativa3d;
    use namespace alternativa3d;
    
    /**
     * A 3x3 kd container display grid/infinite hash-grid "world/universe" implementation
     * that is meant to follow a camera's position in global space.
     * 
     * This is meant for handling scrolling mmorpg-like worlds of infinite sizes
     * using a 3x3 grid for paging, where live entities can be located
     * anywhere in the world.
     * 
     * This KDcontainer shouldn't handle drawing of potential dynamic conflict geometry directly, but
     * instead delegate such live entities to their appopiate sub containers. (THat's
     * where the addEntity(obj:Object3D):HashObject3D method comes in, including some
     * other workarounds) if you did add an object directly to this container
     * via addChild().
     * 
     * Make sure you call update(camera) on this instance BEFORE camera.render(), to ensure the state of the world is 
     * kept up to date as it follows the camera.
     * 
     * @author Glenn Ko
     */
     class WorldContainer extends KDContainer implements IUniverse, IVGDelegater, ICameraUpdatable
    {
        alternativa3d var hashTable:Dictionary = new Dictionary();
        alternativa3d var invCellSize:Number;
        alternativa3d var cellSize:Number;
        alternativa3d var cells:Vector.<*>;
        alternativa3d var _cameraPosGlobal:Vector3D = new Vector3D();
        
        // -- IVGDelegater (a hack to get around inability to delegate dynamic geometry to nested trees);
        // set/unset this in order to delegate vg-drawing to nested containers
        alternativa3d var DELEGATE_VG_DRAW:Object3D;  
        public function notifyVGSkip(delegate:Object3D):void  { DELEGATE_VG_DRAW = delegate; }
        // gets the list of VG (potential conflict geometry) to draw!
        alternativa3d var VG_LIST:VG; 
        public function get vgDrawList():VG { return VG_LIST; }
        override alternativa3d function drawConflictGeometry(camera:Camera3D, parentCanvas:Canvas, geometry:VG):void {
            if (DELEGATE_VG_DRAW) {
                VG_LIST = geometry;
                DELEGATE_VG_DRAW.draw(camera, parentCanvas );
                DELEGATE_VG_DRAW = null;
                VG_LIST = null;
                return;
            }
            super.drawConflictGeometry(camera, parentCanvas, geometry);
        }
        
        public function WorldContainer(cellSize:int=8192, cellHeight:int = 0) 
        {
            super();
            resolveByAABB = false;
            resolveByOOBB = false;
            
            this.cellSize = cellSize;
            invCellSize = 1 / cellSize;
            if (cellHeight == 0) cellHeight = 99999999999999;// Number.MAX_VALUE * .5;
            

            cells = new Vector.<*>(9, true); // 3x3 rows
            
            var ox:Number = -cellSize*1.5;
            var oy:Number = -cellSize*1.5;
            var centerZ:Number = cellHeight * .5;
            var count:int = 0;
            var boxList:Vector.<Object3D> = new Vector.<Object3D>();
            var box:BoxIndexable;
            for (var v:int = 0; v < 3; v++) {
                for (var u:int = 0 ; u < 3; u++) {
                    var index:int = v * 3 + u;                    
                    var xorg:Number = ox + u * cellSize;
                    var zorg:Number = oy + v * cellSize;
                    
                    box = new BoxIndexable(cellSize, cellSize, cellHeight);
                    box.index = count++;
                    
                    box.x = xorg + cellSize*.5;
                    box.y = zorg + cellSize*.5;
                    box.z = centerZ;
                    boxList.push(box);
                }
            }
            createTree(boxList);
            var stack:Vector.<*> = new Vector.<*>();
            stack.push(root);
            while ( stack.length > 0) {
                var node:* = stack.pop();
                if (node.positive) stack.push(node.positive);
                
                if (node.negative) stack.push(node.negative)
                else if ( (box = node.objectList as BoxIndexable) != null) {
                    cells[box.index] = node;
                    node.objectList = null;
            
                }
            }

        }

        
        public function get worldTransform():Object3D  {
            return this;
        }
        
    
        
        /**
         * Adds a brand newly created entity to be registered to the universe.
         * The entity must be positioned beforehand prior to using this method to properly
         * register it to the right zone.
         * @param    obj    The object3D representation of the object
         * @return    A hash object token which you can use pass back to the universe for updating/removal.
         */
        public function addEntity(obj:Object3D):HashObject3D {
            var xer:Number = obj.x + cellSize * .5;
            var yer:Number = obj.y + cellSize * .5;
            var key:String = getKey( ( xer < 0 ? xer - cellSize :xer) * invCellSize,  (yer < 0 ? yer - cellSize : yer) * invCellSize);
            //((obj.x >> cellShift) * 73856093) ^ ( (obj.y >> cellShift) * 19349663);
            var hashObject:HashObject3D;
            var hashObjectList:HashObject3DList = hashTable[key];
            if (hashObjectList == null) {
                hashObjectList = HashObject3DList.createNew( hashObject = HashObject3D.create(obj, key) );
                hashTable[key] = hashObjectList;
                return hashObject;
            }
            hashObjectList.append( hashObject = HashObject3D.create(obj, key) );
            return hashObject;
        }
        
        protected function getKey(tx:int, ty:int):String {
        return tx + "_" + ty;
            //return ( (tx * 1640531513) ^ (ty * 2654435789) ) % 999999999999 ;
            //return ((tx * 73856093) ^ (ty * 19349663));
        }
        

        
        /**
         * Removes a currently existing entity from the universe.
         * This method assumes the Hash Object (and it's assosiated object3D) has a parent.
         * @param    hashObject    
         */
        public function removeEntity(hashObject:HashObject3D):void {
            if ( !(hashObject.parent.disposable) ||  hashObject.parent.len > 1) {  
                hashObject.parent.remove(hashObject);
            }
            else { // recycle (disposable) container which is now (assumed) empty after removal
                hashObject.parent.removeAndDispose();
                delete hashTable[hashObject.key];
            }
        }
        
        /**
         * Use this method if you wish to check all entities at once. (This is best used
         * when you can assume all entities are moving constantly.) 
         */
        public function updateAllEntities():void {
            // iterate through all individual dictionary containers (consider full linear list?)
            for (var hKey:* in hashTable) {
                var hashObjectList:HashObject3DList = hashTable[hKey];
                var len:int = hashObjectList.len;
                var vec:Vector.<HashObject3D> = hashObjectList.vec;
                for (var i:int = 0 ; i < len; i++) {
                    checkHashObject(vec[i]);
                }
            }
        }
        
        /**
         * Use this method if you know an entity has moved, and would need to be checked
         * to determine whether it needs to be re-hashed to a different zone. If it needs
         * to be re-hashed, it'll rehash automatically to a new zone key.
         * @param    hashObject    The hash object token to check
         * @return  Whether it was re-hashed to a different location zone key.
         */
        public function checkHashObject(hashObject:HashObject3D):Boolean {
                var hashObjectList:HashObject3DList;
            
                    var obj:Object3D = hashObject.obj;
                    var xer:Number = obj.x + cellSize * .5;
                    var yer:Number = obj.y + cellSize * .5;
                    var key:String = getKey( (xer < 0 ? xer - cellSize : xer) * invCellSize,  (yer < 0 ? yer - cellSize : yer) * invCellSize);
                    //((obj.x >> cellShift) * 73856093) ^ ( (obj.y >> cellShift) * 19349663);
                    if (key == hashObject.key) return false;
                    hashObjectList = hashTable[key];  // other zone
                    if (hashObjectList != null) {  // re-parent to  other zone
                        removeEntity(hashObject);
                        hashObject.key = key;
                        hashObjectList.append(hashObject);
                    }
                    else {  // re-assign to newly created fresh zone
                        hashObjectList = HashObject3DList.createNew( hashObject = HashObject3D.create(obj, key) );
                        hashTable[key] = hashObjectList;
                    }
                    
                    return true;
                }
        
        /**
         * Registers a specific persistant container-3d implementation at a certain zone. 
         * This container will be non-disposal by default. It is assumed the container
         * is at origin (0,0) position, though it's bounds should represent the topmost
         * x/y values in relation to tx/ty.
         * @param    obj    The obj 3d container implementation
         * @param    tx    The cell tile x index position.
         * @param    ty  The cell tile y index position.
         */
        public function addContainerAtPosition(obj:Object3DContainer, tx:int, ty:int):void {
            registerContainerAtPosition(obj, getKey(tx, ty) );
        }
        
        /**
         * Removes a container-3d implementation at a certain zone. It is assumed
         * this container is non-disposal by default and therefore requires manual
         * removal.
         * @param    tx  The cell tile x index position.
         * @param    ty  The cell tile y index position.
         */
        public function removeContainerAtPosition(tx:int, ty:int):void {
            var key:String = getKey(tx, ty);
            var hashObjectList:HashObject3DList = hashTable[key];
            if (hashObjectList == null) {
                throw new Error("No container at position!"); // silently fail?
                return;
            }
            if (hashObjectList.len > 0) {  
                // container still needed as it's assumed container is specific // (wait, what if it's not in view??)
                hashObjectList.migrateEntitiesTo( HashObject3DList.getPoolableContainer() );
                hashObjectList.disposable = true;
                return;
            }
            hashObjectList.dispose();
            delete hashTable[key];
        }
        
        protected var _cameraPos:Vector3D = new Vector3D(Number.NaN, Number.NaN, Number.NaN);
        
        public function update(camera:Camera3D):void {
            var camParent:Object3DContainer = camera.parent;
            _cameraPos.x = camera.x;
            _cameraPos.y = camera.y;
            _cameraPos.z = camera.z;
            while (camParent  != null) {
                _cameraPos.x += camParent.x;
                _cameraPos.y += camParent.y;
                _cameraPos.z += camParent.z;
                camParent = camParent._parent;
            }
            _cameraPosGlobal.x = _cameraPos.x;
            _cameraPosGlobal.y = _cameraPos.y;
            _cameraPosGlobal.z = _cameraPos.z;
            
            checkCameraPosShift(_cameraPos);
        }
        

            
        // private methods for universe shifts
        
        protected var _lastKey:String;
        // only called after all entities are updated via one of the 2 methods!
        protected function checkCameraPosShift(pos:Vector3D):Boolean {
            pos.x +=  cellSize * .5;
            pos.y +=  cellSize * .5;
            // if needed (updateFromNewCameraPos);
            var keyer:String;
            if (_lastKey != (keyer = getKey( (pos.x<0 ? pos.x - cellSize : pos.x) * invCellSize, (pos.y<0 ? pos.y - cellSize : pos.y) * invCellSize) ) ) {
                _lastKey = keyer;
                updateFromNewCameraPos(pos);
                return true;
            }
            return false;
        }
        

        protected function updateFromNewCameraPos(pos:Vector3D):void {
            
            unregisterContainersInView();

            var tx:int = (pos.x < 0 ? pos.x - cellSize : pos.x) * invCellSize;
            var ty:int = (pos.y < 0 ? pos.y - cellSize : pos.y) * invCellSize;
            
            var remX:int = int(pos.x) % cellSize;
            var remY:int = int(pos.y) % cellSize;
            
            x = (tx * cellSize);
            y = (ty * cellSize);
        
            
            var key:String;
            var count:int = 0;
            var hashObjectList:HashObject3DList;
            var container:Object3DContainer;
            
            key = getKey(tx - 1, ty - 1);
            cells[count].objectList = getContainerAtPosition(key);
            _listKeys[count++] = key;
            
            key = getKey(tx, ty - 1);
            cells[count].objectList = getContainerAtPosition(key);
            _listKeys[count++] = key;
            
            key = getKey(tx + 1, ty - 1);
            cells[count].objectList = getContainerAtPosition(key);
            _listKeys[count++] = key;
            
            key = getKey(tx - 1, ty);
            cells[count].objectList = getContainerAtPosition(key);
            _listKeys[count++] = key;
            
            key = getKey(tx, ty);
            cells[count].objectList = getContainerAtPosition(key);
            _listKeys[count++] = key;
            
            key = getKey(tx + 1, ty);
            cells[count].objectList = getContainerAtPosition(key);
            _listKeys[count++] = key;
            
            key = getKey(tx - 1, ty + 1);
            cells[count].objectList = getContainerAtPosition(key);
            _listKeys[count++] = key;
            
            key = getKey(tx, ty + 1);
            cells[count].objectList = getContainerAtPosition(key);
            _listKeys[count++] = key;
            
            key = getKey(tx + 1, ty + 1);
            cells[count].objectList = getContainerAtPosition(key);
            _listKeys[count++] = key;
        }
        
        protected var _listKeys:Vector.<String> = new Vector.<String>(9, true);
        
        protected function registerContainerAtPosition(obj:Object3DContainer, key:String):void {
            var hashObjectList:HashObject3DList = hashTable[key];
            if (hashObjectList != null) {
                hashObjectList.migrateEntitiesTo(obj);
                hashObjectList.disposable = false;
                return;
            }
            
            hashTable[key] = hashObjectList = HashObject3DList.create(obj);
            
            // ensure always synced regardless
            hashObjectList.container.childrenList = hashObjectList.len > 0 ? hashObjectList.vec[0].obj : null;
            
        }
        
    
        protected function getContainerAtPosition(key:String):Object3DContainer {
            var hashObjectList:HashObject3DList = hashTable[key];
            if (hashObjectList == null) return null;
            
            // assumed disposable!
            if (hashObjectList.container == null) {
                if (!hashObjectList.disposable)  throw new Error("assumption failed!");
                hashObjectList.migrateEntitiesTo( HashObject3DList.getPoolableContainer() );
                hashObjectList.container.x = -x;
                hashObjectList.container.y = -y;
                return hashObjectList.container;
            }
            
            // ensure always synced regardless
            hashObjectList.container.childrenList = hashObjectList.len > 0 ? hashObjectList.vec[0].obj : null;
            hashObjectList.container.x = -x;
            hashObjectList.container.y = -y;
            return hashObjectList.container;
        }
        
        // quick method to unregister all 3x3 containers 
        protected function unregisterContainersInView():void {
            
            for (var i:int = 0; i < 9; i++) {
                var key:String = _listKeys[i];
                var hashObjectList:HashObject3DList = hashTable[key];
                if (hashObjectList == null) continue;
                
                if (hashObjectList.len > 0) {  
                    if (hashObjectList.disposable) {
                        hashObjectList.disposeContainer();
                    }
                    hashObjectList.container = null; // set to null regardless 
                    return;
                }
        
                hashObjectList.dispose();
                delete hashTable[key];
            }
        }
        
        
    }


    import alternativ7.engine3d.core.Object3D;
    import alternativ7.engine3d.core.Object3DContainer;

    
    interface IUniverse 
    {
        function addEntity(obj:Object3D):HashObject3D;
        function removeEntity(hashObject:HashObject3D):void;

        function updateAllEntities():void;
        function checkHashObject(hashObject:HashObject3D):Boolean;

        function addContainerAtPosition(obj:Object3DContainer, tx:int, ty:int):void;
        function removeContainerAtPosition(tx:int, ty:int):void;    
        
        function get worldTransform():Object3D;
    }
    


    import alternativ7.engine3d.core.Camera3D;
    
    
     interface ICameraUpdatable 
    {
        function update(camera:Camera3D):void;
    }
    

    
    import alternativ7.engine3d.core.Object3D;
    import alternativ7.engine3d.core.Object3DContainer;
    import flash.display.BitmapData;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    import flash.geom.Vector3D;
    import alternativ7.engine3d.alternativa3d;
    


    use namespace alternativa3d;
    
    /**
     * A 3x3 tiling overworld implementation. Use this to tile movable/repeatable containers
     * as you travel across the world. 
     * Great for repeating stuffs like tiling-terrain containers (or tiling kd containers ) and such!
     * 
     * @author Glenn Ko
     */
     class OverworldContainer extends WorldContainer
    {
        public static function createDummyList():Vector.<Object3DContainer> {
            var vec:Vector.<Object3DContainer>  = new Vector.<Object3DContainer>(9, true);
            for (var i:int = 0; i < 9; i++) {
                vec[i] = new Object3DContainer();
            }
            return vec;
        }
        private static const DUMMY_TILELIST:Vector.<Object3DContainer> = createDummyList();
        private var _tileList:Vector.<Object3DContainer>;
        
        public static function getDummyList(cellSize:Number, cellHeight:Number):Vector.<Object3DContainer> {
            var vec:Vector.<Object3DContainer>  = new Vector.<Object3DContainer>(9, true);
            for (var i:int = 0; i < 9; i++) {
                vec[i] = new MarkerBoxNumbered(cellSize, cellSize, cellHeight, String(i));
            }
            return vec;
        }
        
        public function OverworldContainer(cellSize:int=8512, cellHeight:int = 0) 
        {
            super(cellSize, cellHeight);
        }
        
        /**
         * Register tilable objects.
         * @param    vec
         */
        public function setTileList(vec:Vector.<Object3DContainer>):void {  
            var len:int = vec.length;
            for (var i:int = 0; i < 9; i++) {
                var obj:Object3D = vec[i];
                obj._parent = this;
            }
            obj.boundMaxX - obj.boundMinX;
            vec.fixed = false;
            vec.length = 9;
            vec.fixed = true;
            _tileList = vec;
        }
        
        
        
        override protected function unregisterContainersInView():void {
            super.unregisterContainersInView();
            for (var i:int = 0; i < 9; i++) {
                _tileList[i].childrenList = null;
            }
        }

        public function getTileIndexAt(xGlobal:Number, yGlobal:Number, calculatedLocalPos:Vector3D):int {

            var pos:Vector3D = new Vector3D(xGlobal, yGlobal);
            var tx:int = (pos.x < 0 ? pos.x - cellSize : pos.x) * invCellSize;
            var ty:int = (pos.y < 0 ? pos.y - cellSize : pos.y) * invCellSize;
            var ix:int;
            var iy:int;
            ix = tx < 0 ?  Math.abs((( -3 * 99999999999) - (tx)) % 3) : tx % 3;
            iy = ty < 0 ?  Math.abs((( -3 * 99999999999) - (ty)) % 3) : ty % 3;  
            calculatedLocalPos.x = xGlobal - (tx*cellSize);
            calculatedLocalPos.y = yGlobal - (ty*cellSize);
            return ((iy % 3) * 3 + (ix % 3));
        }
        
        override protected function updateFromNewCameraPos(pos:Vector3D):void {

            unregisterContainersInView();  

            var tx:int = (pos.x < 0 ? pos.x - cellSize : pos.x) * invCellSize;
            var ty:int = (pos.y < 0 ? pos.y - cellSize : pos.y) * invCellSize;
            var ix:int;
            var iy:int;
            ix = tx < 0 ?  Math.abs((( -3 * 99999999999) - (tx)) % 3) : tx % 3;
            iy = ty < 0 ?  Math.abs((( -3 * 99999999999) - (ty)) % 3) : ty % 3;  
            
            x = (tx * cellSize);
            y = (ty * cellSize);
            
            
            var obj:Object3D;
            
            var key:String;
            var count:int = 0;
            var container:Object3DContainer;
            var hashContainer:Object3DContainer;
            
            var xOff:Number;
            var yOff:Number;
            const ox:Number = -cellSize*1.5;
            const oy:Number = -cellSize*1.5;
            
            var u:int; var v:int;

                    
            key = getKey(tx - 1, ty - 1);  u = 0; v = 0;
            hashContainer = getContainerAtPosition(key);
            cells[count].objectList = container = _tileList[((iy + v) % 3) * 3 + ((ix + u) % 3)]; // get correct mapping to cell
        
            container.x = xOff = ox + u * cellSize;
            container.y = yOff = oy + v * cellSize;  
            if (hashContainer != null) {
                container.childrenList =  hashContainer;
                hashContainer.x = -x - xOff;
                hashContainer.y = -y - yOff;
            }
            _listKeys[count++] = key;
            
            key = getKey(tx, ty - 1);   u = 1; v = 0;
            hashContainer = getContainerAtPosition(key);
            cells[count].objectList = container = _tileList[((iy + v)%3)*3+((ix + u)%3)]; // get correct mapping to cell
            container.x = xOff = ox + u * cellSize;
            container.y = yOff = oy + v * cellSize;  
            if (hashContainer != null) {
                container.childrenList =  hashContainer;
                hashContainer.x = -x - xOff;
                hashContainer.y = -y - yOff;
            }
            _listKeys[count++] = key;
            
        
            
            key = getKey(tx + 1, ty - 1); u = 2; v = 0;
            hashContainer = getContainerAtPosition(key);
            cells[count].objectList = container = _tileList[((iy + v)%3)*3+((ix + u)%3)]; // get correct mapping to cell
            container.x = xOff = ox + u * cellSize;
            container.y = yOff = oy + v * cellSize;  
            
            if (hashContainer != null) {
                container.childrenList =  hashContainer;
                hashContainer.x = -x - xOff;
                hashContainer.y = -y - yOff;
            }
            _listKeys[count++] = key;
    
            
            key = getKey(tx - 1, ty); u = 0; v = 1;
            hashContainer = getContainerAtPosition(key);
            cells[count].objectList = container = _tileList[((iy + v)%3)*3+((ix + u)%3)]; // get correct mapping to cell
            container.x = xOff = ox + u * cellSize;
            container.y = yOff = oy + v * cellSize;  
            if (hashContainer != null) {
                container.childrenList =  hashContainer;
                hashContainer.x = -x - xOff;
                hashContainer.y = -y - yOff;
            }
            _listKeys[count++] = key;

            
            key = getKey(tx, ty); u = 1; v = 1;
            hashContainer = getContainerAtPosition(key);
            cells[count].objectList = container = _tileList[((iy + v) % 3) * 3 + ((ix + u) % 3)]; // get correct mapping to cell
            
            container.x = xOff = ox + u * cellSize;
            container.y = yOff = oy + v * cellSize;  
            if (hashContainer != null) {
                container.childrenList =  hashContainer;
                hashContainer.x = -x - xOff;
                hashContainer.y = -y - yOff;
            }
            _listKeys[count++] = key;
    
            
            key = getKey(tx + 1, ty); u = 2; v = 1;
            hashContainer = getContainerAtPosition(key);
            cells[count].objectList = container = _tileList[((iy + v) % 3) * 3 + ((ix + u) % 3)]; // get correct mapping to cell
            
            container.x = xOff = ox + u * cellSize;
            container.y = yOff = oy + v * cellSize;    
            if (hashContainer != null) {
                container.childrenList =  hashContainer;
                hashContainer.x = -x - xOff;
                hashContainer.y = -y - yOff;
            }
            _listKeys[count++] = key;
    
            
            key = getKey(tx - 1, ty + 1); u = 0; v = 2;
            hashContainer = getContainerAtPosition(key);
            cells[count].objectList = container = _tileList[((iy + v)%3)*3+((ix + u)%3)]; // get correct mapping to cell
            container.x = xOff = ox + u * cellSize;
            container.y = yOff = oy + v * cellSize;  
            if (hashContainer != null) {
                container.childrenList =  hashContainer;
                hashContainer.x = -x - xOff;
                hashContainer.y = -y - yOff;
            }
            _listKeys[count++] = key;

            
            key = getKey(tx, ty + 1);  u = 1; v = 2;
            hashContainer = getContainerAtPosition(key);
            cells[count].objectList = container = _tileList[((iy + v)%3)*3+((ix + u)%3)]; // get correct mapping to cell
            container.x = xOff = ox + u * cellSize;
            container.y = yOff = oy + v * cellSize;  
            if (hashContainer != null) {
                container.childrenList =  hashContainer;
                hashContainer.x = -x - xOff;
                hashContainer.y = -y - yOff;
            }
            _listKeys[count++] = key;
        
            
            key = getKey(tx + 1, ty + 1); u = 2; v = 2;
            hashContainer = getContainerAtPosition(key);
            cells[count].objectList = container = _tileList[((iy + v)%3)*3+((ix + u)%3)]; // get correct mapping to cell
            container.x = xOff = ox + u * cellSize;
            container.y = yOff = oy + v * cellSize;  
            if (hashContainer != null) {
                container.childrenList =  hashContainer;
                hashContainer.x = -x - xOff;
                hashContainer.y = -y - yOff;
            }
            _listKeys[count++] = key;
            
        }
        
        public function get tileList():Vector.<Object3DContainer> 
        {
            return _tileList;
        }
        
    }



    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.filters.BlurFilter;
    import flash.geom.Matrix;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    /**
     * ...
     * @author Glenn Ko
     */
     class StellarTerrainGen extends Sprite
    {
        private var _heightMap:Array;
        private var _tileData:Array;
        public var mapWidth:uint = 128*3;// 768;
        public var mapHeight:uint =  128*3;// 768;
        public var smoothBlur:Number = 5;
        private var _highestHeight:Number = -Number.MAX_VALUE;
        private var _lowestHeight:Number = 0;
        public var water:uint = 2;
        public var heightAdd:uint = 2;
        public var groundTextureLayers:Vector.<Vector.<uint>> = createGroundTextureLayers(10);
        private static function createGroundTextureLayers(amt:int):Vector.<Vector.<uint>> {
            var vec:Vector.<Vector.<uint>> = new Vector.<Vector.<uint>>(amt, true);
            var i:int = vec.length;
            while (--i > -1) {
                vec[i] = new Vector.<uint>();
            }
            return vec;
        }

        
        public function StellarTerrainGen() 
        {
            if (stage != null) {
                stage.align = StageAlign.TOP_LEFT;
                stage.scaleMode = StageScaleMode.SHOW_ALL;
                previewGen();
            }
        }
        
        public function previewGen():void 
        {
            generate();
            addChild ( new Bitmap( getHeightBmpData() ) );
        }
        
        public function getHeightBmpData(use3x3:Boolean=true, useAddHeightAsBase:Boolean = false):BitmapData {
            
            var bmpData:BitmapData = new BitmapData(mapWidth, mapHeight, false, 0);
            var buffer:Sprite = new Sprite();
    var bufferData:BitmapData = new BitmapData(mapWidth + smoothBlur * 2, mapHeight + smoothBlur * 2, false , 0);
            
            

            const base:int = useAddHeightAsBase ? heightAdd : 0;
            for (var v:int = 0; v < mapHeight; v++) {
                for (var u:int = 0; u < mapWidth; u++) {
                    //if (_highestHeight < _heightMap[v][u]) throw new Error("SHOULD NOT BE!" + new Point(_highestHeight, _heightMap[v][u]));
                    
                    //if (_heightMap[v][u] < 0 ) throw new Error("SHOULD NOT BE!");
                    var baseColor:uint = uint( (_heightMap[v][u] - base) / (_highestHeight - base) * 255);
                    baseColor = baseColor << 16 | baseColor << 8 | baseColor;
                    bmpData.setPixel(u, v, baseColor);
                    //linearVec.push(baseColor)
                }
            }
            var offset:int = use3x3 ? -3 : 1;
            var matrix:Matrix =  new Matrix();
            matrix.scale((mapWidth +offset) / mapWidth, (mapHeight +offset) / mapHeight);
            var oldBmpData:BitmapData = bmpData;
            
            bmpData = new BitmapData(mapWidth +offset, mapHeight +offset, false, 0);
            bmpData.draw(oldBmpData, matrix);
            oldBmpData.dispose();
            matrix.identity();
            matrix.tx = smoothBlur;
            matrix.ty = smoothBlur;
            buffer.graphics.beginBitmapFill(bmpData, matrix, true);
            buffer.graphics.drawRect(0, 0, bmpData.width + smoothBlur * 2, bmpData.height + smoothBlur * 2);
            bufferData.draw(buffer);
            bufferData.applyFilter(bufferData, bufferData.rect, new Point(), new BlurFilter(smoothBlur, smoothBlur, 4) );
            
            bmpData.copyPixels(bufferData, new Rectangle(smoothBlur, smoothBlur, buffer.width, buffer.height), new Point() );
        bufferData.dispose();
        buffer.graphics.clear();
        
            return bmpData;
        }
        public function get tileData():Array { return _tileData; }
        
        public function get lowestHeight():Number { return _lowestHeight; }
        
        public function get highestHeight():Number { return _highestHeight; }
        
        public function set highestHeight(value:Number):void 
        {
            _highestHeight = value;
        }
    
        public function generate():void
        {
            _highestHeight = -Number.MAX_VALUE;
            
            //Basic generation variables
            var i:uint;
            var j:uint;

        
            
            //Create a simple heightmap
            var height:Array = _heightMap = new Array();
            for(i = 0; i < mapHeight; i++)
            {
                height.push(new Array());
                for(j = 0; j < mapWidth; j++)
                    height[i].push(13);
            }
            
            //Then bombard it with meteorites of varying sizes
            var hits:uint = 1;
            for(i = 11; i > 2; i--)
            {
                for(j = 0; j < hits; j++)
                    geomod(height,i);
                hits += (12-i);
            }
            
            //Then add some volcanic activity
            hits = 1;
            for(i = 7; i > 3; i--)
            {
                for(j = 0; j < hits; j++)
                    geomod(height,i,false);
                hits += (10-i)*1.8;
            }
            
            //Integerize the heightmap
            for(i = 0; i < mapHeight; i++)
            {
                for(j = 0; j < mapWidth; j++) {
                    height[i][j] = Math.round(height[i][j]) + heightAdd;
                }
            }
            
            //Fill in the heightmap with appropriate basic tiles
            var h:uint;
            var index:uint;
            var prob:Number;
    
            var layerIndex:int;
            var tileData:Array = _tileData = new Array();
            for(i = 0; i < mapHeight; i++)
            {
                tileData.push(new Array());
                for(j = 0; j < mapWidth; j++)
                {
                    //tileData[i].push(height[i][j]); /*
                    h = height[i][j];
                    //if(h == 0)              index = 9;    //sea
            
                     if(h < water+1)     { index = 8; layerIndex=0 }    //shallows
                    else if(h < water+2) { index = 4; layerIndex = 1 }      //sand (shore)
                    else if(h < water+3) { index = 2; layerIndex = 2 }    //dirt (plains)
                    else if(h < water+5) { index = 1; layerIndex = 3 }    //grass (plains)
                    else if (h < water + 6) { index = 12;  layerIndex = 4 }    //terrace
                    else if(h < water+7) { index = 2; layerIndex = 5     }//dirt (plains)
                    else if(h < water+9)  {  index = 1; layerIndex = 6 }    //grass (plains)
                    else if(h < water+12) { index = 11; layerIndex = 7 }    //forest (plains)
                    else if (h < water + 14) { index = 12; layerIndex = 8 }    //hill (foothills
                    else                 { index = 13; layerIndex = 9     }//mountain
                    
                    groundTextureLayers[layerIndex].push((i << 16) | j);
                    tileData[i].push(index);
                    //*/
                }
            }
            
            //Cut ramps in the hills so the highlands are accessible
            var k:uint;
            var l:uint;
            var rx:int;
            var ry:int;
            var orx:uint;
            var ory:uint;
            var rsafe:uint;
            var grid:uint = 8;
            var counts:Array;
            var thickness:uint;
            var rh:uint = mapHeight/grid;
            var rw:uint = mapWidth*.8/grid;
            for(i = 0; i < grid; i++)
            {
                ry = rh*i;
                for(j = 0; j < grid; j++)
                {
                    //Figure out how thick the ramp should be
                    thickness = Math.random()*4;
                    thickness *= 2;
                    if(thickness == 0) thickness = 1;
                    var halfThick:uint = thickness/2;
                    
                    //Find a nice place for a ramp
                    rx = mapWidth*.1+rw*j;
                    rsafe = 256;
                    do
                    {
                        k = ry + Math.random()*rh;
                        l = rx + Math.random()*rw;
                        if(l < halfThick) l = halfThick;
                        if(k < halfThick) k = halfThick;
                        if(l >= mapWidth-halfThick) l = mapWidth-halfThick-1;
                        if(k >= mapHeight-halfThick) k = mapHeight-halfThick-1;
                    } while ((--rsafe > 0) && (height[k][l] != water+5));
                    if(rsafe == 0) continue;
                    
                    //Fire 8 rays from a box centered at the ramp locus
                    counts = new Array(); for(rsafe = 0; rsafe < 8; rsafe++) counts.push(0); rsafe = 0;
                    while((k-counts[rsafe] > 0) && (height[k-counts[rsafe]][l-halfThick] == water+5)) { counts[rsafe]++; } rsafe++;
                    while((k-counts[rsafe] > 0) && (height[k-counts[rsafe]][l+halfThick] == water+5)) { counts[rsafe]++; } rsafe++;
                    while((k+counts[rsafe] < mapHeight) && (height[k+counts[rsafe]][l-halfThick] == water+5)) { counts[rsafe]++; } rsafe++;
                    while((k+counts[rsafe] < mapHeight) && (height[k+counts[rsafe]][l+halfThick] == water+5)) { counts[rsafe]++; } rsafe++;
                    while((l-counts[rsafe] > 0) && (height[k-halfThick][l-counts[rsafe]] == water+5)) { counts[rsafe]++; } rsafe++;
                    while((l-counts[rsafe] > 0) && (height[k+halfThick][l-counts[rsafe]] == water+5)) { counts[rsafe]++; } rsafe++;
                    while((l+counts[rsafe] < mapWidth) && (height[k-halfThick][l+counts[rsafe]] == water+5)) { counts[rsafe]++; } rsafe++;
                    while((l+counts[rsafe] < mapWidth) && (height[k+halfThick][l+counts[rsafe]] == water+5)) { counts[rsafe]++; } rsafe++;
                    
                    //figure out which rays are the longest, then pad them a bit
                    if(counts[1] > counts[0]) counts[0] = counts[1];
                    if(counts[3] > counts[2]) counts[2] = counts[3];
                    if(counts[5] > counts[4]) counts[4] = counts[5];
                    if(counts[7] > counts[6]) counts[6] = counts[7];
                    counts[0] += 2; if(k + counts[0] < mapHeight-3) counts[2] += 2; counts[4] += 2; counts[6] += 2;
                    
                    //Actually draw out the ramps
                    orx = l; ory = k;
                    if(counts[0] + counts[2] < counts[4] + counts[6]) //Vertical ramp
                    {
                        if(counts[0] + counts[2] > 16) continue;
                        if(thickness == 1)
                        {
                            for(k = ory - counts[0]; k < ory + counts[2]; k++)
                                if(height[k][orx] == water+5) tileData[k][orx] = 2; //dirt
                        }
                        else
                        {
                            for(k = ory - counts[0]; k < ory + counts[2]; k++)
                                for(l = orx - halfThick; l < orx + halfThick; l++)
                                    if(height[k][l] == water+5) tileData[k][l] = 2; //dirt
                        }
                    }
                    else //Horizontal ramp
                    {
                        if(counts[4] + counts[6] > 16) continue;
                        if(thickness == 1)
                        {
                            for(l = orx - counts[4]; l < orx + counts[6]; l++)
                                if(height[ory][l] == water+5) tileData[ory][l] = 2; //dirt
                        }
                        else
                        {
                            for(k = ory - halfThick; k < ory + halfThick; k++)
                                for(l = orx - counts[4]; l < orx + counts[6]; l++)
                                    if(height[k][l] == water+5) tileData[k][l] = 2; //dirt
                        }
                    }
                }
            }

            //Recheck the heightmap
            for(i = 0; i < mapHeight; i++) {
                for(j = 0; j < mapWidth; j++) {
                    prob = height[i][j];
                    if (prob > _highestHeight) _highestHeight = prob;
                }
            }
        
        }
        
        private function geomod(Map:Array,Power:uint,Dig:Boolean=true,Outcrop:Point=null):void
        {
            var i:int = 0;
            var j:int = 0;
            var im:int = 0;
            var jm:int = 0;
            var distance:int;
            var radius:uint = Power*Power;
            if(Outcrop != null) radius = 4+Math.random()*12;
            var radius2:uint = radius*radius;
            var epiX:uint;
            var epiY:uint;
            if(Outcrop != null)
            {
                epiX = Outcrop.x;
                epiY = Outcrop.y;
            }
            else
            {
                epiX = uint(Math.random()*Map[0].length);
                epiY = uint(Math.random()*Map.length);
                if(Dig && (Power > 7)) epiX = 0;
                else if(!Dig)
                {
                    if(Power > 5)
                        epiX = Map[0].length/5 + uint(Math.random()*(Map[0].length-2*Map[0].length/5));
                    else if(uint(Math.random()*4) == 0)
                    {
                        epiX = Map[0].length/5 + uint(Math.random()*(Map[0].length-2*Map[0].length/5));
                        epiY = 0;
                    }
                }
            }
            var it:int = epiY+radius;
            var jt:int = epiX+radius;
            for(i = epiY-radius; i < it; i++)
            {
                im = i;
                if(im < 0) im += Map.length;
                else if(im >= Map.length) im -= Map.length;
                for(j = epiX-radius; j < jt; j++)
                {
                    distance = dist(j,i,epiX,epiY);
                    if(distance < radius)
                    {
                        jm = j;
                        if(jm < 0) jm += Map[0].length;
                        else if(jm >= Map[0].length) jm -= Map[0].length;
                        if(Dig) Map[im][jm] -= Power*((radius2-(distance*distance))/radius2);
                        else if(Outcrop != null) Map[im][jm] = (Math.random() > ((radius2-((distance-radius)*(distance-radius)))/radius2))?Power:Map[im][jm];
                        else    Map[im][jm] += Power*(((distance-radius)*(distance-radius))/radius2);
                        if(Map[im][jm] < 0) Map[im][jm] = 0;
                        if(Map[im][jm] > 15) Map[im][jm] = 15;
                    }
                }
            }
        }
        
        private function dist(X1:int,Y1:int,X2:int,Y2:int):Number
        {
            return Math.sqrt((X1-X2)*(X1-X2)+(Y1-Y2)*(Y1-Y2));
        }

        

        
    }


    import alternativ7.engine3d.core.Camera3D;
    import alternativ7.engine3d.core.Object3D;
    import alternativ7.engine3d.alternativa3d;
    import flash.geom.Vector3D;
    use namespace alternativa3d;

    
    /**
     * @author Thatcher Ulrich (tu@tulrich.com)
     * @author Glenn Ko
     * Thatcher's lod terrain implementation.
     */
    
    
     class QuadSquare
    {
        public var Child:Vector.<QuadSquare>;
        private static const SPHERE_CULLER:Object3D = new Object3D(); // for boundsIntersectsSphere() calls.
        
        // center, e, n, w, s [5]  , vertex heights   
        public    var Vertex:Vector.<int> = new Vector.<int>(5, true);    
        // e, s, children: ne, nw, sw, se  [6]
        public    var errorList:Vector.<int> = new Vector.<int>(6, true); 
        
        // Bounds for frustum culling and error testing.
        public var MinY:int;
        public var MaxY:int;    
        
        public var     EnabledFlags:int;    // bit-30, culled,   bits 8-13: culling mask of frustum planes,   bits 0-7: e, n, w, s, ne, nw, sw, se
        public var    SubEnabledCount:Vector.<int>;    // e, s enabled reference counts. [2]
        
        public var    Static:Boolean;
        public var    Dirty:Boolean    // Set when vertex data has changed, but error/enabled data has not been recalculated.
        
    

        
        public function QuadSquare(pcd:QuadCornerData) 
        {
            pcd.Square = this;
            MinY = 1.79e+308;
            MaxY = -1.79e+308;
            
            // Set static to true if/when this node contains real data, and
            // not just interpolated values.  When static == false, a node
            // can be deleted by the Update() function if none of its
            // vertices or children are enabled.
            Static = false;
    
            var    i:int;
            
            /*
            for (i = 0; i < 4; i++) {
                Child[i] = NULL;
            }*/
            Child = new Vector.<QuadSquare>(4, true);

            EnabledFlags = 0;

            SubEnabledCount = new Vector.<int>(2, true);
            SubEnabledCount[0] = 0;
            SubEnabledCount[1] = 0;
            
            // Set default vertex positions by interpolating from given corners.
            // Just bilinear interpolation.
            Vertex[0] = 0.25 * (pcd.Verts[0] + pcd.Verts[1] + pcd.Verts[2] + pcd.Verts[3]);
            Vertex[1] = 0.5 * (pcd.Verts[3] + pcd.Verts[0]);
            Vertex[2] = 0.5 * (pcd.Verts[0] + pcd.Verts[1]);
            Vertex[3] = 0.5 * (pcd.Verts[1] + pcd.Verts[2]);
            Vertex[4] = 0.5 * (pcd.Verts[2] + pcd.Verts[3]);
            

            for (i = 0; i < 2; i++) {
                errorList[i] = 0;
            }
            for (i = 0; i < 4; i++) {
                errorList[i+2] = Math.abs((Vertex[0] + pcd.Verts[i]) - (Vertex[i+1] + Vertex[((i+1)&3) + 1])) * 0.25;
            }

            // Compute MinY/MaxY based on corner verts.
            MinY = MaxY = pcd.Verts[0];
            for (i = 0; i < 4; i++) {
                var    y:Number = pcd.Verts[i];
                if (y < MinY) MinY = y;
                if (y > MaxY) MaxY = y;
            }
            
        }
        
        public function calculateYBounds(pcd:QuadCornerData):void {
            MinY = 1.79e+308;
            MaxY = -1.79e+308;
            var    y:Number
            var i:int;
            for (i = 0; i < 4; i++) {
                y = pcd.Verts[i];
                if (y < MinY) MinY = y;
                if (y > MaxY) MaxY = y;
            }
            for (i = 0; i < 5; i++) {
                y = Vertex[i];
                if (y < MinY) MinY = y;
                if (y > MaxY) MaxY = y;
            }
        }
        
        public function destroy():void {
            for (var i:int = 0; i < 4; i++) {
                if (Child[i] !=null) {
                    Child[i].destroy();
                    Child[i] = null;
                }
            }
        }
        
        public function    SetStatic(cd:QuadCornerData):void
            // Sets this node's static flag to true.  If static == true, then the
            // node or its children is considered to contain significant height data
            // and shouldn't be deleted.
        {
            if (!Static) {
                Static = true;
                
                // Propagate static status to ancestor nodes.
                if (cd.Parent && cd.Parent.Square) {
                    cd.Parent.Square.SetStatic(cd.Parent);
                }
            }
        }
        
        // Enable the specified neighbor.  Indices go { e, n, w, s }.
        private static function getNeighborCornerData(cd:QuadCornerDataNeighbor, index:int):QuadCornerData 
        {
        
            var neighbor:QuadCornerData = cd.neighbors[index];
            if (neighbor == null) return null;
            
            var full:int  = ((1 << cd.Level) << 1);
            // we assume during any root neighbour Update(), this value is resetted back to original
            //neighbor.xorg = index == 0 ? full : index == 2 ? -full : 0;
            //neighbor.zorg = index == 1 ? full : index == 3 ? -full : 0;
            return neighbor;
        }
        
    
        //se,sw,nw,ne
        // ne, nw, sw, se [4]
        public function    SetupCornerData(q:QuadCornerData, cd:QuadCornerData, ChildIndex:int):void
        // Fills the given structure with the appropriate corner values for the
        // specified child block, given our own vertex data and our corner
        // vertex data from cd.
        //
        // ChildIndex mapping:
        // +-+-+
        // |1|0|
        // +-+-+
        // |2|3|
        // +-+-+
        //
        // Verts mapping:
        // 1-0
        // | |
        // 2-3
        //
        // Vertex mapping:
        // +-2-+
        // | | |
        // 3-0-1
        // | | |
        // +-4-+
        {
            var    half:int = 1 << cd.Level;

            q.Parent = cd;
            q.Square = Child[ChildIndex];
            q.Level = cd.Level - 1;
            q.ChildIndex = ChildIndex;
            
            switch (ChildIndex) {
            case 0:
                q.xorg = cd.xorg + half;
                q.zorg = cd.zorg;
                q.Verts[0] = cd.Verts[0];
                q.Verts[1] = Vertex[2];
                q.Verts[2] = Vertex[0];
                q.Verts[3] = Vertex[1];
                break;

            case 1:
                q.xorg = cd.xorg;
                q.zorg = cd.zorg;
                q.Verts[0] = Vertex[2];
                q.Verts[1] = cd.Verts[1];
                q.Verts[2] = Vertex[3];
                q.Verts[3] = Vertex[0];
                break;

            case 2:
                q.xorg = cd.xorg;
                q.zorg = cd.zorg + half;
                q.Verts[0] = Vertex[0];
                q.Verts[1] = Vertex[3];
                q.Verts[2] = cd.Verts[2];
                q.Verts[3] = Vertex[4];
                break;

            case 3:
                q.xorg = cd.xorg + half;
                q.zorg = cd.zorg + half;
                q.Verts[0] = Vertex[1];
                q.Verts[1] = Vertex[0];
                q.Verts[2] = Vertex[4];
                q.Verts[3] = cd.Verts[3];
                break;
            }    
        }
        
        
        
        private static var DetailThreshold:Number = 100;
        private static var FocalLength:Number = 100;

        public function CountNodes():int
        // Debugging function.  Counts the number of nodes in this subtree.
        {
            var count:int = 1;    // Count ourself.

            // Count descendants.
            for (var i:int = 0; i < 4; i++) {
                if (Child[i]) count += Child[i].CountNodes()
                else {
                    for (var u:int = 0; u < 4; u++) {
                        if (Child[u] != null) throw new Error("NOT dense!");
                    }
                }
            }

            return count;
        }
        


public    function GetHeight( cd:QuadCornerData, x:Number, z:Number):Number
// Returns the height of the heightfield at the specified x,z coordinates.
{
    var    half:int = 1 << cd.Level;
    var halfM:Number = 1 / half;
    var    lx:Number = (x - cd.xorg) * halfM;
    var    lz:Number = (z - cd.zorg) * halfM;

    var    ix:int = int(lx);
    var    iz:int = int(lz);

    // Clamp.
    if (ix < 0) ix = 0;
    if (ix > 1) ix = 1;
    if (iz < 0) iz = 0;
    if (iz > 1) iz = 1;

    var    index:int = ix ^ (iz ^ 1) + (iz << 1);
    if (Child[index] && Child[index].Static) {
        // Pass the query down to the child which contains it.
        var    q:QuadCornerData = QuadCornerData.create();
        SetupCornerData(q, cd, index);  // to check pointer validity
        return Child[index].GetHeight(q, x, z);
    }

    // Bilinear interpolation.
    lx -= ix;
    if (lx < 0) lx = 0;
    if (lx > 1) lx = 1;
    
    lz -= iz;
    if (lx < 0) lz = 0;
    if (lz > 1) lz = 1;

    var    s00:Number, s01:Number, s10:Number, s11:Number;
    switch (index) {
    default:
    case 0:
        s00 = Vertex[2];
        s01 = cd.Verts[0];
        s10 = Vertex[0];
        s11 = Vertex[1];
        break;
    case 1:
        s00 = cd.Verts[1];
        s01 = Vertex[2];
        s10 = Vertex[3];
        s11 = Vertex[0];
        break;
    case 2:
        s00 = Vertex[3];
        s01 = Vertex[0];
        s10 = cd.Verts[2];
        s11 = Vertex[4];
        break;
    case 3:
        s00 = Vertex[0];
        s01 = Vertex[1];
        s10 = Vertex[4];
        s11 = cd.Verts[3];
        break;
    }

    return (s00 * (1-lx) + s01 * lx) * (1 - lz) + (s10 * (1-lx) + s11 * lx) * lz;
}



    


private function GetNeighbor(dir:int,  cd:QuadCornerData):QuadSquare
// Traverses the tree in search of the quadsquare neighboring this square to the
// specified direction.  0-3 -. { E, N, W, S }.
// Returns NULL if the neighbor is outside the bounds of the tree.
{
    // If we don't have a parent, then we don't have a neighbor.
    // (Actually, we could have inter-tree connectivity at this level
    // for connecting separate trees together.)
    if (cd.Parent == null) {  
        return cd is QuadCornerDataNeighbor ?  (cd as QuadCornerDataNeighbor).neighbors[dir] != null ? (cd as QuadCornerDataNeighbor).neighbors[dir].Square : null : cd.Square;  // TODO: check, also neighbors[dir] might be optionally null
    }
    
    // Find the parent and the child-index of the square we want to locate or create.
    var p:QuadSquare = null;
    
    var    index:int =  cd.ChildIndex ^ 1 ^ ((dir & 1) << 1);
    var    SameParent:Boolean = ((dir - cd.ChildIndex) & 2) ? true : false;
    
    if (SameParent) {
        p = cd.Parent.Square;
    } else {
        p = cd.Parent.Square.GetNeighbor(dir, cd.Parent);
        if (p == null) return null;
    }
    
    return p.Child[index];
}



public function    AddHeightMap(cd:QuadCornerData,  hm:HeightMapInfo ):void
// Sets the height of all samples within the specified rectangular
// region using the given array of floats.  Extends the tree to the
// level of detail defined by (1 << hm.Scale) as necessary.
{
    // If block is outside rectangle, then don't bother.
    var BlockSize:int = 2 << cd.Level;
    if (cd.xorg > hm.XOrigin + ((hm.XSize + 2) << hm.Scale) ||
        cd.xorg + BlockSize < hm.XOrigin - (1 << hm.Scale) ||
        cd.zorg > hm.ZOrigin + ((hm.ZSize + 2) << hm.Scale) ||
        cd.zorg + BlockSize < hm.ZOrigin - (1 << hm.Scale))
    {
        // This square does not touch the given height array area; no need to modify this square or descendants.
        return;
    }
    
    // Is this enabling on the fly really necessary?
    /*
    if (cd.Parent && cd.Parent.Square) {
        cd.Parent.Square.EnableChild(cd.ChildIndex, cd.Parent);    // causes parent edge verts to be enabled, possibly causing neighbor blocks to be created.
    }
    */
    
    cd.Verts[0] = hm.Sample( cd.xorg + BlockSize, cd.zorg);
    cd.Verts[1] = hm.Sample( cd.xorg, cd.zorg);
    cd.Verts[2] = hm.Sample( cd.xorg, cd.zorg+BlockSize);
    cd.Verts[3] = hm.Sample( cd.xorg + BlockSize, cd.zorg+BlockSize);
    
    var    i:int
    
    var half:int = 1 << cd.Level;
    var size:int = half << 1;
    
    // Create and update child nodes.
    for (i = 0; i < 4; i++) {
        var    q:QuadCornerData = new QuadCornerData(); 
        SetupCornerData(q, cd, i);

        if (cd.Level < hm.Scale) continue;
        
        if (Child[i] == null) {  // && 
            // Create child node w/ current (unmodified) values for corner verts.
            Child[i]  = new QuadSquare(q);
        }
        
        // Recurse.
        //if (Child[i]) {
            Child[i].AddHeightMap(q, hm);
        //}
    }
    
    // Deviate vertex heights based on data sampled from heightmap.
    var    s:Vector.<int> = new Vector.<int>(5,true);
    s[0] = hm.Sample(cd.xorg + half, cd.zorg + half);
    s[1] = hm.Sample(cd.xorg + (half<<1), cd.zorg + half);
    s[2] = hm.Sample(cd.xorg + half, cd.zorg);
    s[3] = hm.Sample(cd.xorg, cd.zorg + half);
    s[4] = hm.Sample(cd.xorg + half, cd.zorg + (half<<1));

    // Modify the vertex heights if necessary, and set the dirty
    // flag if any modifications occur, so that we know we need to
    // recompute error data later.
    for (i = 0; i < 5; i++) {
        //if (s[i] != 0) {
            Dirty = true;
            Vertex[i] = s[i];  //+   dont deviate
        //}
    }

    if (!Dirty) {
        // Check to see if any child nodes are dirty, and set the dirty flag if so.
        for (i = 0; i < 4; i++) {
            if (Child[i] && Child[i].Dirty) {
                Dirty = true;
                break;
            }
        }
    }

    if (Dirty) SetStatic(cd);
}
        
private static const STACK:Vector.<int> = new Vector.<int>(32, true);
private function EnableEdgeVertex( index:int,  IncrementCount:Boolean, cd:QuadCornerData):void
// Enable the specified edge vertex.  Indices go { e, n, w, s }.
// Increments the appropriate reference-count if IncrementCount is true.
{
    if ((EnabledFlags & (1 << index)) && !IncrementCount) return;
    
    // Turn on flag and deal with reference count.
    EnabledFlags |= 1 << index;
    if (IncrementCount  && (index == 0 || index == 3)) {
        SubEnabledCount[index & 1]++;
    }

    // Now we need to enable the opposite edge vertex of the adjacent square (i.e. the alias vertex).

    // This is a little tricky, since the desired neighbor node may not exist, in which
    // case we have to create it, in order to prevent cracks.  Creating it may in turn cause
    // further edge vertices to be enabled, propagating updates through the tree.

    // The sticking point is the quadcornerdata list, which
    // conceptually is just a linked list of activation structures.
    // In this function, however, we will introduce branching into
    // the "list", making it in actuality a tree.  This is all kind
    // of obscure and hard to explain in words, but basically what
    // it means is that our implementation has to be properly
    // recursive.

    // Travel upwards through the tree, looking for the parent in common with our desired neighbor.
    // Remember the path through the tree, so we can travel down the complementary path to get to the neighbor.
    var p:QuadSquare = this;
    var pcd:QuadCornerData = cd;
    var    ct:int = 0;
    const stack:Vector.<int>= STACK;  // TODO: Buffer this and check that it's okay!
    for (;;) {
        var    ci:int = pcd.ChildIndex;

        if (pcd.Parent == null || pcd.Parent.Square == null) {

            
            //if ( !(pcd is QuadCornerDataNeighbor) ) return;
            
            pcd = pcd is QuadCornerDataNeighbor ? getNeighborCornerData( (pcd as QuadCornerDataNeighbor), index ) : pcd;
            if (pcd == null)  return;
            
            p = pcd.Square;
    //    if (p == null) throw new Error("WRR!");
            if (ct > 0) break;
            //else {  // duplicate of below
                index ^= 2;
                p.EnabledFlags |= (1 << index);
                if (IncrementCount  && (index == 0 || index == 3)) {
                    p.SubEnabledCount[index & 1]++;
                }
                return;
            //}
        }
        p = pcd.Parent.Square;
        pcd = pcd.Parent;

        var    SameParent:int = ((index - ci) & 2);
        
        ci = ci ^ 1 ^ ((index & 1) << 1);    // Child index of neighbor node.
        stack[ct++] = ci;
        
        
        if (SameParent) break;
    }

    // Get a pointer to our neighbor (create if necessary), by walking down
    // the quadtree from our shared ancestor.
    p = p.EnableDescendant(ct, stack, pcd);
    
/*
    // (Old legacy code i think..) Travel down the tree towards our neighbor, enabling and creating nodes as necessary.  We'll
    // follow the complement of the path we used on the way up the tree.
    quadcornerdata    d[16];
    int    i;
    for (i = 0; i < ct; i++) {
        int    ci = stack[ct-i-1];

        if (p.Child[ci] == NULL && CreateDepth == 0) CreateDepth = ct-i;    //xxxxxxx
        
        if ((p.EnabledFlags & (16 << ci)) == 0) {
            p.EnableChild(ci, *pcd);
        }
        p.SetupCornerData(&d[i], *pcd, ci);
        p = p.Child[ci];
        pcd = &d[i];
    }
*/

    // Finally: enable the vertex on the opposite edge of our neighbor, the alias of the original vertex.
    index ^= 2;
    p.EnabledFlags |= (1 << index);
    if (IncrementCount  && (index == 0 || index == 3)) {
        p.SubEnabledCount[index & 1]++;
    }
}



private function EnableDescendant( count:int,  path:Vector.<int>, cd:QuadCornerData):QuadSquare
// This function enables the descendant node 'count' generations below
// us, located by following the list of child indices in path[].
// Creates the node if necessary, and returns a pointer to it.
{
    count--;
    var    ChildIndex:int = path[count];

    if ((EnabledFlags & (16 << ChildIndex)) == 0) {
        EnableChild(ChildIndex, cd);
    }
    
    if (count > 0) { // more than 1 index in path, need to recurse until end
        var    q:QuadCornerData = QuadCornerData.create();
        SetupCornerData(q, cd, ChildIndex);
        return Child[ChildIndex].EnableDescendant(count, path, q);
    } else {
        return Child[ChildIndex];
    }
}


private function CreateChild( index:int, cd:QuadCornerData):void
// Creates a child square at the specified index.
{
    if (Child[index] == null) {
        var    q:QuadCornerData = QuadCornerData.create();
        SetupCornerData(q, cd, index);
        
        Child[index] = new QuadSquare(q);
    }
}


private function  EnableChild(index:int, cd:QuadCornerData):void
// Enable the indexed child node.  { ne, nw, sw, se }
// Causes dependent edge vertices to be enabled.
{
    if (cd.Level < 8) throw new Error("SHOULD not allow!");
    
    if ((EnabledFlags & (16 << index)) == 0) {
        EnabledFlags |= (16 << index);
        EnableEdgeVertex(index, true, cd);
        EnableEdgeVertex((index + 1) & 3, true, cd);
        
        if (Child[index] == null) {
            CreateChild(index, cd);
        }
        
        /*
        if ( (Child[index].EnabledFlags & 240) && Child[index].kdNode.quad!=null ) {
            // can collect back quad..
            //if (kdNode.quad != null) throw new Error("To collect back quad as it won't be used!");
            throw new Error("STILL HAVE leaves to render!");
        //    kdNode.quad = null;
        }
        */
        
        // If enable child on a leaf level (ie. 1024 tile size and quad level size 512), must
        // plant QuadRenderData for object at that level!
        
        
        
    }
}


public static var BlockDeleteCount:int = 0;    //xxxxx
public static var BlockUpdateCount:int = 0;    //xxxxx


private function NotifyChildDisable( cd:QuadCornerData, index:int):void
// Marks the indexed child quadrant as disabled.  Deletes the child node
// if it isn't static.
{
                
    //if (cd.Level < 8) throw new Error("FAILED!");
    // Clear enabled flag for the child.

    EnabledFlags &= ~(16 << index);
    
    // Update child enabled counts for the affected edge verts.
    var s:QuadSquare;
    
    if (index & 2) s = this;
    else s = GetNeighbor(1, cd);
    if (s) {
        s.SubEnabledCount[1]--;
    }
    
    if (index == 1 || index == 2) s = GetNeighbor(2, cd);
    else s = this;
    if (s) {
        s.SubEnabledCount[0]--;
    }

    
    if ( !Child[index].Static ) {
        //delete Child[index];
        Child[index] = null;
        BlockDeleteCount++;//xxxxx
    }
}

public function    ResetTree():void
// Clear all enabled flags, and delete all non-static child nodes.
{
    for (var i:int = 0; i < 4; i++) {
        if (Child[i]) {
            Child[i].ResetTree();
            if (!Child[i].Static) {
                Child[i].destroy();
                Child[i] = null;
            }
        }
    }
    EnabledFlags = 0;
    SubEnabledCount[0] = 0;
    SubEnabledCount[1] = 0;
    Dirty = true;
}


///*
public function StaticCullData(cd:QuadCornerData, ThresholdDetail:Number):void
// Examine the tree and remove nodes which don't contain necessary
// detail.  Necessary detail is defined as vertex data with a
// edge-length to height ratio less than ThresholdDetail.
{
    // First, clean non-static nodes out of the tree.
    ResetTree();

    // Make sure error values are up-to-date.
    if (Dirty) RecomputeErrorAndLighting(cd);
    
    // Recursively check all the nodes and do necessary removal.
    // We must start at the bottom of the tree, and do one level of
    // the tree at a time, to ensure the dependencies are accounted
    // for properly.
    var    level:int;
    for (level = 0; level < 15; level++) {
        StaticCullAux(cd, ThresholdDetail, level);
    }
}

public function UpdateStatics(cd:QuadCornerData, camera:Vector3D, focalLen:Number, Detail:Number, culler:ICuller, culling:int):void {
    DetailThreshold = Detail * VERTICAL_SCALE;
    FocalLength = focalLen;
    
    QuadCornerData.BI = 0;
    if (cd.Square.Static) UpdateAux(cd, camera, 0, culler, culling);
    else UpdateStaticsAux(cd, camera, culler, culling);
}

private function UpdateStaticsAux(cd:QuadCornerData, camera:Vector3D, culler:ICuller, culling:int):void {
    if (culling < 0) return;
    
    for (var i:int = 0; i < 4; i++) {
        var child:QuadSquare = Child[i];
        if (child != null  ) {
            var q:QuadCornerData =  QuadCornerData.create();
            SetupCornerData(q, cd, i);
            if (!child.Static) {
                child.UpdateStaticsAux(q, camera, culler, culling != 0 ? culler.cullingInFrustum(culling, q.xorg, q.zorg, q.Square.MinY, q.xorg + (1 << cd.Level), q.zorg + (1 << cd.Level), q.Square.MaxY) : 0 );    
            }
            else {
                
                child.UpdateAux(q, camera, 0, culler, culling != 0 ? culler.cullingInFrustum(culling, q.xorg, q.zorg, q.Square.MinY, q.xorg + (1 << cd.Level), q.zorg + (1 << cd.Level), q.Square.MaxY) : 0 );
            }
        }
    }
}


public    function StaticCullAux(cd:QuadCornerData,  ThresholdDetail:Number,  TargetLevel:int):void
// Check this node and its descendents, and remove nodes which don't contain
// necessary detail.
{
    var    i:int, j:int;
    var    q:QuadCornerData;

    if (cd.Level > TargetLevel) {
        // Just recurse to child nodes.
        for (j = 0; j < 4; j++) {
            if (j < 2) i = 1 - j;
            else i = j;

            if (Child[i]) {
                q = QuadCornerData.create();
                SetupCornerData(q, cd, i);
                Child[i].StaticCullAux(q, ThresholdDetail, TargetLevel);
                
            }
        }
        return;
    }

    // We're at the target level.  Check this node to see if it's OK to delete it.
    
    // Check edge vertices to see if they're necessary.
    var    size:Number = 2 << cd.Level;    // Edge length.
    if (Child[0] == null && Child[3] == null && errorList[0] * ThresholdDetail < size) {
        var    s:QuadSquare = GetNeighbor(0, cd);
        if (s == null || (s.Child[1] == null && s.Child[2] == null)) {

            // Force vertex height to the edge value.
            var    y:Number = (cd.Verts[0] + cd.Verts[3]) * 0.5;
            Vertex[1] = y;
            errorList[0] = 0;
            
            // Force alias vertex to match.
            if (s) s.Vertex[3] = y;
            
            Dirty = true;
        }
    }

    if (Child[2] == null && Child[3] == null && errorList[1] * ThresholdDetail < size) {
        s= GetNeighbor(3, cd);
        if (s == null || (s.Child[0] == null && s.Child[1] == null)) {
            y = (cd.Verts[2] + cd.Verts[3]) * 0.5;
            Vertex[4] = y;
            errorList[1] = 0;
            
            if (s) s.Vertex[2] = y;
            
            Dirty = true;
        }
    }

    // See if we have child nodes.
    var    StaticChildren:Boolean = false;
    for (i = 0; i < 4; i++) {
        if (Child[i]) {
            StaticChildren = true;
            if (Child[i].Dirty) Dirty = true;
        }
    }

    // If we have no children and no necessary edges, then see if we can delete ourself.
    if ( !StaticChildren  && cd.Parent != null) {
        var    NecessaryEdges:Boolean = false;
        for (i = 0; i < 4; i++) {
            // See if vertex deviates from edge between corners.
            var    diff:Number = Math.abs(Vertex[i+1] - (cd.Verts[i] + cd.Verts[(i+3)&3]) * 0.5);
            if (diff > 0.00001) {
                NecessaryEdges = true;
            }
        }

        if (!NecessaryEdges) {
            size *= 1.414213562;    // sqrt(2), because diagonal is longer than side.
            if (cd.Parent.Square.errorList[2 + cd.ChildIndex] * ThresholdDetail < size) {
                cd.Parent.Square.Child[cd.ChildIndex].destroy();    // Delete this.
                cd.Parent.Square.Child[cd.ChildIndex] = null;    // Clear the pointer.
            }
        }
    }
}


///*
 public function RecomputeErrorAndLighting(cd:QuadCornerData):Number 
// Recomputes the error values for this tree.  Returns the
// max error.
// Also updates MinY & MaxY.
// Also computes quick & dirty vertex lighting for the demo.
{
    var    i:int;
    var    y:Number;
    // Measure error of center and edge vertices.
    var    maxerror:Number = 0;

    // Compute error of center vert.
    var    e:Number;
    if (cd.ChildIndex & 1) {
        e = Math.abs(Vertex[0] - (cd.Verts[1] + cd.Verts[3]) * 0.5);
    } else {
        e = Math.abs(Vertex[0] - (cd.Verts[0] + cd.Verts[2]) * 0.5);
    }
    if (e > maxerror) maxerror = e;

    // Initial min/max.
    MaxY = Vertex[0];
    MinY = Vertex[0];

    // Check min/max of corners.
    for (i = 0; i < 4; i++) {
        y = cd.Verts[i];
        if (y < MinY) MinY = y;
        if (y > MaxY) MaxY = y;
    }
    
    // Edge verts.
    e = Math.abs(Vertex[1] - (cd.Verts[0] + cd.Verts[3]) * 0.5);
    if (e > maxerror) maxerror = e;
    errorList[0] = e;
    
    e = Math.abs(Vertex[4] - (cd.Verts[2] + cd.Verts[3]) * 0.5);
    if (e > maxerror) maxerror = e;
    errorList[1] = e;

    // Min/max of edge verts.
    for (i = 0; i < 4; i++) {
        y = Vertex[1 + i];
        if (y < MinY) MinY = y;
        if (y > MaxY) MaxY = y;
    }
    
    // Check child squares.
    for (i = 0; i < 4; i++) {
        
        if (Child[i]) {
            var    q:QuadCornerData = new QuadCornerData();
            SetupCornerData(q, cd, i);
            errorList[i+2] = Child[i].RecomputeErrorAndLighting(q);

            if (Child[i].MinY < MinY) MinY = Child[i].MinY;
            if (Child[i].MaxY > MaxY) MaxY = Child[i].MaxY;
            
    
        } else {
            // Compute difference between bilinear average at child center, and diagonal edge approximation.
            errorList[i+2] = Math.abs((Vertex[0] + cd.Verts[i]) - (Vertex[i+1] + Vertex[((i+1)&3) + 1])) * 0.25;
        }
        if (errorList[i+2] > maxerror) maxerror = errorList[i+2];
    }


    //
    // Compute quickie demo lighting.
    //
    //removed off...not required for this case.

    // The error, MinY/MaxY, and lighting values for this node and descendants are correct now.
    Dirty = false;
    
    return maxerror;
}
//*/

        

public function    VertexTest(x:Number,  y:Number,  z:Number, error:Number,  camera:Vector3D):Boolean
// Returns true if the vertex at (x,z) with the given world-space error between
// its interpolated location and its true location, should be enabled, given that
// the viewpoint is located at Viewer[].
{    
    var    dx:Number = Math.abs(x - camera.x);
    var    dy:Number = Math.abs(y - camera.z);
    var    dz:Number = Math.abs(z - camera.y);
    var    d:Number = dx;
    if (dy > d) d = dy;
    if (dz > d) d = dz;

    return (error * DetailThreshold) > d;
}

public function VertexMipTest(x:Number, y:Number, z:Number, error:Number, camera:Vector3D):Boolean {
//return true;
    var    dx:Number = Math.abs(x - camera.x);
    var    dy:Number = Math.abs(y - camera.z);
    var    dz:Number = Math.abs(z - camera.y);
    var    d:Number = dx;
    if (dy > d) d = dy;
    if (dz > d) d = dz;

    const f:Number = FocalLength * 2;  //*res
    return d < f || (1 + Math.log(d / f) * 1.442695040888963387) < 2 || (error * DetailThreshold) > d;
}

private static var COUNT:int = 0;

public function    BoxTest(x:Number, z:Number, size:Number, miny:Number, maxy:Number, error:Number, camera:Vector3D):Boolean
// Returns true if any vertex within the specified box (origin at x,z,
// edges of length size) with the given error value could be enabled
// based on the given viewer location.
{
    // Find the minimum distance to the box. 
    // Got to check orientation for this...
    var    half:Number = size * 0.5;
    var    dx:Number = Math.abs(x + half - camera.x) - half;
    var    dy:Number =  Math.abs((miny + maxy) * 0.5 - camera.z) - (maxy - miny) * 0.5;  
    var    dz:Number =  Math.abs(z + half - camera.y) - half;
    var    d:Number = dx;
    if (dy > d) d = dy;
    if (dz > d) d = dz;
    
    return (error * DetailThreshold) > d;
}

public function BoxMipTest(x:Number, z:Number, size:Number, miny:Number, maxy:Number, error:Number, camera:Vector3D, cd:QuadCornerData):Boolean {
    //return true;
    
    // Find the minimum distance to the box. 
    // Got to check orientation for this...
    var    half:Number = size * 0.5;
    var    dx:Number = Math.abs(x + half - camera.x) - half;
    var    dy:Number =  Math.abs((miny + maxy) * 0.5 - camera.z) - (maxy - miny) * 0.5;  
    var    dz:Number =  Math.abs(z + half - camera.y) - half;
    var    d:Number = dx;
    if (dy > d) d = dy;
    if (dz > d) d = dz;
    
    const f:Number = FocalLength * 2; //*res
    return d < f || (1 + Math.log(d / f) * 1.442695040888963387) < 2 || (error * DetailThreshold) > d;
}


//const float    VERTICAL_SCALE = 1.0 / 8.0;
public static const     VERTICAL_SCALE:Number = 1.0;


public function    Update(cd:QuadCornerData,  camera:Vector3D, focalLen:Number, Detail:Number, culler:ICuller, culling:int):void
// Refresh the vertex enabled states in the tree, according to the
// location of the viewer.  May force creation or deletion of qsquares
// in areas which need to be interpolated.
{
    QuadCornerData.BI = 0;
    DetailThreshold = Detail * VERTICAL_SCALE;
    FocalLength = focalLen;
    UpdateAux(cd, camera, 0, culler, culling);
}



public    function UpdateAux(cd:QuadCornerData, camera:Vector3D, CenterError:Number, culler:ICuller, culling:int):void
// Does the actual work of updating enabled states and tree growing/shrinking.
{
    // Make sure error values are current.
    if (Dirty) {
        RecomputeErrorAndLighting(cd);
    }
    
    //if (culling < 0) return;
    

    // set culling value
    
    BlockUpdateCount++;    //xxxxx

    var    half:int = 1 << cd.Level;
    var    whole:int = half << 1;
    var    s:QuadSquare;
    

    // See about enabling child verts.
        if ((EnabledFlags & 1) == 0 && VertexTest(cd.xorg + whole, Vertex[1], cd.zorg + half, errorList[0], camera)) EnableEdgeVertex(0, false, cd);    // East vert.
        if ((EnabledFlags & 8) == 0 && VertexTest(cd.xorg + half, Vertex[4], cd.zorg + whole, errorList[1], camera)) EnableEdgeVertex(3, false, cd);    // South vert.
    if (culling >=0 && cd.Level > 8) {  // min LOD level 2^8=256
    
        if ((EnabledFlags & 32) == 0) {
            if ( BoxTest(cd.xorg, cd.zorg, half, MinY, MaxY, errorList[3], camera)  ) {
                EnableChild(1, cd);    // nw child.er
            }
        }
        if ((EnabledFlags & 16) == 0) {
            if ( VertexTest(cd.xorg + half, cd.zorg, half, errorList[2], camera) ) EnableChild(0, cd);    // ne child.
        }
        if ((EnabledFlags & 64) == 0) {
            if ( VertexTest(cd.xorg, cd.zorg + half, half,  errorList[4], camera)  ) EnableChild(2, cd);    // sw child.
        }
        if ((EnabledFlags & 128) == 0) {
            if ( VertexTest(cd.xorg + half, cd.zorg + half, half, errorList[5], camera)  ) EnableChild(3, cd);    // se child.
        }
        
        // Recurse into child quadrants as necessary.
        var    q:QuadCornerData; 
            
        if (EnabledFlags & 32) {
            SetupCornerData(q= QuadCornerData.create(), cd, 1);
            Child[1].UpdateAux(q, camera, errorList[3], culler, culling != 0 ? culler.cullingInFrustum(culling, q.xorg, q.zorg, q.Square.MinY, q.xorg + half, q.zorg + half, q.Square.MaxY) : 0);
        }
        if (EnabledFlags & 16) {
            SetupCornerData(q=QuadCornerData.create(), cd, 0);
            Child[0].UpdateAux(q, camera, errorList[2], culler, culling != 0 ?culler.cullingInFrustum(culling, q.xorg, q.zorg, q.Square.MinY, q.xorg + half, q.zorg + half, q.Square.MaxY) : 0 );        
        }
        if (EnabledFlags & 64) {
            SetupCornerData(q=QuadCornerData.create(), cd, 2);
            Child[2].UpdateAux(q, camera, errorList[4], culler, culling != 0 ?culler.cullingInFrustum(culling, q.xorg, q.zorg, q.Square.MinY, q.xorg + half, q.zorg + half, q.Square.MaxY) : 0 );        
        }
        if (EnabledFlags & 128) {
            SetupCornerData(q=QuadCornerData.create(), cd, 3);
            Child[3].UpdateAux(q, camera, errorList[5], culler, culling != 0 ?culler.cullingInFrustum(culling, q.xorg, q.zorg, q.Square.MinY, q.xorg + half, q.zorg + half, q.Square.MaxY) : 0);
        }
    }
    
    // Test for disabling.  East, South, and center.
    if ((EnabledFlags & 1) && SubEnabledCount[0] == 0 && !VertexTest(cd.xorg + whole, Vertex[1], cd.zorg + half, errorList[0], camera) ) {
        EnabledFlags &= ~1;
        s = GetNeighbor(0, cd);
        if (s) s.EnabledFlags &= ~4;
    }
    if ((EnabledFlags & 8) && SubEnabledCount[1] == 0 && !VertexTest(cd.xorg + half, Vertex[4], cd.zorg + whole, errorList[1], camera) ) {
        EnabledFlags &= ~8;
        s = GetNeighbor(3, cd);
        if (s) s.EnabledFlags &= ~2;
    }
    if ( EnabledFlags ==0 &&
        cd.Parent != null &&
        !BoxTest(cd.xorg, cd.zorg, whole, MinY, MaxY, CenterError, camera) )
    {
        // Disable ourself.
        cd.Parent.Square.NotifyChildDisable(cd.Parent, cd.ChildIndex);    // nb: possibly deletes 'this'.
    }
}



}



    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.DisplayObjectContainer;
    import flash.display.Sprite;
    import flash.geom.Matrix;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    import flash.utils.Dictionary;
    /**
     * ...
     * @author Glidias
     */
    class QuadCornerDataNeighbor extends QuadCornerData
    {
        public var neighbors:Vector.<QuadCornerData> = new Vector.<QuadCornerData>(4, true);
        
        // optional (store related heightmap info)
        public var heightMap:HeightMapInfo;
        
        public function QuadCornerDataNeighbor() 
        {
            
        }
        
        public function setNeighbourQuads(array:QuadCornerData, array1:QuadCornerData, array2:QuadCornerData, array3:QuadCornerData):void 
        {
            neighbors[0] = array;
            neighbors[1] = array1;
            neighbors[2] = array2;
            neighbors[3] = array3;
        }
        
    
        

        
        public static function create3x3WithBitmapData(bmpData:BitmapData, heightMult:Number, heightMin:Number, testCont:DisplayObjectContainer=null):Vector.<QuadCornerDataNeighbor> {
        
            if ( int(bmpData.width / 3) !=  bmpData.width / 3  || int(bmpData.height / 3) !=  bmpData.height / 3  ) {
            
                throw new Error("Dimensions not divisible by 3!");
            }
            
            const cellWidth:int = bmpData.width / 3 + 2;
            const cellHeight:int = bmpData.height / 3 + 2;
            const baseWidth:int = bmpData.width / 3;
            const baseHeight:int = bmpData.height / 3;
    
            
            var vec:Vector.<QuadCornerDataNeighbor> = new Vector.<QuadCornerDataNeighbor>(9, true);
            var temp:BitmapData = new BitmapData(cellWidth, cellHeight, false, 0);
        
            const rect:Rectangle = new Rectangle();
        
            var count:int = 0;
            var qd:QuadCornerDataNeighbor;
            const point:Point = new Point();
            for (var v:int = 0; v < 3; v++) {
                for (var u:int = 0; u < 3; u++) {
                    count++;
                    temp.fillRect(temp.rect, 0);
                    
                    // BASE
                    rect.x = u * baseWidth;
                    rect.y = v * baseHeight;  
                    rect.width = baseWidth;
                    rect.height = baseHeight;
                    point.x = 1;
                    point.y = 1;
                    temp.copyPixels(bmpData, rect, point);
                    
                    
                    // LEFT EDGE
                    ///*
                    point.x = 0;
                    point.y = 1;
                    rect.x = u * baseWidth - 1; 
                    rect.x = rect.x < 0 ? bmpData.width-1 : rect.x >= bmpData.width ? 0 : rect.x;
                    rect.y = v * baseHeight; 
            
    
                    rect.width = 1;
                    rect.height = baseHeight;
                    weldBorderBmpData(bmpData, rect, temp, point, 1);
                    //*/
                    

                    // RIGHT EDGE
                //    /*
                    point.x = cellWidth -1;
                    point.y = 1;
                    rect.x = u * baseWidth + baseWidth; 
                    rect.x = rect.x < 0 ? bmpData.width-1 : rect.x >= bmpData.width ? 0 : rect.x;
                    rect.y = v * baseHeight; 
                
                    
                    rect.width = 1;
                    rect.height = baseHeight;
                    weldBorderBmpData(bmpData, rect, temp, point, -1);
                    //*/
                                
                    
                    // TOP EDGE
                //    /*
                    
                    point.x = 1;
                    point.y = 0;
                    rect.x = u * baseWidth; 
                    rect.y = v * baseHeight - 1; 
                    rect.y = rect.y < 0 ? bmpData.height-1 : rect.y >= bmpData.height ? 0 : rect.y;
                    
                    
                    rect.width = baseWidth;
                    rect.height = 1;
                    weldBorderBmpData(bmpData, rect, temp, point, 1);
                    //*/
                    
                    
                    // BOTTOM EDGE
                    ///*
                    point.x = 1;
                    point.y = cellHeight - 1;
                    rect.x = u * baseWidth; 
                    rect.y = v * baseHeight + baseHeight; 
                    rect.y = rect.y < 0 ? bmpData.height-1 : rect.y >= bmpData.height ? 0 : rect.y;
                    
    
                    
                    rect.width = baseWidth;
                    rect.height = 1;
                    weldBorderBmpData(bmpData, rect, temp, point, -1);
                //    */
                
                    // Top left
                    point.x = u * baseWidth; 
                    point.y = v * baseHeight;
                    temp.setPixel(0, 0, getAverageColors(bmpData, [point.x, point.y,   point.x - 1, point.y -1,   point.x -1, point.y, point.x, point.y - 1]) );
                    
                    // Bottom left
                    point.x = u * baseWidth; 
                    point.y = v * baseHeight + baseHeight - 1;
                    temp.setPixel(0, cellHeight - 1, getAverageColors(bmpData, [point.x, point.y,   point.x - 1, point.y +1,   point.x -1, point.y, point.x, point.y + 1]) );
                    
                    // Top-right
                    point.x = u * baseWidth + baseWidth - 1; 
                    point.y = v * baseHeight;
                    temp.setPixel(cellWidth - 1, 0, getAverageColors(bmpData, [point.x, point.y,   point.x + 1, point.y -1,   point.x +1, point.y,  point.x, point.y - 1]) );
                    
                    // Bottom-right
                    point.x = u * baseWidth + baseWidth - 1; 
                    point.y = v * baseHeight + baseHeight - 1; 
                    temp.setPixel(cellWidth - 1, cellHeight - 1, getAverageColors(bmpData, [point.x, point.y,   point.x + 1, point.y +1,   point.x +1, point.y,  point.x, point.y + 1]) );

                    vec[v * 3 + u] = qd = QuadCornerData.createRoot( 0, 0, (cellWidth-1)* 256, true ) as QuadCornerDataNeighbor;
                    qd.Square.AddHeightMap( qd, qd.heightMap=HeightMapInfo.createFromBmpData(temp, 0, 0, heightMult, heightMin) );
                    qd.Square.RecomputeErrorAndLighting(qd);
                    //return vec;
                }
                
            }
            
            //
            link9NeighbourQuads(vec);
        //    temp.dispose();
            return vec;
        }

        
        private static function weldBorderBmpData(srcBmpData:BitmapData, rect:Rectangle, destBmpData:BitmapData, destPt:Point, delta:int):void {
            var limit:int = rect.width > 1 ? rect.width : rect.height;
            var xi:int = rect.width > 1 ? 1 : 0;
            var yi:int = rect.width > 1 ? 0 : 1;
            var xii:int = xi == 0 ? delta : 0;
            var yii:int = yi == 0 ? delta : 0;
            
            
            
            var x:int = rect.x;
            var y:int = rect.y;
            var xp:int = destPt.x;
            var yp:int = destPt.y;
            var r:int;
            var g:int;
            var b:int;
            var r2:int;
            var g2:int;
            var b2:int;
            var color:int; 
            var color2:int;
            for (var i:int = 0; i < limit; i++) {
                var ox:int = x + xii;
                var oy:int = y + yii;
                //if (xi + yi != 1) throw new Error("Mismatch22!");
                //if (Math.abs(xii + yii) != 1) throw new Error("Mismatch33!");
                ox = ox < 0 ? srcBmpData.width-1 : ox >= srcBmpData.width ? 0 :ox;
                oy = oy < 0 ? srcBmpData.height-1 : oy >= srcBmpData.height ? 0 : oy;
                


                color = srcBmpData.getPixel(x, y);
                r = color >> 16 & 0xFF;
                g = color >> 8 & 0xFF;
                b = color & 0xFF;
                
            
                
                color2 = srcBmpData.getPixel(ox, oy);
                r2 = color2 >> 16 & 0xFF;
                g2 = color2 >> 8 & 0xFF;
                b2 = color2 & 0xFF;
                
                r = (r + r2) / 2;
                g = (g + g2) / 2;
                b = (b + b2) / 2;
                color = (r << 16) | (g << 8) | b;
                destBmpData.setPixel(xp, yp, color );
                x += xi;
                y += yi;
                xp += xi;
                yp += yi;
            }
        }
        
        private static function getAverageColors(srcBmpData:BitmapData, positions:Array):uint {
            var numPos:int = positions.length / 2;
            var x:int;
            var y:int;
            var color:int;
            var r:int;
            var g:int;
            var b:int;
            var rValues:Array = [];
            var gValues:Array = [];
            var bValues:Array = [];
            for (var i:int = 0; i < numPos; i++) {
                x = positions[i*2];
                y = positions[i*2 + 1];
                x = x < 0 ? srcBmpData.width-1 : x >= srcBmpData.width ? 0 :x;
                y = y < 0 ? srcBmpData.height-1 : y >= srcBmpData.height ? 0 : y;
                color = srcBmpData.getPixel(x, y);
                r = color >> 16 & 0xFF;
                g = color >> 8 & 0xFF;
                b = color & 0xFF;
                rValues.push(r);
                gValues.push(g);
                bValues.push(b);
            }
            
            r = 0;
            g = 0;
            b = 0;
            for (i = 0; i < numPos; i++) {
                r += rValues[i];
                g += rValues[i];
                b+= rValues[i];
            }
            r= r / numPos;
            g=g / numPos;
            b = b / numPos;
            color = (r << 16) | (g << 8) | b;
            return color;    
            
        }
        
        

        
        public static function link9NeighbourQuads(vec:Vector.<QuadCornerDataNeighbor>):void {
            vec[0].setNeighbourQuads(vec[1], vec[6], vec[2], vec[3]);
            
            vec[1].setNeighbourQuads(vec[2], vec[7], vec[0], vec[4]);
            
            vec[2].setNeighbourQuads(vec[0], vec[8], vec[1], vec[5]);
            
            vec[3].setNeighbourQuads(vec[4], vec[0], vec[5], vec[6]);
            
            vec[4].setNeighbourQuads(vec[5], vec[1], vec[3], vec[7]);
                
            vec[5].setNeighbourQuads(vec[3], vec[2], vec[4], vec[8]);
                    
            vec[6].setNeighbourQuads(vec[7], vec[3], vec[8], vec[0]);
                        
            vec[7].setNeighbourQuads(vec[8], vec[4], vec[6], vec[1]);
                            
            vec[8].setNeighbourQuads(vec[6], vec[5], vec[7], vec[2]);
        }


        public static function get9NeighbourQuads(arr:*):Vector.<QuadCornerDataNeighbor> {
            var vec:Vector.<QuadCornerDataNeighbor> = new Vector.<QuadCornerDataNeighbor>(9, true);
            for (var i:int = 0; i < 9; i++) {
                vec[i] = (arr[i] as INeighbourQuad).getQuad();
            }
            return vec;
        }
        
        
    }
    
    

    import alternativ7.engine3d.core.VG;
    /**
     * A structure to handle/hold depth sorting stuffs.
     * @author Glenn Ko
     */
     class QuadOrder
    {
        public static var BUFFER:Vector.<QuadOrder> = new Vector.<QuadOrder>();
        public static var BI:int = 0;
        public static var BLEN:int = 0;
        
        public var culling:Vector.<int> = new Vector.<int>(4, true);
        public var vg:Vector.<VG> = new Vector.<VG>(4, true);
    
        public static function create():QuadOrder {
            //return new QuadCornerData();
            var result:QuadOrder;
            if (BI < BLEN) {
                result = BUFFER[BI];
            }
            else {
                result = new QuadOrder();
                BUFFER[BLEN++] = result ;
            }
            BI++;
            return result;
        }

    }



    import alternativ7.engine3d.materials.Material;
    import alternativ7.engine3d.primitives.Box;
    
    /**
     * ...
     * @author Glenn Ko
     */
     class BoxIndexable extends Box
    {
        public var index:int;
        
        public function BoxIndexable(width:Number = 100, length:Number = 100, height:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, heightSegments:uint = 1, reverse:Boolean = false, triangulate:Boolean = false, left:Material = null, right:Material = null, back:Material = null, front:Material = null, bottom:Material = null, top:Material = null) 
        {
            super(width, length, height, widthSegments, lengthSegments, heightSegments, reverse, triangulate, left, right, back, front, bottom, top);
            
        }
        
    }


    import alternativ7.engine3d.alternativa3d;
    import alternativ7.engine3d.core.Camera3D;
    import alternativ7.engine3d.core.Canvas;
    import alternativ7.engine3d.core.Object3D;
    import alternativ7.engine3d.core.Object3DContainer;
    import alternativ7.engine3d.materials.FillMaterial;
    import alternativ7.engine3d.primitives.Box;
    

    use namespace alternativa3d;
    /**
     * ...
     * @author Glenn Ko
     */
     class MarkerBoxNumbered extends Object3DContainer
    {
        
        public function MarkerBoxNumbered(width:Number, length:Number, height:Number, label:String) 
        {
            var child:Object3D;
            var mBox:Box;
            child = addChild( mBox = new MarkerBox(Math.random() * 0xFFFFFF, width, length, height) );
            mBox.setMaterialToAllFaces( new FillMaterial(Math.random() * 0xFFFFFF, .25) );
            mBox.clipping = 2;
            child.x = width * .5;
            child.y = length * .5;
            child.z = height * .5;
            
        

            
            /*
            child = addChild( new Text3D(label, new HelveticaMedium() ) );
            child.x = width * .5 * 2;
            child.y = length * .5 * 2;
            child.z = height * .5;
            s*/
        }
        
        override alternativa3d function draw(camera:Camera3D, parentCanvas:Canvas):void {
        
            super.draw(camera, parentCanvas);
            //throw new Error("YES!");
        }
        
    }


    import alternativ7.engine3d.alternativa3d;
    import alternativ7.engine3d.core.Camera3D;
    import alternativ7.engine3d.materials.FillMaterial;
    import alternativ7.engine3d.materials.Material;
    import alternativ7.engine3d.primitives.Box;
    use namespace alternativa3d;
    
    /**
     * ...
     * @author Glenn Ko
     */
     class MarkerBox extends Box
    {
        
        public function MarkerBox(color:uint = 0x00FF00, width:Number = 10, length:Number = 10, height:Number = 10, widthSegments:uint = 1, lengthSegments:uint = 1, heightSegments:uint = 1, reverse:Boolean = false, triangulate:Boolean = false, left:Material = null, right:Material = null, back:Material = null, front:Material = null, bottom:Material = null, top:Material = null) 
        {
            var material:Material = new FillMaterial(color); 
            super(width, length, height, widthSegments, lengthSegments, heightSegments, reverse, triangulate, material, material, material, material, material, material);
            
        }
    

    
    }
    
    
    
    import alternativ7.engine3d.core.Camera3D;
    import alternativ7.engine3d.core.Canvas;
    import alternativ7.engine3d.core.Object3D;
    import alternativ7.engine3d.core.Object3DContainer;
    import flash.geom.Vector3D;
    import alternativ7.engine3d.alternativa3d;
    use namespace alternativa3d;
    /**
     * This is for handling pre-updates of all 9 connected terrain pieces prior to rendering them (may 
     * be more "stable", since we don't Update() and draw each piece indiviually, but ensure
     * all 9 are updated first.. Updates only occur if the camera changes it's position/focal length.
     * If this class is used, "doUpdate" boolean var flag for the involved terrain containers can be set to 'false'
     * as this class will perform the updating instead during it's public update() call.
     * @author Glidias
     */
     class OverworldTerrainContainer extends OverworldContainer implements ICuller
    {
        private var _lastCameraPosition:Vector3D = new Vector3D(Number.NaN, Number.NaN, Number.NaN);
        private var _cameraPosRelative:Vector3D = new Vector3D();
        private var _camRadius:Number = Number.NaN;
        private var _cameraSphere:Vector3D = new Vector3D();
        public var detail:Number = 30;
        public var waterCulling:Boolean = true;
        public var waterCullZ:Number;
        public var floor:Object3D;
    
        
        public function setDetail(val:Number):void {
            detail = val;
            _lastCameraPosition.x = NaN;
        }
        
        
        public function OverworldTerrainContainer(cellSize:int=8512, cellHeight:int = 0) 
        {
            super(cellSize, cellHeight);
        }
        
        override alternativa3d function collectPlanes(center:Vector3D, a:Vector3D, b:Vector3D, c:Vector3D, d:Vector3D, collector:Vector.<Face>, excludedObjects:Dictionary = null) : void {    
            if (floor != null) {
                floor.composeAndAppend(this);
                floor.collectPlanes(center, a, b, c, d, collector, excludedObjects);
            }
            super.collectPlanes(center, a, b, c, d, collector, excludedObjects);
        }
        
        override public function update(camera:Camera3D):void {
            
            super.update(camera);
        
            if ( _lastCameraPosition.x != _cameraPos.x || _lastCameraPosition.y != _cameraPos.y || _lastCameraPosition.z != z || _lastCameraPosition.w != camera.focalLength) { 
                
                
                _cameraSphere.x = _cameraPosGlobal.x - x;
                _cameraSphere.y = _cameraPosGlobal.y - y;
                _cameraSphere.z = _cameraPosGlobal.z - z;
                if (_lastCameraPosition.w != camera.focalLength) {
                    _cameraSphere.w = getFrustumRadius(camera);
                }
                
                
                for ( var i:int = 0; i < 9; i++) {
                    
                    var object:Object3D = cells[i].objectList as Object3D;

                    var neighbor:INeighbourQuad = object as INeighbourQuad;
                    
                    var quad:QuadCornerDataNeighbor = neighbor.getQuad();
                        
                    // Consider: global/local frustum check instead, but this might mean that camera view rotation changes will also require terrain Updates()!
                    if ( boundIntersectSphere(_cameraSphere, object.x, object.y, quad.Square.MinY, object.x + cellSize, object.y + cellSize, quad.Square.MaxY ) ) {  
                        // we assume always bottom-left origin placement
                        _cameraPosRelative.x = _cameraSphere.x - object.x;
                        _cameraPosRelative.y = _cameraSphere.y - object.y;
                        _cameraPosRelative.z = _cameraSphere.z - object.z;
                        _cameraPosRelative.w = _cameraSphere.w;
                        quad.xorg = 0;
                        quad.zorg = 0;
                        
                        // TOOD: proper frustum culling support? (but no camera transform yet!)
                        // unless we create one first....
                        quad.Square.UpdateStatics(quad, _cameraPosRelative, camera.focalLength, detail, this, 1);  // ICuller dependency hack atm
                    }
                }    
                
                _lastCameraPosition.x = _cameraPos.x;
                _lastCameraPosition.y = _cameraPos.y;
                _lastCameraPosition.z = _cameraPos.z;
                _lastCameraPosition.w = camera.focalLength;
            }

        }
        private static const ORIGIN:Vector3D= new Vector3D();
        private static const DIRECTION:Vector3D = new Vector3D();
        private static const RESULT:Vector3D = new Vector3D();
        private function getFrustumRadius(camera:Camera3D):Number // TODO: check this
        {
            camera.calculateRay(ORIGIN, DIRECTION, 0, 0);
            var scaler:Number = camera.farClipping / DIRECTION.z;
            RESULT.x= DIRECTION.x * scaler;
            RESULT.y=DIRECTION.y * scaler;
            RESULT.z = DIRECTION.z * scaler;
            return RESULT.length;
        }
        

        
        // Note, this isn't fully accruate actually because there's no Frustum before camera update.
        // Need a workaround possibly. For now, boundSphere checks are used only.
        public function cullingInFrustum(culling:int, boundMinX:Number, boundMinY:Number, boundMinZ:Number, boundMaxX:Number, boundMaxY:Number, boundMaxZ:Number):int 
        {
            
            return (waterCulling && boundMaxZ < waterCullZ) ? -1 : boundIntersectSphere(_cameraSphere, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ) ? 1 : -1;
        }
        
        
        override alternativa3d function draw(camera:Camera3D, parentCanvas:Canvas) : void {
            super.draw(camera, parentCanvas);
            if (floor != null && floor.visible) {
                floor.composeAndAppend(this);
                if (floor.cullingInCamera(camera,culling)>=0) floor.draw(camera, parentCanvas);
            }
        }
        
    }

//}


    import alternativ7.engine3d.core.Face;
    import alternativ7.engine3d.core.Vertex;
    import alternativ7.engine3d.core.Wrapper;
    import alternativ7.engine3d.alternativa3d;
    use namespace alternativa3d;
    
    /**
     * ...
     * @author Glidias
     */
     class DummyFace 
    {
        static public var DUMMY_FACE:Face;
        
        public static function getDummyFace():Face {
            return DUMMY_FACE || (DUMMY_FACE=createDummyFace());
        }
        
        static private function createDummyFace():Face 
        {
            var face:Face = new Face();
            var w:Wrapper;
            var v:Vertex;
            face.wrapper = w = new Wrapper();
            w.vertex = v = new Vertex();
            v.x = 0; v.y = 0; v.z = 0;
            
            w.next = w = new Wrapper();
            w.vertex = v = new Vertex();
            v.x = 0; v.y = 0; v.z = 1;
            
            w.next = w = new Wrapper();
            w.vertex = v = new Vertex();
            v.x = 0; v.y = 1; v.z = 1;
            
            face.normalX = 1;
            face.normalY = 0;
            face.normalZ = 0;
            face.offset = 0;
            return face;
        }
        
    }



    /**
     * Marker interface to indicate objects that can (instead of drawing Conflict "VG" geometry
     * directly, will delegate that task instead to the appropiate sub-target!).
     * (This is kind of a hack to get around inability to delegate dynamic geometry to nested trees).
     * @author Glidias
     */
     interface IVGDelegater 
    {
        function notifyVGSkip(delegate:Object3D):void;
        function get vgDrawList():VG;
    }
    
    class HashObject3D
    {
        // all variables for internal use, read-only!
        public var obj:Object3D;
        public var key:String;   // the current zone key
        
        public var index:int; 
        public var parent:HashObject3DList;
        
        public var next:HashObject3D;
        public static var collector:HashObject3D;
        
        public function HashObject3D() 
        {
            
        }
        
        public static function create(obj:Object3D, key:String):HashObject3D {
            var instance:HashObject3D = collector || new HashObject3D();
            collector = instance.next;
            instance.next = null;
            
            instance.obj = obj;
            instance.key = key;
            return instance;
        }
        
    
        
    }


    import alternativ7.engine3d.containers.DistanceSortContainer;
    import alternativ7.engine3d.core.Object3D;
    import alternativ7.engine3d.core.Object3DContainer;
    import alternativ7.engine3d.alternativa3d;

    use namespace alternativa3d;
    /**
     * ...
     * @author Glenn Ko
     */
     class HashObject3DList
    {
        public var vec:Vector.<HashObject3D> = new Vector.<HashObject3D>();
        public var len:int  = 0;
        public var container:Object3DContainer;
        public var disposable:Boolean;
        
        private static var CONTAINER_POOL:Object3D;

        public function HashObject3DList(container:Object3DContainer, disposable:Boolean) 
        {
            this.container = container;
            this.disposable = disposable;
        }
        
        public static function create(container:Object3DContainer):HashObject3DList {
            return new HashObject3DList(container, false);
        }
        
        public function migrateEntitiesTo(newContainer:Object3DContainer):void {
            
            if (container == null) {  
                newContainer.childrenList = len > 0 ? vec[0].obj : null;
                container = newContainer;
                return;
            }    
            
            container.childrenList = null; 
            
            if (disposable) {
                container.next = CONTAINER_POOL;
                CONTAINER_POOL = container;
            }
            
            newContainer.childrenList = len > 0 ? vec[0].obj : null;
            container = newContainer;
        }
        
        
        public static function getPoolableContainer():Object3DContainer {
            if (CONTAINER_POOL == null) {
                return new Object3DContainer();
            }
            var result:Object3D = CONTAINER_POOL;
            CONTAINER_POOL = result.next;
            result.next = null;
            return result as Object3DContainer;
        }
        
        public static function createNew(hashObject:HashObject3D):HashObject3DList {
    
            var instance:HashObject3DList = new HashObject3DList( null, true ); // should pool this
            
            instance.disposable = true;
            instance.vec[0] = hashObject;
            hashObject.parent = instance;
            hashObject.index = 0;
            instance.len = 1;
            
            return instance;
        }
        
        
        public function append(hashObj:HashObject3D):void {
            var tail:HashObject3D = vec[len - 1];
            tail.obj.next = hashObj.obj;
        
            hashObj.parent = this;
            hashObj.index = len;
        
            vec[len++] = hashObj;
        }
        
        public function remove(hashObj:HashObject3D):void {
            len--;
            
            hashObj.parent = null;
            hashObj.obj.next = null;

            if (hashObj.index ==  len) { // pop
                vec[len] = null;
                return;
            }
            
            // pop back
            var tail:HashObject3D = vec[len];
            vec[len - 1].obj.next = null;
            vec[len] = null;
            vec[tail.index = hashObj.index] = tail;
            if (hashObj.index > 0 ) {
                vec[hashObj.index - 1].obj.next = tail.obj;
            }
            tail.obj.next = tail.index + 1 < len  ? vec[tail.index + 1].obj : null; 
        }
        
        // if len == 1  (remove last entity) and cleanup
        public function removeAndDispose():void {
            vec[0].obj.next = null;
            dispose();
        }
        
        public function dispose():void {
            if (disposable && container!=null) {
                container.childrenList = null;
            
                container.visibleChildren.length = 0; // may omit htis
                container.numVisibleChildren = 0;  // may omit this
                container.next = CONTAINER_POOL;
                CONTAINER_POOL = container;
            }
            container = null;
            vec.length = 0;
            len  = 0;
        }
        
        // only allowed if dispoable and container!=null
        public function disposeContainer():void 
        {
            container.visibleChildren.length = 0; // may omit htis
            container.numVisibleChildren = 0;  // may omit this
            container.next = CONTAINER_POOL;
            CONTAINER_POOL = container;
            container = null;
        }
        
    }