2009年2月28日土曜日

日経強いなー

8000円より下での動きで日経の強さを実感するのもアレだが。

しかしこれ、いくらNYと連動せずに真面目に平均線まで反発したとは言っても円安を受けてのものだろうからなあ。市場の主体性が確立できているわけではない。このままアメリカを無視して自力で経済を上向かせて、輸出依存の体制も改善して、本来のセオリーどおりの株高・円高の形になるのは・・・一世紀くらい先?ていうかそんなの出来るの?

むしろ、一時的に見られた円安・株安というパターンがそのうち定着して、ずるずると日本経済が凋落という未来の方があり得そうだ。

2009年2月27日金曜日

三角錐のエッジを鋭くした

WPFの法線って、(面ではなく)頂点毎に指定されて、共有される頂点に対して何も指定しないと、面から計算した法線を自動的に平均化するのね。

というわけで、頂点を共有しないようにしたら、より三角錐らしくなった。
以前:

以後:

ミクさん:

コードも更新した。

2009年2月26日木曜日

継承される事が前提のクラスに、自分が何者か教える方法

スキップリストのライブラリを作る時、ノードクラスには、ユーザがカスタムして使う事を前提にバーチャルメソッドをいくつか用意した。この時、スキップリストのコンテナクラスは、アイテムを引数として受け取り、ノードを生成してリストに追加するメソッドを持つ。問題は、この、ノードの生成という処理はノードクラスの静的メソッドであるべきだという事だ。すると、ノードクラスの基底クラスに、型パラメタとして、``自分が何者であるかを教えてやる''必要が生じる。少し変態的なコードになるが、面白いテクニックなのでまとめておく。

まず、スキップリストのコンテナクラスはこんな感じだ。

public class SkipList<TItem, TNode>
where TItem:class
where TNode:SkipListNode<TItem, TNode>, new()
{
TNode header, footer;
public SkipList()
{
header=SkipListNode<TItem, TNode>.CreateHeader();
footer=SkipListNode<TItem, TNode>.CreateFooter();
...
}
public void Insert(TItem item){
...
TNode newNode=SkipListNode<TItem, TNode>.CreateNode(item);
}
}

んで、ノードクラスはこんな感じだ。

public class SkipListNode<TItem, TNode>
where TItem:class
where TNode:SkipListNode<TItem, TNode>, new()
{
private TNode thisNode;
private TItem item;
private bool isHeader, isFooter;
public static TNode CreateNode(TItem item)
{
TNode newNode=new TNode();
newNode.thisNode=newnode;
newNode.item=Item;
newNode.isHeader=false;
newNode.isFooter=false;
return newNode;
}
public static TNode CreateHeader()
{
TNode newNode=new TNode();
newNode.thisNode=newnode;
newNode.item=null;
newNode.isHeader=true;
newNode.isFooter=false;
return newNode;
}
public static TNode CreateFooter()
{
TNode newNode=new TNode();
newNode.thisNode=newnode;
newNode.item=null;
newNode.isHeader=false;
newNode.isFooter=true;
return newNode;
}
...
他のメソッド(TNode型での自身への参照が欲しければthisでなくthisNodeフィールドを使う)
...
バーチャルなメソッドとかバーチャルなプロパティとか
}

使い方としては、

MyNode:SkipListNode<MyItem, MyNode>{
オーバーライドしたりプロパティを追加したり
}
...
クラスの定義の中で
SkipList<MyItem, MyNode> myList=new SkipList<MyItem, MyNode>();
...

などと使う。

特に、SkipListNode<TItem, TNode>の定義中でthisと書くとその型はSkipListNode<TItem, TNode>型になり、TNode型にならないので、TNode型のthisNodeフィールドを用意する事で、実行時の余計なキャストが必要なくなる。さりげないがここ超重要。

私が知った事

物事は難易度に従ってシーケンシャルに並んでいるものではない。合理的であるか非合理的であるか、可能であるか、不可能であるか、二値の命題によって特徴付けられる。その真理値があまりに人と食い違うとコミュニケーションがとても困難になり、自分の人生、自分の選択について人に説明できないという事態が生じる。そのために、便宜的に難易度というシーケンシャルなパラメタが社会/コミュニティによって導入される。

問題になっているのが因果関係ならば自由意志によって対処できる。問題になっているのが統計であるのならば逃れる事はできない。しかし人は自分の人生が上手く行っている限りはあまねく人は自由意志によって行動を選択すると考えたがる傾向を持つ(例えば先日の麻生総理の医療費問題に関する発言)。

それでも私には、未だ出来る事はあるし、自由意志を用いる余地も存在する。人のキャパシティとは、かくも大きく、そして小さい。

2009年2月25日水曜日

股関節がいてー

雨が久しぶりに上がったので、無理に外出して、帰ってきて座って休憩して、そんでもって立ち上がったら、足に力が入らず、歩くのにすげー気合が必要だった。

眠気も強烈だし微熱は37.2度あって顔がほてるし喉はいがらっぽいしで、もうどうしてくれようか、状態。

その後、脱力感・倦怠感みたいなのは少しだけ(まあ元々常時持続しているわけだが)楽になってきたけど、その代わり股関節がすげー痛てえ。


薬の処方が変わってから、昼夜逆転気味のリズムが戻ってしまったので、それは良くない方向への変化だとは思うが、おかげで夜の時間帯には頭は少しだけすっきりしている。でも今日は外出したので、夜になってから筋肉痛がすごい。それと食後も微熱は引かないが、そもそも37.2度~37.4度の、微熱と胸を張って言えない謎な度合いの微熱。でも顔はほてってぼおっとする。

2009年2月24日火曜日

クオータニオンまとめ

クオータニオン関連のエントリ(1,2,3,4)がカオスなのでここにまとめる。代数的アプローチでなく、2x2複素行列の部分線形空間によるモデルを用いる具体的方法なので、全てを具体的に計算して確かめる事ができる。複素行列の転置共役を^*と書く。

SU(2)={U|UU^*=I, det(U)=1}の生成元を求める、すなわち、(I+εu)(I+εu)^*=I, det(I+εu)=1が一次近似で満たされるためのUの条件を求める。それは、u=-u^*かつTr(u)=0。このようなuの全体は、

J=[i, 0]
[0,-i]

K=[ 0,1]
[-1,0]

L=[0,i]
[i,0]

で張られる実数係数線形空間となる。イメージとしてはSU(2)のIから``遠い''要素をIから``近い''要素I+xJ+yK+zL(x,y,zは小さい)の複数個の積によって作る事ができる。唯一つの生成元xJ+yK+zLのみを用いた積(I+ε(xJ+yK+zL))^(t/εくらいの整数)は、極限として、xJ+yK+zLの指数関数exp(t(xJ+yK+zL))を与える。xJ+yK+zLは有限次元でしかも対角化可能だから、指数関数と言う場合、対角化に基づく定義でもテイラー級数による定義でも良い。

あるいは、exp(t(xJ+yK+zL))という表式は生成元により次々とSU(2)の要素を生成していった結果に加法的にパラメタ付けした結果としてシンボリックなものと捉えても良い。つまりそれはexp(0(xJ+yK+zL))=I, (I+ε(xJ+yK+zL))exp(t(xJ+yK+zL))=exp((t+ε)(xJ+yK+zL))で特徴付けられる。この特徴づけから、極限に関する議論をうっちゃれば、exp(t(xJ+yK+zL))=(I+ε(xJ+yK+zL))^(t/εくらいの整数)が分かるので、指数法則exp(t(xJ+yK+zL))exp(s(xJ+yK+zL))=exp((t+s)(xJ+yK+zL))が得られ、指数関数のシンボルを使う事は正当化される。

なお、逆にSU(2)の要素も対角化可能なので、必ず、適当な生成元の指数関数で書けると分かる。


J^2=K^2=L^2=-I, JK=Lなどにより、I, J, K, Lが張る実数係数線形空間Mの中で積は閉じる。Mの要素をクオータニオンと呼ぼう。Mは、積について閉じているからとりあえずOが零でIが単位元である代数だ。

Mが積について閉じている事から直ちに次の事が言える。(I+ε(xJ+yK+zL))^(t/ε)の展開項、あるいはexp(t(xJ+yK+zL))のテイラー展開項のいずれもI, J, K, Lの種々の積に実数を乗じた形になっているために、exp(t(xJ+yK+zL))はI, J, K, Lの実数係数の線形結合で書ける。つまり、SU(2)はMに含まれる。

#Mは有限次元線形空間の部分空間だから当然閉じているので、収束する級数の部分和がその上にあれば、極限もその上にある。

Mが転置共役についても閉じている事、Mの要素たるクオータニオンとその転置共役との積が(wI+xJ+yK+zL)(wI+xJ+yK+zL)^*=(w^2+x^2+y^2+z^2)Iという風に数ベクトルとしての通常のノルムの二乗をIに乗じた結果を与える事に気が付けば、零行列でないクオータニオンに対して逆行列を作る公式を簡単に書け、しかもその結果はMの中に入る。Mは体であり、wI+xJ+yK+zLに対する共役をwI-xJ-yK-zLと、ノルムの二乗をw^2+x^2+y^2+z^2と定義すれば、逆数は共役/ノルムの二乗で与えられる。


exp(t(xJ+yK+zL))=a0(t)I+a1(t)J+a2(t)K+a3(t)L

と書いてSU(2)の生成元の指数関数のクオータニオンとしての形を具体的に求めよう。素朴なイメージとしては、(I+δt(xJ+yK+zL))を用いて、パラメタtをδtだけ進めていく事で、単位行列から近いexp(t(xJ+yK+zL))から次第に遠いexp(t(xJ+yK+zL))まで次々と求めていく事ができる。このイメージの数学的な対応は

(I+δt(xJ+yK+zL))(a0(t)I+a1(t)I+a2(t)J+a3(t)K)
=a0(t+δt)I+a1(t+δt)J+a2(t+δt)K+a3(t+δt)L ... (1)

から導かれる微分方程式を、a0(0)=1, a1(0)=a2(0)=a3(0)=0の下で解く事だ。

以下(x,y,z)をベクトルωで表し、(a1(t),a2(t),a3(t))をベクトルα(t)で表す事にする(ベクトルを明示するうまい方法がないのでここでだけギリシャ文字はベクトルを表すことにする)。しばしばtをオミットする。

(1)式から得られる解くべき微分方程式は

da0/dt=-ω・α...(2)
dα/dt=a0ω+ω×α...(3)

だ。左辺にそれぞれa0, a1, a2, a3を乗じて足しあげると0となる。これはa0^2+a1^2+a2^2+a3^2が不変である事を表す。t=0ではa0=1で他は0だから、連続性も考慮して、a0=sqrt(1-|α|^2)と書ける事が分かる。後はαの各成分だけ求めれば良い。αの微分方程式は今や

dα/dt=sqrt(1-|α|^2)ω+ω×α ... (4)

である。左からωを外積すると右辺第一項が消えて

d(ω×α)/dt=ω×(ω×α)...(5)

右辺はω×αに直交するから、|ω×α|は保存する。その値は初期条件から0である。とすれば、ω×αは零ベクトルである。この結果は保存則二つ分の情報を齎す。すなわち、αはωに比例し、解かれるべきパラメタとして比例係数のみが残る。α=f(t)ωとおくと、解かれるべき微分方程式は

f(0)=0,
df/dt=sqrt(1-f^2|ω|^2)...(6)

である。解はf(t)=|ω|^(-1)sin|ω|tである。結局、

exp(t(xJ+yK+zL))=cos(sqrt(x^2+y^2+z^2)t)
+sin(sqrt(x^2+y^2+z^2)t)(xJ+yK+zL)/sqrt(x^2+y^2+z^2)...(7)

が得られる。これはノルム1のクオータニオンで、逆にノルム1のクオータニオンは必ずこの形に書ける。つまりSU(2)の要素はノルム1のクオータニオンで逆もまたしかりだ。


最後に、x^2+y^2+z^2=1であるとして、純虚クオータニオンV=aJ+bK+cLを三次元ベクトルvと同一視した時に、(I+ε(xJ+yK+zL))V(I-ε(xJ+yK+zL))が、三次元ベクトルをω=(x,y,z)軸周りにだけ回転させる微小変換である事を示せば良い。つまり、vをv+2εω×vに写す事を言う。これでexp(t/2(xI+yJ+zK))とその共役で三次元空間ベクトルと同一視した純虚クオータニオンを挟む形の変換が三次元空間上の(x,y,z)軸周り角度tの回転変換として用いる事ができると分かる。

