2008年11月1日 星期六

塔塔加高山洗禮

在經歷過上週騎過台中縣三坑的復健練習
把近一個多月沒騎過上坡路段的感覺找回來後
終於是有一點點的信心可以挑戰一下海拔2600多公尺的塔塔加
不過對於同伴從水里出發的邀約還是敬謝不敏
我想,還是乖乖地從同富出發就好
畢竟海拔高度差近2000公尺可不是開玩笑的

清晨5:30分起床,起了個大早,準時於0600上龍井交流道
然後在0635到達名間交流道下與同伴會合
耶,終於輪到不是我睡過頭,不過這種事情其實也沒什麼好高興的
稍微等了一下同伴後重新出發,接著完成與補給車會合
與從水里出發的同伴相會,並發出上車邀約等事情後
0830終於到達了同富國中小學前,正式地開始了一整天的行程

一開始只覺得還算相當輕鬆,相較於上週的坡度,實在相當的小兒科
但是總長超過40KM的上坡旅程還是不可小看
尤其海拔漸漸上昇之後,空氣相對稀薄,所以一開始算放慢了一點點的速度
大約以9~12Km/H的速度一路向上
由於初始總是體力滿滿,不到兩個小時就來到了台21線116K的望高茶園
這邊大約騎了15公里左右


稍做休息,補充水分與糖份,並等後落單同伴確認一下狀況後
1030開始再做出發。
這時實在是太輕敵與看得起自己的體力了。
此時速度仍是不變,想說已經騎了大約1/3的路程了,還是生龍活虎地
腿力仍然沒有什麼異狀,應該是OK啦
一路騎上到觀峰開始休息就覺得不太對勁
雖然對於只剩下約一半多一點(25km左右,全長約45km)
但已經不敢再堅持逼近9Km/H左右的速度
開始以8Km/H為主,然後比較徒一點就6~7Km/H
然後大盤退到最小,開始祈禱腿力不要耗盡

果不其然,在接近中午12點過後,於1230開始正式覺得腿在酸了
雖然還沒有到痛的境界,也只是隱隱約約,明顯是個警訊了
同時感受到的還有速度方面,也一整個降低,此時只來到127Km左右的距離
騎了同樣的兩小時左右,只有推進不到15公里
還逼近到只有10Km的距離
不過肚子餓了,還是得先填飽肚子,就小休一下再說

休息了片刻,再騎了一個小時左右
終於順利穿過某個遂道,不過也只有這一個小時左右的腿力可以發揮
接著開始進入毅力決定一切的關鍵時刻
是的,此該恨不得從觀峰出發的同伴可以馬上下滑
開著補給車連人帶車把我接到塔塔加上
我騎得好酸啊~~~~~~~~~~Orz
是沒有同伴說高海拔所特有的空氣稀薄,吸不到氧氣的感覺
休息個片刻,也是馬上可以上鐵馬來騎個100多公尺
可是馬上腿就會酸,明顯已經沒力了,跟跑20KM半程馬拉松差不多

再這樣休休騎騎一個半小時左右
中途終於看到第三出發點出發的同伴終於下滑,準備開補給車
也遇到了從水里上來的同伴從後面追了上來
看到了友人的加油簡訊,稍稍產生了連續騎3公里的拚勁...
瞄到山壁上標高2310公尺的牌子,訝異於已經爬這麼高的實力
還有自己已經沒力了,還騎在車上心虛地接受開車上來旅客的加油聲 XD
1500左右,此時還差倒數第二站,夫妻樹約不到3公里了

當然這也意味著離最高點,今日的終點塔塔加已經只剩下5公里了
可是無奈於現實的殘酷:
  1. 晚上還要上課
  2. 我是自己一個人開車
  3. 還有天生的惰性 XD
最終我還是妥協只止於夫妻樹,畢竟最終的2公里可是要上昇140公尺的高度
就算我用牽的上去,只會超過預定的離開時間1600
同時山上天黑的快,而天黑的山,是很危險的(都是理由嘛 XD)

終於放開一切,拚著預留的最後腿力
1530左右,到達了夫妻樹

飲恨於此呀 Orz
不過,對於一位體重超過80公斤的胖子
已經相當不錯了嘛,對吧(自我安慰中)

然後車子上補給車,到達塔塔加後
開心地享用熱騰騰的湯麵
與不是很冰涼的啤酒
真的通體舒暢呀
如果腿的耐力再高一點,早上出發再早一點
這目標應該可以達成吧
不過,這樣就有個理由來個第二次啦!

