2012-07-07 25 views
6

Dies ist für eine iPad-Anwendung, aber es ist im Wesentlichen eine mathematische Frage.Mit einer Bezier-Kurve eine Spirale zeichnen

Ich muss einen Kreisbogen mit unterschiedlicher (monoton steigender) Linienbreite zeichnen. Am Anfang der Kurve hätte es eine Anfangsdicke (sagen wir 2 Punkte) und dann würde die Dicke gleichmäßig bis zum Ende des Bogens ansteigen, wo es die größte Dicke hätte (sagen wir 12 Punkte).

Ich denke, der beste Weg, dies zu erreichen ist, indem Sie einen UIBezierPath erstellen und die Form ausfüllen. Mein erster Versuch war es, zwei Kreisbögen (mit versetzten Zentren) zu verwenden, und das hat bis zu 90 ° funktioniert, aber der Bogen wird oft zwischen 90 ° und 180 ° liegen, so dass der Ansatz ihn nicht schneidet.

example of 90 degree arc with increasing thickness

My aktuelle Ansatz ist eine leichte Spirale zu machen (eine geringfügig von den Kreisbogen wuchsen und eine geringfügig schrumpfen) Bezier-quad oder kubische Kurven verwenden. Die Frage ist wo setze ich die Kontrollpunkte so, dass die Abweichung vom Kreisbogen (aka die Form "Dicke") der Wert ist, den ich will.

Constraints:

  • Die Form muss in der Lage sein, in einem beliebigen Winkel zu beginnen und enden (innerhalb von 180 ° voneinander)
  • Die „Dicke“ der Form (Abweichung vom Kreis) sind beginnen und mit den gegebenen Werten
  • die „Dicke“ monoton erhöhen enden muss (es ist nicht immer größer und dann wieder kleiner)
  • es für das Auge glatt auszusehen hat, kann es keine scharfen Kurven sein

Ich bin offen für andere Lösungen.

Antwort

5

Mein Ansatz baut nur zwei Kreisbögen und füllt den Bereich dazwischen. Das schwierige Bit ist, die Zentren und Radien dieser Bögen herauszufinden. Sieht ziemlich gut aus, vorausgesetzt die Dicken sind nicht zu groß. (Ausschneiden und einfügen und selbst entscheiden, ob es Ihren Anforderungen entspricht.) Könnte möglicherweise durch Verwendung eines Beschneidungspfads verbessert werden.

- (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); 
} 
+0

Das sieht wirklich gut aus! Du hast mir eine Menge Arbeit erspart. Dies ist so viel einfacher als der Ansatz, an dem ich arbeitete (die Bezier-Polynomgleichungen für die Spirale zu lösen). Ich habe es für ein Vielfaches von 90 ° arbeiten lassen, aber willkürliche Winkel würden ein Schmerz sein. Das ist viel besser ... –

+0

@JonHull Froh, dass es dir gefällt. Ich habe gerade gemerkt, dass ich implizit davon ausgegangen bin, dass "endingThickness" = "startingThickness", aber du solltest deine Eingabeparameter leicht so arrangieren können, dass diese Bedingung erfüllt ist. Wenn nicht, kann es Szenarien geben, in denen die "projectedEndingThickness" negativ ist, und dann kann ich mich bezüglich der Algebra nicht mehr sicher sein. Es könnte noch funktionieren, aber ich habe es nicht getestet. – inwit

+0

Oh gute Arbeit bro ,,,, du bist ein echter Lebensretter ,,, danke – Dhiru

1

Eine Lösung könnte sein, eine Polylinie manuell zu generieren. Dies ist zwar einfach, hat aber den Nachteil, dass Sie die Anzahl der Punkte erhöhen müssen, die generiert werden, wenn das Steuerelement in hoher Auflösung angezeigt wird. Ich weiß nicht genug über iOS Sie geben iOS/ObjC Beispielcode, aber hier ist einige Python-ish Pseudo-Code:

# 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 
+0

Danke für den Pseudocode. Dies wird mein Backup sein, wenn ich keine Lösung finden kann, die Bezier-Kurven oder -Bögen verwendet. –