2014年12月28日

やってしまった

zlib を使ったコードを Visual C++ で書いて Xcode でテストしていたのですが、謎のエラーに悩まされました。Visual C++ で実行しているときは何もないのですが、Xcode で実行すると落ちるんです。やっとのことで原因をみつけだしてみると...


uint32_t destsize = ...
...
int zerr = ::uncompress(destbuf, (uLongf *)&destsize, sourcebuf, sourcesize);

とかやっていたのが原因でした。問題は (uLongf *)&size の部分です。uLongf は zlib で typedef されている型で、実体は unsigned long です。

Visual C++ だと uint32_t も long も同じ 32 ビットだったのですが、Xcode だと long は 64 ビットなんですよね…

たしかに int とか long は環境によってサイズが異なる、というのはCの常識としてあったわけだけですが、いつの間にかそういう常識も忘れてしまうという話でした。ああ、うん、安易にキャストするものじゃないですよね。ハイ。

(なんか昔 wchar_t のサイズが 2 バイトだと思い込んでいたせいで困ったことになった記憶があるのですが、その教訓は生かされなかったようです)
posted by JUNOSOFT at 03:26| Comment(0) | プログラミング

2014年12月17日

フルスクリーンでのデバッグ中にフリーズした場合の復帰

パソコン1台、モニタ1台の環境で問題になるのが、フルスクリーンゲームのデバッグです。
フルスクリーンでゲーム実行中にブレークポイントに差し掛かったり、フリーズしたりすると、わりとどうしようもないことになります。
デバッガの画面を見たいけとフルスクリーン状態でフリーズしたウィンドウが邪魔で見れない、
デバッグをあきらめ、強制終了しようとしてタスクマネージャを起動しても、タスクマネージャの画面すら隠れてしまうと。
そんなときはもうWindowsを再起動するか、サインアウトするしかなかったのですが、最近少しだけましな方法を発見しました。
Windows8.1限定かもしれませんが、フルスクリーンでフリーズした状態で Ctrl+Alt+Del でメニューを出し、「ユーザー切り替え」を選択し、現在利用中のユーザーを改めて選ぶとデスクトップ画面に復帰することができます。

たとえばフルスクリーン時に 640x480 の解像度にしていた場合、デスクトップ左上の 640x480 の部分は引き続きゲームによって占領されていますが、それ以外の領域は普通に使うことができます。
そのため、エラーダイアログをチェックするなり、デバッガから強制終了するなり、どうにか操作することができます(ただし、フルスクリーン時の解像度がデスクトップ解像度に近い場合、やはり画面の大部分が操作不能のまま残ることになりそうですが…)。
ただ、この方法も100%うまくいくわけではなく、フリーズ内容によっては引き続き画面全体が操作不可能な状態になります

まあ、それでもだいぶ助かりますけどね。
posted by JUNOSOFT at 15:30| Comment(0) | プログラミング

2014年11月26日

yamlをluaのテーブルに変換したい

自分が作っているアプリでは、設定ファイルなどにはだいたい xml を使っていたのですが、いいかげん xml のタイプ量の多さが嫌になり、yaml を使うようになりました。

そこで libyaml を使って yaml が読み込めるようにしたのですが、なんだか xml のときと同じ作業をしているような気がしてなりません。
xml のパーサには TinyXml を使っていますが、その時もどぶじぇくとツリーに気軽にアクセスができるよう、自前のヘルパー関数を作っていました。
libyaml でも全く同じ作業をしています。libyaml はシーケンスを返すだけなので、どのノードがどのノードの子になっているとか親になっているとか、そういうのは自分で解析しないといけません。

そんなわけで、ある時期からは設定ファイル自体を lua 形式で書くようにしました。これが非常に使い勝手が良くて、ただ値をつらつらと並べるほかにも、規則性のあるデータが続くときは for 文で機械的にデータを作り出すこともできます。

こうなると、xml は lua でパースして lua テーブルに置き換え、yaml も lua でパースして lua テーブルに置き換えたくなります。
そうすれば xml だろうが yaml だろうが C からは lua テーブルとしてアクセスすればいいだけです。
そんなわけで適当にググって発見した luaXml と luaYaml の使い勝手を試してみようかなと考えているところです。
posted by JUNOSOFT at 11:18| Comment(0) | プログラミング