2008年10月13日 星期一

假日的小探險~高美濕地~

週日下午,該辦的事都差不多了
對於自己又沒有早起去騎個山路感到後悔
決定延續上週騎到火力發電廠的平地探險之旅
但火力發電廠邊實在太無趣啦
隨手翻開地圖再瞄了一下
瞬間就想起同在台中港區附近的高美濕地

拚到高美濕地再回來
跟想像中的差不多,除了東北季風的強勁讓我覺得有些吃力外
也只比火力發電廠稍稍遠一點,來回大約1個小時半左右
人很多,尤其是情侶,所以風景很不錯(什麼嘛.....)
還有看到有人在拍婚紗照,那個長長的禮服就這樣整個垂到地上
沾到泥土,碰到海水,我想洗的人會很嘔吧(真沒浪漫情懷....)

提到高美濕地,那個巨大的風力發電機是著名的景色
那對新人在拍婚紗照的同時,也是以此為背景主題
可是無可救藥的我,怎樣都揮之不去的印象
是村雨良與城茂的戰鬥
假面騎士Spirits第二部最後一話,ZX vs 強人
果然無可救藥啦

2008年10月8日 星期三

Ogre中文輸入 Final

最新的精簡修改版本→Ogre中文輸入Final修改
想看長篇心得的可以繼續看

嗯…還是從頭講起好了
Ogre是個免費而又強大的OpenSource 3次元圖像引擎
但對於想開發中文(或所有其它多字元)的程式來說
輸入法的無法使用是個大問題(現在顯示已經沒什麼大問題)

原先,所有Ogre的Windows訊息全部被封裝在OgreWindowEventUtilities裡(1.4.X版)
所以實行中文輸入所必須取得的IME訊息均無法正常取得
目前由免費打工仔在簡體網站所製作修改支援中文輸入的部分主要是從這裡切入
不過必須修改原始碼而動到LGPL授權是一個問題
這邊作者也透露了另一個不修改原始碼的可能,直接自行建立自己的視窗,然後手動建立Ogre的Render視窗為子視窗,就這樣取得母視窗的主控制權,進而建立自己想要的訊息處理。

OK,講起來很容易,問題在於要怎麼寫?
Ogre的中國網站只先簡單地點出這段程式。
//假设之前已经执行完创建窗口以及Ogre::Root对象的过程
//hWnd为窗口句柄,root为Ogre::Root类型实例
Ogre::NameValuePairList params;//构造参数
std::stringstream ss;
ss<<hwnd; //窗口句柄
params["externalWindowHandle"] = ss.str();//把窗口句柄做为字符串形式设置到参数中
root->initialise(false);//Ogre::Root对象初始化参数为false,表示手动创建渲染窗口
//下面创建渲染窗口
Ogre::RenderWindow * window = _root->createRenderWindow("name", //名称
        width,//宽度
        height, //高度
        false, //是否全屏显示
        &params);
後續交給讀者...

喔,這可是個說簡單不簡單,說難也不會很難的問題。
如果已經直接讀完ExampleApplication與ExampleFrameListener範例程式碼的人。
手動建立一個Ogre視窗不會很難,你只要照著那個步驟,一個一個建立就行。
可是完全沒用到Windows的基本型式,這也無法先手動產生一個母視窗。
更何況訊息處理咧?還有IME的處理啊!
對於初學者來說,全部都很陌生領域。

所以這邊就大概講一下怎麼繼承ExampleApplication與ExampleFrameListener來做中文輸入。
以改造核心處理的ExampleFrameListener開始。首先建立一個檔案ExampleCFrameListener.h,
並用檔名做繼承的類別名稱。然後除了繼承的,我們需要再加入Singleton與IME用的表頭檔。
#include "ExampleFrameListener.h"
//為了使用Singleton
#include "OgreSingleton.h"
//使用IME函式庫
#include "imm.h"  

建構式建立的地方請直接參考後面的程式碼。
接著就是我們必須設定兩個空的函數去取得轉換完成與還在轉換中的字串
///這邊是取得轉換完成的字串,直接傳給GUI System的injectChar
virtual void GetIMECompResultString(const Ogre::UTFString& tempString) { } ///這邊是取得轉換中的字串,主要是給像新注音需要另外產生視窗來顯示暫時性文字的 virtual void GetIMECompString(const Ogre::UTFString& tempString) { }

