2013年10月02日

WebGL Volume1.1

ご無沙汰でした手(パー)
最近FinalFantasy14によって初MMORPG体験をしたgood sun晴れこと山口です。
夜な夜なエオルゼアを旅しております。

さて、そんな誘惑にかろうじて打ち勝ち、
公約通り前回のblogで作れなかったノイズ関数間に合いました!

131002_blog2.png

exclamation※今回もデモはGoogleChrome(Windows7 / MacOSX)でのみ動作します。exclamation
(IE,FireFox,Operaでは動作しませんふらふら)

デモはこちら

なんと前回とパーティクルの動きが違います!
カールノイズという方式によって動きを与えていますが、
これはノイズ関数によって作られる発散のない速度フィールドによって、
パーティクルに良い感じの乱流台風による動きを与えてくれる仕組みです。

なおノイズ関数の処理は前回と同じくこちらを参考にさせていただきました。
http://prideout.net/blog/?p=63

残念ながら一定時間走らせているとこんな感じになってしまいます。

131002_blog.png

...発散の無い速度フィールド...もうやだ〜(悲しい顔)
次回volume1.2でリベンジします!
ではまた次回。手(パー)

posted by 管理人 at 22:30 | プログラミング

2013年08月13日

Doxygenを使ってみよう!その2

最近、亜熱帯のように暑い日が続きますね晴れ
節電の為にエアコン入れずに頑張っていますが、さすがに夜夜はスイッチ入れてしまいますあせあせ(飛び散る汗)
台風扇風機だけでは生暖かい空気をかき混ぜるだけで全然涼しくないのが残念です。
ご無沙汰していますコンドウです。

前回自動ドキュメント生成ツール"Doxygen"の紹介をさせていただきましたが、
さらーっと紹介して「あとは自力で頑張ってね」と突き放した感が拭えない
ようなので今回は実際に出力するところまで面倒見ようではないかとサンプルを
用意して帰ってきました。

本ページにdoxygen.zipを用意していますのでまずはこれをパソコンに落として
下さい。
パソコンはWindows/Linux/Mac何でも構いませんが、出力できるバッチファイルは
Windowsのものを用意しました。
他のOSの方は若干手を加える必要があります。(といっても大した変更ではありませんが)

おっと、その前に肝心のDoxygen本体をダウンロードしておく必要があります。

http://www.stack.nl/~dimitri/doxygen/download.html

こちらのページから目的のプラットホームを落として下さい。
Windowsであれば、 Doxygen-1.8.4-setup.exe ですね。

無事に落として来たら即インストールです。これもプラットホームによって
変わりますので臨機応変に対応して下さい。


インストールが済みましたら、先程ダウンロードしましたdoxygen.zipをどこかに
展開します。以下のようなフォルダ構成になっています。


 |
 +- doc
 |  |
 |  +- tmp
 |
 +- include


ルートフォルダに build_doxygen.bat があります。これを実行します。
コンソールに「出力成功」と表示されない場合はDoxygenのセットアップがうまく
いっていない (例えば、実行パスが通っていない等)可能性があります。
自力で調査してみましょう。

出力成功が表示されると何かキーを押してコンソールを閉じます。
そして生成された doc/tmp/html/index.html をブラウザで開きます。







と分かりやすいドキュメントが簡単に作れるのはとても魅力的ですよねexclamation



Doxygen対象のソースフォルダはincludeです。(中に1つだけヘッダーファイルが
あります)

Doxygenへは細かい指示が必要です。それがdocフォルダにあるdocument.confです。
このファイルは以下のコマンドを実行しても生成できます。


> doxygen -g document.conf


中には沢山に設定項目が存在しますが、最低限必要な物を紹介します。

【DOXYFILE_ENCODING】
 ソースファイルの文字コード指定です。今回はUTF-8で用意しましたので
 UTF-8を指定しています。

【PROJECT_NAME】
 プロジェクト名です。かっこいい名前を設定しましょう。

【OUTPUT_DIRECTORY】
 ドキュメント(html)が出力されるフォルダの指定です。tmpを指定しています。

【OUTPUT_LANGUAGE】
 生成されるドキュメントの言語指定です。Japaneseを設定しました。

【INPUT】
 Doxygen対象となるソースのフォルダ指定です。本来はドキュメントを取る
 先頭のフォルダを指定します。

取りあえずはこんなところでしょうか。他にも FILE_PATTERNS や EXCLUDE 等
知っていると便利なものもありますので是非自身に合ったDoxygen解説サイトを
探してみて下さいグッド(上向き矢印)

おっと、もう結構な紹介になり紙面(exclamation&question)が少なくなりました。サンプルで
用意したソース(IMemorySystem.h)は1つだけですが、Doxygenがどういうもの
かを知るには十分だと思います。
次回はDoxygenのコマンドについて、もう少し詳しく解説したいと思います。
ではるんるん
Sample File: doxygen.zip


posted by 管理人 at 21:14 | プログラミング

2013年08月12日

WebGL Volume1.0

