3D pointcloud
Pointcloud of a 3D geometric shapes, using 2d flash api.
It's a basic way to draw 3D shapes in isometric projection.
FP 10+ required.
package {
import flash.text.TextFormat;
/**
* by Ke.Kurono. 2014. All rights reserved.
*
*/
import flash.events.TimerEvent;
import flash.utils.Timer;
import flash.display.Shape;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.display.Sprite;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Vector3D;
import flash.text.TextField;
import flash.display.StageQuality;
public class FlashTest extends Sprite {
private var _points:Vector.<Vector3D>;
private var _transformedPoints:Vector.<Vector3D>;
private var _scale:Number = 2;
private var _color:uint = 0x000000;
// euler angles
private var _ex:Number = 0;
private var _ey:Number = 0;
private var _ez:Number = 0;
// canvas
private var _w:Number = 500;
private var _h:Number = 500;
private var _clearScreen:BitmapData = new BitmapData(_w, _h, true, 0xff000000);
private var _fadeScreen:BitmapData = new BitmapData(_w, _h, true, 0x11000000);
private var _canvas:BitmapData = new BitmapData(_w, _h, true, 0x00000000);
// formulas
private var _currentFunction:Function;
private var _functions:Array;
private var _i:uint;
// button's height
private var _buttonDim:Number = 20;
// caption textfield
private var _caption:TextField;
public function FlashTest() {
// write as3 code here..
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
/**
* Entry point
* @param e Event.ADDED_TO_STAGE
*/
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
stage.quality = StageQuality.LOW;
// add functions
_i = 0;
_functions = [
[formulaY1, 'hyperbolic paraboloid'],
[formulaY2, 'ring wave'],
[formulaY3, 'sphere'],
[formulaY4, 'plane waves'],
[formulaY5, 'cube'],
[formulaY6, 'cylinder'],
[formulaY7, 'cone']
];
// add canvas
var bmp:Bitmap = new Bitmap(_canvas, "auto", true);
addChild(bmp);
_currentFunction = formulaY2;
initDataPoints();
// caption text
_caption = new TextField();
_caption.defaultTextFormat = new TextFormat('Verdana', 14, 0xffffff, null, null, null, null, null, 'center');
_caption.text = '';
_caption.y = 10;
_caption.width = stage.stageWidth;
addChild(_caption);
// start rendering
addEventListener(Event.ENTER_FRAME, render);
stage.addEventListener(Event.RESIZE, resize);
resize();
createButtons();
initFormulaChanging();
}
/**
* Build a set of points in 3d space specified by _currentFunction
* @param e
*/
private function initDataPoints(e:Event = null):void {
// build a set of points
_points = buildPointCloud(_currentFunction, 100, false);
}
/**
* Projects 3d vector to 2d plane specified by euler angles
* @param pos initial 3d vector
* @param rot euler angles
* @return transformed 3d vector
*/
private function projectTo2D(pos:Vector3D, rot:Vector3D):Vector3D {
var v2:Vector3D = new Vector3D(
pos.x * Math.cos(rot.x) - pos.y * Math.sin(rot.x),
pos.x * Math.sin(rot.x) + pos.y * Math.cos(rot.x),
pos.z
);
var v1:Vector3D = new Vector3D(
v2.x,
v2.y * Math.cos(rot.y) - v2.z * Math.sin(rot.y)
);
var proj:Vector3D = new Vector3D(
v1.x * Math.cos(rot.z) - v1.y * Math.sin(rot.z),
v1.x * Math.sin(rot.z) + v1.y * Math.cos(rot.z),
0,
pos.w
);
return proj;
}
/**
* Render loop
* @param e Event.Enter_FRAME
*/
private function render(e:Event = null):void {
// do nothing if data is void
if (_points.length == 0) return;
// project
_transformedPoints = new Vector.<Vector3D>();
var i:uint;
var v:Vector3D;
for (i = 0; i < _points.length; i++) {
v = projectTo2D(_points[i], new Vector3D(_ex, _ey, _ez));
//v.scaleBy(_scale);
v.x += _w / 2;
v.y += _h / 2;
_transformedPoints.push(v);
}
// rotate
_ex += .01;
_ey += .01;
_ez += .01;
_ex = wrap2Pi(_ex);
_ey = wrap2Pi(_ey);
_ez = wrap2Pi(_ez);
// draw
rasterize(_transformedPoints, true);
}
/**
* Builds a pointcloud specified by formula relations
* @param formula y = f(x, z)
* @param zoneSize a grid domian size
* @return
*/
private function buildPointCloud(formula:Function, zoneSize:Number = 100, regularGrid:Boolean = true):Vector.<Vector3D> {
var points:Vector.<Vector3D> = new Vector.<Vector3D>();
var x:Number = - zoneSize;
var z:Number = - zoneSize;
var y:Number;
var step:Number = zoneSize / 30;
if (regularGrid) {
while (z < zoneSize) {
x = - zoneSize;
while (x < zoneSize) {
y = formula(x, z);
x += step;
// store
points.push(new Vector3D(x, y, z, 0xffffff * (y/zoneSize)));
}
z += step;
}
} else {
var i:uint;
for (i = 0; i < zoneSize * zoneSize * .5; i++) {
x = zoneSize * (1 - 2 * Math.random());
z = zoneSize * (1 - 2 * Math.random());
y = formula(x, z);
// store
points.push(new Vector3D(x, y, z, 0xffffff * (y/zoneSize)));
}
}
return points;
}
/**
* A relation between x,y,z: y = f(x, z)
* @param x
* @param z
* @return y
*/
private function formulaY1(x:Number, z:Number):Number {
return 30 * z / x;
}
private function formulaY2(x:Number, z:Number):Number {
var arg:Number = .0015 * x * x + .0015 * z * z;
return 50 * Math.cos(arg) * 1 / (arg + 1);
}
private function formulaY3(x:Number, z:Number):Number {
return sign(1 - 2 * Math.random()) * 100 * Math.sqrt(1 - .0001 * x * x - .0001 * z * z);
}
private function formulaY4(x:Number, z:Number):Number {
return 10 * (Math.sin(.1 * x) + Math.cos(.1 * z));
}
private function formulaY5(x:Number, z:Number):Number {
return 100 * (1 - 2 * Math.random());
}
private function formulaY6(x:Number, z:Number):Number {
return sign(1 - 2 * Math.random()) * 100 * Math.sqrt(1 - .0001 * z * z);
}
private function formulaY7(x:Number, z:Number):Number {
return sign(1 - 2 * Math.random()) * 100 * Math.sqrt(.0001 * x * x + .0001 * z * z);
}
/**
* Draw a set of points to canvas
* @param points Pointcloud
* @param fade Allow fade effect instead of direct cleaning of screen
*/
private function rasterize(points:Vector.<Vector3D>, fade:Boolean = false):void {
// clear all
if (fade) {
_canvas.draw(_fadeScreen);
} else {
_canvas.copyPixels(_clearScreen, _clearScreen.rect, new Point());
}
// draw pointcloud
var p:Vector3D;
_canvas.lock();
for each (p in points) {
_canvas.setPixel(numberToPixelCoord(p.x), numberToPixelCoord(p.y), uint(p.w));
}
_canvas.unlock();
}
private function numberToPixelCoord(x:Number):int {
var xi:int = int(x);
return xi;
}
private function resize(e:Event = null):void {
var bmp:Bitmap = getChildAt(0) as Bitmap;
bmp.scaleX = bmp.scaleY = stage.stageHeight / _h;
bmp.x = (stage.stageWidth - _w * bmp.scaleX) / 2;
_caption.width = stage.stageWidth;
}
/**
* Get a sign (positive or negative) of a numeric value
* @param x
* @return
*/
private function sign(x:Number):int {
return (x >= 0) ? 1 : -1;
}
/**
* If x > 2*Pi : x = 0
* @param x
* @return
*/
private function wrap2Pi(x:Number):Number {
if (x > 2 * Math.PI) x = 0;
return x;
}
/**
* Create a clickable rectangle with a handler function
* @param px
* @param py
* @param parent
* @param handler
*/
private function addButton(px:Number, py:Number, parent:Sprite, id:String, handler:Function):void {
var bd:BitmapData = new BitmapData(_buttonDim, _buttonDim, false, 0xffffff * Math.random());
var btn:Bitmap = new Bitmap(bd);
var s:Sprite = new Sprite();
s.addChild(btn);
s.addEventListener(MouseEvent.CLICK, handler);
parent.addChild(s);
s.name = id;
s.x = px;
s.y = py;
}
private function buttonClickHandler(e:Event):void {
var id:int = int((e.currentTarget as Sprite).name);
_currentFunction = _functions[id][0];
_caption.text = _functions[id][1];
initDataPoints();
}
/**
* Add some controls
*/
private function createButtons():void {
var i:uint;
var spacing:Number = (stage.stageHeight - _buttonDim * _functions.length) / (_functions.length - 1);
for (i=0; i<_functions.length; i++) {
addButton(0, i * (_buttonDim + spacing), this, String(i), buttonClickHandler);
}
}
/**
* Init formula changing time
*/
private function initFormulaChanging():void {
var tmr:Timer = new Timer(10000);
tmr.addEventListener(TimerEvent.TIMER, onTimer);
tmr.start();
}
private function onTimer(e:TimerEvent):void {
_i++;
_i = (_i == _functions.length) ? 0 : _i;
_currentFunction = _functions[_i][0];
_caption.text = _functions[_i][1];
initDataPoints();
}
}
}