然後在這邊加入視窗訊息處理函式。
static LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

這邊有點麻煩的是此函式必須為static設定,所以在裡面的處理需要取得指標來運算。
我們為了把GetIMECompResultString與GetIMECompString往這裡丟,所以才設定此物件有Singleton功能以方便馬上利用(註1)。如果還有其它的方式,麻煩請告知。

既然都到這裡了,那當然,就是把上面那兩個函數放在WM_IME_COMPOSITON的訊息裡囉。
不過當然還是要加一些IME本身的字串取得與處理。
//自己處理的IME轉換
case WM_IME_COMPOSITION:

HIMC hImc = ImmGetContext(hwnd);
//正式取得轉換的字串
if (lParam & GCS_RESULTSTR)
{
//ImmGetCompositionStringA( hImc, GCS_RESULTSTR, tCharPtr, dwBufLen );
DWORD dwBufLen;
wchar_t* tCharPtr;
dwBufLen = ImmGetCompositionStringW( hImc, GCS_RESULTSTR, 0, 0 );
if(dwBufLen > 0)
{
tCharPtr = new wchar_t[dwBufLen];
memset( tCharPtr, 0, sizeof(wchar_t) * dwBufLen );
ImmGetCompositionStringW( hImc, GCS_RESULTSTR, tCharPtr, dwBufLen );
tempString = tCharPtr;
ExampleCFrameListener::getSingletonPtr()->GetIMECompResultString(tempString);
}
}// if(lParam...
//取得轉換中的字串
else if (lParam & GCS_COMPSTR)
{
//ImmGetCompositionStringA( hImc, GCS_RESULTSTR, tCharPtr, dwBufLen );
DWORD dwBufLen;
wchar_t* tCharPtr;
dwBufLen = ImmGetCompositionStringW( hImc, GCS_COMPSTR, 0, 0 );
if(dwBufLen > 0)
{
tCharPtr = new wchar_t[dwBufLen];
memset( tCharPtr, 0, sizeof(wchar_t) * dwBufLen );
ImmGetCompositionStringW( hImc, GCS_COMPSTR, tCharPtr, dwBufLen);
tempString = tCharPtr;
ExampleCFrameListener::getSingletonPtr()->GetIMECompString(tempString);
}
else
ExampleCFrameListener::getSingletonPtr()->GetIMECompString("");

}// if(lParam...
ImmReleaseContext(hwnd, hImc);
break;

之所以要取得轉換中的字串,其實是為了新注音。
因為那個還沒轉好的字(正式輸出)是不可能就主動顯示在要輸入的地方,而舊注音有子視窗飄著沒問題。當然按上下鍵選字時,新注音的視窗也會跳出,不過還是看不到一整串的字。
所以才加了GetIMECompString來加以處理這部分。
先不管Singleton的額外設定,輸入中文最主要的部分在此。

再來是ExampleApplication的改造。一樣建個ExampleCApplication.h的檔案,然後引入舊的ExampleApplication.h與剛剛的ExampleCFrameListener.h。
單純繼承的部分就不說了,然後重點首先在於變數。
請加入
protected:
HINSTANCE mHInstance; // HInstance of application, for ime
HWND hwnd;//HWND of appliction for the new windows


然後再建立一個configure的函式修改如下:
if(mRoot->showConfigDialog())
{
// If returned true, user clicked OK so initialise
// Here we choose to let the system create a default rendering window by passing 'true'
/// But the chinese need a manual window. Set it false for manual.
mWindow = mRoot->initialise(false);

/// then create a new window for IME
setupWindow();
return true;
}


接著就是手動建立視窗的重頭戲了。
理所當然我們建立一個setupWindow()的新函式在後面。
接著取得目前的 hinstance
mHInstance = GetModuleHandle( NULL );

註冊一個WNDCLASS如下:
WNDCLASS wincl = { 0, ExampleCFrameListener::WindowProcedure, 0, 0, mHInstance,
LoadIcon(0, IDI_APPLICATION), LoadCursor(NULL, IDC_ARROW),
(HBRUSH)GetStockObject(BLACK_BRUSH), 0, "OgreChineseWnd" };

RegisterClass (&wincl);

請注意那個ExampleCFrameListener::WindowProcedure就是加在這裡。

接著我們建立並指定視窗的大小位置與視窗類別等等
取得其HWND
hwnd = CreateWindow("OgreChineseWnd", "", WS_OVERLAPPEDWINDOW,
left, top, width, height, HWND_DESKTOP, 0, mHInstance, 0);