お久しぶりです!
猛暑続く日々に部屋のエアコンは28.5℃設定なgood sun晴れこと山口です。
水分補給いい気分(温泉)等に気を配りましょう。

本日は久しぶりにWebGLデモ紹介をしたいと思います。
以前のレイマーチングデモを改造した、パーティクルをレイマーチングするデモです。

実はパーティクルの動きをCurl-Noiseを利用した
疑似流体シミュレーションネタにしたかったのですが、
時間が足りずにカールしない版をお届けする事になりました。

という事で、完成版は次回のお楽しみとして、
WebGLでの任意ボリュームデータをレンダリングする話をしようと思います。

20130812_00.png

exclamation※デモはGoogleChrome(Windows7 / MacOSX)でのみ動作します。exclamation
(IE,FireFox,Operaでは動作しませんもうやだ〜(悲しい顔))

デモはこちら

以前のデモはノイズ関数で作成したボリュームをサンプリングしていましたが、
今回はテクスチャを使ってボリュームのサンプリングを行っています。
ボリュームを表現するならピッタリなのが3Dテクスチャですが、
残念なことに現在のWebGLでは3Dテクスチャが使えませんでした。
そこでZ軸方向のデータをテクスチャのU方向に配置する事にしました。
32x32のテクスチャの断面図を横に32個敷き詰めた感じです。
その為、テクスチャのサイズは横32x32,縦32の1024x32というサイズになっています。
サンプリング時はZの補間用に2回サンプリングしてGLSLのmix関数で補間を行っています。

レイマーチングは以前と同じく一定距離レイを進めてその時の密度と遮蔽量を蓄積していきます。
今回はさりげなくコリジョンの玉もレイトレースで一緒に描画してみました。

さて描画に関しては以上です。
最適化していない割に結構速度が出るのが驚きです。
巷ではWebGLでグローバルイルミネーションを利用した綺麗なデモ等が出てきていますが、
レイトレースなデモも増えてくるかもしれないですね。

次回はきちんとカールしたり、コリジョンが動いたり、ジェネレータが動いたり
アクティブなデモにしたいと思います。
今回及び次回に向けて以下サイトを参考にしてパーティクルぴかぴか(新しい)の動きを作成しています。
http://prideout.net/blog/?p=63
またジオメトリ計算にはBrandon Jonesさんのgl-matrixを利用させて頂いています。
https://github.com/toji/gl-matrix
先人はいつだって偉大です。
では次回Volume2.0をお楽しみに!パンチ

posted by 管理人 at 20:06 | プログラミング

2013年07月26日

文字コード

こんにちは。
つい先日に3本目の親知らずを抜きましたふらふら
ついでだから残りの1本も抜いてしまおうか考えているササモンです。


今回のブログは、文字コードについてちょっと書いてみようと思います目

プログラムを書くときに日本語がまったく入っていないASCIIコードだけでプログラムを書くことは少ないですよね?
コメントだったり文字列リテラルが日本語の場合も少なくないと思います。
そういうときは、ファイルの文字コードをうまく選択しないとコンパイルが通らなかったり
いわゆる「ダメ文字」と言われる文字を使った直後の文字が意図せずにエスケープされておかしくなるなどがありますがく〜(落胆した顔)

さて、ここで問題です。
コンパイルされた後の文字列リテラルの文字コードはどうなっているでしょうか?
例えば、UTF-8で保存したソースをコンパイルした場合

const char* sampleText = "この文字コードは?";

上の文字コードは、ファイルがUTF-8なのでUTF-8・・・、では、ありません。この文字コードは、「環境依存」です。
実行コードの文字コードは、ファイルの文字コードに関わらずコンパイラによって決まっています。
一応、コンパイラのオプションで指定したものに変更出来ることもあります。

そんなことしなくても、大丈夫。次のようにすれば良いという人も居ると思います。

const wchar_t* wideString = L"この文字コードは?";

先ほどとは違い、文字列リテラルの前に'L'を付けてwchar_t型の変数に入れます。
こうすることでワイド文字という扱いになります。
では、この文字コードはどうなるでしょう?wchar_tのサイズ見ると2バイトなのでUTF-16?
答えは残念ながら違いますがく〜(落胆した顔)wchar_tは、1バイトより大きい文字も扱えるようにするための型です。
このため、その文字コードが何であるかは決まっていません。(Unicodeであるかどうかすら決まっていません)
つまり、これまた「環境依存」になります。

ここまで来ると依存症なのかちっ(怒った顔)と愚痴の1つも言いたくもなりますが
C++11またはC11でようやく文字コードを指定できるようになりましたわーい(嬉しい顔)

const char16_t* utf16 = u"utf16です";
const char32_t* utf32 = U"utf32です";

文字列リテラルの前にu(小文字)を付けてchar16_t型に入れることでUTF-16になることが確定します。
そして、文字列リテラルの前にU(大文字)を付けてchar32_t型に入れることでUTF-32になることが確定します。
もちろん文字定数でも同様に指定可能です。
また文字列リテラルのみですが、