#この偶然に見える結果は、SU(2)とSO(3)の群としての2対1の対応関係に因っている。つまり、ベースとなっている事実は、|(x,y,z)|=1とした時SU(2)の要素exp(s(aJ+bK+cL))を((a,b,c)は必ずしも規格化されていなくてよい)別のSU(2)の要素によってユニタリ変換した結果exp(t(xJ+yK+zL))exp(s(aJ+bK+cL))exp(-t(xJ+yK+zL))が、``角速度ベクトル''(a,b,c)を右手系における右ネジ回転を基準として、(x,y,z)を軸として角2tだけ回転させるという事実だ。一般の3次元空間ベクトルも、純虚四元数と対応させてこの事実を流用すれば、回転を実行できるというわけだ。こうしてようやく、ベクトルを回転させる公式がクオータニオンとその共役で純虚クオータニオンを挟む形になっている事の奇妙さが解消される。ベクトルを直接回転しているのではなく、回転変換を別の回転変換とその逆変換で挟む``回転変換の回転変換''において``回転軸が回転される''事実が流用されているのだ。

なお、(x,y,z)が正規化されていない場合、(7)より、exp(t/2(xI+yJ+zK))は「角速度|(x,y,z)|で時間tだけ行った回転」と簡単に表現する事ができる。ただし、.NET Frameworkのクオータニオンのコンストラクタは軸ベクトルを無理やり正規化するので、軸ベクトルの代わりに角速度ベクトルを与えるという使い方はできない。これはMSDNでドキュメント化されていないので注意が必要だ。

2009年2月23日月曜日

微熱

37.2度くらいのびみょーな微熱がずっとある。

以前は、微熱は瞬間最大体温という感じで、むしろ低体温の場合が多かった。最近は寝起きと寝る前は極端な低体温だけど、日中~午後~夜の10時くらいまではずっと37度以上で、たまに36.8度の平熱くらいという感じ。でも、一番多く見るのが37.0度ぴったりで、高くても37.2度って感じで、ほーら微熱だぞー、という感じにはならないんだよなあ。顔がほてったりして集中力の欠如に輪がかかる場合には測ってみると結局平熱(36.8度)だったりするし。

ミクさんで拡張現実でWPFなWMPの視覚エフェクトプラグインの動画


視覚エフェクトの動画を作ってきた

朝から雨で気温も低いけど、37.0のびみょーな微熱とともにいつもと同じく倦怠感、目が腫れぼったい、霞む、集中できない、で家にいても何も手につかない。

居たたまれなくなって午後雨が収まってから無理に外出したらすぐにやばいほど疲れ果ててネカフェで休憩。それでふと思い付いて、PCカメラを借りて、最近製作したミクさんでARな視覚エフェクトのCamStudioによるキャプチャ動画を作ってみた。ネカフェのPCって、ゲームもできるようになっているから性能良いなあ。

ずっと座りながらの作業だったのにネカフェを出る時には足が棒のようで、目も一段と霞んで、歩くのに危険を感じる程だった。

というわけで散々苦労した挙句だが、ネット動画用の素材は揃った。

2009年2月22日日曜日

ね、ねむい・・・

ちゃんと睡眠時間は確保できているのに起きたら強烈に眠い。だるい。でも二度寝しても寝れない・・・

睡眠リズムが整った分、午後まで寝ていれば時間をやり過ごせるというわけにいかなくて却って精神的に辛い。

外国からのアクセスが増えている


いやはや、日本人より日本人じゃない人の方が当然多いわけだけど、ARToolkit+WPFの実験ページを英語で作ったら、いつのまにか海外からのアクセスの方が支配的になってきた。

ただ、Google Sitesって、何故か当のGoogleからのクロールにかかり難いんだよなあ。検索してみると、未だに古いタイトルで出てくる。だから``ARToolkitPlus WPF''で検索にかかりにくい。

2009年2月21日土曜日

ミクさんで拡張現実でWPFなWMPの視覚エフェクトプラグインを更新

更新内容は
1.IWMPEffects, IWMPEffects2をそれぞれ明示的に実装した。これで、過去のWMPでも使えるはず。
2.パラメタを少しいじった。

パラメタをいじったので、レジストリのHKCU/software/tomorrowplusplus/WMPEmitMiku以下を削除してから実行すると良いかも。

ここのWMPEmitMiku.zipです。

自分のPCだとパフォーマンスに難があるし、ネカフェだと.net framework3.5のインストールとかに時間がかかったりで、準備に時間がかかり過ぎる。

誰か、3D性能およびWEBカメラの性能の両方が良い環境の方、うまい音楽を選んでパラメタをうまくいじってCamStudioでのキャプチャ動画を作ってくれませんかねえ。CamStudioならスピーカ出力もキャプチャできるし(ただし何故かうちではできない)。

オイオイ

夜、寝られはするけれど、日中の眠気が強烈で夜になると少しすっきりするっていう、昼夜逆転気味の体内リズムが戻ってきたぞ。薬の処方が変わったせいかなあ。

2009年2月20日金曜日

ミクさんで拡張現実でWPFなWMPの視覚エフェクトプラグインを更新

更新内容は
1.パワーの対数を取るオプションを追加し、デフォルトでオンにした
2.インストールの方法に関する説明で、レジストリファイルの修正が必要な事に言及していなかったので言及した。

ここのWMPEmitMiku.zipです。

体調

今日は朝から体調激悪+微熱だったのだが、午後になってちょっとすっきりしてきた。以前は低体温=眠気で辛かったのだが、最近は微熱が体温調節の狂いというより風邪気味の症状の一環として出てくるので、普通に微熱が引く方が調子が良い。

追記:今日は眠気も風邪気味の症状も強いのに、妙にすっきりしているなあ。

ミクさんで拡張現実でWPFなWMPの視覚エフェクトプラグイン


最低限の形にはなった。指数分布に従って確率的にミクさんを放出し、その時に、帯域毎のパワーに応じて単位時間辺りのミクさん放出数を決めている。パラメタなどいじる余地がまだまだあるし、初速度を帯域のパワーと関連させるのもありだろう。

ここのWMPEmitMiku.zipがソース。バイナリも入っているので、readme-en.txtの指示に従えば遊ぶだけも可。

追記:設定をレジストリで持つようにしたら、平均ミク放出数の最小値と最大値を同じにしてしまって音楽に合わせた変化が生まれなくなってしまった。このバグは修正した。

2009年2月19日木曜日

.netアセンブリをCOMサーバとして登録する場合の注意

GUIDがあるんだから、同じ``名前空間.クラス名''のクラスでも問題ないと思ったら問題大有り。生成されるレジストリエントリは

[HKEY_CLASSES_ROOT\MyEffect]
@="WMPWPFTest.MyEffect"

[HKEY_CLASSES_ROOT\MyEffect\CLSID]
@="{F6ADD2D1-C4C0-4E4A-99DF-49AC5A90D23B}"

[HKEY_CLASSES_ROOT\CLSID\{F6ADD2D1-C4C0-4E4A-99DF-49AC5A90D23B}]
@="WMPWPFTest.MyEffect"

[HKEY_CLASSES_ROOT\CLSID\{F6ADD2D1-C4C0-4E4A-99DF-49AC5A90D23B}\InprocServer32]
@="mscoree.dll"
"ThreadingModel"="Both"
"Class"="WMPWPFTest.MyEffect"
"Assembly"="WMPWPFTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
"RuntimeVersion"="v2.0.50727"
"CodeBase"="file:///C:/Users/Ruke/Documents/Visual Studio 2008/Projects/WMPWPFTest/bin/Release/WMPWPFTest.dll"

[HKEY_CLASSES_ROOT\CLSID\{F6ADD2D1-C4C0-4E4A-99DF-49AC5A90D23B}\InprocServer32\1.0.0.0]
"Class"="WMPWPFTest.MyEffect"
"Assembly"="WMPWPFTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
"RuntimeVersion"="v2.0.50727"
"CodeBase"="file:///C:/Users/Ruke/Documents/Visual Studio 2008/Projects/WMPWPFTest/bin/Release/WMPWPFTest.dll"

と、実際に登録されるのはmscoree.dllだ。こいつが.netアセンブリをロードし、マーシャリングをする仕組みになっているっぽい。そこで、たとえGUIDがちがくとも、同じ``名前空間.クラス名''のクラスを複数登録すると、一つのCOMクライアントからそれらを同時に参照する場合に問題が生じる。多分、厳密な名前とやらを付与しても変わらない(未実験)。

そういうわけで、前のエントリで公開したWMPの視覚エフェクトのスケルトンをいじる時は名前空間やクラス名を変えて、その上でクラスのGUIDも変えるというステップを踏みましょう。

C#+WPFによるWMP VisualizerのスケルトンにIWMPEffects2を実装した

これで、他の視覚エフェクトと行儀良く共存できるようになった。
ダウンロードはこちらの WMPWPFTest.zip です。

C#とWPFによるWMPの視覚エフェクトのサンプル


サンプルコードはここのWMPWPFTEST.zipです。

今週はきわめて調子が悪いので、直ぐにというわけにはいかないけれど、これで、マーカ5つくらいを各周波数帯域に対応させてミクさんを音楽に同期して放出できる。周波数帯域の区切り方は対数で行うべきだろうな。

このコードでは、HwndSourceで作ったウィンドウをWMPの子ウィンドウにし、そのRootVisualを自作のユーザコントロールにしている。しかし、IWMPEffectsではこのウィンドウを破棄するタイミングがない。あくまでオンフレーム描画を念頭においた設計になっている。このために、一度この視覚エフェクトを選んでしまうと、他の視覚エフェクトを選んでもそのまま子ウィンドウが残ってしまう。

この問題は、IWMPEffects2では解決するようだ。CreateメソッドやDestroyメソッドが存在する。これもそのうち試してみよう。

C#+WPFによるWindows mediaplayerの視覚エフェクトのスケルトンができた


ようやっと上手くいった。HDCからウィンドウのHWNDをとり、そこからHwndSourceを作ってRootVisualを設定っていうAdam Nahanさんの方針はうまく行かなかったのは何とも謎。結局HwndSourceをコンストラクタから作ってHDCから取得したHWNDを親ウィンドウに、RootVisualを自作のUserControlにするという方針で上手く動いた。

スケルトンのソースはここのWMPWPFTEST.zipです。

それからCOMインタフェースを定義する方法をまとめておく。
1.IDLの中のinterfaceをlibraryで囲んで、midl /newtlb foo.idlのファイル名、でfoo.tlbができる。次にtlbimp foo.tlbでfoo.dllができる。
2.foo.dllをReflectorで開いて必要な定義をcsファイルにコピーし、HDCなど実質void *である型が_RemotableHandleになっている所をIntPtrに直す。

2009年2月18日水曜日

うわー

微熱が37.4度でずっと続いている。何かぼんやりすると思ったら。

HDCが_RemotableHandleになっちゃう

ので、idlの定義で
HRESULT Render([in] TimedLevel *pLevels, [in] int hdc, [in] RECT *prc);
とし、実際のRenderメソッドの実装で
IntPtr hdc_=(IntPtr)hdc;
としてみた。これ、unsafeコードじゃないんだねえ。

実際のIWMPEffectsはEffectMainPanelってユーザコントロールを作っておいて

[Guid("f6add2d1-c4c0-4e4a-99df-49ac5a90d23b")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("MyEffect")]
public class MyEffect : IWMPEffects
{
[DllImport("user32.dll")]
static extern IntPtr WindowFromDC(IntPtr hDC);
EffectMainPanel effectMainPanel;
IntPtr hwnd = IntPtr.Zero;
public MyEffect()
{
effectMainPanel = new EffectMainPanel();
}
#region IWMPEffects メンバ
...
public void Render(ref tagTimedLevel pLevels, int hdc, ref tagRECT prc)
{
IntPtr hdc_ = (IntPtr)hdc;
HwndSource.FromHwnd(WindowFromDC(hdc_)).RootVisual = effectMainPanel;
effectMainPanel.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
effectMainPanel.VerticalAlignment = System.Windows.VerticalAlignment.Stretch;
}
...
}

としてみた。で、上手くいかねー。もうちょっと調べてみよう。

C#+WPFのWindows Media Player視覚エフェクトのスケルトン

ここにWMPWPFTest.zipの名前で置いておいた。

解決

直前のエントリの問題は解決。
クラスをpublicにしていないだけだった。良かった良かった。

後は、
http://blogs.msdn.com/adam_nathan/archive/2005/10/24/using-com-interop-to-create-a-wpf-visualization-for-windows-media-player.aspx
にある。
Although IWMPEffects has several members, the most important one is Render. Windows Media Player calls your Render method regularly, giving you data about the audio, a handle to the device context (HDC) on which you need to draw, and a rectangle defining the bounds. Since WPF supports interoperability with HWNDs, I first needed to get an HWND from the HDC by PInvoking to WindowFromDC. From there, I could set my UserControl as the RootVisual for the HwndSource and update the UI appropriately. I think the obvious next step is to use data binding, but for now I've left that as an exercise for the reader. :)