這時我希望有個不同過去的中文化標題
SetWindowTextW( hwnd, L"Ogre中文輸入測試視窗");

再來就是照著最先的部分,讓Ogre設定一個同樣大小的視窗,為子視窗
Ogre::NameValuePairList params;
params["externalWindowHandle"] = StringConverter::toString((int)hwnd);
mWindow = mRoot->createRenderWindow("Ogre Render Window", width, height, false, &params);


最後不要忘了,讓母視窗顯示,才不會什麼都看不到
ShowWindow (hwnd, SW_SHOWNORMAL);


這樣大致上的設定就差不多了。
接下來的部分,首先要繼承上面的新類別(廢話 XD)
然後編譯的參數要加入imm32,這樣IME才有作用。
當然也不能忘了,要讓GUI System或Ogre能顯示中文字才有辦法看得到輸入
這部分就不加贅述了
要補充的是輸入的部分。
以CEGUI為例:
在CEGUI::System::getSingleton().injectChar( arg.text );前請加入這一行
if ( !ImmIsIME(GetKeyboardLayout(0)) )
讓IME啟動時,無法輸入英數,才不會在轉換前多了一堆自己Keyin的亂碼。
加入GetIMECompResultString的函式如下:
void GetIMECompResultString(const Ogre::UTFString& tempString)

Ogre::UTFString temp = tempString;
for (int i = 0; i < temp.size();i++)
{
CEGUI::System::getSingleton().injectChar( temp[i] ); }

injectChar只能處理單一字元,所以才會用迴圈一個一個輸入整個字串。
另一個GUI System "QuickGUI"的部分大同小異,要多記得把Ogre的中文顯示設定好(它沒辦法像CEGUI本身自己支援Unicode轉碼,要靠Ogre自已顯示),並使用setSupportedCodePoints
把要中文的字碼先往裡面塞,不然就算有輸入,它也會因為找不到對應的,而自動取消,會白忙一場。

最後,修改的完整範例在這裡:
ExampleCFrameListener.h
ExampleCApplication.h

一些地方略有差異,最主要就是第二個檔案的setupWindow的函式有參考RenderSystem的原始碼
可以自動生成置中且與一開始設定相同大小的視窗。
我想在Win32的環境下,使用中文輸入應該就這樣子吧。
當然還有一些IME的功能沒有很完整地利用,不過我想這個任務就交給其它有更深需求的人來做吧。我想寫的東西只要能輸入名字就很足夠了。

感謝在我研究IME過程給我幫助的網友,不然現在可能還是尾巴會帶出一堆亂碼來的版本。
另外測試的電腦有灌過Unicode補完計畫,我不清楚是否對IME有所影響。因為我所寫的是專門給寬字元(Wide Char)用的GetIme的函式,有可能會在不支援unicode的輸入法出差錯。
還有我的編譯器是mingw的gcc,VC還沒試著跑過,不過我想問題應該不大。

這東西其實搞起來難度不高,而且看來在1.4.X版出來後,就有辦法實做出來。
到底是搞中文的人少呢?還是即然已經有修改程式碼的辦法,就無所謂呢?
總之,斷斷續續,忙裡偷閒地搞了至少3年Ogre。
能實現一種中文輸入成功真的很高興。在此分享給大家。

註1:其實我還是個半生不熟的學習者,只知道可以拿來用,還不知道為什麼可以用,以及是否有其它延伸的問題。還忘多多海涵。

2008年10月7日 星期二

Ogre的中文輸入 Part2

這次花了點時間修改了ExampleApplication與ExampleFrameLisntener的範例用檔案,讓可以使用IME的視窗自動產生。然後把WindowProcedure直接放入ExampleFrameLisntener裡,本來是想讓這個訊息處理直接使用ExampleFrameLisnetner裡面的函式,可是失敗了,因為它必須為Static宣告。
所以只好再用Singleton的方式解決,然後把函式宣告為virtual,內容為空,讓範例可以直接修改使用。
另外新注音無法顯示尚未結束轉換的字,目前還是只能用另一個StaticText(CEGUI)或是TextBox(QuickGUI)來幫助顯示,比較起來日文的IME還是比較好啊,可以浮空顯示。
最後的最後,參考了MSDN上的IME範例,還是沒辦法解決那個IME轉換後,會有隨機不確定的亂碼產生,反正產生出來殺了就OK,就先放著不管好了。