2014年10月22日

C#ツールバーのDockをTopにしてもClientで隠される

C# でフォームデザインしているときに地味に困ったのが以下の現象です。

・フォーム上にツールバーを配置する
・ツールバーの Dock を Top に設定してフォームの上に貼りつくようにする
・ツールバーの下側に適当なパネルを配置する
・パネルの Dock を Client に設定する

すると... !!
なんと ツールバーの存在がガン無視されて、パネルがツールバーの裏側にまで回り込んでしまうのです!当然パネルの上端はツールバーによって隠されます。
すごい地味な嫌がらせですよこれは。
仕方がないので Dock で設定せずに Ancher だけでそれっぽいことをやっていたのですが、やっぱり気持ち悪いです。
というわけで調べてみたところ、パネルとツールバーの優先順位を入れ替えればよいという、ただそれだけのことでした。

Docking with ToolStrip and Panel
http://stackoverflow.com/questions/17069256/docking-with-toolstrip-and-panel

[表示]→[その他のウィンドウ]→[ドキュメントアウトライン] と辿っていくと、GUIコントロールのツリー構造を編集できるウィンドウが表示されます。
ここを見ると、ツールバーのほうがパネルよりも上に位置していると思うので、これをドラッグしてツールバーのほうが下側に来るようにします。
この状態で Dock を設定すると、みごとに問題解決します。
posted by JUNOSOFT at 00:43| Comment(0) | プログラミング

2014年10月20日

RGBとアルファ値で別々の合成式を使う

漠然と不思議に思っていたんです。Diretxでアルファ合成したときに、合成後のアルファ値はどうなっているのだろうかと。
普通に考えれば不透明のターゲットに対して半透明の画像をアルファ合成したとき、合成後のアルファ値は不透明なままになるのが自然なのですが、アルファ合成の式を

device->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
device->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA);

と設定しているのにアルファ値だけ計算方法が異なるというのもおかしな話です。となると、アルファ値も上記の式に従っているはずなのですが…。
しかし合成後のアルファ値というのは使う機会がなく、別にどんな値でも最終的に描画される画像にも影響しないために特に追求することなく放置していました。

ところが合成語のアルファ値を使う機会が出てきてしまったのです。そう、レンダーターゲットへの描画です。
不透明画像があるレンダーターゲットに対して半透明画像をアルファ合成してから、そのレンダーターゲットを描画すると、半透明画像の部分だけアルファ値がおかしくなります。

アルファ部分だけ別の合成式にする設定さえあれば…と考えたところでふと思い出したのですが、そういえば以前 OpenGL のマニュアルで Separate blend ナントカって関数を見たなと。
それをみた当時はさっぱり意味が分からず、glBlendFunc 以外にも合成関数があったのかぐらいに思っていたのですが、考えてみたら名前がすごくそれっぽいです。
そこで OpenGL オンラインマニュアルの glBlendFunc を開き、関連項目を見てみると、glBlendFuncSeparate というのがありました。まさに今、欲している機能です。

http://www.opengl.org/sdk/docs/man2/xhtml/glBlendFuncSeparate.xml

というわけで、この glBlendFuncSeparate 相当の機能を Direct3D9 で探してみようと google 先生に聞いてみたところ、D3DRS_SEPARATEALPHABLENDENABLE という単語が引っかかりました。
で、最終的に以下のページに行きつき、目的の関数と設定を得ることができました

http://msdn.microsoft.com/ja-jp/library/bb147328(v=vs.85).aspx
http://marupeke296.com/cgi-bin/cbbs/cbbs.cgi?mode=al2&namber=1423&rev=&no=0&P=R&KLOG=2

これでRGBとAで異なる合成関数を適用してみたところ、見事にアルファがらみの問題がなくなりました。
めでたし、めでたしです。

しかしまさか OpenGL のマニュアルに助けられるとは。あれを思い出さなかったら、調べるのにもっと時間がかかったかもしれません。