に従ってWPFで視覚エフェクトを実装できる。

それから、その視覚エフェクトの登録の仕方は直前のエントリで書いた方法でregasmに作ってもらった.regファイルを(多分管理者権限が必要)登録した後、さらに
http://blogs.wankuma.com/melt/archive/2008/03/12/127425.aspx
に書いてある
この Effects キーのサブキーとしてアプリ名(今回の場合は WmpTest という名前にした)があり、そのサブキーに Property というキーがあります。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MediaPlayer\Objects\Effects\WmpTest\Property

このキーの値として classid というのがあり、ここにプラグイン本体の CLSID を記述します。

WMP はこの CLSID を使って ::CoCreateInstance をするので、CLSID に対応する dll や exe のパスもレジストリに記述しておく必要があります。

もレジストリファイルで用意しておいて登録する。これで万事OKのはず。

ところで、regasmを/regfileオプションで使ったり、HKLM\...\Effects以下への登録もregファイルを用いているのは、開発環境以外でも登録が容易であるようにするため。

なまじIDEに``COM相互運用機能の登録''なんて項目がある物だから、開発環境を使わずに登録する方法が分からずに苦労してしまった。

しかしこの方法で生成されたレジストリファイルには

[HKEY_CLASSES_ROOT\CLSID\{F6ADD2D1-C4C0-4E4A-99DF-49AC5A90D23B}\InprocServer32]
@="mscoree.dll"
"ThreadingModel"="Both"
"Class"="WMPWPFTest.MyEffect"
"Assembly"="WMPWPFTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
"RuntimeVersion"="v2.0.50727"

こんな事が書いてあるんだが、これ、GACに登録しないといけないなんて事ないよな?Assembly=の部分をフルパスにすれば良いんだよな?

いずれにせよ、インストーラは簡単な物を自作しなければいけなそうだなあ。

追記:
regasm /regfile /codebase ビルドされたdll
でフルパスでアセンブリを参照するレジストリファイルが作れる。
そうやって作ったレジストリファイルを管理者権限で登録して、上に書いたようにWindows Media Playerの視覚エフェクトに関するレジストリキーも登録して、コードの方でメソッドにとりあえずMessageBox.Show("Called!")とか書いておいたら、ちゃんと呼ばれた。良かった、良かった。

regasmって再配布してよいのだろうか・・・。

Windows Media Player 10 視覚エフェクトをC#+WPFで作る実験

自力でトライしてみたが、COMについて何も知らないので躓いている。
1.http://msdn.microsoft.com/en-us/windowsmedia/bb190309.aspxからWMP 10のSDKを落とす。
2.SDKのインクルードディレクトリにあるeffects.idlをWmpEffectsLib.idlにコピーして、

