@@ -21,6 +21,7 @@ package starling.display
2121 import starling.geom.Polygon ;
2222 import starling.rendering.IndexData ;
2323 import starling.rendering.VertexData ;
24+ import starling.rendering.Painter ;
2425 import starling.utils.rad2deg ;
2526
2627 /** A display object supporting basic vector drawing functionality. In its current state,
@@ -37,10 +38,19 @@ package starling.display
3738 public function Canvas ()
3839 {
3940 _polygons = new < Polygon>[];
40- _currentPath = new Vector . <Number > () ;
41+ _currentPath = new < Number >[] ;
4142 _fillColor = 0xffffff ;
4243 _fillAlpha = 1.0 ;
4344 touchGroup = true ;
45+
46+ }
47+
48+ /** @inheritDoc */
49+ public override function render (painter :Painter ):void
50+ {
51+ // Behave like Flash's Graphics: if path is attempted to be rendered and unfinished, force close it.
52+ closeCurrentPathIfNeeded();
53+ super . render(painter);
4454 }
4555
4656 /** @inheritDoc */
@@ -176,6 +186,20 @@ package starling.display
176186 tesselateCurve(lastX, lastY, controlX, controlY, anchorX, anchorY, _currentPath );
177187 drawPathIfClosed();
178188 }
189+
190+ public function cubicCurveTo (controlX1 :Number , controlY1 :Number , controlX2 :Number , controlY2 :Number , anchorX :Number , anchorY :Number ):void
191+ {
192+ if (_currentPath . length == 0 )
193+ {
194+ // Behave like Flash's Graphics: if there's no current point, start at (0, 0).
195+ _currentPath . push (0.0 , 0.0 );
196+ }
197+ const lastX: Number = _currentPath [ _currentPath . length - 2 ];
198+ const lastY: Number = _currentPath [ _currentPath . length - 1 ];
199+ tesselateCubicCurve(lastX, lastY, controlX1, controlY1, controlX2, controlY2, anchorX, anchorY, _currentPath , 0 );
200+ drawPathIfClosed();
201+ }
202+
179203 /** Closes the current path if it contains an unfinished polygon. */
180204 private function closeCurrentPathIfNeeded ():void
181205 {
@@ -198,58 +222,67 @@ package starling.display
198222 }
199223
200224 /** Submits a series of IGraphicsData instances for drawing.
201- *
202- * @param graphicsData A Vector containing graphics objects, each of which much implement the flash.display.IGraphicsData interface.
203- */
225+ *
226+ * @param graphicsData A Vector containing graphics objects, each of which must implement the flash.display.IGraphicsData interface.
227+ */
204228 public function drawGraphicsData (graphicsData :Vector .<IGraphicsData>):void
205229 {
206230 var graphicsDataLength: int = graphicsData. length ;
207- for (var graphPropIndex: int = 0 ; graphPropIndex < graphicsDataLength; graphPropIndex++ )
231+ for (var graphPropIndex: int = 0 ; graphPropIndex < graphicsDataLength; graphPropIndex++ )
208232 {
209233 var graphicsProperties: IGraphicsData = graphicsData[ graphPropIndex];
210234
211235 if (graphicsProperties is GraphicsSolidFill)
212- beginFill ((graphicsProperties as GraphicsSolidFill). color , (graphicsProperties as GraphicsSolidFill). alpha );
236+ beginFill (
237+ (graphicsProperties as GraphicsSolidFill). color ,
238+ (graphicsProperties as GraphicsSolidFill). alpha
239+ );
213240
214241 else if (graphicsProperties is GraphicsPath)
215- {
216- var i: int = 0 ;
217- var data : Vector .< Number > = (graphicsProperties as GraphicsPath). data ;
218-
219- var commandLength: int = (graphicsProperties as GraphicsPath). commands. length ;
220- for (var commandIndex: int = 0 ; commandIndex < commandLength; commandIndex++ )
221- {
222- var command: int = (graphicsProperties as GraphicsPath). commands[ commandIndex];
223- switch (command)
224- {
225- case GraphicsPathCommand. MOVE_TO :
226- moveTo (data [ i] , data [ i + 1 ] );
227- i += 2 ;
228- break ;
229-
230- case GraphicsPathCommand. LINE_TO :
231- lineTo (data [ i] , data [ i + 1 ] );
232- i += 2 ;
233- break ;
234-
235- case GraphicsPathCommand. CURVE_TO :
236- curveTo (data [ i] , data [ i + 1 ] , data [ i + 2 ] , data [ i + 3 ] );
237- i += 4 ;
238- break ;
239-
240- default :
241- trace ("[Starling] Canvas.drawGraphicsData: Unimplemented Command in Graphics Path of type" , command);
242- break ;
243- }
244- }
245- }
242+ drawPath((graphicsProperties as GraphicsPath). commands, (graphicsProperties as GraphicsPath). data , (graphicsProperties as GraphicsPath). winding);
246243
247244 else if (graphicsProperties is GraphicsEndFill)
248245 endFill ();
249246
250247 else
251248 trace ("[Starling] Canvas.drawGraphicsData: Unimplemented Graphics Data in input: " ,
252- graphicsProperties, " at index " , graphicsData. indexOf (graphicsProperties));
249+ graphicsProperties, " at index " , graphicsData. indexOf (graphicsProperties));
250+ }
251+ }
252+
253+ public function drawPath (commands :Vector .<int>, data :Vector .<Number>, winding :String = "evenOdd" ):void
254+ {
255+ var i: int = 0 ;
256+ const commandLength: int = commands. length ;
257+ for (var commandIndex: int = 0 ; commandIndex < commandLength; commandIndex++ )
258+ {
259+ var command: int = commands[ commandIndex];
260+ switch (command)
261+ {
262+ case GraphicsPathCommand. MOVE_TO :
263+ moveTo (data [ i] , data [ i + 1 ] );
264+ i += 2 ;
265+ break ;
266+
267+ case GraphicsPathCommand. LINE_TO :
268+ lineTo (data [ i] , data [ i + 1 ] );
269+ i += 2 ;
270+ break ;
271+
272+ case GraphicsPathCommand. CURVE_TO :
273+ curveTo (data [ i] , data [ i + 1 ] , data [ i + 2 ] , data [ i + 3 ] );
274+ i += 4 ;
275+ break ;
276+
277+ case GraphicsPathCommand. CUBIC_CURVE_TO :
278+ cubicCurveTo(data [ i] , data [ i + 1 ] , data [ i + 2 ] , data [ i + 3 ] , data [ i + 4 ] , data [ i + 5 ] );
279+ i += 6 ;
280+ break ;
281+
282+ default :
283+ trace ("[Starling] Canvas.drawPath: Unimplemented Command in Graphics Path of type" , command);
284+ break ;
285+ }
253286 }
254287 }
255288
@@ -276,7 +309,7 @@ package starling.display
276309 _polygons [ _polygons . length ] = polygon;
277310 }
278311
279- /** Func to tesselate a quadratic Curve using recursion, used in curveTo; converted to AS3 from AwayJS.
312+ /** Func to tesselate a quadratic curve using recursion, used in curveTo; converted to AS3 from AwayJS.
280313 * https://github.com/awayjs/graphics/blob/19c9c9912d0254934ba54c9b7049d1b898bf97f2/lib/draw/GraphicsFactoryHelper.ts#L376-L468
281314 */
282315 private static function tesselateCurve (startx :Number , starty :Number , cx :Number , cy :Number , endx :Number ,
@@ -344,6 +377,79 @@ package starling.display
344377 tesselateCurve(ax, ay, c2x, c2y, endx, endy, array_out, iterationCnt);
345378 }
346379
380+ /** Func to tesselate a cubic curve using recursion, used in curveTo; converted to AS3 from AwayJS.
381+ * https://github.com/awayjs/graphics/blob/19c9c9912d0254934ba54c9b7049d1b898bf97f2/lib/draw/GraphicsFactoryHelper.ts#L470-L550
382+ */
383+ private static function tesselateCubicCurve (startx :Number , starty :Number , cx :Number , cy :Number , cx2 :Number , cy2 :Number ,
384+ endx :Number , endy :Number , array_out :Vector .<Number>, iterationCnt :Number = 0 ):void
385+ {
386+ const maxIterations: Number = 6 ;
387+ const minAngle: Number = 1 ;
388+ const minLengthSqr: Number = 1 ;
389+
390+ // calculate length of segment
391+ // this does not include the crtl-point positions
392+ const diff_x: Number = endx - startx;
393+ const diff_y: Number = endy - starty;
394+ const lenSq: Number = diff_x * diff_x + diff_y * diff_y;
395+
396+ // stop subdividing if the angle or the length is to small
397+ if (lenSq < minLengthSqr) {
398+ array_out. push (endx, endy);
399+ return ;
400+ }
401+
402+ // subdivide the curve
403+ const c1x: Number = (startx + cx) * 0.5 ; // new controlpoint 1
404+ const c1y: Number = (starty + cy) * 0.5 ;
405+ const c2x: Number = (cx + cx2) * 0.5 ; // new controlpoint 2
406+ const c2y: Number = (cy + cy2) * 0.5 ;
407+ const c3x: Number = (cx2 + endx) * 0.5 ; // new controlpoint 3
408+ const c3y: Number = (cy2 + endy) * 0.5 ;
409+
410+ const d1x: Number = (c1x + c2x) * 0.5 ; // new controlpoint 1
411+ const d1y: Number = (c1y + c2y) * 0.5 ;
412+ const d2x: Number = (c2x + c3x) * 0.5 ; // new controlpoint 2
413+ const d2y: Number = (c2y + c3y) * 0.5 ;
414+
415+ const ax: Number = (d1x + d2x) * 0.5 ; // new middlepoint 1
416+ const ay: Number = (d1y + d2y) * 0.5 ;
417+
418+ // stop tesselation on maxIteration level. Set it to 0 for no tesselation at all.
419+ if (iterationCnt >= maxIterations) {
420+ array_out. push (ax, ay, endx, endy);
421+ return ;
422+ }
423+
424+ // calculate angle between segments
425+ const angle_1: Number = rad2deg(Math . atan2 (cy - starty, cx - startx));
426+ const angle_2: Number = rad2deg(Math . atan2 (endy - cy, endx - cx));
427+ var angle_delta: Number = angle_2 - angle_1;
428+
429+ // make sure angle is in range -180 - 180
430+ while (angle_delta > 180 ) {
431+ angle_delta -= 360 ;
432+ }
433+ while (angle_delta < - 180 ) {
434+ angle_delta += 360 ;
435+ }
436+
437+ angle_delta = angle_delta < 0 ? - angle_delta : angle_delta;
438+
439+ // stop subdividing if the angle or the length is to small
440+ if (angle_delta <= minAngle) {
441+ array_out. push (endx, endy);
442+ return ;
443+ }
444+
445+ iterationCnt++;
446+
447+ tesselateCubicCurve(
448+ startx, starty, c1x, c1y, d1x, d1y, ax, ay, array_out, iterationCnt);
449+ tesselateCubicCurve(
450+ ax, ay, d2x, d2y, c3x, c3y, endx, endy, array_out, iterationCnt);
451+ }
452+
347453 private function drawPathIfClosed ():void
348454 {
349455 if (_currentPath . length < 4 ) return ; // need at least two points
0 commit comments