KD-Image City (WIP: Alternativa3D-powered)
W-S-A-D to fly around with mouse drag look. Hold Shift to fly faster.
Real-time processed image using Alternativa3D's KDContainer to generate a "growing/evolving" city layout.
This is still a WIP, intend to add optional road cuts per KD-Node/Building split, flying/walking collision detection and other stuffs.
Toggle TAB to preview KD tree of image/3d world. Toggle Backspace to pause city kd generation.
/**
* Copyright Glidias ( http://wonderfl.net/user/Glidias )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/fbYv
*/
/**
フラクタルで画像を描画
パクリ元ネタ:
fladdict » コンピューターに絵画を描かせる
http://fladdict.net/blog/2009/05/computer-painting.html
標準偏差:
http://www.cap.or.jp/~toukei/kandokoro/html/14/14_2migi.htm
画像の読み込み処理:
http://wonderfl.kayac.com/code/3fb2258386320fe6d2b0fe17d6861e7da700706a
RGB->HSB変換:
http://d.hatena.ne.jp/flashrod/20060930#1159622027
**/
package
{
import alternativ7.engine3d.containers.DistanceSortContainer;
import alternativ7.engine3d.containers.KDContainer;
import alternativ7.engine3d.controllers.SimpleObjectController;
import alternativ7.engine3d.core.Debug;
import alternativ7.engine3d.core.EllipsoidCollider;
import alternativ7.engine3d.core.Face;
import alternativ7.engine3d.core.Object3D;
import alternativ7.engine3d.core.Object3DContainer;
import alternativ7.engine3d.core.Vertex;
import alternativ7.engine3d.core.Wrapper;
import alternativ7.engine3d.materials.FillMaterial;
import alternativ7.engine3d.materials.Material;
import alternativ7.engine3d.objects.Mesh;
import alternativ7.engine3d.objects.SkyBox;
import alternativ7.engine3d.primitives.Box;
import alternativ7.engine3d.primitives.Plane;
import com.greensock.easing.Cubic;
import com.greensock.easing.Elastic;
import com.greensock.easing.Linear;
import com.greensock.plugins.HexColorsPlugin;
import com.greensock.plugins.TweenPlugin;
import com.greensock.TweenLite;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Loader;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Vector3D;
import flash.net.URLRequest;
import flash.system.LoaderContext;
import flash.text.TextField;
import flash.ui.Keyboard;
//import portal.controllers.SimpleFlyController;
import alternativ7.engine3d.alternativa3d;
import alternativ7.engine3d.core.Camera3D;
import alternativ7.engine3d.core.View;
use namespace alternativa3d;
/**
* WIP: Real-time processed image using Alternativa3D's KDContainer to generate a "growing" * city.
*
* Since image is used, can consider using perlin noise or other procedural means to
* generate city layout.
*
* I was thinking a Blade runner style city would be nice.
* Would be good to add in geometry/splitting animations (buildings pushing in/out) as part
* of the "growing" effect.
* W-S-A-D to fly around with mouse drag look.
*
* @author Glidias
*/
[SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#ffffff")]
public class KDTreeImage extends Sprite
{
static public const FLOOR_COLOR:uint = 0xAAAAAA;
static public const SPEC_RADIUS:Number = 32;
private const IMAGE_URL:String = "http://farm4.static.flickr.com/3639/3538831894_cca4aabd68.jpg";
//標準偏差の閾値。小さくすると細かくなるけど、小さすぎるとただのモザイクみたくなる。
private const THRESHOLD:Number = 0.1;
private var fillRectangleArray:Array;
private var image:Bitmap;
private var imageData:BitmapData;
private var _canvas:Sprite;
public static const DUMMY_VECTOR1:Vector3D = new Vector3D();
public static const DUMMY_VECTOR2:Vector3D = new Vector3D();
public static const DUMMY_DISPLACE:Vector3D = new Vector3D(0,0,0.1);
private var camera:Camera3D;
private var kdContainer:KDContainer;
private var cameraController:SimpleFlyController;
private var KD_NODE:Class;
private var _lastKDNode:*;
private static const WORLD_SCALE:Number = 128;
private static const INV_255:Number = 1 / 255;
private static const MAX_HEIGHT:Number = 12000;
private var rootContainer:Object3DContainer;
private var _tint:Number;
public function KDTreeImage():void
{
TweenPlugin.activate( [HexColorsPlugin] );
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
initA3D();
removeEventListener(Event.ADDED_TO_STAGE, init);
//画像の読み込み
var req:URLRequest = new URLRequest(IMAGE_URL);
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadComplete);
loader.load( req, new LoaderContext(true));
}
private function initA3D():void
{
fillA3DBuffers(); // may improve performance for initial run
camera = new Camera3D();
camera.view = new View(stage.stageWidth, stage.stageHeight);
stage.addEventListener(Event.RESIZE, onStageResize);
addChild(camera.view);
kdContainer = new KDContainer();
KD_NODE = getKDNodeClass();
rootContainer = new Object3DContainer();
rootContainer.addChild(camera);
cameraController = new SimpleFlyController(new MyEllipsoidCollider(SPEC_RADIUS, SPEC_RADIUS, SPEC_RADIUS), rootContainer, stage, camera, 800, 8);
//camera.addToDebug(Debug.BOUNDS, Box);
camera.addToDebug(Debug.NODES, KDContainer);
//camera.addToDebug(Debug.BOUNDS, KDContainer);
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}
private function fillA3DBuffers():void {
Vertex.collector = Vertex.createList(300);
Face.collector = createFaceList(100);
Wrapper.collector = createWrapperList(300);
}
private function createFaceList(i:int):Face {
var f:Face = new Face();
while ( --i > -1) {
f = f.next = new Face();
}
return f;
}
private function createWrapperList(i:int):Wrapper {
var f:Wrapper = new Wrapper();
while ( --i > -1) {
f = f.next = new Wrapper();
}
return f;
}
private var _doSpawn:Boolean = true;
private function onKeyDown(e:KeyboardEvent):void
{
if (e.keyCode === Keyboard.TAB) {
camera.debug = !camera.debug;
}
if (e.keyCode === Keyboard.BACKSPACE) {
_doSpawn = !_doSpawn;
}
}
private function getKDNodeClass():Class {
var dummy:KDContainer = new KDContainer();
dummy.createTree(new <Object3D>[new Box(8,8,8)]);
return Object(dummy.root).constructor;
}
private function onStageResize(e:Event):void
{
camera.view.width = stage.stageWidth;
camera.view.height = stage.stageHeight;
}
//画像読み込み後の処理
public function loadComplete(e:Event = null):void
{
e.target.removeEventListener(Event.COMPLETE, loadComplete);
image = e.target.loader.content as Bitmap;
imageData = image.bitmapData;
//キャンバス用スプライト
_canvas = new Sprite;
var p:RectanglePiece = new RectanglePiece();
var node:*;
var threshold:Number = kdContainer.threshold;
p.x0 = 0;
p.y0 = 0;
p.x1 = imageData.width;
p.y1 = imageData.height;
p.c = 0;
// Setup root starting
kdContainer.root = setupNode(p);
kdContainer.boundMinX = 0;
kdContainer.boundMinY = 0;
kdContainer.boundMaxX = p.x1 * WORLD_SCALE;
kdContainer.boundMaxY = p.y1 * WORLD_SCALE;
kdContainer.boundMinZ = 0;
kdContainer.boundMaxZ = MAX_HEIGHT;
camera.x = kdContainer.boundMaxX * .5;
camera.y = kdContainer.boundMaxY * .5;
camera.z = MAX_HEIGHT + 63400;
cameraController.updateObjectTransform();
cameraController.lookAtXYZ(camera.x, camera.y, 0);
var skybox:SkyBox = new SkyBox(99999999);
skybox.setMaterialToAllFaces( new FillMaterial(0xEEFEFF) );
rootContainer.addChild(skybox);
var floor:Plane = new Plane(kdContainer.boundMaxX, kdContainer.boundMaxY,1,1,false,false,false,null, new FillMaterial(FLOOR_COLOR) );
floor.clipping = 2;
rootContainer.addChild(floor);
floor.x = kdContainer.boundMaxX * .5;
floor.y = kdContainer.boundMaxY * .5;
rootContainer.addChild(kdContainer);
//フラクタルデータ保持用配列に初期値挿入
fillRectangleArray = new Array(p);
addChild(_canvas);
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function setupNode(p:RectanglePiece):* {
var node:*;
p.node = node = new KD_NODE();
node.boundMinX = p.x0 * WORLD_SCALE;
node.boundMinY = p.y0 * WORLD_SCALE;
node.boundMinZ = 0;
node.boundMaxX = p.x1 * WORLD_SCALE;
node.boundMaxY = p.y1 * WORLD_SCALE;
node.boundMaxZ = MAX_HEIGHT;
return node;
}
// todo: optimization and pooling?
private function createBuilding(p:RectanglePiece, color:uint, fromHeight:Number, fromColor:uint):void
{
///*
//node.boundMaxZ = 0;
//*/
var node:* = p.node;
var height:Number = 1 + _tint * MAX_HEIGHT;
var w:Number = (p.x1 - p.x0) * WORLD_SCALE;
var h:Number = (p.y1 - p.y0) * WORLD_SCALE;
var building:KDBuilding = new KDBuilding(p.x0 * WORLD_SCALE, p.y0 * WORLD_SCALE, w, h, height, color);
building.height = fromHeight;
TweenLite.to(building, 1.6, { height:height, ease:Cubic.easeInOut } );
(building.faceList.material as FillMaterial).color = fromColor;
TweenLite.to(building.faceList.material, .4, { hexColors: {color:color}, ease:Linear.easeNone } );
node.objectList = building;
node.objectBoundList = building;
}
//ループ
private function onEnterFrame(e:Event):void
{
var node:*;
if (!_doSpawn) {
cameraController.update();
camera.transformId++;
camera.render();
return;
}
//フラクタル処理終了
if (fillRectangleArray.length < 1) {
removeEventListener(Event.ENTER_FRAME, onEnterFrame);
var tx:TextField = new TextField();
tx.text = '終了';
tx.textColor = 0xFFFFFF;
addChild(tx);
}else {
//フラクタルデータ保持用配列から1つ取り出す
var rect:RectanglePiece = fillRectangleArray.shift();
var cArray:Array = deviationLogic(rect.x0, rect.y0, rect.x1, rect.y1);
rect.c = cArray[0];
rect.color = cArray[1];
var halfWidth:Number = (rect.x1 - rect.x0) * .5;
var halfHeight:Number = (rect.y1 - rect.y0) * .5;
// 指定した矩形内の輝度の標準偏差値が閾値以上なら2分木して処理続行
if (rect.c > THRESHOLD && (halfWidth > 2 || halfHeight > 2)) {
//矩形を書くよ
/*
_canvas.graphics.lineStyle(0, 0xAAAAAA);
_canvas.graphics.beginFill(cArray[1]);
_canvas.graphics.drawRect(rect.x0, rect.y0, (rect.x1 - rect.x0), (rect.y1 - rect.y0));
*/
node = rect.node;
var removeObj:KDBuilding;
if (rect.parent != null ) {
if ((removeObj=rect.parent.node.objectList) != null) {
rect.parent.node.objectList = null;
// todo: cut road along parent splitter.
if (rect.positive) {
rect.parent.node.positive = node;
rect.parent.node.negative = rect.sibling.node;
}
else {
rect.parent.node.negative = node;
rect.parent.node.positive = rect.sibling.node;
}
TweenLite.killTweensOf(removeObj);
createBuilding(rect, rect.color, removeObj.height, rect.parent.color);
createBuilding(rect.sibling, rect.parent.color, removeObj.height, rect.parent.color);
}
else { // fill up remaining branch (either negative or postiive)
if (rect.positive) {
rect.parent.node.positive = node;
removeObj = rect.node.objectList;
}
else {
rect.parent.node.negative = node;
removeObj = rect.node.objectList;
}
createBuilding(rect, rect.color, removeObj.height, rect.parent.color);
}
}
else { // Root node case!
createBuilding(rect, rect.color, 0, FLOOR_COLOR);
}
//矩形を2分割してフラクタルデータ保持用配列に突っ込む
var rect0:RectanglePiece = new RectanglePiece();
var rect1:RectanglePiece = new RectanglePiece();
var randomX:Number = Math.floor(Math.random()*(halfWidth-4)+4);
var randomY:Number = Math.floor(Math.random()*(halfHeight-4)+4);
// Rather hackish pointers here!
rect0.positive = false;
rect1.positive = true;
rect0.sibling = rect1;
rect1.sibling = rect0;
rect0.parent = rect;
rect1.parent = rect;
if (halfWidth > halfHeight) {
node.axis = 0;
node.coord = (rect.x0 + randomX) * WORLD_SCALE;
node.minCoord = node.coord - kdContainer.threshold;
node.maxCoord = node.coord + kdContainer.threshold;
rect0.x0 = rect.x0; // negative x
rect0.y0 = rect.y0;
rect0.x1 = rect.x0+randomX;
rect0.y1 = rect.y1;
fillRectangleArray.push(rect0);
rect.node.negative
setupNode(rect0);
rect1.x0 = rect.x0+randomX; // postive x
rect1.y0 = rect.y0;
rect1.x1 = rect.x1;
rect1.y1 = rect.y1;
fillRectangleArray.push(rect1);
rect.node.positive;
setupNode(rect1);
}else {
node.axis = 1;
node.coord = (rect.y0 + randomY) * WORLD_SCALE;
node.minCoord = node.coord - kdContainer.threshold;
node.maxCoord = node.coord + kdContainer.threshold;
rect0.x0 = rect.x0; // negative y
rect0.y0 = rect.y0;
rect0.x1 = rect.x1;
rect0.y1 = rect.y0+randomY;
fillRectangleArray.push(rect0);
rect.node.negative;
setupNode(rect0);
rect1.x0 = rect.x0; //postive y
rect1.y0 = rect.y0+randomY;
rect1.x1 = rect.x1;
rect1.y1 = rect.y1;
fillRectangleArray.push(rect1);
rect.node.positive
setupNode(rect1);
}
}
}
//cameraController.setObjectPosXYZ(camera.x, camera.y, camera.z);
cameraController.update();
var sphere:Vector3D = new Vector3D(camera.x, camera.y, camera.z, SPEC_RADIUS);
var len:int = KDBuilding.numCollidables;
var collidables:Vector.<KDBuilding> = KDBuilding.collidables;
var collidable:KDBuilding;
var highestZ:Number = 0;
if (!cameraController.hasMoved) {
// do a dummy collision test
cameraController.collider.getCollision( new Vector3D(camera.x, camera.y, camera.z), DUMMY_DISPLACE ,DUMMY_VECTOR1, DUMMY_VECTOR2, rootContainer);
}
len = KDBuilding.numCollidables;
for (var i:int = 0; i < len; i++) {
collidable = collidables[i];
if (kdContainer.boundIntersectSphere(sphere,collidable.boundMinX, collidable.boundMinY, collidable.boundMinZ, collidable.boundMaxX, collidable.boundMaxY, collidable.boundMaxZ) && collidable.boundMaxZ > highestZ) {
highestZ = collidable.boundMaxZ;
}
}
if ( camera.z < highestZ + SPEC_RADIUS) {
cameraController.setObjectPosXYZ(camera.x, camera.y, highestZ + SPEC_RADIUS);
camera.z = highestZ + SPEC_RADIUS;
}
camera.transformId++;
camera.render();
}
/**
* 指定した矩形間の輝度の標準偏差を求める
* @param x0 左上のx座標
* @param y0 左上のy座標
* @param x1 右下のx座標
* @param y1 右下のy座標
* @return 標準偏差値とカラーの平均
*/
private function deviationLogic(x0:Number,y0:Number,x1:Number,y1:Number):Array {
var rgb:uint = 0;
var r:uint = 0;
var g:uint = 0;
var b:uint = 0;
var hsb:Array = new Array();
var bArray:Array = new Array();
var br:Number = 0;
var av:Number = 0;
//輝度の平均を計算
for (var i:int = x0; i < x1;i++ ) {
for (var j:int = y0; j < y1; j++ ) {
rgb = imageData.getPixel(i, j);
r += (rgb >> 16) & 255;
g += (rgb >> 8) & 255;
b += rgb & 255;
hsb = uintRGBtoHSB(rgb);
br += hsb[2];
bArray.push(hsb[2]);
}
}
av = br / bArray.length;
r = r / bArray.length;
g = g / bArray.length;
b = b / bArray.length;
rgb = (r << 16) | (g << 8) | (b << 0);
_tint = (255 - ( 0.21 * r + 0.71 * g + 0.07 * b )) * INV_255;
//標準偏差を計算
br = 0;
for (i = 0; i < bArray.length; i++ ) {
br += (bArray[i] - av) *(bArray[i] - av);
}
return [Math.sqrt(br / bArray.length),rgb];
}
/**
*
* @param rgb RGB成分(uint)
* @return HSB配列([0]=hue, [1]=saturation, [2]=brightness)
*/
private function uintRGBtoHSB(rgb:uint):Array {
var r:uint = (rgb >> 16) & 255;
var g:uint = (rgb >> 8) & 255;
var b:uint = rgb & 255;
return RGBtoHSB(r, g, b);
}
/** RGBからHSBをつくる
* @param r 色の赤色成分(0~255)
* @param g 色の緑色成分(0~255)
* @param b 色の青色成分(0~255)
* @return HSB配列([0]=hue, [1]=saturation, [2]=brightness)
*/
private function RGBtoHSB(r:int, g:int, b:int):Array {
var cmax:Number = Math.max(r, g, b);
var cmin:Number = Math.min(r, g, b);
var brightness:Number = cmax / 255.0;
var hue:Number = 0;
var saturation:Number = (cmax != 0) ? (cmax - cmin) / cmax : 0;
if (saturation != 0) {
var redc:Number = (cmax - r) / (cmax - cmin);
var greenc:Number = (cmax - g) / (cmax - cmin);
var bluec:Number = (cmax - b) / (cmax - cmin);
if (r == cmax) {
hue = bluec - greenc;
} else if (g == cmax) {
hue = 2.0 + redc - bluec;
} else {
hue = 4.0 + greenc - redc;
}
hue = hue / 6.0;
if (hue < 0) {
hue = hue + 1.0;
}
}
return [hue, saturation, brightness];
}
}
}
import alternativ7.engine3d.containers.KDContainer;
import alternativ7.engine3d.core.Camera3D;
import alternativ7.engine3d.core.Canvas;
import alternativ7.engine3d.core.EllipsoidCollider;
import alternativ7.engine3d.core.Face;
import alternativ7.engine3d.core.Object3D;
import alternativ7.engine3d.core.Vertex;
import alternativ7.engine3d.core.VG;
import alternativ7.engine3d.core.Wrapper;
import alternativ7.engine3d.materials.FillMaterial;
import alternativ7.engine3d.objects.Mesh;
import alternativ7.engine3d.primitives.Box;
import alternativ7.engine3d.alternativa3d;
import flash.geom.Vector3D;
import flash.utils.Dictionary;
use namespace alternativa3d;
/**
* ...
* @author DefaultUser (Tools -> Custom Arguments...)
*/
class RectanglePiece
{
public var x0:Number;
public var y0:Number;
public var x1:Number;
public var y1:Number;
public var c:Number;
public var node:*;
public var parent:RectanglePiece;
public var positive:Boolean;
public var color:uint;
public var sibling:RectanglePiece;
public function RectanglePiece()
{
this.x0 = 0;
this.y0 = 0;
this.x1 = 0;
this.x1 = 0;
this.c = 0;
}
}
class MyEllipsoidCollider extends EllipsoidCollider {
public function MyEllipsoidCollider(radiusX:Number, radiusY:Number, radiusZ:Number) {
super(radiusX, radiusY, radiusZ);
}
override public function calculateDestination (source:Vector3D, displacement:Vector3D, object:Object3D, excludedObjects:Dictionary = null) : Vector3D {
KDBuilding.numCollidables = 0;
return super.calculateDestination(source, displacement, object, excludedObjects);
}
override public function getCollision (source:Vector3D, displacement:Vector3D, resCollisionPoint:Vector3D, resCollisionPlane:Vector3D, object:Object3D, excludedObjects:Dictionary = null) : Boolean {
KDBuilding.numCollidables = 0;
return super.getCollision(source, displacement, resCollisionPoint, resCollisionPlane, object, excludedObjects);
}
public function $calculateDestination(source:Vector3D, displacement:Vector3D, object:Object3D, excludedObjects:Dictionary = null) : Vector3D {
return super.calculateDestination(source, displacement, object, excludedObjects);
}
public function $getCollision (source:Vector3D, displacement:Vector3D, resCollisionPoint:Vector3D, resCollisionPlane:Vector3D, object:Object3D, excludedObjects:Dictionary = null) : Boolean {
return super.getCollision(source, displacement, resCollisionPoint, resCollisionPlane, object, excludedObjects);
}
}
class KDBuilding extends Mesh
{
// TODO: Can consider recycling of KDBuildings
public static var collidables:Vector.<KDBuilding> = new Vector.<KDBuilding>();
public static var numCollidables:int = 0;
public function KDBuilding(xPos:Number, yPos:Number, width:Number, length:Number, height:Number, color:uint)
{
clipping = 2;
sorting = 0;
var v1:Vertex;
var v2:Vertex;
var v3:Vertex;
var v4:Vertex;
var mat:FillMaterial = new FillMaterial(color);
var v:Vertex;
var f:Face;
var w:Wrapper;
// Define top roof vertices
vertexList = v = Vertex.collector || new Vertex(); v.u = 0; v.v = 0;
Vertex.collector = v.next;
v.x = xPos;
v.y = yPos;
v.z = height;
v1 = v;
v.next = v = v.create(); v.u = 0; v.v = 0;
v.x = xPos + width;
v.y = yPos;
v.z = height;
v2 = v;
v.next = v = v.create(); v.u = 0; v.v = 0;
v.x = xPos + width;
v.y = yPos + length;
v.z = height;
v3 = v;
v.next = v = v.create(); v.u = 0; v.v = 0;
v.x = xPos;
v.y = yPos + length;
v.z = height;
v4 = v;
var v5:Vertex;
var v6:Vertex;
var v7:Vertex;
var v8:Vertex;
// Define bottom vertices
v.next = v = v.create(); v.u = 0; v.v = 0;
v.x = xPos;
v.y = yPos;
v.z = 0;
v5 = v;
v.next = v = v.create(); v.u = 0; v.v = 0;
v.x = xPos + width;
v.y = yPos;
v.z = 0;
v6 = v;
v.next = v = v.create(); v.u = 0; v.v = 0;
v.x = xPos + width;
v.y = yPos + length;
v.z = 0;
v7 = v;
v.next = v = v.create(); v.u = 0; v.v = 0;
v.x = xPos;
v.y = yPos + length;
v.z = 0;
v8 = v;
// top face
faceList = f = Face.collector || new Face();
Face.collector = f.next;
f.material = mat;
f.wrapper = w = Wrapper.collector || new Wrapper();
Wrapper.collector = w.next;
w.vertex = v1;
w.next = w = w.create();
w.vertex = v2;
w.next = w = w.create();
w.vertex = v3;
w.next = w = w.create();
w.vertex = v4;
f.normalX = 0;
f.normalY = 0;
f.normalZ = 1;
f.offset = height;
//f.calculateBestSequenceAndNormal();
//if (!f.normal.nearEquals(new Vector3D(0,0,1), 0.001)) throw new Error("MISMATCH top!"+f.normal);
// South face
f.next = f = f.create();
f.material = mat;
f.wrapper = w = w.create();
w.vertex = v5;
w.next = w = w.create();
w.vertex = v6;
w.next = w = w.create();
w.vertex = v2;
w.next = w = w.create();
w.vertex = v1;
f.normalX = 0;
f.normalY = -1;
f.normalZ = 0;
f.offset = -yPos;
//f.calculateBestSequenceAndNormal();
//if (!f.normal.nearEquals(new Vector3D(0,-1,0), 0.001)) throw new Error("MISMATCH south!"+f.normal);
// East Face
f.next = f = f.create();
f.material = mat;
f.wrapper = w = w.create();
w.vertex = v6;
w.next = w = w.create();
w.vertex = v7;
w.next = w = w.create();
w.vertex = v3;
w.next = w = w.create();
w.vertex = v2;
f.normalX = 1;
f.normalY = 0;
f.normalZ = 0;
f.offset = xPos + width;
//f.calculateBestSequenceAndNormal();
//if (!f.normal.nearEquals(new Vector3D(1,0,0), 0.001)) throw new Error("MISMATCH east!"+f.normal);
// North Face
f.next = f = f.create();
f.material = mat;
f.wrapper = w = w.create();
w.vertex = v7;
w.next = w = w.create();
w.vertex = v8;
w.next = w = w.create();
w.vertex = v4;
w.next = w = w.create();
w.vertex = v3;
f.normalX = 0;
f.normalY = 1;
f.normalZ = 0;
f.offset = yPos + length;
//f.calculateBestSequenceAndNormal();
//if (!f.normal.nearEquals(new Vector3D(0,1,0), 0.001)) throw new Error("MISMATCH north!"+f.normal);
// West Face
f.next = f = f.create();
f.material = mat;
f.wrapper = w = w.create();
w.vertex = v8;
w.next = w = w.create();
w.vertex = v5;
w.next = w = w.create();
w.vertex = v1;
w.next = w = w.create();
w.vertex = v4;
f.normalX = -1;
f.normalY = 0;
f.normalZ = 0;
f.offset = -xPos;
//f.calculateBestSequenceAndNormal();
//if (!f.normal.nearEquals(new Vector3D(-1,0,0), 0.001)) throw new Error("MISMATCH west!"+f.normal);
// ^^^ note vertex normals not coded in!
// calculate bounds
boundMinX = xPos;
boundMinY = yPos;
boundMinZ = 0;
boundMaxX = xPos + width;
boundMaxY = yPos + length;
boundMaxZ = height;
// for debugging (checking) purposes
//calculateFacesNormals();
}
// Boiler-plate mesh draw implementation to prevent errors with camera.debug mode due to
// private classes.
override alternativa3d function draw(camera:Camera3D, parentCanvas:Canvas):void {
calculateInverseMatrix();
// either transformId++; or if camera.transformId used as an incrementing timestamp
transformId = camera.transformId;
var f:Face = prepareFaces(camera);
if (f == null) return;
if (culling > 0) {
f = camera.clip(f, culling);
if (f == null) return;
}
drawFaces(camera, parentCanvas.getChildCanvas(true, false, this, 1, blendMode, colorTransform, filters), f);
}
override alternativa3d function getVG(camera:Camera3D):VG {
calculateInverseMatrix();
// either transformId++; or if camera.transformId used as an incrementing timestamp
transformId = camera.transformId;
var f:Face = prepareFaces(camera);
if (f == null) return null;
if (culling > 0) {
camera.clip(f, culling);
if (f == null) return null;
}
return VG.create(this, f, sorting, 0, false);
}
override alternativa3d function collectPlanes(center:Vector3D, a:Vector3D, b:Vector3D, c:Vector3D, d:Vector3D, collector:Vector.<Face>, excludedObjects:Dictionary=null) : void {
transformId++;
collidables[numCollidables++] = this;
super.collectPlanes(center, a, b, c, d, collector, excludedObjects);
}
public function get height():Number { return vertexList.z; }
// TODO: Adjust height of related KD nodes as well once chars are put in
public function set height(value:Number):void
{
var v:Vertex = vertexList;
v.z = value; v = v.next;
v.z = value; v = v.next;
v.z = value; v = v.next;
v.z = value;
faceList.offset = value;
boundMaxZ = value;
}
}
import alternativ7.engine3d.controllers.SimpleObjectController;
import alternativ7.engine3d.core.Camera3D;
import alternativ7.engine3d.core.EllipsoidCollider;
import alternativ7.engine3d.core.Object3D;
import flash.display.InteractiveObject;
import flash.geom.Vector3D;
import flash.utils.Dictionary;
/**
* ...
* @author Glenn Ko
*/
class SimpleFlyController extends SimpleObjectController
{
public var currentPosition:Vector3D;
public var collider:EllipsoidCollider;
public var collidable:Object3D;
public var displacement:Vector3D = new Vector3D();
public var collisionPoint:Vector3D = new Vector3D();
public var lastPosition:Vector3D = new Vector3D();
public var excludedObjects:Dictionary = null;
public var gotCollision:Boolean;
public var collisionPlane:Vector3D = new Vector3D();
public var hasMoved:Boolean = false;
public function SimpleFlyController(collider:EllipsoidCollider, collidable:Object3D, eventSource:InteractiveObject, object:Object3D, speed:Number, speedMultiplier:Number = 3, mouseSensitivity:Number = 1)
{
super(eventSource, object, speed, speedMultiplier, mouseSensitivity);
this.collider = collider;
this.collidable = collidable;
}
override public function update():void {
var object:Object3D = this.object;
if (object == null) return;
if (collider && collidable) {
lastPosition.x = object.x;
lastPosition.y = object.y;
lastPosition.z = object.z;
super.update();
displacement.x = object.x - lastPosition.x;
displacement.y = object.y - lastPosition.y;
displacement.z = object.z - lastPosition.z;
if (displacement.x * displacement.x + displacement.y * displacement.y + displacement.z * displacement.z == 0) {
gotCollision = false;
hasMoved = false;
return;
}
hasMoved = true;
gotCollision = collider.getCollision(lastPosition, displacement, collisionPoint, collisionPlane, collidable);
var dest:Vector3D = collider.calculateDestination(lastPosition, displacement, collidable, excludedObjects);
// set back frame coherant transform values
setObjectPosXYZ(dest.x, dest.y, dest.z);
// refresh values immediately
object.x = dest.x;
object.y = dest.y;
object.z = dest.z;
displacement.x = dest.x - lastPosition.x;
displacement.y = dest.y - lastPosition.y;
displacement.z = dest.z - lastPosition.z;
currentPosition = dest;
}
else {
super.update();
}
}
}