SSブログ

曲がった迷路その32 - NSAffineTransformの原点は? [曲がった壁を持つ迷路の生成]

こないださらした曲がった壁を持つ迷路生成のアプリを作る上でちょっとハマった問題について。

NSAffineTransformの原点と描画の原点

これまでNSAffineTransformを使ったことがなかった。今回、Bezier曲線の座標はポテンシャルの格子間隔を1.0にしたので、ウィンドウに貼付けたNSViewの大きさと違っている。これをフィットさせるためにNSAffineTransformを使おうとしてハマった。

どうしても描画領域が左下にはみ出る。なぜだかわからなかった。 アフィン変換のマトリクスはAppleのドキュメントでは
0726eq83.png
となっていて、マトリクスを後からかける書き方になっているけど、ごく普通の定義になっている。

これでこんなコードを実行させてみる。NSViewのサブクラスを作ってdrawRect:の中に
- (void)drawRect:(NSRect)rect {
    NSBezierPath        *path = [NSBezierPath bezierPathWithRect:[self bounds]];
    NSAffineTransform   *tran = [[NSAffineTransform alloc] init];
    [tran scaleBy:0.5f];                        //(1)
    [path setLineWidth:4.0f];
    [[NSColor purpleColor] set];
    [path stroke];                              //(2)
    [[NSColor redColor] set];
    [[tran transformBezierPath:path] stroke];   //(3)
    [tran set];                                 //(4)
    [[NSColor blackColor] set];
    [path stroke];                              //(5)
    [tran release];
}
と書いて、このNSView(のサブクラス)をウィンドウに貼付ける。
つまり、NSBezierPathを、NSViewの大きさいっぱいの四角で初期化する。また、NSAffineTransformを作って、(1)で0.5倍のスケーリングを設定する。

(2)で、まずBezier pathをそのまま描く。これはNSViewの大きさいっぱいになるので、その内側にある線幅の半分だけが見えるはずである。

(3)でそのBezierの四角にアフィン変換をほどこした新しいbezier曲線を作ってそれを描画する。NSViewの原点が左下なのでその隅が一致した辺の長さが半分の四角形が書かれるはずである。

(4)では作ったアフィン変換を作用させて、(5)でもとのBezier曲線を描いている。

普通に考えれば(3)で描いたのと(5)で描いたのは一致するはずである。ところが図-36のようになった。
0725fig36.png
図の紫色の線は(2)で描いたNSViewいっぱいの線である。赤はアフィン変換したBezier曲線で、黒は同じアフィン変換を設定してもとのpathを書いたものである。赤と黒が一致していない。

いろいろ試してみて、図-37のようにインターフェイスビルダの上でウィンドウに貼付けたNSViewを、左下をウィンドウに一致させた。
0725fig37.png
で、drawRect:の中身はいっさい変更せず描画したのが図-38である。
0725fig38.png
これを見ると赤い線の上に半分の線幅で黒線が乗っている、つまり大きさは一致している。

これはどういうことかというと、要するにBezier曲線を描画する原点と、アフィン変換を適用する原点がずれている、ということである。

アフィン変換の原点はどこにあるのか、というと上の例からあきらかにウィンドウの左下隅、つまりNSEventに入って返されてくるマウス位置の原点と一致しているようである。

マウス位置を自分のView座標に変換するには普通
    NSEvent *event;
    NSPoint  eventLocation = [event locationInWindow];
    NSPoint  localPoint = [self convertPoint:eventLocation fromView:nil];
とする必要がある。これと同じことをしないといけないらしい。実際にやってみると
- (void)drawRect:(NSRect)rect {
    NSBezierPath        *path = [NSBezierPath bezierPathWithRect:[self bounds]];
    NSAffineTransform   *tran = [[NSAffineTransform alloc] init];
    [tran scaleBy:0.5f];
    [path setLineWidth:4.0f];
    [[NSColor purpleColor] set];
    [path stroke];
    [[NSColor redColor] set];
    [[tran transformBezierPath:path] stroke];   //(6)
    NSAffineTransform   *tran2;                 //(7)
    NSPoint             pnt;
    tran2 = [[NSAffineTransform alloc] init];
    pnt = [self convertPoint:NSZeroPoint toView:nil];//(8)
    [tran2 translateXBy:pnt.x
                    yBy:pnt.y];                 //(9)
    [tran2 scaleBy:0.5f];
    [tran2 set];                                //(10)
    [[NSColor blackColor] set];
    [path stroke];
    [tran release];
}
さっきの(4)以降を変更した。新しいNSAffineTransformのインスタンスを作って、元の座標でのView原点の位置を(8)で(NSViewのconvertPoint:toView:を使って。fromとtoの向きがわかりにくい)得て(9)で平行移動をまず設定した後、スケーリングを設定した。これを実行すると図-39のようになって赤線と黒線が一致した。
0725fig39.png
わかれば簡単なんだけど、気がつくまでずいぶん悩んだ。困ったもんだ。

こういうのどこかにドキュメントされているのかなあ。見当たらなかったんだけど、みんなどうしてるんだろ。

またアフィン変換をsetメソッドで設定するとBezier曲線の線幅もスケーリングされるけどBezier曲線にアフィン変換を作用させて新しいBezier曲線を作った場合には線幅は変わらない。

Cocoaではグラフィクスコンテクストがあらわになっていないのでわかりにくいけど、アフィン変換をsetで設定するというのは現行コンテクストに作用させるということになって大域的な作用をおよぼす。多分アフィン変換のsetは実装上、描画システムの深いところで実現されているのだろう。

nice!(0)  コメント(0)  トラックバック(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

献立08/06献立08/07 ブログトップ

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。