3D procedural sphere
You can add or remove segments to vary simplisity of a sphere.
package
{
import com.adobe.utils.AGALMiniAssembler;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.display.Stage3D;
import flash.display.StageAlign;
import flash.display.StageQuality;
import flash.display.StageScaleMode;
import flash.display3D.Context3D;
import flash.display3D.Context3DCompareMode;
import flash.display3D.Context3DProfile;
import flash.display3D.Context3DProgramType;
import flash.display3D.Context3DRenderMode;
import flash.display3D.Context3DTextureFormat;
import flash.display3D.Context3DTriangleFace;
import flash.display3D.Context3DVertexBufferFormat;
import flash.display3D.IndexBuffer3D;
import flash.display3D.Program3D;
import flash.display3D.textures.Texture;
import flash.display3D.VertexBuffer3D;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.events.TouchEvent;
import flash.geom.Matrix;
import flash.geom.Matrix3D;
import flash.geom.Vector3D;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.ui.Keyboard;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
import flash.utils.getTimer;
public class Main_lights_primitives extends Sprite
{
private var _cameraRotation:Vector3D = new Vector3D(-340, -200, 0);
private var _cameraRadius:Number = 100;
private var _mousePos2d:Vector3D;
private var _texture:BitmapData;
private var _mx:Matrix3D = new Matrix3D();
private var _verts:Vector.<Number>; //xyzuvnnn, xyzuvnnn, ... (stride=8)
private var _vStride:uint = 8;
private var _indices:Vector.<uint>;
private var _rotTime:int;
private var _HQ:Boolean = true;
private var _ctx3d:Context3D;
private var _shaders:Program3D;
private var _iBuf:IndexBuffer3D;
private var _vBuf:VertexBuffer3D;
private var _viewMx:Matrix3D;
private var _projMx:Matrix3D;
private var _diffTex:Texture;
private var _camPos:Vector3D;
private var _lightPos:Vector3D = new Vector3D(-100, 100, -100);
private var _ambient:Number = .5;
private var _needsSnapshot:Boolean = false;
private var _snapshot:Bitmap;
public function Main_lights_primitives():void
{
trace('initializing stage 2d ...');
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
prepare2dStage();
requestContext3DOf2dStage();
initInteractivity();
}
private function prepare2dStage():void {
stage.frameRate = 60;
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
stage.quality = qualitySettings(StageQuality.LOW, StageQuality.HIGH);
trace('stage 2d is ready!');
}
/// block 3d ///
private function requestContext3DOf2dStage():void {
trace('creating context 3d ...');
stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreate);
stage.stage3Ds[0].requestContext3D(Context3DRenderMode.AUTO, Context3DProfile.BASELINE);
}
private function onContext3DCreate(e:Event):void {
_ctx3d = (e.target as Stage3D).context3D;
if (!_ctx3d) {
trace('Error: can\'t obtain context 3d!');
return;
} else {
trace('context 3d created!');
}
initContet3D();
initShaders();
initMesh();
initTexture();
// add keyboard handlers
stage.addEventListener(KeyboardEvent.KEY_UP, keyboardHandler);
// start rendering
stage.addEventListener(Event.ENTER_FRAME, onEnterFrame);
initGUIControls();
}
private function initContet3D():void {
// debug
_ctx3d.enableErrorChecking = true;
// handle resizing of a viewport
stage.addEventListener(Event.RESIZE, onResize);
onResize();
}
private function onResize(e:Event = null):void {
_ctx3d.configureBackBuffer(stage.stageWidth, stage.stageHeight, qualitySettings(0, 4), true, false);
}
private function initShaders():void {
// vertex shader which does a 3d transformation of a model
var vs:String =
"m44 op, va0, vc0" + "\n" + // OutputPosition(op) = xyz(va0) * transformMatrix(vc0)
"mov v0, va0" + "\n" + // va0 = xyz, put it as v0
"mov v1, va1" + "\n" + // va1 = uv, put it to FS as v1
"mov v2, va2" + "\n" + // v2 = normal's xyz(va2)
"sub v3, vc4, va0"; // v3 = "light's direction" = "light's position"(vc4) - "current vertex"(va0)
var vsa:AGALMiniAssembler = new AGALMiniAssembler();
vsa.assemble(Context3DProgramType.VERTEX, vs);
var texFlags:String = qualitySettings("<2d>", "<2d,linear,miplinear>");
// fragment shader to render a model
/*var fs:String =
"tex ft0, v1, fs0 " + texFlags + "\n" + // take the texture color from texture(fs0) in uv(v1), <2d,linear,miplinear>
"mov oc, ft0" + "\n"; // color to OutputColor(oc)*/
var fs:String =
"tex ft0, v1, fs0 " + texFlags + "\n" + // take the texture color from texture(fs0) in uv(v1), <2d,linear,miplinear>
"nrm ft1.xyz, v2" + "\n" + // ft1 = norm(n), v2=n. make sure, that n is a unit vector
"nrm ft2.xyz, v3" + "\n" + // ft2 = norm(lightDirection), lightDirection=v3
"dp3 ft3.x, ft1.xyz, ft2.xyz" + "\n" + // ft3.x = dot(n, lightDirection)
"max ft3.x, ft3.x, fc1.x" + "\n" + // ft3.x > 0 !
"add ft3, ft3.x, fc0" + "\n" + // ft3 += ambient
"mul ft0, ft0, ft3" + "\n" + // color *= ft3
//"mov oc, v2"; // draw a normal
"mov oc, ft0"; // color to OutputColor(oc)
var fsa:AGALMiniAssembler = new AGALMiniAssembler();
fsa.assemble(Context3DProgramType.FRAGMENT, fs);
// combine shaders into a single program ready to be uploaded to GPU
_shaders = _ctx3d.createProgram();
_shaders.upload(vsa.agalcode, fsa.agalcode)
}
private function initMesh():void {
// read a raw data
readModel();
// indices
_iBuf = _ctx3d.createIndexBuffer(_indices.length); // total amount of vertices forming the triandles
_iBuf.uploadFromVector(_indices, 0, _indices.length);
// vertices
_vBuf = _ctx3d.createVertexBuffer(_verts.length / _vStride, _vStride);
_vBuf.uploadFromVector(_verts, 0, _verts.length / _vStride);
}
private function initTexture():void {
readTexture();
// textures
_diffTex = createTexture(_texture);
}
private function createTexture(bd:BitmapData):Texture {
var mipMap:BitmapData = bd;
var tex:Texture = _ctx3d.createTexture(bd.width, bd.height, Context3DTextureFormat.BGRA, false);
var mipLevel:int = 0;
tex.uploadFromBitmapData(bd, mipLevel++);
// create mipmaps
while (bd.width > 1 || bd.height > 1) {
mipMap = new BitmapData(Math.max(1, bd.width >> 1), Math.max(1, bd.height >> 1), true, 0);
mipMap.draw(bd, new Matrix(0.5, 0, 0, 0.5, 0, 0), null, null, null, true);
tex.uploadFromBitmapData(mipMap, mipLevel++);
bd = mipMap;
}
return tex;
}
private function onEnterFrame(e:Event):void {
autoRotate();
updateCamera();
render();
}
private function updateCamera():void {
_mx = new Matrix3D();
_viewMx = viewMatrix(_cameraRotation, _cameraRadius, 0);
_projMx = projMatrix(45, stage.stageWidth / stage.stageHeight, 0.1, 1000);
// multiply matrices
_mx.append(_viewMx);
_mx.append(_projMx);
// camera's position
_viewMx.invert();
_camPos = _viewMx.position;
}
private function autoRotate():void {
// update camera
if (!_mousePos2d) {
var dr:Number = (getTimer() - _rotTime) * 0.01;
//_cameraRotation.x += dr;
_cameraRotation.y += dr;
//_cameraRotation.z += dr;
_rotTime = getTimer();
}
}
private function render():void {
if (!_ctx3d) return;
_lightPos = _camPos;
// init renderer for the new frame
_ctx3d.setDepthTest(true, Context3DCompareMode.LESS_EQUAL);
_ctx3d.setCulling(Context3DTriangleFace.FRONT);
_ctx3d.clear(.5, .5, .5);
// upload shaders
_ctx3d.setProgram(_shaders);
// upload constants
// vertex
_ctx3d.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, _mx, true); // vc0 = a 4x4 transform matrix -> 4 registers (vc0-vc3)
_ctx3d.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 4, Vector.<Number>([_lightPos.x, _lightPos.y, _lightPos.z, 0.0])); // vc4 = light's position
// fragment
_ctx3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, Vector.<Number>([_ambient, _ambient, _ambient, 1.0])); // fc0 = ambient
_ctx3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 1, Vector.<Number>([0.0, 0.0, 0.0, 0.0])); // fc1.x = 0
// upload geometry
_ctx3d.setVertexBufferAt(0, _vBuf, 0, Context3DVertexBufferFormat.FLOAT_3); // va0 = vertex xyz
_ctx3d.setVertexBufferAt(1, _vBuf, 3, Context3DVertexBufferFormat.FLOAT_2); // va1 = texCoord uv
_ctx3d.setVertexBufferAt(2, _vBuf, 5, Context3DVertexBufferFormat.FLOAT_3); // va2 = normal nnn
// upload texture
_ctx3d.setTextureAt(0, _diffTex); // 0 means (fs0) in the fragment shader
// draw!
_ctx3d.drawTriangles(_iBuf, 0, _indices.length / 3);
drawSnapshot();
// backbuffer to the screen
_ctx3d.present();
}
private function qualitySettings(low:*, high:*):* {
if (_HQ) {
return high;
} else {
return low;
}
}
private function drawSnapshot():void {
// render to texture if needed. should be called before _ctx3d.present();
if (_needsSnapshot) {
_needsSnapshot = false;
_ctx3d.drawToBitmapData(_snapshot.bitmapData);
_snapshot.visible = true;
_needsSnapshot = false;
// add text
var tf:TextField = new TextField();
tf.text = "This's a snapshot, press \"SPACE\" again to continue.";
tf.autoSize = TextFieldAutoSize.LEFT;
tf.textColor = 0xffffff;
_snapshot.bitmapData.draw(tf);
}
}
private function askForSnapshot():void {
if (!_snapshot) {
_snapshot = new Bitmap(new BitmapData(stage.stageWidth, stage.stageHeight, false));
addChild(_snapshot);
}
if (_snapshot.visible) {
_snapshot.visible = false;
} else {
_needsSnapshot = true;
}
}
private function keyboardHandler(e:KeyboardEvent):void {
switch (e.keyCode) {
case Keyboard.SPACE:
askForSnapshot();
break;
default:
break;
}
}
/// block
private function initInteractivity():void {
// mouse listeners
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDwn);
stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
stage.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
// touch listeners
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
stage.addEventListener(TouchEvent.TOUCH_BEGIN, onTouchBegin);
stage.addEventListener(TouchEvent.TOUCH_MOVE, onTouchMove);
stage.addEventListener(TouchEvent.TOUCH_END, onTouchEnd);
}
private function onMouseDwn(e:MouseEvent):void {
_mousePos2d = new Vector3D(mouseX, mouseY);
}
private function onMouseUp(e:MouseEvent):void {
_mousePos2d = null;
_rotTime = getTimer();
}
private function onMouseMove(e:MouseEvent):void {
if (!_mousePos2d) return;
_cameraRotation.x += (mouseY - _mousePos2d.y) * 0.5;
_cameraRotation.y += (mouseX - _mousePos2d.x) * 0.5;
_mousePos2d = new Vector3D(mouseX, mouseY);
}
private function onMouseWheel(e:MouseEvent):void {
var factor:Number = (e.delta > 0) ? 1.1 : 0.9;
_cameraRadius *= factor
updateRadius(factor);
}
private function onTouchBegin(e:TouchEvent):void {
_mousePos2d = new Vector3D(e.stageX, e.stageY);
}
private function onTouchMove(e:TouchEvent):void {
if (!_mousePos2d) return;
_cameraRotation.x += (e.stageY - _mousePos2d.y) * 0.5;
_cameraRotation.y += (e.stageX - _mousePos2d.x) * 0.5;
_mousePos2d = new Vector3D(e.stageX, e.stageY);
}
private function onTouchEnd(e:TouchEvent):void {
_mousePos2d = null;
_rotTime = getTimer();
}
private function projMatrix(FOV:Number, aspect:Number, zNear:Number, zFar:Number):Matrix3D {
var sy:Number = 1.0 / Math.tan(FOV * Math.PI / 360.0),
sx:Number = sy / aspect;
return new Matrix3D(Vector.<Number>([
sx, 0.0, 0.0, 0.0,
0.0, sy, 0.0, 0.0,
0.0, 0.0, zFar / (zNear - zFar), -1.0,
0.0, 0.0, (zNear * zFar) / (zNear - zFar), 0.0]));
}
private function viewMatrix(rot:Vector3D, dist:Number, centerY:Number):Matrix3D {
var m:Matrix3D = new Matrix3D();
m.appendTranslation(0, -centerY, 0);
m.appendRotation(rot.z, new Vector3D(0, 0, 1));
m.appendRotation(rot.y, new Vector3D(0, 1, 0));
m.appendRotation(rot.x, new Vector3D(1, 0, 0));
m.appendTranslation(0, 0, -dist);
return m;
}
private function readModel():void {
SphereMesh.buildGeometry();
// geometry
_verts = new Vector.<Number>();
_indices = new Vector.<uint>();
var i:uint;
for (i = 0; i < SphereMesh.vertices.length / 3; i++) {
// xyz
_verts.push(SphereMesh.vertices[3*i]);
_verts.push(SphereMesh.vertices[3*i+1]);
_verts.push(SphereMesh.vertices[3*i+2]);
// uv
_verts.push(SphereMesh.uvs[2*i]);
_verts.push(SphereMesh.uvs[2 * i + 1]);
// nxnynz
_verts.push(SphereMesh.normals[3*i]);
_verts.push(SphereMesh.normals[3*i+1]);
_verts.push(SphereMesh.normals[3*i+2]);
}
for (i = 0; i < SphereMesh.indices.length; i++) {
_indices.push(SphereMesh.indices[i]);
}
}
private function readTexture():void {
SphereMesh.buildTexture();
// texture
_texture = SphereMesh.texture;
}
private function updateRadius(f:Number):void {
var i:uint;
for (i = 0; i < _verts.length; i++) {
_verts[i] *= f;
}
}
private function initGUIControls():void {
new Button("+", this, 10, 10, onUpButtonClick);
new Button("-", this, 10, 70, onDownButtonClick);
}
private function onDownButtonClick(e:MouseEvent):void {
SphereMesh.stepsH = SphereMesh.stepsV *= .5;
SphereMesh.stepsH = SphereMesh.stepsV = checkRange(SphereMesh.stepsH, 2, 245);
trace(SphereMesh.stepsH);
initMesh();
initTexture();
}
private function onUpButtonClick(e:MouseEvent):void {
SphereMesh.stepsH = SphereMesh.stepsV *= 1.5;
SphereMesh.stepsH = SphereMesh.stepsV = checkRange(SphereMesh.stepsH, 2, 245);
trace(SphereMesh.stepsH);
initMesh();
initTexture();
}
private function checkRange(v:Number, min:Number, max:Number):Number {
if (v <= min) v = min;
if (v >= max) v = max;
return v;
}
}
}
import flash.display.BitmapData;
import flash.display.Graphics;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.geom.*;
import flash.text.TextField;
class SphereMesh {
public static const color:uint = 0x00ccff;
public static const alpha:Number = 1;
public static const R:Number = 10;
public static var stepsH:uint = 10;
public static var stepsV:uint = 10;
// v0,v1,v2, v0,v1,v2, ...
public static var indices:Array = [];
// x,y,z, x,y,z, ...
public static var vertices:Array = [];
// u,v, u,v, ...
public static var uvs:Array = [];
// nx,ny,nz, nx,ny,nz, ...
public static var normals:Array = [];
public static var texture:BitmapData;
public static function buildTexture():void {
texture = new BitmapData(512, 512, false);
var i:uint, j:uint;
var size:uint = texture.height / stepsV;
var checker:Shape = new Shape();
checker.graphics.beginFill(color, alpha);
for (i = 0; i < texture.width / size; i++) {
for (j = 0; j < texture.height / size; j++) {
with (checker.graphics) {
drawRect((2 * i + j % 2) * size, j * size, size, size);
}
}
}
checker.graphics.endFill();
texture.draw(checker);
}
/**
* Fill indices, vertices, normals and uvs
*/
public static function buildGeometry():void {
// reset all
indices = new Array();
vertices = new Array();
uvs = new Array();
normals = new Array();
var i:uint, j:uint;
var p:Vector3D; // position of a vertex
var n:Vector3D; // normal of a vertex
var uv:Vector3D; // uv of a vertex
var Phi:Number, Theta:Number;
var dPhi:Number = Math.PI / stepsV;
var dTheta:Number = 2 * Math.PI / stepsH;
// build vertices
for (i = 0; i <= stepsV; i++) {
Phi = i * dPhi;
for (j = 0; j <= stepsH; j++) {
Theta = j * dTheta;
// vertex
p = new Vector3D(Math.sin(Phi)*Math.cos(Theta), Math.cos(Phi), Math.sin(Phi)*Math.sin(Theta));
p.scaleBy(R);
// normal (for sphere it equals its pos)
n = new Vector3D(p.x, p.y, p.z);
n.normalize();
// uvs
uv = new Vector3D(Theta / (2 * Math.PI), Phi / Math.PI);
// save
vertices.push(p.x);
vertices.push(p.y);
vertices.push(p.z);
normals.push(n.x);
normals.push(n.y);
normals.push(n.z);
uvs.push(uv.x);
uvs.push(uv.y);
}
}
// build indices (triangles)
for (i = 0; i <= stepsH; i++) {
indices.push(0);
indices.push(i + 1);
indices.push(i);
}
var baseIndex:uint = 0;
var ringVertexCount:int = stepsH + 1;
for (i = 0; i < stepsV; i++) {
for (j = 0; j < stepsH; j++) {
indices.push(baseIndex + i * ringVertexCount + j);
indices.push(baseIndex + i * ringVertexCount + j + 1);
indices.push(baseIndex + (i + 1) * ringVertexCount + j);
indices.push(baseIndex + (i + 1) * ringVertexCount + j);
indices.push(baseIndex + i * ringVertexCount + j + 1);
indices.push(baseIndex + (i + 1) * ringVertexCount + j + 1);
}
}
}
}
class Button extends Sprite {
private var _body:Sprite;
private var _text:TextField;
private var _pressed:Boolean = false;
public function get caption():String {
return _text.text;
}
public function Button(name:String, parent:Sprite, x:Number, y:Number, clickHandler:Function) {
super();
_body = new Sprite();
_text = new TextField();
_text.text = name;
_text.selectable = false;
_text.x = 5;
_text.y = 15;
_text.width = 50;
update();
addChild(_text);
addChild(_body);
parent.addChild(this);
this.x = x;
this.y = y;
this.buttonMode = true;
addEventListener(MouseEvent.CLICK, clickHandler);
}
private function update():void {
var g:Graphics = _body.graphics;
g.clear();
g.lineStyle(1, 0);
g.beginFill(0xcccccc, .1);
g.drawRect(0, 0, 50, 50);
g.endFill();
}
}