.NET クラスライブラリヘルプ(HTML)をコマンドラインからサッと作れるようにする
オンラインで公開するためのヘルプの作成を Jenkins とかに引っかけるために調べてみました。
準備
Sandcastle Help File Builder をインストールする
http://shfb.codeplex.com/releases/view/574439
VisualStudio のアドオンとかいろいろ入れるか問われますが、Sandcastle 本体だけでOKです。
プロジェクトファイル (*.shfbproj) を作る
インストールした SandcastleBuilderGUI.exe を起動して、
メニューの [File] > [New Project] から。
出力するヘルプの形式を選択する
真ん中のビューの[Build these help file format]から適当に。
ビルド
ビルドは MSBuild コマンドで行います。
MSBuild /p:Configuration=Release /property:SHFBROOT="D:\Program Files (x86)\EWSoftware\Sandcastle Help File Builder" "プロジェクトファイル.shfbproj"
成功すると "Help" フォルダにヘルプファイルが生成されます。
あとはこのフォルダをサーバに送って index.html にアクセス。無事に表示できました。
NanoVG のソースコードを読んでみた
NanoVG
https://github.com/memononen/nanovg
HTML5 の Canvas API ライクなベクタグラフィックスライブラリ。
「ベクタグラフィックスかっけぇ!どうやってるの?」と思ったのがひとつ。
「今作ってるゲームライブラリにはまともな図形描画機能が無い」と思ったのがもうひとつ。
自前で実装するか、外部ライブラリを組み込むか。
基本的な流れ
① nvgBeginFrame() で一連の描画を開始する。
② nvgBeginPath() で図形ひとつ分の描画を開始する。
③ nvgMoveTo(), nvgLineTo(), nvgRect(), nvgCircle() 等で図形を作る。
④ nvgFillPaint() または nvgStrokeColor() 等でで塗りつぶし方法を指定する。
⑤ nvgFill() または nvgStroke() で描画を行う。
⑥ nvgEndFrame() で一連の描画を終了する。
少し細かく。
② はキャッシュのクリア。一連の処理の中ではメモリ使用量を計っていて、
メモリが足りなくなるたびに realloc() している。そのメモリ使用量を 0 にしている。
ちなみにキャッシュ本体は NVGpathCache。
③ は描画コマンドの生成。nanovg はすぐに描画を行うのではなく、
図形生成に関する処理は先ず一度コマンド化される。
コマンド種別(enum) とパラメータ(座標等) が並ぶ float 配列。
④ は次の描画で使う NVGpaint 構造体の指定。いわゆる「ブラシ」に相当する。
NVGpaint は nvgLinearGradient() 等で生成する。(構造体の初期化だけで malloc とかしないので割とガンガン作ってもOK)
⑤ はプリミティブの構築と、実際の描画を行う。
かなり複雑なので後述。
描画コマンドの作成
こんなかんじ。
コマンドの配列 ・・・ NVGcontext::commands
コマンドの数 ・・・ NVGcontext::ncommands
↓例えば、nvgRoundedRect() が作るコマンドはこう。
float vals[] = { NVG_MOVETO, x, y+ry, // 左上 NVG_LINETO, x, y+h-ry, // 下へ NVG_BEZIERTO, x, y+h-ry*(1-NVG_KAPPA90), x+rx*(1-NVG_KAPPA90), y+h, x+rx, y+h, // 左下のカーブ NVG_LINETO, x+w-rx, y+h, NVG_BEZIERTO, x+w-rx*(1-NVG_KAPPA90), y+h, x+w, y+h-ry*(1-NVG_KAPPA90), x+w, y+h-ry, NVG_LINETO, x+w, y+ry, NVG_BEZIERTO, x+w, y+ry*(1-NVG_KAPPA90), x+w-rx*(1-NVG_KAPPA90), y, x+w-rx, y, NVG_LINETO, x+rx, y, NVG_BEZIERTO, x+rx*(1-NVG_KAPPA90), y, x, y+ry*(1-NVG_KAPPA90), x, y+ry, NVG_CLOSE };
コマンドの追加は nvg__appendCommands() で。
メモリが不足していれば realloc する。
プリミティブの構築と描画
重要なのは以下の関数たち。
nvg__flattenPaths()
nvg__tesselateBezier()
nvg__calculateJoins()
nvg__expandFill()
glnvg__renderFill()
・nvg__flattenPaths()
これは描画コマンドを解析して頂点配列を生成する。
dpiを考慮したベジェ線の生成もここ。
この時点ではまだ線分の太さは考慮していない。
点情報は NVGpoint 構造体で、頂点配列はキャッシュに生成される。
また、ひとつ前の点からの相対座標 NVGpoint::dx,dy も計算している。
・nvg__tesselateBezier()
ベジェ線の生成。nvg__flattenPaths() から呼ばれる。
カーブを構成する 4点を受け取り、間が十分に小さくなるまで再帰で点を作っている。
アルゴリズムまでじっくり見てないけど、入力が点4つだから Catmull-Rom っぽい?
長い曲線を描画しようとすると計算量が跳ね上がりそう。
・nvg__calculateJoins()
各点に対して、ひとつ前の点との位置関係から「押し出し方向 (NVGpoint::dmx,dmy)」を求めている。
これは向きベクトルで、線の太さを乗算すると最終的な頂点位置が求まる仕組み。
・nvg__expandFill()
ここまでで生成した情報を元に、最終的な頂点配列を生成する。
この頂点配列は OpenGL の頂点バッファとして描画される。
また、アンチエイリアス用の Stroke も作っている。
これは NVGparams::edgeAntiAlias が 1 のときに行われる処理で、
詳しくは読んでいないがおそらく透明度を落とした細い Stroke を図形の外周に沿って生成しているのだと思う。
・glnvg__renderFill() (OpenGL ドライバ)
必要なデータは既にそろっているので、頂点バッファや
シェーダの Uniform 変数にセットして描画するだけ。
塗りつぶしについて
nvgLinearGradient() や nvgRadialGradient() で作られる NVGpaint なナニモノなのか。
最初全然わからなかったけど、どうやらこれはフラグメントシェーダにてあるピクセルを innerColor と outerColor の色座標系(いい言葉が思いつかないけど) にトランスフォームする座標変換行列(+補助情報) みたいです。
シェーダのコードは nanovg_gl2.h の fillFragShader 変数に入ってるのが読みやすいかも。
ピクセル座標を 0.0 ~ 1.0 に変換して、
innerColor ~ outerColor を線形補間してる。
NanoVG でできないこと
一度の nvgFill() では、WPF の GradientStop みたいに複数の色でグラデーションを作るのは無理みたい。
https://msdn.microsoft.com/ja-jp/library/system.windows.media.lineargradientbrush%28v=vs.110%29.aspx
複数回に分けて描画することになる。
DirectX で動かすには
必要なドライバ関数は NVGparams 構造体にまとまっている。
これらを DirectX 用に実装することになるが、そんなに数は多くない。
struct NVGparams { int (*renderCreate)(void* uptr); int (*renderCreateTexture)(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data); int (*renderDeleteTexture)(void* uptr, int image); int (*renderUpdateTexture)(void* uptr, int image, int x, int y, int w, int h, const unsigned char* data); int (*renderGetTextureSize)(void* uptr, int image, int* w, int* h); void (*renderViewport)(void* uptr, int width, int height); void (*renderCancel)(void* uptr); void (*renderFlush)(void* uptr); void (*renderFill)(void* uptr, NVGpaint* paint, NVGscissor* scissor, float fringe, const float* bounds, const NVGpath* paths, int npaths); void (*renderStroke)(void* uptr, NVGpaint* paint, NVGscissor* scissor, float fringe, float strokeWidth, const NVGpath* paths, int npaths); void (*renderTriangles)(void* uptr, NVGpaint* paint, NVGscissor* scissor, const NVGvertex* verts, int nverts); void (*renderDelete)(void* uptr); };
DXライブラリで、もっと線形代数する 2 ~ フライトシミュレータ
フライトシミュレータが作れるっていうのはね、
僕ら3Dゲーム制作で線形代数を嗜んだ者にとって
ひとつの大きな目標であると思うんだ。
⋀⋀ / ̄|
_/(・ω・)/☆ |
!/ .} ̄ ̄ ̄ /
i\_}/ ̄|__/≡=
` ̄ ̄
クォータニオンベースのフライトシミュレータを作ってみました。
自作ライブラリを使ってますので、参考にする際はご注意ください。
プロジェクト一式
ソース
#include <DxLib.h> #include <LuminoMathDxLib.h> using namespace Lumino; int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { ChangeWindowMode(TRUE); if (DxLib_Init() == -1) return -1; int playerModel = MV1LoadModel("Plane1.x"); int skyModel = MV1LoadModel("BG_Sky1.x"); int groundModel = MV1LoadModel("Ground1.x"); SetCameraNearFar(1.0f, 10000.0f); SetupCamera_Perspective(Math::PI / 2); Quaternion playerRot; // 自機の回転情報 Vector3 playerPos; // 自機の位置 float velocity = 5.0f; // 自機の速度 float rotVelocity = 0.02f; // 自機の回転速度 // メインループ while (ProcessMessage() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0) { //----------------------------------------------------- // 自機の姿勢更新 // ↓ キー if (CheckHitKey(KEY_INPUT_DOWN) == 1) { // ピッチは自機が原点にいるとすると、X軸周り、つまり右手方向を軸とした回転と考えることができる。 // X 軸を表すベクトルは (1, 0, 0) で、Vector3::UnitX 定数がこれを表す。 // このベクトルを自機の現在の回転姿勢 (playerRot) で座標変換すると、 // 現在の姿勢から見た右手方向がわかる。これを axis とする。 Vector3 axis = Vector3::Transform(Vector3::UnitX, playerRot); // 得られた右手方向を軸として、-rotVelocity (ラジアン単位) 分、 // 現在の回転姿勢をさらに回転させる。 playerRot.RotateAxis(axis, -rotVelocity); } // ↑ キー if (CheckHitKey(KEY_INPUT_UP) == 1) { Vector3 axis = Vector3::Transform(Vector3::UnitX, playerRot); playerRot.RotateAxis(axis, rotVelocity); } // ← キー if (CheckHitKey(KEY_INPUT_LEFT) == 1) { // ロールは自機が原点にあるとすると、Z軸周りの回転となる。 // Z軸を表すベクトルは (0, 0, 1) で、Vector3::UnitZ 定数がこれを表す。 // 後はピッチの時と同じ。 Vector3 axis = Vector3::Transform(Vector3::UnitZ, playerRot); playerRot.RotateAxis(axis, rotVelocity); } // → キー if (CheckHitKey(KEY_INPUT_RIGHT) == 1) { Vector3 axis = Vector3::Transform(Vector3::UnitZ, playerRot); playerRot.RotateAxis(axis, -rotVelocity); } // 自機を前進させる。 // まずロールと同じ方法で正面方向を求める。 // これは単位ベクトルで、速度を掛けることで自機の位置をどれだけ進めればよいかがわかる。 Vector3 front = Vector3::Transform(Vector3::UnitZ, playerRot); playerPos += front * velocity; //----------------------------------------------------- // カメラの姿勢更新 // カメラの位置は自機から 5.0 だけ後ろに離れたところにしたい。 // 自機のロールと同じ方法で、背面 -5.0 の位置を求めている。 Vector3 back = Vector3::Transform(Vector3(0, 0, -5.0f), playerRot); Vector3 cameraPos = playerPos + back; // 上方向もこれまでと同じく座標変換で求める。 // 元の方向は (0, 1, 0) で、Vector3::UnitY 定数がこれを表す。 Vector3 cameraUp = Vector3::Transform(Vector3::UnitY, playerRot); // 必要な情報が揃った。カメラの姿勢を設定する SetCameraPositionAndTargetAndUpVec(cameraPos, playerPos, cameraUp); //----------------------------------------------------- // 描画処理 ClearDrawScreen(); // 背景球は、地形がめり込んで見えないように ZWrite を無効にして描画する MV1SetPosition(skyModel, playerPos); MV1SetWriteZBuffer(skyModel, FALSE); MV1DrawModel(skyModel); // 地面の描画 (半透明) MV1SetWireFrameDrawFlag(groundModel, FALSE); MV1SetOpacityRate(groundModel, 0.2f); MV1DrawModel(groundModel); // 地面の描画 (ワイヤーフレーム) MV1SetWireFrameDrawFlag(groundModel, TRUE); MV1SetOpacityRate(groundModel, 1.0f); MV1DrawModel(groundModel); // クォータニオンと位置ベクトルからワールド座標変換行列を求め、自機の姿勢として設定する Matrix mat = Matrix::AffineTransformation( Vector3::One, // 拡大率は (1, 1, 1) Vector3::Zero, // 回転の原点はモデルの原点 playerRot, // 回転情報 playerPos); // 位置 MV1SetMatrix(playerModel, mat); MV1DrawModel(playerModel); // [おまけ] 光源の方向を向いたときに画面の輝度を上げる。 // 光源の向きと自機の向きで内積を取ると、互いに完全に向き合うときは -1.0 を、垂直であれば 0.0 が返る。 // ここでは -0.5 ~ -1.0 の間で徐々に明るくしている。 Vector3 ligntDir(1, -1, -1); ligntDir.Normalize(); float dot = Vector3::Dot(ligntDir, mat.GetFront()); if (dot < -0.5f) { SetDrawBlendMode(DX_BLENDMODE_ALPHA, (int)(255 * (-(dot + 0.5f)))); DrawBox(0, 0, 640, 480, GetColor(255, 255, 255), TRUE); } // info SetDrawBlendMode(DX_BLENDMODE_ALPHA, 255); DrawString(0, 0, "← → キーでロール", GetColor(100, 100, 100)); DrawString(0, 16, "↑ ↓ キーでピッチ", GetColor(100, 100, 100)); ScreenFlip(); } DxLib_End(); return 0; }
簡単に解説
ポイントはオイラー角を使わず、3Dモデルに MV1SetMatrix する直前で、
クォータニオンを行列に変換してセットすること。
これはオイラー角のジンバルロックを避けるためです。
回転するとき、例えば上を仰ぎたい (ピッチを操作する) 時は、
X軸で回転するのではなく「今の姿勢から見た右方向」を軸にして回転します。
// playerRot は現在の自機の回転姿勢 (Quaternion) // 右方向 (回転軸) を求める Vector3 axis = Vector3::Transform(Vector3(1, 0, 0), playerRot); // 右方向を軸にして、現在の姿勢から 0.05 (ラジアン) 回転する playerRot.RotateAxis(axis, 0.05);
自機の向いている方向に移動する時は、回転と同じ方法で「正面方向」を求めます。
正面方向に速度を乗算すると、進むべき量がわかりますので、
これを現在の自機の位置に加算すると、進ませた後の位置になります。
// playerPos は現在の自機の位置 (Vector3) // 正面方向を求める Vector3 front = Vector3::Transform(Vector3(0, 0, 1), playerRot); // 速度 5.0 で前進させる playerPos += front * 5.0;
最後に Matrix::AffineTransformation で
回転姿勢と位置を取りまとめてひとつの行列を作っています。
Matrix mat = Matrix::AffineTransformation( Vector3::(1, 1, 1), // 拡大率 Vector3::(0, 0, 0), // 回転の原点はモデルの原点 playerRot, // 回転情報 playerPos); // 位置 MV1SetMatrix(playerModel, mat);
DXライブラリで、もっと線形代数する ~ Lumino.Math を DXライブラリ で使う
前回の記事で紹介した Lumino.Math を DXライブラリ用にパッケージ化しました。
DXライブラリでカメラの向いてる方に移動したいとか、
クォータニオンするとか、視錐台カリングするとか、IKするとか、
いろいろなことがやり易くなるかなと。
準備
ダウンロードした ZIP の中にある「プロジェクトに追加すべきファイル_VC用」
フォルダの内容を、お使いのDXライブラリの
「プロジェクトに追加すべきファイル_VC用」フォルダに上書きしてください。
使い方
ソースファイルには "LuminoMathDxLib.h" を include してください。
次のように、"DxLib.h" の後に include する必要があります。
#include <DxLib.h> #include <LuminoMathDxLib.h> using namespace Lumino; // 全てのクラスは Lumino 名前空間に入っています
DXライブラリでは VGet() にてベクトルを作りますが、
Lumino.Math ではコンストラクタを使用します。
VECTOR dxVec = VGet(100.0f, 200.0f, 400.0f); // DXライブラリ Vector3 vec(100.0f, 200.0f, 400.0f); // Lumino.Math
Vector3 クラスと Matrix クラスは、DXライブラリの VECTOR、MATRIX へ
直接代入できます。
Vector3 vec(1, 2, 3); VECTOR dxVec = vec;
直接 DXライブラリの関数に渡すこともできます。
Matrix mat = Matrix::RotationYawPitchRoll(0.0, 0.5, 1.0); MV1SetMatrix(model, mat);
DX ライブラリを使用した3Dモデルビューアのサンプル
マウスでぐりぐり動かせるモデルビューアです。
回転にはクォータニオンを使用しています。
#include <DxLib.h> #include <LuminoMathDxLib.h> using namespace Lumino; int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { ChangeWindowMode(TRUE); if (DxLib_Init() == -1) return -1; SetCameraNearFar(1.0f, 150.0f); int model = MV1LoadModel(/*モデルファイル名*/); Quaternion modelRot; // モデルの回転姿勢 int lastMouseX = -1; // マウスドラッグ量を調べるための変数 (前回のクリック位置) int lastMouseY = -1; // マウスドラッグ量を調べるための変数 (前回のクリック位置) float cameraZ = -5.0f; // カメラの前後位置 // メインループ while (ProcessMessage() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0) { //----------------------------------------------------- // マウス移動量から回転姿勢を更新 int mouseState = GetMouseInput(); if (mouseState & MOUSE_INPUT_LEFT) { int mouseX, mouseY; GetMousePoint(&mouseX, &mouseY); if (lastMouseX >= 0) { // 前回のマウスX座標との差分を角度として、現在の姿勢をY軸周りで回転する modelRot.RotateY(0.01f * (lastMouseX - mouseX)); } if (lastMouseY >= 0) { // 前回のマウスY座標との差分を角度として、現在の姿勢をX軸周りで回転する modelRot.RotateX(0.01f * (lastMouseY - mouseY)); } lastMouseX = mouseX; lastMouseY = mouseY; } else { // マウスドラッグ解除 lastMouseX = -1; lastMouseY = -1; } // カメラ位置はマウスホイールの移動量から適当に設定 cameraZ += GetMouseWheelRotVol(); //----------------------------------------------------- // 描画処理 ClearDrawScreen(); // カメラ位置の設定 SetCameraPositionAndTargetAndUpVec( VGet(0, 0, cameraZ), VGet(0, 0, 0), VGet(0, 1, 0)); // クォータニオン(回転姿勢)を行列に変換してモデルに設定、描画する Matrix modelMat = Matrix::RotationQuaternion(modelRot); MV1SetMatrix(model, modelMat); MV1DrawModel(model); // info DrawString(0, 0, "マウス左ドラッグでモデルを回転します。", GetColor(255, 255, 255)); DrawString(0, 16, "マウスホイールでカメラを前後に移動します。", GetColor(255, 255, 255)); ScreenFlip(); } DxLib_End(); return 0; }
C++ 用 3D グラフィックアプリ向けの数学ライブラリ Lumino.Math
ゲーム等の3Dグラフィックアプリ開発では、ベクトルや行列等の線形代数は避けて通れません。
DirectX はヘルパー関数が充実していますが、それでも IK や視錐台カリングとかやりだすと微妙に足りなかったり、
OpenGL や DXライブラリを使おうとすると大半を自分で書き上げなければならなかったりといろいろ不満が残ります。
さて、実は世の中にはすでにいくつか有名な数学ライブラリが存在します。
しかし、実際のところゲーム向けにはややオーバースペック気味で、
インターフェイスも "ゲーム寄り" ではなく汎用的に作られた
ホントの "数学寄り" です。
というところで、DXライブラリ や DirectX に慣れ親しんだ人が使いやすい
数学ライブラリが欲しいなと思い、Lumino.Math を公開しました。
使用例
// ヘッダファイルと名前空間 #include <LuminoMath.h> using namespace Lumino; // 全てのクラスは Lumino 名前空間に入っています
// ベクトルを正規化する Vector3 v1(10, 20, 0); Vector3 v2 = Vector3::Normalize(v2); // 正規化した新しいベクトルを作る v1.Normalize(); // 自分自身を正規化する
// ワールド・ビュー・プロジェクション行列を作る Matrix world; world = Matrix::Scaling(2.0f); // 拡縮率 world *= Matrix::RotationAxis(Vector3(0, 1, 0), 0.25f); // 任意の軸を中心に回転する world *= Matrix::Translation(10, 20, 0); // 平行移動量 Matrix view = Matrix::LookAtLH( Vector3(10, 20, 30), // 視点の位置 Vector3(0, 0, 0), // 注視点 Vector3(0, 1, 0)); // 上方向 Matrix proj = Matrix::PerspectiveFovLH( 0.25f, // 視野角 640.0f / 480.0f, // アスペクト比 1, // 最近ビュー平面 1000); // 最遠ビュー平面 Matrix wvp = world * view * proj;
// 3D ベクトルを行列で座標変換する Vector3 pos1(10, 20, 30); Vector3 pos2 = Vector3::TransformCoord(pos1, wvp);
// オイラー角・回転行列・クォータニオンの相互変換 Matrix m1 = Matrix::RotationYawPitchRoll(0.1f, 0.2f, 0.3f); Vector3 angles = Matrix::ToEulerAngles(m1); Quaternion q1 = Quaternion::RotationMatrix(m1); Matrix m2 = Matrix::RotationQuaternion(q1); Quaternion q2 = Quaternion::RotationYawPitchRoll(0.1f, 0.2f, 0.3f); Vector3 angles = Quaternion::ToEulerAngles(q2);
// エルミートスプライン補間 Vector3 posStart(10, 0, 0); // 開始位置 Vector3 velStart(0.1f, 0, 0); // 開始時の速度 Vector3 posEnd(0, 10, 0); // 目的位置 Vector3 velEnd(0, 0, 0); // 目的位置に着いたときの速度 Vector3 pos = Vector3::Hermite(posStart, velStart, posEnd, velEnd, 0.5f); // ちょうど半分の時間のときの位置を返す
// 球面線形補間 Quaternion quaStart = RotationAxis(Vector3(0, 1, 0), 0.1f); Quaternion quaEnd = RotationAxis(Vector3(0, 0, 1), 0.2f); Quaternion q = Quaternion::Slerp(quaStart, quaEnd, 0.5f);
CMake を使って複数のバージョンの VisualC++ をターゲットとしたライブラリのビルドを自動化する
VisualC++ でスタティックライブラリを使うときは、ライブラリを作る側と使う側のバージョンを合わせておく必要があります。
(厳密にはランタイム関数を使う場合に合わせておく必要がある。例えば STL を使ったライブラリを作ったなんていうとき)
つまり、VisualStudio2008 以降を対象として、ビルド済みのバイナリを配布したいというときは、
対応するすべてのソリューションファイルやプロジェクトファイルを作る必要があります。
例えば、以下のプロジェクトファイルから成るプロジェクトがあるとします。
・ライブラリ本体
・単体テスト
・サンプル
対象のバージョンは、VS2008、2010、2012、2013、(2015)。
プレビュー版の2015を含めても 5バージョン×3プロジェクトファイル で、計15個の .vcproj や .vcxproj を管理する必要があります。
仕事でなければ 2008 はもう切りたいところなんだけどね。
一時は全部手動で管理しようとしたのですが、2008 ⇔ 2010 の "継続的な" 同期だけでも手を焼きました。
というか、作業は単純なのですが非常にめんどくさい。
これを全部、管理するのはあまりにも非効率で胃とか痛くなっちゃう原因になりかねません。
やりたいこと
- VS2008 ~ 2013 までのプロジェクトファイルの生成
- 生成したプロジェクトファイルのビルド
- 単体テストの実行
- ひとつのバッチファイルを叩くだけでこれらを自動化
準備
- プロジェクトファイルを作れる CMakeLists.txt を用意しておく。
- 対象のVisualStudio が全てインストールされていること。(多分 Express でも大丈夫)
- VisualStudio のツールセットをロードするバッチファイルのパスを確認。(vcvarsall.bat や VsDevCmd.bat)
- MSBuild のパスを確認。
vcvarsall.bat や VsDevCmd.bat のパスは、スタートメニューにある VisualStudio フォルダ内の「開発者コマンドプロンプト」みたいな名前のショートカットのプロパティから確認できます。
少し厄介なのは MSBuild で、.NET のパッケージに入っているものや VisualStudio に付属しているもの等、色々なところに存在しています。
ググって最初に見つかるのは "C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" あたりかな。
しかし、私の環境ではこの exe で VS2010 がビルドできませんでした。
ということで、使うのはこちらの exe → "C:\Program Files\MSBuild\12.0\Bin"
このあたりが参考になるかも → http://lalupin4.hatenablog.jp/entry/2014/01/03/003156
コマンドラインから CMake で VisualStudio プロジェクトを作る
cmake -G "ジェネレータ名"
"ジェネレータ名" には "Visual Studio 10" 等を入れます。
どんなものが使えるのかは本家ヘルプを参照。
http://www.cmake.org/cmake/help/v2.8.8/cmake.html#opt:-Ggenerator-name
※上記ヘルプにはありませんが、VS2013 を使いたいときは "Visual Studio 12" とします。
MSBuild でビルドする
MSBuild を実行する前に、現在のコマンドプロンプトに VisualStudio のツールセットをロードする必要があります。
と言っても、vcvarsall.bat または VsDevCmd.bat を実行するだけ。
call VsDevCmd.bat
ロードしたら、MSBuild を実行します。
Debug 版と Release 版 両方作りたいので、以下のように2回実行します。
MSBuild "XXXX.sln" /p:Configuration=Debug MSBuild "XXXX.sln" /p:Configuration=Release
単体テストを実行する
ビルドして出来た exe を実行するだけです。
テストに失敗したら終了コードを 0 以外の値にしておき、エラー処理へジャンプさせています。
"Debug\UnitTest.exe" if "ERRORLEVEL" LEQ "0" goto UNITTEST_FAILED "Release\UnitTest.exe" if "ERRORLEVEL" LEQ "0" goto UNITTEST_FAILED
まとめと今後
ということで、cmake の -G オプションと MSBuild のパスあたりに気をつければ何とかできます。
記事にすると前書きの割りにあっさりしてます。
ただ、できればレジストリからインストール済みのVisualStudioのパスなりMSBuildのパスなりを取ってこれればベストなんだけど、
まずは自分の家で胃を痛めずにデプロイできればいいかなと。
今回のノリで実際に作ったプロジェクトはこちらに置いてあります。
https://github.com/lriki/Lumino.Math
WPFでドッキングウィンドウ(AvalonDock) 使い方 その2 - AvalonDockのアーキテクチャ
AvalonDock2.0 のソースコードを実際に追ってみました。
さて、前回の最後の例のように、ドキュメント1つと、その右にドッキングウィンドウを持つウィンドウのXAML 要素は以下のような感じになります。
・DockingManager
・LayoutRoot
・LayoutPanel
・LayoutDocumentPane
・LayoutDocument
・LayoutAnchorablePane
・LayoutAnchorable
ここで 「なるほど、じゃあ LayoutAnchorable が Window コントロールだな」と思って、例えばウィンドウタイトルのテキストにデータバインドをかけようとして
<LayoutAnchorable Title={Binding Name}>
なんて書いても、バインドされません。
クラス定義を追ってみるとわかりますが、Layout~ 要素は FrameworkElement ではありません。DataContext を持たないため、バインドできません。
これらは UI要素ではなく、静的な階層構造(つまり、初期状態)を定義するただのデータクラスです。(AvalonDock 内では LayoutElement というクラスのサブクラスです)
じゃあ Window コントロールはどこ?ウィンドウタイトルにバインドしたいときはどうするの?
AvalonDock の構成要素
AvalonDock の中核は以下の3つの要素で構成されています。
・LayoutElement
・LayoutItem
・LayoutControl
・LayoutElement
Document や Anchorable 等の静的な位置関係・階層構造を定義するためのデータクラスです。
通常、XAML には LayoutElement を記述します。
↑で例に挙げた XAML 要素も全て LayoutElement のサブクラスです。
・LayoutItem
DockingManager は LayoutElement の構造を元に、LayoutItem を生成します。
LayoutItem ドッキング要素が表示するべき情報を握っているオブジェクトです。
・タイトル文字列
・アクティブであるか
・閉じることができるか
・要素内部に表示するコントロール
等々…
ドッキングウィンドウコントロールに対する ViewModel とみなすことができます。
また、FrameworkElement を継承しており、バインドターゲットにすることができます。
例えばドッキングウィンドウのタイトル文字列にバインドしたい場合は LayoutItem.Title プロパティにバインドすることになります。
LayoutItem は、元になる LayoutElement と対になるインスタンスがつくられます。
LayoutDocument → LayoutDocumentItem
LayoutAnchorable → LayoutAnchorableItem
なお、LayoutItem は動的に生成されるため XAML から直接アクセスすることはできません。
LayoutItem のプロパティにバインドするときは Style や Template を駆使することになります。
・LayoutControl (ILayoutControl)
DockingManager は LayoutItem の状態を元に、LayoutControl を生成します。
LayoutControl は LayoutItem に対する View です。
LayoutItem の状態 (ドッキングしているか、していないか等) によって様々なコントロールが作られます。
LayoutDocument
フローティング中 → LayoutDocumentFloatingWindowControl (Window を継承)
ドッキング中 → LayoutDocumentPaneControl (TabControl を継承)
※あくまでイメージ。実際はさらに細かく子コントロールが作られ、かなり複雑です。
このため、LayoutControl の寿命は保証できません。
これは普通のListViewItem 等と同じですね。
いきなり AvalonDock のソースコードに突撃してもその複雑さに面くらいますが、
こうして一歩引いて眺めてみると割とシンプルで、ちゃんとMVVMに則っています。
非 MVVM アプリでできるのはここまでです
ここまでで LayoutItem が表示に必用な情報を握っていることがわかったので、
「じゃあ LayoutItem の Style を 作って、Title プロパティにバインドすれば行けるのでは?」とか小細工にトライしたくなるのですが、LayoutItem は UI ツリーからは見えないところにいるので無理です。
コードビハインドでガシガシ潜っていく処理を書けば何とかたどり着けはします。
しかし、AvalonDockに限らずそういう回りくどいコードを書くときはライブラリの使い方を間違っていることが多いです。こと MVVM パターンが絡む問題については。
取得したつもりの View が実はまだ作成されていなかった等、何が起こるかわかりませんので危険です。
ということで、次からは MVVM アプリとしての実装に移っていきます。