2007年6月16日
這兩天遇到的一些問題,引起的一些思考,覺得有必要寫下來。
一. 面向?qū)ο蟮腁PI接口設(shè)計(jì),如何做到向后兼容。一個(gè)軟件存在多個(gè)模塊,如果提供基礎(chǔ)API的模塊變化了,那么依賴于它的應(yīng)用模塊都必須重新編譯和部署。這就對(duì)基礎(chǔ)API模塊的向后兼容性提出了要求。完全通用的方法是不存在的,任何方法都需要根據(jù)實(shí)際情況調(diào)整,這里僅僅提供一些比較通用但是也不明確的方法:
方法一:擴(kuò)展時(shí)對(duì)象只增加方法和屬性,原有的屬性和方法保留。這對(duì)于c++等基于二進(jìn)制對(duì)象布局的方法在使用時(shí)需要非常小心,否則極易引起內(nèi)存訪問違規(guī)。但是對(duì)于ActionScript基于元數(shù)據(jù)的語言來說,這一方法一般不會(huì)有什么問題。對(duì)于Java的情況不是非常清楚,估計(jì)與actionscript情況差不多。
方法二:增加新的對(duì)象(使用繼承)提供擴(kuò)展功能,原來的對(duì)象保留。不過對(duì)于耦合的類關(guān)系,只增加一個(gè)類往往并不能達(dá)到目的。
這里必須注意的一點(diǎn)是,API提供者與API使用者保持單向依賴的關(guān)系,API不應(yīng)該依賴于具體的應(yīng)用。對(duì)于多模塊的軟件來說架構(gòu)最重要的是兩點(diǎn):
1. 從需求中抽象出API,并且將API的開發(fā)交給素質(zhì)較高的人員,而應(yīng)用之間松散耦合,通過API發(fā)生關(guān)系。
2. API本身空間也要做劃分,將之切割成為正交的空間,這樣API擴(kuò)展時(shí),影響控制在局部。
順便說說向前兼容的問題,這要求新的Client兼容老的API,這在API設(shè)計(jì)中很少碰到,但是在設(shè)計(jì)軟件的文檔存儲(chǔ)格式(Save/Load)時(shí)常常遇到,這要求新的應(yīng)用在開發(fā)時(shí),做判斷,判斷屬性不存在時(shí)應(yīng)該如何處理,也就是提供一個(gè)默認(rèn)值。
對(duì)于其它的Server-Client結(jié)構(gòu),比如WebService的擴(kuò)展,XML擴(kuò)展等等,我想也應(yīng)該有類似的方法。所以我也想去看看一些公開的API接口是如何設(shè)計(jì)和擴(kuò)展的,比如FaceBook,不過說到底還是抽象與空間劃分的問題,而這些并沒有通用的方法,都依賴于具體的需求。
二. 面向接口的設(shè)計(jì)實(shí)際上就是合理的劃分對(duì)象空間。對(duì)于對(duì)象在擴(kuò)展時(shí),我們常常會(huì)發(fā)現(xiàn)并沒有辦法把它劃分成樹狀的類關(guān)系,常常我們發(fā)現(xiàn)從一個(gè)類A派生了兩個(gè)類B和C,但是又存在第三種情況,它的行為包含部分B的行為也包含部分C的行為。實(shí)際上類的空間劃分,和數(shù)據(jù)庫設(shè)計(jì)是一樣的,每一次劃分(繼承)相當(dāng)于以一個(gè)索引劃分對(duì)象空間,但是很多時(shí)候劃分有多個(gè)索引,這時(shí)候要想劃分成單一的一棵樹是不可能的。這時(shí)候就需要進(jìn)一步細(xì)化對(duì)象的空間劃分,并將之劃分為正交的多個(gè)空間。舉個(gè)例子:Window派生出TransparentWindow和OpaqueWindow,這是一種劃分,但是我們又發(fā)現(xiàn)另一種劃分,WindowWithTitleBar與WindowWithoutTitleBar,他們都是對(duì)Window的劃分。這時(shí)候我們應(yīng)該想到的是,Window可以拆分為:ITitleBar, IMainWindow,各種Window可以選擇實(shí)現(xiàn)或者不實(shí)現(xiàn)這些接口。當(dāng)?shù)谌呤褂眠@些對(duì)象時(shí),直接訪問接口即可,因?yàn)槠渌慕涌诳赡苁撬麄儾⒉魂P(guān)心的。當(dāng)存在多種索引時(shí),將對(duì)象拆分為正交的空間,每一個(gè)空間尤其自己的單一索引,這應(yīng)該是對(duì)象劃分的一種通用原則。就像單根繼承一樣,這樣會(huì)使得對(duì)象的劃分結(jié)構(gòu)非常清晰。
對(duì)于某些不是特別復(fù)雜的情況,如果存在多種劃分索引,不妨用單一的類表示全部的對(duì)象,對(duì)某些對(duì)象,某些屬性方法是無效的,這樣實(shí)際上在簡(jiǎn)單情況下是很有效的,因?yàn)檫^度的劃分會(huì)造成復(fù)雜性,使用者可能不知道在哪個(gè)對(duì)象上找到他需要的屬性或方法了,這就好像在一張表上設(shè)計(jì)了全部的屬性,盡管有些屬性是抵觸的,有些屬性是相關(guān)的。隨著不斷的擴(kuò)展,這張表會(huì)越來越大,這就需要開始對(duì)對(duì)象空間做劃分了,所以說到底,還是一個(gè)需求復(fù)雜度的問題,這里最重要的是掌握一個(gè)劃分的度,何時(shí)劃分,劃分到什么程度,決定這些的往往不僅僅是技術(shù)因素,還有商業(yè)上,運(yùn)營(yíng)上,時(shí)間上,成本上的因素,這也是最難判斷的一點(diǎn),需要在實(shí)際中去具體問題具體分析,這里就能體現(xiàn)經(jīng)驗(yàn)的重要性了,其實(shí)在架構(gòu)上方法都是很Genralized的,這些和實(shí)現(xiàn)一個(gè)具體的算法大部相同,所以設(shè)計(jì)模式都在講模式一定有其上下文,也是這個(gè)道理嗎
胡扯了很多,先到這里吧,等有時(shí)間做進(jìn)一步的整理
http://www.aar.cn/wallpaper/Desktop/Natural/2150/F_WQTP_1680x1050_Q.Html
http://opensource.adobe.com/wiki/display/site/Source
Fellow evangelist Duane Nickull has posted the slides from his
Web 2.0 Design Patterns, Models and Analysis presentation
http://www.pranaframework.org/
reference:
Inversion of Control Containers and the Dependency Injection Pattern, by Martin Fowler
Prana是一個(gè)用Actionscript寫的IoC Framework,理念和Spring非常類似,目的是為了盡可能降低類之間的依賴性,通過xml配置文件使得編譯依賴性降低,可以動(dòng)態(tài)裝配。這在Java的世界里是非常有意義的,因?yàn)樗械氖虑槎及l(fā)生在server端,Client端并不需要知道這一切。但是在Flex的世界里,swf是客戶端下載下來運(yùn)行在client,如果要達(dá)到動(dòng)態(tài)裝配的目的,client必須能夠有最新需要?jiǎng)討B(tài)裝配的class的字節(jié)碼,這必然要求swf重新編譯,那么這就失去了Ioc的意義了
轉(zhuǎn)載自:http://www.cnblogs.com/sharplife/archive/2007/09/03/880641.html
最近看時(shí)學(xué)習(xí)Flex應(yīng)用,開始對(duì)Flex和Flash的關(guān)系有些模糊,讀了Oreilly的Programming Flex 2才算是明白些,現(xiàn)記下。
1、Flex應(yīng)用程序的生命周期
Flex應(yīng)用就其根本上講就是Flash應(yīng)用,只不過其是基于Flex Framework(由ActionScript寫就)開發(fā)的。Flex應(yīng)用程序的根對(duì)象的是SystemManager(不是我們?cè)趂lex應(yīng)用上看到的Application根元素),繼承自flash.dispaly.MovieClip—flash player display type,MovieClip是一種支持timeline基本元素幀frame的對(duì)象,在Flex Framework中SystemManager是特殊的,含有兩幀(其他component都是一幀的),分別是preloader和真正的Application,preloader幀可以迅速下載下來并用于顯示應(yīng)用下載進(jìn)度,一旦Flex應(yīng)用的SystemManager實(shí)例進(jìn)入第二幀,將創(chuàng)建Flex主應(yīng)用application實(shí)例并賦予本身的屬性application(在進(jìn)入第二幀之前是null),自此application(flex主應(yīng)用)的內(nèi)部生命周期、事件開始運(yùn)作:
preinitialize:application已經(jīng)實(shí)例化但尚未創(chuàng)建任何child component
initialize:已經(jīng)創(chuàng)建child component但對(duì)其進(jìn)行布局(lay out)
creationComplete:application已經(jīng)完成實(shí)例化并完成所有child component的布局
SystemManager有一個(gè)topLevelSystemManager對(duì)象,指向一個(gè)SystemManager實(shí)例,是所有當(dāng)前在flash player運(yùn)行的任何東西的根(root),如果flex被作為主應(yīng)用加載到flash player則上述屬性將指向其本身(self-refrencing),但當(dāng)flex應(yīng)用是被另一flex應(yīng)用載入的,其自身的SystmenManager的topLevelSystemManager屬性則不是自引用了,而是指向其父應(yīng)用的SystemManager實(shí)例。所有UIComponent的子類都有一個(gè)systemManager屬性指向應(yīng)用的SystemManager實(shí)例,在被SystemManger實(shí)例監(jiān)聽的component的事件發(fā)生冒泡時(shí),其將擁有事件處理鏈上最后的處理權(quán)。
2、Flash palyer和Framwork的區(qū)別
Flash player是Flex應(yīng)用和flash應(yīng)用的運(yùn)行環(huán)境,兩應(yīng)用對(duì)其擁有完全平等的操作權(quán)(通過Flash player提供的API),兩應(yīng)用形成的.swf文件在flash player中是同樣的表現(xiàn),不同的不是應(yīng)用的內(nèi)容而是其各自的創(chuàng)建方式。Flex的Framework在開發(fā)和運(yùn)行之間為應(yīng)用提供了一層抽象,F(xiàn)lex應(yīng)用編譯時(shí)會(huì)將必要的framwork library編譯進(jìn).swf文件(同樣影響應(yīng)用文件的大小等),主要的flash player class當(dāng)然不會(huì)被編譯到.swf中,因?yàn)樗麄円呀?jīng)存在于flash player中了,最終形成與flash應(yīng)用同樣的flash player可以理解的指令。
關(guān)于flash player class和flex framework的區(qū)分很方便,前者的class以flash開頭,如flash.net.URLLoader,而后者則以mx開頭,如mx.controls.Button
3、動(dòng)態(tài)載入另外的flex應(yīng)用
<mx:SWFLoader source=”src/*.swf”/>
Swfloader的content屬性指向被載入的flex應(yīng)用的SystemManager實(shí)例(其application屬性指向被載入felx應(yīng)用的Application實(shí)例),swfloader加載、初始化被載入flex應(yīng)用時(shí)會(huì)dispatch出init事件,可與其中監(jiān)聽被載入flex應(yīng)用的SystemManager實(shí)例的ApplicationComplete事件,事件發(fā)生時(shí)被載入content的Application對(duì)象方可以引用
與inithandler中event.target.content.addEventListener(FlexEvent.APPLICATION_COMPLETE,func);
與applicationCompleteHandler中event.target.application.method…
4、理解應(yīng)用程序域(application domain)
一個(gè)應(yīng)用程序domain(類似于.net的appdoamin)中有flex應(yīng)用的相關(guān)類定義、資源等,被載入的新flex應(yīng)用可以存在于一個(gè)全新的、隔離的domain中(占額外的內(nèi)存資源)、可以存在于當(dāng)前domain的子doamin中(共享父domain的資源、類定義,須注意類定義被取代的情況)、也可以直接存在與當(dāng)前doamin中(同樣須注意類定義沖突),如runtime shared library。
代碼中實(shí)現(xiàn)這三種方式的應(yīng)用(主要應(yīng)用到flash.system.LoaderContext、flash.display.Loader或flash.net.URLLoader、flash.system.ApplicationDomain)
var context:LoaderContext = new LoaderContext( );
context.applicationDomain = new ApplicationDomain(ApplicationDomain.currentDomain);//載入作為子domain
context.applicationDomain = new ApplicationDomain();//載入作為全新domain
context.applicationDomain = ApplicationDomain.currentDomain;//載入當(dāng)前domain
var request:URLRequest = new URLRequest("RuntimeLoadingExample.swf");
var loader:Loader = new Loader( );
loader.load(request, context);
5、關(guān)于preloader
Preloader是一個(gè)輕量級(jí)的類,在systemManager的第一幀被實(shí)例化,preloader會(huì)dispatch出一系列的事件,由progress bar監(jiān)聽實(shí)現(xiàn)loading界面,一旦應(yīng)用進(jìn)入第二幀待application初始化后會(huì)借由system manager通知preloader初始化進(jìn)度,preloader通知system manager其準(zhǔn)備待刪除
Preloader的事件dispatch:
progress
Indicates download progress
complete
Indicates that the download is complete
rslError
Indicates that a runtime shared library could not load
rslProgress
Indicates the download progress for a runtime shared library
rslComplete
Indicates that the download is complete for runtime shared libraries
initProgress
Indicates that the application is initializing
initComplete
Indicates that the application has initialized
如此,preloader可以定制化了。
Over,暈倒!~
有時(shí)會(huì)出現(xiàn)IDE中調(diào)試時(shí)出錯(cuò),但是在外部直接運(yùn)行程序不出錯(cuò)的情況,或者反之,出現(xiàn)這種情況的原因一般都是“當(dāng)前路徑”引起的,也就是CurrentPath不一樣,這可能導(dǎo)致dll加載搜索路徑不一樣,以及其它一些路徑引起的問題。
Flex中的Event傳遞主要有三個(gè)階段:capturing, targeting, bubbling。比如一個(gè)Button收到了一個(gè)消息,首先會(huì)從其根父UI Object上開始逐步Capture直到其父Object,然后由該Button履行Target階段,最后再以Capture相反的方向Bubble。當(dāng)然這些階段都是相對(duì)DisplayObject來說的,對(duì)于其他的Object比如Socket,Event只會(huì)交給Target對(duì)象處理。
先來看Capture階段:
這個(gè)階段是從父到子的一個(gè)過程,典型應(yīng)用:myPanel.addEventListener(MouseEvent.MOUSE_DOWN, clickHandler, true);
注意第三個(gè)參數(shù)useCapture被設(shè)為true,表示clickHandler只想處理Capture過程的事件,如果還想處理bubble階段的事件,那么必須再以u(píng)seCapture=false調(diào)用一次addEventListener
Flex Develop Guide中有一句話:The capturing phase is very rarely used, and it can also be computationally intensive. By contrast, bubbling is much more common.我還不是特別理解,先寫下來再說吧。
再看Target階段:
這個(gè)很簡(jiǎn)單,由DispatchEvent的對(duì)象直接處理。
然后是Bubble階段。Bubble階段只有bubbles屬性為true的Event才會(huì)有這個(gè)過程,包括change
, click
, doubleClick
, keyDown
, keyUp
, mouseDown
, and mouseUp等事件。對(duì)于自定義事件,bubbles能否設(shè)成true還未知,因?yàn)樗坪跏侵蛔x的,還有待驗(yàn)證。
本來打算今天下午征人去打網(wǎng)球的,邊等人應(yīng)征邊打?qū)崨r,結(jié)果人沒征到,實(shí)況卻有所進(jìn)步了,hiahia!
最近,每天下班后實(shí)況幾乎成為俺的必修課了。可是,不知道是因?yàn)槭裁矗趺淳褪峭娌贿^電腦呢,搞得每次都很煩躁。偶爾也有狀態(tài)好打得不錯(cuò)的時(shí)候,只是每次都曇花一現(xiàn),過了兩天又被電腦郁悶。防守到是好說,只要不是沖得太猛,保持好陣形,一般不會(huì)出什么大錯(cuò)。但是進(jìn)攻就很頭疼,屢屢打不開局面,打世界杯往往是三場(chǎng)小組賽一球不進(jìn),一球未失,最后積三分飲恨出局,那個(gè)郁悶就別提了。今天不知不覺進(jìn)攻居然打開了局面,而且突然有所感悟。最高難度,世界杯,小組賽三場(chǎng)比賽,1:0,2:0,3:0芝麻開花節(jié)節(jié)高。1/8決賽2:0,1/4決賽1:0順利進(jìn)入半決賽,只可惜遇到了法國(guó)這個(gè)變態(tài)隊(duì),還有特雷澤蓋這個(gè)變態(tài)球員。上半場(chǎng)完全被法國(guó)壓著打,下半場(chǎng)好不容易有所起色,有了一腳極具威脅的射門,以及后續(xù)的連續(xù)進(jìn)攻,卻被特雷澤蓋這個(gè)變態(tài)反擊中30米開外,背對(duì)球門的一腳半轉(zhuǎn)身軟綿綿的地滾球洞穿球門,而這個(gè)時(shí)候法國(guó)隊(duì)前場(chǎng)是2打5的局面。雖然被電腦賴了一回,但我并不生氣,因?yàn)榭偹阌行┻M(jìn)攻的感覺了,在這個(gè)感覺消失之前,一定要寫下來,并且溫故而知新。
說說進(jìn)攻吧:
1. 千萬不能急,除非對(duì)方拚搶非常兇狠的情況下,不要隨便快速出球,一定要等拿穩(wěn)了球,傳球方向確定之后再出球。
2. 當(dāng)對(duì)方逼搶上來的時(shí)候,還有一定距離的時(shí)候,可以按對(duì)方?jīng)_刺的方向相反的方向帶球,過了這個(gè)逼搶的人之后,再輕松出球
3. 當(dāng)出現(xiàn)同等機(jī)會(huì)的球的時(shí)候,要提前預(yù)測(cè)拚搶沖撞之后球的方向,在合適的時(shí)機(jī)采取行動(dòng),而不要從一開始就一直按住鏟球或者搶球鍵不放。
4. 要注意發(fā)現(xiàn)敵人的空檔,尤其是吸引了對(duì)方上來拚搶之后,拼搶得人原來所在的位置是一定有空檔的。這時(shí)候通過合理的方式將球轉(zhuǎn)移到那個(gè)方向,一定會(huì)有所收獲的。
5. 前面沒有好機(jī)會(huì)的時(shí)候,可以回傳,然后慢慢組織進(jìn)攻,拉開對(duì)方防守陣線,制造和尋找空檔。
6. 當(dāng)機(jī)會(huì)出現(xiàn)的時(shí)候不要猶豫,堅(jiān)決轉(zhuǎn)身,堅(jiān)決加速,掄開了大腳就射吧。尤其是球到了前鋒腳下的時(shí)候。
7. 當(dāng)出現(xiàn)賴皮點(diǎn)球的時(shí)候,打中路低平球,往往有奇效,否則,你就等著吧,不是偏了,就是中柱。
至于防守:
1. 保持陣形最重要
2. 即時(shí)發(fā)現(xiàn)空檔,而且是沖著空檔去補(bǔ)位,不是沖著對(duì)方球員去補(bǔ)位
3. 角球要小心謹(jǐn)慎
4. 出現(xiàn)緊急情況時(shí),該放倒就放倒,紅牌也再所不惜
5. 當(dāng)進(jìn)入關(guān)鍵比賽之后,隨時(shí)保持緊張,不能有絲毫松懈,今天就是沒有緊逼特雷澤蓋導(dǎo)致球門失守的,其實(shí)當(dāng)時(shí)他旁邊至少有兩個(gè)防守隊(duì)員,當(dāng)時(shí)就該放倒這個(gè)變態(tài)的。
目前來講,打?qū)崨r還是沒有戰(zhàn)術(shù)上的東西,以后一定要在這方面培養(yǎng)一下,否則就永遠(yuǎn)不可能有真的進(jìn)步了,呵呵。這可能與我性格優(yōu)柔寡斷,總是拿不定主意有關(guān)系吧。一定要克服這個(gè)毛病,哼哼!
待續(xù)。。。
osg存在兩棵樹,場(chǎng)景樹和渲染樹。場(chǎng)景樹是一顆Node組成的樹,這些Node可能是矩陣變換,或者是狀態(tài)切換,或者是真正的可繪制對(duì)象,它既反映了場(chǎng)景的空間結(jié)構(gòu),也反映了對(duì)象的狀態(tài)。而渲染樹則是一顆以StateSet和RenderLeaf為節(jié)點(diǎn)的樹,它可以做到StateSet相同的RenderLeaf同時(shí)渲染從而不用切換Opengl狀態(tài),并且做到盡量少的在多個(gè)不同State間切換。渲染樹在CullVisitor的cull過程中逐漸創(chuàng)建。
SceneView包含兩個(gè)與渲染相關(guān)的兩個(gè)成員,一個(gè)RenderStage對(duì)象與StateGraph對(duì)象
StateGraph顧名思義,就是以狀態(tài)為節(jié)點(diǎn)的圖。StateGraph包含了真正的可渲染對(duì)象RenderLeaf,但是一個(gè)StateGraph是不夠的,因?yàn)椴煌腞enderLeaf可能會(huì)有不同的StateSet,于是StateGraph內(nèi)部包含一個(gè)以StateSet為key,StateGraph為value的Map對(duì)象,從而形成一顆渲染樹
渲染時(shí)以該渲染樹為基準(zhǔn)按一定順序逐漸渲染各個(gè)RenderLeaf。以何種方式遍歷該樹呢,這正是RenderStage的任務(wù)。
RenderStage從RenderBin派生
RenderBin包含了一個(gè)StateGraphList,該List將渲染樹中的各個(gè)StateGraph摘取出來,形成列表。形成列表的過程就是遍歷渲染樹的過程。RenderStage可以在RenderBin渲染之前之后做一些預(yù)處理和后處理,以完成一些特殊效果。
RenderStage包含兩種類型的RenderBin,透明與不透明的。對(duì)于Transparent RenderBin比較難處理,就是必須按深度順序調(diào)用gl函數(shù)渲染對(duì)象,否則可能半透明會(huì)有問題。對(duì)于Opaque RenderBin則沒有此限制,它只需按照盡量少切換狀態(tài)的原則排列StateGraph即可。
StateSet的SetRenderingHint函數(shù)可以用來控制使用那個(gè)RenderBin進(jìn)行渲染,題外話,StateSet的setAttributeAndModes函數(shù)可以指定AlphaFunc與BlendFunc,前者功能相當(dāng)于Alpha測(cè)試,后者則反映了Alpha混合的方式。使用方式類似下面:
BlendFunc* func = new BlendFunc();
func->setFunction(...);
dstate->setAttributeAndModes(func, StateAttribute::ON);
可以參考的相關(guān)osg代碼:
void CullVisitor::apply(Geode& node)
void CullVisitor::addDrawableAndDepth(osg::Drawable* drawable,osg::RefMatrix* matrix,float depth)
StateGraph的部分函數(shù)。。。
void RenderLeaf::render(State& state,RenderLeaf* previous)
void RenderBin::drawImplementation(osg::State& state,RenderLeaf*& previous)
void RenderStage::drawImplementation(osg::State& state,RenderLeaf*& previous)
這兩天終于閑了下來有時(shí)間寫點(diǎn)東西了,只記得想寫相機(jī)已經(jīng)是很久遠(yuǎn)的事情了,開發(fā)中涉及到相機(jī)相關(guān)的內(nèi)容也已經(jīng)是兩個(gè)月之前了。
在3D的世界里相機(jī)與矩陣是密不可分的,首先在投影之前,有模型矩陣和視圖矩陣,這兩者并沒有本質(zhì)上的區(qū)別,一個(gè)是站在模型的角度,另一個(gè)就是站在觀察者的角度了。模型的左移相當(dāng)于相機(jī)右移,有鑒于此,OPENGL中并不區(qū)分Model Matrix 和 View Matrix,而是將兩者統(tǒng)稱為ModelView Matrix.
以gluLookAt函數(shù)為例,該函數(shù)根據(jù)眼睛的位置,場(chǎng)景中心的位置,以及一個(gè)從觀察者視角向上的向量定一個(gè)視圖轉(zhuǎn)換,實(shí)際上做的還是應(yīng)用一個(gè)ModelView Matrix。原點(diǎn)位置和眼睛位置確定了z方向向量,向上的向量確定了y方向向量,兩者正交,叉積就是z方向向量了,這樣就可以確定一個(gè)視圖矩陣了。
相機(jī)不僅僅與ModelView Matrix有關(guān),而且也與投影矩陣有關(guān)系。有了相機(jī),再結(jié)合ViewPort大小,F(xiàn)OVy(Y方向Field Of View)或者Aspect Ratio,近裁減面,遠(yuǎn)裁減面就可以確定透視投影矩陣了。
一個(gè)4*4的矩陣如何與模型/視圖變換聯(lián)系起來呢?看這個(gè)圖,前三個(gè)列向量分別代表新坐標(biāo)系的x,y,z軸方向,而最后一個(gè)向量則代表平移量(新坐標(biāo)原點(diǎn)),而矩陣的(4,4)元素則是一個(gè)放大因子,他同時(shí)將所有點(diǎn)之間的距離放大。如果我們把一個(gè)四維向量與之相乘,就可以得到新的坐標(biāo)了。
什么是萬向節(jié)鎖(Gimbal Lock)呢?這是采用歐拉角的方式表示相機(jī)時(shí)出現(xiàn)的問題。這個(gè)問題源于繞軸旋轉(zhuǎn)時(shí)自由度的丟失。因?yàn)樾D(zhuǎn)到軸向時(shí)將無法確定是從哪個(gè)方向旋轉(zhuǎn)過來的。這就有點(diǎn)像是北極與南極點(diǎn)的經(jīng)度無法確定一個(gè)道理。而且在這個(gè)地方,可能出現(xiàn)角度的不連續(xù)變化。即直接從0度跳轉(zhuǎn)到180度。在相機(jī)方向平行于X軸向時(shí),繞X軸的旋轉(zhuǎn)不會(huì)有任何效果,也就是說,從數(shù)學(xué)上來講此時(shí)的ModelView Matrix始終是不變的。在計(jì)算時(shí),由于角度變化不連續(xù),所以計(jì)算的結(jié)果是很不穩(wěn)定的。例如漫游旋轉(zhuǎn)時(shí),簡(jiǎn)單的增加角度,可能在某些臨界值上出現(xiàn)錯(cuò)誤的情況,典型的就是繞某一個(gè)軸的來回震蕩,這也就是所謂Lock的意義了吧。
這兩天一直在研究一個(gè)Crash問題,其表現(xiàn)非常明顯就是Memory Heap被破壞了,但是由于破壞堆的現(xiàn)場(chǎng)無法準(zhǔn)確定位,發(fā)生Crash的地方已經(jīng)不是現(xiàn)場(chǎng),所以一直都沒找到原因。最后只好將代碼Roll Back回去,一個(gè)一個(gè)模塊的試,最終發(fā)現(xiàn)問題出現(xiàn)在某一個(gè)模塊中指針類型的強(qiáng)制轉(zhuǎn)換引起的虛函數(shù)調(diào)用錯(cuò)誤上。
錯(cuò)誤是這樣的,有一個(gè)指針是A類型的,被強(qiáng)制轉(zhuǎn)換為B類型,并且通過B類型調(diào)用B的虛函數(shù),但是實(shí)際上調(diào)用的虛函數(shù)地址在A的虛函數(shù)表中。由于兩者參數(shù)并不相同,所以導(dǎo)致錯(cuò)誤出現(xiàn)。
B類型的函數(shù)參數(shù)中有一個(gè)std::vector類型,由于c++遵循cdecl調(diào)用約定,所以是由被調(diào)用端負(fù)責(zé)清理堆棧,這時(shí)候就會(huì)調(diào)用std::vector的析構(gòu)函數(shù),而實(shí)際上該參數(shù)已經(jīng)在調(diào)用A的虛函數(shù)時(shí)被破壞了,在執(zhí)行完這個(gè)函數(shù)之后,棧是正確的,但是堆已經(jīng)被std::vector的析構(gòu)函數(shù)破壞,所以出現(xiàn)了heap Corruption的錯(cuò)誤。
Heap Corruption是C++開發(fā)中非常棘手的一個(gè)問題,其引起的Crash有兩點(diǎn)非常難以琢磨:
1. 在Debug版較難或者不出現(xiàn),在Release版常常出現(xiàn)
2. 在Release版本上也是在非現(xiàn)場(chǎng)出現(xiàn),而且往往在大量釋放內(nèi)存的地方出現(xiàn)。
相信應(yīng)該有比較好C++的Heap Corruption工具,BoundChecker曾經(jīng)用過,可惜太復(fù)雜不會(huì)用,不知道有沒有非常有效的檢測(cè)Heap Corruption工具。
最近好懶啊,不愿意打字。突然想起以前的一個(gè)做的一個(gè)職業(yè)心理分析,挺準(zhǔn)的,呵呵,把它貼出來,豐富一下俺的Blog吧,很神奇的是,最后列出的職業(yè)中前三項(xiàng)分別是我以前做的,現(xiàn)在做的和將來的目標(biāo)。
Psytopic分析:您的性格類型是“INTP”(內(nèi)向+直覺+思維+知覺)
對(duì)任何感興趣的事物,都要探索一個(gè)合理的解釋。喜歡理論和抽象的事情,喜歡理念思維多于社交活動(dòng)。沉靜,滿足,有彈性,適應(yīng)力強(qiáng)。在他們感興趣的范疇內(nèi),有非凡的能力去專注而深入地解決問題。有懷疑精神,有 時(shí)喜歡批判,常常善于分析。
INTP型的人是解決理性問題者。他們很有才智和條理性,以及創(chuàng)造才華的突出表現(xiàn)。INTP型的人外表平靜、緘默、超然,內(nèi)心卻專心致志于分析問題。他們苛求精細(xì)、慣于懷疑。他們努力尋找和利用原則以理解許多想法。 他們喜歡有條理和有目的的交談,而且可能會(huì)僅僅為了高興,爭(zhēng)論一些無益而瑣細(xì)的問題。只有有條理的推理才會(huì)使他們信服。通常INTP型的人是足智多謀、有獨(dú)立見解的思考者。他們重視才智,對(duì)于個(gè)人能力有強(qiáng)烈的欲 望,有能力也很感興趣向他人挑戰(zhàn)。 INTP型的人最主要的興趣在于理解明顯的事物之外的可能性。他們樂于為了改進(jìn)事物的目前狀況或解決難題而進(jìn)行思考。他們的思考方式極端復(fù)雜,而且他們能很好地組織概念和想法。 偶爾,他們的想法非常復(fù)雜,以致于很難向別人表達(dá)和被他人理解。 INTP型的人十分獨(dú)立,喜歡冒險(xiǎn)和富有想象力的活動(dòng)。他們靈活易變、思維開闊,更感興趣的是發(fā)現(xiàn)有創(chuàng)見而且合理的解決方法,而不是僅僅看到成為事 實(shí)的解決方式。
您適合的領(lǐng)域有:計(jì)算機(jī)技術(shù) 理論研究、學(xué)術(shù)領(lǐng)域 專業(yè)領(lǐng)域 創(chuàng)造性領(lǐng)域等
您適合的職業(yè)有:
· 電腦軟件設(shè)計(jì)師
· 系統(tǒng)分析人員
· 計(jì)算機(jī)程序員
· 研究開發(fā)專業(yè)人員
· 數(shù)據(jù)庫管理
· 故障排除專家
· 戰(zhàn)略規(guī)劃師
· 金融規(guī)劃師
· 信息服務(wù)開發(fā)商
· 變革管理顧問
· 企業(yè)金融律師
· 大學(xué)教授
· 科研機(jī)構(gòu)研究人員
· 數(shù)學(xué)家
· 物理學(xué)家
· 經(jīng)濟(jì)學(xué)家
· 考古學(xué)家
· 歷史學(xué)家
· 證券分析師
· 金融投資顧問
· 律師
· 法律顧問
· 財(cái)務(wù)專家
· 偵探
· 各類發(fā)明家
· 作家
· 設(shè)計(jì)師
· 音樂家
· 藝術(shù)家
· 藝術(shù)鑒賞
本來上個(gè)周末就應(yīng)該寫Camera相關(guān)的內(nèi)容的,結(jié)果拖到今天還沒寫,現(xiàn)在有想寫一點(diǎn)osg中的Render Engine,今天還了解了https的基本原理,也有所感觸,都想寫下來。可惜今天太累了,以后慢慢寫吧,不會(huì)說我是等明天吧,呵呵。
剛開始使用Ogre時(shí)總是碰到內(nèi)存泄露,而且往往是一泄千里,等半分鐘才能打完日志,我想這和Ogre中的大量大對(duì)象很有關(guān)系。下面就來分析一下內(nèi)存泄露的產(chǎn)生原因。
1. MFC中使用Ogre時(shí)發(fā)生的內(nèi)存泄露
這個(gè)問題比較有意思,其實(shí)并沒有發(fā)生泄露,而是MFC自作主張的認(rèn)為發(fā)生了內(nèi)存泄露,實(shí)際上內(nèi)存并不是沒有釋放,而是在VC報(bào)內(nèi)存泄露之后釋放,先來看一看MFC報(bào)內(nèi)存泄露時(shí)的調(diào)用堆棧:
msvcr71d.dll!_CrtDumpMemoryLeaks() 行2208 C
mfc71d.dll!_AFX_DEBUG_STATE::~_AFX_DEBUG_STATE() 行127 C++
mfc71d.dll!_AFX_DEBUG_STATE::`scalar deleting destructor'() + 0xf C++
mfc71d.dll!CProcessLocalObject::~CProcessLocalObject() 行472 + 0x26 C++
mfc71d.dll!CProcessLocal<_AFX_DEBUG_STATE>::~CProcessLocal<_AFX_DEBUG_STATE>() + 0xf C++
mfc71d.dll!$E10() + 0xd C++
mfc71d.dll!_CRT_INIT(void * hDllHandle=0x7c140000, unsigned long dwReason=0, void * lpreserved=0x00000001) 行234 C
mfc71d.dll!_DllMainCRTStartup(void * hDllHandle=0x7c140000, unsigned long dwReason=0, void * lpreserved=0x00000001) 行288 + 0x11 C
AFX_DEBUG_STATE的析構(gòu)函數(shù):
_AFX_DEBUG_STATE::~_AFX_DEBUG_STATE()
{
#ifndef _AFX_NO_DEBUG_CRT
_CrtDumpMemoryLeaks();
int nOldState = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
_CrtSetDbgFlag(nOldState & ~_CRTDBG_LEAK_CHECK_DF);
_CrtSetReportHook(pfnOldCrtReportHook);
_CrtSetDumpClient(pfnOldCrtDumpClient);
#endif // _AFX_NO_DEBUG_CRT
}
很顯然CrtDumpMemoryLeaks()是在mfc71d.dll卸載時(shí)被調(diào)用的,如果這個(gè)時(shí)候OgreMain_d.dll還沒有卸載,那么在Ogre中new的全局變量也就還沒有釋放,所以MFC會(huì)認(rèn)為產(chǎn)生了內(nèi)存泄露。如何處理這樣的問題呢。很簡(jiǎn)單,讓OgreMain_d.dll在mfc71d.dll之前析構(gòu),但是默認(rèn)的MFC程序似乎不是這樣干的(為什么呢?),這就要求對(duì)項(xiàng)目設(shè)置作一點(diǎn)調(diào)整,使得Mfc71d.dll在OgreMian之前被鏈接,這樣程序運(yùn)行時(shí)MFC71d就會(huì)早于Ogre加載,也就晚于Ogre卸載。具體設(shè)置如下:
i) in the General tab, switch "Use MFC in a shared DLL" to "Use Standard Windows Libraries"
ii) in the C/C++/Preprocessor tab, add _AFXDLL to the preprocessor definitions
iii) in the Linker/Input tab, add mfc80d.lib anywhere before OgreMain_d.lib
另一種方法是,使用Ogre自己的MemoryManager,并且禁止調(diào)用MFC的DEBUG_NEW,這需要先
#define OGRE_DEBUG_MEMORY_MANAGER 1
然后刪除cpp中的以下行
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
這樣Ogre中會(huì)使用自己的new/delete,而不是調(diào)用vccrt中的_heap_alloc_debug
2. Ogre中的對(duì)象沒有釋放
由于Ogre中的很多對(duì)象并不是只要delete Root就可以釋放的。最好所有的對(duì)象都不要自己new,而是通過Ogre::Root,Ogre::SceneManager等創(chuàng)建,這些對(duì)象在Root析構(gòu)時(shí)會(huì)自己銷毀,但是對(duì)于從Ogre類派生的類,由于Ogre不存在Create這些類的函數(shù),所以只能在自己的代碼中new產(chǎn)生,并由自己負(fù)責(zé)析構(gòu)了,比如MovableObject派生的MovableText。當(dāng)然Ogre也會(huì)給你一個(gè)將新對(duì)象加入其管理的接口,對(duì)于MovableText就必須再實(shí)現(xiàn)一個(gè)MovableTextFactory才行。總之要小心小心再小心。
最后抱怨一下Ogre太大了,有一個(gè)OgreLite就好了。現(xiàn)在這樣使用起來光鏈接都要半天,真是太夸張了,所以沒事最好不要修改Ogre庫,呵呵。
最近由于工作的關(guān)系,希望用一個(gè)3D圖形引擎實(shí)現(xiàn)棋牌類的2D游戲桌面。為了提供盡可能好的兼容性,選擇了Ogre作為后臺(tái)渲染引擎,因?yàn)樗脑u(píng)價(jià)在開源3D引擎中很不錯(cuò),而且同時(shí)支持Opengl和D3D,所以沒有多想就選擇了它。到后期才發(fā)現(xiàn),Ogre雖然是號(hào)稱“Object Oriented Graphics Render Engine”,而且確實(shí)在場(chǎng)景,相機(jī),窗口等等的三維對(duì)象的建模上做得相當(dāng)不錯(cuò),但是其對(duì)于OCP等原則的遵循僅僅是大面上,Ogre中隨處可見一些巨大的對(duì)象類。所以要擴(kuò)展它或者重用它的部分功能簡(jiǎn)直是太困難了,不得已而拋棄了Ogre。這倒不一定是Ogre的錯(cuò),因?yàn)镺gre定位于游戲制作的,追求的是最新最酷的特效,這樣肯定很難從一開始就抽象好的。而且它要同時(shí)支持Opengl和D3D,這也是一個(gè)抽象的困難點(diǎn)。
雖然沒有使用Ogre,但是對(duì)于Ogre中多窗口渲染方式的實(shí)現(xiàn)還是很下了一番功夫的,不想這些功夫白白浪費(fèi),還是把它寫出來與所有人共享吧。
為了提供同一個(gè)用戶同時(shí)打多局游戲的可能,所以采用Ogre的話必須支持多桌面渲染,這里有兩點(diǎn)需要考慮:
1. 窗口渲染而不是全屏幕渲染
2. 多窗口渲染,每一個(gè)窗口使用不同的三維場(chǎng)景數(shù)據(jù)。
先說說Ogre如何做到窗口渲染吧。Ogre中對(duì)應(yīng)每一個(gè)渲染窗口有一個(gè)RenderWindow對(duì)象,該對(duì)象通過Root的CreateRenderWindow方法創(chuàng)建,為了與現(xiàn)有的GUI窗口兼容,我不希望Ogre為我產(chǎn)生新的HWND,而是把一個(gè)HWND作為參數(shù)傳遞給CreateRenderWindow方法,這是通過以下方法做到的:
Ogre::NameValuePairList parms;
parms["externalWindowHandle"] = Ogre::StringConverter::toStrin((long)hwnd);
bool bFullScreen = false; RECT rect; ::GetWindowRect(hwnd, &rect);
int windowWidth = rect.right - rect.left;
int windowHeight = rect.bottom - rect.top;
Ogre::RenderWindow* window = m_OgreRoot->createRenderWindow(windowName, windowWidth, windowHeight, bFullScreen, &parms);
關(guān)于多窗口渲染,則需要先說說Ogre中的場(chǎng)景結(jié)構(gòu):
如圖所示,Ogre中一切起源于Root對(duì)象,它負(fù)責(zé)產(chǎn)生和銷毀所有Manager。SceneManager負(fù)責(zé)管理場(chǎng)景數(shù)據(jù),所以為了實(shí)現(xiàn)多窗口,必須要有多個(gè)SceneManager實(shí)例,SceneManager中包含MovableObject,顧名思義,MovableObject是場(chǎng)景中可以運(yùn)動(dòng)的物體,SceneManager中還有一些靜止不變的數(shù)據(jù),比如Terrain等等,圖中并未列出。MovableObject如果不掛在SceneNode上在場(chǎng)景中是無法顯示出來的,一個(gè)SceneNode上可以掛多個(gè)MovableObject,這樣組織成一棵場(chǎng)景樹的結(jié)構(gòu),值得一提的是MovableObject是無法共享的,也就是說它只能掛在一個(gè)SceneNode下,想共享的話,只有使用Mesh了。Mesh由MeshManager產(chǎn)生,它定義了基本幾何形狀。一輛汽車可以同時(shí)在場(chǎng)景多個(gè)部分出現(xiàn),顯然不需要每一輛汽車創(chuàng)建一個(gè)幾何形狀,公用一個(gè)Mesh即可,這正是Entity的工作方式。Entity還提供了位置,動(dòng)畫等信息的封裝。
有了多個(gè)SceneManager之后如何讓這些場(chǎng)景渲染到指定的多個(gè)窗口上呢。這要通過Camera和Viewport實(shí)現(xiàn)。這里也正是Ogre設(shè)計(jì)得比較精巧的地方。Camera定義了觀察者從哪個(gè)位置以及角度觀察場(chǎng)景,以及可以觀察的范圍(視角,最遠(yuǎn)與最近位置等等),而RenderWindow通過Camera創(chuàng)建一個(gè)自身的Viewport,使得場(chǎng)景可以渲染到RenderWindow上。具體的渲染機(jī)制我并沒有深入研究,在Root中應(yīng)該有相關(guān)代碼。
說了這么多,還是來看看Ogre中多窗口渲染的代碼吧
Ogre::NameValuePairList parms;
parms["externalWindowHandle"] = Ogre::StringConverter::toString((long)hwnd);
RECT rect;
::GetWindowRect(hwnd, &rect);
int windowWidth = rect.right - rect.left;
int windowHeight = rect.bottom - rect.top;
Ogre::RenderWindow* window = m_OgreRoot->createRenderWindow(ogreNameFactory::applyWindowName(), windowWidth, windowHeight, false, &parms);
// Create SceneManager
m_SceneManager = m_OgreRoot->createSceneManager(Ogre::ST_GENERIC, ogreNameFactory::applySceneManagerName());
// Set ambient light
m_SceneManager->setAmbientLight(Ogre::ColourValue(1.0, 1.0, 1.0));
//Create Camera
m_Camera = m_SceneManager->createCamera(ogreNameFactory::applyCameraName());
m_Camera->setProjectionType(Ogre::PT_ORTHOGRAPHIC);
m_Camera->setFixedYawAxis( true, Ogre::Vector3(0, -1, 0) );
//// Position it at 500 in Z direction
m_Camera->setPosition(Ogre::Vector3(Ogre::Real(windowWidth)/2.0, Ogre::Real(windowHeight)/2.0, -260));
//// Look back along -Z
m_Camera->lookAt(Ogre::Vector3(Ogre::Real(windowWidth)/2.0, Ogre::Real(windowHeight)/2.0, 0));
m_Camera->setFOVy(Ogre::Degree(180.0 - 1.6416));
m_Camera->setNearClipDistance(5);
m_Camera->setFarClipDistance(271);
// Create one viewport, entire window
Ogre::Viewport* vp = window->addViewport(m_Camera);
vp->setBackgroundColour(Ogre::ColourValue(0,0,0));
//// Alter the camera aspect ratio to match the viewport
m_Camera->setAspectRatio(Ogre::Real(windowWidth) / Ogre::Real(windowHeight));
////Used for Hittest
Ogre::Ray aimRay = m_Camera->getCameraToViewportRay( 0, 0 );
m_RaySceneQuery = m_SceneManager->createRayQuery(aimRay);
// Make window active and post an update
window->setActive(true);
window->update();
代碼中與相機(jī)設(shè)置相關(guān)的部分看不懂美關(guān)系,關(guān)于相機(jī)的設(shè)置,我會(huì)在另一篇文章中詳細(xì)分析之。以上的代碼將成功的在hwnd上初始化渲染環(huán)境,現(xiàn)在要做得就是在SceneManager中添加Entity了,需要重繪的時(shí)候調(diào)用RenderWindow的update()即可。
時(shí)間不早了,打算今晚去星美看通宵電影,關(guān)于Camera的詳細(xì)分析就留到明天繼續(xù)吧,哈哈。