太さの変わるBezier曲線の生成 - その26 [考え中 - 太さの変わるBezier曲線]
今日もまた送別会があった。今日は初めて僕より年上。こないだのことがあったのでもったいないけどあまり飲まずにかえってきた。来月すぐまたある。しかしこう送別会が続くと出費がかさんで苦しい。
さて、AGG方式のSmoothingのNSBezierPathのカテゴリとしての実装の続き。今回はアルゴリズムそのものの実装。
Smoothingの中間点位置の計算
それでは具体的にAGG方式で制御点の位置を計算するメソッドgetControlPoints:WithAnchors:andKValue:。これは、端点座標保持用のクラスDCAnchorsOfSubPathのプライベートメソッド。
typedef NSPoint DCVector; static float distanceBetween(NSPoint *p0, NSPoint *p1); static DCVector vector(NSPoint *to, NSPoint *from); static NSPoint addVectorToPoint(NSPoint *pos, DCVector *vector); static DCVector scalarMultOfVector(DCVector *vector, float scalar); - (void)getControlPoints:(NSPointArray)controls WithAnchors:(NSPointArray)points andKValue:(float)kvalue { float lneg = distanceBetween(points, points + 1); float lpos = distanceBetween(points + 1, points + 2); if ((lneg <= 0.0f) || (lpos <= 0.0f) || NSEqualPoints(points[0], points[2])) { // (1) controls[0] = points[1]; controls[1] = points[1]; return; } else { float leng = lneg + lpos; float alphaneg = kvalue * lneg / leng; float alphapos = kvalue * lpos / leng; DCVector difvec = vector(points, points + 2); DCVector negvec = scalarMultOfVector(&difvec, alphaneg * 0.5f); DCVector posvec = scalarMultOfVector(&difvec, -alphapos * 0.5f); controls[0] = addVectorToPoint(points + 1, &negvec); controls[1] = addVectorToPoint(points + 1, &posvec); } }最初のいくつかのC関数は2次元のベクトル演算のための関数である。うっかりNSPointへのポインタを引数に受ける関数を書いてしまって、NSEqualPoints()と一貫性がなくなってしまった。美しくないけど、間違えばコンパイラが教えてくれるので修正は後回しにする。
まず最初に制御点間の距離を計算して、少なくとも一方の距離が0、あるいは両側の端点が一致している場合はAGG方式ではSmoothingできないので中間制御点を端点に一致させて終わる。
それ以外の場合、Smoothingが可能なので式-95に従って計算しているだけである。
本当は両側の端点が一致している場合だけ排除すればいいのだけど、距離0の場合はSmoothingにならない(微係数が連続にならない)のでやるだけ無駄で、計算量を減らすためにこうしている。さらには、サブパスの端点を取り出すときに同一座標のの端点が連続するときは1点に集約するか、逆に同じ座標を繰り返すところではSmoothingされないので例えばハート型のしっぽのような点を含めたい場合はそうすればよい。どうするかはSmoothingの仕様による、ということになる。
制御点を使って曲線を引く
計算した制御点を使って曲線を描き直すメソッド、appendSubPathTo:WithControls:は
- (void)appendSubPathTo:(NSBezierPath *)path WithControls:(NSPoint *)controls { int count = [anchors count]; int c; [path moveToPoint:[[anchors objectAtIndex:0] pointValue]]; for (c = 0 ; c < count - 1 ; c ++) [path curveToPoint:[[anchors objectAtIndex:c + 1] pointValue] controlPoint1:controls[2 * c] controlPoint2:controls[2 * c + 1]]; if (isClosed) [path closePath]; }これも簡単。curveTo~メソッドで順番に描いていくだけ。
これで実装は全部おしまい。実際に動作させたらどうなるか、次に見る。
コメント 0