6

これは iPad アプリケーション用ですが、基本的には数学の問題です。

線幅が変化する (単調に増加する) 円弧を描く必要があります。曲線の始点では、最初の太さ (2 ポイントとしましょう) を持ち、弧の終点で最大の太さ (12 ポイントとしましょう) になるまで、太さは滑らかに増加します。

これを作成する最善の方法は、UIBezierPath を作成して形状を塗りつぶすことです。私の最初の試みは、2 つの円弧 (中心がずれている) を使用することでした。これは 90° までは問題なく動作しましたが、円弧は多くの場合 90° から 180° の間になるため、この方法ではうまくいきません。

厚さが増加する 90 度円弧の例

私の現在のアプローチは、ベジェ クワッドまたはキュービック カーブを使用して、わずかならせん (円弧からわずかに成長し、もう 1 つがわずかに縮小する) を作成することです。問題は、円弧からの偏差 (別名、形状の「厚さ」) が必要な値になるように、コントロール ポイントをどこに配置するかです。

制約:

  • 形状は、任意の角度 (互いに 180° 以内) で開始および終了できる必要があります。
  • 形状の「厚さ」(円からの偏差) は、指定された値で開始および終了する必要があります
  • 「厚さ」は単調に増加する必要があります(大きくなってから再び小さくなることはできません)
  • 滑らかに見える必要があります。急な曲がりがあってはなりません。

私は他のソリューションにもオープンです。

4

2 に答える 2

5

私のアプローチは、2 つの円弧を作成し、その間の領域を埋めるだけです。難しいのは、これらの円弧の中心と半径を計算することです。厚さが大きすぎない限り、かなり良いように見えます。(カット アンド ペーストして、必要に応じて自分で判断してください。) クリッピング パスを使用することで改善される可能性があります。

- (void)drawRect:(CGRect)rect
{
  CGContextRef context = UIGraphicsGetCurrentContext();

  CGMutablePathRef path = CGPathCreateMutable();

  // As appropriate for iOS, the code below assumes a coordinate system with
  // the x-axis pointing to the right and the y-axis pointing down (flipped from the standard Cartesian convention).
  // Therefore, 0 degrees = East, 90 degrees = South, 180 degrees = West,
  // -90 degrees = 270 degrees = North (once again, flipped from the standard Cartesian convention).
  CGFloat startingAngle = 90.0;  // South
  CGFloat endingAngle = -45.0;   // North-East
  BOOL weGoFromTheStartingAngleToTheEndingAngleInACounterClockwiseDirection = YES;  // change this to NO if necessary

  CGFloat startingThickness = 2.0;
  CGFloat endingThickness = 12.0;

  CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
  CGFloat meanRadius = 0.9 * fminf(self.bounds.size.width / 2.0, self.bounds.size.height / 2.0);

  // the parameters above should be supplied by the user
  // the parameters below are derived from the parameters supplied above

  CGFloat deltaAngle = fabsf(endingAngle - startingAngle);

  // projectedEndingThickness is the ending thickness we would have if the two arcs
  // subtended an angle of 180 degrees at their respective centers instead of deltaAngle
  CGFloat projectedEndingThickness = startingThickness + (endingThickness - startingThickness) * (180.0 / deltaAngle);

  CGFloat centerOffset = (projectedEndingThickness - startingThickness) / 4.0;
  CGPoint centerForInnerArc = CGPointMake(center.x + centerOffset * cos(startingAngle * M_PI / 180.0),
                                          center.y + centerOffset * sin(startingAngle * M_PI / 180.0));
  CGPoint centerForOuterArc = CGPointMake(center.x - centerOffset * cos(startingAngle * M_PI / 180.0),
                                          center.y - centerOffset * sin(startingAngle * M_PI / 180.0));

  CGFloat radiusForInnerArc = meanRadius - (startingThickness + projectedEndingThickness) / 4.0;
  CGFloat radiusForOuterArc = meanRadius + (startingThickness + projectedEndingThickness) / 4.0;

  CGPathAddArc(path,
               NULL,
               centerForInnerArc.x,
               centerForInnerArc.y,
               radiusForInnerArc,
               endingAngle * (M_PI / 180.0),
               startingAngle * (M_PI / 180.0),
               !weGoFromTheStartingAngleToTheEndingAngleInACounterClockwiseDirection
               );

  CGPathAddArc(path,
               NULL,
               centerForOuterArc.x,
               centerForOuterArc.y,
               radiusForOuterArc,
               startingAngle * (M_PI / 180.0),
               endingAngle * (M_PI / 180.0),
               weGoFromTheStartingAngleToTheEndingAngleInACounterClockwiseDirection
               );

  CGContextAddPath(context, path);

  CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
  CGContextFillPath(context);

  CGPathRelease(path);  
}
于 2012-07-08T04:24:09.710 に答える
1

解決策の 1 つは、ポリラインを手動で生成することです。これは単純ですが、コントロールが高解像度で表示される場合、生成するポイントの量を拡大する必要があるという欠点があります。iOS/ObjC のサンプル コードを提供できるほど iOS についてよく知りませんが、Python 風の疑似コードを次に示します。

# lower: the starting angle
# upper: the ending angle
# radius: the radius of the circle

# we'll fill these with polar coordinates and transform later
innerSidePoints = []
outerSidePoints = []

widthStep = maxWidth / (upper - lower)
width = 0

# could use a finer step if needed
for angle in range(lower, upper):
    innerSidePoints.append(angle, radius - (width / 2))
    outerSidePoints.append(angle, radius + (width / 2))
    width += widthStep

# now we have to flip one of the arrays and join them to make
# a continuous path.  We could have built one of the arrays backwards
# from the beginning to avoid this.

outerSidePoints.reverse()
allPoints = innerSidePoints + outerSidePoints # array concatenation

xyPoints = polarToRectangular(allPoints) # if needed
于 2012-07-07T20:20:32.283 に答える