posted by JUNOSOFT at 08:59| Comment(0) | プログラミング

2014年09月29日

FLTK2.0

コントロールのドッキングができる FLTK Dock (http://fltkdock.sourceforge.net/) を使ってみたくて入手したのですが、どうやら FLTK 2.0 専用のようで、FLTK 1.3 ではコンパイルできません。
そこで FLTK 2.0 を持ってきてビルドしようとしたのですが、ライブラリをビルドする時に苦労したのでメモです。

まず FLTK 2 はサブバージョンのリポジトリから直接もってくる必要があるので、svn をインストールします。自分は Tortoise SVN を使いました。説明文によれば、ユーザー名とパスワードは両方とも "ENTER" と入れればよいそうです。
FLTK のリポジトリをチェックアウトしたら ide/visualc/ にある Visual C++ のファイルを開きます。
いくつかプロジェクトが含まれていますが、そのうちの "fltk" が fltk2 のスタティックライブラリのビルド用で、"fltk_gl" が OpenGL と組み合わせる時に使うライブラリです。これをビルドし、できあがったライブラリをリンクすれば良いのですが、そう素直には行きません。

ここで作ったライブラリを自分のプロジェクトからリンクして使おうとすると、elete_associations_for の実体がないというリンカエラーが発生します。
この実体、実は src/win32/WidgetAssociation.cxx にあるのですが、このソースファイルは元のfltk プロジェクトファイルには含まれておらず、#include “win32/WidgetAssociation.cxx”という感じで run.cxx から直接インクルードされています。
ソースファイルを連結しているだけなので問題ないように見えるのですが、なぜかダメみたいです。
というわけで、win32/WidgetAssociation.cxx を fltk プロジェクトに追加し、再びビルドします。

これでリンカエラーが出ずに、FLTK2 が使えるようになりました。

そして当初の目的であった FLTK Dock を使おうとしたら、なんと古いバージョンの FLTK2 用らしく、いろいろなところでコンパイルエラーが。
一気にモチベーションがさがり、それっきり FLTK を触っていません…。
posted by JUNOSOFT at 05:03| Comment(0) | プログラミング

2014年09月04日

FLTK

必要最低限の機能を押さえていて、インストール&ビルドが楽、ということで最近 FLTK 1.3 をテストしています。
ボタンやツリービュー、メニュー、タブなど一通りのコントロールがそろっていて、なによりも無改造で日本語に対応している点がよいですね!
フォントを変更する必要すらありません。FLTK は文字列を utf-8 で扱っているので(wchar_t ではなく、マルチバイト文字列であることに注意)、それさえ気を付ければ簡単に日本語を表示する事ができます。

まず普通にアルファベットを表示したりするときは

Fl_Window *wnd = new Fl_Window(0, 0, 320, 240, “HELLO”);

のようにします。日本語で表示させたい時はまず UTF8 に変換する必要がありますから、

std::string utf8(const std::string &s);

とかいう適当な変換関数を用意して、

Fl_Window *wnd = new Fl_Window(0, 0, 320, 240, utf8(“ウィンドウ”).c_str());

のようにすればいいです…といいたいところですが、これはうまく行きません。
Fl_Window をはじめとして FLTK のコントロールはコンストラクタで渡された文字列ポインタをそのまま保持しているみたいです。
そのため文字列の実体がずっと存在することが前提となっているんですね。
ところが上記のような指定方法だと、文字列の実体は初期化式を抜けた時に破棄されてしまい、ウィンドウのタイトルとして不正な文字列が使われてしまいます。

これを回避するには、単純に static な文字列を指定するか、あるいは文字列引数を省略して(コンストラクタでの文字列指定はオプション引数になっている)
とりあえず生成してしまい、あとから copy_label でタイトルを指定する必要があります。

Fl_Window *wnd = new Fl_Window(0, 0, 320, 240);
wnd->copy_label(utf8(“ウィンドウ”).c_str());


コンストラクタ以外でタイトルを指定するには label() と copy_label() があるのですが、
label() だとコンストラクタで指定するのと同じで文字列のポインタがそのまま保持されるだけになってしまい、ダメです。
copy_label() ならその名の通り文字列のコピーを保持するようになるため、安心して扱う事が出来ます

ちなみに 文字コード変換の関数はこんな感じで作れます

static std::wstring ToWide(int codepage, const std::string &s) {
if (s.empty()) return std::wstring();
int len = MultiByteToWideChar(codepage, 0, &s[0], -1, NULL, 0);
len--; // 終端の\0は不要
if (len <= 0) return std::wstring();
std::wstring ws(len, L'\0');
MultiByteToWideChar(codepage, 0, &s[0], -1, &ws[0], ws.length());
return ws;
}

static std::string ToMBSC(int codepage, const std::wstring &ws) {
if (ws.empty()) return std::string();
int len = WideCharToMultiByte(codepage, 0, &ws[0], -1, NULL, 0, NULL, NULL);
len--; // 終端の\0は不要
if (len <= 0) return std::string();
std::string s(len, '\0');
WideCharToMultiByte(codepage, 0, &ws[0], -1, &s[0], s.length(), NULL, NULL);
return s;
}

std::string utf8(const std::string &s) {
const int SJIS = 932;
std::wstring ws = ToWide(SJIS, s)
return ToMBSC(CP_UTF8, ws);
}

FLTK 2.0 もためしてみようかな


posted by JUNOSOFT at 06:38| Comment(0) | プログラミング

2014年08月15日

エディットコンテニュが効かない時

VC++ 2012 (Express) で、特定のプロジェクトだけエディットコンテニュが効かない現象に悩まされました。
一時停止中に適当に編集してから続行しても、再コンパイルする気配を全く見せず、なにかメッセージが出るわけでもなく、何事もなかったかのように再開します。
当然編集内容は反映されていません。

以前から使っているプロジェクトや、新規作成した状態プロジェクトではちゃんとできるので、なにか設定が悪いはず。
いろいろ考え、もしかしたら、と思って静的リンクしているライブラリを外してみたところ、見事にエディットコンテニュできるようになりました。

どうやらリンクしているライブラリに、リリースモードでビルドされていたものが混ざっていたようです。
というわけでライブラリのソースを持ってきてデバッグモードで自前ビルドし、そのライブラリにリンクするようにしたところ、うまくいきました。
無駄な時間を過ごしてしまった…
posted by JUNOSOFT at 23:49| Comment(0) | プログラミング

2014年08月12日

minmax

WinDef.h に min マクロと max マクロがあって、ちょくちょく使っていたのですがこれって std::min std::max と思い切り衝突するんですよね...
どうせなテンプレート版の min/max を使いたいのですが、そうすると Visual C++ のコンパイラに怒られるのであきらめていました。
ところが、よくよく見たら

#ifndef NOMINMAX

なんてスイッチがあるではないですか。WinDef.h をインクルードする (Windows.h から間接インクルードしている場合がほとんどだと思う)
前にこれを define しておけば無効化されるようです。


しかし min max が定義済みってのは少しやりにくいですね。あと、rad1 が定義済みなのも地味に痛いんですよ。
ラジアン値を入れるのに一時変数名で rad = GetAngle(A) みたいに使ったりするので。角度値を複数入れようとし rad0 = GetAngle(A); rad1 = GetAngle(B) とかやろうとするとアウトです。


posted by JUNOSOFT at 05:42| Comment(0) | プログラミング

2014年06月10日

文字列定数


Lua スクリプトなり HLSL なり、短いコードを外部ファイルに依存せずに動かしたい時には

 const char *lua_script = "function add(a, b) return a+b; end; function sub(a, b) return a-b; end";

などと書くわけですが、これだと見にくいので無理やり改行して

 const char *script = " \
  function add(a, b) return a+b; end \
  function sub(a, b) return a-b; end \
 ";

とかやっていたのですが、行の最後をエスケープするのて結構面倒なんですよね。

そこで、最近になって知ったのは #define STR(x) #x を使う方法です。
define の引数を使うときに # をつけると、引数がそのまま引用符でくくられるのですが(assert の警告文とかで使われてますよね)
これをうまく使ってやろうというやつです。つまり

 #define STR(x) #x
 const char *script = STR(
  function add(a, b) return a+b; end
  function sub(a, b) return a-b; end
 );

と書いてやれば、たとえ式の途中で改行していても問題なく引用符の中に入り、文字列定数になるというわけです。便利ですね。
もっと早くに知っていれば...

ちなみに

 #define STR(x) #x
 const char *script = STR(
  function add(a, b) return a+b; end // コメント
  function sub(a, b) return a-b; end // コメント
 );

のように定数内部にコメントを書いた場合、このコメント部分は文字列定数には入りません。しかし、

 #define STR(x) #x
 const char *script = STR(
 #if 1
  function add(a, b) return a+b; end
  function sub(a, b) return a-b; end
 #endif
 );

のようにプリプロセッサ命令を書いた場合は、文字列定数にもそのまま含まれています。
この動作がコンパイラ依存かどうかはわかりませんが、少なくとも MSVC ではそうなってました。

また、ソース上での改行文字はすべて無視されます。全部つながった1行の文字列定数として扱われるため、
HLSLのように改行が関係ないコードだったらよいのですが、lua のように改行が命令の区切りになっている言語では注意が必要です。

Lua の場合、正しく動かすためには改行文字を文字列定数にきちんと含めるか、

 #define STR(x) #x
 const char *script = STR(
  function add(a, b) return a+b; end \n
  function sub(a, b) return a-b; end \n
 );

常に ; をつけて命令の区切りを明示する必要があります

 #define STR(x) #x
 const char *script = STR(
  function add(a, b) return a+b; end;
  function sub(a, b) return a-b; end;
 );

posted by JUNOSOFT at 08:44| Comment(0) | プログラミング

2014年06月06日

GLFW

GLUT にはよくお世話になっているのですが、ウィンドウを閉じても閉じきらなかったり(?)、
マルチウィンドウ非対応とか、ウィンドウハンドルを取得する関数がないとか、不満もいくつかありました。

もう何年もメンテナンスされてないし、これ以上の進化は望めそうにありません。
GLUT の後継ぽくなっている freeglut でもいいんですが、もう少し素直に使えるやつがないかなーと思っていたら、GLFW というものがあるみたいですね。
マルチウィンドウにも対応しているし、ウィンドウハンドルもとれるぽいし、さらに GLUTと違ってウィンドウにポインタを一つ保持させることができるので、
クラスと組み合わせて使うとが簡単にできそうです。次からはこれでテストプログラムを書いてみようと思います。

http://www.glfw.org/docs/latest/index.html
posted by JUNOSOFT at 00:01| Comment(0) | プログラミング

2014年05月18日

win32+

いまさらな感じだと思いますが、最近 Win32++ をよく使っています。

wxWidgets はなんかデカいし、Qt は QtCreator 使わずに VC++ だけでやると面倒くさそうだし、CLR は楽だけどクライアント側に .NET 必須なのがなんとなく嫌だし、C# も C# だけで作るなら楽だけど C++ 資産と一緒に使うと面倒だし。

Win32++ の、余計なことをしないで最低限APIを包み込むだけな感じが大好きです。
これでいいんだよ、これで…
posted by JUNOSOFT at 08:36| Comment(0) | プログラミング

2014年04月11日

ベジェ曲線の微分

さて今回、とあるキャラクターを動かす時に、その移動経路をベジェ曲線で指定するようにしてみました。
移動そのものはサクサクできたのですが、キャラクターが常に進行方向を向くようにするという処理で少々時間がかかってしまいました。
最初に思いついたのは、実際の直前の位置と現在位置から移動量 (dx, dy) を取得し、これを逆三角関数 atan につっこむ方法です。当たり前ですが、これだと移動量が (0, 0) になった時(つまり停止中)に向きを定義する事が出来ません。
なら、移動中だけ向きを更新して停止中は向きを変更しなければよい、ということになります。しかしそれでは生成直後の向きがやっぱり分かりません。もちろん初期角度を決めても良いのですが、手入力した初期角度と実際の移動向きが一致しないと向きがパッと切り替わったように見えてしまいます。
「初期角度は違和感がないように注意して入力してね」で解決しますが、なんだか悪い予感しかしません。

そして「ベジェ曲線といえどもプログラムで描画するときは折れ線で近似しているんだから、その折れ線の向きをそのまま使えば良いじゃないか」という当たり前の結論になったわけですが、この折れ線、描画したものは滑らかに見えても、その向きにキャラクターを自動回転させると、キャラクターの向きがカクカク変わっているように見えてしまいます。

どうしようかなーと考えた時、やっと思いついた(というか真っ先に思いつくべきだった)のですが、そもそもベジェ曲線は媒介変数 t を使った x と y の式であって、知りたいのはその接線の向き(傾き)なのだから、このを微分するだけで問題解決なわけです。

というわけで

0 <= t <= 1
A = (x0, y0)
B = (x1, y1)
C = (x2, y2)
D = (x3, y3)

とおいたとき、ベジェ曲線の式は

P = A (1-t)^3 + 3 B (1-t)^2 t + 3 C (1-t) t^2 + D t^3

これを展開して t についてまとめると

P = (-A + 3B - 3C + D) t^3 + 3(A-2B+C) t^2 + 3(-A + B) t + A

これを t について微分すればよいので

P' = 3(-A + 3B - 3C + D) t^2 + 6(A-2B+C) t + 3(-A + B)

で接線の傾きを求める事が出来ます。
posted by JUNOSOFT at 09:57| Comment(0) | プログラミング

2014年03月20日

AntTweakBar

AntTweakBar というGUIライブラリが使いやすいと某所で聞いたため、試しに入れてみました。
OpenGL でも Direct3D9, 10, 11 でもどれでもOKというのがいいですね。
以下、AntTweakBar をスタティックライブラリでリンクするときのメモです (VisualC++2013 にて)

http://anttweakbar.sourceforge.net/doc/ からソースをダウンロード
・src フォルダ内のプロジェクトファイルを開く
・構成プロパティを変更する
  構成の種類を「スタティックライブラリ(.lib)」に変更
  Debug版ならターゲット名を「$(ProjectName)Static_d」に変更し、Release版なら「$(ProjectName)Static」に変更
  ターゲットの拡張子を「.lib」に変更
  プラットフォームツールセットを「Visual Studio 2013 - WindowsXP (v120_xp) に変更(WindowsXPにも対応させる場合)
  プリプロセッサの定義の TW_EXPORT を TW_STATIC に置き換える
・ビルドする
・出来上がったファイル AntTweakBarStatic_d.lib と AntTweakBarStatic.lib を、ライブラリパスの通っている場所にコピー
  
  
・自分のプロジェクトの設定で、プリプロセッサに TW_STATIC と TW_NO_PRAGMA を追加する
・Debug 版なら AntTweakBarStatic_d.lib にリンクし、Release 版なら AntTweakBarStatic.lib にリンクするようにしておく
  例:
   #ifdef _DEBUG
   # pragma comment(lib, "AntTweakBarStatic_d")
   #else
   # pragma comment(lib, "AntTweakBarStatic")
   #endif

デバッグ用のGUIとしてゲームに組み込んだりして使えそうです

posted by JUNOSOFT at 21:59| Comment(0) | プログラミング

2014年02月25日

複数のウィンドウに対する描画2

以前複数ウィンドウに対する描画について書きましたが、実際に試した結果いくつか間違っていた点がありました。
というわけで、ひとつのデバイスで複数のウィンドウに描画する手順の修正版を以下に示します。

■ 準備する

 HWND hWnd1 = ウィンドウ1
 HWND hWnd2 = ウィンドウ2
 
 // プレゼントパラメータの作成
 D3DPRESENT_PARAMETERS pp1, pp2;
 pp1.hDeviceWindow = hWnd1;
 pp2.hDeviceWindow = hWnd2;
 ... 設定つづく ...

 // 普通にデバイスを作成
 D3D9->CreateDevice(..., &pp1, &Device);

 // ウィンドウ1に描画するために、最初のスワップチェーンを取得
 Device->GetSwapChain(0, &SwapChain1)

 // ウィンドウ2に描画するために、追加のスワップチェーンを作成して取得
 Device->CreateAdditionalSwapChain(&pp2, &SwapChain2);


■ hWnd1 に対して描画するとき

 // スワップチェーン1のバックバッファを描画先にする
 IDirect3DSurface9 *back;
 SwapChain1->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &back);
 Device->SetRenderTarget(0, back);
 back->Release();

 Device->BeginScene();
 // いろいろ描画
 Device->EndScene();

 // hWnd1 に転送
 SwapChain1->Present(NULL, NULL, hWnd1, NULL, 0);


