forked from: Progressive LOD tree drawing test
forked from Progressive LOD terrain page loading (diff: 477)
removed the greensock loading stuff from original, just needed to preview tree (hgrid) layout with a more user friendly utility. A simple quadtree streaming LOD grid for terrain-related stuff.
ActionScript3 source code
/**
* Copyright Glidias ( http://wonderfl.net/user/Glidias )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/4lG7
*/
package //tests.islands
{
import flash.display.Shape;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.text.TextField;
/**
* ...
* @author Glenn Ko
*/
public class TestTerrainLODTreeUtil extends Sprite
{
private var treeUtil:TerrainLODTreeUtil = new TerrainLODTreeUtil();
private var field:TextField;
public function TestTerrainLODTreeUtil()
{
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
addChild(treeUtil.previewShape);
treeUtil.previewShape.scaleX = 32 / treeUtil.smallestSquareSize / 4;
treeUtil.previewShape.scaleY = 32/treeUtil.smallestSquareSize / 4;
graphics.clear();
graphics.beginFill(0xDDDDDD, 1);
graphics.drawRect(0, 0, 512, 512);
graphics.lineStyle(null, 0xFFFFFF);
graphics.moveTo(256, 0);
graphics.lineTo(256, 512);
graphics.moveTo(0, 256);
graphics.lineTo(512, 256);
addEventListener(Event.ENTER_FRAME, onEnterframe);
field = new TextField();
field.autoSize = "left";
addChild(field);
}
private function onEnterframe(e:Event):void
{
var shape:Shape = treeUtil.previewShape;
// shape.x = -stage.stageWidth * .5;
// shape.y = -stage.stageHeight * .5;
field.text =(shape.mouseX/treeUtil.boundingSpace.width*2) + "::"+ ( Math.round( shape.mouseX/treeUtil.boundingSpace.width*2)==1 && Math.round(shape.mouseY/treeUtil.boundingSpace.height*2)==1 ? "within bounds" : "outside bounds need to shift" );
treeUtil.update( shape.mouseX , shape.mouseY );
//
}
}
}
import flash.display.BitmapData;
import flash.display.Graphics;
import flash.display.Shape;
import flash.geom.Point;
import flash.geom.Rectangle;
/**
* Utility to manage a hiercahical quadtree of TerrainLODs
* @author Glenn Ko
*/
class TerrainLODTreeUtil
{
public const BASE_PATCH:int = 4;
// up to 4 levels
public const QUERY_SIZE:int = 1024;
public var smallestSquareSize:Number =128;
public var boundingSpace:Rectangle = new Rectangle(0, 0, 8192,8192 );
private var boundingTiles:Rectangle = new Rectangle(boundingSpace.x/smallestSquareSize, boundingSpace.y/smallestSquareSize, boundingSpace.width / smallestSquareSize, boundingSpace.height / smallestSquareSize);
private var offset6:Vector.<int>= new Vector.<int>(6*4, true);
private var _lmx:int = -int.MAX_VALUE;
private var _lmy:int = -int.MAX_VALUE;
static private const ORIGIN:Point = new Point();
private var levelPxOffsets:Vector.<int> = new Vector.<int>();
private var gridLookup:BitmapData;
private var boundWidth:Number;
private var boundHeight:Number;
private var rectFiller:Rectangle = new Rectangle();
public var previewShape:Shape = new Shape();
public var _numDrawnItems:int;
//private var stateLookup:BitmapData;
// private var lastStatelookup:BitmapData;
public function TerrainLODTreeUtil()
{
gridLookup = new BitmapData(boundingTiles.width, boundingTiles.height, false, 0);
previewShape.scaleX = previewShape.scaleY = 32 / smallestSquareSize;
///*
offset6[0] = 0; offset6[1] = 1; // nw (0)
offset6[2] = 1; offset6[3] = 1;
offset6[4] = 1; offset6[5] = 0;
offset6[6] = -1; offset6[7] = 0; // ne (0)
offset6[8] = -1; offset6[9] = 1;
offset6[10] = 0; offset6[11] = 1;
offset6[12] =0; offset6[13] = -1; // sw (|=2,) 10
offset6[14] = 1; offset6[15] = -1;
offset6[16] = 1; offset6[17] = 0;
offset6[18] = -1; offset6[19] = 0; // se (|=2,|=1) 11
offset6[20] = -1; offset6[21] = -1;
offset6[22] = 0; offset6[23] = -1;
//*/
var qSize:uint = smallestSquareSize;
levelPxOffsets.push(0);
var count:int = 0;
var count2:int = 0;
var level:int = 0;
boundWidth = Math.floor(boundingSpace.width) / smallestSquareSize;
boundHeight = Math.floor(boundingSpace.height) / smallestSquareSize;
while (qSize < QUERY_SIZE ) {
qSize *= 2;
count += (boundWidth >> level);
levelPxOffsets.push(count );
level++;
}
}
public function get graphics():Graphics {
return previewShape.graphics;
}
public var offsetX:Number = 0;
public var offsetY:Number = 0;
public function update(mx:int, my:int):void {
// if (mx < 0) mx = 0;
//if (my < 0) my = 0;
var mix:int = Math.round(mx/smallestSquareSize);
var miy:int = Math.round(my / smallestSquareSize);
// if (mix >= boundWidth) mix = boundWidth - 1; // clamp round up cases
// if (miy >= boundHeight) miy = boundHeight - 1;
if (_lmx === mix && _lmy === miy) {
return;
}
_lmx = mix;
_lmy = miy;
graphics.clear();
gridLookup.fillRect(gridLookup.rect, 0xFF0000);
// lastStatelookup.copyPixels(stateLookup, stateLookup.rect, ORIGIN);
// previewChangeState.fillRect(previewChangeState.rect, 0);
// stateLookup.fillRect(stateLookup.rect, 0);
var rect:Rectangle;
rect = new Rectangle(mix * smallestSquareSize - smallestSquareSize * 4 * BASE_PATCH, miy * smallestSquareSize - smallestSquareSize * 4 * BASE_PATCH, smallestSquareSize * 8 * BASE_PATCH, smallestSquareSize * 8 * BASE_PATCH);
graphics.lineStyle(0, 0xFF0000, 1);
graphics.drawRect(rect.x, rect.y, rect.width, rect.height);
graphics.lineStyle(0, 0, 1);
var qSize:int;
qSize = smallestSquareSize;
_numDrawnItems = 0;
rect.width = smallestSquareSize*4;
rect.height = smallestSquareSize * 4;
rect.x = mix * smallestSquareSize - smallestSquareSize * 2
rect.y = miy * smallestSquareSize - smallestSquareSize * 2
var level:int = 0;
drawOntoGrid(rect, qSize, level);
while (qSize < QUERY_SIZE ) {
rect.x -= rect.width * .5; rect.y -= rect.height * .5; rect.width *= 2, rect.height *= 2;
level++;
qSize *= 2;
drawOntoGrid(rect, qSize, level);
}
}
private function drawOntoGrid(sampleRegion:Rectangle, gridSize:int, level:int):void {
var xLimit:int = Math.ceil( (sampleRegion.x+sampleRegion.width) / gridSize );
var yLimit:int = Math.ceil( (sampleRegion.y + sampleRegion.height) / gridSize );
var parentGridSize:int = gridSize * 2;
if (xLimit > boundingTiles.x + boundingTiles.width) xLimit = boundingTiles.x + boundingTiles.width ;
if (yLimit > boundingTiles.y + boundingTiles.height) yLimit = boundingTiles.y + boundingTiles.height ;
var yStart:int = Math.floor(sampleRegion.y / gridSize);
var xStart:int = Math.floor(sampleRegion.x / gridSize);
if (xStart < boundingTiles.x) xStart = boundingTiles.x;
if (yStart < boundingTiles.y) yStart = boundingTiles.y;
rectFiller.width = gridSize / smallestSquareSize;
rectFiller.height = gridSize / smallestSquareSize;
var levelPxOffset:int = levelPxOffsets[level];
var randColor:uint = 0xFF + (level + 1);// * (65535/8);
var gotParent:Boolean = parentGridSize <= QUERY_SIZE;
for (var yi:int = yStart; yi < yLimit; yi++) {
for (var xi:int = xStart; xi < xLimit; xi ++) {
var xis:int;
var yis:int;
rectFiller.x = xis =( xi - boundingTiles.x) * rectFiller.width;
rectFiller.y = yis = (yi - boundingTiles.y) * rectFiller.height;
if ( (gridLookup.getPixel(xis, yis) & 0xFFFF00) != 0xFF0000) {
// this is already filled up by something
//BitmapData().compare()
continue;
}
if (gridSize == smallestSquareSize ) graphics.lineStyle(2, 0xFF)
else graphics.lineStyle(0, 0, 1);
graphics.beginFill(0xFF0000, .2);
graphics.drawRect(xi * gridSize, yi * gridSize, gridSize, gridSize);
gridLookup.fillRect(rectFiller, randColor);
// stateLookup.setPixel(xi+levelPxOffset, yi, 0xFF0000);
if (gotParent) {
if (gridSize == smallestSquareSize) graphics.lineStyle(1, 0xFF0000, 1)
else graphics.lineStyle(0, 0, 1)
var result:int = 0;
// based on current region, which quadrant and I'm in?? draw rect on 3 other quadrants.
var xii:int = xi*gridSize;
var yii:int = yi*gridSize;
result|= ((xi*gridSize) % parentGridSize) != 0 ? 1 : 0;
result |= ((yi * gridSize) % parentGridSize) != 0 ? 2 : 0;
result *= 6;
rectFiller.x = (xis) + offset6[result]*rectFiller.width;
rectFiller.y = (yis) + offset6[result + 1] * rectFiller.height;
if (gridLookup.getPixel(rectFiller.x, rectFiller.y) === 0xFF0000) {
gridLookup.fillRect(rectFiller, randColor);
graphics.beginFill(0xFF0000, .2);
graphics.drawRect(xii+ offset6[result] * gridSize, yii+(offset6[result + 1]) * gridSize, gridSize, gridSize);
// stateLookup.setPixel(xi+ levelPxOffset+offset6[result], yi+ offset6[result+1], 0xFF0000);
}
rectFiller.x = (xis) + offset6[result+2]*rectFiller.width;
rectFiller.y = (yis) + offset6[result+3]*rectFiller.height;
if (gridLookup.getPixel(rectFiller.x, rectFiller.y) === 0xFF0000) {
gridLookup.fillRect(rectFiller, randColor);
graphics.beginFill(0xFF0000, .2);
graphics.drawRect(xii+ offset6[result + 2] * gridSize, yii+(offset6[result + 3]) * gridSize, gridSize, gridSize);
// stateLookup.setPixel(xi+ levelPxOffset+offset6[result+2], yi+ offset6[result+3], 0xFF0000);
}
rectFiller.x = (xis) + offset6[result+4]*rectFiller.width;
rectFiller.y = (yis) + offset6[result + 5] * rectFiller.height;
if (gridLookup.getPixel(rectFiller.x, rectFiller.y) === 0xFF0000) {
gridLookup.fillRect(rectFiller, randColor);
graphics.beginFill(0xFF0000, .2); graphics.drawRect(xii+ offset6[result + 4] * gridSize, yii+offset6[result + 5] * gridSize, gridSize, gridSize);
// stateLookup.setPixel(xi+ levelPxOffset+offset6[result+4], yi+ offset6[result+5], 0xFF0000);
}
}
}
}
}
}