3D Trickart
ある一点から見ると一枚の絵に見えるけど視点を移動すると……
*
* 画面ドラッグでカメラを回転できます。
* 画像をロードするとそのときのカメラ位置で投影するので
* 色々なカメラアングルでロードしてみると面白いと思います。
*
* 画像はこちらからお借りしました。
* http://ja.wikipedia.org/wiki/%E3%83%A2%E3%83%8A%E3%83%BB%E3%83%AA%E3%82%B6
*
* 参考サイト
* http://www.c3.club.kyutech.ac.jp/gamewiki/index.php?3D%BA%C2%C9%B8%CA%D1%B4%B9
/**
* Copyright poiasd ( http://wonderfl.net/user/poiasd )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/cnho
*/
/**
* ある一点から見ると一枚の絵に見えるけど視点を移動すると……
*
* 画面ドラッグでカメラを回転できます。
* 画像をロードするとそのときのカメラ位置で投影するので
* 色々なカメラアングルでロードしてみると面白いと思います。
*
* 画像はこちらからお借りしました。
* http://ja.wikipedia.org/wiki/%E3%83%A2%E3%83%8A%E3%83%BB%E3%83%AA%E3%82%B6
*
* 参考サイト
* http://www.c3.club.kyutech.ac.jp/gamewiki/index.php?3D%BA%C2%C9%B8%CA%D1%B4%B9
*/
package {
import com.bit101.components.PushButton;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Graphics;
import flash.display.Loader;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Matrix;
import flash.geom.Matrix3D;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.geom.Vector3D;
import flash.net.FileFilter;
import flash.net.FileReference;
import flash.net.URLRequest;
import flash.system.LoaderContext;
[SWF (width = "465", height = "465", frameRate = "30", backgroundColor = "0xFFFFFF")]
public class Main extends Sprite {
private const _URL:String = "http://assets.wonderfl.net/images/related_images/9/9f/9f8a/9f8a11bc3f4602c960dee21db5f64ec3ba3c112b";
private var _loader:Loader;
private var _fileReference:FileReference;
private var _fileFilter:FileFilter;
private var _loadButton:PushButton;
private var _stageWidth:Number;
private var _stageHeight:Number;
private var _centerX:Number;
private var _centerY:Number;
private var _camera:Camera3D;
private var _focalLength:Number;
private var _viewport:Sprite;
private var _planes:Vector.<Plane>;
private var _sortedIndices:Vector.<int>;
private var _plainImage:BitmapData;
public function Main() {
_init();
}
private function _init():void {
stage.scaleMode = "noScale";
stage.align = "LT";
_stageWidth = stage.stageWidth;
_stageHeight = stage.stageHeight;
_centerX = _stageWidth * 0.5;
_centerY = _stageHeight * 0.5;
graphics.beginFill(0xEEEEEE);
graphics.drawRect(0, 0, _stageWidth, _stageHeight);
graphics.endFill();
_camera = new Camera3D(stage);
_camera.fov = 30;
_camera.distance = 1400;
_camera.azimuth = -45;
_camera.elevation = -30;
_focalLength = _camera.focalLength;
_viewport = addChild(new Sprite()) as Sprite;
_plainImage = _createPlainImage();
_planes = new Vector.<Plane>(6, true);
_planes[0] = new Plane( 0, -200, 0, 400, 400, new Vector3D(-90, 0, 0), _plainImage);
_planes[1] = new Plane( 0, 0, -200, 400, 400, new Vector3D( 0, 0, 0), _plainImage);
_planes[2] = new Plane( 200, 0, 0, 400, 400, new Vector3D( 0, -90, 0), _plainImage);
_planes[3] = new Plane( 0, 200, 0, 400, 400, new Vector3D( 90, 0, 0), _plainImage);
_planes[4] = new Plane( 0, 0, 200, 400, 400, new Vector3D( 0, 180, 0), _plainImage);
_planes[5] = new Plane(-200, 0, 0, 400, 400, new Vector3D( 0, 90, 0), _plainImage);
_sortedIndices = Vector.<int>([0, 1, 2, 3, 4, 5]);
_sortedIndices.fixed = true;
_fileReference = new FileReference();
_fileFilter = new FileFilter("Images(JPEG, GIF, PNG)", "*.jpg;*.jpeg;*.gif;*.png");
_fileReference.addEventListener(Event.SELECT, _selectHandler);
_fileReference.addEventListener(Event.COMPLETE, _completeHandler);
_loadButton = new PushButton(this, 0, 0, "LOAD", _clickHandler);
_loadButton.width = 80;
_loadButton.draw();
_loader = new Loader();
_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, _loaderCompleteHandler);
_loader.load(new URLRequest(_URL), new LoaderContext(true));
stage.addEventListener(MouseEvent.MOUSE_DOWN, _mouseDownHandler);
stage.addEventListener(MouseEvent.MOUSE_UP, _mouseUpHandler);
}
private function _loaderCompleteHandler(event:Event):void {
_loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, _loaderCompleteHandler);
var images:Array = _createImages(Bitmap(_loader.content).bitmapData);
for (var i:int = 0; i < 6; i++) {
_planes[i].image = images[i];
}
_render();
}
private var _preMousePos:Point = new Point();
private function _mouseDownHandler(event:MouseEvent):void {
if (event.target is PushButton) return;
_preMousePos.x = stage.mouseX;
_preMousePos.y = stage.mouseY;
stage.addEventListener(MouseEvent.MOUSE_MOVE, _mouseMoveHandler);
}
private function _mouseMoveHandler(event:MouseEvent):void {
var dx:Number = (stage.mouseX - _preMousePos.x) * 0.5;
var dy:Number = (stage.mouseY - _preMousePos.y) * 0.5;
var el:Number = _camera.elevation - dy;
el = (el + 180) % 360;
el += el < 0 ? 180 : -180;
_camera.up.y = Math.abs(el) <= 90 ? -1 : 1;
_camera.azimuth += dx * _camera.up.y;
_camera.elevation = el;
_project();
_render();
_preMousePos.x = stage.mouseX;
_preMousePos.y = stage.mouseY;
}
private function _mouseUpHandler(event:MouseEvent):void {
stage.removeEventListener(MouseEvent.MOUSE_MOVE, _mouseMoveHandler);
}
private function _selectHandler(event:Event):void {
_fileReference.load();
}
private function _completeHandler(event:Event):void {
_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, _loaderCompleteHandler);
_loader.loadBytes(_fileReference.data);
}
private function _clickHandler(event:MouseEvent):void {
_fileReference.browse([_fileFilter]);
}
private var _matrix3D:Matrix3D = new Matrix3D();
private function _project():void {
_matrix3D.identity();
_matrix3D.append(_camera.viewMatrix);
for each (var plane:Plane in _planes) {
var vectors:Vector.<Vector3D> = plane.vectors;
var tvectors:Vector.<Vector3D> = plane.transformedVectors;
var pverts:Vector.<Number> = plane.projectedVerts;
var v:Vector3D;
var total:Number = 0;
for (var i:int = 0, j:int = 0; i < 4; i++) {
tvectors[i] = v = _matrix3D.transformVector(vectors[i]);
var t:Number = 1 / v.z;
pverts[j++] = _centerX + v.x * _focalLength * t;
pverts[j++] = _centerY + v.y * _focalLength * t;
plane.uvtData[i * 3 + 2] = t;
total += v.z;
}
plane.centerZ = total * 0.25;
}
}
private function _render():void {
_sortedIndices.sort(function(m:int, n:int):Number {
return _planes[n].centerZ - _planes[m].centerZ;
});
var g:Graphics = _viewport.graphics;
g.clear();
//g.lineStyle(0, 0xFFFFFF, 0.2);
for (var i:int = 0; i < 6; i++) {
var plane:Plane = _planes[_sortedIndices[i]];
g.beginBitmapFill(plane.image, null, false, true);
g.drawTriangles(plane.projectedVerts, plane.indices, plane.uvtData);
g.endFill();
}
}
private function _createImages(material:BitmapData):Array {
_project();
var list:Array = _getPositivePlaneIndices();
var rect:Rectangle = _getRect(list);
var offset:Number = 0;
if (material.width <= material.height) {
rect.height = material.height * rect.width / material.width;
} else {
var temp:Number = rect.width;
rect.width = material.width * rect.height / material.height;
offset = ((rect.width - temp) * 0.5) / rect.width;
}
var images:Array = [_plainImage, _plainImage, _plainImage, _plainImage, _plainImage, _plainImage];
var shape:Shape = new Shape();
var g:Graphics = shape.graphics;
var vertices:Vector.<Number> = Vector.<Number>([0, 0, 400, 0, 0, 400, 400, 400]);
var indices:Vector.<int> = Vector.<int>([0, 1, 2, 1, 3, 2]);
var uvtData:Vector.<Number> = new Vector.<Number>();
var pverts:Vector.<Number>;
for (var i:int = 0; i < list.length; i++) {
var index:int = list[i];
pverts = _planes[index].projectedVerts;
for (var j:int = 0, k:int = 0; j < 4; j++) {
uvtData[j * 3] = (pverts[k++] - rect.x) / rect.width + offset;
uvtData[j * 3 + 1] = (pverts[k++] - rect.y) / rect.height;
uvtData[j * 3 + 2] = _planes[index].uvtData[11 - j * 3];
}
g.clear();
g.beginBitmapFill(material, null, false, true);
g.drawTriangles(vertices, indices, uvtData);
g.endFill();
var image:BitmapData = new BitmapData(400, 400, false, 0xFFFFFF);
image.draw(shape);
images[index] = image;
}
return images;
}
private function _createPlainImage():BitmapData {
var shape:Shape = new Shape();
var g:Graphics = shape.graphics;
g.beginFill(0x111111, 0.6);
g.drawRect(0, 0, 400, 400);
g.beginFill(0xFFFFFF, 0.2);
g.drawRect(0, 0, 400, 400);
g.drawRect(1, 1, 398, 398);
g.beginFill(0xFFFFFF);
g.drawRect(180, 198, 40, 4);
g.beginFill(0xFFFFFF);
g.drawRect(198, 180, 4, 40);
g.endFill();
var plainImage:BitmapData = new BitmapData(400, 400, true, 0xFFFFFF);
plainImage.draw(shape);
return plainImage;
}
private function _getPositivePlaneIndices():Array {
var list:Array = [];
for (var i:int = 0; i < 6; i++) {
var pverts:Vector.<Number> = _planes[i].projectedVerts;
var v1:Vector3D = new Vector3D(pverts[2] - pverts[0], pverts[3] - pverts[1], 0);
var v2:Vector3D = new Vector3D(pverts[4] - pverts[0], pverts[5] - pverts[1], 0);
var cross:Vector3D = v1.crossProduct(v2);
if (cross.z > 0) list.push(i);
}
return list;
}
private function _getRect(list:Array):Rectangle {
var rect:Rectangle = new Rectangle(_stageWidth, _stageHeight, -_stageWidth, -_stageHeight);
for (var i:int = 0; i < list.length; i++) {
var pverts:Vector.<Number> = _planes[list[i]].projectedVerts;
for (var j:int = 0; j < 4; j++) {
var x:Number = pverts[j * 2];
var y:Number = pverts[j * 2 + 1];
rect.left = x < rect.left ? x : rect.left;
rect.right = x > rect.right ? x : rect.right;
rect.top = y < rect.top ? y : rect.top;
rect.bottom = y > rect.bottom ? y : rect.bottom;
}
}
return rect;
}
}
}
import flash.display.BitmapData;
import flash.display.Stage;
import flash.geom.Matrix3D;
import flash.geom.Vector3D;
class Plane {
public var vectors:Vector.<Vector3D>;
public var transformedVectors:Vector.<Vector3D>;
public var projectedVerts:Vector.<Number>;
public var indices:Vector.<int>;
public var uvtData:Vector.<Number>;
public var centerZ:Number;
public var image:BitmapData;
public function Plane(x:Number, y:Number, z:Number, width:Number, height:Number, rotation:Vector3D, image:BitmapData) {
width *= 0.5, height *= 0.5;
var m:Matrix3D = new Matrix3D();
m.appendRotation(rotation.x, Vector3D.X_AXIS);
m.appendRotation(rotation.y, Vector3D.Y_AXIS);
m.appendRotation(rotation.z, Vector3D.Z_AXIS);
m.appendTranslation(x, y, z);
vectors = new Vector.<Vector3D>(4, true);
transformedVectors = new Vector.<Vector3D>(4, true);
var list:Array = [-1, -1, 1, -1, -1, 1, 1, 1];
for (var i:int = 0; i < 4; i++) {
var v:Vector3D = new Vector3D();
v.x = list[i * 2] * width;
v.y = list[i * 2 + 1] * height;
v.z = 0;
v.w = 1;
transformedVectors[i] = vectors[i] = m.transformVector(v);
}
projectedVerts = new Vector.<Number>(8, true);
indices = Vector.<int>([0, 1, 2, 1, 3, 2]);
indices.fixed = true;
uvtData = Vector.<Number>([0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1]);
uvtData.fixed = true;
this.image = image;
}
}
class Camera3D {
private var _stage:Stage;
private var _target:Vector3D;
private var _up:Vector3D;
private var _fov:Number;
private var _focalLength:Number;
private var _distance:Number;
private var _azimuth:Number = 0;
private var _elevation:Number = 0;
private var _x:Number;
private var _y:Number;
private var _z:Number;
public function Camera3D(stage:Stage) {
_stage = stage;
target = new Vector3D();
up = new Vector3D(0, -1, 0);
fov = 55;
distance = 1000;
}
public function get target():Vector3D {
return _target;
}
public function set target(value:Vector3D):void {
_target = value;
}
public function get up():Vector3D {
return _up;
}
public function set up(value:Vector3D):void {
_up = value;
}
public function get fov():Number {
return _fov;
}
public function set fov(value:Number):void {
_fov = value;
var size:int = Math.min(_stage.stageWidth, _stage.stageHeight);
_focalLength = 0.5 * size / Math.tan(0.5 * _fov * Math.PI / 180);
}
public function get focalLength():Number {
return _focalLength;
}
public function set distance(value:Number):void {
_distance = value;
_rectangular();
}
public function get distance():Number {
return _distance;
}
public function set azimuth(value:Number):void {
_azimuth = value;
_rectangular();
}
public function get azimuth():Number {
return _azimuth;
}
public function set elevation(value:Number):void {
_elevation = value;
_rectangular();
}
public function get elevation():Number {
return _elevation;
}
public function set x(value:Number):void {
_x = value;
_polar();
}
public function get x():Number {
return _x;
}
public function set y(value:Number):void {
_y = value;
_polar();
}
public function get y():Number {
return _y;
}
public function set z(value:Number):void {
_z = value;
_polar();
}
public function get z():Number {
return _z;
}
public function get viewMatrix():Matrix3D {
var eye:Vector3D = new Vector3D(x, y, z);
var zaxis:Vector3D = new Vector3D(_target.x - x, _target.y - y, _target.z - z);
zaxis.normalize();
var xaxis:Vector3D = zaxis.crossProduct(_up);
xaxis.normalize();
var yaxis:Vector3D = zaxis.crossProduct(xaxis);
var trans:Vector3D = new Vector3D(-xaxis.dotProduct(eye), -yaxis.dotProduct(eye), -zaxis.dotProduct(eye));
var v:Vector.<Number> = Vector.<Number>([xaxis.x, yaxis.x, zaxis.x, 0,
xaxis.y, yaxis.y, zaxis.y, 0,
xaxis.z, yaxis.z, zaxis.z, 0,
trans.x, trans.y, trans.z, 1]);
return new Matrix3D(v);
}
private function _rectangular():void {
var az:Number = _azimuth / 180 * Math.PI;
var el:Number = _elevation / 180 * Math.PI;
var adj:Number = _distance * Math.cos(el);
_x = Math.cos(az) * adj;
_y = Math.sin(el) * _distance;
_z = Math.sin(az) * adj;
}
private function _polar():void {
_distance = Vector3D.distance(new Vector3D(_x, _y, _z), _target);
var el:Number = Math.asin(_y / _distance);
var adj:Number = _distance * Math.cos(el);
_azimuth = Math.asin(_z / adj) / Math.PI * 180;
_elevation = el / Math.PI * 180;
}
}