■ hWnd2 に対して描画するとき

 // スワップチェーン2のバックバッファを描画先にする
 IDirect3DSurface9 *back;
 SwapChain2->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &back);
 Device->SetRenderTarget(0, back);
 back->Release();

 Device->BeginScene();
 // いろいろ描画
 Device->EndScene();

 // hWnd2 に転送
 SwapChain2->Present(NULL, NULL, hWnd2, NULL, 0);


これでOKです。
・CreateAdditionalSwapChain で作成したスワップチェーンは、Direct3DDevice9::Reset のリセット前に破棄し、リセット後に再生成する必要があります。
・エラー処理を省略しているので、実際に使う場合は適当にエラーチェックしてください

posted by JUNOSOFT at 17:27| Comment(0) | プログラミング

2014年02月18日

複数のウィンドウに対する描画

これまでキャラクターエディタやマップエディタなど、いろいろなツールを作ってきましたが、
たとえ複数のウィンドウがあった方が便利だな、と思った場合でも、無理矢理1ウィンドウにまとめていました。
なぜなら、複数のウィンドウに対して Direct3D で描画するのが非常に面倒に思えたからです。

複数のウィンドウがあるということは描画先のウィンドウハンドルが複数存在するということで、Direct3Dデバイスの作成時にウィンドウハンドルを渡している以上、ウィンドウハンドル1つにつき Direct3D デバイスが1つ必要なわけです。
デバイスを複数つくるだけならまだしも、テクスチャはデバイスごとに管理されているため、全く同じ画像を表示したい場合でも、デバイスごとに別々のテクスチャを作成しなければなりません。同じ絵を表示するウィンドウが2つあるなら、作成・管理するテクスチャの数も2倍になるということです。

