從早期用 2D 的方式去模擬,
到後來用 pv3d or away3d 很多 3D framework 都可以辦到。
本篇文章用 flash 內建的 3D 功能來實做,
大概說明一下,
flash 3D 座標系,Z軸愈往螢幕愈大。
弧度,圓一圈為 Math.PI * 2
anglePer = (Math.PI * 2) / NUM_ITEMS;
從弧度推算 x/z,將物件依序排列
angle = i * anglePer;
mc.x = Math.cos(angle) * RADIU_X;
mc.z = Math.sin(angle) * RADIU_Z;
設定排在最前面的物件 z=0
container.z = RADIU_Z;
沿 Y 軸旋轉 conainer
container.rotationY += SPEED;
物件也要跟著反轉(這樣物件才能面向使用者)
arrItems[i].rotationY -= SPEED;
因為 flash 3D 不支援 ZSort,只好自己處理
SimpleZSort3D.simpleZSort3DChildren(container, false);
透視變形,取得 root 顯示物件的透視投影設定以及變更 perspectiveProjection 屬性的
視野和投影中心屬性
projection = root.transform.perspectiveProjection
預設為 stage 中心
projection.projectionCenter = new Point(stage.stageWidth >> 1, stage.stageHeight >> 1 - 100);
因為我很懶,
接下來改用 TweenLite 幫忙處理轉動的部份
先計算轉動要花的時間
var dif:Number = clickid - currid;
if (dif != dif % (NUM_ITEMS >> 1)) {
dif = (dif < 0) ? dif + NUM_ITEMS : dif - NUM_ITEMS;
}
dif = (dif < 0 ? -dif : dif);
var time:Number = dif * SPEED;
計算旋轉角度
rotY = degPer * (clickid - frontid);
接下來就交給 TweenLite 處理
TweenMax.to(container, time, { shortRotation: { rotationY: rotY }} );
TweenMax.to(arrItems[i], time, { shortRotation:{rotationY: -rotY }} );
最後附上結果圖,
完整 code 如下。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package | |
{ | |
import flash.display.Sprite; | |
import flash.events.MouseEvent; | |
import iqcat.utility.TextUtils; | |
/** | |
* draw circle | |
* @author jacky | |
*/ | |
public class Item extends Sprite | |
{ | |
public var onClick:Function = null; | |
private var _id:uint; | |
public function Item(__id:uint, __r:Number = 50.0) | |
{ | |
_id = __id; | |
graphics.beginFill(0xFFFFFF * Math.random()); | |
graphics.drawCircle(0, 0, __r); | |
graphics.endFill(); | |
addChild(TextUtils.textToBitmap(String(_id))); | |
cacheAsBitmap = true; | |
addEventListener(MouseEvent.CLICK, handleClick); | |
} | |
private function handleClick(e:MouseEvent):void | |
{ | |
trace("id:" + _id); | |
if (onClick != null) { | |
onClick(_id, e); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package | |
{ | |
import com.greensock.TweenMax; | |
import flash.display.Sprite; | |
import flash.events.Event; | |
import flash.events.MouseEvent; | |
import flash.geom.PerspectiveProjection; | |
import flash.geom.Point; | |
/** | |
* carousel effect | |
* @author jacky | |
*/ | |
public class Main extends Sprite | |
{ | |
// 四的倍數 | |
static public const NUM_ITEMS:int = 12; | |
public const RADIU_X:Number = 400; | |
public const RADIU_Z:Number = 400; | |
public const SPEED:Number = .5; | |
public const ACTIVE_BLUR:Boolean = true; | |
private var projection:PerspectiveProjection; | |
private var anglePer:Number; | |
private var degPer:Number; | |
private var container:Sprite; | |
private var arrItems:Vector.<Item> = new Vector.<Item>(); | |
private var isMoving:Boolean; | |
private var rotY:Number; | |
private var clickid:int; | |
private var frontid:int; | |
private var currid:int; | |
public function Main():void | |
{ | |
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 | |
isMoving = false; | |
// 最前面的物件的 id | |
currid = frontid = NUM_ITEMS - NUM_ITEMS / 4; | |
// 透視變形,取得 root 顯示物件的透視投影設定以及變更 perspectiveProjection 屬性的視野和投影中心屬性 | |
projection = root.transform.perspectiveProjection | |
// 預設為 stage 中心 | |
projection.projectionCenter = new Point(stage.stageWidth >> 1, stage.stageHeight >> 1 - 100); | |
container = new Sprite(); | |
container.x = stage.stageWidth >> 1; | |
container.y = stage.stageHeight >> 1; | |
// 設定排在最前面的物件 z=0 | |
container.z = RADIU_Z; | |
addChild(container); | |
anglePer = (Math.PI * 2) / NUM_ITEMS; | |
// 弧度,圓一圈為 Math.PI * 2 | |
degPer = 360 / NUM_ITEMS; | |
var mc:Item; | |
var angle:Number; | |
for (var i:int = 0; i < NUM_ITEMS; i++) { | |
mc = new Item(i, 50); | |
angle = i * anglePer; | |
mc.x = Math.cos(angle) * RADIU_X; | |
mc.z = Math.sin(angle) * RADIU_Z; | |
//mc.rotationY = ( -360 / NUM_ITEMS) * i - 90; | |
mc.onClick = move; | |
arrItems.push(mc); | |
container.addChild(mc); | |
} | |
updateObj(); | |
//addEventListener(Event.ENTER_FRAME, render); | |
TweenMax.delayedCall(.25, function():void { move(2)} ); | |
} | |
private function move(__id:int = 0, e:MouseEvent = null):void | |
{ | |
if (!isMoving) { | |
isMoving = true; | |
clickid = __id; | |
// 計算轉動要花的時間 | |
var dif:Number = clickid - currid; | |
if (dif != dif % (NUM_ITEMS >> 1)) { | |
dif = (dif < 0) ? dif + NUM_ITEMS : dif - NUM_ITEMS; | |
} | |
dif = (dif < 0 ? -dif : dif); | |
var time:Number = dif * SPEED; | |
currid = __id; | |
rotY = degPer * (clickid - frontid); | |
TweenMax.to(container, time, { shortRotation: { rotationY: rotY }, onUpdate:updateObj, onComplete:onComplete } ); | |
var len:int = arrItems.length; | |
for (var i:int = 0; i < len; i++) { | |
TweenMax.to(arrItems[i], time, { shortRotation:{rotationY: -rotY }} ); | |
} | |
} | |
} | |
private function onComplete():void | |
{ | |
isMoving = false; | |
} | |
private function updateObj():void | |
{ | |
SimpleZSort3D.simpleZSort3DChildren(container, false); | |
if(ACTIVE_BLUR) { | |
var len:int = arrItems.length; | |
var b:Number; | |
for (var i:int = 0; i < len; i++) { | |
b = len - container.getChildIndex(arrItems[i]); | |
// 最前面三個不用blur | |
arrItems[i].filters = (b > 3) ? [new BlurFilter(b >> 1, b >> 1, 3)] : []; | |
} | |
} | |
} | |
private function render(e:Event):void | |
{ | |
SimpleZSort3D.simpleZSort3DChildren(container, false); | |
container.rotationY += SPEED; | |
for (var i:int = 0; i < arrItems.length; i++) { | |
arrItems[i].rotationY -= SPEED; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package | |
{ | |
import flash.display.DisplayObject; | |
import flash.display.DisplayObjectContainer; | |
import flash.geom.Matrix3D; | |
/** | |
* simple zsort | |
* @author jacky | |
*/ | |
public class SimpleZSort3D | |
{ | |
public static function simpleZSort3DChildren( doc:DisplayObjectContainer, recurse:Boolean = true ) : void | |
{ | |
//transforms from local to world oordinate frame | |
doc.z = doc.z; | |
var transform:Matrix3D = doc.transform.getRelativeMatrix3D( doc.stage ); | |
var numChildren:int = doc.numChildren; | |
//v = ( n * 3 )- (x,y,z) set for each child | |
var vLength:int = numChildren * 3; | |
var vLocal:Vector.<Number> = new Vector.<Number>( vLength, true ); | |
var vWorld:Vector.<Number> = new Vector.<Number>( vLength, true ); | |
var vIndex:int = 0; | |
for( var i:int = 0; i <numChildren; i++ ) | |
{ | |
var child:DisplayObject = doc.getChildAt( i ); | |
if (!(child == null && i == 0)) { | |
if( recurse && child is DisplayObjectContainer ) simpleZSort3DChildren( DisplayObjectContainer( child ), true ); | |
vLocal[ vIndex ] = child.x; | |
vLocal[ vIndex + 1 ] = child.y; | |
vLocal[ vIndex + 2 ] = child.z; | |
vIndex += 3; | |
} | |
} | |
transform.transformVectors( vLocal, vWorld ); | |
//bubble sorts children along world z-axis | |
for( i = numChildren - 1; i > 0; i-- ) | |
{ | |
var hasSwapped:Boolean = false; | |
vIndex = 2; | |
for( var j:int = 0; j < i; j++ ) | |
{ | |
//z value at that index for each child | |
var z1:Number = vWorld[ vIndex ]; | |
vIndex += 3; | |
var z2:Number = vWorld[ vIndex ]; | |
if( z2> z1 ) | |
{ | |
//swap | |
doc.swapChildrenAt( j, j + 1 ); | |
vWorld[ vIndex - 3 ] = z2; | |
vWorld[ vIndex ] = z1; | |
//mark as swapped | |
hasSwapped = true; | |
} | |
} | |
if( !hasSwapped ) return; | |
} | |
} | |
} | |
} |
沒有留言:
張貼留言