const char* utf8 = u8"utf8です";

とu8を前につけるとUTF-8を定義出来るようです。
(これを書いている最中に念のため調べてみるとマクロの_STDC_UTF_16__、__STDC_UTF_32__が1に定義されている必要があるようです。
これが定義されていない場合・・・「環境依存」になりますモバQ

自分の環境だけでプログラムをしていると、その環境でしか通用しない書き方をしているということに気が付かないこともあります。
昨日のブログと打って変わって基本的なところですが、こういう基本のところも大事にしていきたいですね位置情報

それでは。






posted by 管理人 at 18:54 | プログラミング

2013年07月17日

enchant.jsとjsdo.it

こんにちは。音楽は洋楽ばかり聴いてる店長です。

今日は enchant.jsjsdo.it についてです。

enchant.js は HTML5 + JavaScript で作られたゲームエンジンです。
javascriptの勉強もかねて家で触ってみました。

簡単に2Dアニメーションできたり
イベント駆動で作れたりと触ってて楽しいものでしたわーい(嬉しい顔)

また、enchant.js はjsdo.itで簡単に試すことができますexclamation
インストールなどの作業無く、ブラウザ上で実際に試せるのは非常にやりやすくて良いですね。
他の人が作った作品をForkすることもできるので、開発者として楽しい部分もあります。

他にも 9leap で enchant.js で作られたゲームが公開されています。
こういった投稿サイトが用意されているのもモチベーションが上がりますグッド(上向き矢印)


まだまだ知らない言語やエンジンがあるので、
これからも色んな技術に触れて、よりスキルを高めていきたいと思います手(グー)

posted by 管理人 at 20:26 | プログラミング

2013年06月21日

Doxygenを使ってみよう!

最近、めっきり目視力が落ちて夕方にもなると視界がボケボケになって
加齢の衰えを感じずにはいられない今日この頃。ご無沙汰しておりますコンドウです。

ところでDoxygenってご存知でしょうか? googleで検索すると何やら英語のサイトが
見つかりますね。google先生の自動翻訳で最初の出だし辺りを訳してみると


Doxygenは
ソースコードからドキュメントを生成

doxygenは、注釈付きのC + +ソースからドキュメントを生成するためのデファクトス
タンダードツールですが、それはまた、C、Objective-Cの、C#、PHPは、JavaやPython、
IDL(CORBA、Microsoft、およびUNO / OpenOfficeのフレーバーなど、他の一般的な
プログラミング言語をサポートしています)、Fortranでは、VHDL、Tclで、そしてある
程度D.doxygenは、3つの方法であなたを助けることができます。


一部訳が変なところもありますが十分意図が伝わってきます。
そうです。doxygenとはソースコードからドキュメントを生成する為のとても便利な
ツールなのですexclamation×2
ただソースを解析してドキュメントを自動で出力できるほどインテリジェンスなもの
でも無い為、ある程度ソースにコメントを決められたルールの中で記述していく必要が
あります。ではどんな感じでコメントを記述すれば良いのか一例を紹介します。


/*!
@brief 画面に修飾された文字列を表示

予め設定されたフォントサイズ、フォント種類で画面の任意の位置に文字列を表示する。

@note この関数を実行する前に必ず、画面を初期化しておかなければならない。
@see getColor setColor

@param [in] x 文字列の開始X座標
@param [in] y 文字列の開始Y座標
@param [in] mode 修飾するモード
@param [in] ptr 文字列

@return 表示された文字数

@author Y.Kondo
@date 2013/06/21
*/
int dispStrings(int x, int y, int mode, const char *ptr);


コメント開始時の/*!@で始まるキーワードがdoxygen用の単語となります。
@briefは概要を、@paramは引数を、@returnは戻り値の説明とそれぞれ意味が決まって
おります。

関数のプロトタイプ宣言の上に付けるとソースを修正する時でもコメントを修正しや
すくなりそうです。

他にも@file、@defgroup、@code等々、ドキュメントを見やすくする為の単語が沢山
存在しますがまずは基本的な物からスタートしていき生成されたドキュメント
(.html形式で出力される)を確認しながら、コメント記述に目磨きをかけていければ
別途ドキュメントを作成する手間は大幅に半減できる非常に優れもののツールです。

doxygenの魅力を伝えるにはまだまだお伝えしたい事が沢山ありますが
「doxygen コメント 書き方」
等で検索していただくと先人たちの教えを戴けるかと思います。

もしソースのドキュメント管理に苦労している方ありましたら最初の内は覚える事で
苦労しますが必ず後に大きな助けになりますexclamation×2


posted by 管理人 at 21:29 | プログラミング

2013年05月24日

グリとブランのC++講座

こんにちは。
半袖通勤始めました晴れ
ドラゴンでするんるん

5月も下旬になりましたね。
かわいいは周りの環境が変化しやすい季節ですグッド(上向き矢印)
4月に入社した新入社員の方々の中には、
研修を終えて現場で働き始めた人も多いのではないかと思います。
ヘキサドライブでも4名の新人が研修を終え、
より良いゲームを作るために日々頑張っている姿が見受けられますわーい(嬉しい顔)

そこで新人のC++入門者向けに、社内勉強会を開催しましたexclamation×2



主にC++98のSTLに関して解説しています。
基本的な内容だからこそ、改めて見直しながら他のプログラマーと意見のやり取りをしていると、
僕自身にも新たな発見がありますねひらめき

ヘキサドライブでは社内勉強会や日々の会話を通じて、
技術の共有や若手の教育に積極的に取り組んでいます。
興味を持った学生のみなさん、
ヘキサドライブではまだまだゲームプログラマーを募集中です手(グー)
リクナビ2014ヘキサドライブページ
5月31日必着です。ご注意下さい。

たくさんのご応募をお待ちしていますexclamation

posted by 管理人 at 19:32 | プログラミング

2013年05月13日

よびだせ!Objectve-C!!

こんにちは。
ゴールデンウィークは遠出しなかったマイキーです猫

普段遠方へ行っている地元の友人がこちらに戻ってくるので……という寸法です。
こんなゴールデンウィークも有りかなぁと、のほほんいい気分(温泉)と過ごしておりましたが、どこかへ出掛けても良かったかなぁという気持ちも少々。
夏はどこかにでかけるぞぉー晴れという野望を密かに抱いておりますが、果たして夏まで持続するのかexclamation&question
ご期待下さいぴかぴか(新しい)
***

最近ふとしたきっかけでObjective-Cに触れる機会があり、ちょこちょこ勉強をするようになりました。
最初は独特な文法で面食らいましたが、習うより慣れろ精神でゴリゴリサンプルを作ったりして遊んでいます。
個人的に、メソッド呼び出し時に引数にキーワードを記述可能な所に好感が持てますグッド(上向き矢印)おそらく、引数の用途・意図を呼び出す側からも明確にする為の措置でしょうか。
キーワードを記述するとメソッド実行部分のコードが少々読みづらくなる(のは私だけ?)側面もありますが、書き方次第で可読性はいかようにでもなるので、今の所好感触です。
今後とも上手く付き合っていきたい言語ですひらめき

さて、まずはその構文の特異さに面食らったObjective-Cですが、中身も結構C/C++と違っているようです。
一つ例を挙げると、メソッド呼び出し時のジャンプ先アドレスの解決がコンパイル時ではなく、実行時に動的に行われているのだとか目
「あれ、それって遅くない?」と思われた方は正にその通りで、以下のメンバ変数を加算するメソッド「addValue」をそれぞれiPhone4Sで100万回ずつ実行した所

* C++

class Adder_cplusplus
{
public:
Adder_cplusplus(void):_value(0){}

void addValue(void){ _value += 1; }

private:
int _value;
};

void doProc(void)
{
Adder_cplusplus* pAdder = new Adder_cplusplus();
for( int i(0); i < 1000000; ++i ){
pAdder->addValue();
}
delete pAdder;
}


* Objective-C

@interface Adder_objectivec : NSObject
{
@private
int _value;
}

- (void)addValue;

@end

@implementation Adder_objectivec

- (void)addValue
{
_value += 1;
}

@end

void doProc(void)
{
Adder_objectivec* adder = [[Adder_objectivec alloc] init];
for( int i(0); i < 1000000; ++i ){
[adder addValue];
}
[adder release];
}


C++側は平均25ms、Objective-C側は平均84msと、およそ3倍強近い差がありました。
一度の呼出しでは大した差になりませんが、膨大な回数のループの中で何度も呼出される場合を考慮すると少しでもオーバーヘッドをそぎ落としたい所ですたらーっ(汗)
調べてみると、methodForSelectorやinstanceMethodForSelectorというメソッドで関数ポインタを取得出来、そこからメソッドを呼び出すとオーバーヘッドを削る事が出来るとのこと。
よし、じゃあそれやってみようexclamation×2という事で修正したObjective-Cのコードがこちら左斜め下


@interface Adder_objectivec : NSObject
{
@private
int _value;
}

- (void)addValue;

@end

@implementation Adder_objectivec

- (void)addValue
{
_value += 1;
}

@end

void doProc(void)
{
Adder_objectivec* adder = [[Adder_objectivec alloc] init];

SEL selector = @selector(addValue);
// instanceMethodForSelectorの返り値はIMP【id(*)(id, SEL, ...))】型なのでキャストする
void(*func)(id, SEL, ...) = (void(*)(id, SEL, ...))[Adder_objectivec instanceMethodForSelector:selector];

for( int i(0); i < 1000000; ++i ) {
func(adder, selector);
}
[adder release];
}


平均実行速度は平均64msに。25%程、ほんのり高速化する事が出来ました揺れるハート
ループ内で都度つど行われていたジャンプ先の検索を、事前に行うようにする事で処理速度が上昇した、という訳ですね。
メソッド呼び出し時のオーバーヘッドが実行時のボトルネックとなる事は多くありませんが、ほんんんの少しでも良いから速度を上げたい!!という時は使ってみて良いかもしれません。
高速化は細かな改善の積み重ねですので、この高速化が役に立つ日がいつか来るかも?

それではまた手(パー)

posted by 管理人 at 21:26 | プログラミング

2013年04月19日

経路探索アルゴリズム

なんと ドラゴンが おきあがり
なかまに なりたそうに こちらをみている!
なかまに してあげますか?

>はい
 いいえ

ドラゴンが なかまに くわわった!

・・・

そんなこんながありまして、ヘキサドライブの一員となりましたるんるん
はじめまして、ドラゴンですグッド(上向き矢印)

ヘキサドライブは志の高い人が多く、それに刺激されて僕も負けじとレベルアップを目指す毎日です手(グー)
より良いコンテンツを作っていけるよう、頑張っていきたいと思います。


さて、少し前に大阪メンバーで集まってお花見をしていましたが、同時期に東京メンバーもお花見をしましたかわいい
それの場所取りで皆を待っている時間に「Rasende Roboter」というボードゲームをしたのですが、一言で言うとゴールまでの最短経路を探すゲームで、プログラマ3人でプレイしたために本気の戦いを繰り広げてしまいましたexclamation
とても面白いボードゲームなので、興味を持たれた方は是非プレイしてみて下さい。

経路探索といえば、ゲームでも敵キャラのAIなどで実装されますねひらめき
今回は探索アルゴリズムの中でも割とポピュラーな A* を紹介しようと思います。

A*の概要に関してはこちらを参照して下さい。
A*の特徴としては、単純に全ての経路を調べていくのではなく、よりゴールに近いと推測される方を優先して調べることにより、評価回数を減らすことができる点ですね。
以下の条件下でA*を実装してみました。

・移動は上下左右のみ
・隣接ノード間の移動コストは常に1
・マップの左上端のノードをスタートとし、右下端のノードをゴールとする


マップデータ(MapData.h)

namespace Map
{
// マップデータ(0:壁、1:通路)
static const int DATA[] = {
1,0,1,1,1,1,1,1,0,1,
1,0,1,0,1,0,1,1,1,1,
1,1,1,0,1,0,1,0,0,1,
1,0,0,1,1,1,1,1,0,1,
1,0,1,1,0,0,1,1,0,1,
1,1,0,1,1,1,1,1,0,1,
0,1,0,1,0,1,0,0,0,0,
0,1,0,1,1,0,1,1,1,1,
0,1,0,1,1,1,1,1,0,1,
1,1,0,0,1,0,1,1,0,1,
};

// マップサイズ
static const int WIDTH = 10; // 幅
static const int HEIGHT = 10; // 高さ

// スタート地点
static const int START_X = 0;
static const int START_Y = 0;

// ゴール地点
static const int GOAL_X = WIDTH - 1;
static const int GOAL_Y = HEIGHT - 1;
}

…ちょっとわかりにくいですね。
マップはこんな感じです。

■■■■■■■■■■■■


■■
■■
■■

■■■■■■■
■■
■■
■■
■■■■■■■■■■■■

main.cpp

#include <functional>
#include <iostream>
#include <vector>
#include <queue>
#include <memory>
#include "MapData.h"

//===========================================================================
// ノードクラス
//===========================================================================
class Node
{
public:
//-----------------------------------------------------------
//! @name 初期化
//-----------------------------------------------------------
//@{

Node(int posX, int posY)
: _pParentNode(nullptr)
, _posX(posX)
, _posY(posY)
, _costFromStartNode(0)
, _costToGoalNode(0)
{}

//@}
//-----------------------------------------------------------
//! @name setter/getter
//-----------------------------------------------------------
//@{

Node& SetParentNode(Node* pNode) { _pParentNode = pNode; return *this; }
Node* GetParentNode(void) const { return _pParentNode; }

Node& SetPosX(int posX) { _posX = posX; return *this; }
int GetPosX(void) const { return _posX; }

Node& SetPosY(int posY) { _posY = posY; return *this; }
int GetPosY(void) const { return _posY; }

Node& SetCostFromStartNode(int costFromStartNode) { _costFromStartNode = costFromStartNode; return *this; }
int GetCostFromStartNode(void) const { return _costFromStartNode; }

Node& SetCostToGoalNode(int costToGoalNode) { _costToGoalNode = costToGoalNode; return *this; }
int GetCostToGoalNode(void) const { return _costToGoalNode; }

int GetTotalCost(void) const { return _costFromStartNode + _costToGoalNode; }

//@}
//-----------------------------------------------------------
//! @name operator
//-----------------------------------------------------------
//@{

bool operator == (Node node)
{
return (this->_posX == node._posX && this->_posY == node._posY);
}

void operator = (Node node)
{
this->_pParentNode = node._pParentNode;
this->_posX = node._posX;
this->_posY = node._posY;
this->_costFromStartNode = node._costFromStartNode;
this->_costToGoalNode = node._costToGoalNode;
}

//@}

private:
Node* _pParentNode; // 親ノード
int _posX; // X座標
int _posY; // Y座標
int _costFromStartNode; // スタートノードからの最小コスト
int _costToGoalNode; // ゴールノードまでの最小コスト
};

typedef std::shared_ptr<Node> NodePtr;
typedef std::vector<NodePtr> NodePtrVector;

namespace {
//---------------------------------------------------------------------------
// 壁判定
//! @param [in] posX X座標
//! @param [in] posY Y座標
//! @return 壁ならtrue
//---------------------------------------------------------------------------
bool IsWall(int posX, int posY)
{
if (posX < 0 || Map::WIDTH <= posX) return true;
if (posY < 0 || Map::HEIGHT <= posY) return true;
return (Map::DATA[posX + posY * Map::WIDTH] == 0);
}

//---------------------------------------------------------------------------
// ゴールまでの推定コストを計算
//! @param [in] posX X座標
//! @param [in] posY Y座標
//! @return ゴールまでの距離
//---------------------------------------------------------------------------
int CalcCostToGoalNode(int posX, int posY)
{
return std::abs(Map::GOAL_X - posX) + std::abs(Map::GOAL_Y - posY);
}

//---------------------------------------------------------------------------
// ゴールまでの推定コストを計算
//! @param [in] node ノード
//! @return ゴールまでの距離
//---------------------------------------------------------------------------
int CalcCostToGoalNode(const Node& node)
{
return CalcCostToGoalNode(node.GetPosX(), node.GetPosY());
}
}

//---------------------------------------------------------------------------
// スタートアップ
//---------------------------------------------------------------------------
int main()
{
//------------------------------------------------------------
// 変数定義
//------------------------------------------------------------

NodePtrVector openList;
NodePtrVector closeList;

//------------------------------------------------------------
// ラムダ式を用いた関数定義
//------------------------------------------------------------

// Node位置比較用関数
auto compareNodeByTotalCost = [](NodePtr pNode1, NodePtr pNode2) -> int
{
return pNode1->GetTotalCost() > pNode2->GetTotalCost();
};

// リスト内に含まれているかどうかの判定用関数
auto isInNodeList = [](NodePtrVector& list, const NodePtr& node) -> NodePtr
{
for (NodePtrVector::iterator it = list.begin(); it != list.end(); ++it)
{
NodePtr nodeItem = (*it);
if (*node == *nodeItem)
{
return nodeItem;
}
}
return nullptr;
};

//------------------------------------------------------------
// A*のアルゴリズム
//------------------------------------------------------------

// スタートノード
NodePtr pStartNode(new Node(Map::START_X, Map::START_Y));
int startNodeCostToGoalNode = CalcCostToGoalNode(*pStartNode);
pStartNode->SetCostToGoalNode(startNodeCostToGoalNode);

// ゴールノード
NodePtr pGoalNode(new Node(Map::GOAL_X, Map::GOAL_Y));

// オープンリストから取り出す
openList.push_back(pStartNode);

while (true)
{
// オープンリストが空なら検索失敗
if (openList.empty())
{
std::cout << "探索失敗" << std::endl;
exit(1);
}

// 最小コストのノードをオープンリストから取り出す
std::sort(openList.begin(), openList.end(), compareNodeByTotalCost);
NodePtr pBaseNode = openList.back();
openList.pop_back();

// ゴールノードと一致したら検索終了
if (*pBaseNode == *pGoalNode)
{
*pGoalNode = *pBaseNode;
break;
}

// 取り出したノードをクローズリストに移す
closeList.push_back(pBaseNode);

// 隣接ノードをチェック
// 今回は上下左右のみ
for (int dy = -1; dy <= 1; ++dy)
{
for (int dx = -1; dx <= 1; ++dx)
{
// 同位置判定
if (dx == 0 && dy == 0) continue;

// 斜めを考慮しない
if (dx != 0 && dy != 0) continue;

// 隣接ノード位置
int pAdjacentNodePosX = pBaseNode->GetPosX() + dx;
int pAdjacentNodePosY = pBaseNode->GetPosY() + dy;

// 壁判定
if (IsWall(pAdjacentNodePosX, pAdjacentNodePosY)) continue;

// 隣接ノードの各コスト
int adjacentNodeCostFromStart = pBaseNode->GetCostFromStartNode() + 1; // 親から子への移動コストは1
int adjacentNodeCostToGoalNode = CalcCostToGoalNode(pAdjacentNodePosX, pAdjacentNodePosY);

// 隣接ノード
NodePtr pAdjacentNode(new Node(pAdjacentNodePosX, pAdjacentNodePosY));
pAdjacentNode->SetParentNode(pBaseNode.get())
.SetCostFromStartNode(adjacentNodeCostFromStart)
.SetCostToGoalNode(adjacentNodeCostToGoalNode);

NodePtr pSearchedNode = nullptr;

// オープンリストに含まれているか
pSearchedNode = isInNodeList(openList, pAdjacentNode);
if (pSearchedNode)
{
// オープンリストにあったノードより隣接ノードのコストが小さければ、オープンリストのノードを上書き
if (pAdjacentNode->GetTotalCost() < pSearchedNode->GetTotalCost())
{
*pSearchedNode = *pAdjacentNode;
}
continue;
}

// クローズリストに含まれているか
pSearchedNode = isInNodeList(closeList, pAdjacentNode);
if (pSearchedNode)
{
// クローズリストにあったノードより隣接ノードのコストが小さければ、クローズリストから削除してオープンリストに追加
if (pAdjacentNode->GetTotalCost() < pSearchedNode->GetTotalCost())
{
std::remove(closeList.begin(), closeList.end(), pSearchedNode);
openList.push_back(pAdjacentNode);
}
continue;
}

// どちらにも含まれていなければオープンリストに追加
openList.push_back(pAdjacentNode);
}
}
}

//------------------------------------------------------------
// 結果
//------------------------------------------------------------

// ゴールノードから親ノードを辿ることで、スタートノードまでの経路が算出される
Node* pNode = pGoalNode.get();
while (true)
{
std::cout << "X:" << pNode->GetPosX() << ", Y:" << pNode->GetPosY() << std::endl;

if ((pNode = pNode->GetParentNode()) == nullptr)
{
break;
}
}
}

結果は以下の通りになりました。
無事に最短経路を通ることができてますね手(チョキ)

■■■■■■■■■■■■
■*■***
■*■*■*■
■***■*■■■
■■**
*■■
■*
■■■*■■■■■■
■■■****■
■■■*****■*■
■■■*■
■■■■■■■■■■■■


それではまた次回に晴れ

posted by 管理人 at 14:03 | プログラミング

2013年04月16日

ネイティブコードとマネージドコードを同時に利用する方法

こんにちは。店長ですわーい(嬉しい顔)
暑い日と寒い日が繰り返してますが、
皆様は体調を崩してないでしょうか?

今日は、
ネイティブコードとマネージドコードを同時に使うテクニック
を紹介致します。

例えば、
C++でゲーム用の描画ライブラリを作ったあと、
”この描画ライブラリを使ってグラフィカルなツール作りたいなぁ・・・”
と思ったことは無いでしょうか?
そして実践してみると、
・WIN32APIが使いにくい。
・もっとリッチなGUIを使いたい。
・ツール部分だけはC#使いたい。

など頭を悩ませた人も少なくないと思いますもうやだ〜(悲しい顔)

そういう時に役に立つかもしれないテクニックです手(チョキ)
あんまり詳細なこと書くと大変量になるので、要点以外省くことをご了承ください。

例として
・Windows環境
・VisualStudioでの開発
・C++で描画ライブラリ(graphics.dll)を作成(ネイティブコード)
・C#とxamlでクライアントウィンドウ部分を作成(マネージドコード)
・描画結果は青い背景と白い四角形のポリゴン

をやってみます。


C++描画ライブラリ側の設定
1,dllを作る
2,C#から機能を使うための関数を用意する
3,モジュール定義ファイル(.def)を用意する


1,ついて
ライブラリとして出力する形式をdllにしてください。

2,について
以下のような描画ライブラリのヘッダファイルがあり、
その機能をC#側で使ってみようと思います。

/**
* @brief レンダラークラス
*/
class CRenderer
{
public:
// コンストラクタ
CRenderer();
// デストラクタ
~CRenderer();

// 初期化
bool Initialize();
// 終了処理
void Finalize();

// 活性化
bool Activate();
// 非活性
void Deactivate();

// ウィンドウの設定
void SetWindow( HWND i_wnd );
// 描画
void Render();

private:
HWND m_hwnd; // ウィンドウ
HDC m_hdc; // デバイスコンテキスト
HGLRC m_hglrc;
};


ところが、このままではC#からC++クラス内の関数を使うことができません。

そこで、このクラスの機能を呼び出すための関数を用意いたします。
ヘッダファイル

#ifdef __cplusplus
extern "C"
{
#endif __cplusplus

// インスタンスの生成
CRenderer* NewRenderer();

// インスタンスの削除
void DeleteRenderer(CRenderer* p);

// 初期化
bool InitializeRenderer(CRenderer* p);

// 終了処理
void FinalizeRenderer(CRenderer* p);

// ウィンドウの設定
void SetWindowRenderer(CRenderer* p,HWND i_hwnd);

// 描画処理
void RenderRenderer(CRenderer* p);

#ifdef __cplusplus
}
#endif __cplusplus


ソースファイル

CRenderer* NewRenderer(){
return new CRenderer();
}

void DeleteRenderer(CRenderer* p){
delete p;
}

bool InitializeRenderer(CRenderer* p){
return p->Initialize();
}

void FinalizeRenderer(CRenderer* p){
p->Finalize();
}

void SetWindowRenderer(CRenderer* p,HWND i_hwnd) {
p->SetWindow( i_hwnd );
}

void RenderRenderer(CRenderer* p) {
p->Render();
}

機能としてはクラスのインスタンスを渡して、
各関数を呼び出すだけです。
このようにクッションを置くことで
C#側から呼び出すことができるようになります。
手(グー)

3,について
dllの関数をエクスポートするためのモジュール定義ファイルを用意します。

; graphics.def : DLL のモジュール パラメータを宣言します。

LIBRARY "graphics"

EXPORTS
; 明示的なエクスポートはここへ記述できます

NewRenderer ;インスタンス生成
DeleteRenderer ;インスタンス破棄

InitializeRenderer ;初期化
FinalizeRenderer ;終了

SetWindowRenderer ;ウィンドウの設定

RenderRenderer ;描画


__declspecキーワードを使う方法でも同じ事ができますが、
個人的な好みで別ファイルとしております。

このファイルをプロジェクトのプロパティ→リンカー→入力→モジュール定義ファイル
に設定しておきます。拡張子は.defです。

これでビルドが完了したら、C++ライブラリ側の準備は完了ですグッド(上向き矢印)


C#クライアントウィンドウ側の準備
1,C++のCRendererの機能を呼び出す用意
2,ウィンドウのハンドルを設定


1,について
まず、graphiscs.dllの関数を呼び出せるようにします。
C#側のCRendererクラス

/**
* レンダラー
*/
class CRenderer
{
[DllImport("./dll/graphics.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr NewRenderer();
[DllImport("./dll/graphics.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void DeleteRenderer(IntPtr p);
[DllImport("./dll/graphics.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern bool InitializeRenderer(IntPtr p);
[DllImport("./dll/graphics.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void FinalizeRenderer(IntPtr p);
[DllImport("./dll/graphics.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void SetWindowRenderer(IntPtr p, IntPtr hwnd );
[DllImport("./dll/graphics.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void RenderRenderer(IntPtr p);
// コンストラクタ
public CRenderer()
{
}

// 生成
public void New()
{
m_renderer = NewRenderer();
}

// 破棄
public void Delete()
{
DeleteRenderer( m_renderer );
}

// 初期化
public bool Initialize()
{
return InitializeRenderer( m_renderer );
}

// 終了
public void Finalize()
{
FinalizeRenderer(m_renderer);
}

// ウィンドウの設定
public void SetWindow(IntPtr hwnd)
{
SetWindowRenderer(m_renderer, hwnd);
}

// ウィンドウの設定
public void Render()
{
RenderRenderer(m_renderer);
}

private IntPtr m_renderer = IntPtr.Zero;
}


2,について
描画を行うウィンドウのハンドルを取得し、CRendererクラスに設定します。
タイマーイベントを使っての描画の呼び出しも例として記載しておきます。
ウィンドウのサイズなどの定義はMainWindow.xamlに記載されているものとします。

// MainWindow.xaml の相互作用ロジック
public partial class MainWindow : Window
{
private CRenderer renderer;
private DispatcherTimer timer;

// コンストラクタ
public MainWindow()
{
InitializeComponent();
}

// 初期化処理
private void WindowSourceInitialized(object sender, EventArgs e)
{
// ウィンドウハンドル取得
IntPtr hWnd = new WindowInteropHelper(this).Handle;

// レンダラーの生成
renderer = new CRenderer();
renderer.New();
renderer.SetWindow(hWnd);
renderer.Initialize();

//タイマー
timer = new DispatcherTimer();
timer.Tick += new EventHandler(DispatcherTimerTick);
timer.Interval = new TimeSpan(0, 0, 0, 0, 16); // 16msecで描画
timer.Start();
}

// タイマーイベント
private void DispatcherTimerTick(object sender, EventArgs e)
{
if (renderer != null)
renderer.Render();
}

// サイズ変更処理
private void WindowSizeChanged(object sender, SizeChangedEventArgs e)
{
// if (renderer != null)
// renderer.SizeChanged(e.NewSize);
}

// 後片付け
private void WindowClosed(object sender, EventArgs e)
{
renderer.Finalize();
renderer.Delete();

if (renderer != null)
// {
// renderer.Dispose();
renderer = null;
// }
}
}


WindowSourceInitialized関数内で
・ウィンドウハンドルの取得。
・CRendererクラスの準備。
・タイマーの準備を行なっております。

また、描画はタイマーイベントで、
DispatcherTimerTick関数内で行なっております。


一応MainWindow.xamlも記載しておきます。








今回はあまり大事な部分ではありませんが、
このファイルを編集するだけで簡単にウィンドウの設定ができます。


これでC#側も準備が整いましたグッド(上向き矢印)


実行結果は以下のようになります。

20130416_native_and_managed.jpg

上記のような手順を踏むことで、
C++などのネイティブコードで作ったライブラリを利用しつつ、
マネージドコードの強みを生かしてGUIを作ることなどができます。


最後に、
現在でも速度が求められるコアな部分はネイティブコードが主流です。
ただ、マネージドコードの強みを利用することでツールなどの開発効率は非常に良くなります。
プログラム言語も適材適所で使っていけるようになるとことが大事ですねるんるん

上記サンプルの実行ファイルとdllファイルを置いておきますので、おためしください。
サンプル



posted by 管理人 at 16:21 | プログラミング