これが非常臭そうに思えたので、Direct3D による描画が必要なツールを作る場合、ウィンドウが1つで済むよう、どうにかやりくりしていました。

.... とか思ってたら、なんか SwapChain つかえばできるよ的な話が聞こえてきました。
調べてみた所、まさに欲しかった機能です。
というわけで、いま描画先として二つのウィンドウハンドル hWnd1, hWnd2 がある場合、それぞれに描画する手順(DirectX9の場合)は:

- hWnd1 を指定した D3DPRESENT_PARAMETERS を用意し、普通に IDirect3D9::CreateDevice して IDirect3DDevice9 *dev を作成
- dev->GetSwapChain で hWnd1 に対応するスワップチェーン IDirect3DSwapChain9 *sc1 を取得
- hWnd2 を指定した D3DPRESENT_PARAMETERS を用意し、dev->CreateAdditionalSwapChain で hWnd2 に対応したスワップチェーン sc2 を作成
- hWnd1 に描画する時は、sc1->GetBackBuffer → dev->SetRenderTarget で描画先をセットして描画し、dev->Present で転送(BeginScene, EndScene忘れずに)
- hWnd2 に描画する時は、sc2->GetBackBuffer → dev->SetRenderTarget で描画先をセットして描画し、dev->Present で転送(BeginScene, EndScene忘れずに)

