forked from: KD-Image City (WIP: Alternativa3D-powered)
forked from KD-Image City (WIP: Alternativa3D-powered) (diff: 497)
フラクタルで画像を描画 パクリ元ネタ: fladdict » コンピューターに絵画を描かせる http://fladdict.net/blog/2009/05/computer-painting.html 標準偏差: http://www.cap.or.jp/~toukei/kandokoro/html/14/14_2migi.htm 画像の読み込み処理: http://wonderfl.kayac.com/code/3fb2258386320fe6d2b0fe17d6861e7da700706a RGB->HSB変換: http://d.hatena.ne.jp/flashrod/20060930#1159622027 WIP: Real-time processed image using Alternativa3D's KDContainer to generate a "growing" * city. Since image is used, can consider using perlin noise or other procedural means to generate city layout. I was thinking a Blade runner style city would be nice. Would be good to add in geometry/splitting animations (buildings pushing in/out) as part of the "growing" effect. W-S-A-D to fly around with mouse drag look. @author Glidias
ActionScript3 source code
/**
* Copyright Ludd ( http://wonderfl.net/user/Ludd )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/1LPP
*/
// forked from Glidias's KD-Image City (WIP: Alternativa3D-powered)
/**
フラクタルで画像を描画
パクリ元ネタ:
fladdict » コンピューターに絵画を描かせる
http://fladdict.net/blog/2009/05/computer-painting.html
標準偏差:
http://www.cap.or.jp/~toukei/kandokoro/html/14/14_2migi.htm
画像の読み込み処理:
http://wonderfl.kayac.com/code/3fb2258386320fe6d2b0fe17d6861e7da700706a
RGB->HSB変換:
http://d.hatena.ne.jp/flashrod/20060930#1159622027
**/
package
{
import alternativ7.engine3d.containers.KDContainer;
import alternativ7.engine3d.controllers.SimpleObjectController;
import alternativ7.engine3d.core.Debug;
import alternativ7.engine3d.core.Object3D;
import alternativ7.engine3d.core.Object3DContainer;
import alternativ7.engine3d.core.Vertex;
import alternativ7.engine3d.materials.FillMaterial;
import alternativ7.engine3d.materials.Material;
import alternativ7.engine3d.objects.Mesh;
import alternativ7.engine3d.objects.SkyBox;
import alternativ7.engine3d.primitives.Box;
import alternativ7.engine3d.primitives.Plane;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Loader;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.net.URLRequest;
import flash.system.LoaderContext;
import flash.text.TextField;
import flash.ui.Keyboard;
import alternativ7.engine3d.alternativa3d;
import alternativ7.engine3d.core.Camera3D;
import alternativ7.engine3d.core.View;
use namespace alternativa3d;
/**
* WIP: Real-time processed image using Alternativa3D's KDContainer to generate a "growing" * city.
*
* Since image is used, can consider using perlin noise or other procedural means to
* generate city layout.
*
* I was thinking a Blade runner style city would be nice.
* Would be good to add in geometry/splitting animations (buildings pushing in/out) as part
* of the "growing" effect.
* W-S-A-D to fly around with mouse drag look.
*
* @author Glidias
*/
[SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#ffffff")]
public class KDTreeImage extends Sprite
{
private const IMAGE_URL:String = "http://farm4.static.flickr.com/3639/3538831894_cca4aabd68.jpg";
//標準偏差の閾値。小さくすると細かくなるけど、小さすぎるとただのモザイクみたくなる。
private const THRESHOLD:Number = 0.1;
private var fillRectangleArray:Array;
private var image:Bitmap;
private var imageData:BitmapData;
private var _canvas:Sprite;
private var camera:Camera3D;
private var kdContainer:KDContainer;
private var cameraController:SimpleObjectController;
private var KD_NODE:Class;
private var _lastKDNode:*;
private static const WORLD_SCALE:Number = 128;
private static const INV_255:Number = 1 / 255;
private static const MAX_HEIGHT:Number = 12000;
private var rootContainer:Object3DContainer;
private var _tint:Number;
public function KDTreeImage():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
initA3D();
removeEventListener(Event.ADDED_TO_STAGE, init);
//画像の読み込み
var req:URLRequest = new URLRequest(IMAGE_URL);
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadComplete);
loader.load( req, new LoaderContext(true));
}
private function initA3D():void
{
camera = new Camera3D();
camera.view = new View(stage.stageWidth, stage.stageHeight);
stage.addEventListener(Event.RESIZE, onStageResize);
addChild(camera.view);
kdContainer = new KDContainer();
KD_NODE = getKDNodeClass();
cameraController = new SimpleObjectController(stage, camera, 800, 8);
rootContainer = new Object3DContainer();
rootContainer.addChild(camera);
//camera.addToDebug(Debug.BOUNDS, Box);
camera.addToDebug(Debug.NODES, KDContainer);
//camera.addToDebug(Debug.BOUNDS, KDContainer);
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}
private var _doSpawn:Boolean = true;
private function onKeyDown(e:KeyboardEvent):void
{
if (e.keyCode === Keyboard.TAB) {
camera.debug = !camera.debug;
}
if (e.keyCode === Keyboard.BACKSPACE) {
_doSpawn = !_doSpawn;
}
}
private function getKDNodeClass():Class {
var dummy:KDContainer = new KDContainer();
dummy.createTree(new <Object3D>[new Box(8,8,8)]);
return Object(dummy.root).constructor;
}
private function onStageResize(e:Event):void
{
camera.view.width = stage.stageWidth;
camera.view.height = stage.stageHeight;
}
//画像読み込み後の処理
public function loadComplete(e:Event = null):void
{
e.target.removeEventListener(Event.COMPLETE, loadComplete);
image = e.target.loader.content as Bitmap;
imageData = image.bitmapData;
//キャンバス用スプライト
_canvas = new Sprite;
var p:RectanglePiece = new RectanglePiece();
var node:*;
var threshold:Number = kdContainer.threshold;
p.x0 = 0;
p.y0 = 0;
p.x1 = imageData.width;
p.y1 = imageData.height;
p.c = 0;
// Setup root starting
kdContainer.root = setupNode(p);
kdContainer.boundMinX = 0;
kdContainer.boundMinY = 0;
kdContainer.boundMaxX = p.x1 * WORLD_SCALE;
kdContainer.boundMaxY = p.y1 * WORLD_SCALE;
kdContainer.boundMinZ = 0;
kdContainer.boundMaxZ = MAX_HEIGHT;
camera.x = kdContainer.boundMaxX * .5;
camera.y = kdContainer.boundMaxY * .5;
camera.z = MAX_HEIGHT + 63400;
cameraController.updateObjectTransform();
cameraController.lookAtXYZ(camera.x, camera.y, 0);
var skybox:SkyBox = new SkyBox(99999999);
skybox.setMaterialToAllFaces( new FillMaterial(0xEEFEFF) );
rootContainer.addChild(skybox);
var floor:Plane = new Plane(kdContainer.boundMaxX, kdContainer.boundMaxY,1,1,false,false,false,null, new FillMaterial(0xAAAAAA) );
floor.clipping = 2;
rootContainer.addChild(floor);
floor.x = kdContainer.boundMaxX * .5;
floor.y = kdContainer.boundMaxY * .5;
rootContainer.addChild(kdContainer);
//フラクタルデータ保持用配列に初期値挿入
fillRectangleArray = new Array(p);
addChild(_canvas);
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function setupNode(p:RectanglePiece):* {
var node:*;
p.node = node = new KD_NODE();
node.boundMinX = p.x0 * WORLD_SCALE;
node.boundMinY = p.y0 * WORLD_SCALE;
node.boundMinZ = 0;
node.boundMaxX = p.x1 * WORLD_SCALE;
node.boundMaxY = p.y1 * WORLD_SCALE;
node.boundMaxZ = MAX_HEIGHT;
return node;
}
// todo: optimization and pooling?
private function createBuilding(p:RectanglePiece, color:uint):void
{
///*
//node.boundMaxZ = 0;
//*/
var node:* = p.node;
var mat:Material = new FillMaterial(color);
var height:Number = 1 + _tint * MAX_HEIGHT;
var w:Number = (p.x1 - p.x0) * WORLD_SCALE;
var h:Number = (p.y1 - p.y0) * WORLD_SCALE;
var mesh:Mesh = new Box(w, h, height, 1, 1, 1, false, false, mat, mat, mat, mat, mat, mat);
///*
for (var v:Vertex = mesh.vertexList; v != null; v = v.next) {
v.x += w * .5 + p.x0 * WORLD_SCALE;
v.y += h * .5 + p.y0 * WORLD_SCALE;
v.z += height * .5;
}
mesh.calculateBounds();
mesh.calculateFacesNormals();
//*/
//mesh.x = (w * .5 ) + p.x0 * WORLD_SCALE;
//mesh.y = (h * .5) + p.y0 * WORLD_SCALE;
//mesh.z = height * .5;
//kdContainer.addChild(mesh);
//node.boundMaxZ = height;
//if ( !Bounds3D.getBoundsOf(node).equals2D(Bounds3D.getBoundsOf(mesh))) throw new Error("Not equal!:"+Bounds3D.getBoundsOf(node) + ", "+Bounds3D.getBoundsOf(mesh) );
node.objectList = mesh;
node.objectBoundList = mesh;
}
//ループ
private function onEnterFrame(e:Event):void
{
var node:*;
if (!_doSpawn) {
cameraController.update();
camera.render();
return;
}
//フラクタル処理終了
if (fillRectangleArray.length < 1) {
removeEventListener(Event.ENTER_FRAME, onEnterFrame);
var tx:TextField = new TextField();
tx.text = '終了';
tx.textColor = 0xFFFFFF;
addChild(tx);
}else {
//フラクタルデータ保持用配列から1つ取り出す
var rect:RectanglePiece = fillRectangleArray.shift();
var cArray:Array = deviationLogic(rect.x0, rect.y0, rect.x1, rect.y1);
rect.c = cArray[0];
rect.color = cArray[1];
var halfWidth:Number = (rect.x1 - rect.x0) * .5;
var halfHeight:Number = (rect.y1 - rect.y0) * .5;
// 指定した矩形内の輝度の標準偏差値が閾値以上なら2分木して処理続行
if (rect.c > THRESHOLD && (halfWidth > 2 || halfHeight > 2)) {
//矩形を書くよ
/*
_canvas.graphics.lineStyle(0, 0xAAAAAA);
_canvas.graphics.beginFill(cArray[1]);
_canvas.graphics.drawRect(rect.x0, rect.y0, (rect.x1 - rect.x0), (rect.y1 - rect.y0));
*/
node = rect.node;
var removeObj:Object3D;
if (rect.parent != null ) {
if ((removeObj=rect.parent.node.objectList) != null) {
rect.parent.node.objectList = null;
// todo: cut road along parent splitter.
if (rect.positive) {
rect.parent.node.positive = node;
rect.parent.node.negative = rect.sibling.node;
}
else {
rect.parent.node.negative = node;
rect.parent.node.positive = rect.sibling.node;
}
createBuilding(rect, rect.color);
createBuilding(rect.sibling, rect.parent.color);
}
else { // fill up remaining branch (either negative or postiive)
if (rect.positive) rect.parent.node.positive = node;
else rect.parent.node.negative = node;
createBuilding(rect, rect.color);
}
}
else { // Root node case!
createBuilding(rect, rect.color);
}
//矩形を2分割してフラクタルデータ保持用配列に突っ込む
var rect0:RectanglePiece = new RectanglePiece();
var rect1:RectanglePiece = new RectanglePiece();
// Rather hackish pointers here!
rect0.positive = false;
rect1.positive = true;
rect0.sibling = rect1;
rect1.sibling = rect0;
rect0.parent = rect;
rect1.parent = rect;
if (halfWidth > halfHeight) {
node.axis = 0;
node.coord = (rect.x0 + halfWidth) * WORLD_SCALE;
node.minCoord = node.coord - kdContainer.threshold;
node.maxCoord = node.coord + kdContainer.threshold;
rect0.x0 = rect.x0; // negative x
rect0.y0 = rect.y0;
rect0.x1 = rect.x0+halfWidth;
rect0.y1 = rect.y1;
fillRectangleArray.push(rect0);
rect.node.negative
setupNode(rect0);
rect1.x0 = rect.x0+halfWidth; // postive x
rect1.y0 = rect.y0;
rect1.x1 = rect.x1;
rect1.y1 = rect.y1;
fillRectangleArray.push(rect1);
rect.node.positive;
setupNode(rect1);
}else {
node.axis = 1;
node.coord = (rect.y0 + halfHeight) * WORLD_SCALE;
node.minCoord = node.coord - kdContainer.threshold;
node.maxCoord = node.coord + kdContainer.threshold;
rect0.x0 = rect.x0; // negative y
rect0.y0 = rect.y0;
rect0.x1 = rect.x1;
rect0.y1 = rect.y0+halfHeight;
fillRectangleArray.push(rect0);
rect.node.negative;
setupNode(rect0);
rect1.x0 = rect.x0; //postive y
rect1.y0 = rect.y0+halfHeight;
rect1.x1 = rect.x1;
rect1.y1 = rect.y1;
fillRectangleArray.push(rect1);
rect.node.positive
setupNode(rect1);
}
}
}
cameraController.update();
camera.render();
}
/**
* 指定した矩形間の輝度の標準偏差を求める
* @param x0 左上のx座標
* @param y0 左上のy座標
* @param x1 右下のx座標
* @param y1 右下のy座標
* @return 標準偏差値とカラーの平均
*/
private function deviationLogic(x0:Number,y0:Number,x1:Number,y1:Number):Array {
var rgb:uint = 0;
var r:uint = 0;
var g:uint = 0;
var b:uint = 0;
var hsb:Array = new Array();
var bArray:Array = new Array();
var br:Number = 0;
var av:Number = 0;
//輝度の平均を計算
for (var i:int = x0; i < x1;i++ ) {
for (var j:int = y0; j < y1; j++ ) {
rgb = imageData.getPixel(i, j);
r += (rgb >> 16) & 255;
g += (rgb >> 8) & 255;
b += rgb & 255;
hsb = uintRGBtoHSB(rgb);
br += hsb[2];
bArray.push(hsb[2]);
}
}
av = br / bArray.length;
r = r / bArray.length;
g = g / bArray.length;
b = b / bArray.length;
rgb = (r << 16) | (g << 8) | (b << 0);
_tint = (255 - ( 0.21 * r + 0.71 * g + 0.07 * b )) * INV_255;
//標準偏差を計算
br = 0;
for (i = 0; i < bArray.length; i++ ) {
br += (bArray[i] - av) *(bArray[i] - av);
}
return [Math.sqrt(br / bArray.length),rgb];
}
/**
*
* @param rgb RGB成分(uint)
* @return HSB配列([0]=hue, [1]=saturation, [2]=brightness)
*/
private function uintRGBtoHSB(rgb:uint):Array {
var r:uint = (rgb >> 16) & 255;
var g:uint = (rgb >> 8) & 255;
var b:uint = rgb & 255;
return RGBtoHSB(r, g, b);
}
/** RGBからHSBをつくる
* @param r 色の赤色成分(0~255)
* @param g 色の緑色成分(0~255)
* @param b 色の青色成分(0~255)
* @return HSB配列([0]=hue, [1]=saturation, [2]=brightness)
*/
private function RGBtoHSB(r:int, g:int, b:int):Array {
var cmax:Number = Math.max(r, g, b);
var cmin:Number = Math.min(r, g, b);
var brightness:Number = cmax / 255.0;
var hue:Number = 0;
var saturation:Number = (cmax != 0) ? (cmax - cmin) / cmax : 0;
if (saturation != 0) {
var redc:Number = (cmax - r) / (cmax - cmin);
var greenc:Number = (cmax - g) / (cmax - cmin);
var bluec:Number = (cmax - b) / (cmax - cmin);
if (r == cmax) {
hue = bluec - greenc;
} else if (g == cmax) {
hue = 2.0 + redc - bluec;
} else {
hue = 4.0 + greenc - redc;
}
hue = hue / 6.0;
if (hue < 0) {
hue = hue + 1.0;
}
}
return [hue, saturation, brightness];
}
}
}
/**
* ...
* @author DefaultUser (Tools -> Custom Arguments...)
*/
class RectanglePiece
{
public var x0:Number;
public var y0:Number;
public var x1:Number;
public var y1:Number;
public var c:Number;
public var node:*;
public var parent:RectanglePiece;
public var positive:Boolean;
public var color:uint;
public var sibling:RectanglePiece;
public function RectanglePiece()
{
this.x0 = 0;
this.y0 = 0;
this.x1 = 0;
this.x1 = 0;
this.c = 0;
}
}