- Modification -
ベジエ曲線ツールのドローオブジェクトの作成の実装

■ ベジエ曲線ツールのドローオブジェクトの作成に必要なもの
じゃ、ベジエ曲線ツールの実装の話だ。まず、必要なインスタンス変数を考えてみよう。
必要なものは、ポリゴンツールのときと変わらない。各点の配列と、パスが閉じているかどうかを示すフラグだ。
|
Sketch/DocumentModel.subproj/SKTBezierPath.h (created by mkino)
|
@interface SKTBezierPath : SKTGraphic {
@private
NSMutableArray* _points;
BOOL _isPathClosed;
...
}
|
ただし、点の内容はポリゴンツールのときとは違うんだ。ベジエ曲線は、線が通るアンカーポイントと、その線の曲がり具合を制御するコントロールポイントでできているんだ。アンカーポイント 1 つにつき、コントロールポイント 2 つが定義される。
SKTBezierPath クラスでは、この 2 種類の点を 1 つの配列に突っ込むことにした。突っ込む順番は、最初のアンカーポイント、最初のアンカーポイントの後ろ側のコントロールポイント、2 つめのアンカーポイントの前側のコントロールポイント、2 つめのコントロールポイント、2 つめのアンカーポイントの後ろ側のコントロールポイント、、、っていう順序だ。
| index |
kind of point |
| 0 |
Anchor point 0 |
| 1 |
Control point 0, 1 |
| 2 |
Control point 1, 0 |
| 3 |
Anchor point 1 |
| 4 |
Control point 1, 1 |
| 5 |
Control point 2, 0 |
| 6 |
Anchor point 2 |
点は、アンカーポイントで始まって、必ずアンカーポイントで終わるものとする。だから、最初の 1 つめの点以降は、追加するときは 3 つの点をセットで追加するようにする。パスが閉じているときは、最初のアンカーポイントと最後のアンカーポイントの座標を同じにするんだ。
で、実際に実装するには creatWithEvent:inView: をオーバーライドするんだ。
■ ポリゴンツールのドローオブジェクトの作成の実装
で、実際の実装の話なんだけど、いつも通り createWithEvent:inView: をオーバーライドする。ポリゴンのときと結構似ているので、そちらと見比べてみてくれ。
|
Sketch/DocumentModel.subproj/SKTBezierPath.m (created by mkino)
|
- (BOOL)createWithEvent:(NSEvent *)event inView:(SKTGraphicView *)view {
NSPoint point = [view convertPoint:[event locationInWindow] fromView:nil];
NSPoint lastAnchorPt;
NSRect drawingBounds, lastDrawingBounds;
BOOL snapsToGrid = [view snapsToGrid];
float spacing = [view gridSpacing];
BOOL echoToRulers = [[view enclosingScrollView] rulersVisible];
NSEventType type;
[self startBoundsManipulation];
if (snapsToGrid) {
point.x = floor((point.x / spacing) + 0.5) * spacing;
point.y = floor((point.y / spacing) + 0.5) * spacing;
}
[self setBounds:NSMakeRect(point.x, point.y, 0.0, 0.0)];
if (echoToRulers) {
[view beginEchoingMoveToRulers:[self bounds]];
}
// Append first anchor point
[self appendFirstAnchorPoint:point];
lastAnchorPt = point;
lastDrawingBounds.origin = point;
lastDrawingBounds.size.width = 1;
lastDrawingBounds.size.height = 1;
// Tracking loop
while (1) {
event = [[view window] nextEventMatchingMask:
(NSLeftMouseDownMask |
NSLeftMouseDraggedMask |
NSLeftMouseUpMask)];
point = [view convertPoint:[event locationInWindow] fromView:nil];
if (snapsToGrid) {
point.x = floor((point.x / spacing) + 0.5) * spacing;
point.y = floor((point.y / spacing) + 0.5) * spacing;
}
type = [event type];
if (type == NSLeftMouseDown && [event clickCount] == 1) {
// Append points for a bezier path
[self appendAnchorPoint:point
controlPoint1:_lastControlPoint
controlPoint2:point];
lastAnchorPt = point;
_lastControlPoint = point;
}
else if (type == NSLeftMouseDragged) {
int index = [self indexOfLastAnchorPoint];
NSPoint flippedCtrlPt;
flippedCtrlPt.x = 2 * lastAnchorPt.x - point.x;
flippedCtrlPt.y = 2 * lastAnchorPt.y - point.y;
// Move the last control point
if (index != 0) {
[self movePointAtIndex:index - 1 toPoint:flippedCtrlPt];
}
_lastControlPoint = point;
}
else if (type == NSLeftMouseUp) {
if ([_points count] > 1) {
int index = [self pointUnderPoint:lastAnchorPt];
if (index == 0) {
// Fit to first point
[self movePointAtIndex:[self indexOfLastAnchorPoint]
toPoint:[self pointAtIndex:0]];
// Close path and go out from loop
_isPathClosed = YES;
break;
}
}
}
// Set bounds and calculate drawing bounds
[self setBounds:[[self bezierPath] bounds]];
drawingBounds = [self drawingBounds];
[view setNeedsDisplayInRect:
NSUnionRect(drawingBounds, lastDrawingBounds)];
lastDrawingBounds = drawingBounds;
if (echoToRulers) {
[view continueEchoingMoveToRulers:[self bounds]];
}
// In case of double click
if ([event clickCount] >= 2) {
break;
}
}
if (echoToRulers) {
[view stopEchoingMoveToRulers];
}
[self stopBoundsManipulation];
return !NSEqualRects([self bounds], NSZeroRect);
}
|
処理の流れも、ポリゴンのときと同じく、
- イベントの待ち
- アンカーポイントとコントロールポイントのハンドル
- 再描画領域の確定
っていう流れになっている。これらのうち、イベントの待ち、と、再描画領域の確定、の部分は、ポリゴンのときとほとんど同じなので、アンカーポイントとコントロールポイントのハンドルにしぼって見てみよう。
◆ アンカーポイントとコントロールポイントのハンドル
このメソッドでは、トラッキングループを作って、その中でアンカーポイントと、コントロールポイントをセットしているんだ。トラッキングループでは、NSLeftMouseDown、NSLeftMouseDragged、NSLeftMouseUp のイベントを待っている。それぞれを見てみよう。
- NSLeftMouseDown、アンカーポイントとコントロールポイントの追加
まず、マウスを押したとき。このときは、そのポイントを新しいアンカーポイントとして追加する。同時に、そいつの前 2 つ分のコントロールポイントも追加する。下の図でいうと、Anchor point 1 を追加するときは、Control point 0, 1 と Control point 1, 0 を追加するんだ。
Control point 1, 1 は、この時点では追加されない。次のアンカーポイントを追加するときに、されるんだ。
|
Sketch/DocumentModel.subproj/SKTBezierPath.m (created by mkino)
|
- (BOOL)createWithEvent:(NSEvent *)event inView:(SKTGraphicView *)view {
...
// Tracking loop
while (1) {
...
if (type == NSLeftMouseDown && [event clickCount] == 1) {
// Append points for a bezier path
[self appendAnchorPoint:point
controlPoint1:_lastControlPoint
controlPoint2:point];
lastAnchorPt = point;
_lastControlPoint = point;
}
...
}
|
Control Point 1, 0 は、この時点ではアンカーポイントといっしょ。ドラッグしたときに、動かされる。
- NSLeftMouseDragged、コントロールポイントの移動
続いてマウスのドラッグのとき。ここではコントロールポイントを移動する。
|
Sketch/DocumentModel.subproj/SKTBezierPath.m (created by mkino)
|
- (BOOL)createWithEvent:(NSEvent *)event inView:(SKTGraphicView *)view {
...
// Tracking loop
while (1) {
...
else if (type == NSLeftMouseDragged) {
int index = [self indexOfLastAnchorPoint];
NSPoint flippedCtrlPt;
flippedCtrlPt.x = 2 * lastAnchorPt.x - point.x;
flippedCtrlPt.y = 2 * lastAnchorPt.y - point.y;
// Move the last control point
if (index != 0) {
[self movePointAtIndex:index - 1 toPoint:flippedCtrlPt];
}
_lastControlPoint = point;
}
...
}
|
現在のポイントがコントールポイントの 1 つになる。もう 1 つのコントロールポイントは、アンカーポイントを挟んで、反対側になる。それを求めたものが、flippedCtrlPt だ。そして、その値で、配列の中にあるコントロールポイントの値を変更してやる。これで、ドラッグに応じてコントロールポイントが動くぜ。
- NSLeftMouseUp、パスを閉じる
で、最後。マウスのボタンを離したとき。ここでは、パスのクローズの処理が必要になる。
最後のアンカーポイントの位置が、最初のアンカーポイントの範囲に入るなら、最後のポイントと最初のポイントを完全に重ねて、パスを閉じるんだ。
|
Sketch/DocumentModel.subproj/SKTBezierPath.m (created by mkino)
|
- (BOOL)createWithEvent:(NSEvent *)event inView:(SKTGraphicView *)view {
...
// Tracking loop
while (1) {
...
else if (type == NSLeftMouseUp) {
if ([_points count] > 1) {
int index = [self pointUnderPoint:lastAnchorPt];
if (index == 0) {
// Fit to first point
[self movePointAtIndex:[self indexOfLastAnchorPoint]
toPoint:[self pointAtIndex:0]];
// Close path and go out from loop
_isPathClosed = YES;
break;
}
}
}
...
}
|
これで、ベジエ曲線オブジェクトの出来上がり。
|