でいけるみたいですよ?(実は未確認)
posted by JUNOSOFT at 18:32| Comment(0) | プログラミング

2014年02月07日

ラスタライゼーションルール

ラインの端1ピクセルがが描画されないのって、もしかしてコレのせいなんですかね…?

http://msdn.microsoft.com/en-us/library/windows/desktop/bb147314#Point_and_Line_Rules
posted by JUNOSOFT at 21:10| Comment(0) | プログラミング

2014年01月25日

乗算済みアルファ

レンダーターゲットにアルファ付き画像を描画し、それをテクスチャとして別の画面に張り付けると、アルファ合成が二回行われるために透明度が下がってしまう…という問題があります。
これはそういうものだと思って諦め、やらないようにしていたのですが、単に乗算済みアルファ合成を使えばよかったんですね…。

言われてみれば確かにその通りで、簡単に解決する問題でした orz
posted by JUNOSOFT at 11:54| Comment(0) | プログラミング

2014年01月09日

ツリー構造をもったキャラクター

ツリー構造を持ったキャラクターがあるとき、普通に考えると

struct CHARA {
CHARA *ArrayOfChild;
};

のように、親が子を管理することになるのですが、場合によっては子が親を参照していたほうが楽になる場合があります。

struct CHARA {
CHARA *Parent;
};