[
object,
uuid(D3984C13-C3CB-48e2-8BE5-5168340B4F35),
helpstring("IWMPEffects: Public interface for Windows Media Player SDK."),
pointer_default(unique),
oleautomation
]
interface IWMPEffects : IUnknown
{

から

interface IWMPEffects2 : IWMPEffects
{
HRESULT SetCore( [in] IWMPCore * pPlayer );
HRESULT Create( [in] HWND hwndParent );
HRESULT Destroy();
HRESULT NotifyNewMedia( [in] IWMPMedia * pMedia );
HRESULT OnWindowMessage( [in] UINT msg, [in] WPARAM WParam, [in] LPARAM LParam, [in] LRESULT * plResultParam );
HRESULT RenderWindowed( [in] TimedLevel * pData, [in] BOOL fRequiredRender );
}

までを

[
uuid(d282e51a-5bb9-46c2-aa73-decf29cba6f2),
version(1.0)
]
library WmpEffectsLib{
...中身...
}

で囲む。なお、uuidの生成にはuuidgenまたはguidgenを使う。
2.midl /newtlb WmpEffectsLib.idlしてWmpEffectsLib.tlbを生成
3.tlbimp WmpEffectsLib.tlbしてWmpEffectsLib.dllを生成
4.WmpEffectsLib.idlを見ながらconstやenumの対応する定義をするC#コードを作成。
後有用そうなコメントもコピー。

namespace WmpEffectsLib
{
static class WmpEffectsLibUtils
{
// These flags are to be returned in the GetCapabilities method. They
// indicate to the effects host what the effect supports
public const uint EFFECT_CANGOFULLSCREEN = 0x00000001; // can the effect go full screen?
public const uint EFFECT_HASPROPERTYPAGE = 0x00000002; // does the effect have a property page?
public const uint EFFECT_VARIABLEFREQSTEP = 0x00000004; // should effect return frequency data with variable size steps?
public const uint EFFECT_WINDOWEDONLY = 0x00000008; // can the effect render only in windowed mode?

public const uint EFFECT2_FULLSCREENEXCLUSIVE = 0x00000010;


public const int SA_BUFFER_SIZE = 1024; // number of frequency/waveform samples

enum PlayerState // audio playback states
{
stop_state = 0, // audio is currently stopped
pause_state = 1, // audio is currently paused
play_state = 2 // audio is currently playing
};

// The tagTimedLevel structure is passed to the Render() method of the effect. It holds the frequency,
// waveform and state data need to render the effect.
//
// The first dimension of each array corresponds to the channel: 0-left/mono 1-right(stereo only)
// The second dimension contains the sampled levels. The frequency data ranges
// from 0..255. The wave form data represents -128..127 but is stored as
// 0..255. The state contains the current audio playback state. The time stamp provides
// the relative time of these samples in the audio stream.
}
}

5.MyEffects.csに以下を記述

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WmpEffectsLib;
using System.Runtime.InteropServices;

namespace WMPWPFTest
{
[Guid("f6add2d1-c4c0-4e4a-99df-49ac5a90d23b")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("MyEffect")]
class MyEffect:IWMPEffects
{
#region IWMPEffects メンバ

public void DisplayPropertyPage(ref _RemotableHandle hwndOwner)
{
return;
}

public void GetCapabilities(out uint pdwCapabilities)
{
pdwCapabilities = 0;
}

public void GetCurrentPreset(out int pnPreset)
{
pnPreset = 0;
}

public void GetPresetCount(out int pnPresetCount)
{
pnPresetCount = 1;
}

public void GetPresetTitle(int nPreset, out string bstrPresetTitle)
{
switch (nPreset) {
case 0: bstrPresetTitle = "test";
break;
default:
bstrPresetTitle = "test";
break;
}
}

public void GetTitle(out string bstrTitle)
{
bstrTitle = "WMPEPFTest";
}

public void GoFullscreen(int fFullScreen)
{
return;
}

public void MediaInfo(int lChannelCount, int lSampleRate, string bstrTitle)
{
return;
}

public void Render(ref tagTimedLevel pLevels, ref _RemotableHandle hdc, ref tagRECT prc)
{
return;
}

public void RenderFullScreen(ref tagTimedLevel pLevels)
{
return;
}

public void SetCurrentPreset(int nPreset)
{
return;
}

#endregion
}
}

6.プロジェクトのプロパティのアセンブリ情報で``アセンブリをCOM参照可能にする''をONにする。
7.ビルド
8.regasm /regfile bin/releaseに生成されたdll でHKCR\CLSID\にクラス情報を登録するレジストリファイルを生成

この8.で「登録する型がないため、登録スクリプトは何も作成されません」と非情なお言葉。

もうちょっとCOM参照可能なクラスを作る方法を調べてみよう。

2009年2月17日火曜日

テスト

コメントが付いたらブログの表示がされなくなったぞ。なんでだ?

追記:ああ良かった。エントリを一つ作ったら表示されるようになった。
環境依存の問題かもしれないので、今後このブログが表示されていない事があったら、メールで知らせてくださいね。

Windows Media Player 10 視覚エフェクトをC#+WPFで作るサンプル発見。ただしアーカイブが壊れている。

メモ:WMP 10のSDKはhttp://msdn.microsoft.com/en-us/windowsmedia/bb190309.aspx。

ミクさん大放出をWindwos Media Playerの視覚エフェクトに仕立てようと画策中。
ただ、COMインタフェースで、かつ、C++前提のサンプルしかない。.NETのアセンブリからはCOMインタフェースを公開する事ができるようになっているので、理論的には問題はないが、自分の知識不足で敷居が高い(COMとの相互運用およびCOMそのものについて何も知らない)。

COM InterOpのエキスパートっぽいAdama Nahanさんが、
http://blogs.msdn.com/adam_nathan/archive/2005/10/24/using-com-interop-to-create-a-wpf-visualization-for-windows-media-player.aspx
こういう記事を書いていて、C#+WPFで視覚エフェクトを作るサンプルまでアップロードされているのだが、何故かアーカイブが壊れている。怪しい英語でメールを送ってみたのだが、対応してくれるだろうか。ブログも去年から更新していないし・・・

2009年2月16日月曜日

株価

GM(笑)状態であまり反応しないね。アメリカが経済の中心じゃなくなって円高&株高のセオリー通りの状態に戻るのも遠くない未来の事なのかも。でも、日本の景気悪化って、アメリカ発の経済危機が波及したってよりも、バブル崩壊から立ち直っていないのを数値でごまかしてきたのがごまかしきれなくなったってだけだしね。

中国とかが新しいビジネスの中心地になって、そこに依存する形の経済に落ち着いたりするのが未来のあり得るシナリオかな。IT関連での世界的な存在感のなさ(どうしてネットブック市場に乗り遅れるなんて高度な芸当ができるんだ、日本のメーカは)を考えると、依存する所かふるい落とされかねないけれどね。国が音頭をとって何かしない限りは、ずっとガラパゴスのままやっていく積もりなんだろうなあ、日本のメーカは。

体調

今日は、朝の1~2時間の少しだけ頭がすっきりする時間がなくなってしまった。朝からずっと横になっていた。最近、耐え切れないほどの筋肉痛というのは週に二日くらいで、それ以外は倦怠感という以上の物ではないのだが、朝~昼にかけて関節の曲げ伸ばしに非常に苦労する。体が縮こまった感じ。WikipediaのCFSのページには「朝の体のこわばり」という症状が書いてあって、イメージし難かったんだけど、こういう感じの事なのかなあ。本当に、CFSじゃないという形で、そして展望が持てる形で、決着が付いて欲しいのだが。まあ来月の診察まではあれこれ考えてもしようがない。

そしてやはり一番辛いのが毎日徹夜明け状態である事。筋肉痛も、例えば眠りが浅かったりとそれが強かった場合に合わせて強まる感じ。目から額からつーんとして、目が霞んで、目蓋が腫れぼったくて開けておくのが大変ですごい細目になって、寝込んでしまうわけではないけれど眠気が持続して、何をしようとしたかを簡単に忘れたり、集中力が極端に落ちていたり。投薬治療のお陰か、睡眠のリズムが整った分、寝ていれば一日が過ぎ去るというわけにいかず、却って精神的に辛い。(それでミクさんやってたりするわけだが。C#は高度に構造化された高級言語で機械的に扱えるのは本当に助かる。これがC++だったら・・・今の状態で扱うのはまず無理だ。拷問の類だ。)

それと、起き抜けに風邪気味の症状が強まるのだが、一日続くわけではなく、午後までには軽い咳・痰が続くだけになる。微熱というよりもむしろ36度を切るような低体温が続き、午後遅くに少し活動し易くなってくると、普通は平熱の36.8度くらいになるのだが、時々37度を越える事がある。寝る前にはまた36.5度くらいまで下がっていく。微熱というよりも、体内リズムと体温のリズムは一致して普通に上下しているのだが、その振れ幅が大きい、つまり体温調節が上手く行っていなという感じなので、仮にCFSだとして、いわゆる感染症類似の症状というやつじゃなくて、自律神経系の問題の範疇なのだろう。リンパの腫れもないし。

というか、医学的知識がないのでリンパの腫れってのもイメージできない症状なんだが。まあ圧痛があるというので、あれば自分でそれと分かるはずの症状なのだろう。そういえば、数年前までの数年間、ちょうど今よりも風気味の症状がめちゃくちゃ強いのが続いた期間(声が枯れるまで咳が2週間くらい続くというのが年に何度もあった)に、下あごの所に金玉のようにコリコリした物が左右二つできて、首を前に傾けるとそれが押されて痛かったりしたのだが、あれってリンパ節の腫れだったのかなあ。リンパ節の位置とかってさっぱり分からないんだよなあ。

2009年2月15日日曜日

XNA3.0のランタイム

メモ:
http://www.microsoft.com/downloads/details.aspx?FamilyID=6521d889-5414-49b8-ab32-e3fff05a4c50&DisplayLang=en

一様重力化ではトルクって0なのね

表題の通り。
重心を基準とした位置ベクトルをr_i, 質量をm_iとして重力加速度ベクトルをgとすると、重心を基準としたトルクは
Σr_i×m_ig=(Σm_ir_i)×g
だけど、Σm_ir_iは、今、重心が原点だから、零ベクトルだ。

何でこんな当たり前の事知らなかったんだろう。

というわけで、剛体の回転コードを追加したとしても、初期角速度ベクトルがそのまま保存するだけというつまらない事になる。空気抵抗を進行方向からの圧力として、面毎に計算するようにするとかすれば回転するようになるわけだな?でも、面積の計算とか面倒だなあ。ヘロンの公式だっけ?そんなのを使うのか・・・。それに、空気抵抗のパラメタの調節が面倒そうだなあ。

よし、ミクさんはとりあえずこの辺で終わりとしよう。

ミクさん大放出



2009年2月14日土曜日

がー。

まじかよ。回転行列の生成元って、交換子はパウリ行列の交換関係と一致するけれど、SxSy=Szみたいなのは満たさないじゃん(一方でパウリ行列は四元数の基底として正しく使用できる)。積が閉じない以上今まで書いてきた話は当然にして全部間違っていた。SU(2)とSO(3)の局所同型の話って、完全にLie群、Lie環の話であって、一方で四元数の話は、Lie群、Lie環の話とは全然向いている方向が違うんだなあ。

クオータニオン⇒回転の対応は、じゃあそんなに自然なものじゃなくて、二段構えになっているのかな?つまり、四元数が直接対応するのはIとパウリ行列を基底とする実数係数線形空間なわけで、SU(2)がその中に入っているのは、リー環がまさにパウリ行列で張られて、かつIを加えると(交換子のみならず通常の)積が閉じている、という議論から言えてこれは比較的自然なストーリーだ。一方でさらにトレースレスのエルミート行列と三次元ベクトルを
[z/2, x/2+iy/2]
[x/2-iy/2,-z/2]
みたいな感じで対応させるとこれもこの線形空間に入っていて、しかもSU(2)は``三次元ベクトルを回転''させる、という話が介在してようやく三次元ベクトルの回転に四元数が使えるわけだ。多分そういう二段構造になっている。

ラッキーな事に、対角化でなく加法定理を調べる事を選択したから、その部分はパウリ行列の線形結合の指数関数の話にそのまま使える。ただし、パウリ行列はブロッホ球上の``点''を角速度2で回転させるようなジェネレータだから回転時間が回転角の半分で済むので、θがθ/2の形で入るわけだ。

四元数がベクトルを回転させるのって、もっと自然な話だと思ってたんだけど、偶々としか言いようがないぞ、これ。高次元に多分拡張できないし。

まあいいや、なんとなくクオータニオンの使い方は分かってきたし。でも、剛体の回転シミュレーションはまだ敷居が高いかも。まあどうせ遊びだ。気長に気長に。

追記:パウリ行列でなく、それにiを乗じた物、つまり

J=[i, 0]
[0,-i]

K=[ 0,1]
[-1,0]

L=[0,i]
[i,0]

に単位行列Iを加えた物が四元数の単位要素と対応する。考えてみればこれらをSU(2)のジェネレータとして使う場合、iを乗じてから指数の肩に乗っけるのだから、以前の加法定理の議論を流用するには、当然そうあるべきだった。これでようやく、これら単位要素は二乗すると-Iになるという虚数単位と同様の振る舞いをするようになり、I, -I, J, -J, K, -K, L, -Lの間で積が閉じる。さらに行列としての共役転置も閉じ、これは四元数の共役と対応する。後でSU(2)と四元数の対応を書くが、ユニタリ行列の逆は共役転置なので、SU(2)に対応させた四元数の逆数は共役により簡単に得られる事になる。

3次元ベクトルと行列の対応は

[ix/2 , y/2+z/2]=xJ+yK+zL
[-y/2+z/2, -ix/2 ]

となり、トレースレスの反エルミート行列となる。こうして3次元ベクトルは純四元数(I成分がない)に対応するだけでなく、見かけの上で数ベクトルとしての三次元ベクトルをそのまま四元数と同一視できる。1/2の係数は、共役転置との積が、つまり四元数としてのノルムの二乗が、ベクトルのノルムの二乗と一致するようにするためだ。

一方でSU(2)も四元数と、|(nx,ny,nz)=1|の時

exp(t(nxJ+nyK+nzL))=cos(t)I+sin(t)(nxJ+nyK+nzL)

で対応する。SU(2)が四元数に対応する、つまりI,J,K,Lが張る実係数線形空間内にある事は既に書いたI, -I, J, -J, K, -K, L, -Lの間の(交換子のみならず)積が閉じる事から直ちに言えるが、この具体的な表式からさらに次の事実を見出す事ができる。すなわち、SU(2)の要素は四元数としてノルム1であり、しかもノルム1の四元数は全てこの形に書ける。つまり、SU(2)は、四元数体の中にノルム1である物からなる部分集合として埋め込まれる。既に注意したように、SU(2)の場合、四元数としての共役と、四元数としての逆数と、行列としての共役転置と、行列としての逆行列が全て対応する。

これら二つの全く異なる意味での対応関係と、上で示した形で表したSU(2)がxJ+yK+zLの形の``三次元ベクトル''をtの2倍の角度だけ(nx,ny,nz)を軸として``回転''させるという話まで考えて、結局三つの半ば独立した話を全て合わせてようやくクオータニオンが回転変換に使用できるというわけだ。

元から全く自然なストーリではない非自明な結果であり、それ故に使い方だけを説明される場合が多いわけだが、なんとなく自然なストーリーが背後にありそうに見えてしまって、勝手な哲学を見出そうとして、それで混乱するわけだ。q v conj(q)でベクトルvが回転します、なんてあっさり書いてあると、まるで、ベクトルを四元数が``直接''回転するように見えるのでどうにか自然な説明を見出そうとしてしまいがちになるが、実際にはこのように、何段階もの対応関係を経て、ようやく四元数は回転を実行できるのだ。行列のユニタリ行列による変換だから、ユニタリ行列とその共役転置で両側から挟む形になるわけだ。

WPFのクオータニオンのコンストラクタ

リフレクタで見て見た。ちょっと修正して見やすくすると

public Quaternion(Vector3D axisOfRotation, double angleInDegrees)
{
angleInDegrees = angleInDegrees % 360.0;
double num2 = angleInDegrees * (2*Math.Pi/360.0);
double length = axisOfRotation.Length;
Vector3D vectord = (Vector3D) ((axisOfRotation / length) * Math.Sin(0.5 * num2));
this._x = vectord.X;
this._y = vectord.Y;
this._z = vectord.Z;
this._w = Math.Cos(0.5 * num2);
this._isNotDistinguishedIdentity = true;
}

つまり、まず俺の計算はやっぱりどこか間違っていて、cos, sinの中は角度/2。それからこのコンストラクタは回転軸を正規化し、angleInDegreesは正確に回転角の意味。後、in degreesかよ!!

角速度ベクトル、回転時間が与えられた時の回転行列

対角化が面倒そうだったので、加法定理を求める方針でやってみる事にした。
つまり、

(I+δt(x Sx+y Sy+z Sz))(a0(t)I+a1(t)Sx+a2(t)Sy+a3(t)Sz)
=a0(t+δt)I+a1(t+δt)Sx+a2(t+δt)Sy+a3(t+δt)Sz ... (1)

を満たし、かつa0(0)=1, a1(0)=a2(0)=a3(0)=0であるような関数(連続性とか微分可能性とかの細かな扱いは知らない。必要なら行列の指数関数の話と併用すれば論理の隙間は埋められるだろう)を求めれば良い。それが求まるとa0(t)I+a1(t)Sx+a2(t)Sy+a3(t)Szが、角速度ベクトル(x,y,z), つまり、回転軸(x,y,z)/|(x,y,z)|, 角速度|(x,y,z)|でtだけ回転した時の回転行列、つまり、回転軸(x,y,z)/|(x,y,z)|, 回転角|(x,y,z)|tの回転行列を表す。

(x,y,z)をベクトルωで表し、(a1(t),a2(t),a3(t))をベクトルα(t)で表す事にする(ベクトルを明示するうまい方法がないのでここでだけギリシャ文字はベクトルを表すことにする)。しばしばtをオミットする。

すると、(1)式から
da0/dt=-ω・α
dα/dt=a0ω+ω×α
が得られる。
左辺にそれぞれa0, a1, a2, a3を書けて足しあげると0となる。これはa0^2+a1^2+a2^2+a3^2が不変である事を表す。t=0ではa0=1で他は0だから、連続性も考慮して、
a0=sqrt(1-|α|^2)
と書ける事が分かる。後はαの各成分だけ求めれば良い。αの微分方程式は

dα/dt=sqrt(1-|α|^2)ω+ω×α ... (2)

であるから、左からωを外積すると右辺第一項が消えて
d(ω×α)/dt=ω×(ω×α)
を得る。右辺はω×αに直交するから、|ω×α|は保存する(まじめにやるならば両辺とω×αの内積を取って右辺が消滅する事をがんばって計算すればd(|ω×α|^2)/dt=0を示せる。超めんどい。)。その値はt=0を参照すれば、0である。とすれば、ω×αは零ベクトルである。(2)式はいまや

dα/dt=sqrt(1-|α|^2)ω ... (3)

となる。αの各成分は初期値が等しく、微分係数が定数にωの各成分を乗じた形になっているので、αは全ての時刻でωに比例する。α=f(t)ω, とおくとf(t)が満たす微分方程式は
f(0)=0,
df/dt=sqrt(1-f^2|ω|^2)
である。必要ならばさらにg(t)=f(t)|ω|と変数変換しても良いが、ともかく、解が
f(t)=|ω|^(-1)sin|ω|t
と求まる。結局、

exp(t(x Sx+y Sy+z Sz))=cos(sqrt(x^2+y^2+z^2)t)
+sin(sqrt(x^2+y^2+z^2)t)(x Sx+y Sy+z Sz)/sqrt(x^2+y^2+z^2)

と求まる。特に、sqrt(x^2+y^2+z^2)=1ならばexp(t(x Sx+y Sy+z Sz))は軸(x,y,z)周りの角度tだけの回転行列を表すが、それは、

exp(t(x Sx+y Sy+z Sz))=cos(t)I
+sin(t)(x Sx+y Sy+z Sz)

である。

で、記憶によればこの結果は間違っていて、cos(t/2), sin(t/2)みたいになるはずなんだけど・・・結局、そのうち対角化の方もやってみなきゃならなそう。あと、そろそろWPFのQuaternionクラスのコンストラクタに色々なパラメタを入れたり、それからRotateTransform3Dを作って行列を調べたりして、ここまでの話と照らし合わせて見よう。あっちは、右手系だからちゃんと整合せいると思うんだけど・・・。Quaternionのコンストラクタには軸と角度を与えるコンストラクタがあるのだが、正規化されていないベクトルを与えた時の挙動が少し気になる。場合によっては、これらの引数は角速度ベクトルと回転時間としての役割を持つかもしれないし、内部で強制的に正規化されるかもしれない。この辺り全くドキュメント化されていない。

あ、足が、目が・・・

調子に乗ってたら著しく体調が悪くなってきた。最近、朝起きて数十分とその後との落差が酷い。

せっかくだから剛体回転でも、と思って準備

MSDNのクオータニオン関係の説明が詳細を欠いていて、係数の役割や回転変換との関係付けなど細かな点で不明な所があるので、剛体の回転も含めて整理しようとしたのだが、やっぱり集中力が阻害されて本をまともに読めない。まあ復習だしと思って自分ではじめからやってみる。

まず、三次元回転行列とは三次元の基底ベクトルを並べて作る。つまり、その行または列は正規直交する。従って回転行列RはRR^T=Iで特徴付けられる。

単位行列Iの近くの回転行列だけを考える。つまり、式(I+εM)(I+εM)^T=Iが、εを小さくしていった時にどんな条件式と等価になるかを考える。それは、
M=-M^T
である。この条件式はMの定数倍に対して保存される。つまり、Mに対して成り立てばεMに対しても成り立つ。つまり、単位行列Iの``近く''の回転行列の集合は
{I+M, where M=-M^T, Mは``小さい''}
と書ける。そこで、線形空間
L={M, where M=-M^T}
を考える事が重要になる。

反対称という事は、対角成分は0で上三角部分を決めると下三角部分が決まるから自由に決められるパラメタは三つしかない。これは、この線形空間が三次元空間である事を意味する。なんて勿体つけなくても、基底は直ぐに見つかって、

[0, 0,0]
[0, 0,1]
[0,-1,0]

[ 0,0,1]
[ 0,0,0]
[-1,0,0]

[ 0,1,0]
[-1,0,0]
[ 0,0,0]

がLを張る。

x,y,zが対称的に扱われるように、以下、右手系における右ネジ回転を基準とする事にする。これでこのエントリにおける不明瞭な点はなくなるだろう。後でWPFのクオータニオンと対応付ける必要はあるが。また、点の座標の変換と座標系の変換を明確に区別する。

(x,y,z)にある点をx軸でθ回転した時移る先の座標(x',y',z')は

[x'] [1,0  ,0  ]
[y']=[0,cosθ,-sinθ]
[z'] [0,sinθ,cosθ ]

と表される。この事から、x軸中心の微小なθ回転は

[0,0,0]
I+[0,0,-θ]
[0,θ,0]

と表される。他の軸中心の微小回転も同様に求められる。そこでLの基底を次のように取り直すと便利だ。

[0,0, 0]
Sx=[0,0,-1]
[0,1, 0]

[ 0,0,1]
Sy=[ 0,0,0]
[-1,0,0]

[0,-1,0]
Sz=[1, 0,0]
[0, 0,0]

この場合、積はIを加えれば閉じていて、反対称である。つまり
SxSx=-I, SxSy=Sz, SySx=-Sz, ... .

恒等変換の``近く''の回転変換を次々と作用させていく事で、恒等変換から``遠く''の回転変換も作る事ができる。剛体の回転運動はこのようにイメージできる。対応する概念は単位行列の``近く''の回転行列の複数回の積だ。つまり、x(t), y(t), z(t)を与えて、
Π(I+δt(x(t_n)Sx+y(t_n)Sy+z(t_n)S_z)), where δt=t_n-t_(n-1) ...(1)
によって単位行列から``連続に''別の回転行列に移る事ができる。ここでSx, S_y, S_zが単位ラジアンあたりの各軸周りの回転に対応していた事を思い出せば、x(t), y(t), z(t)は角速度ベクトルの各成分を与えると分かる。

この計算を実行するのは難しいが、ひとつだけ分かる事は、I, Sx, Sy, Szの積が閉じているために、この結果は必ず、I, Sx, Sy, Szの線形結合になるという事だ(Lの基底と違い、Iが加わる事に注意!)。これは重要な事だ。というのも、回転行列の積を繰り返すと計算誤差により徐々に回転行列の条件=正規直交性を満たさなくなってしまうからだ。しかし、これら四つの基底に関する係数成分を時々刻々と計算するならば、計算誤差がどれだけあろうとも、それは回転変換に厳密な意味で対応し続ける!こうして四元数が登場する。

少なくとも剛体のシミュレーションに関する限り式(1)の計算結果を具体的に表す方法がない事はほとんど問題にならない。なぜならば、実際に、その時点での回転行列に恒等変換の近くの回転行列を乗じて、回転変換を更新する処理を繰り返せば良いからだ。変数に更新量を加算したり乗じたりしてループを回す類の処理は、シミュレーションにおける最も基本的なアルゴリズムで誰もが良く慣れているはずだ。また、すでに注意したように、回転行列自身の代わりに基底行列I, Sx, Sy, Szの係数を保存し、回転行列の積を計算する時には、基底行列の間の積の関係を使って係数の組から結果の係数を求めるようにすれば、係数に対応する回転行列は厳密に何らかの回転変換に対応し続ける。

存在する問題は二つだ。一つは、(1)式の積に現れる各項は近似的にしか回転行列に対応しないという事。もう一つは初期姿勢を決めるために(1)式の極限の具体的な結果が一つ欲しいという事。二つ目が解決されれば、その結果は、一つ目の問題の解決にも貢献するだろう。つまり、本物の微小な回転変換を繰り返し合成して一般の回転変換を作る事ができるようになる。

さて、(1)式の計算結果を具体的に求められる簡単なケースが一つある。それは、x(t), y(t), z(t)が一定である場合で、結果は行列の指数関数になる。その意味も明確だ。これは、角速度ベクトル[x, y, z]による、軸・角速度一定の回転を表す。特に、|[x,y,z]|=1ならば、時刻t=θにおいて角度θだけ回転した事になる。これがいわゆるクオータニオンと、軸/回転角で表した回転の関係だ。

ふう。次回は、せっかくだからSU(2)でやらずにこのまま気合で対角化して指数関数を求めるか。

2009年2月13日金曜日

ちょ、

足が、足がやばい・・・

クオータニオンを使って姿勢をランダムにした


クオータニオンで一様な回転を生成するには、単にその四つの係数を四次元球面上に一様に分布させれば良い(⇒これは誤り。回転軸を球面上一様分布で、回転角を一様分布で生成してクオータニオンを生成すべき。だったら、AxisAngleRoatate3Dで良かったんだな。まあ良いや。回転軸は確かに一様だし)。実際には、四つの係数を[0,1]の一様分布で生成する事を長さが1以下になるまで繰り返して、その後まずクオータニオンを作ってしまってからNormalize()を呼べば良い。

というわけで今現在のコードで、ミクさんのモデルデータは重心が原点にくるように移動させてあって、それをContentsとするVisual3DのTransformにTransform3DGroupを設定して、そのChildrenをRotateTransform3D⇒TranslateTransform3Dの順に設定し、この二つの変換を支配するQuaternionおよびPoint3Dを公開して、UpdatePose()を呼ぶと変換に反映するようにした。現在はPoint3Dのみを重力加速度に従って動かしているけれど、このデータ構造なら、オイラーの剛体方程式に従って剛体回転も容易に扱えるはず。やる気はしないけれど。

コードと実行ファイルはARWithWPFMikuEmitted.zipを詳細サイトのダウンロードページから取ってきてください。

ちなみに、この画像ではボタンの``Emit Miku!''の綴りが``Emmit Miku!''となっていて格好悪いけれど、アップしたソースでは修正してあります。

ARToolkitPlusで体感ゲームを作る予定は未定だけど準備だけ


ミクさんがマーカの位置からランダムな初速度を与えられて射出されて重力で自由落下するぞ。

ミクさんは三次元CG@七葉の4169さんのローポリなデータをお借りしています。

しかしWPFのStoryboard使わずに普通にオンフレームアニメーションにしてしまったので、プログラム的にはあまり面白くない。一連のARToolkit+WPFの実験はWPFを活かす事に主眼があるので続くかどうかは未定。

演算子とジェネリック

演算子の実装を表す型制約がないから何かやだねー、というお話。2chのC#板で出ていた。
この手の話は、C++風にダックタイピングが出来たら・・・という話にもなるのだが、そういう方針で、まあマクロのお化け的なC++のテンプレートにおけるダックタイピングとは違くてつまりは実行時に動的にメソッドを生成するという方針だけど、どなたかがExpression TreeのCompileメソッドを使うやり方を示していた。

で、それならCodeDOMでもできるじゃん、と思って書いたのがこんなコード。

class Multiplier<T>
{
public T Mult(T l, T r)
{
string nameOfT = typeof(T).FullName;
string assemblyOfT = typeof(T).Assembly.ManifestModule.FullyQualifiedName;
CompilerResults cr = null;

using (CodeDomProvider provider
= new CSharpCodeProvider(new Dictionary() { { "CompilerVersion", "v3.5" } })) {
CompilerParameters cp = new CompilerParameters() {
GenerateInMemory = true
};
cp.ReferencedAssemblies.AddRange(new string[] { "System.Core.dll", "mscorlib.dll", assemblyOfT });
string className = "Multiplier_" + nameOfT.Replace('.', '_');
string namespaceName="GenericTest";
string qualifiedClassName = namespaceName + "." + className;
string source = "namespace " +namespaceName+"{ public class " + className + "{public " + nameOfT + " Mult(" + nameOfT + " l, " + nameOfT + @" r){return l*r;}}}";

cr = provider.CompileAssemblyFromSource(cp, source);
if (cr.Errors.HasErrors) {
throw new Exception(String.Join(Environment.NewLine, cr.Errors.OfType().Select(e => e.ToString()).ToArray()));
}
Type typeOfMultiplier = cr.CompiledAssembly.GetType(qualifiedClassName);
Object multiplier = Activator.CreateInstance(typeOfMultiplier);
MethodInfo multiplierMethodMult = typeOfMultiplier.GetMethod("Mult");
return (T)multiplierMethodMult.Invoke(multiplier, new object[] { l, r });
}
}
}

その後、Nyaruruさんの詳しい記事を見つけたのでそれも投稿しておいた。Nyaruruさん(のさらに引用先)曰く、Expression Treeはセマンティクスをデータ構造として保持するからコードを構造を持った抽象的対象として扱えるのが利点で、作ってすぐさまコンパイルして動的メソッド生成のために使うなんてのは、邪道であるらしいのですが、まあCodeDOM使うとこんなに酷い事になりますよ、というお話でした。

ていうかこのコードはMult()呼ぶたびにコンパイルするのも酷いな。せめてstaticなコンストラクタでやるべきだ。

2009年2月11日水曜日

回ってフワフワするようにした



写真じゃ分かり難いけれど(当たり前だ)、z軸中心の回転とz軸方向の平行移動のアニメーションを加えた。さながらデジタルフィギュア。

コードはいつもの所においてある。実行ファイル入りなのでとりあえず遊びたい人もどうぞ。

追記:

ニコニコ動画にアップした。

体調

今までは疲労感=倦怠感であって、明確で強い筋肉痛というのは週に二日くらいだったのだけど、いつの間にか完全に日常化してしまっている。腕から足から酷い。それに目がショボショボして目蓋が重く集中力が極端に阻害される。この数ヶ月で良くなったのは睡眠のリズムが整った事だけど、それでも毎日徹夜明け状態は変わらない。むしろ辛ければ寝ていれば良かった時期よりも一日の過ごし方に工夫が要るのでかえって耐え難い。だからミクさんでARというのも何ともアレだが、まだできる事があるというのは良い事だと思おう。

で、ミクさんをWPFのStoryboardでアニメーションさせるのが思ったよりも難航している。まあ気長にやるさ。

2009年2月10日火曜日

ミクさんの動画


2009年2月9日月曜日

ミクさんリアルだ


今回から、カメラの位置だけでなくマーカの位置の左と右に点光源を置いたのでミクさんは下から照明されているのだが、お陰で怖い程のリアリティがある。画像じゃ伝わらないかもしれないけれど、ちゃんとマーカの動きが追従されている時、どきっとするくらいに本物らしい時がある。これは完全にモデル製作者の力量に拠ってるよなあ。日曜モデリングさまに感謝を。

追記:せっかくの拡張現実なので、現実感と非現実感が同居している感じに照明を調節してみた。

ね、ねむいー

尋常じゃなく眠い。夜までは起きている生活リズムを作りたいからと無理をしすぎかも。でもまあ今日はちょっと面白い事ができた。

ミクさんのコード

一応置いておいたけどまだやっつけなので注意。とりあえず動くとは思う。

ミクさんを表示してみた


もう何番煎じというレベルではないですが。しかしネタとしてはともかく、スケルトンが超整理されていて3D表示がWPFであるためにプログラミングという観点からはとっても可能性が広がっているのです。

さあ、データを気合入れて作って振り付けをアニメーションするのは職人に任せて、WPFを活かしたコードでのお気楽アニメーションをやってみるぞ!

ちなみに、データは日曜モデリングさまものです。というかデータの準備に、工学ナビの中の人のこのエントリを参考にしたというわけなんだけど。

mqoからxamlの3Dデータの準備の仕方のメモ。Identityを使用して、この記事を参考にした。

1.mqoをメタセコイアで読み込む。一度正方形を適当な大きさで作ってミクさんのフットプリントを確認しておく。今回のデータの場合、およそ100x100の正方形に収まるが、そのうち大半は髪の毛のボリュームなので、プログラムでスケール変換する時にmarkerwidth/50.0を係数とする。(markerwidthはARToolkitPlusの初期化時に渡すマーカのマーカ座標系における幅。)
2.Identityが吐くxamlにはエンコーディング指定がないので、日本語の材質名、物体名などは変更しておく。なお、今回のデータの場合、日本語の名前の材質は全て未使用(製作の過程で一時的に割り当てたっぽい)だったのでざっくり削除した。
3.Identityはインストール先のディレクトリのTexturesディレクトリからのみテクスチャファイルを探す。従って一度テクスチャファイルは全てコピーする。
4.Identityでmqoファイルを開く。ファイル-名前を付けて保存メニューを選択し、xamlをファイルの種類として選び、UV座標と法線にチェックを入れて保存する。この時オリジナルのテクスチャがあるディレクトリに出力すると改めてテクスチャファイルを生成しようとしてエラーが出るが別に問題はない。気になるなら別のディレクトリに出力する。
5.xamlファイルをがんばって書き換えて次のようにする。この時x:Nameは全てx:Keyに書き換える。

<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<MaterialGroup x:Key="Material_miku_pose1_face" >
...
<Transform3DGroup x:Key="Transform_Character_miku_pose1_Character_0" >
...
<MeshGeometry3D x:Key="Geometry_miku_pose1_head_0" ... />
...
<Model3DGroup x:Key="ここにプログラムから参照する名前を入れる。今回はmiku_pose1とした">
...
</Model3DGroup>
</ResourceDictionary>
6:ソリューションにMIKUフォルダを作り、xamlとテクスチャファイルを全て入れる。プロパティで``ビルドアクション''を「コンテンツ」に、``出力ディレクトリにコピー''を「新しい場合はコピー」にする(注:xamlもコンパイルせずにこのようにした方が、ユーザがデータを入れ替えられて良いと思うのでそうした)。
7:Window1.xamlに
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="MIKU/miku_pose1_resource.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
という記述を追加。これで、コードビハインドのWindow1クラス中で(Model3D)this.Resources["miku_pose1"]としてミクさんのモデルを参照できる。

キャリブレーションの仕方を英語で書いた

http://sites.google.com/site/augmentedrealitytestingsite/#TOC-How-to:-Camera-Calibration
にキャリブレーションの仕方を英語で書いておいた。この情報は探しても全然見つからないのでかなり有用だと思う。頭がパンクしかけたのでかなり端折ったけど、かえってこのエントリに書いたキャリブレーションの仕方の説明よりも簡略になって分かりやすくなった気がする。

mqo to xamlができるソフト

Identityというソフトがあるらしい。これでマーカーの上でミクさんを踊らしたりとかできるのかな。

連番mqo出力ができるアニメーションツール RokDeBone2なんてのもある。

ありゃ、Identityって、ただの変換ツールかと思ったらアニメーションツールなのか。それでxaml出力ができるって、アニメーションするオブジェクトをマーカに表示するだけならプログラム超簡単にならないか?

あと、メタセコイアへのポインタ


追記:Identityを使ってみたが、Viewbox3Dをルート要素としブラウザで開くと単体で動作する手取り足取り状態のxamlが出力される。これはこれで便利だが、プログラムから参照するのには不便。ここの記事にリソースディクショナリへの加工の仕方がある。

2009年2月8日日曜日

英語サイトを作った

ARToolkitPlus+WPFは必要としている人は少ないが確実に有用である類のプロジェクトなので、世界に広く公開した方が良い。それで、英語サイトを作った。
http://sites.google.com/site/augmentedrealitytestingsite/
今はアドイン「身も蓋もない」の軒先を借りている状況なので、ソースコードのコメントを英語でリライトしたらこちらに完全に移行するつもり。

追記:コメントを英語にしたので移行した。今後はソースなどはこちらにアップロードする。

デモ動画

デモ動画を作ってみた。

2009年2月7日土曜日

サンプルを更新

ここにおいてあるARToolktPlus+DShowNET+WPFのサンプルを更新した。

今回、デフォルトを自分用のキャリブレーショファイルにしてしまったので、これで上手くいかない場合は設定でLogitech_Notebook_Pro.calを選択するか、このエントリを参考に自分用のキャリブレーションファイルを作ってください。

それと、もう少しましなPCカメラ欲しいなあ。同じ30万画素でも普通はもっとましらしいし、ネット通販ならそれらでも980円で買えたりする。ちょっとGroovy GR-CAM030はひどすぎる。980円とは言え、安物買いの・・・って奴だ。

And finally...

MatlabのスクリプトってC++にコンバートした上でコンパイルするなんて事ができるので、もしかしたらと思って探したら、Camera Calibration Toolbox For MatlabのModified Versionのページで紹介されていたー

さあ、これでたぶん、カメラのキャリブレーションが自前でできるぞ。

実際にやってみた。

まず、Windows 7でやっているので、.net framework 1.1がないと怒られた。

http://www.microsoft.com/downloads/details.aspx?familyid=262D25E3-F589-4842-8157-034D1E7CF3A3&displaylang=ja

http://www.microsoft.com/downloads/details.aspx?FamilyId=04DBAF2E-61ED-43F4-8D2A-CCB2BAB7B8EB&displaylang=ja

をダウンロードしてインストール。

http://research.graphicon.ru/calibration/gml-c-camera-calibration-toolbox-5.html
から GML C++ Camera Calibration Toolbox の``Latest Binaries''をダウンロードしてインストール。

インストール先の``Calibration pattern''フォルダ中のpattern_A4.pdfを印刷。

GML Camera Calibration Toolboxを起動して、白紙のアイコンのボタンをクリック。。pattern_A4.pdfを印刷したなら、Object Sizeは7, 10, square sizeは24(実測値を使う事!)くらいを入力。

何らかの方法で印刷したパターンを撮影した画像を用意する。私が使用しているGroovy GR-CAM030では、ボタンを押すと動画のキャプチャソフトが起動する(付属CDからドライバをインストールしたわけではないのでWindows 7標準の機能かもしれない)が、ここでもう一度ボタンを押すと静止画がキャプチャできる。何枚か用意する。この時
http://research.graphicon.ru/calibration/recommendations-for-taking-calibration-photos.html
を参考にして色々な姿勢から撮影する。枚数は多い方が良い。意外と認識に失敗する事が多い。また、全ての画像のサイズは等しくする事。全ての測定結果はピクセルで表される。

プラス記号のアイコンのボタンをクリックして画像を全部追加する。チェッカーボードが二枚重なっているボタンを押すと全ての画像に対して認識が試行される。失敗した画像はマイナスのアイコンのボタンで削除。あまり成功枚数が少ないor姿勢のバリエーションが少なければ、新たに画像を追加してチェッカーボード一枚のアイコンのボタンで個別に認識を試行できる。

カメラのアイコンのボタンを押すとキャリブレーションが実行される。
で、下のようなテキストファイルを作る。分かり易いように``,''を入れたが、これは実際には書いてはいけない。

ARToolKitPlus_CamCal_Rev02
使用した画像の幅, 使用した画像の高さ, 出力されたprincipal pointの一つ目, 出力されたprincipal pointの二つ目, 出力されたfocal lengthの一つ目, 出力されたfocal lengthの二つ目, Distortionの四つの数字を順に, 0.0, 0.0, 10くらいの好きな数字


これで自分のカメラ用のキャリブレーションファイルが完成!!・・・と思う。

ちょっと気になるのは、640x480の画像を使ってGroovy GR-CAM030をキャリブレーションすると、principal pointのxが420くらいになる事。実際にずれているのか、それともパラメタの解釈か手順に問題があるのか・・・。

で、このキャリブレーションファイルを使ってARしてみた結果:

まあ特に何が変わったとも思えないけど心なしか自然になった気がする。ブラシーボ効果かもしれない。

体調

今の薬の処方になってから、朝方に少しだけ集中力が得られる時間帯ができて、それでプログラミングなんかをしたりして、そして昼頃にかけて結局いつもの持続する倦怠感・眠気、目がショボショボ、風邪っぽい症状が強まって、何も手につかないのが耐えられなくて倦怠感を吹き飛ばそうと無理やりに外出して強烈に疲れて帰ってくる、という感じになっている。人間って面白くて、調子がよければ少しだらけ気味にアクティブに動くのだけど、中途半端に調子が悪くいと、とてもアクティブに動くのだ。だから最近、外出する頻度はむしろ上がっているのだが、朝方の時間帯を除けば、結局体調は悪くなる一方。もう、自分の足で歩けるのはいつまでやら、という思いから無理に外出しているだけという感じ。

来月にもう一度CFSの専門医の診察を受ける事になっているが、診断名すらつかない状況だけでも解決して欲しいという気持ち半分、現状治療法のない病気という絶望的結論を怖がる気持ち半分というところ。

しかし最近のAR関連の活動もそうだが、人間、こんなどん底でも色々な事ができる物だ。人間というものに心底感心する。

MATLABでキャリブレーションできるらしい?

MATLAB camera calibration toolboxでキャリブレーションすると、ほとんどARToolkitPlusが必要とするのと同じ形式でパラメタが得られるらしい。

Description of Calibration Parametersの項を見ると、2次元ベクトルであるfc, cc(これがARToolkitPlusのパラメタfc_x, fc_y, cc_x, cc_yにあたるのだろう)と、5次元ベクトルであるkc, そしてスカラalpha_cが得られる結果であるらしい。

そして、First Calibration Exampleのページを見ると、
Notice that the skew coefficient alpha_c and the 6th order radial distortion coefficient (the last entry of kc) have not been estimated (this is the default mode). Therefore, the angle between the x and y pixel axes is 90 degrees. In most practical situations, this is a very good assumption.
と書いてある。つまり、alpha_cはピクセルのx軸とy軸のなす角度の90度からのズレを表していて、これは普通無視して良く、実際ARToolkitPlusのキャリブレーションパラメタに対応するパラメタはない。一方で、ARToolkitPlusはkc_1からkc_6までを要求するが、このToolboxではkc_6は計算しないのがデフォルト。という事は得られた5次元ベクトルkcの要素を順にkc_1,..., kc_5とし、kc_6は0とすれば良いわけだ。

ARToolkitPlusのカメラキャリブレーションファイルの形式
ARToolKitPlus_CamCal_Rev02
xsize ysize cc_x cc_y fc_x fc_y kc1 kc2 kc3 kc3 kc5 kc6 iter
と比較すると、結局、iterは好きな数を指定し、xsize, ysizeはキャリブレーションに用いた画像のサイズを指定し、そしてcc_x, cc_y, fc_x, fc_y, kc1,..., kc_6はこのツールボックスの出力から得られる事になる。

Matlabを持っていないので試せないけれどね。

追記:というか、ARToolkitPlusのページには、
Improved camera calibration model (MATLAB camera calibration toolbox support)

As of version 2.0, ARToolKitPlus is compatible with the camera calibration
model used by Jean-Yves Bouguet's Camera Calibration Toolbox for MATLAB. An improved version of the toolbox (highly recommended) that includes automated corner/calibration object detection is available
from the Graphics Media Lab at Moscow State University.

The ASCII-file containing the camera calibration parameters should look as follows:
[line1]: ARToolKitPlus_CamCal_Rev02
[line2]: xsize ysize cc_x cc_y fc_x fc_y kc1 kc2 kc3 kc3 kc5 kc6 iter

xsize, ysize: calibrated frame dimensions (does not have to match the frame dimensions at runtime)
cc_x, cc_y: principal point location (in pixels)
fc_x, fc_y: focal length (in pixels)
kc1..kc6: radial/tangential distortion coefficients (kc6 currently not in use)
iter: number of iterations for distortion compensation

It is strongly recommended to enable undistortion lookup tables when using the improved calibration model.
A detailed description of the calibration parameters is available here.
と書いてあるので、kc_1からkc_5までをツールボックスの出力から取得して、kc_6は0にするという事で本当にかまわないようだ。それから、iterは大きい方が計算精度が上がるが時間がかかる、と。いいなー。MATLAB欲しいなあ。

Artoolkit Plus用のキャリブレーションの仕方

Artoolkit Plus用のカメラキャリブレーションファイルの作り方だが、必要な情報としてはArtoolkit付属のキャリブレーションツールで全て得られるらしい。ただ、表示される情報とArtoolkit Plusのカメラキャリブレーションファイルのフォーマット内のパラメタとの対応が不明確。

以下推測に基づく手順:
ArtoolkitのSourceForgeのページからARToolKit-2.72.1-bin-win32.zipを取ってきて適当な場所に解凍する。これにはバイナリが入っているので、キャリブレーションの目的だけならビルドの必要はない。

工学ナビの記事のこの項にある手順を実行する。ARToolkitの置き場所などは適宜読み替える。

最後に表示された
SIZE = 640, 480
Distortion factor = 375 223.5 6.2 1.003
1323.86047 -18.27697 410.18302 0.00000
0.00000 1285.40599 -243.19888 0.00000
0.00000 0.00000 1.00000 0.00000

が重要。これは、この記事中のパラメタ名の意味で、おそらく
SIZE = xsize, ysize
Distortion factor = cc_x cc_y 謎 謎
fc_x 謎1 謎2 謎
謎4 fc_y 謎3 謎
謎5 謎6 謎 謎

と並んでいる。分からないのはkc1 kc2 kc3 kc3 kc5 kc6 iterの7個のパラメタだが、最近のカメラなら歪み補正はされているだろうので、全て0で良いだろう。まあ、私のカメラは980円の安物のGroovy GR-CAM030で歪みまくっていたが。

#たぶん謎1~謎6が何らかの順番でkc1~kc6に対応していると考えられる。

そういうわけで、このケースだと、
ARToolKitPlus_CamCal_Rev02
640 480 375 223.5 1323.86047 1285.40599 0.0 0.0 0.0 0.0 0.0 0.0 0

という内容を持ったテキストファイルを作って、たとえばGroovy GR-CAM030.calというファイル名で保存する。

ちなみにここに公開しているARToolkit(Plus)+DShowNET+WPFのVisual C# 2008用サンプルでは、ARWithWPF.exe.config中でカメラキャリブレーションファイルを指定できるようになっているので、ここで紹介している手順でカメラキャリブレーションファイルを作ったらこのファイルを編集して欲しい。

キャリブレーション前(画角が不適切):

キャリブレーション後:

2009年2月6日金曜日

GUIを付けて透明度をいじれるようにした


やっぱりシュパパッとGUIをコードできてこそWPFとの組み合わせにした意味があるよな、と主って、透明度をスライダで設定できるようにしてみた。もちろん、バインディングを使っているので追加したコードは超短い。
xamlに追加したコード:

<StackPanel Grid.Row="0" Orientation="Horizontal">
<Label Padding="5,0,0,0" VerticalAlignment="Bottom">ARオブジェクトの透明度:</Label>
<Label Padding="0,0,5,0" VerticalAlignment="Bottom"
ContentStringFormat="##0%"
Content="{Binding ElementName=OpacitySlider, Path=Value}">
</Label>
<Slider Name="OpacitySlider" VerticalAlignment="Bottom" Width="100"
Minimum="0" Maximum="1.0" TickFrequency="0.01" IsSnapToTickEnabled="True"
Value="1.0" SmallChange="0.01" LargeChange="0.1"/>
</StackPanel>

Visual Brushを作成するコード:

Label label = new Label() {
Content = textureTexts[i],
Foreground = Brushes.OrangeRed,
Padding = new Thickness(0),
};
Viewbox viewBox = new Viewbox();
viewBox.Stretch = Stretch.Uniform;
viewBox.Child = label;
Border border = new Border() {
Background = Brushes.LimeGreen,
Width = 1.0,
Height = 1.0
};
border.SetBinding(Border.OpacityProperty,new Binding("Value"){
Source=OpacitySlider
});
border.Child = viewBox;
VisualBrush labelBrush = new VisualBrush(border);
labelBrush.Viewbox = new Rect(0, 0, 1, 1);
labelBrush.Stretch = Stretch.UniformToFill;

ARToolkit(Plus)+DShowNET+WPFのサンプルを更新


冗長性が高く認識率に優れるらしいBchタイプのマーカに対応した。readme.txtを参照のこと。アーカイブはこちら

上に掲載したのはBchマーカを使った場合の実行画面。ただ、特に検出率が高まった感じはしなかった。画像ではパーフェクトだけど。

追記:ARToolkitPlusのラッパーコード中にこういう記述がある。
Id-based markers directly encode the marker id in the image.
Simple markers use 3-times redundancy to increase robustness,

Id-based markers directly encode the marker id in the image.
while BCH markers use an advanced CRC algorithm to detect and repair marker damages.

ちょっと意味がとり難いが、おそらく、Simpleマーカは冗長化した上で2Dバーコードにエンコードするアプローチなのに対し、Bchは冗長化をしない代わりにCRCチェックサムを埋め込んで成功例が少なくて良いからIDが確実に読み取れたマーカのみを許容するアルゴリズムを使っているということだろう。Simpleが512種類に対してBchが4096種類なのだから、後者の方が冗長化の度合いが高いはずがない。

configファイルを使って見つからなかったマーカの位置を推定する機能をARToolkitPlusは持っていて、たとえば剛体の姿勢測定が用途として想定されているようだが、この場合マーカの冗長性よりも使えるマーカの数を増やした上で少なくて良いからIDが正しく読み取れるマーカを見つける事の方が重要になる。なにしろ四点で剛体の姿勢は決まる!Bchはそういう用途のためのマーカシステムで、IDはどうでも良くてマーカの姿勢の検出精度だけを問題にするのならばSimpleマーカシステムの方が優れているのだろう。

というわけで、単純な遊び用途ならばBchマーカの出番はなさそうだ。剛体の姿勢検出なんて遊びじゃやらないし、第一Configファイルを用意するための準備がかなり大変だ。

ARToolkitPlus+DshowNET+WPFのソースを更新

ここにおいてあるソースを更新した。
更新内容は主にバグ取り。ウィンドウのLoadedイベントでthis.Close()すると例外が投げられるのね。Application.Current.Shutdown()するようにした。これで、キャプチャデバイスを選択しなかった場合に変な終了の仕方をしていたのが、行儀良く終了するようになった。

これでコードは相当に整理された。ARToolkit関連のサンプルコードとしては最も単純化されていてサンプルコード/スケルトンとしての役割を果たし易いと自負している。ただ、コードのコピペが多く変数名のつけ方が自分流のつけ方と整合的でない部分があるので、少しリファクタリングしておきたいところ。構造はもうこれで良いだろう。window1.xaml.csの行数もメソッド数も十分に少ない。

2009年2月5日木曜日

異なる大きさのマーカ、同じIDを持つマーカ


ArManWrap.ARTKPSetPatternWidthメソッドにはマーカの大きさの実測値を指定するのだと思っていたのだが、そうではないようだ。これは、マーカ座標系におけるマーカをあらわす正方形の大きさを指定するメソッドみたいだ。つまり、例えば10.0を指定すると、(-5,-5)-(5,-5)-(5,5)-(-5,5)を頂点とする正方形を作ってから変換行列を取得して適用してやると、ちょうど画面上でマーカと重なるようになる。

そういうわけで、使っていたマーカが一辺3cmなのでこのパラメタに30を指定していたのだが、大きさの違うマーカもそのまま認識してくれた。

また、2Dバーコードを認識するというコンセプト上、同じIDのマーカが複数あっても何の問題もなく認識してくれた。中々守備範囲が広い。

カメラの位置に点光源を置いた


ネタとしては弱くなったが、陰影がつくようになった事が分かりやすいように三角柱を置くようにした。


環境光と組み合わせると少し自然になった。もちろん、実際の光環境と不整合であるが故の気持ち悪さはどうしようもない。が、逆にそれが、バーチャルリアリティーな感じで良い。こういった類は、チープなグラフィックの方が効果が喜んでもらえるものだ。あんまり凄いグラフィックだと、photoshopで合成したみたいに見えちゃうからね。

こうしたちょっとした変更は全部xamlの変更ですんでしまう。WPF恐るべし。

高速化


ARToolkitPlusとWPFの組み合わせのサンプルのコードを少し高速化した。といっても、高速化の余地はほとんどなくて、描画のコールバックの度にキットの初期化とクリーンアップを行っていたのをウィンドウのロード時とクローズ時に行うようにしただけ。でも結構な負荷だったようでかなり動作が改善された。

それにしても今日は、午前中に横になっていたら、午後になって割と調子が良い。眼はすげー霞んでるけど。

体調

最近、朝5時くらいに眼が覚めてそれから9時くらいまでは若干比較的調子が良いのでARで遊んだりできるわけだが、今日は8時までしかもたなかった。なんか感冒様の症状が強まっていて、のどに痰が絡んで息から嫌なにおいがするし、熱はないけど熱っぽいし、眼がしょぼしょぼして霞む。ARの方も一区切りついたし今日はゆっくりしよう。

サウンドオンリーで会議中の方々


ARToolkitPlus+DShowNET+WPFのサンプル



ARTolkitPlus+DShowNET+WPFのサンプルをここに置いておいた。この構成で、全てが上手くいけば、Visual C# 2008で一発でビルドが通るはず。

遊び方:
  • data/00-04.pngを開いて、マーカの一辺が3cmくらいになるように印刷します。マーカーを少し白い余白が残るように切り取ります。
  • ソースに興味があるならばARWithWPF.slnを開きます。三つのプロジェクトが入っていますが、全てビルドしてみましょう。上手くいけば一発で通ります。ソースに興味がなければ、バイナリ入りなので次に進みます。
  • PCカメラの接続を確認してARWithWPF/bin/release/ARWithWPF.exeを実行します。設定ダイアログは初期値のままでかまいません。
  • マーカを何枚か平らな場所に置いてカメラに映るように配置します。文字が表示されたら成功!ID0番のマーカは少し認識し難いです。

なお、今回から設定をアプリケーションスコープにした。変更の必要があれば、開発環境からデフォルト値を変更するか、bin/release/ARWithWPF.exe.configを書き換えてください。

2009年2月4日水曜日

ARToolkit:複数のマーカ



複数のマーカを検出する場合、conigファイルを作って、使う2次元バーコードのIDを列挙しないといけないみたい。512個を自由に、というわけにはいかないのか・・・。

まあそれほど問題にはならないだろう。

追記:ぐわー、いじってたら動かなくなった。しかも肝心な部分でコード履歴がない。最新のはまだシングルマーカの。やり直しだ。ただ、サンプルからほとんどコピペで上手く行くのは分かったのでもうどうにかなるだろう。

ARToolkitの実験の続き:2DバーコードのIDをテクスチャにした


これで複数マーカー対応にした時に楽しいはずだ。

ちなみにこのテクスチャはLabelオブジェクトを使ってVisualBrushを作る事で超手抜きで実現している。WPFのリッチさが早速発揮されているというわけ。Direct3DやOpenGLで文字をテクスチャにしろと言われたら、技術的な問題は何もないにしても、とても手間がかかるだろう。

ARToolkitの実験ソース置き場を作った

これからはここに置く。現時点でのソースを置いておいた。まじでVisual C# express 2008オンリーでビルドできます。巷のARToolkit関連のサンプルと比べるとビルドにたどり着くまでの敷居は圧倒的に低いでしょう。

マーカも0番から4番までのシートを同梱しました。一辺が3cmくらいになるように印刷すると設定を変えなくてすみます。あるいは、どこだか(c:\documents and settings\ユーザ名\Local Settings\Application Data\ARWithWPFの下のどこか)に作られるuser.configファイルで設定を変えてください。もちろん、開発環境からプロジェクトのプロパティ→設定タブで設定を変えてもかまいません。

それと、とりあえず、3D表示するオブジェクトの大きさが決めうちになっていたのをマーカーの大きさに合わせるようにしたらかなり良くフィットするようになった。カメラのキャリブレーションの必要は私の環境ではとりあえず必要ないようだ。

DirectShowの経験が不足していてグラフやらピンやらさっぱり分からないので人のサンプルコードを丸写し。そのためか終了時にキャプチャを終了させる関数を呼び出したら帰ってこなくなることがある。開発環境から実行しているならデバッグメニューから強制終了してください。

TODO:何もしないとActiveMovie Windowとやらが表示されてキャプチャ結果が映されてうざいので散々苦労して非表示にする(存在はしたまま)事ができたのだが、そもそもグラフとやらの組み方次第では出てこないようにできると思うので調べておこう。

ARToolkitPlusとDShowNETとWPFの組み合わせで動いたー



やっぱりカメラのキャリブレーションファイルの形式がARToolkit本家とARToolkitPlusとで違っていた。でも、ARToolkitPlusについてきたキャリプレーションツールはビルドにてこずっている。ていうか無理。もうプリプロセッサが存在する言語と付き合うのは無理。ヘッダファイルという存在が吐き気がする。

というわけでとりあえずARToolkitPlusに付属の適当なカメラのキャリブレーションファイルを使用してみたらあっさりと上手く行ってしまった。イエイ!正しいキャリブレーションじゃないので少し歪むのはご愛嬌。

面白いのは、本家のARToolkitはパターンファイルをプログラムで読み込んで、それと同じパターンをマーカとして撮影すると認識されるという仕組みなのに対し、ARToolkitでは二次元バーコードを用いる。つまり、プログラム側でパターンを登録する必要なしに、Simpleバージョンのコードなら512種類、BCHバージョンなら4096種類のマーカを自由に使える。プログラム側では認識したマーカのid番号が取得できる。この画像ではウィンドウのタイトルに、4番目のidのマーカが認識されたと表示されているが、0番から4番まで撮影するマーカを取り替えるとちゃんとこのタイトルの表示が変わってちょっとうれしい。

こうなると、当面の目標は複数のマーカを識別して、その上にバーコードに対応する番号を描画するプログラムを作る事だな。

それはそうと、キャリプレーションはどうにかしたいなあ。


改めてポインタを。
ARToolkitPlusの.net用ラッパー:
http://www.brains-n-brawn.com/default.aspx?vDir=wpfaugreal
DShowNET:
http://www.codeproject.com/KB/directx/directshownet.aspx
DShowNETのサンプル:
http://web.sfc.keio.ac.jp/~shokai/archives/2007/01/directshownet-capture-save-image.html

NyARToolkitがいやだったのはそもそもDirectXの経験がない事もあるのだが、DirectX SDKが必要で環境が特異的になってしまう事。一方でARToolkitPlusの.netラッパー+DShowNET+WPFの組み合わせならば、事実上は標準の.net framework環境とbrains-N-brawnによる``ARToolKitPlus - modified to be a DLL and export C-style functions that can be pInvoked from C#''だけで完結する。今のソリューション構成はARToolkitPlusの.netラッパーとDShowNETのプロジェクトをそれぞれ独立させて、それらのビルドターゲットアセンブリを自分のプロジェクトから参照するようにしているが、これは必要ならばひとつのプロジェクトにまとめる事すらできる。

ARToolkitを使う上では、導入しなければいけない物が多すぎて開発のスタートアップ段階で躓きやすいのと、人に配布するとき何をどうすれば良いのかさっぱりわからないというのが問題だと思うのだが、私が今やっているARToolkitPlusの.netラッパー+DShowNET+WPFの組み合わせならば、標準の.net framework環境とbrains-N-brawnによるARToolKitPlusのdllで全て完結する。しかも3DについてはWPFによって容易なコーディングが可能だ。初音ミクがウマウマする動画がニコニコ動画で流行った後、ARToolkitはアカデミックな領域以外ではやはり敷居が高くて一時的なブームに終わった感があるが、この試みをもう少し続けていけば、かなり敷居を下げられると思う。

あれれ?

かなりコードが整理されてきて、もう動かない理由なんか何もないのだが、うーん、サンプルと同じ事をキャプチャした動画に対してやっているだけなのに動かない。

ひょっとしたらカメラのキャリブレーションファイルに、ARToolkitPlusとARToolkitで互換性がないのかもしれないと思ってARToolkitPlusを落としてきたが、ツールのバイナリがなくC++のコードのみ。これでもARToolkitと違って整理されたクラスライブラリになっているので一部では歓迎されたようだが、俺としては、もうC#以外触りたくない。せめてJavaくらいだ、許せるのは。あ、VBのVer.6までは別格ね。

というポリシーを曲げて現在Visual C++ 2008 express editionをインストール中。プリプロセッサ使いまくりのコードだったらやだなあ。ビルド通すのに数日かかったりしないだろうな。

directshow関連

ARToolkitをWPFで動かすサンプルが手に入ってわりとうまく行っているので、PCカメラからのキャプチャの方法をメモ:

http://web.sfc.keio.ac.jp/~shokai/archives/2007/01/directshownet-capture-save-image.html
http://www.codeproject.com/KB/directx/directshownet.aspx
http://www.codeproject.com/KB/directx/directxcapture.aspx

2009年2月3日火曜日

ARToolkit+WPFのサンプルらしきものを見つけた

http://www.hitlabnz.org/forum/showthread.php?t=546
http://www.brains-n-brawn.com/default.aspx?vDir=wpfaugreal

NyARToolkit with WPFの途中経過

980円で買ってきた超安物のPCカメラでもサンプルプログラムはちゃんと動いてなかなか面白かった。

自分で書いたWPFアプリケーションのコードも、PCカメラからキャプチャした動画を表示してマーカーを検出する所までは簡単にできた。

問題は、やはりマーカー座標系からカメラ座標系への変換と射影変換。

private Matrix3D NyARDoubleMatrix34ToMatrix3D(NyARDoubleMatrix34 mat)
{
return new System.Windows.Media.Media3D.Matrix3D
(mat.m00,mat.m10,mat.m20,0,
mat.m01,mat.m11,mat.m21,0,
mat.m02, mat.m12, mat.m22, 0,
mat.m03,mat.m13,mat.m23,1);
}

こういうメソッドを作っておいてViewport3D.Cameraの子要素にしたMatrixCameraオブジェクトであるMainViewport3DCameraに対して

//AR用カメラパラメタファイルをロード
m_ap = new NyARParam();
m_ap.loadARParamFromFile(Properties.Settings.Default.AR_CAMERA_FILE);
m_ap.changeScreenSize(Properties.Settings.Default.Width, Properties.Settings.Default.Height);

//投影行列をAR用カメラパラメタから設定
NyARPerspectiveProjectionMatrix ProjectionMatrix = m_ap.getPerspectiveProjectionMatrix();
MainViewport3DCamera.ProjectionMatrix = NyARDoubleMatrix34ToMatrix3D(ProjectionMatrix);

こんな感じで射影行列を設定。んでもってModelVisual3DオブジェクトであるcubeVisual3Dに対して

cubeVisual3D.Transform=new MatrixTransform3D(NyARDoubleMatrix34ToMatrix3D(result_mat));

こんな感じで変換行列を設定。

コードとしてはこんな感じで良いはずなのだが、何かを勘違いしていて動かない。

そもそも変換行列の類は意味合いがライブラリ/フレームワークでみんな異なるからなあ。でも、行列の行-列番号のアサインとかちゃんと確かめたんだけどなあ。

ARTOOLKITとNyARTOOLKIT事始め

NyARTOOLKIT For C#を使い、キャプチャはキット任せ、グラフィックはWPFの3D描画を使って何かやってみよう作戦の覚書:

NyARTOOLKITを使うので、ARTOOLKITはソースは必要なく、キャリブレーションやパターンデータファイルの作成用ツールをバイナリで入手できれば十分。なのでSourceForgeのページからARToolKit-2.72.1-bin-win32.zipをダウンロードして適当な場所に解凍しておく。

これらバイナリの使用にはGLUTが必要。Nateのページからglut-3.7.6-bin.zipをダウンロードして中身のglut32.dllをsystem32フォルダにコピー。Windows SDKをインストールしているので、せっかくだからついでにc:\program files\Microsoft SDKs\Windows\v6.1フォルダ以下のInclude\glフォルダにglut.hを、Libフォルダにglut.libをコピーしておく。

NyARTOOLKITはDirectX SDKを必要とする。ここからDirectX SDK (November 2008)をダウンロード。

NyARTOOLKITはここからNyARToolkitCS-2.1.1.zipをダウンロード。適当に解凍。DirectX SDKが導入されていれば直ちにforFW2.0\NyARToolkitCS.slnをビルド可能。せっかくなのでここにあるUma2Desktop(for Windows(.Net Framework))をダウンロード。これも直ちにビルド可能。

TODO:
1.描画をWPFにやらせたいので、NyARToolkitのCaptureTestサンプルアプリケーションを下にスケルトンを作る
2.工学ナビの記事を下にARToolkit付属のバイナリを使ってパターンファイルとキャリブレーションファイルを作ってみる。

2009年2月2日月曜日

スパム?

アクセス解析を見ていると定期的に
http://www.blogger.com/navbar.g?targetBlogID=ほにゃららほにゃらら
こんな感じの参照元からのアクセスのバーストがある。何だろうこれ。スパムにしては効率悪いよなあ。

と思っていたら今日モノホンのコメントスパムが。Cheap Air Jordan 、Nike Dunk and Silver Tiffanyがどーたらこーたら。容赦なく削除した。blogspotってcaptcha通らないとコメントできなかったよなあ。こうしてまたひとつ、スパムボットが知性認定を受けたのであった。

#captchaってのは本来、高度な人工知能は知性を持つと言えるのか、との問いに対し、「ある種のテストをパスするのならば実際上区別しようがなくなるので知性があると認めるしかないだろう」と、確かチューリングが提案した概念。すなわちその満たすべき性質とは、(1)人間は必ずパスできる(2)ショボイ人工知能にはパスできない、である。だからcaptchaが破られるのは人工知能の性能向上という喜ばしい事態だし、人間ですらパスするのが容易でないテストを作ってスパム耐性を高めるというのは本来の意味のcaptchaとしては本末転倒なのだ。俺、最近しょっちゅうcaptchaに弾かれるのだが、本当に人間なのか不安になるぞ。
#で、「んなこと言ったって機械は機械だよ。知性だなんて認めたくないよやだやだ」と駄々をこねたのがいわゆる中国人の部屋のパラドックス。パラドックスというより、アンドロイド差別宣言だね。

し、しどい・・・


金曜日にロングポジションが完璧に失敗して12000円の追証が発生。でもどう考えても平均線からの強い反発なので今日の下げで追証解消できるだろうと7980でドテンショートした。

で、今日の動き。何だよこれ。9時30分くらいに7800を割ったタイミングで返済すれば余裕で首はつながった。でも、一度返済してしまうと大引けまで資金が拘束されるので前場引けかせめて10時半まで引っ張る事に決めていたのでそのまま放置していたらひたすら強い上げ相場に。今日上げる意味がさっぱり分からない。これ絶対後場で前場安値の7790を割ってダダ下げの展開になるよ。ムキー。

まあ、現在の健康状態ではデイトレはどうにも負担になりすぎるので、手を引く良い機会になったということで。

製作者がこの体たらくでは「身も蓋もない」の説得力も何もあったものじゃないわけですが、まあ皆さん私を反面教師にして、欲を出してオーバーナイトなんてせずにデイトレに徹して、「身も蓋もない」を活用してください。こういう展開になった以上ちょっとずるい引用の仕方だけど、「身も蓋もない」をデイトレに本格的に活用した三週間の記録はこちらにあります。

本当に、スイングスタイルに切り替えた途端に外しまくって一週間で一気に損が膨らんだのには肝が冷えた。レバレッジの大きさを全然理解していなかった。しかしデイトレに限ればならば225先物は実に魅力的な投資対象になり得る。いつか機会があったらまた参戦したいな。誰か種になるくらいの量の寄付をアドインに対してしてくれないかなあ。

というわけでタイジョー。ちゃんちゃん。

2009年2月1日日曜日

はっちゅう君プラスのアドインライブラリのAddInFormの使い方(改)

何度か取り上げたAddInFormの拡張の仕方。はっちゅう君プラス本体を最小化した時にフロートウィンドウに対するドックインジケータが表示されるというのは勘違いだった。よって、(1)フロート時に最前面に表示する(2)フロートウィンドウが起動時に復元された時サイズも正しく復元する、の二点のみを解決すれば良い。コードはこんな感じ。

public class ほにゃらら : AddInForm
{
#region メソッド
private void setIndependentWindowOrDependentWindow()
{
if (this.DockState == DockState.Float) {
this.TopMost = true;
this.TopLevel = true;
} else {
this.TopMost = false;
this.TopLevel = false;
}
}
#endregion

#region カスタムしたメソッド
protected override string GetPersistString()
{
//インスタンス毎に保持する設定を永続化文字列に", "に続けて繋げて返す。
return base.GetPersistString() + ", " + ほにゃらら;
}
protected override void OnDockStateChanged(EventArgs e)
{
setIndependentWindowOrDependentWindow();
base.OnDockStateChanged(e);
}
protected override void OnSizeChanged(EventArgs e)
{
if (this.DockState == DockState.Float) {
this.ClientSize = this.ClientSize;
}
base.OnSizeChanged(e);
}
protected override void OnLoad(EventArgs e)
{
setIndependentWindowOrDependentWindow();
base.OnLoad(e);
}
#endregion
}

ただ、起動時に復元されたフローティングフォームの最前面表示の設定(TopMost=true)が有効にならない問題は未だ解決せず。まあ、ビルトインの注文フォーム(フロート)でも同じ問題があるし、ここまでやれば御の字だろう。


追記:上記問題は、setIndependentWindowOrDependentWindowメソッドを

private void setIndependentWindowOrDependentWindow()
{
if (this.DockState == DockState.Float) {
this.TopMost = true;
this.TopLevel = true;
} else {
this.TopMost = true;//ポイント!
this.TopLevel = false;
}
}

と変更すると解決するようだ。どうやら、はっちゅう君プラスの起動時にフォームが復元される時、DockStateがDockState.Floatにセットされていないのが問題のようだが、ドッキングフォームの場合TopMostプロパティに意味はないので、フロート状態であるか否かに関わらずTopMostプロパティをtrueにしてやれば良いという事らしい。

最近の体調

最近、朝6時くらいには目が覚めてしまって、それから1~2時間、思考力・集中力の阻害される症状が和らぐ時間帯がある。その代わりこの時間帯は筋肉痛がひどく、耐え難い。