円をベジェに変換しようヽ(´∀`)ノ
数学が全然できないシーラカンスでありますが、このたびは円弧を任意の大きさ任意の角度で描画する必要に迫られました。言語はC#なのでGDI+を使えばいいのですが、pinvokeしまくりなプログラムを書いている私にはそんな邪道なことはできません(-_-;)
どうでもいいことですが、HDCハンドルに対してnative APIを使った場合、Graphicsから取得したHDCハンドルに描画すると元のGraphicsに変更が反映されるのですが、GraphicsをHDCハンドルから作成しそのGraphicsに対して描画しても元のHDCハンドルには変更が反映されません。(お経みたいだ……)
なのでHDCハンドルに対してGraphicsクラスのメソッドで描画しようと思った場合、
- HDCからGraphicsを作成
- Graphicsに対してゴニョゴニョ
- Graphicsの内容をHDCにライトバック
のようにする必要があります。
で、任意の角度・大きさの楕円の円弧を描くときには、その曲線情報をベジェに変換する必要があるですが、これがまた難しいのです。参考ページ(http://www.tensyo.com/urame/prog/ALGO.HTM#C1)が無かったらきっとできなかったでしょう。HPの作者さんには感謝です。
すいません、今日は眠いのでまた明日にさせてください。いい夢を。
コードじぇねれーと4
今回で4回目になるコードじぇねれーとですが、おそらく今回が最後です。次の更新がいつになるか自分でも楽しみです^^
ここで、なぜソース生成を行うのか考えてみます。ソース生成を行うと
- 生成元ソースと生成後ソースの同期を常に考える必要がある。
- 生成ソースの更新を手動・もしくは自動でやるための準備が必要になる。
- ソース生成プログラムを別に書く必要がある。
などのデメリットが出てきます。メリットは言語機能の大幅な拡張が行えることです。というか理由はこれしかないでしょう。本当はやりたく無いのです。それにもかかわらず、言語機能が足り無い・どうしても○○ができない、などどうにもならない理由で行うのです。
一般的にこれは言語の欠陥です。その言語の世界で閉じることができないので、しょうがなく外部ツールを持ち出すのです。よく考えてみてください。日本語ではどうしても表現できない○○があるから、そこだけは英語を使わざるを得ない、という状況を。日本語ではカタカナが使えるのでそんな状況は考えられないかもしれませんが、中国語だとすべて漢字で表す必要があり少しは現実味があるでしょうか。とにかく、かなりださいです。
言語機能が足りてないなら、拡張してもらうようMS*1やSunに働きかければ?という考えもあるかもしれません。しかし、それではだめなんですよ。プログラミング言語は自然言語よりよほど単純(というか単純にせざるを得ない)ので、すべての機能を網羅することは実質的に不可能なのです。なので未知の言語の欠陥を埋めようとするなら、言語内に何らかの言語機能拡張機能が必要になるのです。私はそれこそが今まで見てきたソース生成を言語内で行う機能だと思うのです。
マクロは#defineなどで行われる文字列置き換え機能、つまりはソース生成機能です。lispはマクロとS式のおかげでめちゃめちゃな拡張性を得ています。「C++の設計と進化」によると、Bjarne Stroustrup はマクロを言語機能から削除しようとしたができなかったとありますね。マクロに変わる代替機能を提示できなかったためです。最近トレンドの言語では「美しくない」や「危険」という理由で言語機能から削除されているわけですが、マクロが無ければ外部ツールに頼らざるを得ません。本当に正しいのはいったいどちらでしょうね?
*1:MVPな方もいるのでなかなかマイ糞社とは書けなくなったな〜
コードじぇねれーと3
前回、生成先ファイルから生成元ファイルに変更を反映させるにはマクロが使えると書きました。勢いで書いてしまったのですが、よく考えてみると「生成後のソースの変更を生成元のソースに反映させる」というのとはちょっと違うかもしれません(^^ まあ細かいことはおいておきましょう。もしc++で多重継承が使えなかったとしてmix-inはこのように実現できます。
file: module.inc
int m_value;
int getValue() { return m_value; }
void setValue(int value) { m_value = value; }file: test.h
class Test {
public:
Test() {}#include "module.inc"
};(.incはinclude専用ファイルであることを示しています。)
分かるでしょうか。要するにmix-inファイルを別に用意して#includeしてやるだけなんです(もちろん#defineを使っても構いませんがコンパイルエラー箇所の検出が難しくなります)。こうすると生成先に間違いがあった時でも変更は生成元で行われるので、少なくとも変更箇所を記憶する必要はありません。#includeは本当に単純なテキストコピーを行うので、他言語では排斥の対象になっていますが、うまく使うと本当に便利です。
コードじぇねれーと2
前回、C#によるmix-in作成や描画クラス作成など、ソース作成プログラムの例を紹介した訳ですが、実はこれには重大な問題があります。コードジェネレートでは一般的に、生成元ソースに何らかの操作をして新たなソースを作成するわけですが、そうすると生成後のソースに手を入れても生成元のソースにはその変更が反映されません。しごく当たり前なことですが、これが結構困るのです。
たとえば生成後のソースがケアレスミスでコンパイルに通らなかったとします。もちろんコンパイルに通すため生成後のファイルをいじっていくわけですが、コンパイルに通した後はそれと同じコードを出力させるために生成元のソースを変更する必要があります。つまり、生成後のソースでいじった場所をすべて覚えておく必要があります。1・2ヶ所ならいいのですが、10〜20となるとやってられなくなりますし、また変更した後に間違えてソースの再作成を行うと今までの苦労がすべて水の泡となります。自分の気づかないうちに生成先ソースを変更していたりすると悲惨なことになります。
こういった問題があるので、ファイルのバックアップを取ったり「このソースは自動生成されています。編集しないでください。ヽ(`Д´)ノ」といったメッセージを残すのですが、これでは問題の本質は何も解決されません。何が足りないかというと生成先のソースを変更したら、その変更が生成元に反映されるようなメカニズムです。特に前回見たような、ソースをまんまコピーするようなソース作成ではこの機能があるととても幸せになれます。
実はこの機能、C#にこそありませんがcppやcにはあります。そう、おなじみのマクロです。
コードじぇねれーと
[mixin in C#2.0] [id:yaneurao:20060113]
↑でやねうらおさんがC#2.0でmin-inを実現しています。原理は簡単で、外部ツールによってクラス実装をマージしてやります。マージするときにpartialキーワードを使うところが素敵です。
私も以前C#での2D描画クラスをジェネレートするスクリプトを作成しました。2D描画ではジオメトリ処理(矩形コピーや拡大縮小など)とピクセル演算(単純コピー・加算・減算など)を同時に扱う必要があり、うまい方法を考えないとルーチンの数が極端に多くなります。そこで、cppではテンプレートをフル活用したGTL等の実装があり、cでかかれたSDLではマクロを多用することで問題の解決を図っています。しかし、C#にはマクロはなく、ジェネリックを使ってもインライン展開ができないので、ほとほと困ってしまったわけです。
そこで考えた後、外部スクリプトによるソース自動生成を行いました。「ソースを書き換えるときは生成元を書き換える」ということだけ忘れなければなかなかよい解決方法だと思います(^^
他にもポーティングや.netらしく違う言語で書いてしまうといった方法があります。私はpinvokeにかなり頼ったソースを書いていますが、たまによくわからないエラーが発生したりステップイン不可能なので、ポーティングはあまりやりたくないです。理想は後者だと思うんですが、.netで他言語との提携の仕方がわからないというヘタレゆえの問題があり見送りました。
スクリプト言語Lua4
いろいろ考えた結果、おそらく一番簡単なのが、luabind用にクラスをエクスポートするジェネレータを書くことです。
class_(L, "Test") .def("hoge", &Test::hoge);
luabind を使うとこんな感じで簡単にクラスを出力できるので、rubyやperlでこれをやってくれるスクリプトを書けばOKです。javadoc風コメントを使っているなら、@lua指定のあるクラスだけ出力するようにすれば便利だと思います。
実装上の注意としては
- メソッドのオーバーロードをどうするか?
- クラス継承をどうするか?
- publicメソッドだけ抽出するのが意外と面倒。
- luabindが複雑なので出力を1ファイルにまとめてしまうとコンパイルできない(vc6で大量のクラスを出力するとインターナルエラー)ことがある。
ことぐらいで、これさえ考えれば後は楽勝です。
■
大変遅れましたが、あけましておめでとうございます。日記の更新はさぼりがちですが、暇なときにでも見てやってください。
ちょうど10月頃に某企画からプログラミングの仕事を頂き、今はそれをやっている状態です。