というのは、親から子をたどっていくと、複数の子を持っている場合はそこで分岐が発生するのですが、子から親を辿って行くのなら分岐は発生しないからです。
キャラクターAがキャラクターBの子孫かどうかを調べるのは大変ですが(世代が離れるほど子孫が増えるので)、逆にキャラクターBがキャラクターAの先祖かどうかを調べるのは、非常に簡単だということです。
さらに、キャラクターごとに子キャラクターの配列を持たせる、なんて必要もなくなり、ただひとつ、親キャラクターへの参照を持つだけで済みます。
当然双方向ではなく子から親への片方向ツリー構造になるため、親から子になにかを伝搬させることは非常に無駄の多い処理になりますが…

例えばキャラクターAの子要素としてキャラクターB,C があり、さらにBの子要素としてDがあり、子要素の座標が親からの相対位置で定義されるような場合、トップダウン方式だとキャラクターAの座標を決め、再起処理などでB,C,Dの位置を決めていくことになります。しかし、もしキャラクター B, C, D が、たまにしか必要のない要素だとしたら?
毎回これらツリーのすべての要素の座標を求めるのは無駄です。そういうときはむしろボトムアップで座標を決めて行きます。Bの座標が必要になったら、その親であるAの座標を見る。まだ決定されていなかったら、そこでAの座標を決定する。Aの座標が決定したらBの座標も決まる。この時点でAとBの座標が分かります。つぎにCの座標が必要になったなら、その親であるAの座標を問い合わせます。Aの座標は既に求まっているので、計算せずにただAの座標を返すだけでOKです。結果的に、その時必要なオブジェクトの座標だけが計算され、その時使われなかったオブジェクトの座標は計算されないままになります。

