├── libs ├── graphics.fla ├── graphics.swc └── starling.swc ├── src ├── assets │ ├── roi.png │ ├── bricks_pow2.jpg │ └── line_pattern.png ├── demos │ ├── DemoHoles.as │ ├── DemoDashedLine.as │ ├── DemoCloneInstances.as │ ├── DemoSVG.as │ ├── DemoLineStyles.as │ ├── DemoPixiAdvanced.as │ ├── DemoPixiSimple.as │ ├── DemoGraphicsData.as │ ├── DemoAnimatedArc.as │ ├── DemoPixiDynamic.as │ ├── DemoTextureFill.as │ └── DemoPieChart.as ├── StarlingRoot.as └── Boot.as ├── draw_lib ├── bin-release │ └── draw_lib.swc ├── src │ └── com │ │ └── roipeker │ │ └── starling │ │ └── draw │ │ ├── utils │ │ ├── earcut │ │ │ └── Node.as │ │ ├── GraphicCurves.as │ │ ├── lines │ │ │ ├── Pnt.as │ │ │ ├── LineStrokePixi.as │ │ │ └── ComplexLineStroke.as │ │ ├── QuadraticUtils.as │ │ ├── BezierUtils.as │ │ ├── ArcUtils.as │ │ ├── svg │ │ │ ├── SVGColor.as │ │ │ └── DPathParser.as │ │ └── GraphUtils.as │ │ ├── math │ │ └── shapes │ │ │ ├── AbsShape.as │ │ │ ├── StarPoly.as │ │ │ ├── Circle.as │ │ │ ├── Ellipse.as │ │ │ ├── ShapeType.as │ │ │ ├── MeshShape.as │ │ │ ├── Poly.as │ │ │ ├── RoundRect.as │ │ │ └── Rect.as │ │ ├── builders │ │ ├── AbsShapeBuilder.as │ │ ├── RectBuilder.as │ │ ├── MeshBuilder.as │ │ ├── PolyBuilder.as │ │ ├── CircleBuilder.as │ │ ├── GraphicsDataBuild.as │ │ └── RoundRectBuilder.as │ │ ├── AbsMesh.as │ │ ├── styles │ │ ├── FillStyle.as │ │ ├── LineStyle.as │ │ └── GradientFillStyle.as │ │ ├── GraphData.as │ │ └── GraphGeom.as └── draw_lib.iml ├── .gitignore ├── .idea ├── encodings.xml ├── vcs.xml ├── flexCompiler.xml ├── modules.xml ├── misc.xml └── runConfigurations │ └── demos.xml ├── LICENSE ├── draw demos.iml ├── README.md └── app.xml /libs/graphics.fla: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roipeker/draw-lib/HEAD/libs/graphics.fla -------------------------------------------------------------------------------- /libs/graphics.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roipeker/draw-lib/HEAD/libs/graphics.swc -------------------------------------------------------------------------------- /libs/starling.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roipeker/draw-lib/HEAD/libs/starling.swc -------------------------------------------------------------------------------- /src/assets/roi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roipeker/draw-lib/HEAD/src/assets/roi.png -------------------------------------------------------------------------------- /src/assets/bricks_pow2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roipeker/draw-lib/HEAD/src/assets/bricks_pow2.jpg -------------------------------------------------------------------------------- /src/assets/line_pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roipeker/draw-lib/HEAD/src/assets/line_pattern.png -------------------------------------------------------------------------------- /draw_lib/bin-release/draw_lib.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roipeker/draw-lib/HEAD/draw_lib/bin-release/draw_lib.swc -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Actionscript template 2 | bin/ 3 | dev-assets/ 4 | .idea/workspace.xml 5 | .idea/tasks.xml 6 | # Other files and folders 7 | .settings/ -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/flexCompiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/utils/earcut/Node.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 03/01/2019. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.utils.earcut { 8 | public class Node { 9 | 10 | public var i:int; 11 | public var x:Number; 12 | public var y:Number; 13 | public var prev:Node = null; 14 | public var next:Node = null; 15 | public var z:uint = 0; 16 | public var prevZ:Node; 17 | public var nextZ:Node; 18 | public var steiner:Boolean = false; 19 | 20 | public function Node($i:int, $x:Number, $y:Number) { 21 | i = $i; 22 | x = $x; 23 | y = $y; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/math/shapes/AbsShape.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-11. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.math.shapes { 8 | import starling.utils.StringUtil; 9 | 10 | public class AbsShape { 11 | 12 | public var holes:Array; 13 | protected var _type:ShapeType; 14 | 15 | public function AbsShape(type:ShapeType) { 16 | _type = type; 17 | holes = []; 18 | } 19 | 20 | public function get type():ShapeType { 21 | return _type; 22 | } 23 | 24 | // todo: add return pool... 25 | 26 | public function reset():void { 27 | holes.length = 0; 28 | } 29 | 30 | public function toString():String { 31 | return StringUtil.format("[AbsShape type={0}]", _type); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Roi Peker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/demos/DemoHoles.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-15. 4 | // 5 | // ================================================================================================= 6 | 7 | package demos { 8 | import com.roipeker.starling.draw.Draw; 9 | 10 | import starling.display.Sprite; 11 | import starling.events.Event; 12 | 13 | public class DemoHoles extends Sprite { 14 | 15 | public function DemoHoles() { 16 | addEventListener(Event.ADDED_TO_STAGE, init); 17 | } 18 | 19 | private function init(event:Event):void { 20 | stage.color = 0x0; 21 | 22 | var g:Draw = new Draw(); 23 | addChild(g); 24 | g.x = 200; 25 | g.y = 200; 26 | 27 | 28 | g.beginFill(0xff0000, .6); 29 | g.drawCircle(50, 50, 70); 30 | g.endFill(); 31 | 32 | g.beginFill(0x00ff00, .7); 33 | g.drawCircle(20, 20, 90); 34 | g.endFill(); 35 | 36 | g.beginHole(); 37 | g.drawCircle(40, 40, 12); 38 | g.drawCircle(-10, -10, 20); 39 | g.drawRect(-30, -40, 10, 10); 40 | g.endHole(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/StarlingRoot.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-11. 4 | // 5 | // ================================================================================================= 6 | 7 | package { 8 | import demos.*; 9 | 10 | import starling.core.Starling; 11 | import starling.display.Sprite; 12 | import starling.events.Event; 13 | 14 | public class StarlingRoot extends Sprite { 15 | 16 | public function StarlingRoot() { 17 | addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); 18 | Starling.current.showStats = true; 19 | } 20 | 21 | private function onAddedToStage(event:Event):void { 22 | 23 | // just uncomment 1 line at a time to test. 24 | 25 | // addChild(new DemoPixiSimple()); 26 | // addChild(new DemoPixiAdvanced()); 27 | // addChild(new DemoPixiDynamic()); 28 | // addChild(new DemoAnimatedArc()); 29 | // addChild(new DemoDashedLine()); 30 | // addChild(new DemoCloneInstances()); 31 | // addChild(new DemoHoles()); 32 | // addChild(new DemoSVG()); 33 | // addChild(new DemoPieChart()); 34 | // addChild(new DemoGraphicsData()); 35 | // addChild(new DemoLineStyles()); 36 | addChild(new DemoTextureFill()); 37 | } 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/demos/DemoDashedLine.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-15. 4 | // 5 | // ================================================================================================= 6 | 7 | package demos { 8 | import com.roipeker.starling.draw.Draw; 9 | 10 | import starling.core.Starling; 11 | import starling.display.Sprite; 12 | import starling.events.Event; 13 | 14 | public class DemoDashedLine extends Sprite { 15 | 16 | public function DemoDashedLine() { 17 | addEventListener(Event.ADDED_TO_STAGE, init); 18 | } 19 | 20 | private function init(event:Event):void { 21 | const d:Draw = new Draw(); 22 | d.x = 100; 23 | d.y = 100; 24 | addChild(d); 25 | 26 | const tweenObj:Object = {linePercent: 0}; 27 | 28 | const polygon:Array = [ 29 | 10, 10, 30 | 50, 15, 31 | 60, 60, 32 | 20, 40 33 | ]; 34 | 35 | Starling.juggler.tween(tweenObj, 1, {linePercent: 1, repeatCount: 0, onUpdate: drawit}); 36 | 37 | function drawit():void { 38 | d.clear(); 39 | d.lineStyle(2, 0xff0000 ); 40 | d.beginFill(0x00ff00, .5); 41 | d.dashedPolygon(polygon, 5, 3, tweenObj.linePercent); 42 | d.drawCircle(100,100,40); 43 | } 44 | 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/math/shapes/StarPoly.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-11. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.math.shapes { 8 | import com.roipeker.starling.draw.utils.GraphUtils; 9 | 10 | import starling.utils.StringUtil; 11 | 12 | public class StarPoly extends Poly { 13 | 14 | // TODO: add pool. 15 | 16 | public function StarPoly(x:Number, y:Number, points:int, radius:Number, innerRadius:Number = 0, rotation:Number = 0) { 17 | innerRadius = innerRadius || radius / 2; 18 | var startAngle:Number = (-1 * Math.PI / 2) + rotation; 19 | var len:int = points * 2; 20 | var delta:Number = GraphUtils.PI2 / len; 21 | var polygon:Array = []; 22 | for (var i:int = 0; i < len; i++) { 23 | const r:Number = i % 2 ? innerRadius : radius; 24 | const a:Number = (i * delta) + startAngle; 25 | polygon.push( 26 | x + (r * Math.cos(a)), 27 | y + (r * Math.sin(a)) 28 | ); 29 | } 30 | super(polygon); 31 | } 32 | 33 | override public function toString():String { 34 | return StringUtil.format("[StarPoly #points={0}]", points.length); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/demos/DemoCloneInstances.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-15. 4 | // 5 | // ================================================================================================= 6 | 7 | package demos { 8 | import com.roipeker.starling.draw.Draw; 9 | 10 | import starling.display.Sprite; 11 | import starling.events.Event; 12 | 13 | public class DemoCloneInstances extends Sprite { 14 | 15 | public function DemoCloneInstances() { 16 | addEventListener(Event.ADDED_TO_STAGE, init ); 17 | } 18 | 19 | private function init(event:Event):void { 20 | const graph1:Draw = new Draw(); 21 | addChild(graph1); 22 | 23 | graph1.beginFill(0x00ff00, .2); 24 | graph1.drawCircle(20, 20, 20); 25 | graph1.drawRoundRect(40,40,90, 60, 12); 26 | graph1.endFill(); 27 | 28 | // geometry is SHARED with graph1 29 | /*var graph2:Draw = graph1.clone(); 30 | addChild(graph2); 31 | graph2.x = 0; 32 | graph2.y = 200; 33 | // this will affect graph1 geometry as well. 34 | graph2.lineStyle(10, 0xff0000, .5); 35 | graph2.drawRect(100,100,50,50);*/ 36 | 37 | var graph2:Draw = new Draw(); 38 | addChild(graph2); 39 | graph2.x = 0; 40 | graph2.y = 200; 41 | graph2.copyFrom(graph1); 42 | 43 | 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/math/shapes/Circle.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-11. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.math.shapes { 8 | import starling.utils.StringUtil; 9 | 10 | public class Circle extends AbsShape { 11 | 12 | // TODO: add pool. 13 | 14 | public var x:Number; 15 | public var y:Number; 16 | public var radius:Number; 17 | 18 | public function Circle(x:Number = 0, y:Number = 0, radius:Number = 0) { 19 | this.x = x; 20 | this.y = y; 21 | this.radius = radius; 22 | super(ShapeType.CIRC); 23 | } 24 | 25 | public function clone():Circle { 26 | return new Circle(x, y, radius); 27 | } 28 | 29 | public function contains(x:Number, y:Number):Boolean { 30 | if (radius <= 0) return false; 31 | const r2:Number = radius * radius; 32 | const dx:Number = x - this.x; 33 | const dy:Number = y - this.y; 34 | return dx * dx + dy * dy <= r2; 35 | } 36 | 37 | public function getBounds():Rect { 38 | return new Rect(x - radius, y - radius, radius * 2, radius * 2); 39 | } 40 | 41 | override public function toString():String { 42 | return StringUtil.format("[Circle x={0}, y={1}, radius={2}]", x, y, radius); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/builders/AbsShapeBuilder.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-11. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.builders { 8 | import com.roipeker.starling.draw.GraphData; 9 | import com.roipeker.starling.draw.GraphGeom; 10 | import com.roipeker.starling.draw.math.shapes.ShapeType; 11 | 12 | import flash.utils.Dictionary; 13 | 14 | public class AbsShapeBuilder { 15 | 16 | 17 | private static var _map:Dictionary; 18 | 19 | private static function init():void { 20 | if (_map) return; 21 | _map = new Dictionary(false); 22 | _map[ShapeType.RECT] = new RectBuilder(); 23 | _map[ShapeType.RRECT] = new RoundRectBuilder(); 24 | _map[ShapeType.ELIP] = _map[ShapeType.CIRC] = new CircleBuilder(); 25 | _map[ShapeType.POLY] = new PolyBuilder(); 26 | _map[ShapeType.TRI] = new MeshBuilder(); 27 | } 28 | 29 | public static function get(type:ShapeType):AbsShapeBuilder { 30 | if (!_map) init(); 31 | return _map[type]; 32 | } 33 | 34 | /** 35 | * Abstract class. 36 | */ 37 | public function AbsShapeBuilder() { 38 | } 39 | 40 | public function build(shapeData:GraphData):void { 41 | } 42 | 43 | public function triangulate(shapeData:GraphData, geometry:GraphGeom):void { 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/math/shapes/Ellipse.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-11. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.math.shapes { 8 | import starling.utils.StringUtil; 9 | 10 | public class Ellipse extends AbsShape { 11 | 12 | // TODO: add pool. 13 | 14 | public var x:Number; 15 | public var y:Number; 16 | public var halfW:Number; 17 | public var halfH:Number; 18 | 19 | public function Ellipse(x:Number = 0, y:Number = 0, halfW:Number = 0, halfH:Number = 0) { 20 | this.x = x; 21 | this.y = y; 22 | this.halfW = halfW; 23 | this.halfH = halfH; 24 | super(ShapeType.ELIP); 25 | } 26 | 27 | public function clone():Ellipse { 28 | return new Ellipse(x, y, halfW, halfH); 29 | } 30 | 31 | public function contains(x:Number, y:Number):Boolean { 32 | if (halfW <= 0 || halfH <= 0) return false; 33 | var normx:Number = (x - this.x) / halfW; 34 | var normy:Number = (x - this.y) / halfH; 35 | return normx * normx + normy * normy <= 1; 36 | } 37 | 38 | public function getBounds():Rect { 39 | return new Rect(x - halfW, y - halfH, halfW, halfH); 40 | } 41 | 42 | override public function toString():String { 43 | return StringUtil.format("[Ellipse x={0}, y={1}, half_width={2}, half_height={3}]", x, y, halfW, halfH); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/builders/RectBuilder.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-11. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.builders { 8 | import com.roipeker.starling.draw.GraphData; 9 | import com.roipeker.starling.draw.GraphGeom; 10 | import com.roipeker.starling.draw.math.shapes.Rect; 11 | 12 | public class RectBuilder extends AbsShapeBuilder { 13 | 14 | public function RectBuilder() { 15 | super(); 16 | } 17 | 18 | override public function build(shapeData:GraphData):void { 19 | const rect:Rect = shapeData.shape as Rect; 20 | shapeData.points.length = 0; 21 | shapeData.points.push( 22 | rect.x, rect.y, 23 | rect.x + rect.w, rect.y, 24 | rect.x + rect.w, rect.y + rect.h, 25 | rect.x, rect.y + rect.h, 26 | rect.x, rect.y 27 | ); 28 | } 29 | 30 | override public function triangulate(shapeData:GraphData, geometry:GraphGeom):void { 31 | const points:Array = shapeData.points; 32 | const vertPos:int = geometry.points.length >> 1; 33 | geometry.points.push( 34 | points[0], points[1], 35 | points[2], points[3], 36 | points[6], points[7], 37 | points[4], points[5] 38 | ); 39 | geometry.indices.push( 40 | vertPos, vertPos + 1, vertPos + 2, 41 | vertPos + 1, vertPos + 2, vertPos + 3 42 | ); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /draw_lib/draw_lib.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/AbsMesh.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 31/12/2018. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw { 8 | import starling.display.Mesh; 9 | import starling.geom.Polygon; 10 | import starling.rendering.IndexData; 11 | import starling.rendering.VertexData; 12 | import starling.styles.MeshStyle; 13 | 14 | public class AbsMesh extends Mesh { 15 | 16 | protected var _poly:Polygon; 17 | protected var _color:int = 0xffffff; 18 | 19 | public function AbsMesh(vdata:VertexData = null, idata:IndexData = null, style:MeshStyle = null) { 20 | if (!vdata) vdata = new VertexData(); 21 | if (!idata) idata = new IndexData(); 22 | _poly = new Polygon(); 23 | super(vdata, idata, style); 24 | } 25 | 26 | public function processVerticesList(vertices:Array, color:int = -1):void { 27 | 28 | var vdata:VertexData = vertexData; 29 | var idata:IndexData = indexData; 30 | 31 | vdata.clear(); 32 | idata.clear(); 33 | 34 | if (!_poly) { 35 | _poly = new Polygon( vertices ) 36 | } else { 37 | _poly.numVertices = 0; 38 | _poly.addVertices.apply(null, vertices); 39 | } 40 | _poly.triangulate(idata); 41 | _poly.copyToVertexData(vdata); 42 | if (color > -1) { 43 | _color = color; 44 | vdata.colorize('color', _color); 45 | } 46 | setIndexDataChanged(); 47 | } 48 | 49 | public function getVertex():VertexData { 50 | return vertexData; 51 | } 52 | 53 | public function getIndex():IndexData { 54 | return indexData; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/math/shapes/ShapeType.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-11. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.math.shapes { 8 | import starling.utils.StringUtil; 9 | 10 | public class ShapeType { 11 | 12 | public static const CIRC:ShapeType = new ShapeType("CIRC"); 13 | public static const ELIP:ShapeType = new ShapeType("ELIP"); 14 | public static const RECT:ShapeType = new ShapeType("RECT"); 15 | public static const RRECT:ShapeType = new ShapeType("RRECT"); 16 | public static const POLY:ShapeType = new ShapeType("POLY"); 17 | public static const TRI:ShapeType = new ShapeType("TRI"); 18 | 19 | public static function equals(shapeA:ShapeType, shapeB:ShapeType):Boolean { 20 | return shapeA == shapeB; 21 | } 22 | 23 | private var _type:String; 24 | 25 | public function ShapeType(type:String = null) { 26 | _type = type; 27 | } 28 | 29 | public function toString():String { 30 | return StringUtil.format("[ShapeType type={0}]", _type); 31 | } 32 | 33 | public function isCircle():Boolean { 34 | return this == ShapeType.CIRC; 35 | } 36 | 37 | public function isEllipse():Boolean { 38 | return this == ShapeType.ELIP; 39 | } 40 | 41 | public function isPolygon():Boolean { 42 | return this == ShapeType.POLY; 43 | } 44 | 45 | public function isRectangle():Boolean { 46 | return this == ShapeType.RECT; 47 | } 48 | 49 | public function isRoundRectangle():Boolean { 50 | return this == ShapeType.RRECT; 51 | } 52 | 53 | public function isTri():Boolean { 54 | return this == ShapeType.TRI; 55 | } 56 | 57 | public function get type():String { 58 | return _type; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/styles/FillStyle.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-11. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.styles { 8 | import flash.geom.Matrix; 9 | 10 | import starling.textures.Texture; 11 | 12 | import starling.utils.StringUtil; 13 | 14 | public class FillStyle { 15 | 16 | // TODO: add pool 17 | 18 | public var color:uint; 19 | public var alpha:Number; 20 | public var visible:Boolean; 21 | public var gradient:GradientFillStyle; 22 | public var matrix:Matrix; 23 | 24 | public var texture:Texture; 25 | // only valid for pow of 2 textures. 26 | public var textureRepeat:Boolean; 27 | 28 | public function FillStyle() { 29 | gradient = new GradientFillStyle(); 30 | reset(); 31 | } 32 | 33 | public function reset():void { 34 | // defaults. 35 | gradient.reset(); 36 | color = 0xFFFFFF; 37 | alpha = 1; 38 | visible = false; 39 | textureRepeat=false; 40 | matrix = null ; 41 | texture = null; // might be a default WHITE texture 42 | } 43 | 44 | public function clone():FillStyle { 45 | const fill:FillStyle = new FillStyle(); 46 | fill.color = color; 47 | fill.alpha = alpha; 48 | fill.texture = texture ; 49 | fill.matrix = matrix ; 50 | fill.textureRepeat=textureRepeat; 51 | fill.visible = visible; 52 | fill.gradient.copyFrom(gradient); 53 | return fill; 54 | } 55 | 56 | public function toString():String { 57 | return StringUtil.format("[FillStyle color=0x{0}, alpha={1}, visible={2}, gradient={3}, texture={4}, textureRepeat={5}]", 58 | color.toString(16).toUpperCase(), alpha, visible, gradient, texture,textureRepeat); 59 | } 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/utils/GraphicCurves.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 04/01/2019. 4 | // 5 | // ported from PixiJS 6 | // https://github.com/pixijs/pixi.js 7 | // 8 | // ================================================================================================= 9 | 10 | package com.roipeker.starling.draw.utils { 11 | 12 | /** 13 | * Graphics curves resolution settings. If `adaptive` flag is set to `true`, 14 | * the resolution is calculated based on the curve's length to ensure better visual quality. 15 | * Adaptive draw works with `bezierCurveTo` and `quadraticCurveTo`. 16 | * 17 | * @static 18 | * @constant 19 | * @memberof PIXI 20 | * @name GRAPHICS_CURVES 21 | * @type {object} 22 | * @property {boolean} adaptive=false - flag indicating if the resolution should be adaptive 23 | * @property {number} maxLength=10 - maximal length of a single segment of the curve (if adaptive = false, ignored) 24 | * @property {number} minSegments=8 - minimal number of segments in the curve (if adaptive = false, ignored) 25 | * @property {number} maxSegments=2048 - maximal number of segments in the curve (if adaptive = false, ignored) 26 | */ 27 | public class GraphicCurves { 28 | public function GraphicCurves() { 29 | } 30 | 31 | public static var adaptive:Boolean = false; 32 | public static var maxLength:Number = 10; 33 | public static var minSegments:uint = 8; 34 | public static var maxSegments:uint = 2048; 35 | 36 | public static function segmentsCount(length:Number, defaultSegments:uint = 20):uint { 37 | if (adaptive) { 38 | return defaultSegments; 39 | } 40 | var result:uint = Math.ceil(length / maxLength); 41 | if (result < minSegments) { 42 | result = minSegments; 43 | } else if (result > maxSegments) { 44 | result = maxSegments; 45 | } 46 | return result; 47 | } 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.idea/runConfigurations/demos.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 26 | 41 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/styles/LineStyle.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-11. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.styles { 8 | import flash.display.CapsStyle; 9 | import flash.display.JointStyle; 10 | 11 | import starling.utils.StringUtil; 12 | 13 | public class LineStyle extends FillStyle { 14 | 15 | // TODO: add pool 16 | 17 | public var width:Number; 18 | public var alignment:Number; 19 | 20 | // -- extra props. 21 | public var joint:String; // JoinStyle 22 | public var caps:String; // CapsStyle 23 | public var miterLimit:Number; 24 | 25 | public function LineStyle() { 26 | reset(); 27 | } 28 | 29 | override public function clone():FillStyle { 30 | const fill:LineStyle = new LineStyle(); 31 | fill.texture = texture; 32 | fill.textureRepeat = textureRepeat; 33 | fill.matrix = matrix; 34 | fill.width = width ; 35 | fill.alignment = alignment; 36 | fill.joint= joint; 37 | fill.caps= caps; 38 | fill.miterLimit= miterLimit; 39 | fill.color = color ; 40 | fill.alpha= alpha; 41 | fill.visible= visible; 42 | fill.gradient.copyFrom(gradient); 43 | return fill ; 44 | } 45 | 46 | override public function reset():void { 47 | super.reset(); 48 | color = 0x000000; 49 | width = 0; 50 | alignment = 0.5; 51 | miterLimit = 10; 52 | joint = JointStyle.MITER; 53 | caps = CapsStyle.SQUARE; 54 | } 55 | 56 | override public function toString():String { 57 | return StringUtil.format("[LineStyle width={0}, alignment={1}, color=0x{2}, alpha={3}, visible={4}, joint={5}, caps={6}, gradient={7}, texture={8}]", 58 | width, alignment, color.toString(16).toUpperCase(), alpha, visible, joint, caps, gradient, texture); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/styles/GradientFillStyle.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-11. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.styles { 8 | import starling.utils.StringUtil; 9 | 10 | public class GradientFillStyle { 11 | 12 | // basic gradient with 2 colors. 13 | public var color1:uint; 14 | public var color2:uint; 15 | public var alpha1:Number; 16 | public var alpha2:Number; 17 | public var angle:Number; 18 | public var visible:Boolean; 19 | 20 | public function GradientFillStyle() { 21 | color1 = 0xFFFFFF; 22 | color2 = 0x000000; 23 | alpha1 = 1; 24 | alpha2 = 1; 25 | angle = 0; 26 | visible = false; 27 | } 28 | 29 | public function reset():void { 30 | color1 = 0xFFFFFF; 31 | color2 = 0x000000; 32 | alpha1 = 1; 33 | alpha2 = 1; 34 | angle = 0; 35 | visible = false; 36 | } 37 | 38 | public function clone():GradientFillStyle { 39 | var fill:GradientFillStyle = new GradientFillStyle(); 40 | fill.color1 = color1; 41 | fill.color2 = color2; 42 | fill.alpha1 = alpha1; 43 | fill.alpha2 = alpha2; 44 | fill.angle = angle; 45 | fill.visible = visible; 46 | return fill; 47 | } 48 | 49 | public function copyFrom(gradient:GradientFillStyle):void { 50 | color1 = gradient.color1; 51 | color2 = gradient.color2; 52 | alpha1 = gradient.alpha1; 53 | alpha2 = gradient.alpha2; 54 | angle = gradient.angle; 55 | visible = gradient.visible; 56 | } 57 | 58 | public function toString():String { 59 | return StringUtil.format("[GradientFillStyle colors=0x{0}-0x{1}, alphas={2}-{3}, angle={4}, visible={5}]", 60 | color1.toString(16).toUpperCase(), color2.toString(16).toUpperCase(), alpha1, alpha2, angle, visible); 61 | } 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/utils/lines/Pnt.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 10/01/2019. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.utils.lines { 8 | public class Pnt { 9 | 10 | public var x:Number; 11 | public var y:Number; 12 | 13 | public function key():String { 14 | return x + 'x' + y; 15 | } 16 | 17 | public function Pnt(x:Number = 0, y:Number = 0) { 18 | this.x = x; 19 | this.y = y; 20 | } 21 | 22 | public function scalarMult(f:Number):Pnt { 23 | this.x *= f; 24 | this.y *= f; 25 | return this; 26 | } 27 | 28 | public function perpendicular():Pnt { 29 | var x:Number = this.x; 30 | this.x = -this.y; 31 | this.y = x; 32 | return this; 33 | } 34 | 35 | public function toString():String { 36 | return 'Pnt: x=' + x + ' y=' + y; 37 | } 38 | 39 | public function invert():Pnt { 40 | this.x = -this.x; 41 | this.y = -this.y; 42 | return this; 43 | } 44 | 45 | [Inline] 46 | public final function length():Number { 47 | return Math.sqrt(this.x * this.x + this.y * this.y); 48 | } 49 | 50 | public function normalize():Pnt { 51 | var mod:Number = this.length(); 52 | this.x /= mod; 53 | this.y /= mod; 54 | return this; 55 | } 56 | 57 | [Inline] 58 | public final function angle():Number { 59 | return this.y / this.x; 60 | } 61 | 62 | [Inline] 63 | public static function Angle(p0:Pnt, p1:Pnt):Number { 64 | return Math.atan2(p1.x - p0.x, p1.y - p0.y); 65 | } 66 | 67 | public static function Add(p0:Pnt, p1:Pnt):Pnt { 68 | return new Pnt(p0.x + p1.x, p0.y + p1.y); 69 | } 70 | 71 | public static function Sub(p1:Pnt, p0:Pnt):Pnt { 72 | return new Pnt(p1.x - p0.x, p1.y - p0.y); 73 | } 74 | 75 | public static function Middle(p0:Pnt, p1:Pnt):Pnt { 76 | return Add(p0, p1).scalarMult(.5); 77 | } 78 | 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/GraphData.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-11. 4 | // 5 | // ported from PixiJS 6 | // https://github.com/pixijs/pixi.js 7 | // 8 | // ================================================================================================= 9 | 10 | package com.roipeker.starling.draw { 11 | import com.roipeker.starling.draw.math.shapes.AbsShape; 12 | import com.roipeker.starling.draw.math.shapes.ShapeType; 13 | import com.roipeker.starling.draw.styles.FillStyle; 14 | import com.roipeker.starling.draw.styles.LineStyle; 15 | 16 | import flash.geom.Matrix; 17 | 18 | import starling.utils.StringUtil; 19 | 20 | public class GraphData { 21 | 22 | // TODO: add pool 23 | 24 | public var shape:AbsShape; 25 | public var fillStyle:FillStyle; 26 | public var lineStyle:LineStyle; 27 | public var matrix:Matrix; 28 | public var type:ShapeType; 29 | public var points:Array; 30 | public var holes:Array; 31 | 32 | public function GraphData(shape:AbsShape, fillStyle:FillStyle = null, lineStyle:LineStyle = null, matrix:Matrix = null) { 33 | this.shape = shape; 34 | this.fillStyle = fillStyle; 35 | this.lineStyle = lineStyle; 36 | this.matrix = matrix; 37 | type = shape.type; 38 | points = []; 39 | holes = []; 40 | } 41 | 42 | public function reset():void { 43 | // TODO: when add pool to holes, check if contains graphData. 44 | /*for (var i:int = 0, ilen:int = holes.length; i < ilen; i++) { 45 | holes[i].reset(); 46 | }*/ 47 | // return pool 48 | fillStyle.reset(); 49 | lineStyle.reset(); 50 | shape.reset(); 51 | points.length = 0; 52 | holes.length = 0; 53 | } 54 | 55 | public function clone():GraphData { 56 | return new GraphData(shape, fillStyle, lineStyle, matrix); 57 | } 58 | 59 | public function dispose():void { 60 | reset(); 61 | shape = null; 62 | holes = null; 63 | points = null; 64 | lineStyle = null; 65 | fillStyle = null; 66 | matrix = null; 67 | } 68 | 69 | public function toString():String { 70 | return StringUtil.format("[GraphData type={0}\nfill_style={1}\nline_style={2}\nshape={3}]", 71 | type, fillStyle, lineStyle, shape); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/builders/MeshBuilder.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-11. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.builders { 8 | import com.roipeker.starling.draw.GraphData; 9 | import com.roipeker.starling.draw.GraphGeom; 10 | import com.roipeker.starling.draw.math.shapes.MeshShape; 11 | 12 | public class MeshBuilder extends AbsShapeBuilder { 13 | 14 | public function MeshBuilder() { 15 | super(); 16 | } 17 | 18 | override public function build(shapeData:GraphData):void { 19 | const shape:MeshShape = shapeData.shape as MeshShape; 20 | // vectors vs array. 21 | const len:int = shape.vertices.length; 22 | for (var i:int = 0; i < len; i++) { 23 | shapeData.points[i] = shape.vertices[i]; 24 | } 25 | } 26 | 27 | override public function triangulate(shapeData:GraphData, geometry:GraphGeom):void { 28 | 29 | // TODO: doesnt work well with lineStyle, only fills. 30 | 31 | const shapePoints:Vector. = MeshShape(shapeData.shape).vertices; 32 | const shapeIndices:Vector. = MeshShape(shapeData.shape).indices; 33 | 34 | const verts:Array = geometry.points; 35 | const indices:Array = geometry.indices; 36 | 37 | // vectors vs array. 38 | for (var i:int = 0, ilen:int = shapePoints.length; i < ilen; i++) { 39 | verts[verts.length] = shapePoints[i]; 40 | } 41 | 42 | if (shapeIndices && shapeIndices.length) { 43 | ilen = shapeIndices.length; 44 | for (i = 0; i < ilen; i++) { 45 | indices[indices.length] = shapeIndices[i]; 46 | } 47 | } else { 48 | // validate amount of vertices... 49 | var numPoints:int = shapePoints.length / 2; 50 | if (numPoints % 3 != 0) { 51 | trace("[MeshBuilder] Error in triangulation, must be divisible by 3"); 52 | } else { 53 | const vertPos:int = geometry.points.length >> 1; 54 | for (i = 0; i < numPoints; i += 3) { 55 | indices.push( 56 | vertPos + i, vertPos + 1 + i, vertPos + 2 + i 57 | ); 58 | } 59 | } 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/math/shapes/MeshShape.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 03/01/2019. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.math.shapes { 8 | import starling.utils.StringUtil; 9 | 10 | // TODO: doesn't work well with lineStyle. 11 | 12 | public class MeshShape extends AbsShape { 13 | 14 | /*private static const _pool:Vector. = new Vector.(); 15 | public static function put(obj:MeshShape):void { 16 | if (obj) { 17 | obj.indices = null; 18 | obj.vertices = null; 19 | obj.uvs = null; 20 | _pool[_pool.length] = obj; 21 | } 22 | } 23 | 24 | public static function get(vertices:Vector., indices:Vector. = null, uvs:Array = null):MeshShape { 25 | if (_pool.length == 0) return new MeshShape(vertices, indices, uvs); 26 | var obj:MeshShape = _pool.pop(); 27 | obj.vertices = vertices; 28 | obj.indices = indices; 29 | obj.uvs = uvs; 30 | return obj; 31 | }*/ 32 | 33 | /*public static function get(vertices:Array, indices:Array, uvs:Array):MeshShape { 34 | return new MeshShape(vertices, indices, uvs); 35 | }*/ 36 | 37 | public static function get empty():MeshShape { 38 | return new MeshShape(); 39 | } 40 | 41 | public var vertices:Vector.; 42 | public var indices:Vector.; 43 | public var uvs:Array; 44 | 45 | /** 46 | * @param vertices 47 | * @param indices 48 | * @param uvs 49 | */ 50 | public function MeshShape(vertices:Vector. = null, indices:Vector. = null, uvs:Array = null) { 51 | this.vertices = vertices; 52 | this.indices = indices; 53 | this.uvs = uvs; 54 | super(ShapeType.TRI); 55 | } 56 | 57 | public function clone():MeshShape { 58 | return new MeshShape(vertices, indices, uvs); 59 | } 60 | 61 | public function copyFrom(rect:MeshShape):MeshShape { 62 | vertices = rect.vertices; 63 | indices = rect.indices; 64 | uvs = rect.uvs; 65 | return this; 66 | } 67 | 68 | override public function toString():String { 69 | return StringUtil.format( 70 | '[ MeshShape vertices={0}, indices={1}, uvs={2} ]', vertices, indices, uvs); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/builders/PolyBuilder.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-11. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.builders { 8 | import com.roipeker.starling.draw.GraphData; 9 | import com.roipeker.starling.draw.GraphGeom; 10 | import com.roipeker.starling.draw.math.shapes.Poly; 11 | import com.roipeker.starling.draw.utils.earcut.Earcut; 12 | 13 | public class PolyBuilder extends AbsShapeBuilder { 14 | 15 | public function PolyBuilder() { 16 | super(); 17 | } 18 | 19 | override public function build(shapeData:GraphData):void { 20 | const points:Array = Poly(shapeData.shape).points; 21 | for (var i:int = 0, ilen:int = points.length; i < ilen; i++) { 22 | shapeData.points[i] = points[i]; 23 | } 24 | } 25 | 26 | override public function triangulate(shapeData:GraphData, geometry:GraphGeom):void { 27 | var points:Array = shapeData.points; 28 | if (points.length >= 6) { 29 | const verts:Array = geometry.points; 30 | const indices:Array = geometry.indices; 31 | var ilen:int; 32 | var holes:Array = []; 33 | 34 | ilen = shapeData.holes.length; 35 | if (ilen) { 36 | for (var i:int = 0; i < ilen; i++) { 37 | // holes.push(points.length / 2); 38 | holes[holes.length] = points.length / 2; 39 | points = points.concat(shapeData.holes[i].points); 40 | } 41 | } 42 | 43 | var triangles:Array = Earcut.earcut(points, holes ); 44 | if (!triangles) return; 45 | 46 | const vertPos:int = verts.length >> 1; 47 | 48 | ilen = triangles.length; 49 | for (i = 0; i < ilen; i += 3) { 50 | /*indices.push( 51 | triangles[i] + vertPos, 52 | triangles[i + 1] + vertPos, 53 | triangles[i + 2] + vertPos 54 | );*/ 55 | indices[indices.length] = triangles[i] + vertPos; 56 | indices[indices.length] = triangles[i + 1] + vertPos; 57 | indices[indices.length] = triangles[i + 2] + vertPos; 58 | } 59 | 60 | ilen = points.length; 61 | for (i = 0; i < ilen; i++) { 62 | verts[verts.length] = points[i]; 63 | } 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/math/shapes/Poly.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-11. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.math.shapes { 8 | import flash.geom.Point; 9 | 10 | import starling.utils.StringUtil; 11 | 12 | public class Poly extends AbsShape { 13 | 14 | public var closed:Boolean; 15 | public var points:Array; 16 | 17 | public function Poly(points:Array = null) { 18 | this.points = []; 19 | super(ShapeType.POLY); 20 | if (points != null) { 21 | setup(points); 22 | } 23 | } 24 | 25 | override public function reset():void { 26 | super.reset(); 27 | points.length = 0; 28 | } 29 | 30 | public function setup(points:Array):void { 31 | if (points[0] is Array) { 32 | points = points[0]; 33 | } else if (points[0] is Point) { 34 | var p:Array = []; 35 | for (var i:int = 0, ilen:int = points.length; i < ilen; i++) { 36 | p[p.length] = points[i].x; 37 | p[p.length] = points[i].y; 38 | } 39 | points = p; 40 | } 41 | closed = true; 42 | this.points = points; 43 | close(); 44 | } 45 | 46 | public function clone():Poly { 47 | return new Poly(this.points); 48 | } 49 | 50 | public function close():void { 51 | closed = true; 52 | const arr:Array = points; 53 | if (arr[0] != arr[arr.length - 2] || arr[1] != arr[arr.length - 1]) { 54 | arr[arr.length] = arr[0]; 55 | arr[arr.length] = arr[1]; 56 | // arr.push(arr[0], arr[1]); 57 | } 58 | } 59 | 60 | public function contains(px:Number, py:Number):Boolean { 61 | var inside:Boolean = false; 62 | const arr:Array = points; 63 | const len:int = arr.length >> 1; 64 | var j:int = len - 1; 65 | for (var i:int = 0; i < len; i++) { 66 | var xi:Number = arr[i * 2]; 67 | var yi:Number = arr[(i * 2) + 1]; 68 | var xj:Number = arr[j * 2]; 69 | var yj:Number = arr[(j * 2) + 1]; 70 | const intersect:Boolean = ((yi > py) !== (yj > py)) && (px < ((xj - xi) * ((py - yi) / (yj - yi))) + xi); 71 | if (intersect) { 72 | inside = !inside; 73 | } 74 | } 75 | return inside; 76 | } 77 | 78 | override public function toString():String { 79 | return StringUtil.format("[Poly #points={0}]", this.points.length >> 1); 80 | } 81 | 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/demos/DemoSVG.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-15. 4 | // 5 | // ================================================================================================= 6 | 7 | package demos { 8 | import com.roipeker.starling.draw.svg.SVGDraw; 9 | 10 | import flash.geom.Rectangle; 11 | 12 | import starling.display.Sprite; 13 | import starling.events.Event; 14 | import starling.utils.RectangleUtil; 15 | 16 | public class DemoSVG extends Sprite { 17 | 18 | public function DemoSVG() { 19 | addEventListener(Event.ADDED_TO_STAGE, init); 20 | } 21 | 22 | private function init(event:Event):void { 23 | 24 | stage.color = 0x333333; 25 | 26 | var svg:SVGDraw = new SVGDraw(); 27 | addChild(svg); 28 | 29 | // DrawLib has a limitation when it draws polygons, as it doesn't support winding. 30 | // So when you have "holes" inside shapes, you can fill them with a color, and define in the 31 | // or set the property svg::holeColor=0xff0000. 32 | 33 | // svg.parse(svg_samples.icoYesXML); // the CHECK has a fill color that is defined as hole, check the SVG. 34 | // svg.parse(svg_samples.pencilXML); 35 | // svg.parse(svg_samples.materialIcoCheck); 36 | // svg.parse(svg_samples.gmailIcon); 37 | // svg.parse(svg_samples.icoErrorXml); 38 | // svg.parse(svg_samples.tiger); 39 | // svg.parse(svg_samples.android); 40 | // svg.parse(svg_samples.fish); 41 | // svg.parse(svg_samples.modzilla1); 42 | // svg.parse(svg_samples.apple); 43 | // svg.parse(svg_samples.face); 44 | // svg.parse(svg_samples.google); 45 | 46 | // svg.parse(svg_samples.naturalDisaster1); 47 | // svg.parse(svg_samples.naturalDisaster2); 48 | svg.parse(svg_samples.emergency1); 49 | 50 | // buggy 51 | // svg.parse(svg_samples.gmaps); 52 | // svg.parse(svg_samples.gphotos); 53 | 54 | // center pivot, adjust size, center... 55 | svg.validate(); 56 | svg.alignPivot(); 57 | 58 | const max:Rectangle = new Rectangle(0, 0, stage.stageWidth / 2, stage.stageHeight / 2); 59 | 60 | // match the biggest dimensions 61 | var out:Rectangle = RectangleUtil.fit(svg.bounds, max); 62 | // trace(out); 63 | 64 | svg.width = out.width; 65 | svg.height = out.height; 66 | // svg.scaleY = svg.scaleX; 67 | svg.x = stage.stageWidth / 2; 68 | svg.y = stage.stageHeight / 2; 69 | 70 | /*svg.width = stage.stageWidth / 2; 71 | svg.scaleY = svg.scaleX; 72 | svg.x = stage.stageWidth / 2; 73 | svg.y = stage.stageHeight / 2;*/ 74 | 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /draw demos.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/Boot.as: -------------------------------------------------------------------------------- 1 | package { 2 | 3 | import flash.display.Bitmap; 4 | import flash.display.BitmapData; 5 | import flash.display.Loader; 6 | import flash.display.Sprite; 7 | import flash.display.StageAlign; 8 | import flash.display.StageQuality; 9 | import flash.display.StageScaleMode; 10 | import flash.events.Event; 11 | import flash.filesystem.File; 12 | import flash.filesystem.FileMode; 13 | import flash.filesystem.FileStream; 14 | import flash.geom.Rectangle; 15 | import flash.utils.ByteArray; 16 | 17 | import starling.core.Starling; 18 | 19 | [SWF(width="800", height="600", backgroundColor="#FFFFFF", frameRate="60")] 20 | public class Boot extends Sprite { 21 | 22 | private var starling:Starling; 23 | 24 | public function Boot() { 25 | 26 | init2(); 27 | 28 | // loaderInfo.addEventListener(Event.COMPLETE, onLoaderComplete); 29 | } 30 | 31 | private function init2():void { 32 | var path:String = '/Users/rodrigo/dev/flutter/mine/flutter_scrapper/assets/images/wild.png'; 33 | var f:File = new File(path); 34 | var fs:FileStream = new FileStream(); 35 | var ba:ByteArray = new ByteArray(); 36 | fs.open(f,FileMode.READ); 37 | fs.readBytes(ba); 38 | fs.close(); 39 | 40 | var loader:Loader= new Loader(); 41 | loader.contentLoaderInfo.addEventListener("complete", function(e){ 42 | 43 | 44 | var bd:BitmapData = Bitmap(loader.content).bitmapData; 45 | trace( bd.width>>1, bd.height>>1 ); 46 | // var px:uint = bd.getPixel(bd.width>>1, bd.height>>1); 47 | var px:uint = bd.getPixel32(473, 856); 48 | 49 | trace('iag loaded', px, '-', px.toString(16)); 50 | 51 | }); 52 | loader.loadBytes(ba); 53 | } 54 | 55 | private function onLoaderComplete(event:Event):void { 56 | stage.scaleMode = StageScaleMode.NO_SCALE; 57 | stage.align = StageAlign.TOP_LEFT; 58 | stage.quality = StageQuality.HIGH; 59 | 60 | starling = new Starling(StarlingRoot, stage); 61 | starling.antiAliasing = 2; 62 | starling.supportBrowserZoom = false; 63 | starling.supportHighResolutions = true; 64 | // starling.skipUnchangedFrames = true; 65 | starling.simulateMultitouch = false; 66 | starling.start(); 67 | 68 | stage.addEventListener(Event.RESIZE, onStageResize); 69 | } 70 | 71 | private function onStageResize(evt:Event):void { 72 | if (starling) { 73 | starling.stage.stageWidth = stage.stageWidth; 74 | starling.stage.stageHeight = stage.stageHeight; 75 | const viewport:Rectangle = starling.viewPort; 76 | viewport.width = stage.stageWidth; 77 | viewport.height = stage.stageHeight; 78 | starling.viewPort = viewport; 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # draw-lib 2 | 3 | Draw tries to mimic [AS3 Graphics API](https://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/Graphics.html) but is powered by [Starling](https://gamua.com/starling/). Was heavily ported from the Graphics implementation in PixiJS. 4 | And, although I do not recommend it for production use, the code wasn't tested enough and is unoptimized, it seems to work fine for simple vector shapes in my testings. 5 | 6 | ------------- 7 | 8 | #### Donation 9 | Support **draw-lib** via [Paypal](https://www.paypal.me/roipeker/) 10 | 11 | [![Donate via PayPal](https://cdn.rawgit.com/twolfson/paypal-github-button/1.0.0/dist/button.svg)](https://www.paypal.me/roipeker/) 12 | 13 | 14 | 15 | ##### IDE Software provided by JetBrains 16 | [![Jetbrains](https://raw.githubusercontent.com/tuarua/WebViewANE/master/screenshots/jetbrains.png)](https://www.jetbrains.com) 17 | 18 | ------------- 19 | 20 | 21 | ## Getting Started 22 | 23 | The code in this repo contains a *IntellijIdea* project with 2 projects modules: 24 | * draw_demos 25 | * draw_lib 26 | 27 | You can get a precompiled binary for Draw inside [_/draw_lib/bin-release/draw_lib.swc_](https://github.com/roipeker/draw-lib/tree/master/draw_lib/bin-release/draw_lib.swc), but the Intellij project uses Build Configurations dependencies to run the samples. 28 | 29 | ### Prerequisites 30 | 31 | Intellij and some version of [AdobeSDK](https://www.adobe.com/devnet/air/air-sdk-download.html) compatible with Starling v2.5.1 32 | 33 | ### API and demos. 34 | 35 | Considere the API very similar to the AS3 Graphics one. 36 | 37 | I didn't have the time to write documentation (or code comments), but 90% of the code was ported from PixiJS, so check their [docs](http://pixijs.download/dev/docs/PIXI.Graphics.html). 38 | 39 | ## demos screenshots 40 | 41 | ![demo 1](../media/images/demo1.png?raw=true) 42 | ![demo 2](../media/images/demo2.png?raw=true) 43 | ![demo 3](../media/images/demo3.gif?raw=true) 44 | ![demo 4](../media/images/demo4.gif?raw=true) 45 | ![demo 5](../media/images/demo5.gif?raw=true) 46 | ![demo 7](../media/images/demo7.png?raw=true) 47 | ![demo 8](../media/images/demo8.png?raw=true) 48 | ![demo 9](../media/images/demo9.gif?raw=true) 49 | ![demo 10](../media/images/demo10.gif?raw=true) 50 | ![demo 11](../media/images/demo11.png?raw=true) 51 | 52 | 53 | ## Contributing 54 | 55 | That's why I setup this repo! So, use pull request. 56 | 57 | You can also [buy me a coffee](https://www.paypal.me/roipeker/). 58 | 59 | 60 | ## Authors 61 | 62 | * **Rodrigo Lopez** - *Initial work* - [roipeker](https://github.com/roipeker) 63 | 64 | ## Acknowledgments 65 | 66 | * [Starling](https://forum.starling-framework.org/) 67 | * [PixiJS](https://www.pixijs.com/) 68 | 69 | ## License 70 | 71 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 72 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/math/shapes/RoundRect.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-11. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.math.shapes { 8 | import starling.utils.StringUtil; 9 | 10 | public class RoundRect extends AbsShape { 11 | 12 | public static function get empty():RoundRect { 13 | return new RoundRect(); 14 | } 15 | 16 | public var x:Number; 17 | public var y:Number; 18 | public var w:Number; 19 | public var h:Number; 20 | public var tlr:Number; 21 | public var trr:Number; 22 | public var blr:Number; 23 | public var brr:Number; 24 | 25 | public function RoundRect(x:Number = 0, y:Number = 0, w:Number = 0, h:Number = 0, tlr:Number = 10, trr:Number = 10, blr:Number = 10, brr:Number = 10) { 26 | this.x = x; 27 | this.y = y; 28 | this.w = w; 29 | this.h = h; 30 | this.tlr = tlr; 31 | this.trr = trr; 32 | this.blr = blr; 33 | this.brr = brr; 34 | adjustRadius(); 35 | super(ShapeType.RRECT); 36 | } 37 | 38 | private final function adjustRadius():void { 39 | if (trr < 0) trr = tlr; 40 | if (blr < 0) blr = tlr; 41 | if (brr < 0) brr = tlr; 42 | } 43 | 44 | public function clone():RoundRect { 45 | return new RoundRect(x, y, w, h, tlr, trr, blr, brr); 46 | } 47 | 48 | // not used. 49 | // TODO: add all rect corners checks. 50 | public function contains(px:Number, py:Number):Boolean { 51 | if (w <= 0 || h <= 0) return false; 52 | if (px >= x && px < x + w) { 53 | if (py >= y && py < y + h) { 54 | if ((py >= y + tlr && py <= y + h - tlr) || 55 | (px >= x + tlr && px <= x + w - tlr)) { 56 | return true; 57 | } 58 | var dx:Number = px - (x + tlr); 59 | var dy:Number = py - (y + tlr); 60 | const radius2:Number = tlr * tlr; 61 | if ((dx * dx + dy * dy) <= radius2) return true; 62 | dx = px - (x + w - radius2); 63 | if ((dx * dx + dy * dy) <= radius2) return true; 64 | dy = py - (y + h - radius2); 65 | if ((dx * dx + dy * dy) <= radius2) return true; 66 | dx = px - (x + tlr); 67 | if ((dx * dx + dy * dy) <= radius2) return true; 68 | } 69 | } 70 | return false; 71 | } 72 | 73 | override public function toString():String { 74 | return StringUtil.format("[RoundRect x={0}, y={1}, halfW={2}, halfH={3}, radius={4}]", x, y, w, h, tlr); 75 | } 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/builders/CircleBuilder.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-11. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.builders { 8 | import com.roipeker.starling.draw.GraphData; 9 | import com.roipeker.starling.draw.GraphGeom; 10 | import com.roipeker.starling.draw.math.shapes.Circle; 11 | import com.roipeker.starling.draw.math.shapes.Ellipse; 12 | 13 | public class CircleBuilder extends AbsShapeBuilder { 14 | 15 | // Defines a global factor to reduce by the radius of width and height 16 | // Higher number = less segments. 17 | public static var segmentReduceFactor:Number = 2.5; 18 | 19 | public function CircleBuilder() { 20 | super(); 21 | } 22 | 23 | override public function build(shapeData:GraphData):void { 24 | const points:Array = shapeData.points; 25 | var x:Number, y:Number, w:Number = 0, h:Number = 0; 26 | var totalSegments:int; 27 | 28 | if (shapeData.type.isCircle()) { 29 | const circleData:Circle = shapeData.shape as Circle; 30 | x = circleData.x; 31 | y = circleData.y; 32 | w = h = circleData.radius; 33 | // totalSegments = Math.floor(30 * Math.sqrt(halfW)) / segmentReduceFactor; 34 | } else if (shapeData.type.isEllipse()) { 35 | const ellipseData:Ellipse = shapeData.shape as Ellipse; 36 | x = ellipseData.x; 37 | y = ellipseData.y; 38 | w = ellipseData.halfW; 39 | h = ellipseData.halfH; 40 | // totalSegments = Math.floor(15 * Math.sqrt(halfW + halfH)) / segmentReduceFactor; 41 | } 42 | 43 | if (w == 0 || h == 0) return; 44 | totalSegments = 15 * Math.sqrt(w + h) | 0; 45 | totalSegments /= segmentReduceFactor; 46 | points.length = 0; 47 | 48 | const segment:Number = (Math.PI * 2) / totalSegments; 49 | 50 | for (var i:int = 0; i < totalSegments; i++) { 51 | const angle:Number = segment * i; 52 | points.push( 53 | x + (-Math.sin(angle) * w), 54 | y + (-Math.cos(angle) * h) 55 | ); 56 | } 57 | points.push(points[0], points[1]); 58 | } 59 | 60 | override public function triangulate(shapeData:GraphData, geometry:GraphGeom):void { 61 | const points:Array = shapeData.points; 62 | const verts:Array = geometry.points; 63 | const indices:Array = geometry.indices; 64 | const center:int = verts.length >> 1; 65 | var vertPos:int = center; 66 | 67 | // Circle and Ellipse have x,y 68 | verts.push(shapeData.shape['x'], shapeData.shape['y']); 69 | for (var i:int = 0, ilen:int = points.length; i < ilen; i += 2) { 70 | verts.push(points[i], points[int(i + 1)]); 71 | indices.push(vertPos, center, ++vertPos); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/demos/DemoLineStyles.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-16. 4 | // 5 | // ================================================================================================= 6 | 7 | package demos { 8 | import com.roipeker.starling.draw.Draw; 9 | 10 | import flash.display.CapsStyle; 11 | import flash.display.JointStyle; 12 | 13 | import starling.display.Sprite; 14 | import starling.events.Event; 15 | 16 | public class DemoLineStyles extends Sprite { 17 | 18 | public function DemoLineStyles() { 19 | addEventListener(Event.ADDED_TO_STAGE, init); 20 | } 21 | 22 | private function init(event:Event):void { 23 | var draw:Draw = new Draw(); 24 | addChild(draw); 25 | 26 | // when default to JoinStyle.MITTER, CapsStyle.SQUARE, it uses the optimized PixiJS lines routine, waaay less polygons. 27 | // ans also supports ::alignment to shift from 0 (line outside the shape), to 1 (line inside shape)... although there's no restrictions for the value. 28 | draw.lineStyle(20, 0x0, .5); 29 | draw.moveTo(490, 40); 30 | draw.lineTo(600, 200); 31 | draw.lineTo(640, 100); 32 | 33 | 34 | // How aligmnent works..... 35 | draw.lineStyle(10, 0xff0000, .25, 1); 36 | draw.beginFill(0x00ff00, 1); 37 | draw.drawCircle(50, 300, 40); 38 | draw.endFill(); 39 | 40 | draw.lineStyle(10, 0xff0000, .25, .5); 41 | draw.beginFill(0x00ff00, 1); 42 | draw.drawCircle(150, 300, 40); 43 | draw.endFill(); 44 | 45 | draw.lineStyle(10, 0xff0000, .25, 0); 46 | draw.beginFill(0x00ff00, 1); 47 | draw.drawCircle(250, 300, 40); 48 | draw.endFill(); 49 | 50 | // with -1, the line offsets to the outside to the specified line width (thickness) 51 | draw.lineStyle(10, 0xff0000, .25, -1); 52 | draw.beginFill(0x00ff00, 1); 53 | draw.drawCircle(350, 300, 40); 54 | draw.endFill(); 55 | 56 | /// ------ 57 | 58 | // complex line style (joins and caps) uses a different "rendering" system... 59 | 60 | draw.lineStyle(12, 0xff0000, .75, 0, JointStyle.BEVEL, CapsStyle.ROUND); 61 | draw.beginFill(0x00ff00, .6); 62 | draw.drawRect(50, 50, 80, 80); 63 | draw.endFill(); 64 | 65 | draw.lineStyle(20, 0xff0000, 1, 0.5, JointStyle.ROUND, CapsStyle.ROUND); 66 | draw.moveTo(120 + 60, 80); 67 | draw.lineTo(120 + 200, 200); 68 | draw.lineTo(120 + 300, 50); 69 | draw.lineTo(120 + 10, 200); 70 | 71 | draw.lineStyle(4, 0x00ff00, .7, 0.5, JointStyle.BEVEL, CapsStyle.NONE); 72 | draw.moveTo(120 + 60, 80); 73 | draw.lineTo(120 + 200, 200); 74 | draw.lineTo(120 + 300, 50); 75 | draw.lineTo(120 + 10, 200); 76 | 77 | // 2 color gradients in fills and lines (Thanks to @JohnBlackburne) 78 | draw.lineGradientStyle(5, 0xc21500, 0xffc500, Math.PI / 2, .4, .4, 0.5); 79 | draw.beginGradientFill(0xe4e4d9, 0x215f00, Math.PI, 1, 1); 80 | draw.drawRoundRectComplex(450, 280, 130, 90, 20, 12, 40, 20); 81 | draw.endFill(); 82 | 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/utils/QuadraticUtils.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 04/01/2019. 4 | // 5 | // ported from PixiJS 6 | // https://github.com/pixijs/pixi.js 7 | // 8 | // ================================================================================================= 9 | 10 | package com.roipeker.starling.draw.utils { 11 | public class QuadraticUtils { 12 | public function QuadraticUtils() { 13 | } 14 | 15 | /** 16 | * Calculate length of quadratic curve 17 | * @see {@link http://www.malczak.linuxpl.com/blog/quadratic-bezier-curve-length/} 18 | * for the detailed explanation of math behind this. 19 | * 20 | * @private 21 | * @param {number} fromX - x-coordinate of curve start point 22 | * @param {number} fromY - y-coordinate of curve start point 23 | * @param {number} cpX - x-coordinate of curve control point 24 | * @param {number} cpY - y-coordinate of curve control point 25 | * @param {number} toX - x-coordinate of curve end point 26 | * @param {number} toY - y-coordinate of curve end point 27 | * @return {number} Length of quadratic curve 28 | */ 29 | public static function quadraticBezierLength(p0x:Number, p0y:Number, p1x:Number, p1y:Number, p2x:Number, p2y:Number):Number { 30 | var ax:Number = p0x - 2 * p1x + p2x; 31 | var ay:Number = p0y - 2 * p1y + p2y; 32 | var bx:Number = 2 * p1x - 2 * p0x; 33 | var by:Number = 2 * p1y - 2 * p0y; 34 | var A:Number = 4 * (ax * ax + ay * ay); 35 | var B:Number = 4 * (ax * bx + ay * by); 36 | var C:Number = bx * bx + by * by; 37 | var Sabc:Number = 2 * Math.sqrt(A + B + C); 38 | var A_2:Number = Math.sqrt(A); 39 | var A_32:Number = 2 * A * A_2; 40 | var C_2:Number = 2 * Math.sqrt(C); 41 | var BA:Number = B / A_2; 42 | return (A_32 * Sabc + A_2 * B * (Sabc - C_2) + (4 * C * A - B * B) * Math.log((2 * A_2 + BA + Sabc) / (BA + C_2))) / (4 * A_32); 43 | } 44 | 45 | /** 46 | * Calculate the points for a quadratic bezier curve and then draws it. 47 | * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c 48 | * 49 | * @param {number} cpX - Control point x 50 | * @param {number} cpY - Control point y 51 | * @param {number} toX - Destination point x 52 | * @param {number} toY - Destination point y 53 | * @param {number[]} points - Points to add segments to. 54 | */ 55 | public static function curveTo(cpX:Number, cpY:Number, toX:Number, toY:Number, points:Array):void { 56 | var fromX:Number = points[points.length - 2]; 57 | var fromY:Number = points[points.length - 1]; 58 | const n:uint = GraphicCurves.segmentsCount( 59 | QuadraticUtils.quadraticBezierLength(fromX, fromY, cpX, cpY, toX, toY) 60 | ); 61 | var xa:Number = 0; 62 | var ya:Number = 0; 63 | var j:Number; 64 | for (var i:int = 1; i <= n; ++i) { 65 | j = i / n; 66 | xa = fromX + ((cpX - fromX) * j); 67 | ya = fromY + ((cpY - fromY) * j); 68 | points.push(xa + (((cpX + ((toX - cpX) * j)) - xa) * j), 69 | ya + (((cpY + ((toY - cpY) * j)) - ya) * j)); 70 | } 71 | } 72 | } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/builders/GraphicsDataBuild.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-15. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.builders { 8 | import com.roipeker.starling.draw.Draw; 9 | 10 | import flash.display.*; 11 | import flash.utils.Dictionary; 12 | 13 | public class GraphicsDataBuild { 14 | public function GraphicsDataBuild() { 15 | } 16 | 17 | // SPECIAL TO BUILD. 18 | public static var _graphCommandsMap:Dictionary; 19 | 20 | public static function initGraphMapClasses():void { 21 | if(_graphCommandsMap) return ; 22 | _graphCommandsMap = new Dictionary(false); 23 | _graphCommandsMap[GraphicsStroke] = _grapStroke; 24 | _graphCommandsMap[GraphicsPath] = _graphPath; 25 | _graphCommandsMap[GraphicsSolidFill] = _graphSolidFill; 26 | _graphCommandsMap[GraphicsEndFill] = _graphEndFill; 27 | _graphCommandsMap[GraphicsGradientFill] = _graphGradientFill; 28 | } 29 | 30 | private static function _grapStroke(obj:Draw, g:GraphicsStroke) { 31 | var o:Object = { 32 | width: g.thickness || 0, 33 | miterlimit: g.miterLimit, 34 | caps: CapsStyle.SQUARE,//g.caps, 35 | joints: JointStyle.MITER//g.joints 36 | }; 37 | if (g.fill) { 38 | if (g.fill is GraphicsSolidFill) addSolid(g.fill as GraphicsSolidFill); 39 | } 40 | 41 | function addSolid(fill:GraphicsSolidFill):void { 42 | o.color = fill.color, o.alpha = fill.alpha; 43 | } 44 | 45 | obj.lineStyle(o.width, o.color, o.alpha, .5, o.joints, o.caps, o.miterlimit); 46 | } 47 | 48 | private static function _graphPath(obj:Draw, g:GraphicsPath) { 49 | obj.drawPath(g.commands, g.data); 50 | } 51 | 52 | private static function _graphSolidFill(obj:Draw, g:GraphicsSolidFill) { 53 | obj.beginFill(g.color, g.alpha); 54 | } 55 | 56 | private static function _graphGradientFill(obj:Draw, g:GraphicsGradientFill) { 57 | var angle:Number = 0; 58 | if (g.matrix) { 59 | const a:Number = g.matrix.a; 60 | const b:Number = g.matrix.b; 61 | const c:Number = g.matrix.c; 62 | const d:Number = g.matrix.d; 63 | var r:Number; 64 | if (a != 0 || b != 0) { 65 | r = Math.sqrt(a * a + b * b); 66 | angle = b > 0 ? Math.acos(a / r) : -Math.acos(a / r); 67 | } else if (c != 0 || d != 0) { 68 | r = Math.sqrt(c * c + d * d); 69 | angle = Math.PI / 2 - (d > 0 ? Math.acos(-c / r) : -Math.acos(c / r)); 70 | } 71 | } 72 | obj.beginGradientFill(g.colors[1], g.colors[0], angle, g.alphas[0], g.alphas[1]); 73 | } 74 | 75 | private static function _graphEndFill(obj:Draw, g:GraphicsEndFill) { 76 | obj.endFill(); 77 | } 78 | 79 | public function drawGraphicsData(graphicsData:Vector.):void { 80 | if (!_graphCommandsMap) initGraphMapClasses(); 81 | for (var i:int = 0, ilen:int = graphicsData.length; i < ilen; i++) { 82 | var gd:IGraphicsData = graphicsData[i]; 83 | var clase:Class = Object(gd).constructor; 84 | if (clase && _graphCommandsMap[clase]) { 85 | _graphCommandsMap[clase](this, gd); 86 | } else { 87 | trace("DrawGraphicsData unsupported command:", clase, JSON.stringify(gd)); 88 | } 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/demos/DemoPixiAdvanced.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-15. 4 | // 5 | // ================================================================================================= 6 | 7 | package demos { 8 | import com.roipeker.starling.draw.Draw; 9 | 10 | import starling.display.Sprite; 11 | import starling.events.Event; 12 | 13 | /** 14 | * 15 | * Ported from 16 | * https://pixijs.io/examples/#/graphics/advanced.js 17 | * 18 | * WARNING: texture fills/lineStyles not supported. 19 | * 20 | */ 21 | public class DemoPixiAdvanced extends Sprite { 22 | 23 | public function DemoPixiAdvanced() { 24 | addEventListener(Event.ADDED_TO_STAGE, init); 25 | } 26 | 27 | private function init(event:Event):void { 28 | 29 | stage.color = 0x333333; 30 | 31 | // const sprite = PIXI.Sprite.from('examples/assets/bg_rotate.jpg'); 32 | 33 | // // BEZIER CURVE //// 34 | // information: https://en.wikipedia.org/wiki/Bézier_curve 35 | 36 | const realPath:Draw = new Draw(); 37 | realPath.lineStyle(2, 0xFFFFFF, 1); 38 | realPath.moveTo(0, 0); 39 | realPath.lineTo(100, 200); 40 | realPath.lineTo(200, 200); 41 | realPath.lineTo(240, 100); 42 | 43 | realPath.x = 50; 44 | realPath.y = 50; 45 | 46 | addChild(realPath); 47 | 48 | 49 | const bezier:Draw = new Draw(); 50 | bezier.lineStyle(5, 0xAA0000, 1); 51 | bezier.bezierCurveTo(100, 200, 200, 200, 240, 100); 52 | 53 | bezier.x = 50; 54 | bezier.y = 50; 55 | 56 | addChild(bezier); 57 | 58 | 59 | // // BEZIER CURVE 2 //// 60 | const realPath2:Draw = new Draw(); 61 | realPath2.lineStyle(2, 0xFFFFFF, 1); 62 | realPath2.moveTo(0, 0); 63 | realPath2.lineTo(0, -100); 64 | realPath2.lineTo(150, 150); 65 | realPath2.lineTo(240, 100); 66 | 67 | realPath2.x = 320; 68 | realPath2.y = 150; 69 | 70 | addChild(realPath2); 71 | 72 | 73 | 74 | const bezier2:Draw = new Draw(); 75 | bezier2.lineStyle(10, 0xffffff); 76 | // bezier2.lineTextureStyle(10, sprite.texture); 77 | bezier2.bezierCurveTo(0, -100, 150, 150, 240, 100); 78 | 79 | bezier2.x = 320; 80 | bezier2.y = 150; 81 | 82 | addChild(bezier2); 83 | 84 | // // ARC //// 85 | const arc:Draw = new Draw(); 86 | arc.lineStyle(5, 0xAA00BB, 1); 87 | arc.arc(600, 100, 50, Math.PI, 2 * Math.PI); 88 | 89 | addChild(arc); 90 | 91 | // // ARC 2 //// 92 | const arc2:Draw = new Draw(); 93 | arc2.lineStyle(6, 0x3333DD, 1); 94 | arc2.arc(650, 270, 60, 2 * Math.PI, 3 * Math.PI / 2); 95 | 96 | addChild(arc2); 97 | 98 | // // ARC 3 //// 99 | const arc3:Draw = new Draw(); 100 | arc3.lineStyle(20, 0xff0000); 101 | // arc3.lineTextureStyle(20, sprite.texture); 102 | arc3.arc(650, 420, 60, 2 * Math.PI, 2.5 * Math.PI / 2); 103 | 104 | addChild(arc3); 105 | 106 | // / Hole //// 107 | const rectAndHole:Draw = new Draw(); 108 | rectAndHole.beginFill(0x00FF00); 109 | rectAndHole.drawRect(350, 350, 150, 150); 110 | rectAndHole.beginHole(); 111 | rectAndHole.drawCircle(375, 375, 25); 112 | rectAndHole.drawCircle(425, 425, 25); 113 | rectAndHole.drawCircle(475, 475, 25); 114 | rectAndHole.endHole(); 115 | rectAndHole.endFill(); 116 | 117 | addChild(rectAndHole); 118 | 119 | // // Line Texture Style //// 120 | const beatifulRect:Draw = new Draw(); 121 | 122 | // beatifulRect.lineTextureStyle(20, sprite.texture); 123 | beatifulRect.lineStyle(20, 0x00ff00); 124 | beatifulRect.beginFill(0xFF0000); 125 | beatifulRect.drawRect(80, 350, 150, 150); 126 | beatifulRect.endFill(); 127 | 128 | addChild(beatifulRect); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/demos/DemoPixiSimple.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-15. 4 | // 5 | // ================================================================================================= 6 | 7 | package demos { 8 | import com.roipeker.starling.draw.Draw; 9 | 10 | import starling.display.Sprite; 11 | import starling.events.Event; 12 | 13 | /** 14 | * Ported from 15 | * https://pixijs.io/examples/#/graphics/simple.js 16 | */ 17 | public class DemoPixiSimple extends Sprite { 18 | 19 | public function DemoPixiSimple() { 20 | addEventListener(Event.ADDED_TO_STAGE, init); 21 | } 22 | 23 | private function init(event:Event):void { 24 | 25 | stage.color = 0x333333 ; 26 | 27 | const graphics:Draw = new Draw(); 28 | addChild(graphics); 29 | 30 | // Rectangle 31 | graphics.beginFill(0xDE3249); 32 | graphics.drawRect(50, 50, 100, 100); 33 | graphics.endFill(); 34 | 35 | // Rectangle + line style 1 36 | graphics.lineStyle(2, 0xFEEB77, 1); 37 | graphics.beginFill(0x650A5A); 38 | graphics.drawRect(200, 50, 100, 100); 39 | graphics.endFill(); 40 | 41 | // Rectangle + line style 2 42 | graphics.lineStyle(10, 0xFFBD01, 1); 43 | graphics.beginFill(0xC34288); 44 | graphics.drawRect(350, 50, 100, 100); 45 | graphics.endFill(); 46 | 47 | // Rectangle 2 48 | graphics.lineStyle(2, 0xFFFFFF, 1); 49 | graphics.beginFill(0xAA4F08); 50 | graphics.drawRect(530, 50, 140, 100); 51 | graphics.endFill(); 52 | 53 | // Circle 54 | graphics.lineStyle(0); // draw a circle, set the lineStyle to zero so the circle doesn't have an outline 55 | graphics.beginFill(0xDE3249, 1); 56 | graphics.drawCircle(100, 250, 50); 57 | graphics.endFill(); 58 | 59 | // Circle + line style 1 60 | graphics.lineStyle(2, 0xFEEB77, 1); 61 | graphics.beginFill(0x650A5A, 1); 62 | graphics.drawCircle(250, 250, 50); 63 | graphics.endFill(); 64 | 65 | // Circle + line style 2 66 | graphics.lineStyle(10, 0xFFBD01, 1); 67 | graphics.beginFill(0xC34288, 1); 68 | graphics.drawCircle(400, 250, 50); 69 | graphics.endFill(); 70 | 71 | // Ellipse + line style 2 72 | graphics.lineStyle(2, 0xFFFFFF, 1); 73 | graphics.beginFill(0xAA4F08, 1); 74 | graphics.drawEllipse(600, 250, 80, 50); 75 | graphics.endFill(); 76 | 77 | // draw a shape 78 | graphics.beginFill(0xFF3300); 79 | graphics.lineStyle(4, 0xffd900, 1); 80 | graphics.moveTo(50, 350); 81 | graphics.lineTo(250, 350); 82 | graphics.lineTo(100, 400); 83 | graphics.lineTo(50, 350); 84 | graphics.endFill(); 85 | 86 | // draw a rounded rectangle 87 | graphics.lineStyle(2, 0xFF00FF, 1); 88 | graphics.beginFill(0x650A5A, 0.25); 89 | graphics.drawRoundRect(50, 440, 100, 100, 16); 90 | graphics.endFill(); 91 | 92 | // draw star 93 | graphics.lineStyle(2, 0xFFFFFF); 94 | graphics.beginFill(0x35CC5A, 1); 95 | graphics.drawStar(360, 370, 5, 50); 96 | graphics.endFill(); 97 | 98 | // draw star 2 99 | graphics.lineStyle(2, 0xFFFFFF); 100 | graphics.beginFill(0xFFCC5A, 1); 101 | graphics.drawStar(280, 510, 7, 50); 102 | graphics.endFill(); 103 | 104 | // draw star 3 105 | graphics.lineStyle(4, 0xFFFFFF); 106 | graphics.beginFill(0x55335A, 1); 107 | graphics.drawStar(470, 450, 4, 50); 108 | graphics.endFill(); 109 | 110 | // draw polygon 111 | const path:Array = [ 112 | 600, 370, 113 | 700, 460, 114 | 780, 420, 115 | 730, 570, 116 | 590, 520 117 | ]; 118 | 119 | graphics.lineStyle(0); 120 | graphics.beginFill(0x3500FA, 1); 121 | graphics.drawPolygon(path); 122 | graphics.endFill(); 123 | 124 | addChild(graphics); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/demos/DemoGraphicsData.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-15. 4 | // 5 | // ================================================================================================= 6 | 7 | package demos { 8 | import com.roipeker.starling.draw.Draw; 9 | 10 | import flash.display.Graphics; 11 | import flash.display.MovieClip; 12 | import flash.display.Shape; 13 | 14 | import starling.animation.Transitions; 15 | import starling.core.Starling; 16 | import starling.display.Sprite; 17 | import starling.events.Event; 18 | import starling.events.Touch; 19 | import starling.events.TouchEvent; 20 | import starling.events.TouchPhase; 21 | 22 | public class DemoGraphicsData extends Sprite { 23 | 24 | private var isHovering:Boolean = false; 25 | private var ninja2:Draw; 26 | 27 | public function DemoGraphicsData() { 28 | addEventListener(Event.ADDED_TO_STAGE, init); 29 | } 30 | 31 | private function init(event:Event):void { 32 | stage.color = 0xffffffff; 33 | 34 | // old school, mimic graphics data. 35 | 36 | var ninja1:Draw = makeDrawFromMovieClip(new ninja_girl_idle()); 37 | ninja1.x = ninja1.width / 2; 38 | ninja1.y = ninja1.height / 2; 39 | 40 | 41 | // as we can see with these MovieClips from the SWC, 42 | // Draw's drawing commands are a little buggy. 43 | 44 | var koala:Draw = makeDrawFromMovieClip(new koala_mc()); 45 | koala.scale = .5; 46 | koala.x = stage.stageWidth - koala.width / 2; 47 | koala.y = koala.height / 2; 48 | 49 | 50 | var mole:Draw = makeDrawFromMovieClip(new mole_mc()); 51 | mole.x = stage.stageWidth - mole.width / 2; 52 | mole.y = stage.stageHeight - mole.height / 2; 53 | 54 | var pinocchio:Draw = makeDrawFromMovieClip(new pinocchio_mc()); 55 | pinocchio.scale = .5; 56 | pinocchio.x = pinocchio.width / 2; 57 | pinocchio.y = stage.stageHeight - pinocchio.height / 2; 58 | 59 | // centered, animated, interactive ninja 60 | 61 | ninja2 = new Draw(); 62 | addChild(ninja2); 63 | 64 | // copy ninja1 geometry, (no need for parsing). 65 | ninja2.copyFrom(ninja1, true); 66 | ninja2.alignPivot(); 67 | ninja2.scale = 0.1; 68 | 69 | ninja2.x = stage.stageWidth / 2; 70 | ninja2.y = stage.stageHeight / 2; 71 | 72 | // scale ninja 73 | Starling.juggler.tween(ninja2, 4, {scale: 6, reverse: true, repeatCount: 0, repeatDelay: 1}); 74 | 75 | ninja2.rotation = -.3; 76 | Starling.juggler.tween(ninja2, .7, { 77 | rotation: .3, 78 | reverse: true, 79 | repeatCount: 0, 80 | transition: Transitions.EASE_IN 81 | }); 82 | 83 | ninja2.touchable = true; 84 | ninja2.addEventListener(TouchEvent.TOUCH, handleNinjaTouch); 85 | } 86 | 87 | private function handleNinjaTouch(e:TouchEvent):void { 88 | var t:Touch = e.getTouch(ninja2); 89 | if (t && t.phase == TouchPhase.HOVER) { 90 | // HOVER 91 | hover(true); 92 | } else if (t == null) { 93 | // OUT 94 | hover(false); 95 | } 96 | } 97 | 98 | private function hover(flag:Boolean):void { 99 | if (isHovering == flag) return; 100 | isHovering = flag; 101 | Starling.juggler.tween(ninja2, .15, 102 | {alpha: isHovering ? 0.5 : 1, transition: Transitions.EASE_OUT}); 103 | } 104 | 105 | private function makeDrawFromMovieClip(mc:MovieClip):Draw { 106 | var draw:Draw = new Draw(); 107 | addChild(draw); 108 | draw.drawGraphicsData(getGraphics(mc).readGraphicsData()); 109 | draw.validate(); 110 | draw.alignPivot(); 111 | // draw.touchGroup = true ; 112 | draw.touchable = false; 113 | return draw; 114 | } 115 | 116 | private function getGraphics(mc:MovieClip):Graphics { 117 | return Shape(mc.getChildAt(0)).graphics; 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/math/shapes/Rect.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-11. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.math.shapes { 8 | import starling.utils.MathUtil; 9 | import starling.utils.StringUtil; 10 | 11 | public class Rect extends AbsShape { 12 | 13 | // TODO: add pool. 14 | 15 | public static function get empty():Rect { 16 | return new Rect(); 17 | } 18 | 19 | public var x:Number; 20 | public var y:Number; 21 | public var w:Number; 22 | public var h:Number; 23 | 24 | public function Rect(x:Number = 0, y:Number = 0, w:Number = 0, h:Number = 0) { 25 | this.x = x; 26 | this.y = y; 27 | this.w = w; 28 | this.h = h; 29 | super(ShapeType.RECT); 30 | } 31 | 32 | public function get left():Number { 33 | return x; 34 | } 35 | 36 | public function get right():Number { 37 | return x + w; 38 | } 39 | 40 | public function get top():Number { 41 | return y; 42 | } 43 | 44 | public function get bottom():Number { 45 | return y + h; 46 | } 47 | 48 | public function clone():Rect { 49 | return new Rect(x, y, w, h); 50 | } 51 | 52 | public function copyFrom(rect:Rect):Rect { 53 | x = rect.x; 54 | y = rect.y; 55 | w = rect.w; 56 | h = rect.h; 57 | return this; 58 | } 59 | 60 | public function copyTo(rect:Rect):Rect { 61 | rect.x = x; 62 | rect.y = y; 63 | rect.w = w; 64 | rect.h = h; 65 | return rect; 66 | } 67 | 68 | public function contains(x:Number, y:Number):Boolean { 69 | if (w <= 0 || h <= 0) { 70 | return false; 71 | } 72 | if (x >= this.x && x < this.x + w) { 73 | if (y >= this.y && y < this.y + h) { 74 | return true; 75 | } 76 | } 77 | return false; 78 | } 79 | 80 | public function pad(x:Number, y:Number = Number.NaN):void { 81 | x = x || 0; 82 | y = y || (y !== 0 ? x : 0); 83 | this.x -= x; 84 | this.y -= y; 85 | w += x * 2; 86 | h += y * 2; 87 | } 88 | 89 | public function fit(rect:Rect):void { 90 | if (x < rect.x) { 91 | w += x; 92 | if (w < 0) w = 0; 93 | x = rect.x; 94 | } 95 | if (y < rect.y) { 96 | h += y; 97 | if (h < 0) h = 0; 98 | y = rect.y; 99 | } 100 | 101 | if (x + w > rect.x + rect.w) { 102 | w = rect.w - x; 103 | if (w < 0) { 104 | w = 0; 105 | } 106 | } 107 | 108 | if (y + h > rect.y + rect.h) { 109 | h = rect.h - y; 110 | if (h < 0) { 111 | h = 0; 112 | } 113 | } 114 | } 115 | 116 | public function ceil(resolution:Number = 1, eps:Number = .001):void { 117 | const x2:Number = Math.ceil((x + w - eps) * resolution) / resolution; 118 | const y2:Number = Math.ceil((y + h - eps) * resolution) / resolution; 119 | x = (x + eps * resolution | 0) / resolution; 120 | y = (y + eps * resolution | 0) / resolution; 121 | w = x2 - x; 122 | h = y2 - y; 123 | } 124 | 125 | public function enlarge(rect:Rect):void { 126 | const min:Function = MathUtil.min; 127 | const max:Function = MathUtil.max; 128 | const x1:Number = min(x, rect.x); 129 | const x2:Number = max(x + w, rect.x + rect.w); 130 | const y1:Number = min(y, rect.y); 131 | const y2:Number = max(y + h, rect.y + rect.h); 132 | x = x1; 133 | w = x2 - x1; 134 | y = y2; 135 | h = y2 - y1; 136 | } 137 | 138 | override public function toString():String { 139 | return StringUtil.format("[Rect x={0}, y={1}, halfW={2}, halfH={3}]", x, y, w, h); 140 | } 141 | 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/demos/DemoAnimatedArc.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-15. 4 | // 5 | // ================================================================================================= 6 | 7 | package demos { 8 | import com.roipeker.starling.draw.Draw; 9 | 10 | import starling.animation.Tween; 11 | import starling.core.Starling; 12 | import starling.display.Sprite; 13 | import starling.events.Event; 14 | import starling.events.Touch; 15 | import starling.events.TouchEvent; 16 | import starling.events.TouchPhase; 17 | 18 | public class DemoAnimatedArc extends Sprite { 19 | 20 | public function DemoAnimatedArc() { 21 | addEventListener(Event.ADDED_TO_STAGE, init); 22 | } 23 | 24 | private function init(event:Event):void { 25 | stage.color = 0xffffff ; 26 | 27 | const g:Draw = new Draw(); 28 | addChild(g); 29 | 30 | // tween these properties 31 | const arcData:Object = { 32 | x: 150, 33 | y: 150, 34 | radius: 150 / 3, 35 | angle: 10, 36 | color: 0xff0000 37 | }; 38 | 39 | g.addEventListener(TouchEvent.TOUCH, handleTouch); 40 | 41 | function handleTouch(e:TouchEvent):void { 42 | // when clicked. 43 | var t:Touch = e.getTouch(g, TouchPhase.BEGAN); 44 | if (t) { 45 | arcData.color = Math.random() * 0xffffff; 46 | if (tween.progress == 0 || tween.progress == 1) { 47 | drawArc(); 48 | } 49 | } 50 | } 51 | 52 | const RAD:Number = Math.PI / 180; 53 | var tween:Tween; 54 | tween = new Tween(arcData, 1, 'linear'); 55 | tween.reverse = true; 56 | tween.repeatCount = 0; 57 | tween.repeatDelay = .5; 58 | tween.onUpdate = drawArc; 59 | tween.animate('angle', 361); 60 | Starling.juggler.add(tween); 61 | 62 | function drawArc():void { 63 | g.clear() 64 | .lineStyle(20, arcData.color, 1) 65 | // .lineGradientStyle(30, 0xff0000, 0xff00ff, .3 ) 66 | .arc(arcData.x, arcData.y, arcData.radius, 0, arcData.angle * RAD); 67 | } 68 | 69 | pacman(); 70 | } 71 | 72 | private function pacman():void { 73 | var pacman:Draw = new Draw(); 74 | addChild(pacman); 75 | 76 | pacman.x = 400; 77 | pacman.y = 200; 78 | 79 | var o:Object = {p: 0.1, b: 1}; 80 | 81 | 82 | Starling.juggler.tween(o, .25, {p: 1, reverse: true, repeatCount: 0, onUpdate: drawPackman}); 83 | 84 | drawPackman(); 85 | 86 | blink(); 87 | 88 | function blink() { 89 | Starling.juggler.tween(o, .12, { 90 | delay: .1 + Math.random(), 91 | b: 0, 92 | reverse: true, 93 | onComplete:blink, 94 | repeatCount: 2 95 | }); 96 | } 97 | 98 | function drawPackman():void { 99 | 100 | const maxAngle:Number = Math.PI * 2 / 4; 101 | 102 | var angle:Number = o.p * maxAngle; 103 | var endAng:Number = Math.PI * 2 - angle / 2; 104 | 105 | pacman.clear() 106 | .lineStyle(4, 0x0, .5, 1) 107 | .beginFill(0xfdea6c, 1) 108 | .moveTo(0, 0) 109 | .arc(0, 0, 80, angle / 2, endAng) 110 | .lineTo(0, 0) 111 | .endFill() 112 | 113 | .lineStyle(0) 114 | 115 | .beginFill(0xf2d159, 1) 116 | .moveTo(0, 0) 117 | .arc(0, 0, 80, angle/2,Math.PI ) 118 | .lineTo(0, 0) 119 | .endFill() 120 | 121 | .beginFill(0x4d4d4d, .95) 122 | .drawEllipse(10 - Math.cos(endAng) * 10, -30 + Math.sin(endAng) * 20, 12, 12 * o.b) 123 | .endFill() 124 | 125 | // .beginFill(0x462b57, .95) 126 | .lineStyle(4, 0x0, 1) 127 | .moveTo(Math.cos(endAng)*80,Math.sin(angle/2)*80) 128 | .lineTo(200,200); 129 | } 130 | 131 | 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/demos/DemoPixiDynamic.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-15. 4 | // 5 | // ================================================================================================= 6 | 7 | package demos { 8 | import com.roipeker.starling.draw.Draw; 9 | 10 | import starling.display.Sprite; 11 | import starling.events.Event; 12 | import starling.events.Touch; 13 | import starling.events.TouchEvent; 14 | import starling.events.TouchPhase; 15 | 16 | /** 17 | * Ported from 18 | * https://pixijs.io/examples/#/graphics/dynamic.js 19 | */ 20 | public class DemoPixiDynamic extends Sprite { 21 | 22 | public function DemoPixiDynamic() { 23 | addEventListener(Event.ADDED_TO_STAGE, init); 24 | } 25 | 26 | private function init(event:Event):void { 27 | 28 | stage.color = 0x333333; 29 | stage.color = 0x0; 30 | 31 | const graphics:Draw = new Draw(); 32 | addChild(graphics); 33 | 34 | // set a fill and line style 35 | graphics.beginFill(0xFF3300); 36 | graphics.lineStyle(10, 0xffd900, 1); 37 | 38 | // draw a shape 39 | graphics.moveTo(50, 50); 40 | graphics.lineTo(250, 50); 41 | graphics.lineTo(100, 100); 42 | graphics.lineTo(250, 220); 43 | graphics.lineTo(50, 220); 44 | graphics.lineTo(50, 50); 45 | graphics.endFill(); 46 | 47 | // set a fill and line style again 48 | graphics.lineStyle(10, 0xFF0000, 0.8); 49 | graphics.beginFill(0xFF700B, 1); 50 | 51 | // draw a second shape 52 | graphics.moveTo(210, 300); 53 | graphics.lineTo(450, 320); 54 | graphics.lineTo(570, 350); 55 | graphics.quadraticCurveTo(600, 0, 480, 100); 56 | graphics.lineTo(330, 120); 57 | graphics.lineTo(410, 200); 58 | graphics.lineTo(210, 300); 59 | graphics.endFill(); 60 | 61 | // draw a rectangle 62 | graphics.lineStyle(2, 0x0000FF, 1); 63 | graphics.drawRect(50, 250, 100, 100); 64 | 65 | // draw a circle 66 | graphics.lineStyle(0); 67 | graphics.beginFill(0xFFFF0B, 0.5); 68 | graphics.drawCircle(470, 200, 100); 69 | graphics.endFill(); 70 | 71 | graphics.lineStyle(20, 0x33FF00); 72 | graphics.moveTo(30, 30); 73 | graphics.lineTo(600, 300); 74 | 75 | 76 | // let's create a moving shape 77 | var thing:Draw = new Draw(); 78 | addChild(thing); 79 | thing.x = 620 / 2; 80 | thing.y = 380 / 2; 81 | 82 | // avoid frame drops on mouse move. 83 | thing.touchable = false; 84 | graphics.touchable = false; 85 | 86 | var count:Number = 0; 87 | 88 | stage.addEventListener(TouchEvent.TOUCH, handleTouch); 89 | 90 | // run the render loop 91 | addEventListener(Event.ENTER_FRAME, animate); 92 | 93 | // Just click on the stage to draw random lines 94 | function handleTouch(e:TouchEvent) { 95 | var t:Touch = e.getTouch(stage, TouchPhase.BEGAN); 96 | if (t) onClick(); 97 | } 98 | 99 | function onClick() { 100 | graphics.lineStyle(Math.random() * 30, Math.random() * 0xFFFFFF, 1); 101 | graphics.moveTo(Math.random() * 620, Math.random() * 380); 102 | graphics.bezierCurveTo(Math.random() * 620, Math.random() * 380, 103 | Math.random() * 620, Math.random() * 380, 104 | Math.random() * 620, Math.random() * 380); 105 | } 106 | 107 | function animate() { 108 | count += 0.1; 109 | thing.clear(); 110 | thing.lineStyle(10, 0xff0000, 1); 111 | thing.beginFill(0xffFF00, 0.5); 112 | 113 | thing.moveTo(-120 + Math.sin(count) * 20, -100 + Math.cos(count) * 20); 114 | thing.lineTo(120 + Math.cos(count) * 20, -100 + Math.sin(count) * 20); 115 | thing.lineTo(120 + Math.sin(count) * 20, 100 + Math.cos(count) * 20); 116 | thing.lineTo(-120 + Math.cos(count) * 20, 100 + Math.sin(count) * 20); 117 | // thing.lineTo(-120 + Math.sin(count) * 20, -100 + Math.cos(count) * 20); 118 | thing.closePath(); 119 | 120 | thing.rotation = count * 0.1; 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/utils/BezierUtils.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 04/01/2019. 4 | // 5 | // ported from PixiJS 6 | // https://github.com/pixijs/pixi.js 7 | // 8 | // ================================================================================================= 9 | 10 | package com.roipeker.starling.draw.utils { 11 | public class BezierUtils { 12 | public function BezierUtils() { 13 | } 14 | 15 | 16 | /** 17 | * Utilities for bezier curves 18 | * @class 19 | * @private 20 | */ 21 | /** 22 | * Calculate length of bezier curve. 23 | * Analytical solution is impossible, since it involves an integral that does not integrate in general. 24 | * Therefore numerical solution is used. 25 | * 26 | * @private 27 | * @param {number} fromX - Starting point x 28 | * @param {number} fromY - Starting point y 29 | * @param {number} cpX - Control point x 30 | * @param {number} cpY - Control point y 31 | * @param {number} cpX2 - Second Control point x 32 | * @param {number} cpY2 - Second Control point y 33 | * @param {number} toX - Destination point x 34 | * @param {number} toY - Destination point y 35 | * @return {number} Length of bezier curve 36 | */ 37 | public static function curveLength(fromX:Number, fromY:Number, cpX:Number, cpY:Number, cpX2:Number, cpY2:Number, toX:Number, toY:Number):Number { 38 | const n:uint = 10; 39 | var result:Number = 0.0; 40 | var t:Number = 0.0; 41 | var t2:Number = 0.0; 42 | var t3:Number = 0.0; 43 | var nt:Number = 0.0; 44 | var nt2:Number = 0.0; 45 | var nt3:Number = 0.0; 46 | var x:Number = 0.0; 47 | var y:Number = 0.0; 48 | var dx:Number = 0.0; 49 | var dy:Number = 0.0; 50 | var prevX:Number = fromX; 51 | var prevY:Number = fromY; 52 | 53 | for (var i:int = 1; i <= n; ++i) { 54 | t = i / n; 55 | t2 = t * t; 56 | t3 = t2 * t; 57 | nt = (1.0 - t); 58 | nt2 = nt * nt; 59 | nt3 = nt2 * nt; 60 | 61 | x = (nt3 * fromX) + (3.0 * nt2 * t * cpX) + (3.0 * nt * t2 * cpX2) + (t3 * toX); 62 | y = (nt3 * fromY) + (3.0 * nt2 * t * cpY) + (3 * nt * t2 * cpY2) + (t3 * toY); 63 | dx = prevX - x; 64 | dy = prevY - y; 65 | prevX = x; 66 | prevY = y; 67 | result += Math.sqrt((dx * dx) + (dy * dy)); 68 | } 69 | return result; 70 | } 71 | 72 | /** 73 | * Calculate the points for a bezier curve and then draws it. 74 | * 75 | * @param {number} cpX - Control point x 76 | * @param {number} cpY - Control point y 77 | * @param {number} cpX2 - Second Control point x 78 | * @param {number} cpY2 - Second Control point y 79 | * @param {number} toX - Destination point x 80 | * @param {number} toY - Destination point y 81 | * @param {number[]} points - Path array to push points into 82 | */ 83 | public static function curveTo(cpX:Number, cpY:Number, cpX2:Number, cpY2:Number, toX:Number, toY:Number, points:Array):void { 84 | var fromX:Number = points[points.length - 2]; 85 | var fromY:Number = points[points.length - 1]; 86 | points.length -= 2; 87 | var n:uint = GraphicCurves.segmentsCount( 88 | BezierUtils.curveLength(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY) 89 | ); 90 | // trace("curve Segment count:", n, GraphicCurves.adaptive, GraphicCurves.maxLength ); 91 | var dt:Number = 0; 92 | var dt2:Number = 0; 93 | var dt3:Number = 0; 94 | var t2:Number = 0; 95 | var t3:Number = 0; 96 | 97 | points.push(fromX, fromY); 98 | 99 | for (var i:uint = 1, j:Number = 0; i <= n; ++i) { 100 | j = i / n; 101 | 102 | dt = (1 - j); 103 | dt2 = dt * dt; 104 | dt3 = dt2 * dt; 105 | 106 | t2 = j * j; 107 | t3 = t2 * j; 108 | points.push( 109 | (dt3 * fromX) + (3 * dt2 * j * cpX) + (3 * dt * t2 * cpX2) + (t3 * toX), 110 | (dt3 * fromY) + (3 * dt2 * j * cpY) + (3 * dt * t2 * cpY2) + (t3 * toY) 111 | ); 112 | } 113 | } 114 | 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/utils/ArcUtils.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 04/01/2019. 4 | // 5 | // ported from PixiJS 6 | // https://github.com/pixijs/pixi.js 7 | // 8 | // ================================================================================================= 9 | 10 | package com.roipeker.starling.draw.utils { 11 | public class ArcUtils { 12 | 13 | public function ArcUtils() { 14 | } 15 | 16 | /** 17 | * The arcTo() method creates an arc/curve between two tangents on the canvas. 18 | * 19 | * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! 20 | * 21 | * @param {number} x1 - The x-coordinate of the beginning of the arc 22 | * @param {number} y1 - The y-coordinate of the beginning of the arc 23 | * @param {number} x2 - The x-coordinate of the end of the arc 24 | * @param {number} y2 - The y-coordinate of the end of the arc 25 | * @param {number} radius - The radius of the arc 26 | * @return {object} If the arc length is valid, return center of circle, radius and other info otherwise `null`. 27 | */ 28 | public static function curveTo(x1:Number, y1:Number, x2:Number, y2:Number, radius:Number, points:Array):Object { 29 | const fromX:Number = points[points.length - 2]; 30 | const fromY:Number = points[points.length - 1]; 31 | 32 | const a1:Number = fromY - y1; 33 | const b1:Number = fromX - x1; 34 | const a2:Number = y2 - y1; 35 | const b2:Number = x2 - x1; 36 | const mm:Number = Math.abs((a1 * b2) - (b1 * a2)); 37 | 38 | if (mm < 1.0e-8 || radius === 0) { 39 | if (points[points.length - 2] !== x1 || points[points.length - 1] !== y1) { 40 | points.push(x1, y1); 41 | } 42 | return null; 43 | } 44 | 45 | const dd:Number = (a1 * a1) + (b1 * b1); 46 | const cc:Number = (a2 * a2) + (b2 * b2); 47 | const tt:Number = (a1 * a2) + (b1 * b2); 48 | const k1:Number = radius * Math.sqrt(dd) / mm; 49 | const k2:Number = radius * Math.sqrt(cc) / mm; 50 | const j1:Number = k1 * tt / dd; 51 | const j2:Number = k2 * tt / cc; 52 | const cx:Number = (k1 * b2) + (k2 * b1); 53 | const cy:Number = (k1 * a2) + (k2 * a1); 54 | const px:Number = b1 * (k2 + j1); 55 | const py:Number = a1 * (k2 + j1); 56 | const qx:Number = b2 * (k1 + j2); 57 | const qy:Number = a2 * (k1 + j2); 58 | const startAngle:Number = Math.atan2(py - cy, px - cx); 59 | const endAngle:Number = Math.atan2(qy - cy, qx - cx); 60 | 61 | return { 62 | cx: (cx + x1), 63 | cy: (cy + y1), 64 | radius: radius, 65 | startAngle: startAngle, 66 | endAngle: endAngle, 67 | anticlockwise: (b1 * a2 > b2 * a1) 68 | }; 69 | } 70 | 71 | /** 72 | * The arc method creates an arc/curve (used to create circles, or parts of circles). 73 | * 74 | * @param {number} startX - Start x location of arc 75 | * @param {number} startY - Start y location of arc 76 | * @param {number} cx - The x-coordinate of the center of the circle 77 | * @param {number} cy - The y-coordinate of the center of the circle 78 | * @param {number} radius - The radius of the circle 79 | * @param {number} startAngle - The starting angle, in radians (0 is at the 3 o'clock position 80 | * of the arc's circle) 81 | * @param {number} endAngle - The ending angle, in radians 82 | * @param {boolean} anticlockwise - Specifies whether the drawing should be 83 | * counter-clockwise or clockwise. False is default, and indicates clockwise, while true 84 | * indicates counter-clockwise. 85 | * @param {number} n - Number of segments 86 | * @param {number[]} points - Collection of points to add to 87 | */ 88 | public static function arc(startX:Number, startY:Number, cx:Number, cy:Number, radius:Number, 89 | startAngle:Number, endAngle:Number, anticlockwise:Boolean, points:Array):void { 90 | const sweep:Number = endAngle - startAngle; 91 | const n:uint = GraphicCurves.segmentsCount( 92 | Math.abs(sweep) * radius, 93 | Math.ceil(Math.abs(sweep) / (GraphUtils.PI2)) * 30 94 | ); 95 | const theta:Number = (sweep) / (n * 2); 96 | const theta2:Number = theta * 2; 97 | const cTheta:Number = Math.cos(theta); 98 | const sTheta:Number = Math.sin(theta); 99 | const segMinus:Number = n - 1; 100 | const remainder:Number = (segMinus % 1) / segMinus; 101 | for (var i:int = 0; i <= segMinus; ++i) { 102 | var real:Number = i + (remainder * i); 103 | var angle:Number = ((theta) + startAngle + (theta2 * real)); 104 | var c:Number = Math.cos(angle); 105 | var s:Number = -Math.sin(angle); 106 | points.push( 107 | (((cTheta * c) + (sTheta * s)) * radius) + cx, 108 | (((cTheta * -s) + (sTheta * c)) * radius) + cy 109 | ); 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/demos/DemoTextureFill.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-15. 4 | // 5 | // ================================================================================================= 6 | 7 | package demos { 8 | import com.roipeker.starling.draw.Draw; 9 | 10 | import flash.display.BitmapData; 11 | import flash.display.Shape; 12 | import flash.geom.Matrix; 13 | 14 | import starling.core.Starling; 15 | import starling.display.Image; 16 | import starling.display.Sprite; 17 | import starling.events.Event; 18 | import starling.textures.RenderTexture; 19 | import starling.textures.Texture; 20 | import starling.utils.MathUtil; 21 | 22 | public class DemoTextureFill extends Sprite { 23 | 24 | [Embed(source="../assets/bricks_pow2.jpg")] 25 | public static const BricksSmallTextureAsset:Class; 26 | 27 | [Embed(source="../assets/roi.png")] 28 | public static const RoiTextureAsset:Class; 29 | 30 | [Embed(source="../assets/line_pattern.png")] 31 | public static const LinePattTextureAsset:Class; 32 | 33 | public function DemoTextureFill() { 34 | addEventListener(Event.ADDED_TO_STAGE, init); 35 | } 36 | 37 | private function init(event:Event):void { 38 | 39 | stage.color = 0x333333; 40 | 41 | 42 | // test1(); 43 | test2(); 44 | } 45 | 46 | private function test2():void { 47 | var bd:BitmapData = new RoiTextureAsset().bitmapData; 48 | // NOTE: mipmapping is required for texture repeat! 49 | var tx:Texture = Texture.fromBitmapData(bd, false); 50 | 51 | const graphics:Draw = new Draw(); 52 | graphics.x = 50; 53 | graphics.y = 20; 54 | addChild(graphics); 55 | 56 | // -- Flash drawing API -- 57 | var shape:Shape = new Shape(); 58 | Starling.current.nativeOverlay.addChild(shape); 59 | shape.x = 400; 60 | shape.y = 50//graphics.y + tx.height + 10; 61 | 62 | var matrix:Matrix = new Matrix(); 63 | var displacement:Number = 0; 64 | 65 | // addEventListener(Event.ENTER_FRAME, update); 66 | 67 | // var img:Image = new Image(); 68 | // img.tileGrid 69 | // update(null); 70 | 71 | // hack texture size? 72 | var tw:int = MathUtil.getNextPowerOfTwo(tx.width); 73 | // var tw2:int = MathUtil.getNextPowerOfTwo(tx.width/2); 74 | var th:int = MathUtil.getNextPowerOfTwo(tx.height); 75 | var img:Image = new Image(tx); 76 | img.readjustSize(tw,th); 77 | trace(tx.width,tx.height, img.width, tw, th); 78 | // need to use mipmaps in RenderTexture, so make a new RenderTexture with those params in true. 79 | var rt:RenderTexture = new RenderTexture(tw,th, true, 2); 80 | rt.draw(img); 81 | tx = rt ; 82 | // addChild(img); 83 | 84 | update(null); 85 | 86 | function update(e:Event):void { 87 | 88 | displacement += .5;// 2.5; 89 | 90 | matrix.identity(); 91 | matrix.scale(.2, .2); 92 | // matrix.translate(displacement, 0); 93 | // matrix.rotate(Math.PI / 3); 94 | 95 | graphics.clear() 96 | .beginTextureFill(tx, 0xffffff, .8, matrix, true) 97 | .drawRect(0, 0, tx.width/2, tx.height/2 ) 98 | .endFill(); 99 | 100 | // flash drawing. 101 | shape.graphics.clear(); 102 | shape.graphics.beginBitmapFill(bd, matrix, true); 103 | shape.graphics.drawRect(0, 0, bd.width / 2, bd.height / 2); 104 | shape.graphics.endFill(); 105 | } 106 | } 107 | 108 | private function test1():void { 109 | var bd:BitmapData = new BricksSmallTextureAsset().bitmapData; 110 | 111 | // NOTE: mipmapping is required for texture repeat! 112 | var tx:Texture = Texture.fromBitmapData(bd, true); 113 | 114 | var line_tx:Texture = Texture.fromEmbeddedAsset(LinePattTextureAsset, true); 115 | 116 | const graphics:Draw = new Draw(); 117 | graphics.x = 50; 118 | graphics.y = 20; 119 | addChild(graphics); 120 | 121 | const graphicsBorder:Draw = new Draw(); 122 | graphicsBorder.x = 50; 123 | graphicsBorder.y = 20; 124 | addChild(graphicsBorder); 125 | 126 | // -- Flash drawing API -- 127 | var shape:Shape = new Shape(); 128 | Starling.current.nativeOverlay.addChild(shape); 129 | shape.x = 50; 130 | shape.y = graphics.y + tx.height + 10; 131 | 132 | var matrix:Matrix = new Matrix(); 133 | var displacement:Number = 0; 134 | 135 | addEventListener(Event.ENTER_FRAME, update); 136 | 137 | function update(e:Event):void { 138 | 139 | displacement += .5;// 2.5; 140 | 141 | matrix.identity(); 142 | matrix.translate(displacement, 0); 143 | 144 | 145 | graphicsBorder.clear() 146 | .lineTextureStyle(12, line_tx, true, 0x0000ff, 1, matrix) 147 | .drawCircle(120, 120, 120) 148 | .endFill(); 149 | 150 | 151 | matrix.rotate(Math.PI / 3); 152 | 153 | graphics.clear() 154 | .beginTextureFill(tx, 0xffffff, .8, matrix, true) 155 | .drawRoundRect(0, 0, tx.width, tx.height, 10) 156 | .endFill(); 157 | 158 | // flash drawing. 159 | shape.graphics.clear(); 160 | shape.graphics.beginBitmapFill(bd, matrix, true); 161 | shape.graphics.drawRoundRect(0, 0, tx.width, tx.height + 20, 10); 162 | shape.graphics.endFill(); 163 | } 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/demos/DemoPieChart.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-16. 4 | // 5 | // ================================================================================================= 6 | 7 | package demos { 8 | import com.roipeker.starling.draw.Draw; 9 | import com.roipeker.starling.draw.utils.GraphicCurves; 10 | 11 | import starling.animation.Transitions; 12 | 13 | import starling.core.Starling; 14 | 15 | import starling.display.Sprite; 16 | import starling.events.Event; 17 | import starling.events.Touch; 18 | import starling.events.TouchEvent; 19 | import starling.events.TouchPhase; 20 | import starling.text.BitmapFont; 21 | import starling.text.TextField; 22 | import starling.text.TextFormat; 23 | 24 | public class DemoPieChart extends Sprite { 25 | 26 | public function DemoPieChart() { 27 | addEventListener(Event.ADDED_TO_STAGE, init); 28 | } 29 | 30 | private function init(event:Event):void { 31 | 32 | // improves 33 | GraphicCurves.adaptive = true; 34 | 35 | 36 | var cmd:Draw = new Draw(); 37 | addChild(cmd); 38 | 39 | cmd.x = stage.stageWidth / 2; 40 | cmd.y = stage.stageHeight / 2; 41 | 42 | // colors... 43 | const colors:Array = [ 44 | 0x4D51A7, 0xF389D8, 0x4AC1B9, 0xAA74F3, 0xE3B490 45 | ]; 46 | 47 | const colorData:Array = []; 48 | const numbers:Array = [ 49 | 'one', 'two', 'three', 'four', 'five' 50 | ]; 51 | 52 | const format:TextFormat = new TextFormat(BitmapFont.MINI, -1, 0x0); 53 | colors.forEach(function (color:uint, i:int, arr:Array) { 54 | 55 | var p:Number = 1 / (colors.length); 56 | var text:TextField = new TextField(60, 10, numbers[i], format); 57 | text.batchable = true; 58 | text.alignPivot(); 59 | // text.filter = new DropShadowFilter(1, 0.78, 0x0, .6, 1, .6); 60 | addChild(text); 61 | 62 | colorData.push({ 63 | text: text, 64 | color: color, 65 | percent: p, 66 | fromPercent: 0, 67 | selectionPercent: 0, 68 | toPercent: 0 69 | }); 70 | }); 71 | 72 | var _selected:int = -1; 73 | stage.addEventListener(TouchEvent.TOUCH, handleStageTouch); 74 | 75 | function handleStageTouch(e:TouchEvent) { 76 | var t:Touch = e.getTouch(stage, TouchPhase.BEGAN); 77 | if (t) { 78 | selectNext(); 79 | } 80 | } 81 | 82 | function selectNext():void { 83 | if (_selected > -1) { 84 | // tweeen the object. 85 | toggleSelection(_selected, false); 86 | } 87 | ++_selected; 88 | if (_selected > colorData.length - 1) { 89 | _selected = 0; 90 | } 91 | toggleSelection(_selected, true); 92 | } 93 | 94 | function toggleSelection(idx:int, flag:Boolean):void { 95 | Starling.juggler.tween( 96 | colorData[idx], .5, { 97 | selectionPercent: flag ? 1 : 0, 98 | transition:Transitions.EASE_IN_OUT, 99 | onUpdate: renderColors 100 | } 101 | ); 102 | } 103 | 104 | const tweenPercent:Object = {p: 0}; 105 | const numColors:int = colorData.length; 106 | var startAngle:Number = 0; 107 | 108 | // grab one and tween. 109 | randomPercent(); 110 | 111 | function randomPercent():void { 112 | var p:Number = 1; 113 | for (var i:int = 0; i < numColors; i++) { 114 | var reducer:Number = .1 + Math.random() * .4; 115 | var p2:Number = p * reducer; 116 | if (i == numColors - 1) { 117 | p2 = p; 118 | } else { 119 | p -= p2; 120 | } 121 | colorData[i].fromPercent = colorData[i].percent; 122 | colorData[i].toPercent = p2; 123 | } 124 | 125 | tweenPercent.p = 0; 126 | Starling.juggler.tween(tweenPercent, .5, 127 | { 128 | delay: .4, 129 | p: 1, 130 | onUpdate: transitionPercents, 131 | onComplete: randomPercent, 132 | transition: Transitions.EASE_OUT 133 | }); 134 | } 135 | 136 | function transitionPercents():void { 137 | for (var i:int = 0; i < numColors; i++) { 138 | const vo:Object = colorData[i]; 139 | vo.percent = vo.fromPercent + (vo.toPercent - vo.fromPercent) * tweenPercent.p; 140 | 141 | } 142 | // cmd.rotation += .001; 143 | startAngle += .001; 144 | renderColors(); 145 | } 146 | 147 | function renderColors() { 148 | const maxAngle:Number = Math.PI * 2; 149 | const radius:Number = 100; 150 | 151 | cmd.clear(); 152 | 153 | for (var i:int = 0; i < colorData.length; i++) { 154 | const vo:Object = colorData[i]; 155 | var angPercent:Number = vo.percent * maxAngle; 156 | var endAngle:Number = startAngle + angPercent; 157 | 158 | var selection:Number = vo.selectionPercent; 159 | var thickness:Number = 30 + selection * 30 ; 160 | 161 | cmd.lineStyle(thickness, vo.color, 1, selection/2); 162 | cmd.arc(0, 0, radius, startAngle, endAngle); 163 | 164 | var tf:TextField = vo.text; 165 | var textAngle:Number = startAngle + angPercent / 2; 166 | tf.x = cmd.x + Math.cos(textAngle) * (radius + 30 / 2 ); 167 | tf.y = cmd.y + Math.sin(textAngle) * (radius + 30 / 2 ); 168 | tf.rotation = textAngle + Math.PI / 2; 169 | tf.text = numbers[i] + ' ' + Math.round(vo.percent * 100) + '%'; 170 | 171 | startAngle += angPercent; 172 | } 173 | } 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/builders/RoundRectBuilder.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-11. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.builders { 8 | import com.roipeker.starling.draw.GraphData; 9 | import com.roipeker.starling.draw.GraphGeom; 10 | import com.roipeker.starling.draw.math.shapes.RoundRect; 11 | import com.roipeker.starling.draw.utils.earcut.Earcut; 12 | 13 | import starling.utils.MathUtil; 14 | 15 | public class RoundRectBuilder extends AbsShapeBuilder { 16 | 17 | // number of segments per corner, is NOT a divider, but the actual segment number. 18 | public static var cornerNumSegments:uint = 20; 19 | 20 | private static function quadraticBezierCurve(fromX:Number, fromY:Number, cpX:Number, cpY:Number, toX:Number, toY:Number, out:Array = null) { 21 | if (!out) out = []; 22 | var n:int = cornerNumSegments; 23 | var xa:Number = 0; 24 | var ya:Number = 0; 25 | var xb:Number = 0; 26 | var yb:Number = 0; 27 | var x:Number = 0; 28 | var y:Number = 0; 29 | 30 | 31 | for (var i:int = 0, j:Number = 0; i <= n; ++i) { 32 | 33 | 34 | j = i / n; 35 | xa = getPt(fromX, cpX, j); 36 | ya = getPt(fromY, cpY, j); 37 | xb = getPt(cpX, toX, j); 38 | yb = getPt(cpY, toY, j); 39 | 40 | x = getPt(xa, xb, j); 41 | y = getPt(ya, yb, j); 42 | 43 | /* if ( constrain ){ 44 | if ( x < constrain.x || y < constrain.y 45 | || x > constrain.right || y > constrain.bottom ){ 46 | violated = true ; 47 | break ; 48 | trace('vuiolation') 49 | } 50 | }*/ 51 | // out.push(x, y); 52 | out[out.length] = x; 53 | out[out.length] = y; 54 | } 55 | return out; 56 | } 57 | 58 | [Inline] 59 | private static function getPt(n1:Number, n2:Number, perc:Number):Number { 60 | return n1 + ((n2 - n1) * perc); 61 | } 62 | 63 | public function RoundRectBuilder() { 64 | super(); 65 | } 66 | 67 | override public function build(shapeData:GraphData):void { 68 | const rrect:RoundRect = shapeData.shape as RoundRect; 69 | const points:Array = shapeData.points; 70 | 71 | const x:Number = rrect.x, 72 | y:Number = rrect.y, 73 | w:Number = rrect.w, 74 | h:Number = rrect.h; 75 | 76 | var tlr:Number = rrect.tlr, 77 | trr:Number = rrect.trr, 78 | blr:Number = rrect.blr, 79 | brr:Number = rrect.brr; 80 | 81 | // constrains the radius to the maximun size. 82 | const MIN_SIZE:Number = 0.0000000001; 83 | 84 | // TODO: fix MAX_SIZE and clamp(), when using complex round rect, corners can be larger than h/2 || w/2. 85 | const MAX_SIZE:Number = MathUtil.min(w, h) / 2; 86 | 87 | tlr = MathUtil.clamp(tlr, MIN_SIZE, MAX_SIZE); 88 | trr = MathUtil.clamp(trr, MIN_SIZE, MAX_SIZE); 89 | blr = MathUtil.clamp(blr, MIN_SIZE, MAX_SIZE); 90 | brr = MathUtil.clamp(brr, MIN_SIZE, MAX_SIZE); 91 | 92 | points.length = 0; 93 | 94 | var allSame:Boolean = tlr == trr && tlr == blr && tlr == brr; 95 | 96 | // special case for "pill" shapes, the quad curve doesn't make a proper half circle. 97 | // although Circle or Bezier curves should be an optional setting maybe. 98 | if (allSame && tlr == MAX_SIZE) { 99 | const len:int = cornerNumSegments; 100 | var hpi:Number = Math.PI / 2; 101 | const segment:Number = hpi / len; 102 | var cx:Number = x + tlr; 103 | var cy:Number = y + tlr; 104 | var offsetAngle:Number = Math.PI; 105 | 106 | // top left 107 | addSegments(points, len, segment, cx, cy, offsetAngle, tlr); 108 | 109 | cx = x + w - tlr; 110 | offsetAngle += hpi; 111 | // top right 112 | addSegments(points, len, segment, cx, cy, offsetAngle, tlr); 113 | 114 | offsetAngle += hpi; 115 | cy = y + h - tlr; 116 | // bottom right 117 | addSegments(points, len, segment, cx, cy, offsetAngle, tlr); 118 | 119 | offsetAngle += hpi; 120 | cx = x + tlr; 121 | // bottom left 122 | addSegments(points, len, segment, cx, cy, offsetAngle, tlr); 123 | 124 | points.push(points[0], points[1]); 125 | 126 | } else { 127 | points.push(x, y + tlr); 128 | quadraticBezierCurve(x, y + h - blr, x, y + h, x + blr, y + h, points); 129 | quadraticBezierCurve(x + w - brr, y + h, x + w, y + h, x + w, y + h - brr, points); 130 | quadraticBezierCurve(x + w, y + trr, x + w, y, x + w - trr, y, points); 131 | quadraticBezierCurve(x + tlr, y, x, y, x, y + tlr + MIN_SIZE, points); 132 | } 133 | } 134 | 135 | private function addSegments(points:Array, len:int, segment:Number, cx:Number, cy:Number, offsetAngle:Number, radius:Number):void { 136 | var angle:Number; 137 | for (var i:int = 0; i < len; i++) { 138 | angle = segment * i + offsetAngle; 139 | points.push( 140 | cx + (Math.cos(angle) * radius), 141 | cy + (Math.sin(angle) * radius) 142 | ); 143 | } 144 | } 145 | 146 | override public function triangulate(shapeData:GraphData, geometry:GraphGeom):void { 147 | const points:Array = shapeData.points; 148 | const indices:Array = geometry.indices; 149 | const verts:Array = geometry.points; 150 | const vertPos:int = verts.length >> 1; 151 | 152 | // reduce the triangulation. 153 | const triangles:Array = Earcut.earcut( points ); 154 | 155 | // const triangles:Array = points; 156 | var ilen:int; 157 | 158 | ilen = triangles.length; 159 | for (var i:int = 0; i < ilen; i += 3) { 160 | indices[indices.length] = vertPos + triangles[i]; 161 | indices[indices.length] = vertPos + triangles[i + 1]; 162 | indices[indices.length] = vertPos + triangles[i + 2]; 163 | } 164 | 165 | /*ilen = points.length; 166 | for (i = 0; i < ilen; i++) { 167 | // verts.push(points[i], points[++i]); 168 | verts[verts.length] = points[i]; 169 | verts[verts.length] = points[++i]; 170 | }*/ 171 | ilen = points.length; 172 | for (i = 0; i < ilen; i++) { 173 | verts[verts.length] = points[i]; 174 | } 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/utils/svg/SVGColor.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 06/01/2019. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.utils.svg { 8 | public class SVGColor { 9 | 10 | private static var names:Object = { 11 | aliceblue: "f0f8ff", 12 | antiquewhite: "faebd7", 13 | aqua: "0ff", 14 | aquamarine: "7fffd4", 15 | azure: "f0ffff", 16 | beige: "f5f5dc", 17 | bisque: "ffe4c4", 18 | black: "000", 19 | blanchedalmond: "ffebcd", 20 | blue: "00f", 21 | blueviolet: "8a2be2", 22 | brown: "a52a2a", 23 | burlywood: "deb887", 24 | burntsienna: "ea7e5d", 25 | cadetblue: "5f9ea0", 26 | chartreuse: "7fff00", 27 | chocolate: "d2691e", 28 | coral: "ff7f50", 29 | cornflowerblue: "6495ed", 30 | cornsilk: "fff8dc", 31 | crimson: "dc143c", 32 | cyan: "0ff", 33 | darkblue: "00008b", 34 | darkcyan: "008b8b", 35 | darkgoldenrod: "b8860b", 36 | darkgray: "a9a9a9", 37 | darkgreen: "006400", 38 | darkgrey: "a9a9a9", 39 | darkkhaki: "bdb76b", 40 | darkmagenta: "8b008b", 41 | darkolivegreen: "556b2f", 42 | darkorange: "ff8c00", 43 | darkorchid: "9932cc", 44 | darkred: "8b0000", 45 | darksalmon: "e9967a", 46 | darkseagreen: "8fbc8f", 47 | darkslateblue: "483d8b", 48 | darkslategray: "2f4f4f", 49 | darkslategrey: "2f4f4f", 50 | darkturquoise: "00ced1", 51 | darkviolet: "9400d3", 52 | deeppink: "ff1493", 53 | deepskyblue: "00bfff", 54 | dimgray: "696969", 55 | dimgrey: "696969", 56 | dodgerblue: "1e90ff", 57 | firebrick: "b22222", 58 | floralwhite: "fffaf0", 59 | forestgreen: "228b22", 60 | fuchsia: "f0f", 61 | gainsboro: "dcdcdc", 62 | ghostwhite: "f8f8ff", 63 | gold: "ffd700", 64 | goldenrod: "daa520", 65 | gray: "808080", 66 | green: "008000", 67 | greenyellow: "adff2f", 68 | grey: "808080", 69 | honeydew: "f0fff0", 70 | hotpink: "ff69b4", 71 | indianred: "cd5c5c", 72 | indigo: "4b0082", 73 | ivory: "fffff0", 74 | khaki: "f0e68c", 75 | lavender: "e6e6fa", 76 | lavenderblush: "fff0f5", 77 | lawngreen: "7cfc00", 78 | lemonchiffon: "fffacd", 79 | lightblue: "add8e6", 80 | lightcoral: "f08080", 81 | lightcyan: "e0ffff", 82 | lightgoldenrodyellow: "fafad2", 83 | lightgray: "d3d3d3", 84 | lightgreen: "90ee90", 85 | lightgrey: "d3d3d3", 86 | lightpink: "ffb6c1", 87 | lightsalmon: "ffa07a", 88 | lightseagreen: "20b2aa", 89 | lightskyblue: "87cefa", 90 | lightslategray: "789", 91 | lightslategrey: "789", 92 | lightsteelblue: "b0c4de", 93 | lightyellow: "ffffe0", 94 | lime: "0f0", 95 | limegreen: "32cd32", 96 | linen: "faf0e6", 97 | magenta: "f0f", 98 | maroon: "800000", 99 | mediumaquamarine: "66cdaa", 100 | mediumblue: "0000cd", 101 | mediumorchid: "ba55d3", 102 | mediumpurple: "9370db", 103 | mediumseagreen: "3cb371", 104 | mediumslateblue: "7b68ee", 105 | mediumspringgreen: "00fa9a", 106 | mediumturquoise: "48d1cc", 107 | mediumvioletred: "c71585", 108 | midnightblue: "191970", 109 | mintcream: "f5fffa", 110 | mistyrose: "ffe4e1", 111 | moccasin: "ffe4b5", 112 | navajowhite: "ffdead", 113 | navy: "000080", 114 | oldlace: "fdf5e6", 115 | olive: "808000", 116 | olivedrab: "6b8e23", 117 | orange: "ffa500", 118 | orangered: "ff4500", 119 | orchid: "da70d6", 120 | palegoldenrod: "eee8aa", 121 | palegreen: "98fb98", 122 | paleturquoise: "afeeee", 123 | palevioletred: "db7093", 124 | papayawhip: "ffefd5", 125 | peachpuff: "ffdab9", 126 | peru: "cd853f", 127 | pink: "ffc0cb", 128 | plum: "dda0dd", 129 | powderblue: "b0e0e6", 130 | purple: "800080", 131 | rebeccapurple: "663399", 132 | red: "f00", 133 | rosybrown: "bc8f8f", 134 | royalblue: "4169e1", 135 | saddlebrown: "8b4513", 136 | salmon: "fa8072", 137 | sandybrown: "f4a460", 138 | seagreen: "2e8b57", 139 | seashell: "fff5ee", 140 | sienna: "a0522d", 141 | silver: "c0c0c0", 142 | skyblue: "87ceeb", 143 | slateblue: "6a5acd", 144 | slategray: "708090", 145 | slategrey: "708090", 146 | snow: "fffafa", 147 | springgreen: "00ff7f", 148 | steelblue: "4682b4", 149 | tan: "d2b48c", 150 | teal: "008080", 151 | thistle: "d8bfd8", 152 | tomato: "ff6347", 153 | turquoise: "40e0d0", 154 | violet: "ee82ee", 155 | wheat: "f5deb3", 156 | white: "fff", 157 | whitesmoke: "f5f5f5", 158 | yellow: "ff0", 159 | yellowgreen: "9acd32" 160 | }; 161 | 162 | public static function resolveHex(strColor:String):int { 163 | if (!strColor) return -1; 164 | strColor = strColor.toLowerCase(); 165 | 166 | if (strColor.charAt(0) == '#') { 167 | strColor = strColor.substr(1); 168 | strColor = resolveSimplified(strColor); 169 | return parseInt(strColor, 16); 170 | } else { 171 | var c:int = hexByName(strColor); 172 | // strColor.replace(/([a-f0-9])/ig, '$1$1') 173 | if (c == -1) { 174 | trace('color not supported!'); 175 | return 0x00ff00; 176 | } 177 | return c; 178 | } 179 | } 180 | 181 | private static function resolveSimplified(strColor:String):String { 182 | if (strColor.length != 3) return strColor; 183 | return strColor.replace(/([a-f0-9])/ig, '$1$1'); 184 | } 185 | 186 | // Make it easy to access colors via `hexNames[hex]` 187 | private static var hexNames:Object = flip(names); 188 | 189 | public static function nameByHex(hex:uint):String { 190 | var key:String = hex.toString(16); 191 | if (hexNames[key]) return hexNames[key]; 192 | return ''; 193 | } 194 | 195 | public static function hexByName(name:String):int { 196 | var res:String = names[name]; 197 | if (res) { 198 | return parseInt('0x' + resolveSimplified(res)); 199 | } else { 200 | return -1; 201 | } 202 | } 203 | 204 | // `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }` 205 | private static function flip(o:Object):Object { 206 | var flipped:Object = {}; 207 | for (var i:String in o) { 208 | if (o.hasOwnProperty(i)) { 209 | flipped[o[i]] = i; 210 | } 211 | } 212 | return flipped; 213 | } 214 | 215 | public function SVGColor() { 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/utils/svg/DPathParser.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 05/01/2019. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.utils.svg { 8 | public class DPathParser { 9 | 10 | private var regExpMap:Object = { 11 | command: /\s*([achlmqstvzo])/gi, 12 | number: /\s*([+-]?\d*\.?\d+(?:e[+-]?\d+)?)/gi, 13 | comma: /\s*(?:(,)|\s)/g, 14 | flag: /\s*([01])/g 15 | }; 16 | 17 | private var matchers:Object = {}; 18 | private var commands:Array; 19 | private var index:int; 20 | private var data:String; 21 | private var relative:Boolean; 22 | private var curr_cmd:String; 23 | 24 | private static const FLAG:String = 'flag'; 25 | private static const COMMA:String = 'comma'; 26 | private static const COMMAND:String = 'command'; 27 | 28 | private static const NUMBER:String = 'number'; 29 | private static const COORD_PAIR:String = 'coord_pair'; 30 | private static const ARC_DEF:String = 'arc_definition'; 31 | 32 | 33 | public function DPathParser() { 34 | init(); 35 | } 36 | 37 | private function init():void { 38 | /* var res:* = arrayReduce([0, 1, 2, 3, 4], function (valorAnterior:Number, valorActual:Number, indice:int, vector:Array):Number { 39 | return valorAnterior + valorActual; 40 | }, 10); 41 | trace("REsult:", res); 42 | 43 | return;*/ 44 | 45 | matchers[NUMBER] = function (must:Boolean):Number { 46 | var a:* = get(NUMBER, must); 47 | return Number(a); 48 | }; 49 | 50 | matchers[COORD_PAIR] = function (must:Boolean):Object { 51 | var xs:String = get(NUMBER, must); 52 | if (!xs && !must) return null; 53 | get(COMMA, false); 54 | var ys:String = get(NUMBER, true); 55 | return {x: Number(xs), y: Number(ys)}; 56 | }; 57 | 58 | matchers[ARC_DEF] = function (must:Boolean):Object { 59 | var radii:Object = matchers[COORD_PAIR](must); 60 | if (!radii && !must) return null; 61 | get(COMMA); 62 | var rot:Number = Number(get(NUMBER, true)); 63 | get(COMMA, true); 64 | var large:Boolean = Boolean(Number(get(FLAG, true))); 65 | get(COMMA); 66 | var clockwise:Boolean = Boolean(Number(get(FLAG, true))); 67 | get(COMMA); 68 | var end:Object = matchers[COORD_PAIR](true); 69 | return { 70 | radii: radii, 71 | rotation: rot, large: large, clockwise: clockwise, end: end 72 | }; 73 | } 74 | } 75 | 76 | public function reset():void { 77 | curr_cmd = null; 78 | data = null; 79 | commands = null; 80 | relative = false; 81 | index = 0; 82 | } 83 | 84 | private function get(key:String, must:Boolean = false):String { 85 | var re:RegExp = regExpMap[key]; 86 | re.lastIndex = index; 87 | var res:Object = re.exec(data); 88 | if (!res || res.index != index) { 89 | if (!must) return null; 90 | throw new Error("Expected " + key + " at position " + index); 91 | } 92 | index = re.lastIndex; 93 | return res[1]; 94 | } 95 | 96 | 97 | public function parse(d:String):Array { 98 | reset(); 99 | data = d; 100 | commands = []; 101 | relative = false; 102 | 103 | var len:int = d.length; 104 | while (index < len) { 105 | curr_cmd = get(COMMAND, false); 106 | if (!curr_cmd) { 107 | ++index; 108 | continue; 109 | } 110 | var upcmd:String = curr_cmd.toUpperCase(); 111 | relative = curr_cmd != upcmd; 112 | var seq:Array; 113 | if (upcmd == "M") { 114 | seq = getSeq(COORD_PAIR).map(function (coords:Object, i:int, arr:Array) { 115 | if (i == 1) curr_cmd = relative ? 'l' : 'L'; 116 | return makeCommand({end: coords}); 117 | }); 118 | } else if (upcmd == "L" || upcmd == "T") { 119 | seq = getSeq(COORD_PAIR).map(function (coords:Object, i:int, arr:Array) { 120 | return makeCommand({end: coords}); 121 | }); 122 | } else if (upcmd == "C") { 123 | seq = getSeq(COORD_PAIR); 124 | if (seq.length % 3) { 125 | throw new Error("Expected coordinate pair triplet at position " + index); 126 | } 127 | seq = arrayReduce(seq, function (prev:Object, coords:Object, i:int, arr:Array):* { 128 | var rest:int = i % 3; 129 | if (!rest) { 130 | prev.push(makeCommand({cp1: coords})); 131 | } else { 132 | var last:Object = prev[prev.length - 1]; 133 | if (rest === 1) { 134 | last.cp2 = coords; 135 | } else { 136 | last.end = coords; 137 | } 138 | } 139 | return prev; 140 | }, []) as Array; 141 | 142 | } else if (upcmd == "Q" || upcmd == "S") { 143 | 144 | seq = getSeq(COORD_PAIR); 145 | if (seq.length & 1) { 146 | throw new Error("Expected coordinate pair couple at position " + index); 147 | } 148 | seq = arrayReduce(seq, function (prev:Object, coords:Object, i:int, arr:Array):Object { 149 | var odd:int = i & 1; 150 | if (!odd) { 151 | prev.push(makeCommand({cp: coords})); 152 | } else { 153 | var last:Object = prev[prev.length - 1]; 154 | last.end = coords; 155 | } 156 | return prev; 157 | }, []) as Array; 158 | 159 | } else if (upcmd == "H" || upcmd == "V") { 160 | // trace("adding h/v!") 161 | seq = getSeq(NUMBER).map(function (value:Object, i:int, arr:Array) { 162 | // trace("WTF ? ! >> ", value) 163 | return makeCommand({value: value}); 164 | }); 165 | } else if (upcmd == "A") { 166 | seq = getSeq(ARC_DEF).map(function (value:Object, i:int, arr:Array) { 167 | return makeCommand(value); 168 | }); 169 | } else if (upcmd == "Z") { 170 | seq = [{code: "Z"}] 171 | } else if (upcmd == "O") { 172 | seq = [{code: "O"}] 173 | } 174 | // trace("Rel", rel, curr_cmd); 175 | // ++index; 176 | commands.push.apply(commands, seq); 177 | } 178 | return commands; 179 | } 180 | 181 | private function makeCommand(obj:Object):Object { 182 | obj.code = curr_cmd; 183 | obj.relative = relative; 184 | return obj; 185 | } 186 | 187 | private function getSeq(id:String):Array { 188 | var s:Array = []; 189 | var matched:Object; 190 | var must:Boolean = true; 191 | var valid:Boolean = true; 192 | while (valid) { 193 | matched = matchers[id](must); 194 | if (matched) { 195 | s.push(matched); 196 | must = Boolean(get("comma", false)); 197 | valid = true; 198 | } else { 199 | valid = false; 200 | } 201 | } 202 | return s; 203 | } 204 | 205 | private static function arrayReduce(arr:Array, callback:Function, initValue:* = null):* { 206 | var len:int = arr.length; 207 | var prevValue:*; 208 | if (initValue) { 209 | var i:int = 0; 210 | prevValue = initValue; 211 | } else { 212 | i = 1; 213 | prevValue = arr[0]; 214 | } 215 | for (i; i < len; i++) { 216 | var nextValue:* = arr[i]; 217 | if (nextValue == null) continue; 218 | prevValue = callback(prevValue, nextValue, i, arr); 219 | } 220 | return prevValue; 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /app.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.roipeker.StarlingDraw 4 | Starling Draw Lib 5 | Starling Draw Lib 6 | 1.0.0 7 | roipeker 2019 8 | 9 | 10 | SWF file name is set automatically at compile time 11 | Draw Demo 12 | standard 13 | true 14 | portrait 15 | true 16 | true 17 | direct 18 | true 19 | high 20 | 21 | 22 | 23 | 55 | 56 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 114 | 115 | 116 | 117 | 118 | 119 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 150 | 151 | ]]> 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/GraphGeom.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-11. 4 | // 5 | // ported from PixiJS 6 | // https://github.com/pixijs/pixi.js 7 | // 8 | // ================================================================================================= 9 | 10 | package com.roipeker.starling.draw { 11 | import com.roipeker.starling.draw.builders.AbsShapeBuilder; 12 | import com.roipeker.starling.draw.math.shapes.AbsShape; 13 | import com.roipeker.starling.draw.math.shapes.ShapeType; 14 | import com.roipeker.starling.draw.styles.FillStyle; 15 | import com.roipeker.starling.draw.styles.LineStyle; 16 | import com.roipeker.starling.draw.utils.GraphUtils; 17 | 18 | import flash.geom.Matrix; 19 | 20 | import starling.textures.Texture; 21 | 22 | public class GraphGeom { 23 | 24 | private var _shapesData:Array; 25 | private var _dirty:int = 0; 26 | private var _cacheDirty:int = -1; 27 | 28 | public var points:Array; 29 | public var indices:Array; 30 | public var colors:Array; 31 | public var gradients:Array; 32 | 33 | // todo: remove this "globals" props when supporting batching or/and multiple Meshes rendering. 34 | public var texture:Texture; 35 | public var textureRepeat:Boolean; 36 | 37 | public var uvs:Array; 38 | 39 | public function GraphGeom() { 40 | points = []; 41 | indices = []; 42 | colors = []; 43 | gradients = []; 44 | uvs = []; 45 | _shapesData = []; 46 | } 47 | 48 | public function drawHole(shape:AbsShape, matrix:Matrix):GraphData { 49 | if (!_shapesData.length) return null; 50 | const shapeData:GraphData = new GraphData(shape, null, null, matrix); 51 | const lastShapeData:GraphData = _shapesData[int(_shapesData.length - 1)]; 52 | lastShapeData.holes.push(shapeData); 53 | _dirty++; 54 | return shapeData; 55 | } 56 | 57 | public function drawShape(shape:AbsShape, fillStyle:FillStyle, lineStyle:LineStyle, matrix:Matrix):GraphGeom { 58 | const shapeData:GraphData = new GraphData(shape, fillStyle, lineStyle, matrix); 59 | _shapesData[_shapesData.length] = shapeData; 60 | _dirty++; 61 | return this; 62 | } 63 | 64 | public function calculate():void { 65 | if (_dirty == _cacheDirty) return; 66 | 67 | _cacheDirty = _dirty; 68 | const shapes:Array = _shapesData; 69 | var currentColor:uint = 0xFFFFFF; 70 | var currentAlpha:Number = 1; 71 | 72 | texture = null; 73 | textureRepeat = false; 74 | 75 | this.uvs.length = 0; 76 | this.points.length = 0; 77 | this.indices.length = 0; 78 | this.colors.length = 0; 79 | this.gradients.length = 0; 80 | 81 | for (var i:int = 0, ilen:int = shapes.length; i < ilen; i++) { 82 | const shapeData:GraphData = _shapesData[i]; 83 | const command:AbsShapeBuilder = AbsShapeBuilder.get(shapeData.type); 84 | command.build(shapeData); 85 | if (shapeData.matrix) { 86 | transformPoints(shapeData.points, shapeData.matrix); 87 | } 88 | 89 | var fillStyle:FillStyle = shapeData.fillStyle; 90 | var lineStyle:FillStyle = shapeData.lineStyle; 91 | 92 | // trick to toggle fill/line. 93 | for (var j:int = 0; j < 2; j++) { 94 | var style:FillStyle = j == 0 ? fillStyle : lineStyle; 95 | if (!style.visible) continue; 96 | if (style.color != currentColor || style.alpha != currentAlpha) { 97 | currentColor = style.color; 98 | currentAlpha = style.alpha; 99 | } 100 | const start:int = points.length >> 1; 101 | if (j == 0) { 102 | if (shapeData.holes.length) { 103 | processHoles(shapeData.holes); 104 | AbsShapeBuilder.get(ShapeType.POLY).triangulate(shapeData, this); 105 | } else { 106 | command.triangulate(shapeData, this); 107 | } 108 | } else { 109 | GraphUtils.resolveLine(shapeData, this); 110 | } 111 | const size:int = (points.length >> 1) - start; 112 | 113 | if (style.gradient && style.gradient.visible) { 114 | gradients.push( 115 | start, 116 | start + size, 117 | style.gradient.color1, style.gradient.color2, 118 | style.gradient.angle, 119 | style.gradient.alpha1, style.gradient.alpha2 120 | ); 121 | } else { 122 | colors.push( 123 | start, 124 | size, 125 | currentColor, 126 | currentAlpha 127 | ); 128 | } 129 | texture = style.texture; 130 | textureRepeat = style.textureRepeat; 131 | if (style.texture) { 132 | addUvs(points, uvs, style.texture, start, size, style.matrix); 133 | } 134 | } 135 | } 136 | } 137 | 138 | private function addUvs(verts:Array, uvs:Array, texture:Texture, start:int, size:int, matrix:Matrix):void { 139 | var index:int = 0; 140 | while (index < size) { 141 | var x:Number = verts[(start + index) * 2]; 142 | var y:Number = verts[((start + index) * 2) + 1]; 143 | if (matrix) { 144 | const nx:Number = (matrix.a * x) + (matrix.c * y) + matrix.tx; 145 | y = (matrix.b * x) + (matrix.d * y) + matrix.ty; 146 | x = nx; 147 | } 148 | index++; 149 | uvs.push(x / texture.width, y / texture.height); 150 | } 151 | 152 | // TODO: adjust UVS for textureAtlases. 153 | // check if it works. 154 | trace(texture.frame); 155 | if ( texture.frame && ( texture.frame.width < texture.width || texture.frame.height < texture.height )) { 156 | adjustUvs(uvs, texture, uvs.length, size); 157 | } 158 | } 159 | 160 | private function adjustUvs(uvs:Array, texture:Texture, start:int, size:int) { 161 | const eps:Number = 1e-6; 162 | const finish:int = start + (size * 2); 163 | const frame:flash.geom.Rectangle = texture.frame; 164 | const scaleX:Number = frame.width / texture.width; 165 | const scaleY:Number = frame.height / texture.height; 166 | var offsetX:Number = frame.x / frame.width; 167 | var offsetY:Number = frame.y / frame.height; 168 | var minX:int = Math.floor(uvs[start] + eps); 169 | var minY:int = Math.floor(uvs[start + 1] + eps); 170 | for (var i:int = start + 2; i < finish; i += 2) { 171 | minX = Math.min(minX, Math.floor(uvs[i] + eps)); 172 | minY = Math.min(minY, Math.floor(uvs[i + 1] + eps)); 173 | } 174 | offsetX -= minX; 175 | offsetY -= minY; 176 | for (i = start; i < finish; i += 2) { 177 | uvs[i] = (uvs[i] + offsetX) * scaleX; 178 | uvs[i + 1] = (uvs[i + 1] + offsetY) * scaleY; 179 | } 180 | } 181 | 182 | private function transformPoints(points:Array, matrix:Matrix):void { 183 | /*var x:Number, y:Number, idx:int; 184 | for (var i:int = 0, len:uint = points.length >> 1; i < len; i++) { 185 | idx = i * 2; 186 | x = points[idx]; 187 | y = points[idx + 1]; 188 | points[idx] = (matrix.a * x) + (matrix.c * y) + matrix.tx; 189 | points[idx + 1] = (matrix.b * x) + (matrix.d * y) + matrix.ty; 190 | /!*points[idx] += matrix.tx; 191 | points[idx + 1] += matrix.ty;*!/ 192 | }*/ 193 | 194 | const len:int = points.length / 2; 195 | for (var i:int = 0; i < len; i++) { 196 | const x:Number = points[(i * 2)]; 197 | const y:Number = points[(i * 2) + 1]; 198 | 199 | points[(i * 2)] = (matrix.a * x) + (matrix.c * y) + matrix.tx; 200 | points[(i * 2) + 1] = (matrix.b * x) + (matrix.d * y) + matrix.ty; 201 | } 202 | } 203 | 204 | private function processHoles(holes:Array):void { 205 | for (var i:int = 0, ilen:int = holes.length; i < ilen; i++) { 206 | var command:AbsShapeBuilder = AbsShapeBuilder.get(holes[i].type); 207 | command.build(holes[i]); 208 | if (holes[i].matrix) { 209 | transformPoints(holes[i].points, holes[i].matrix); 210 | } 211 | } 212 | } 213 | 214 | public function clear():GraphGeom { 215 | if (_shapesData.length > 0) { 216 | // TODO: return shapesData objects to pool. 217 | _dirty++; 218 | _shapesData.length = 0; 219 | uvs.length = 0; 220 | texture = null; 221 | points.length = 0; 222 | indices.length = 0; 223 | colors.length = 0; 224 | gradients.length = 0; 225 | } 226 | return this; 227 | } 228 | 229 | public function clone():GraphGeom { 230 | var output:GraphGeom = new GraphGeom(); 231 | output._shapesData = _shapesData.concat(); 232 | output.points = points.concat(); 233 | output.indices = indices.concat(); 234 | output.colors = colors.concat(); 235 | output.gradients = gradients.concat(); 236 | return output; 237 | } 238 | 239 | public function dispose():void { 240 | clear(); 241 | _shapesData = null; 242 | points = null; 243 | uvs = null; 244 | indices = null; 245 | colors = null; 246 | gradients = null; 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/utils/GraphUtils.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 03/01/2019. 4 | // 5 | // ported from PixiJS 6 | // https://github.com/pixijs/pixi.js 7 | // 8 | // ================================================================================================= 9 | 10 | package com.roipeker.starling.draw.utils { 11 | import com.roipeker.starling.draw.GraphData; 12 | import com.roipeker.starling.draw.GraphGeom; 13 | import com.roipeker.starling.draw.styles.LineStyle; 14 | import com.roipeker.starling.draw.utils.lines.LineStrokePixi; 15 | import com.roipeker.starling.draw.utils.lines.ComplexLineStroke; 16 | 17 | import flash.display.CapsStyle; 18 | import flash.display.JointStyle; 19 | import flash.geom.Point; 20 | 21 | public class GraphUtils { 22 | 23 | public static const PI2:Number = Math.PI * 2; 24 | 25 | public function GraphUtils() { 26 | } 27 | 28 | // buffer points. 29 | private static const POINT1:Point = new Point(); 30 | private static const POINT2:Point = new Point(); 31 | 32 | public static function resolveLine(graphData:GraphData, graphGeom:GraphGeom):void { 33 | if (graphData.points.length === 0) { 34 | return; 35 | } 36 | // if using complex line... build with another approach (way more triangles though). 37 | if ( graphData.lineStyle.joint != JointStyle.MITER || graphData.lineStyle.caps != CapsStyle.SQUARE ) { 38 | 39 | // most versatile approach. 40 | buildLineComplex(graphData, graphGeom); 41 | 42 | // only accepts partially line joins. 43 | // LineStrokePixi.buildLine(graphData, graphGeom); 44 | } else { 45 | buildLine(graphData, graphGeom); 46 | } 47 | } 48 | 49 | public static function buildLineComplex(graphicsData:GraphData, graphicsGeometry:GraphGeom):void { 50 | // TODO: optimize... 51 | var points:Array = graphicsData.points; 52 | var style:LineStyle = graphicsData.lineStyle; 53 | var verts:Array = graphicsGeometry.points; 54 | 55 | trace(points.length,verts.length); 56 | ComplexLineStroke.minSegmentPixels = 2; 57 | 58 | var ppts:Array = ComplexLineStroke.getPntsFromNumbers(points); 59 | var res:Array = ComplexLineStroke.getStrokeGeometry(ppts, { 60 | width: style.width, 61 | cap: style.caps, 62 | join: style.joint, 63 | miterLimit: style.miterLimit 64 | }); 65 | 66 | var indexCount:int = res.length; 67 | var indexStart:int = verts.length / 2; 68 | // const indices:Array = graphicsGeometry.indices; 69 | 70 | // push all the geom. 71 | for (var i:int = 0, j:int = verts.length, ilen:int = res.length; i < ilen; i++) { 72 | verts[j++] = res[i].x; 73 | verts[j++] = res[i].y; 74 | } 75 | 76 | var v:Array = graphicsGeometry.indices; 77 | var si:int = v.length; 78 | for (i = 0; i < indexCount; i++) { 79 | v[si++] = indexStart + i; 80 | } 81 | } 82 | 83 | public static function buildLine(graphicsData:GraphData, geom:GraphGeom):void { 84 | // TODO OPTIMISE! 85 | var points:Array = graphicsData.points;//|| graphicsData.shape.points.slice(); 86 | if (points.length === 0) { 87 | return; 88 | } 89 | 90 | var style:LineStyle = graphicsData.lineStyle; 91 | // get first and last point.. figure out the middle! 92 | var firstPoint:Point = POINT1;//new Point(points[0], points[1]); 93 | var lastPoint:Point = POINT2;//new Point(points[points.length - 2], points[points.length - 1]); 94 | firstPoint.x = points[0]; 95 | firstPoint.y = points[1]; 96 | lastPoint.x = points[points.length - 2]; 97 | lastPoint.y = points[points.length - 1]; 98 | 99 | // if the first point is the last point - gonna have issues :) 100 | if (firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) { 101 | // need to clone as we are going to slightly modify the shape.. 102 | points = points.slice(); 103 | points.pop(); 104 | points.pop(); 105 | // lastPoint = new Point(points[points.length - 2], points[points.length - 1]); 106 | lastPoint.x = points[points.length - 2]; 107 | lastPoint.y = points[points.length - 1]; 108 | 109 | var midPointX:Number = lastPoint.x + ((firstPoint.x - lastPoint.x) * 0.5); 110 | var midPointY:Number = lastPoint.y + ((firstPoint.y - lastPoint.y) * 0.5); 111 | 112 | points.unshift(midPointX, midPointY); 113 | points.push(midPointX, midPointY); 114 | } 115 | 116 | // var verts:Array = graphicsGeometry.points; 117 | var indexCount:int = points.length; 118 | var length:int = indexCount / 2; 119 | var indexStart:int = geom.points.length / 2; 120 | // DRAW the Line 121 | var width:Number = style.width / 2; 122 | 123 | // sort color 124 | var p1x:Number = points[0]; 125 | var p1y:Number = points[1]; 126 | var p2x:Number = points[2]; 127 | var p2y:Number = points[3]; 128 | var p3x:Number = 0; 129 | var p3y:Number = 0; 130 | 131 | var perpx:Number = -(p1y - p2y); 132 | var perpy:Number = p1x - p2x; 133 | var perp2x:Number = 0; 134 | var perp2y:Number = 0; 135 | var perp3x:Number = 0; 136 | var perp3y:Number = 0; 137 | 138 | var dist:Number = Math.sqrt((perpx * perpx) + (perpy * perpy)); 139 | 140 | perpx /= dist; 141 | perpy /= dist; 142 | perpx *= width; 143 | perpy *= width; 144 | 145 | const ratio:Number = style.alignment; 146 | var r1:Number = (1 - ratio) * 2; 147 | var r2:Number = ratio * 2; 148 | 149 | // start 150 | geom.points.push( 151 | p1x - (perpx * r1), 152 | p1y - (perpy * r1)); 153 | 154 | geom.points.push( 155 | p1x + (perpx * r2), 156 | p1y + (perpy * r2)); 157 | 158 | var i:int; 159 | for (i = 1; i < length - 1; ++i) { 160 | p1x = points[(i - 1) * 2]; 161 | p1y = points[((i - 1) * 2) + 1]; 162 | 163 | p2x = points[i * 2]; 164 | p2y = points[(i * 2) + 1]; 165 | 166 | p3x = points[(i + 1) * 2]; 167 | p3y = points[((i + 1) * 2) + 1]; 168 | 169 | perpx = -(p1y - p2y); 170 | perpy = p1x - p2x; 171 | 172 | dist = Math.sqrt((perpx * perpx) + (perpy * perpy)); 173 | perpx /= dist; 174 | perpy /= dist; 175 | perpx *= width; 176 | perpy *= width; 177 | 178 | perp2x = -(p2y - p3y); 179 | perp2y = p2x - p3x; 180 | 181 | dist = Math.sqrt((perp2x * perp2x) + (perp2y * perp2y)); 182 | perp2x /= dist; 183 | perp2y /= dist; 184 | perp2x *= width; 185 | perp2y *= width; 186 | 187 | var a1:Number = (-perpy + p1y) - (-perpy + p2y); 188 | var b1:Number = (-perpx + p2x) - (-perpx + p1x); 189 | var c1:Number = ((-perpx + p1x) * (-perpy + p2y)) - ((-perpx + p2x) * (-perpy + p1y)); 190 | var a2:Number = (-perp2y + p3y) - (-perp2y + p2y); 191 | var b2:Number = (-perp2x + p2x) - (-perp2x + p3x); 192 | var c2:Number = ((-perp2x + p3x) * (-perp2y + p2y)) - ((-perp2x + p2x) * (-perp2y + p3y)); 193 | 194 | var denom:Number = (a1 * b2) - (a2 * b1); 195 | if (Math.abs(denom) < 0.1) { 196 | denom += 10.1; 197 | geom.points.push( 198 | p2x - (perpx * r1), 199 | p2y - (perpy * r1)); 200 | 201 | geom.points.push( 202 | p2x + (perpx * r2), 203 | p2y + (perpy * r2)); 204 | 205 | continue; 206 | } 207 | 208 | var px:Number = ((b1 * c2) - (b2 * c1)) / denom; 209 | var py:Number = ((a2 * c1) - (a1 * c2)) / denom; 210 | var pdist:Number = ((px - p2x) * (px - p2x)) + ((py - p2y) * (py - p2y)); 211 | 212 | if (pdist > (196 * width * width)) { 213 | perp3x = perpx - perp2x; 214 | perp3y = perpy - perp2y; 215 | 216 | dist = Math.sqrt((perp3x * perp3x) + (perp3y * perp3y)); 217 | perp3x /= dist; 218 | perp3y /= dist; 219 | perp3x *= width; 220 | perp3y *= width; 221 | 222 | geom.points.push(p2x - (perp3x * r1), p2y - (perp3y * r1)); 223 | 224 | geom.points.push(p2x + (perp3x * r2), p2y + (perp3y * r2)); 225 | 226 | geom.points.push(p2x - (perp3x * r2 * r1), p2y - (perp3y * r1)); 227 | 228 | indexCount++; 229 | } else { 230 | geom.points.push(p2x + ((px - p2x) * r1), p2y + ((py - p2y) * r1)); 231 | geom.points.push(p2x - ((px - p2x) * r2), p2y - ((py - p2y) * r2)); 232 | } 233 | } 234 | 235 | p1x = points[(length - 2) * 2]; 236 | p1y = points[((length - 2) * 2) + 1]; 237 | 238 | p2x = points[(length - 1) * 2]; 239 | p2y = points[((length - 1) * 2) + 1]; 240 | 241 | perpx = -(p1y - p2y); 242 | perpy = p1x - p2x; 243 | 244 | dist = Math.sqrt((perpx * perpx) + (perpy * perpy)); 245 | perpx /= dist; 246 | perpy /= dist; 247 | perpx *= width; 248 | perpy *= width; 249 | 250 | geom.points.push(p2x - (perpx * r1), p2y - (perpy * r1)); 251 | geom.points.push(p2x + (perpx * r2), p2y + (perpy * r2)); 252 | 253 | // var indices:Array = graphicsGeometry.indices; 254 | // indices.push(indexStart); 255 | indexCount -= 2; 256 | for (i = 0; i < indexCount; ++i) { 257 | // indices.push(indexStart, indexStart + 1, indexStart + 2); 258 | geom.indices.push(indexStart, indexStart + 1, indexStart + 2); 259 | indexStart++; 260 | } 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/utils/lines/LineStrokePixi.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 2019-05-16. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.utils.lines { 8 | import com.roipeker.starling.draw.GraphData; 9 | import com.roipeker.starling.draw.GraphGeom; 10 | import com.roipeker.starling.draw.styles.LineStyle; 11 | 12 | import flash.display.JointStyle; 13 | 14 | import flash.geom.Point; 15 | 16 | /** 17 | * 18 | * Unfinished implementation (round joins are not accurate) and missing cap styles. 19 | * 20 | * Ported from: 21 | * https://github.com/pixijs/pixi.js/pull/5325 22 | * 23 | * Might be available in Pixi v5.1 24 | * 25 | */ 26 | public class LineStrokePixi { 27 | public function LineStrokePixi() { 28 | } 29 | 30 | private static const TOLERANCE:Number = 0.0001; 31 | private static const PI_LBOUND:Number = Math.PI - TOLERANCE; 32 | private static const PI_UBOUND:Number = Math.PI + TOLERANCE; 33 | public static const PI_2:Number = 2 * Math.PI; 34 | 35 | public static function buildLine(graphicsData:GraphData, graphicsGeometry:GraphGeom ) 36 | { 37 | // TODO OPTIMISE! 38 | var points:Array = graphicsData.points ;//|| graphicsData.shape.points.slice(); 39 | if (points.length === 0) 40 | { 41 | return; 42 | } 43 | // if the line width is an odd number add 0.5 to align to a whole pixel 44 | // commenting this out fixes #711 and #1620 45 | // if (graphicsData.lineWidth%2) 46 | // { 47 | // for (i = 0; i < points.length; i++) 48 | // { 49 | // points[i] += 0.5; 50 | // } 51 | // } 52 | 53 | const style:LineStyle = graphicsData.lineStyle; 54 | 55 | // get first and last point.. figure out the middle! 56 | var firstPoint:Point = new Point(points[0], points[1]); 57 | var lastPoint:Point = new Point(points[points.length - 2], points[points.length - 1]); 58 | 59 | // if the first point is the last point - gonna have issues :) 60 | if (firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) 61 | { 62 | // need to clone as we are going to slightly modify the shape.. 63 | points = points.slice(); 64 | points.pop(); 65 | points.pop(); 66 | 67 | lastPoint = new Point(points[points.length - 2], points[points.length - 1]); 68 | 69 | const midPointX:Number = lastPoint.x + ((firstPoint.x - lastPoint.x) * 0.5); 70 | const midPointY:Number = lastPoint.y + ((firstPoint.y - lastPoint.y) * 0.5); 71 | 72 | points.unshift(midPointX, midPointY); 73 | points.push(midPointX, midPointY); 74 | } 75 | 76 | const verts:Array = graphicsGeometry.points; 77 | const length:int = points.length / 2; 78 | var indexCount:int = points.length; 79 | var indexStart:int = verts.length / 2; 80 | trace(verts); 81 | 82 | // DRAW the Line 83 | const width:Number = style.width / 2; 84 | 85 | // sort color 86 | var p1x:Number = points[0]; 87 | var p1y:Number = points[1]; 88 | var p2x:Number = points[2]; 89 | var p2y:Number = points[3]; 90 | var p3x:Number = 0; 91 | var p3y:Number = 0; 92 | 93 | var perpx:Number = -(p1y - p2y); 94 | var perpy:Number = p1x - p2x; 95 | var perp2x:Number = 0; 96 | var perp2y:Number = 0; 97 | 98 | var midx:Number = 0; 99 | var midy:Number = 0; 100 | 101 | var dist12:Number = 0; 102 | var dist23:Number = 0; 103 | var distMid:Number = 0; 104 | var minDist:Number = 0; 105 | 106 | var dist:Number = Math.sqrt((perpx * perpx) + (perpy * perpy)); 107 | 108 | perpx /= dist; 109 | perpy /= dist; 110 | perpx *= width; 111 | perpy *= width; 112 | 113 | const ratio:Number = style.alignment;// 0.5; 114 | const r1:Number = (1 - ratio) * 2; 115 | const r2:Number = ratio * 2; 116 | 117 | // start 118 | verts.push( 119 | p1x - (perpx * r1), 120 | p1y - (perpy * r1)); 121 | 122 | verts.push( 123 | p1x + (perpx * r2), 124 | p1y + (perpy * r2)); 125 | 126 | for (var i:int = 1; i < length - 1; ++i) 127 | { 128 | p1x = points[(i - 1) * 2]; 129 | p1y = points[((i - 1) * 2) + 1]; 130 | 131 | p2x = points[i * 2]; 132 | p2y = points[(i * 2) + 1]; 133 | 134 | p3x = points[(i + 1) * 2]; 135 | p3y = points[((i + 1) * 2) + 1]; 136 | 137 | perpx = -(p1y - p2y); 138 | perpy = p1x - p2x; 139 | 140 | perp2x = -(p2y - p3y); 141 | perp2y = p2x - p3x; 142 | 143 | dist = len(perpx, perpy); 144 | perpx /= dist; 145 | perpy /= dist; 146 | perpx *= width; 147 | perpy *= width; 148 | 149 | dist = len(perp2x, perp2y); 150 | perp2x /= dist; 151 | perp2y /= dist; 152 | perp2x *= width; 153 | perp2y *= width; 154 | 155 | const a1:Number = p1y - p2y; 156 | const b1:Number = p2x - p1x; 157 | const a2:Number = p3y - p2y; 158 | const b2:Number = p2x - p3x; 159 | 160 | const denom:Number = (a1 * b2) - (a2 * b1); 161 | const join:String = style.joint; 162 | 163 | var px:Number; 164 | var py:Number; 165 | var pdist:Number; 166 | 167 | // parallel or almost parallel ~0 or ~180 deg 168 | if (Math.abs(denom) < TOLERANCE) 169 | { 170 | // bevel, miter or round ~0deg 171 | if (join !== JointStyle.ROUND || Math.abs(angleDiff(perpx, perpy, perp2x, perp2y)) < TOLERANCE) 172 | { 173 | verts.push( 174 | p2x - (perpx * r1), 175 | p2y - (perpy * r1) 176 | ); 177 | 178 | verts.push( 179 | p2x + (perpx * r2), 180 | p2y + (perpy * r2) 181 | ); 182 | 183 | continue; 184 | } 185 | else // round ~180deg 186 | { 187 | px = p2x; 188 | py = p2y; 189 | pdist = 0; 190 | } 191 | } 192 | else 193 | { 194 | const c1:Number = ((-perpx + p1x) * (-perpy + p2y)) - ((-perpx + p2x) * (-perpy + p1y)); 195 | const c2:Number = ((-perp2x + p3x) * (-perp2y + p2y)) - ((-perp2x + p2x) * (-perp2y + p3y)); 196 | 197 | px = ((b1 * c2) - (b2 * c1)) / denom; 198 | py = ((a2 * c1) - (a1 * c2)) / denom; 199 | pdist = ((px - p2x) * (px - p2x)) + ((py - p2y) * (py - p2y)); 200 | } 201 | 202 | // funky comparison to have backwards compat which will fall back by default to miter 203 | // TODO: introduce miterLimit 204 | if (join !== JointStyle.BEVEL && join !== JointStyle.ROUND && pdist <= (196 * width * width)) 205 | { 206 | verts.push(p2x + ((px - p2x) * r1), p2y + ((py - p2y) * r1)); 207 | 208 | verts.push(p2x - ((px - p2x) * r2), p2y - ((py - p2y) * r2)); 209 | } 210 | else 211 | { 212 | const flip:Boolean = shouldFlip(p1x, p1y, p2x, p2y, p3x, p3y); 213 | 214 | dist12 = len(p2x - p1x, p2y - p1y); 215 | dist23 = len(p3x - p2x, p3y - p2y); 216 | minDist = Math.min(dist12, dist23); 217 | 218 | if (flip) 219 | { 220 | perpx = -perpx; 221 | perpy = -perpy; 222 | perp2x = -perp2x; 223 | perp2y = -perp2y; 224 | 225 | midx = (px - p2x) * r1; 226 | midy = (py - p2y) * r1; 227 | distMid = len(midx, midy); 228 | 229 | if (minDist < distMid) 230 | { 231 | midx /= distMid; 232 | midy /= distMid; 233 | midx *= minDist; 234 | midy *= minDist; 235 | } 236 | 237 | midx = p2x - midx; 238 | midy = p2y - midy; 239 | } 240 | else 241 | { 242 | midx = (px - p2x) * r2; 243 | midy = (py - p2y) * r2; 244 | distMid = len(midx, midy); 245 | 246 | if (minDist < distMid) 247 | { 248 | midx /= distMid; 249 | midy /= distMid; 250 | midx *= minDist; 251 | midy *= minDist; 252 | } 253 | 254 | midx += p2x; 255 | midy += p2y; 256 | } 257 | 258 | // if (join === LINE_JOIN.ROUND) 259 | if (join === JointStyle.ROUND) 260 | { 261 | const rad = flip ? r1 : r2; 262 | 263 | // eslint-disable-next-line max-params 264 | indexCount += buildRoundCap(midx, midy, 265 | p2x + (perpx * rad), p2y + (perpy * rad), 266 | p2x + (perp2x * rad), p2y + (perp2y * rad), 267 | p3x, p3y, 268 | verts, 269 | flip); 270 | } 271 | else if (join === JointStyle.BEVEL || pdist > (196 * width * width)) // TODO: introduce miterLimit 272 | { 273 | if (flip) 274 | { 275 | verts.push(p2x + (perpx * r2), p2y + (perpy * r2)); 276 | verts.push(midx, midy); 277 | 278 | verts.push(p2x + (perp2x * r2), p2y + (perp2y * r2)); 279 | verts.push(midx, midy); 280 | } 281 | else 282 | { 283 | verts.push(midx, midy); 284 | verts.push(p2x + (perpx * r1), p2y + (perpy * r1)); 285 | 286 | verts.push(midx, midy); 287 | verts.push(p2x + (perp2x * r1), p2y + (perp2y * r1)); 288 | } 289 | 290 | indexCount += 2; 291 | } 292 | } 293 | } 294 | 295 | p1x = points[(length - 2) * 2]; 296 | p1y = points[((length - 2) * 2) + 1]; 297 | 298 | p2x = points[(length - 1) * 2]; 299 | p2y = points[((length - 1) * 2) + 1]; 300 | 301 | perpx = -(p1y - p2y); 302 | perpy = p1x - p2x; 303 | 304 | dist = Math.sqrt((perpx * perpx) + (perpy * perpy)); 305 | perpx /= dist; 306 | perpy /= dist; 307 | perpx *= width; 308 | perpy *= width; 309 | 310 | verts.push(p2x - (perpx * r1), p2y - (perpy * r1)); 311 | 312 | verts.push(p2x + (perpx * r2), p2y + (perpy * r2)); 313 | 314 | const indices:Array = graphicsGeometry.indices; 315 | 316 | // indices.push(indexStart); 317 | 318 | for (i = 0; i < indexCount - 2; ++i) 319 | { 320 | indices.push(indexStart, indexStart + 1, indexStart + 2); 321 | 322 | indexStart++; 323 | } 324 | } 325 | 326 | private static function len(x:Number, y:Number):Number 327 | { 328 | return Math.sqrt((x * x) + (y * y)); 329 | } 330 | 331 | /** 332 | * Check turn direction. If counterclockwise, we must invert prep vectors, otherwise they point 'inwards' the angle, 333 | * resulting in funky looking lines. 334 | * 335 | * @ignore 336 | * @private 337 | * @param {number} p0x - x of 1st point 338 | * @param {number} p0y - y of 1st point 339 | * @param {number} p1x - x of 2nd point 340 | * @param {number} p1y - y of 2nd point 341 | * @param {number} p2x - x of 3rd point 342 | * @param {number} p2y - y of 3rd point 343 | * 344 | * @returns {boolean} true if perpendicular vectors should be flipped, otherwise false 345 | */ 346 | private static function shouldFlip(p0x:Number, p0y:Number, p1x:Number, p1y:Number, p2x:Number, p2y:Number):Boolean 347 | { 348 | return ((p1x - p0x) * (p2y - p0y)) - ((p2x - p0x) * (p1y - p0y)) < 0; 349 | } 350 | 351 | private static function angleDiff(p0x:Number, p0y:Number, p1x:Number, p1y:Number):Number 352 | { 353 | const angle1:Number = Math.atan2(p0x, p0y); 354 | const angle2:Number = Math.atan2(p1x, p1y); 355 | 356 | if (angle2 > angle1) 357 | { 358 | if ((angle2 - angle1) >= PI_LBOUND) 359 | { 360 | return angle2 - PI_2 - angle1; 361 | } 362 | } 363 | else if ((angle1 - angle2) >= PI_LBOUND) 364 | { 365 | return angle2 - (angle1 - PI_2); 366 | } 367 | 368 | return angle2 - angle1; 369 | } 370 | 371 | private static function buildRoundCap(cx:Number, cy:Number, p1x:Number, p1y:Number, p2x:Number, p2y:Number, nxtPx:Number, nxtPy:Number, verts:Array, flipped:Boolean):int 372 | { 373 | const cx2p0x:Number = p1x - cx; 374 | const cy2p0y:Number = p1y - cy; 375 | 376 | var angle0:Number = Math.atan2(cx2p0x, cy2p0y); 377 | var angle1:Number = Math.atan2(p2x - cx, p2y - cy); 378 | 379 | var startAngle:Number = angle0; 380 | 381 | if (angle1 > angle0) 382 | { 383 | if ((angle1 - angle0) >= PI_LBOUND) 384 | { 385 | angle1 = angle1 - PI_2; 386 | } 387 | } 388 | else if ((angle0 - angle1) >= PI_LBOUND) 389 | { 390 | angle0 = angle0 - PI_2; 391 | } 392 | 393 | var angleDiff:Number = angle1 - angle0; 394 | const absAngleDiff:Number = Math.abs(angleDiff); 395 | 396 | if (absAngleDiff >= PI_LBOUND && absAngleDiff <= PI_UBOUND) 397 | { 398 | const r1x:Number = cx - nxtPx; 399 | const r1y:Number = cy - nxtPy; 400 | 401 | if (r1x === 0) 402 | { 403 | if (r1y > 0) 404 | { 405 | angleDiff = -angleDiff; 406 | } 407 | } 408 | else if (r1x >= -TOLERANCE) 409 | { 410 | angleDiff = -angleDiff; 411 | } 412 | } 413 | 414 | const radius:Number = len(cx2p0x, cy2p0y); 415 | const segCount:int = ((15 * absAngleDiff * Math.sqrt(radius) / Math.PI) >> 0) + 1; 416 | const angleInc:Number = angleDiff / segCount; 417 | 418 | startAngle += angleInc; 419 | var i:int ; 420 | var angle:Number; 421 | if (flipped) 422 | { 423 | verts.push(p1x, p1y); 424 | verts.push(cx, cy); 425 | 426 | for (i = 1, angle = startAngle; i < segCount; i++, angle += angleInc) 427 | { 428 | verts.push(cx + ((Math.sin(angle) * radius)), 429 | cy + ((Math.cos(angle) * radius))); 430 | verts.push(cx, cy); 431 | } 432 | 433 | verts.push(p2x, p2y); 434 | verts.push(cx, cy); 435 | } 436 | else 437 | { 438 | verts.push(cx, cy); 439 | verts.push(p1x, p1y); 440 | 441 | for ( i = 1, angle = startAngle; i < segCount; i++, angle += angleInc) 442 | { 443 | verts.push(cx, cy); 444 | verts.push(cx + ((Math.sin(angle) * radius)), 445 | cy + ((Math.cos(angle) * radius))); 446 | } 447 | 448 | verts.push(cx, cy); 449 | verts.push(cx + ((Math.sin(angle1) * radius)), 450 | cy + ((Math.cos(angle1) * radius))); 451 | } 452 | 453 | return segCount * 2; 454 | } 455 | 456 | } 457 | } 458 | -------------------------------------------------------------------------------- /draw_lib/src/com/roipeker/starling/draw/utils/lines/ComplexLineStroke.as: -------------------------------------------------------------------------------- 1 | // ================================================================================================= 2 | // 3 | // Created by Rodrigo Lopez [roipeker™] on 10/01/2019. 4 | // 5 | // ================================================================================================= 6 | 7 | package com.roipeker.starling.draw.utils.lines { 8 | import flash.display.CapsStyle; 9 | import flash.display.JointStyle; 10 | 11 | /** 12 | * Ported from 13 | * https://hypertolosana.github.io/efficient-webgl-stroking 14 | * 15 | * Not optimized in any way. 16 | * 17 | */ 18 | 19 | public class ComplexLineStroke { 20 | public function ComplexLineStroke() { 21 | } 22 | 23 | public static var EPSILON:Number = 0.0001; 24 | public static var minSegmentPixels:Number = 6; 25 | 26 | public static const PI_M_EP:Number = Math.PI - EPSILON; 27 | public static const PI_P_EP:Number = Math.PI + EPSILON; 28 | public static const PI_2:Number = 2 * Math.PI; 29 | 30 | 31 | public static function getStrokeGeometry(points:Array, attrs:Object, vert:Array = null):Array { 32 | // trivial reject 33 | if (points.length < 2) { 34 | return null; 35 | } 36 | var cap:String = attrs.cap || CapsStyle.NONE; 37 | var join:String = attrs.join || JointStyle.BEVEL; 38 | var lineWidth:Number = (attrs.width || 1) / 2; 39 | var miterLimit:Number = attrs.miterLimit || 10; 40 | // var vertices:Array = []; 41 | if (!vert) vert = []; 42 | var middlePnts:Array = []; // middle points per each line segment. 43 | var closed:Boolean = false; 44 | if (points.length === 2) { 45 | join = JointStyle.BEVEL; 46 | createTriangles(points[0], Pnt.Middle(points[0], points[1]), points[1], vert, lineWidth, join, miterLimit); 47 | } else { 48 | // * Disable for simulation purposes, but uncomment for have the system working otherwise. 49 | if (points[0] === points[points.length - 1] || 50 | (points[0].x === points[points.length - 1].x && points[0].y === points[points.length - 1].y)) { 51 | var p0:Pnt = points.shift(); 52 | p0 = Pnt.Middle(p0, points[0]); 53 | points.unshift(p0); 54 | points.push(p0); 55 | closed = true; 56 | } 57 | const npoints:uint = points.length; 58 | var i:int, len:int; 59 | for (i = 0, len = npoints - 1; i < len; i++) { 60 | if (i == 0) { 61 | middlePnts.push(points[0]); 62 | } else if (i == npoints - 2) { 63 | middlePnts.push(points[uint(npoints - 1)]) 64 | } else { 65 | middlePnts.push(Pnt.Middle(points[i], points[uint(i + 1)])); 66 | } 67 | } 68 | for (i = 1, len = middlePnts.length; i < len; i++) { 69 | createTriangles(middlePnts[uint(i - 1)], points[i], middlePnts[i], vert, lineWidth, join, miterLimit); 70 | } 71 | } 72 | 73 | const numPoints:uint = points.length; 74 | 75 | if (!closed) { 76 | var p00:Pnt; 77 | var p01:Pnt; 78 | if (cap === CapsStyle.ROUND) { 79 | p00 = vert[0]; 80 | p01 = vert[1]; 81 | var p02:Pnt = points[1]; 82 | var p10:Pnt = vert[vert.length - 1]; 83 | var p11:Pnt = vert[vert.length - 3]; 84 | var p12:Pnt = points[numPoints - 2]; 85 | createRoundCap(points[0], p00, p01, p02, vert); 86 | createRoundCap(points[numPoints - 1], p10, p11, p12, vert); 87 | 88 | } else if (cap === CapsStyle.SQUARE) { 89 | p00 = vert[vert.length - 1]; 90 | p01 = vert[vert.length - 3]; 91 | createSquareCap( 92 | vert[0], 93 | vert[1], 94 | Pnt.Sub(points[0], points[1]).normalize().scalarMult(Pnt.Sub(points[0], vert[0]).length()), 95 | vert); 96 | createSquareCap( 97 | p00, 98 | p01, 99 | Pnt.Sub(points[numPoints - 1], points[numPoints - 2]).normalize().scalarMult(Pnt.Sub(p01, points[numPoints - 1]).length()), 100 | vert); 101 | } 102 | } 103 | return vert; 104 | } 105 | 106 | public static function createSquareCap(p0:Pnt, p1:Pnt, dir:Pnt, verts:Array) { 107 | const p1dir:Pnt = Pnt.Add(p1, dir); 108 | verts.push(p0, Pnt, p1dir, p1, p1dir, p0); 109 | } 110 | 111 | 112 | public static function createRoundCap(center:Pnt, _p0:Pnt, _p1:Pnt, nextPntInLine:Pnt, verts:Array):void { 113 | var radius:Number = Pnt.Sub(center, _p0).length(); 114 | var angle0:Number = Math.atan2((_p1.y - center.y), (_p1.x - center.x)); 115 | var angle1:Number = Math.atan2((_p0.y - center.y), (_p0.x - center.x)); 116 | var orgAngle0:Number = angle0; 117 | if (angle1 > angle0) { 118 | if (angle1 - angle0 >= PI_M_EP) { 119 | angle1 = angle1 - PI_2; 120 | } 121 | } else { 122 | if (angle0 - angle1 >= PI_M_EP) { 123 | angle0 = angle0 - PI_2; 124 | } 125 | } 126 | var angleDiff:Number = angle1 - angle0; 127 | const absDiff:Number = Math.abs(angleDiff); 128 | if (absDiff >= PI_M_EP && absDiff <= PI_P_EP) { 129 | var r1:Pnt = Pnt.Sub(center, nextPntInLine); 130 | if (r1.x === 0) { 131 | if (r1.y > 0) { 132 | angleDiff = -angleDiff; 133 | } 134 | } else if (r1.x >= -EPSILON) { 135 | angleDiff = -angleDiff; 136 | } 137 | } 138 | var nsegments:uint = (Math.abs(angleDiff * radius) / minSegmentPixels) >> 0; 139 | nsegments++; 140 | var angleInc:Number = angleDiff / nsegments; 141 | for (var i:int = 0; i < nsegments; i++) { 142 | 143 | verts.push(new Pnt(center.x, center.y)); 144 | verts.push(new Pnt( 145 | center.x + radius * Math.cos(orgAngle0 + angleInc * i), 146 | center.y + radius * Math.sin(orgAngle0 + angleInc * i) 147 | )); 148 | verts.push(new Pnt( 149 | center.x + radius * Math.cos(orgAngle0 + angleInc * (1 + i)), 150 | center.y + radius * Math.sin(orgAngle0 + angleInc * (1 + i)) 151 | )); 152 | } 153 | } 154 | 155 | [Inline] 156 | public static function signedArea(p0:Pnt, p1:Pnt, p2:Pnt):Number { 157 | return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y); 158 | } 159 | 160 | [Inline] 161 | public static function lineIntersection(p0:Pnt, p1:Pnt, p2:Pnt, p3:Pnt):Pnt { 162 | var a0:Number = p1.y - p0.y; 163 | var b0:Number = p0.x - p1.x; 164 | var a1:Number = p3.y - p2.y; 165 | var b1:Number = p2.x - p3.x; 166 | var det:Number = a0 * b1 - a1 * b0; 167 | if (det > -EPSILON && det < EPSILON) { 168 | return null; 169 | } else { 170 | var c0:Number = a0 * p0.x + b0 * p0.y; 171 | var c1:Number = a1 * p2.x + b1 * p2.y; 172 | var x:Number = (b1 * c0 - b0 * c1) / det; 173 | var y:Number = (a0 * c1 - a1 * c0) / det; 174 | return new Pnt(x, y); 175 | } 176 | } 177 | 178 | 179 | public static function createTriangles(p0:Pnt, p1:Pnt, p2:Pnt, verts:Array, width:Number, join:String, miterLimit:Number) { 180 | var t0:Pnt = Pnt.Sub(p1, p0); 181 | var t2:Pnt = Pnt.Sub(p2, p1); 182 | 183 | t0.perpendicular(); 184 | t2.perpendicular(); 185 | // triangle composed by the 3 points if clockwise or couterclockwise. 186 | // if counterclockwise, we must invert the line threshold points, otherwise the intersection point 187 | // could be erroneous and lead to odd results. 188 | if (signedArea(p0, p1, p2) > 0) { 189 | t0.invert(); 190 | t2.invert(); 191 | } 192 | t0.normalize(); 193 | t2.normalize(); 194 | t0.scalarMult(width); 195 | t2.scalarMult(width); 196 | 197 | 198 | const a_p0t0:Pnt = Pnt.Add(p0, t0); 199 | const a_p1t0:Pnt = Pnt.Add(p1, t0); 200 | const s_p0t0:Pnt = Pnt.Sub(p0, t0); 201 | const s_p1t2:Pnt = Pnt.Sub(p1, t2); 202 | const a_p2t2:Pnt = Pnt.Add(p2, t2); 203 | const a_p1t2:Pnt = Pnt.Add(p1, t2); 204 | const s_p2t2:Pnt = Pnt.Sub(p2, t2); 205 | 206 | var pintersect:Pnt = lineIntersection( 207 | a_p0t0, 208 | a_p1t0, 209 | a_p2t2, 210 | a_p1t2 211 | ); 212 | 213 | var anchor:Pnt = null; 214 | var anchorLength:Number = Number.MAX_VALUE; 215 | if (pintersect) { 216 | anchor = Pnt.Sub(pintersect, p1); 217 | anchorLength = anchor.length(); 218 | } 219 | var dd:int = (anchorLength / width) | 0; 220 | var p0p1:Pnt = Pnt.Sub(p0, p1); 221 | var p0p1Length:Number = p0p1.length(); 222 | var p1p2:Pnt = Pnt.Sub(p1, p2); 223 | var p1p2Length:Number = p1p2.length(); 224 | 225 | 226 | /** 227 | * the cross point exceeds any of the segments dimension. 228 | * do not use cross point as reference. 229 | */ 230 | if (anchorLength > p0p1Length || anchorLength > p1p2Length) { 231 | verts.push(a_p0t0, s_p0t0, a_p1t0, s_p0t0, a_p1t0, Pnt.Sub(p1, t0)); 232 | if (join === JointStyle.ROUND) { 233 | createRoundCap(p1, a_p1t0, a_p1t2, p2, verts); 234 | } else if (join === JointStyle.BEVEL || (join === JointStyle.MITER && dd >= miterLimit)) { 235 | verts.push(p1, a_p1t0, a_p1t2); 236 | } else if (join === JointStyle.MITER && dd < miterLimit && pintersect) { 237 | verts.push(a_p1t0, p1, pintersect, a_p1t2, p1, pintersect); 238 | } 239 | verts.push(a_p2t2, s_p1t2, a_p1t2, a_p2t2, s_p1t2, s_p2t2); 240 | } else { 241 | const s_p1an:Pnt = Pnt.Sub(p1, anchor); 242 | 243 | verts.push(a_p0t0, s_p0t0, s_p1an, a_p0t0, s_p1an, a_p1t0); 244 | if (join === JointStyle.ROUND) { 245 | var _p0:Pnt = a_p1t0; 246 | var _p1:Pnt = a_p1t2; 247 | var _p2:Pnt = s_p1an; 248 | var center:Pnt = p1; 249 | verts.push(_p0, center, _p2); 250 | createRoundCap(center, _p0, _p1, _p2, verts); 251 | verts.push(center, _p1, _p2); 252 | } else { 253 | if (join === JointStyle.BEVEL || join === JointStyle.MITER) { 254 | verts.push(a_p1t0, a_p1t2, s_p1an); 255 | } 256 | if (join === JointStyle.MITER && dd < miterLimit) { 257 | verts.push(pintersect, a_p1t0, a_p1t2); 258 | } 259 | } 260 | verts.push(a_p2t2, s_p1an, a_p1t2, a_p2t2, s_p1an, s_p2t2); 261 | } 262 | } 263 | 264 | 265 | /*public static function createTriangles2(p0:Pnt, p1:Pnt, p2:Pnt, verts:Array, indices:Array, width:Number, join:String, miterLimit:Number) { 266 | var t0:Pnt = Pnt.Sub(p1, p0); 267 | var t2:Pnt = Pnt.Sub(p2, p1); 268 | 269 | t0.perpendicular(); 270 | t2.perpendicular(); 271 | 272 | // triangle composed by the 3 points if clockwise or couterclockwise. 273 | // if counterclockwise, we must invert the line threshold points, otherwise the intersection point 274 | // could be erroneous and lead to odd results. 275 | if (signedArea(p0, p1, p2) > 0) { 276 | t0.invert(); 277 | t2.invert(); 278 | } 279 | t0.normalize(); 280 | t2.normalize(); 281 | t0.scalarMult(width); 282 | t2.scalarMult(width); 283 | var pintersect:Pnt = lineIntersection( 284 | Pnt.Add(t0, p0), 285 | Pnt.Add(t0, p1), 286 | Pnt.Add(t2, p2), 287 | Pnt.Add(t2, p1) 288 | ); 289 | 290 | var anchor:Pnt = null; 291 | var anchorLength:Number = Number.MAX_VALUE; 292 | if (pintersect) { 293 | anchor = Pnt.Sub(pintersect, p1); 294 | anchorLength = anchor.length(); 295 | } 296 | var dd:Number = (anchorLength / width) | 0; 297 | var p0p1:Pnt = Pnt.Sub(p0, p1); 298 | var p0p1Length:Number = p0p1.length(); 299 | var p1p2:Pnt = Pnt.Sub(p1, p2); 300 | var p1p2Length:Number = p1p2.length(); 301 | /!** 302 | * the cross point exceeds any of the segments dimension. 303 | * do not use cross point as reference. 304 | *!/ 305 | var idx:int = indices.length - 1; 306 | 307 | if (anchorLength > p0p1Length || anchorLength > p1p2Length) { 308 | 309 | verts.push(Pnt.Add(p0, t0)); 310 | indices.push(idx + 1); 311 | 312 | verts.push(Pnt.Sub(p0, t0)); 313 | indices.push(idx + 2); 314 | 315 | verts.push(Pnt.Add(p1, t0)); 316 | indices.push(idx + 3); 317 | 318 | verts.push(Pnt.Sub(p0, t0));//2 319 | indices.push(idx + 2); 320 | 321 | verts.push(Pnt.Add(p1, t0));//3 322 | indices.push(idx + 3); 323 | 324 | verts.push(Pnt.Sub(p1, t0)); 325 | indices.push(idx + 4); 326 | 327 | var lastIdx:int; 328 | if (join === JOIN_ROUND) { 329 | createRoundCap(p1, Pnt.Add(p1, t0), Pnt.Add(p1, t2), p2, verts, indices); 330 | } else if (join === JOIN_BEVEL || (join === JOIN_MITER && dd >= miterLimit)) { 331 | 332 | verts.push(p1); 333 | indices.push(idx + 5); 334 | 335 | verts.push(Pnt.Add(p1, t0));//3 336 | indices.push(idx + 3); 337 | 338 | verts.push(Pnt.Add(p1, t2)); 339 | indices.push(idx + 6); 340 | lastIdx = idx + 6; 341 | 342 | } else if (join === JOIN_MITER && dd < miterLimit && pintersect) { 343 | 344 | verts.push(Pnt.Add(p1, t0));//3 345 | indices.push(idx + 3); 346 | 347 | verts.push(p1); 348 | indices.push(idx + 5); 349 | 350 | verts.push(pintersect); 351 | indices.push(idx + 6); 352 | 353 | verts.push(Pnt.Add(p1, t2)); 354 | indices.push(idx + 7); 355 | lastIdx = idx + 7; 356 | 357 | verts.push(p1);//5 358 | indices.push(idx + 5); 359 | 360 | verts.push(pintersect);//6 361 | indices.push(idx + 6); 362 | } 363 | 364 | // offset. 365 | idx = lastIdx; 366 | 367 | verts.push(Pnt.Add(p2, t2)); 368 | indices.push(idx + 1); 369 | 370 | verts.push(Pnt.Sub(p1, t2)); 371 | indices.push(idx + 2); 372 | 373 | verts.push(Pnt.Add(p1, t2));//6 374 | indices.push(lastIdx); 375 | 376 | verts.push(Pnt.Add(p2, t2));//7 377 | indices.push(idx + 1); 378 | 379 | verts.push(Pnt.Sub(p1, t2));//8 380 | indices.push(idx + 2); 381 | 382 | verts.push(Pnt.Sub(p2, t2)); 383 | indices.push(idx + 3); 384 | 385 | } else { 386 | 387 | verts.push(Pnt.Add(p0, t0)); 388 | indices.push(idx + 1); 389 | 390 | verts.push(Pnt.Sub(p0, t0)); 391 | indices.push(idx + 2); 392 | 393 | verts.push(Pnt.Sub(p1, anchor)); 394 | indices.push(idx + 3); 395 | 396 | verts.push(Pnt.Add(p0, t0));//1 397 | indices.push(idx + 1); 398 | 399 | verts.push(Pnt.Sub(p1, anchor));//3 400 | indices.push(idx + 3); 401 | 402 | verts.push(Pnt.Add(p1, t0)); 403 | indices.push(idx + 4); 404 | 405 | if (join === JOIN_ROUND) { 406 | var _p0:Pnt = Pnt.Add(p1, t0); 407 | var _p1:Pnt = Pnt.Add(p1, t2); 408 | var _p2:Pnt = Pnt.Sub(p1, anchor); 409 | var center:Pnt = p1; 410 | 411 | verts.push(_p0); 412 | indices.push(idx + 5); 413 | 414 | verts.push(center); 415 | indices.push(idx + 6); 416 | 417 | verts.push(_p2); 418 | indices.push(idx + 7); 419 | 420 | createRoundCap(center, _p0, _p1, _p2, verts, indices); 421 | 422 | verts.push(center);//6 423 | indices.push(idx + 6); 424 | 425 | verts.push(_p1); 426 | indices.push(idx + 8); 427 | 428 | verts.push(_p2);//7 429 | indices.push(idx + 7); 430 | 431 | } else { 432 | if (join === JOIN_BEVEL || (join === JOIN_MITER && dd >= miterLimit)) { 433 | verts.push(Pnt.Add(p1, t0)); 434 | indices.push(idx + 5); 435 | 436 | verts.push(Pnt.Add(p1, t2)); 437 | indices.push(idx + 6); 438 | 439 | verts.push(Pnt.Sub(p1, anchor)); 440 | indices.push(idx + 7); 441 | } 442 | 443 | if (join === JOIN_MITER && dd < miterLimit) { 444 | var max:int = verts.length; 445 | verts.push(pintersect); 446 | verts.push(Pnt.Add(p1, t0)); 447 | verts.push(Pnt.Add(p1, t2)); 448 | indices.push(max, max + 1, max + 2); 449 | } 450 | } 451 | 452 | max = verts.length; 453 | verts.push(Pnt.Add(p2, t2)); 454 | indices.push(max); 455 | 456 | verts.push(Pnt.Sub(p1, anchor));//3 457 | indices.push(idx + 3); 458 | 459 | verts.push(Pnt.Add(p1, t2)); 460 | indices.push(max + 1); 461 | 462 | verts.push(Pnt.Add(p2, t2)); 463 | indices.push(max + 2); 464 | 465 | verts.push(Pnt.Sub(p1, anchor));//3 466 | indices.push(idx + 3); 467 | 468 | verts.push(Pnt.Sub(p2, t2)); 469 | indices.push(max + 3); 470 | 471 | } 472 | }*/ 473 | 474 | 475 | public static function getPntsFromNumbers(points:Object):Array { 476 | var res:Array = []; 477 | var len:int = points.length; 478 | for (var i:int = 0; i < len; i += 2) { 479 | res.push(new Pnt(points[i], points[i + 1])); 480 | } 481 | return res; 482 | } 483 | } 484 | } 485 | --------------------------------------------------------------------------------