2009年11月29日 星期日

PV3D 練習 - Facebook 好友大頭照

DEMO:(需登入fb)
http://www.asn.com.tw/flex/20091129.htm
20110827更新連結…
http://www.vercenter.nknu.edu.tw/flex/FacebookFriends/LongfellowHelloFlex.html

在 facebook 上登入,讀取好友名單及大頭照,再將大頭照轉成 BitmapMaterial,用這個材質生成 PLANE,再置入 PV3D 場景中。

效果跟效能,並不是絕對的反比關係。




SOURCE:
package study
{
    import game.FacebookAP;

    import com.facebook.data.users.FacebookUser;
   
    import flash.display.Bitmap;
    import flash.display.Loader;
    import flash.display.LoaderInfo;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.filters.BlurFilter;
    import flash.net.URLRequest;
    import flash.system.LoaderContext;
       
    import org.papervision3d.core.effects.view.ReflectionView;
    import org.papervision3d.core.math.Quaternion;
    import org.papervision3d.cameras.CameraType;
    import org.papervision3d.materials.BitmapMaterial;
    import org.papervision3d.objects.DisplayObject3D;
    import org.papervision3d.objects.primitives.Plane;

    public class study21 extends ReflectionView
    {
        private var currentQuat:Quaternion = new Quaternion();
        private var targetQuat:Quaternion = new Quaternion();
        private var slerp:Number = 0;
        private var fbRadius:Number;
        private var carousel:DisplayObject3D = new DisplayObject3D();
        private var isMouseDown:Boolean;
        private var fb:FacebookAP;
        private var save_mouseX:Number;
        private var save_rotationY:Number;
        private static var planeWidth:Number = 50;
        private static var planeHeight:Number = 50;
       
        public function study21()
        {
            super(0,0,true,false,CameraType.FREE);
            this.addEventListener(Event.ADDED_TO_STAGE, init0);
        }
       
        private function init0( event:Event ):void
        {
            this.removeEventListener(Event.ADDED_TO_STAGE, init0);
            fb = new FacebookAP();
            fb.addEventListener( Event.COMPLETE, init1 );
            fb.init();
        }

        private function init1( event:Event ):void
        {
            fb.removeEventListener( Event.COMPLETE, init1 );
           
             opaqueBackground=0;
            surfaceHeight = -5;
            viewport.interactive=true;
            viewportReflection.filters = [new BlurFilter(3,3,3)];
            setReflectionColor(.7, .7, .7);

            fbRadius = ( fb.friends.length * (planeWidth+planeWidth/2) )/(2*Math.PI);

             camera.target = carousel;
            camera.position = carousel.position;
            camera.y = planeHeight/2;
             camera.moveBackward( fbRadius+150 );
           
             for( var i:int=0; i
             {
                  var user:FacebookUser = fb.friends.getItemAt(i) as FacebookUser;
                var lc:LoaderContext = new LoaderContext(true);       
                var loader:Loader = new Loader();
                loader.contentLoaderInfo.addEventListener( Event.COMPLETE, onLoadBitmap );
                if( user.pic_square == "" )
                    loader.load(new URLRequest( "images/q_silhouette.gif" ), lc );
                else
                    loader.load(new URLRequest( user.pic_square ), lc);
            }

            scene.addChild(carousel);
            this.addEventListener(Event.ENTER_FRAME, onENTER_FRAME );
            this.stage.addEventListener( MouseEvent.MOUSE_DOWN, onMOUSE_DOWN );
            this.stage.addEventListener( MouseEvent.MOUSE_UP, onMOUSE_UP );
        }

        private function onLoadBitmap( event:Event ):void
        {
            var bodyMaterial:BitmapMaterial = new BitmapMaterial( Bitmap(LoaderInfo(event.target).content).bitmapData );
            bodyMaterial.interactive=true;
            bodyMaterial.doubleSided=true
           
            var plane:DisplayObject3D = new Plane( bodyMaterial,planeWidth,planeHeight,1,1 );
           
            plane.rotationY = ( 360 / fb.friends.length ) * carousel.numChildren;
            plane.moveForward( fbRadius );
            plane.y = planeHeight/2;
           
            carousel.addChild(plane);
        }      

        private function onMOUSE_DOWN(e:Event):void
        {
            isMouseDown = true;
            save_mouseX = this.stage.mouseX;
            save_rotationY = carousel.rotationY;
        }
       
        private function onMOUSE_UP(e:Event):void
        {
            isMouseDown = false;
        }
       
        private function onENTER_FRAME(e:Event):void
        {
            if( isMouseDown )
            {
                carousel.rotationY = save_rotationY + (180/Math.PI)*Math.atan((save_mouseX-this.stage.mouseX)/(fbRadius+150));
            } else {
                carousel.rotationY -= ( this.stage.mouseX - this.stage.stageWidth/2 )/200;

                slerp += (1 - slerp) * .05;
                var quat:Quaternion = Quaternion.slerp( currentQuat, targetQuat, slerp );
                carousel.transform = quat.matrix;
             }
        
            singleRender();
        }
    }
}

2009年11月27日 星期五

PV3D 練習 - 加入影片

現在可以邊開車邊看電視了 ^ ^"

http://www.asn.com.tw/flex/cardrive/CarDriver4.html
20110827連結更新…
http://www.vercenter.nknu.edu.tw/flex/CarDriver/CarDriver.html

不過所使用的影片檔是伺服端的 flv 檔,若要使用外部的連結(如 youtube),則會有 security domain 的問題,還要再研究。

幾個問題要注意…

一、DAE 物件有可能是很多個小物件組成的,在 3DS 裡會有不同的名稱及材質名稱,若需要讓 DAE 物件能產生事件 ( 譬如這個例子中液晶電視的畫面會接收滑鼠按下的事件來停止、撥放影片 ),必須從讀入的 DAE 物件中抽取出子物件 ( getChildByName ),再把這個子物件加上 InteractiveScene3DEvent (材質亦需事先設定好 interactive = true,才能正確接收到事件。

二、同樣是 DAE 物件,由於物件的建立順序是先建立 DisplayObject3D 再 LOAD 進模型及上材質,因此在 Render 的時候要特別注意,別在還沒 FileLoadEvent.LOAD_COMPLETE 之前就先調用子物件 ( 例如本例中的車承軸及車輪 ),否則會誤用 NULL 物件。 解決的辦法是確認全部 LOAD 完再 startRendering 或者在每次使用前作 NULL check。

三、材質使用貼圖 bitmap 時要特別小心原 bitmap 的檔案大小也會嚴重影響到 render 的效能 ( 例如被我消去的草坪 = =",跟原本很大張的木箱圖 )。


 PV3D 的效能確實不是很理想,但想必隨著 ADOBE 日後支援 3D 硬體 GPU 加速 (希望早點實現 ),PV3D (或其它 WEB 3D 引擎 ) 應該也會更讚才是,在此之前,先把效率最優化的技巧學好吧~ ^ ^


另外順便介紹一個超驚人的 Web 3D 引擎 UNITY,底下是官網 DEMO…
http://unity3d.com/gallery/live-demos/tropical-paradise
需要安裝個小小元件才能看到,不過效果驚人。

2009年11月26日 星期四

PV3D 練習-幾種加入光影材質的方式

開車加入光影材質…
http://www.asn.com.tw/flex/cardrive/CarDriver3.html



幾種加入光影材質的方式…

在PV3D 裡面有所謂的 shadematerial,用來產生會按照光源反應出明暗變化的效果的材質,在使用上很方便,只需產生一個點光源,再利用它來 new 出一個 ShadeMaterial,再賦予物件使用。

最簡單的像是 org.papervision3d.materials.shadematerials 底下的 FlatShadeMaterial …

         private function foo():void
         {
            var light:PointLight3D = new PointLight3D();
            var shaderMaterial:FlatShadeMaterial =  new FlatShadeMaterial( light, 0x123456, 0x000000 );
            var plane:Plane = new Plane(shaderMaterial,500,500);
         }

但有時會需要用貼圖的方式來生成物件的表面紋理,比較像真,這時要透過 shader 來重新製作俱備貼圖的 ShadedMaterial…

[Embed(source="assets/photo.jpg")]
private var bmpAsset:Class;
         private function foo():void
         {
            var light:PointLight3D = new PointLight3D();
             var yourBitmap:Bitmap = new bmpAsset() as Bitmap;
            var bitmapMaterial:BitmapMaterial = new BitmapMaterial(yourBitmap.bitmapData);
            var shader:FlatShader= new FlatShader( light, 0xffffff, 0x333333);
            var shaderMaterial:ShadedMaterial = new ShadedMaterial(bitmapMaterial, shader);
            plane = new Plane(shaderMaterial,500,500);
         }

上面是靜態讀圖的作法,但是若是透過 BitmapFileMaterial 動態即時讀圖檔,卻會失敗…

         private function foo():void
         {
            var fileMaterial:BitmapFileMaterial = new BitmapFileMaterial("assets/FocusBody.jpg");
            /// after FileLoadEvent.LOAD_COMPLETE ....
            var light:PointLight3D = new PointLight3D();
            var shader:FlatShader = new FlatShader(light, 0xFFFFFF, 0x333333);
            var shaderMaterial:ShadedMaterial = new ShadedMaterial(fileMaterial, shader);
            var plane:Plane = new Plane(shaderMaterial,500,500);
       }

因為 BitmapFileMaterial 並不是 BitmapMaterial 的子類別  ~"~,所以要稍微轉一下…


         private function foo():void
         {
            var fileMaterial:BitmapFileMaterial = new BitmapFileMaterial("assets/FocusBody.jpg");
            /// after FileLoadEvent.LOAD_COMPLETE ....
            var light:PointLight3D = new PointLight3D();
            var shader:FlatShader = new FlatShader(light, 0xFFFFFF, 0x333333);
            var bitmapMaterial:BitmapMaterial = new BitmapMaterial( fileMaterial.bitmap );
            var shaderMaterial:ShadedMaterial = new ShadedMaterial(bitmapMaterial, shader);
            var plane:Plane = new Plane(shaderMaterial,500,500);
        }

另外要注意,Cube 六面體若是要使用 ShadedMaterial 的話,六面都要 new 個新 shader 給它,否則會有三角形黑色塊產生 = =",很麻煩,而且跑起來非常秏效能。

org.papervision3d.materials.shader 底下還有很多種不同的 shader 可以使用,效果皆不同…
FlatShader
PhongShader
CellShader
EnvMapShader
GouraudShader

2009年11月24日 星期二

多圖 BitmapFileMaterial 讀取方式

上次提到了 BitmapFileMaterial load 的時候使用一個 array,利用事件傳遞的方式來檢測什麼時候所有的圖片讀取完畢,好進行下一個有前後相依的步驟。

結果,今天 trace 了一下 BitmapFileMaterial 這個類別,發現原來它早就有把類似的機制作在裡面了,在BitmapFileMaterial.as 的這行…

        static public var callback :Function;

於是,要讀取一連串的圖檔就變的更容易了…

        public function init():void
        {
            BitmapFileMaterial.callback = loadComplete;
            floorMaterial = new BitmapFileMaterial("assets/grassTexture.jpg");
            bodyMaterial = new BitmapFileMaterial("assets/FocusBody.jpg");
            wheelMaterial = new BitmapFileMaterial("assets/FocusWheel.jpg");
            boxMaterial = new BitmapFileMaterial("assets/box.jpg");
        }

        private function loadComplete():void
        {
            BitmapFileMaterial.callback = null;
            ///......
        }


雖然如此,但使用上也要特別小心,因為 BitmapFileMaterial.callback 是 public static 的,意即所有其它程序也是有可能在你讀取一堆圖檔的時候,使用同樣的方式設定了 callback 的函式,那就會出錯嘍(潛藏的BUG)。

2009年11月23日 星期一

PV3D 防止破圖的幾種方法

一、使用 QuadrantRenderEngine…

新版的 PV3D 可以使用 QuadrantRenderEngine 代替原來的 BasicRenderEngine

renderer = new QuadrantRenderEngine(QuadrantRenderEngine.ALL_FILTERS);

但是效能很差,可能還需要細部的參數設定。

二、自己手動作 ViewportLayer…

            viewport.containerSprite.sortMode = ViewportLayerSortMode.INDEX_SORT;
            objViewportLayer = new ViewportLayer(viewport, null);
            objViewportLayer.layerIndex = 1;
            viewport.containerSprite.addLayer(objViewportLayer);
            objViewportLayer.addDisplayObject3D(YOUR_OBJECT, true);

同一層的物件依然存在破圖的問題,並且,上層物件絕對會遮蓋住下層物件,所以在分配上要自己作好控制,效能很好。

三、在近距離的情況下,多邊形會被省略不畫而導致的缺面…

            renderer.clipping = new FrustumClipping(FrustumClipping.BOTTOM);

2009年11月21日 星期六

PV3D 練習 - 開車

開車兜兜風…
http://www.asn.com.tw/flex/cardrive/cardrive.html

http://www.asn.com.tw/flex/cardrive/cardrive2.html with BOX2D

程式參考…
http://pv3d.org/2009/01/23/springcamera3d-and-driving-a-car/
重點在 driveCar() 及 updateCar() 兩個函式。

汽車控制幾乎都使用原作的,看別人的程式可以學到很多,沒想到原來不難,缺的大概就是經驗跟創意。

另一個重點是汽車的模型,在建模的時候就要先規畫好,要能配合程式運作,包括物件軸心、面向、四個輪子的軸心跟方向、子物件的名稱…。

圖檔及資源的讀取部份改寫如下…

使用一個陣列 push 所有需要讀取資源的 loader,並加入事件監聽,在收到完成的事件後再移出陣例,最後判斷是否己全數讀取完成,再進行其它的初始化工作。

        private var loadAssets:Array = new Array();
        private function initAssets():void
        {
            removeEventListener(Event.ADDED_TO_STAGE, initAssets);

            floorMaterial = new BitmapFileMaterial("assets/grassTexture.jpg");
            floorMaterial.addEventListener(FileLoadEvent.LOAD_COMPLETE,onLoadAssets);
            loadAssets.push(floorMaterial);

            bodyMaterial = new BitmapFileMaterial("assets/FocusBody.jpg");
            bodyMaterial.addEventListener(FileLoadEvent.LOAD_COMPLETE,onLoadAssets);
            loadAssets.push(bodyMaterial);

            wheelMaterial = new BitmapFileMaterial("assets/FocusWheel.jpg");
            wheelMaterial.addEventListener(FileLoadEvent.LOAD_COMPLETE,onLoadAssets);
            loadAssets.push(wheelMaterial);
        }
       
        private function onLoadAssets(e:FileLoadEvent):void
        {
            var idx:Number = loadAssets.indexOf(e.target);
            if( idx != -1 )
            {
                if( e.target is BitmapFileMaterial )
                    (e.target as BitmapFileMaterial).removeEventListener(FileLoadEvent.LOAD_COMPLETE,onLoadAssets);
                loadAssets.splice( idx, 1 );
                if( loadAssets.length == 0 )
                    initOther();
            }
        }

2009年11月20日 星期五

PV3D DAEMC2 練習

DAEMC2 是一個讓 PV3D 可以讀取 dae 動作模型的組件,藉由它,可以讓你在 PV3D 中控制撥放預先制作好的動作。

http://www.asn.com.tw/flex/BoxMan/BoxMan.html

程式參考 這裡,但改成了 DAEMC2 版 frame 的新的操作方式。

DAEMC2 教學可以參考 影片

2009年11月11日 星期三

Paper Vision 3D 正確 destory 物件的方法

由於 AS3 有 garbage collection 的機制,這樣的機制有好有壞,但千萬別以為有 garbage collection 就不用去在意 memory 的議題,因為不管 garbage collection 運作的原理是什麼,它總還是有一套運作的規則,不去依循這個規則的話,仍然會造成 memory leaks。

garbage collection 主要的扮演角色的是在程式背後的「管理程式」,記錄、管理使用者(這裡指程式設計師) 記憶體的使用狀況,它底層的意義就是在記錄某段記憶體是不是有物件(指標、參考…)指向它,若沒的話,便能進行釋放。

可以想像的到,一個環狀的參考一定會造成 garbage collection 的困擾,因此,在具備 garbage collection 的系統下寫程式,有些地方便要特別注意,才不會造成問題。

以前在 C++,常常會使用指標將自己 allocate 到的 memory 記錄下來,以便在物件 destroy 時一併釋放,但在 garbage collection 系統,這樣的習慣卻會造成「過多的」或「意外的」參考到了沒用到的記憶體,反而造成 garbage collection 無法正確運作。

因此在寫 AS3 時,儘量在要用到時才去 new 一個物件,而不需要在初始化時就把一切 new 出來放在私有變數裡去作準備,當然,這也不是通則,期待更好的 garbage collection 系統出現。

回歸正題,Paper Vision 3D 物件的創建跟刪除便出現了類似上述的問題,當你用「動態」的產生物件 (譬如 sphere 或 cube) 時,由於物件需要參考到 Material 材質,而材質又會記錄著使用這個 Material 的物件 list,於是當你刪除物件( 從 scene 移除並設為 null ) 卻發現 garbage collection 起不了作用,memory 依然被佔用住,這樣的 memory leaks 造成的後果會非常嚴重,memory 用量會不斷攀升,最後效能直線下降。

在 Paper Vision 3D 團隊還沒修正這段(bug?)或 改用更好的作法之前…
解決方法:

        private function destory3DObject():void
        {
            var objArray:Array = new Array();
            var obj:DisplayObject3D;
            for each ( obj in this.scene.objects )
                objArray.push(obj);
           
            for ( obj=objArray.pop(); obj; obj=objArray.pop() )
            {
                var mmArray:Array = new Array();
                var mm:MaterialObject3D;
                for each ( mm in obj.materials )
                    mmArray.push(mm);

                for ( mm=mmArray.pop(); mm; mm=mmArray.pop() )
                {
                    mm.interactive = false;
                    mm.unregisterObject(obj);
                    obj.materials.removeMaterial(mm);
                    mm.destroy();
                }
                obj.materials = null;
                //obj.material.animated = false;
                obj.material.interactive = false;
                obj.material.unregisterObject(obj);               
                this.scene.removeChild( obj );
                obj.material.destroy()
                obj.material = null;
            }
        }

 每個步驟的先後順序很重要,次序相反有可能還是沒清楚跟 garbage collection 系統交待好你不要這段 memory 了,你會發現,最後參考到這段 memory 的會是區域變數 var obj,如此才能保證在離開 scope 後沒有任何參考指標與該物件有關聯。

這個函式是一次清除所有的  DisplayObject3D物件,若只想清除物定物件,那可以使用 array 來裝你的 DisplayObject3D 物件即可,程式雷同。

所以其實 garbage collection 好壞見仁見智吧,好像只不過換了方式去管理你創出來的物件/記憶體罷了,恐怕還是需要瞭解黑箱下的作業方式,才能真正去掌握你所有的資源。

2009年11月10日 星期二

BOX2D + PV3D 物理運動模擬-線上測試盒

網址 :http://www.asn.com.tw/flex/box2dpv3d_world.htm

使用方法,在 creat 頁,填上整個 world 的 XML 描述檔,再按 creat 即可建立一個新的物理模擬世界,按 sample world 有範例可以參考。

說明:
這個程式是依據 boristhebrave Box2DWith 的 Box2D XML loader
線上建立 Box2D 物件,同時轉換成 PV3D 物件對應( 目前支援 CUBE、SPHERE )
支援 TAG 如下…(細節請參考 boristhebrave 的 b2XML.as 文件說明)

world
body
circle
box
distance
prismatic
revolute
gear


<body userData="PV3D" position="200 100" id="head">
<circle radius="15" density="1" friction="0.4" restitution="0.3">
</body>

即是在XY座標(200,100)的位置建立一個半徑15的圓,其密度=1,摩擦力=0.4,反彈=0.3
其中 userData="PV3D" 代表要同時建立一個 PV3D 的對應3D物件-球。

更新…
2009/11/13-PV3D物件增加z軸初始及物件厚度的解析。(BOX適用)
語法:userData="PV3D,Z,DEPTH"
範例:userData="PV3D,50,20" - 在z軸50的位置上建立厚度20的PV3D物件(CUBE)
*實際碰撞處理仍為BOX2D引擎。

更高效處理 micro second 的方式

更高效處理 micro second 的方式…  以 STM32 為例… __IO unsigned long sys_tick = 0; void SysTick_Handler(void) {     HAL_IncTick();     sys_tick += (SysTi...