Skip to content

Commit 3806fed

Browse files
authored
Add drawPath, cubicCurveTo and make path force-closing closer to Flash (#1140)
1 parent beab08b commit 3806fed

1 file changed

Lines changed: 145 additions & 39 deletions

File tree

starling/src/starling/display/Canvas.as

Lines changed: 145 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)