eleqtriq

Ich möchte an dieser Stelle eine Technik vorstellen um kubische Bezier-Kurven in Flash zu zeichnen. Falls kubische Bezier-Kurven Neuland für Euch sind, solltet ihr vorher einen kurzen Abstecher zur Wikipedia machen.

Beispiel öffnen

Ein Nachteil von Flash ist die Tatsache, dass esnicht in der Lage ist kubische Bezierkurven darzustellen. Nutzt man die Flash IDE um eine Kurve von Hand zu zeichnen, wird Flash diese in eine Anzahl kleiner Einzelsegmente zerlegen, und die ursprüngliche kubische Bezierkurve wird mit vielen kleinen quadratischen Bezierkurven genähert (den meisten Flash-Designern wird das schon einmal passiert sein: zeichnet eine einfache Bezierkurve in Flash. Hebt die Auswahl auf und selektiert die Kurve anschließend wieder. Plötzlich ist die Kurve in lauter kurze Abschnitte aufgeteilt, auch wenn sie vorher nur wenige Ankerpunkte hatte). Wenn man eine Kurve mit der Flash Drawing-API zeichnen möchte, gibt es ohnehin keine andere Möglichkeit als die "curveTo()"-Methode, die neben Start- und End- nur einen lausigen quadratischen Kontrollpunkt zulässt.

Generische Mittelpunkt Näherung

Die „normale” Art und Weise, dieses Problem zu handhaben ist eine Technik namens "Generische Mittelpunkt Näherung". Es existieren diverse Actionscript-Klassen, die diese Technik verwenden, um Flash dazu zu bringen kubische Beziwerkurven darzustellen. Das Prinzip hinter der generischen Mittelpunkt Näherung ist im Diagramm links dargestellt. Im Grunde genommen ist es ein Algorithmus um eine kubische Bezierkurve in 4 quadratische Bezierkurven zu zerlegen. Diese Methode ist recht anspruchslos in Bezug auf Rechenleistung und verspricht in den meisten Fällen gute Ergebnisse. Dafür zahlt man aber mit einem Mangel an Präzision. In der Regel fällt das nicht weiter auf, aber manchmal (z. B. bei extrem steilen, oder sich selbst schneidenden Kurven) sind die Ergebnisse "hubbelig" oder unförmig. Es ist halt nicht für jeden Anwendungsfall die beste Lösung.

Die gute Nachricht: es ist nicht unbedingt sehr bekannt, aber in der Tat existiert eine Flash-Klasse für kubische Bezier-Kurven. Sie heisst "BezierSegment" und ist im "fl.motion" Package versteckt. Die schlechte Nachricht: sie wurde nicht dafür vorgesehen, Objekte auf der Bühne darzustellen, sondern für Animationen mittels Actionscript. Mit ein paar kleinen Basteleien lässt sich diese Klasse aber sehr wohl dafür einsetzen, hochpräzise kubische Bezierkurven mittels Actionscript zu zeichnen.

Kubische Bezierkurve mit der BezierSegment-Klasse:

1. Imort der Klasse:

import fl.motion.BezierSegment;

2. Ein neues BezierSegment erstellen:

var bezier:BezierSegment=new BezierSegment(point_1, control_1, point_2, control_2);

3. Das Segment zerlegen:

Die Kordinaten eines Punktes auf dem Bezier-Segment ermittelt man mit der getValue(t:Number)-Methode. t interpoliert einen Wert auf der Kurve, t=1 ist dabei der Maximalwert (der Endpunkt). Wollen wir also die Koordinaten desjenigen Punktes ermitteln, der genau bei 50% auf der Kurve liegt, sagen wir einfach

var val:Point=bezier.getValue(0.5)

Cool, oder?

Beispiel: eine gepunktete Linie

Wir fangen mal mit etwas einfachem an, einer gepunkteten Linie. Zuerst legen wir die Auflösung fest, sagen wir mal 20. Jetzt müssen wir nur noch jede (1/20)te Position einen Punkt zeichnen:

Sorry, no Flash
var resolution:uint=20; var step:Number =1/resolution; var t:Number=0; while(t<=1){ var pos:Point=bezier.getValue(t); with(this.graphics){ beginFill(0x990000, 1); drawCircle(pos.x, pos.y, 5); endFill; } t+=step; }

Das Ergebnis ist eine hübsche gepunktete Linie.

Schön und gut. Aber wie verbindet man die Punkte denn jetzt bitteschön?

Das BezierSegment zerlegen

Kommen wir also zum kniffligen Teil: um die Punkte mit Kurven zu verbinden, brauchen wir die Koordinaten für einen quadratischen Kontrollpunkt. Diesen finden wir folgendermaßen:

1. Wir ermitteln die Tangente durch den ersten und den letzten Punkt unseres Abschnittes. Die geschieht mit dem sogenannten Decasteljau-Algorithmus.

2. Wir suchen den Schnittpunkt der beiden Tangenten und...

3. ...nutzen diesen Schnittpunkt als Kontrollpunkt unserer quadratischen Kurve.

BezierSegment Fehler beim Zerlegen

Manchmal kann es vorkommen, dass die Kurve derart außergewöhnlich ist und gleichzeitig die gewählte Auflösung derart niedrig, dass wir das Segment einmal mehr zerteilen müssen. Anschließend muss erneut geprüft werden, ob der Darstellungsfehler noch besteht und falls ja, müssen die beiden Segmente erneut zerlegt werden, solange bis der Fehler verschwunden ist. Wie erkennen wir, ob der Schnittpunkt korrekt ermittelt wurde? Ganz einfach: die Entfernung punkt1-Kontrollpunkt oder punkt2-Kontrollpunkt darf nicht größer sein, als die Entfernung Punkt1-Punkt2.