posted by JUNOSOFT at 13:12| Comment(0) | プログラミング

2014年01月07日

お手軽輪郭線

2Dにしろ3Dにしろ、輪郭線を描画したい!! という需要がまれによくあるんですが、そういう時によく使っている方法です。
やり方は超簡単で、例えば1ピクセル幅の黒い輪郭線を書きたいのなら

位置を(-1, -1)だけずらし、黒で塗りつぶされた物体を描画
位置を(+1, -1)だけずらし、黒で塗りつぶされた物体を描画
位置を(-1, +1)だけずらし、黒で塗りつぶされた物体を描画
位置を(+1, +1)だけずらし、黒で塗りつぶされた物体を描画
本来の位置に、本来の色を使って普通に物体を描画

これだけです。これはポリゴンモデルの輪郭線にも、ドット絵の輪郭線にも使えます。
(ただし、1ピクセル幅の尖った部分を持つ画像などは、その先端部分の輪郭がうまく描画されません。完璧にキレイに描きたい場合は、上記に加えて(-1,0) (+1,0) (0,-1) (0,+1)だけずらした描画が必要になります)

Ace of Wands では、会話用の袋文字を表示するのにこの方法を使いました。

ちなみにこの方法だと、ほとんど重ねた状態で画像を5回描画するため、
物体が半透明だったり、輪郭線だけを半透明にしたい などの場合には対処できません。

まあ、本来ならピクセルシェーダーを活用するべきなのでしょうが、すごくお手軽かつキレイにできるので重宝しています
posted by JUNOSOFT at 12:02| Comment(0) | プログラミング