2008年10月5日 星期日

九月的噴射氣流攻擊(ジェットストリームアタック)

九月份真不是個騎自行車的好日子啊!
一連來了三個颱風不說,還大部分都往假日撞啊。
週六都已經因工,因為工作而無法出門
還來這套噴射氣流攻擊實在讓我整個人活力降到了幾近谷底。

這時候要是有鋼彈就好
「是呀,爸爸做的鋼彈是無敵的(湯尼調)」
只要我的自行車像鋼彈一樣
連颱風天都能出門啊,哈哈哈哈哈
....妄想結束
不過現實終究是現實,颱風當天先不管
颱風走後,山區均是一片殘破
大雪山那邊據說也是處處坍崩
13KM就斷了的樣子(一週前左右的消息)
今年初剛開放的雪見游憩區(司馬限林道)也是暫時封閉
看來要深秋或是入冬才有辦法再去朝聖

最近就先在自家附近的小丘崚地好好地練習吧

2008年9月21日 星期日

Ogre輸入中文的辦法

如果還沒什麼人創新辦法的話,目前總共有三種
一種是直接修改源碼,讓已經被封裝的WindwosEvent可以被使用。
一種也是修改源碼,過去有個所謂i18n的patch版本(1.0.x到1.2.x左右的事吧)
這是利用SLD的Input來做,理論上是跨平台的最好做法。
最後一種,也是最麻煩的一種,改在建立Ogre視窗時動手腳
不破壞Ogre原始碼,可以直接使用PreBuilt SDK的辦法,雖然只能用在Windows。

這是從Ogre的中國非官方站看來的
應該也是修改源碼讓其可以支援中文輸入的"免費打工仔"寫的
就是利用Ogre最初就有的特性,可以內嵌成為子視窗的辦法
讓我們直接利用父視窗的一切來取得IME的文字

喔...這樣講起來很容易,可是實作起來...
如果不是對Windows的訊息運作有一定的了解
還有對如何取得IME的文字有相當的認知
同時對於如何手動建立Ogre而不使用ExampleApplication的繼承的話,
還真的是不知從何開始著手。

就這樣,斷斷續續搞了我半個多月才起來....
正式說起來是三天左右
而且還是個不完全的缺陷範列
第一天從一開始我著手方向就錯誤了,所以一直做不出來,就此中斷
第二天是前天,工作到一半忽然靈光一動發現錯誤的地方
然後成功地補捉了WM_IME_COMPOSITION訊息,顯示了個MessageBOX出來
第三天是昨天,花了一個下午的時間,把中文成功地在QuickGUI裡輸入
是用injectChar輸入喔,不是用setText喔。
之所以不完全,是我只有處理IME轉換的部分訊息,所以新注音不能正常使用
沒辦法做還沒轉換前的暫存編輯(就是選字看不到)
也沒做UNICODE轉換,所以萬一輸入法不支援Unicode也不能用。
但總算是實現長久以來的夢想

不過話說回來,這與我真正寫遊戲,好像沒多大關係喔
如果不是非常執著於在Ogre裡面輸入,搞個DialogBox也可以辦到啊
我也不用處理什麼IME訊息,頂多要做BIG5轉UTF8而已
所以我在幹什麼啊!

2008年9月2日 星期二

Ogre 1.6.0 RC1釋出


Ogre 1.6.0 RC1
千呼萬喚始出來。
最新版本的Ogre3D終於出來了。
不虧是大改版。
新的概念與東西一大堆。
稍微研究一下,還蠻令人訝異的。
像新的Script Compilier已經進化到,具有簡單的物件導向概念,能繼承與使用變數。
還有全新屬性Portal Connected Zone的ScneneManager(PCZSM)。
這PCZSM簡單地說,就是將類似LOD的模型(Model)概念整個使用與室內場景與Terrian上。
LOD是離愈遠,模型會簡單化以節省資源,而PCZSM則是鏡頭(Camera)看不到的東西全部省略,
並不是被材質擋住了,是連線架構(Wireframe)都看不到。
另外還新加入了Parallel-Split Shadow Map等等的新東西。
可惜最近工作比較忙,看來只能忙裡偷閒,找空白時間來研究研究。

不過這麼大改的東西,我拿用修改舊的專案會改不完
我看還是先用1.4.X來持續目前的計畫,反正這只是RC1
要跳等1.6.1 Release再說。