Nun haben wir also eine nette kleine Klasse um in Flash sehr präzise Bezierkurven zu zeichnen. Im Beispiel könnt Ihr vergleichen, wie eine mit GMA erstellte Kurve neben einer mittels BezierSegment gezeichneten Kurve aussieht. Natürlich stellt die BezierSegment-Kurve etwas höhere Anforderungen an die Rechenleistung. Andererseits liegt es in der Hand des Entwicklers, wie hoch er die Auflösung wählt. Neben der besseren Darstellung hat die BS-Kurve andere Vorteile: es existieren Beispielsweise diverse mehr oder weniger komplizierte Methoden um Schnittpunkte zu interpolieren. Aber da wir die Kurve ja schon in eine Reihe von Einzelsegmenten aufgeteilt haben, könnten wir genauso einfach mit brutaler Gewalt über alle Segmente loopen, prüfen ob die Umgebungsrechtecke sich überschneiden, Segmente erneut aufteilen, wieder loopen, bis wir nahe genug an einem Schnittpunkt sind.

Show Source

Wenn Ihr mit dem Code herumspielen wollt, könnt Ihr ihn hier herunterladen. Wie immer gebe ich keine Garantien und gestatte außerdem niemandem, meinen Code zum Bau von Cruise-Missiles oder zum quälen von Hamstern zu verwenden. Viel Spaß!

This is my technique of drawing cubic Bezier-curves in Flash. If you are new to cubic and quadratic bezier-curves, you should check wikipedia first.

Show Example

One drawback of flash is its inability to display cubic bezier curves. If you draw cubic curves manually in the Flash-IDE, it will be spliced into several segments and the cubic curve will be approximated from a number of quadratic bezier curves (most designers run into this sooner or later: draw a simple bezier-curve with 3 or few anchors to the stage. Deselect and select again. Suddenly you will find your shape splitted into a number of segments). If you draw a curve by calling actionscript's drawing-api, there simply is no other option than the quadratic "curveTo()"-method anyway, offering a start- and an endpoint but only one lousy quadratic control-point.

Generic Midpoint Approximation

The common way to deal with this problem is a technique called "generic midpoint-approximation". There are some classes around, that use this technique to trick flash into displaying cubic bezieres. The basics behind this technique can be seen in the diagram to the left. Basically it is an algorithm for splicing a cubic bezier into 4 quadratic segments. Anyway this method comes at a cost, mainly a lack of precision. You won't notice in most situations, but sometimes (curve extremely steep, self-intersecting, etc.) the method produces somehow "bumpy" results. It is not the best solution for all use-cases.

The good news: it has not been noticed widely, but flash indeed *has* a class for cubic bezieres. It is called "BezierSegment" and is buried deeply within the "fl.motion" package. The bad news: it is not intended for being drawn to the stage, but for animations via actionscript. But with some tweaking and tinkering we will be able to force it into displaying high precision cubic bezieres on the stage as well.

Drawing A Cubic Bezier with BezierSegment-Class:

1. Import the class:

import fl.motion.BezierSegment;

2. Create a cubic bezierSegment:

var bezier:BezierSegment=new BezierSegment(point_1, control_1, point_2, control_2);

3. split the segment into slices

The coordinates of a point on the BezierSegment can be determined with its getValue(t:Number)-Method. t interpolates a value on the bezier, with t=1 the maximum possible value. I. e. if we want to find the value of our bezier at 50% we simply call:

var val:Point=bezier.getValue(0.5)

Is that cool?

Example: dotted line:

Lets try something easy: a dotted line. First we determine a resolution, lets say 20. All we have to do now is draw a circle every (1/20)th position:

Sorry, no Flash
var resolution:uint=20; var step:Number =1/resolution; var t:Number=0; while(t<=1){ var pos:Point=bezier.getValue(t); with(this.graphics){ beginFill(0x990000, 1); drawCircle(pos.x, pos.y, 5); endFill; } t+=step; }

The result is a nice dotted line.

O. k, you gave me a dotted line. Cool. But how about connecting the dots?

Slicing BezierSegment

This is the tricky part: If we want to connect the dots with curves, we need the coordinates for a quadratic control-point. Here is how to find it:

1. determine the tangents of the beginning-point of our slice and of the end-point of our slice. This is achieved with the help of the so called Decasteljau-Algorithm.

2. find the intersection-point of the tangents and

3. use this intersection-point as the control point of our quadratic-segment.

BezierSegment Slicing-Error

In some situations, when the curve is too weird and the resolution is too low, we have to divide our segment once more. Afterwards we must check again and slice the segments until we found the right intersection-point. How do we Know wether the intersection point is calculated correctly? The distance point1-handle or point2-handle may not be greater then the distance point1-point2.

Finally we've got a nice class for drawing very precise cubic bezier-curves in flash. Check out the example where you can play with a motion-bezier and a GMP-Bezier at the same time. Of course we have to pay for the higher precision with more computations. But it is in your hand to set the resolution to the lowest necessary value. And besides its smoother appearance it has other advantages. For example there exist several more or less complicated interpolation-methods for finding intersections of bezier-curves. But as we already do have a number of segments, we can use a brute force-Method by looping through all our segments, check if some bounding-boxes intersect, Split up our segments again, loop again until we are close enough to a valid intersection-point.

Show Source

If you want to play with the code, download the source files here. As always, I don't give any warrantys and it is not allowed to use my code for building cruise-missiles or tormenting hamsters. Have fun!

Trackback

2 Responses to “Kubische Bezierkurven in FlashCubic Bezier in Flash

  1. Comment by Daniel 09/10/2010

    Beautifull !… congratulations !.

    Daniel.

  2. Comment by Eke 04/20/2011

    Thanks, it helped me a lot :)