3D ドーナッツ
package
{
import flash.display.*;
import flash.events.*;
import flash.text.*;
import flash.ui.Keyboard;
import flash.utils.getTimer;
[SWF(width="465", height="465",
backgroundColor="0x000000", frameRate="30")]
public class Donuts02 extends Sprite
{
private var _donuts:Donuts;
private var _matrix:Matrix3D;
private var _render:Render;
private var _textField:TextField;
private var _rx:Number = 0;
private var _ry:Number = 0;
private var _rz:Number = 0;
private var _isAutoPlay:Boolean = false;
private const STAGE_WIDTH:Number = stage.stageWidth;
private const STAGE_HEIGHT:Number = stage.stageHeight;
public function Donuts02()
{
configure();
var pi:Number = Math.PI;
var start:int;
var i:int;
_donuts = new Donuts(16, 0.7);
_matrix = new Matrix3D();
_render = new Render(graphics, _matrix);
addTextField();
mainLoop(null);
stage.addEventListener(MouseEvent.CLICK, changePlayMode);
_textField.text =
"Partitions : " + _donuts.numPartitions + "\n" +
"Vertices : " + _donuts.vertices.length + "\n" +
"Stretch : " + _donuts.stretch;
}
private function configure():void
{
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.showDefaultContextMenu = false;
}
private function addTextField():void
{
_textField = new TextField();
_textField.defaultTextFormat = new TextFormat(
null, null, 0xFFFFFF, null, null, null, null, null, TextFormatAlign.RIGHT);
_textField.autoSize = TextFieldAutoSize.RIGHT;
_textField.background = true;
_textField.backgroundColor = 0x000000;
_textField.selectable = false;
_textField.x = STAGE_WIDTH - _textField.width;
addChild(_textField);
var announce:TextField = new TextField();
announce.defaultTextFormat = new TextFormat(
null, null, 0xFFFFFF, null, null, null, null, null, TextFormatAlign.LEFT);
announce.autoSize = TextFieldAutoSize.LEFT;
announce.background = true;
announce.backgroundColor = 0x000000;
announce.selectable = false;
announce.text =
"\"DOWN\" : Partitions -\n" +
"\"UP\" : Partitions +\n" +
"\"LEFT\" : Stretch -\n" +
"\"RIGHT\" : Stretch +\n" +
"\"Z\" : Polygon on/off\n" +
"\"X\" : Wireframe on/off";
announce.y = STAGE_HEIGHT - announce.height;
addChild(announce);
}
private function mainLoop(event:Event):void
{
_matrix.identity();
_matrix.scale(50, 50, 50);
_matrix.rotateX(_rx);
_matrix.rotateY(_ry);
_matrix.rotateZ(_rz);
_matrix.translate(STAGE_WIDTH / 2, STAGE_HEIGHT / 2, 0);
graphics.clear();
_render.drawIndexedPrimitive(
Render.PRIMITIVE_POLYGON, _donuts.vertices, _donuts.indices);
_rx += 0.04;
_ry += 0.08;
_rz += 0.02;
}
private function changePlayMode(event:MouseEvent):void
{
if (_isAutoPlay)
{
removeEventListener(Event.ENTER_FRAME, mainLoop);
stage.removeEventListener(KeyboardEvent.KEY_DOWN, changeDonuts);
}
else
{
addEventListener(Event.ENTER_FRAME, mainLoop);
stage.addEventListener(KeyboardEvent.KEY_DOWN, changeDonuts);
}
_isAutoPlay = !_isAutoPlay;
}
private function changeDonuts(event:KeyboardEvent):void
{
var numPartitions:int = _donuts.numPartitions;
switch (event.keyCode)
{
case Keyboard.UP:
if (numPartitions >= 0)
{
_donuts.numPartitions += 1;
}
break;
case Keyboard.DOWN:
if (numPartitions > 0)
{
_donuts.numPartitions -= 1;
}
break;
case Keyboard.LEFT:
_donuts.stretch -= 0.02;
break;
case Keyboard.RIGHT:
_donuts.stretch += 0.02;
break;
case 88:
//trace("X");
_render.wireframeEnabled = !_render.wireframeEnabled;
break;
case 90:
//trace("Z");
_render.polygonEnabled = !_render.polygonEnabled;
break;
}
_textField.text =
"Partitions : " + _donuts.numPartitions + "\n" +
"Vertices : " + _donuts.vertices.length + "\n" +
"Stretch : " + _donuts.stretch.toString().substr(0, 4);
}
}
}
import flash.display.Graphics;
class Render
{
public static const PRIMITIVE_POLYGON:int = 1;
private var _graphics:Graphics;
private var _matrix:Matrix3D;
private var _mode:int;
private var _polygonEnabled:Boolean = true;
private var _wireframeEnabled:Boolean = false;
// 光源ベクトル
private var _light:Vector3D = new Vector3D( 0.0, 0.0, -1.0);
// アンビエント(環境光)の色
private var _ambient:Vector3D = new Vector3D( 0.2, 0.2, 0.2);
// ディフューズ(拡散光)の色
private var _diffuse:Vector3D = new Vector3D( 0.8, 0.8, 0.8);
/**
* 新しい Render インスタンスを作成します。
*/
public function Render(
graphics:Graphics = null, matrix:Matrix3D = null, mode:int = 1):void
{
_graphics = graphics;
_matrix = matrix;
_mode = mode;
}
public function drawIndexedPrimitive(type:int, vertices:Array, indices:Array):void
{
var i:int;
var l:int;
var tv:Array = [];
// 座標を一次変換
for (i = 0, l = vertices.length; i < l; ++i)
{
tv.push(_matrix.transform(vertices[i]));
}
// ラスタライズ
switch (type)
{
case PRIMITIVE_POLYGON:
var triangles:Array = [];
for (i = 0, l = indices.length; i < l; i += 3)
{
triangles.push(new Triangle3D(
tv[indices[i ]],
tv[indices[i + 1]],
tv[indices[i + 2]]
));
}
// 深度ソート
triangles.sortOn("depth", Array.DESCENDING | Array.NUMERIC);
for each (var triangle:Triangle3D in triangles)
{
drawPolygon(triangle);
}
break;
}
}
private function drawPolygon(triangle:Triangle3D):void
{
if (polygonEnabled)
{
// 面の法線ベクトルを求める。
var n:Vector3D = triangle.normal();
// 光源と法線の内積を取る。
var w:Number = n.dot(_light);
// 背面カリング処理。
// 内積が負なら背面なので描画しない。(ただしパースをつけた場合は正確な判定方法ではない。)
if (w < 0)
{
return;
}
var r:int = (_ambient.x + w * _diffuse.x) * 255;
var g:int = (_ambient.y + w * _diffuse.y) * 255;
var b:int = (_ambient.z + w * _diffuse.z) * 255;
r = (r < 0) ? 0 : (r > 255) ? 255 : r;
g = (g < 0) ? 0 : (g > 255) ? 255 : g;
b = (b < 0) ? 0 : (b > 255) ? 255 : b;
_graphics.beginFill(r << 16 | g << 8 | b);
}
if (wireframeEnabled)
{
_graphics.lineStyle(0, 0xFF0000);
}
_graphics.moveTo(triangle.v1.x, triangle.v1.y);
_graphics.lineTo(triangle.v2.x, triangle.v2.y);
_graphics.lineTo(triangle.v3.x, triangle.v3.y);
_graphics.lineTo(triangle.v1.x, triangle.v1.y);
_graphics.endFill();
}
public function get graphics():Graphics
{
return _graphics;
}
public function set graphics(value:Graphics):void
{
_graphics = value;
}
public function get matrix():Matrix3D
{
return _matrix;
}
public function set matrix(value:Matrix3D):void
{
_matrix = value;
}
public function get polygonEnabled():Boolean
{
return _polygonEnabled;
}
public function set polygonEnabled(value:Boolean):void
{
_polygonEnabled = value;
}
public function get wireframeEnabled():Boolean
{
return _wireframeEnabled;
}
public function set wireframeEnabled(value:Boolean):void
{
_wireframeEnabled = value;
}
}
class Donuts
{
private var _vertices:Array;
private var _indices:Array;
private var _numPartitions:int;
private var _stretch:Number;
/**
* @param numPartitions 分割数
* @param stretch 拡がり
*/
public function Donuts(numPartitions:int = 16, stretch:Number = 1):void
{
_numPartitions = numPartitions;
_stretch = stretch;
setup(numPartitions, stretch);
}
/**
* @private
*/
private function setup(numPartitions:int, stretch:Number):void
{
var n:int = numPartitions;
var i:int;
var j:int;
var a:Number = 0;
var c:Number = 0;
var addc:Number = Math.PI * 2 / n;
var s:Number;
var z:Number;
var x:Number;
var y:Number;
_vertices = [];
for (i = 0; i < n; ++i)
{
s = Math.sin(c) + 2;
z = Math.cos(c);
for (j = 0; j < n; ++j)
{
x = Math.cos(a);
y = Math.sin(a);
_vertices.push(new Vector3D(
x * s * stretch,
y * s * stretch,
z * stretch
));
a += Math.PI / n * 2;
}
c += addc;
}
var m:int = n * n;
var off:Number;
_indices = [];
for (i = 0; i < n; ++i)
{
off = i * n;
for (j = 0; j < n; ++j)
{
_indices.push((off + n) % m + (j + 1) % n);
_indices.push( off + (j + 1) % n);
_indices.push( off + j );
_indices.push((off + n) % m + j );
_indices.push((off + n) % m + (j + 1) % n);
_indices.push( off + j );
}
}
}
public function get vertices():Array
{
return _vertices.concat();
}
public function get indices():Array
{
return _indices.concat();
}
public function get numPartitions():int
{
return _numPartitions;
}
public function set numPartitions(value:int):void
{
_numPartitions = value;
setup(value, stretch);
}
public function get stretch():Number
{
return _stretch;
}
public function set stretch(value:Number):void
{
_stretch = value;
setup(numPartitions, value);
}
}
class Triangle3D
{
private var _v1:Vector3D;
private var _v2:Vector3D;
private var _v3:Vector3D;
/**
* 新しい Triangle3D インスタンスを作成します。
*/
public function Triangle3D(v1:Vector3D, v2:Vector3D, v3:Vector3D):void
{
_v1 = v1;
_v2 = v2;
_v3 = v3;
}
public function get v1():Vector3D
{
return _v1;
}
public function set v1(value:Vector3D):void
{
_v1 = value;
}
public function get v2():Vector3D
{
return _v2;
}
public function set v2(value:Vector3D):void
{
_v2 = value;
}
public function get v3():Vector3D
{
return _v3;
}
public function set v3(value:Vector3D):void
{
_v3 = value;
}
/**
* 各ベクトルの z の値を比較し、最も低い数値を返します。
* 深度ソート時に Array オブジェクトの sortOn と合わせて使用します。
*/
public function get depth():Number
{
var min:Number = (v1.z < v2.z) ? v1.z : v2.z;
return (min < v3.z) ? min : v3.z;
}
/**
* このトライアングルを面とする法線ベクトルを返します。
*
* @return このトライアングルを面とする法線ベクトル
*/
public function normal():Vector3D
{
var n:Vector3D = new Vector3D();
n.cross(v1.distance(v2), v1.distance(v3));
n.normalize();
return n;
}
/**
* このトライアングルのストリング表現を返します。
*
* @return このトライアングルのストリング表現
*/
public function toString():String
{
var temp:String = "Triangle3D {\n" +
"\tv1 : " + v1 + "\n" +
"\tv2 : " + v2 + "\n" +
"\tv3 : " + v3 + "\n}";
return temp;
}
}
class Matrix3D
{
private var _matrix:Array;
/**
* 新しい Matrix3D インスタンスを作成します。
*/
public function Matrix3D():void
{
identity();
}
public function identity():void
{
_matrix = [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
];
}
public function scale(sx:Number, sy:Number, sz:Number)
{
_matrix = _multiply(_matrix, [
sx, 0, 0, 0,
0, sy, 0, 0,
0, 0, sz, 0,
0, 0, 0, 1
]);
}
public function translate(dx:Number, dy:Number, dz:Number)
{
_matrix = _multiply(_matrix, [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
dx, dy, dz, 1
]);
}
public function rotateX(angleRadians:Number)
{
var sin = Math.sin(angleRadians);
var cos = Math.cos(angleRadians);
_matrix = _multiply(_matrix, [
1, 0, 0, 0,
0, cos, sin, 0,
0, -sin, cos, 0,
0, 0, 0, 1
]);
}
public function rotateY(angleRadians:Number)
{
var sin = Math.sin(angleRadians);
var cos = Math.cos(angleRadians);
_matrix = _multiply(_matrix, [
cos, 0, -sin, 0,
0, 1, 0, 0,
sin, 0, cos, 0,
0, 0, 0, 1
]);
}
public function rotateZ(angleRadians:Number)
{
var sin = Math.sin(angleRadians);
var cos = Math.cos(angleRadians);
_matrix = _multiply(_matrix, [
cos, sin, 0, 0,
-sin, cos, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
}
/**
* このマトリクスで指定のベクトルを一次変換します。
*
* @return 一次変換されたベクトル
*/
public function transform(v:Vector3D):Vector3D
{
var temp:Vector3D = new Vector3D();
var m:Array = _matrix;
temp.x = m[0] * v.x + m[4] * v.y + m[ 8] * v.z + m[12];
temp.y = m[1] * v.x + m[5] * v.y + m[ 9] * v.z + m[13];
temp.z = m[2] * v.x + m[6] * v.y + m[10] * v.z + m[14];
return temp;
}
/**
* このマトリクスと指定マトリクスの成分を乗算(合成)します。
*
* @param m 対象の Matrix3D オブジェクト
*/
public function multiply(m:Matrix3D):void
{
_matrix = _multiply(m.toArray(), _matrix);
}
/**
* @private
*/
private function _multiply(m1:Array, m2:Array):Array
{
var temp:Array = [];
var i:int = 0;
for (var y:int = 0; y < 4; ++y)
{
for (var x:int = 0; x < 4; ++x)
{
temp[x + i] =
m1[i ] * m2[x ] +
m1[i + 1] * m2[x + 4] +
m1[i + 2] * m2[x + 8] +
m1[i + 3] * m2[x + 12];
}
i += 4;
}
return temp;
}
/**
* このマトリクスの成分配列を返します。
*
* @return このマトリクスの成分配列
*/
public function toArray():Array
{
return _matrix.concat();
}
/**
* このマトリクスのストリング表現を返します。
*
* @return このマトリクスのストリング表現
*/
public function toString():String
{
var temp:String = "Matrix3D [\n";
for (var i:int = 0, l:int = _matrix.length; i < l; ++i)
{
if (i % 4 == 0)
{
if (i != 0)
{
temp += "\n";
}
temp += "\t";
}
if (i != l - 1)
{
temp += _matrix[i] + ", ";
}
else
{
temp += _matrix[i] + "\n]";
}
}
return temp;
}
}
class Vector3D
{
private var _x:Number;
private var _y:Number;
private var _z:Number;
/**
* 新しい Vector3D インスタンスを作成します。
*/
public function Vector3D(x:Number = 0, y:Number = 0, z:Number = 0):void
{
_x = x;
_y = y;
_z = z;
}
public function get x():Number
{
return _x;
}
public function set x(value:Number):void
{
_x = value;
}
public function get y():Number
{
return _y;
}
public function set y(value:Number):void
{
_y = value;
}
public function get z():Number
{
return _z;
}
public function set z(value:Number):void
{
_z = value;
}
/**
* このベクトルを正規化して単位ベクトルにします。
* 単位ベクトルとは大きさが 1 のベクトルのことです。
*/
public function normalize():void
{
var l:Number = 1 / Math.sqrt(x * x + y * y + z * z);
x *= l;
y *= l;
z *= l;
}
/**
* 指定された 2 つのベクトルの外積をこのベクトルに設定します。
* 外積は主に、面に垂直な法線ベクトルを求めるために使用します。
*
* @param v1 1 つ目のベクトル
* @param v1 2 つ目のベクトル
*/
public function cross(v1:Vector3D, v2:Vector3D):void
{
x = v1.y * v2.z - v1.z * v2.y;
y = v1.z * v2.x - v1.x * v2.z;
z = v1.x * v2.y - v1.y * v2.x;
}
/**
* このベクトルと指定ベクトルの内積を返します。
* 内積を取るとベクトルとベクトルのなす角度が求まります。
*
* @param v ベクトル
* @return このベクトルと指定ベクトルの内積
*/
public function dot(v:Vector3D):Number
{
return x * v.x + y * v.y + z * v.z;
}
/**
* 指定された 2 つのベクトルの内積を返します。
* 内積を取るとベクトルとベクトルのなす角度が求まります。
*
* @param v1 1 つ目のベクトル
* @param v1 2 つ目のベクトル
* @return 指定された 2 つのベクトルの内積
*/
public static function dot(v1:Vector3D, v2:Vector3D):Number
{
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
}
/**
* ベクトルを反転します。
*/
public function invert():void
{
x *= -1;
y *= -1;
z *= -1;
}
/**
* このベクトルと指定ベクトルとの差分を表すベクトルを返します。
*
* @return 差分を表すベクトル
*/
public function distance(v:Vector3D):Vector3D
{
return new Vector3D(x - v.x, y - v.y, z - v.z);
}
/**
* このベクトルのコピーを返します。
*
* @return このベクトルのコピー
*/
public function clone():Vector3D
{
return new Vector3D(x, y, z);
}
/**
* このベクトルのストリング表現を返します。
*
* @return このベクトルのストリング表現
*/
public function toString():String
{
return "Vector3D {x:" + x + ", y:" + y + ", z:" + z + "}";
}
}