Graphics.hx 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872
  1. package h2d;
  2. import h2d.impl.BatchDrawState;
  3. import hxd.Math;
  4. import hxd.impl.Allocator;
  5. private typedef GraphicsPoint = hxd.poly2tri.Point;
  6. @:dox(hide)
  7. class GPoint {
  8. public var x : Float;
  9. public var y : Float;
  10. public var r : Float;
  11. public var g : Float;
  12. public var b : Float;
  13. public var a : Float;
  14. public function new() {
  15. }
  16. public function load(x, y, r, g, b, a ){
  17. this.x = x;
  18. this.y = y;
  19. this.r = r;
  20. this.g = g;
  21. this.b = b;
  22. this.a = a;
  23. }
  24. }
  25. private class GraphicsContent extends h3d.prim.Primitive {
  26. var tmp : hxd.FloatBuffer;
  27. var index : hxd.IndexBuffer;
  28. var state : BatchDrawState;
  29. var buffers : Array<{ buf : hxd.FloatBuffer, vbuf : h3d.Buffer, idx : hxd.IndexBuffer, ibuf : h3d.Indexes, state : BatchDrawState }>;
  30. var bufferDirty : Bool;
  31. var indexDirty : Bool;
  32. #if track_alloc
  33. var allocPos : hxd.impl.AllocPos;
  34. #end
  35. public function new() {
  36. buffers = [];
  37. state = new BatchDrawState();
  38. #if track_alloc
  39. this.allocPos = new hxd.impl.AllocPos();
  40. #end
  41. }
  42. public inline function addIndex(i) {
  43. index.push(i);
  44. state.add(1);
  45. indexDirty = true;
  46. }
  47. public inline function add( x : Float, y : Float, u : Float, v : Float, r : Float, g : Float, b : Float, a : Float ) {
  48. tmp.push(x);
  49. tmp.push(y);
  50. tmp.push(u);
  51. tmp.push(v);
  52. tmp.push(r);
  53. tmp.push(g);
  54. tmp.push(b);
  55. tmp.push(a);
  56. bufferDirty = true;
  57. }
  58. public function setTile( tile : h2d.Tile ) {
  59. state.setTile(tile);
  60. }
  61. public function next() {
  62. var nvect = tmp.length >> 3;
  63. if( nvect < 1 << 15 )
  64. return false;
  65. buffers.push( { buf : tmp, idx : index, vbuf : null, ibuf : null, state: state } );
  66. tmp = new hxd.FloatBuffer();
  67. index = new hxd.IndexBuffer();
  68. var tex = state.currentTexture;
  69. state = new BatchDrawState();
  70. state.setTexture(tex);
  71. super.dispose();
  72. return true;
  73. }
  74. override function alloc( engine : h3d.Engine ) {
  75. if (index.length <= 0) return ;
  76. var alloc = Allocator.get();
  77. buffer = alloc.ofFloats(tmp, hxd.BufferFormat.H2D);
  78. #if track_alloc
  79. @:privateAccess buffer.allocPos = allocPos;
  80. #end
  81. indexes = alloc.ofIndexes(index);
  82. for( b in buffers ) {
  83. if( b.vbuf == null || b.vbuf.isDisposed() ) b.vbuf = alloc.ofFloats(b.buf, hxd.BufferFormat.H2D);
  84. if( b.ibuf == null || b.ibuf.isDisposed() ) b.ibuf = alloc.ofIndexes(b.idx);
  85. }
  86. bufferDirty = false;
  87. indexDirty = false;
  88. }
  89. public function doRender( ctx : h2d.RenderContext ) {
  90. if ( index.length == 0 ) return;
  91. flush();
  92. for ( b in buffers ) b.state.drawIndexed(ctx, b.vbuf, b.ibuf);
  93. state.drawIndexed(ctx, buffer, indexes);
  94. }
  95. public function flush() {
  96. if( buffer == null || buffer.isDisposed() ) {
  97. alloc(h3d.Engine.getCurrent());
  98. } else {
  99. var allocator = Allocator.get();
  100. if ( bufferDirty ) {
  101. allocator.disposeBuffer(buffer);
  102. buffer = allocator.ofFloats(tmp, hxd.BufferFormat.H2D);
  103. bufferDirty = false;
  104. }
  105. if ( indexDirty ) {
  106. allocator.disposeIndexBuffer(indexes);
  107. indexes = allocator.ofIndexes(index);
  108. indexDirty = false;
  109. }
  110. }
  111. }
  112. override function dispose() {
  113. for( b in buffers ) {
  114. if( b.vbuf != null ) Allocator.get().disposeBuffer(b.vbuf);
  115. if( b.ibuf != null ) Allocator.get().disposeIndexBuffer(b.ibuf);
  116. b.vbuf = null;
  117. b.ibuf = null;
  118. b.state.clear();
  119. }
  120. if( buffer != null ) {
  121. Allocator.get().disposeBuffer(buffer);
  122. buffer = null;
  123. }
  124. if( indexes != null ) {
  125. Allocator.get().disposeIndexBuffer(indexes);
  126. indexes = null;
  127. }
  128. state.clear();
  129. super.dispose();
  130. }
  131. public function clear() {
  132. dispose();
  133. tmp = new hxd.FloatBuffer();
  134. index = new hxd.IndexBuffer();
  135. buffers = [];
  136. }
  137. }
  138. /**
  139. A simple interface to draw arbitrary 2D geometry.
  140. Usage notes:
  141. * While Graphics allows for multiple unique textures, each texture swap causes a new drawcall,
  142. and due to that it's recommended to minimize the amount of used textures per Graphics instance,
  143. ideally limiting to only one texture.
  144. * Due to how Graphics operate, removing them from the active `h2d.Scene` will cause a loss of all data.
  145. **/
  146. class Graphics extends Drawable {
  147. var content : GraphicsContent;
  148. var tmpPoints : Array<GPoint>;
  149. var pindex : Int;
  150. var curR : Float;
  151. var curG : Float;
  152. var curB : Float;
  153. var curA : Float;
  154. var lineSize : Float;
  155. var lineR : Float;
  156. var lineG : Float;
  157. var lineB : Float;
  158. var lineA : Float;
  159. var doFill : Bool;
  160. var xMin : Float;
  161. var yMin : Float;
  162. var xMax : Float;
  163. var yMax : Float;
  164. var xMinSize : Float;
  165. var yMinSize : Float;
  166. var xMaxSize : Float;
  167. var yMaxSize : Float;
  168. var ma : Float = 1.;
  169. var mb : Float = 0.;
  170. var mc : Float = 0.;
  171. var md : Float = 1.;
  172. var mx : Float = 0.;
  173. var my : Float = 0.;
  174. /**
  175. The Tile used as source of Texture to render.
  176. **/
  177. public var tile : h2d.Tile;
  178. /**
  179. Adds bevel cut-off at line corners.
  180. The value is a percentile in range of 0...1, dictating at which point edges get beveled based on their angle.
  181. Value of 0 being not beveled and 1 being always beveled.
  182. **/
  183. public var bevel = 0.25; //0 = not beveled, 1 = always beveled
  184. /**
  185. Create a new Graphics instance.
  186. @param parent An optional parent `h2d.Object` instance to which Graphics adds itself if set.
  187. **/
  188. public function new(?parent) {
  189. super(parent);
  190. content = new GraphicsContent();
  191. tile = h2d.Tile.fromColor(0xFFFFFF);
  192. clear();
  193. }
  194. override function onRemove() {
  195. super.onRemove();
  196. clear();
  197. }
  198. /**
  199. Clears the Graphics contents.
  200. **/
  201. public function clear() {
  202. content.clear();
  203. tmpPoints = [];
  204. pindex = 0;
  205. lineSize = 0;
  206. xMin = Math.POSITIVE_INFINITY;
  207. yMin = Math.POSITIVE_INFINITY;
  208. yMax = Math.NEGATIVE_INFINITY;
  209. xMax = Math.NEGATIVE_INFINITY;
  210. xMinSize = Math.POSITIVE_INFINITY;
  211. yMinSize = Math.POSITIVE_INFINITY;
  212. yMaxSize = Math.NEGATIVE_INFINITY;
  213. xMaxSize = Math.NEGATIVE_INFINITY;
  214. }
  215. override function getBoundsRec( relativeTo, out, forSize ) {
  216. super.getBoundsRec(relativeTo, out, forSize);
  217. if( tile != null ) {
  218. if( forSize ) addBounds(relativeTo, out, xMinSize, yMinSize, xMaxSize - xMinSize, yMaxSize - yMinSize);
  219. else addBounds(relativeTo, out, xMin, yMin, xMax - xMin, yMax - yMin);
  220. }
  221. }
  222. function isConvex( points : Array<GPoint> ) {
  223. var first = true, sign = false;
  224. for( i in 0...points.length ) {
  225. var p1 = points[i];
  226. var p2 = points[(i + 1) % points.length];
  227. var p3 = points[(i + 2) % points.length];
  228. var s = (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x) > 0;
  229. if( first ) {
  230. first = false;
  231. sign = s;
  232. } else if( sign != s )
  233. return false;
  234. }
  235. return true;
  236. }
  237. function flushLine( start ) {
  238. var pts = tmpPoints;
  239. var last = pts.length - 1;
  240. var prev = pts[last];
  241. var p = pts[0];
  242. content.setTile(h2d.Tile.fromColor(0xFFFFFF));
  243. var closed = p.x == prev.x && p.y == prev.y;
  244. var count = pts.length;
  245. if( !closed ) {
  246. var prevLast = pts[last - 1];
  247. if( prevLast == null ) prevLast = p;
  248. var gp = new GPoint();
  249. gp.load(prev.x * 2 - prevLast.x, prev.y * 2 - prevLast.y, 0, 0, 0, 0);
  250. pts.push(gp);
  251. var pNext = pts[1];
  252. if( pNext == null ) pNext = p;
  253. var gp = new GPoint();
  254. gp.load(p.x * 2 - pNext.x, p.y * 2 - pNext.y, 0, 0, 0, 0);
  255. prev = gp;
  256. } else if( p != prev ) {
  257. count--;
  258. last--;
  259. prev = pts[last];
  260. }
  261. for( i in 0...count ) {
  262. var next = pts[(i + 1) % pts.length];
  263. var nx1 = prev.y - p.y;
  264. var ny1 = p.x - prev.x;
  265. var ns1 = Math.invSqrt(nx1 * nx1 + ny1 * ny1);
  266. var nx2 = p.y - next.y;
  267. var ny2 = next.x - p.x;
  268. var ns2 = Math.invSqrt(nx2 * nx2 + ny2 * ny2);
  269. var nx = nx1 * ns1 + nx2 * ns2;
  270. var ny = ny1 * ns1 + ny2 * ns2;
  271. var ns = Math.invSqrt(nx * nx + ny * ny);
  272. nx *= ns;
  273. ny *= ns;
  274. var size = nx * nx1 * ns1 + ny * ny1 * ns1; // N.N1
  275. // *HACK* we should instead properly detect limits when the angle is too small
  276. if(size < 0.1) size = 0.1;
  277. var d = lineSize * 0.5 / size;
  278. nx *= d;
  279. ny *= d;
  280. if(size > bevel) {
  281. content.add(p.x + nx, p.y + ny, 0, 0, p.r, p.g, p.b, p.a);
  282. content.add(p.x - nx, p.y - ny, 0, 0, p.r, p.g, p.b, p.a);
  283. var pnext = i == last ? start : pindex + 2;
  284. if( i < count - 1 || closed ) {
  285. content.addIndex(pindex);
  286. content.addIndex(pindex + 1);
  287. content.addIndex(pnext);
  288. content.addIndex(pindex + 1);
  289. content.addIndex(pnext);
  290. content.addIndex(pnext + 1);
  291. }
  292. pindex += 2;
  293. }
  294. else {
  295. //bevel
  296. var n0x = next.x - p.x;
  297. var n0y = next.y - p.y;
  298. var sign = n0x * nx + n0y * ny;
  299. var nnx = -ny;
  300. var nny = nx;
  301. var size = nnx * nx1 * ns1 + nny * ny1 * ns1;
  302. var d = lineSize * 0.5 / size;
  303. nnx *= d;
  304. nny *= d;
  305. var pnext = i == last ? start : pindex + 3;
  306. if(sign > 0) {
  307. content.add(p.x + nx, p.y + ny, 0, 0, p.r, p.g, p.b, p.a);
  308. content.add(p.x - nnx, p.y - nny, 0, 0, p.r, p.g, p.b, p.a);
  309. content.add(p.x + nnx, p.y + nny, 0, 0, p.r, p.g, p.b, p.a);
  310. content.addIndex(pindex);
  311. content.addIndex(pnext);
  312. content.addIndex(pindex + 2);
  313. content.addIndex(pindex + 2);
  314. content.addIndex(pnext);
  315. content.addIndex(pnext + 1);
  316. }
  317. else {
  318. content.add(p.x + nnx, p.y + nny, 0, 0, p.r, p.g, p.b, p.a);
  319. content.add(p.x - nx, p.y - ny, 0, 0, p.r, p.g, p.b, p.a);
  320. content.add(p.x - nnx, p.y - nny, 0, 0, p.r, p.g, p.b, p.a);
  321. content.addIndex(pindex + 1);
  322. content.addIndex(pnext);
  323. content.addIndex(pindex + 2);
  324. content.addIndex(pindex + 1);
  325. content.addIndex(pnext);
  326. content.addIndex(pnext + 1);
  327. }
  328. content.addIndex(pindex);
  329. content.addIndex(pindex + 1);
  330. content.addIndex(pindex + 2);
  331. pindex += 3;
  332. }
  333. prev = p;
  334. p = next;
  335. }
  336. content.setTile(tile);
  337. }
  338. static var EARCUT = null;
  339. function flushFill( i0 ) {
  340. if( tmpPoints.length < 3 )
  341. return;
  342. var pts = tmpPoints;
  343. var p0 = pts[0];
  344. var p1 = pts[pts.length - 1];
  345. var last = null;
  346. // closed poly
  347. if( hxd.Math.abs(p0.x - p1.x) < 1e-9 && hxd.Math.abs(p0.y - p1.y) < 1e-9 )
  348. last = pts.pop();
  349. if( isConvex(pts) ) {
  350. for( i in 1...pts.length - 1 ) {
  351. content.addIndex(i0);
  352. content.addIndex(i0 + i);
  353. content.addIndex(i0 + i + 1);
  354. }
  355. } else {
  356. var ear = EARCUT;
  357. if( ear == null )
  358. EARCUT = ear = new hxd.earcut.Earcut();
  359. for( i in ear.triangulate(pts) )
  360. content.addIndex(i + i0);
  361. }
  362. if( last != null )
  363. pts.push(last);
  364. }
  365. function flush() {
  366. if( tmpPoints.length == 0 )
  367. return;
  368. if( doFill ) {
  369. flushFill(pindex);
  370. pindex += tmpPoints.length;
  371. if( content.next() )
  372. pindex = 0;
  373. }
  374. if( lineSize > 0 ) {
  375. flushLine(pindex);
  376. if( content.next() )
  377. pindex = 0;
  378. }
  379. tmpPoints = [];
  380. }
  381. /**
  382. Begins a solid color fill.
  383. Beginning new fill will finish previous fill operation without need to call `Graphics.endFill`.
  384. @param color An RGB color with which to fill the drawn shapes.
  385. @param alpha A transparency of the fill color.
  386. **/
  387. public function beginFill( color : Int = 0, alpha = 1. ) {
  388. flush();
  389. tile = h2d.Tile.fromColor(0xFFFFFF);
  390. content.setTile(tile);
  391. setColor(color,alpha);
  392. doFill = true;
  393. }
  394. /**
  395. Position a virtual tile at the given position and scale. Every draw will display a part of this tile relative
  396. to these coordinates.
  397. Note that in by default, Tile is not wrapped, and in order to render tiling texture, `Drawable.tileWrap` have to be set.
  398. Additionally, both `Tile.dx` and `Tile.dy` are ignored (use `dx`/`dy` arguments instead)
  399. as well as tile defined size of the tile through `Tile.width` and `Tile.height` (use `scaleX`/`scaleY` relative to texture size).
  400. Beginning new fill will finish previous fill operation without need to call `Graphics.endFill`.
  401. @param dx An X offset of the Tile relative to Graphics.
  402. @param dy An Y offset of the Tile relative to Graphics.
  403. @param scaleX A horizontal scale factor applied to the Tile texture.
  404. @param scaleY A vertical scale factor applied to the Tile texture.
  405. @param tile The tile to fill with. If null, uses previously used Tile with `beginTileFill` or throws an error.
  406. Previous tile is remembered across `Graphics.clear` calls.
  407. **/
  408. public function beginTileFill( ?dx : Float, ?dy : Float, ?scaleX : Float, ?scaleY : Float, ?tile : h2d.Tile ) {
  409. if ( tile == null )
  410. tile = this.tile;
  411. if ( tile == null )
  412. throw "Tile not specified";
  413. flush();
  414. this.tile = tile;
  415. content.setTile(tile);
  416. setColor(0xFFFFFF);
  417. doFill = true;
  418. if( dx == null ) dx = 0;
  419. if( dy == null ) dy = 0;
  420. if( scaleX == null ) scaleX = 1;
  421. if( scaleY == null ) scaleY = 1;
  422. dx -= tile.x;
  423. dy -= tile.y;
  424. var tex = tile.getTexture();
  425. var pixWidth = 1 / tex.width;
  426. var pixHeight = 1 / tex.height;
  427. ma = pixWidth / scaleX;
  428. mb = 0;
  429. mc = 0;
  430. md = pixHeight / scaleY;
  431. mx = -dx * ma;
  432. my = -dy * md;
  433. }
  434. /**
  435. Draws a Tile at given position.
  436. See `Graphics.beginTileFill` for limitations.
  437. This methods ends current fill operation.
  438. @param x The X position of the tile.
  439. @param y The Y position of the tile.
  440. @param tile The tile to draw.
  441. **/
  442. public function drawTile( x : Float, y : Float, tile : h2d.Tile ) {
  443. beginTileFill(x, y, tile);
  444. drawRect(x, y, tile.width, tile.height);
  445. endFill();
  446. }
  447. /**
  448. Sets an outline style. Changing the line style ends the currently drawn line.
  449. @param size Width of the outline. Setting size to 0 will remove the outline.
  450. @param color An outline RGB color.
  451. @param alpha An outline transparency.
  452. **/
  453. public function lineStyle( size : Float = 0, color = 0, alpha = 1. ) {
  454. flush();
  455. this.lineSize = size;
  456. lineA = alpha;
  457. lineR = ((color >> 16) & 0xFF) / 255.;
  458. lineG = ((color >> 8) & 0xFF) / 255.;
  459. lineB = (color & 0xFF) / 255.;
  460. }
  461. /**
  462. Ends the current line and starts new one at given position.
  463. **/
  464. public inline function moveTo(x,y) {
  465. flush();
  466. lineTo(x, y);
  467. }
  468. /**
  469. Ends the current fill operation.
  470. **/
  471. public function endFill() {
  472. flush();
  473. doFill = false;
  474. }
  475. /**
  476. Changes current fill color.
  477. Does not interrupt current fill operation and can be utilized to customize color per vertex.
  478. During tile fill operation, color serves as a tile color multiplier.
  479. @param color The new fill color.
  480. @param alpha The new fill transparency.
  481. **/
  482. public inline function setColor( color : Int, alpha : Float = 1. ) {
  483. curA = alpha;
  484. curR = ((color >> 16) & 0xFF) / 255.;
  485. curG = ((color >> 8) & 0xFF) / 255.;
  486. curB = (color & 0xFF) / 255.;
  487. }
  488. /**
  489. Draws a rectangle with given parameters.
  490. @param x The rectangle top-left corner X position.
  491. @param y The rectangle top-left corner Y position.
  492. @param w The rectangle width.
  493. @param h The rectangle height.
  494. **/
  495. public function drawRect( x : Float, y : Float, w : Float, h : Float ) {
  496. flush();
  497. lineTo(x, y);
  498. lineTo(x + w, y);
  499. lineTo(x + w, y + h);
  500. lineTo(x, y + h);
  501. lineTo(x, y);
  502. var e = 0.01; // see #776
  503. tmpPoints[0].x += e;
  504. tmpPoints[0].y += e;
  505. tmpPoints[1].y += e;
  506. tmpPoints[3].x += e;
  507. tmpPoints[4].x += e;
  508. tmpPoints[4].y += e;
  509. flush();
  510. }
  511. /**
  512. Draws a rounded rectangle with given parameters.
  513. @param x The rectangle top-left corner X position.
  514. @param y The rectangle top-left corner Y position.
  515. @param w The rectangle width.
  516. @param h The rectangle height.
  517. @param radius Radius of the rectangle corners.
  518. @param nsegments Amount of segments used for corners. When `0` segment count calculated automatically.
  519. **/
  520. public function drawRoundedRect( x : Float, y : Float, w : Float, h : Float, radius : Float, nsegments = 0 ) {
  521. if (radius <= 0) {
  522. return drawRect(x, y, w, h);
  523. }
  524. x += radius;
  525. y += radius;
  526. w -= radius * 2;
  527. h -= radius * 2;
  528. flush();
  529. if( nsegments == 0 )
  530. nsegments = Math.ceil(Math.abs(radius * hxd.Math.degToRad(90) / 4));
  531. if( nsegments < 3 ) nsegments = 3;
  532. var angle = hxd.Math.degToRad(90) / (nsegments - 1);
  533. inline function corner(x, y, angleStart) {
  534. for ( i in 0...nsegments) {
  535. var a = i * angle + hxd.Math.degToRad(angleStart);
  536. lineTo(x + Math.cos(a) * radius, y + Math.sin(a) * radius);
  537. }
  538. }
  539. lineTo(x, y - radius);
  540. lineTo(x + w, y - radius);
  541. corner(x + w, y, 270);
  542. lineTo(x + w + radius, y + h);
  543. corner(x + w, y + h, 0);
  544. lineTo(x, y + h + radius);
  545. corner(x, y + h, 90);
  546. lineTo(x - radius, y);
  547. corner(x, y, 180);
  548. flush();
  549. }
  550. /**
  551. Draws a circle centered at given position.
  552. @param cx X center position of the circle.
  553. @param cy Y center position of the circle.
  554. @param radius Radius of the circle.
  555. @param nsegments Amount of segments used to draw the circle. When `0`, amount of segments calculated automatically.
  556. **/
  557. public function drawCircle( cx : Float, cy : Float, radius : Float, nsegments = 0 ) {
  558. flush();
  559. if( nsegments == 0 )
  560. nsegments = Math.ceil(Math.abs(radius * 3.14 * 2 / 4));
  561. if( nsegments < 3 ) nsegments = 3;
  562. var angle = Math.PI * 2 / nsegments;
  563. for( i in 0...nsegments + 1 ) {
  564. var a = i * angle;
  565. lineTo(cx + Math.cos(a) * radius, cy + Math.sin(a) * radius);
  566. }
  567. flush();
  568. }
  569. /**
  570. Draws an ellipse centered at given position.
  571. @param cx X center position of the ellipse.
  572. @param cy Y center position of the ellipse.
  573. @param radiusX Horizontal radius of an ellipse.
  574. @param radiusY Vertical radius of an ellipse.
  575. @param rotationAngle Ellipse rotation in radians.
  576. @param nsegments Amount of segments used to draw an ellipse. When `0`, amount of segments calculated automatically.
  577. **/
  578. public function drawEllipse( cx : Float, cy : Float, radiusX : Float, radiusY : Float, rotationAngle : Float = 0, nsegments = 0 ) {
  579. flush();
  580. if( nsegments == 0 )
  581. nsegments = Math.ceil(Math.abs(radiusY * 3.14 * 2 / 4));
  582. if( nsegments < 3 ) nsegments = 3;
  583. var angle = Math.PI * 2 / nsegments;
  584. var x1, y1;
  585. for( i in 0...nsegments + 1 ) {
  586. var a = i * angle;
  587. x1 = Math.cos(a) * Math.cos(rotationAngle) * radiusX - Math.sin(a) * Math.sin(rotationAngle) * radiusY;
  588. y1 = Math.cos(rotationAngle) * Math.sin(a) * radiusY + Math.cos(a) * Math.sin(rotationAngle) * radiusX;
  589. lineTo(cx + x1, cy + y1);
  590. }
  591. flush();
  592. }
  593. /**
  594. Draws a pie centered at given position.
  595. @param cx X center position of the pie.
  596. @param cy Y center position of the pie.
  597. @param radius Radius of the pie.
  598. @param angleStart Starting angle of the pie in radians.
  599. @param angleLength The pie size in clockwise direction with `2*PI` being full circle.
  600. @param nsegments Amount of segments used to draw the pie. When `0`, amount of segments calculated automatically.
  601. **/
  602. public function drawPie( cx : Float, cy : Float, radius : Float, angleStart:Float, angleLength:Float, nsegments = 0 ) {
  603. if(Math.abs(angleLength) >= Math.PI * 2) {
  604. return drawCircle(cx, cy, radius, nsegments);
  605. }
  606. flush();
  607. lineTo(cx, cy);
  608. if( nsegments == 0 )
  609. nsegments = Math.ceil(Math.abs(radius * angleLength / 4));
  610. if( nsegments < 3 ) nsegments = 3;
  611. var angle = angleLength / (nsegments - 1);
  612. for( i in 0...nsegments ) {
  613. var a = i * angle + angleStart;
  614. lineTo(cx + Math.cos(a) * radius, cy + Math.sin(a) * radius);
  615. }
  616. lineTo(cx, cy);
  617. flush();
  618. }
  619. /**
  620. Draws a double-edged pie centered at given position.
  621. @param cx X center position of the pie.
  622. @param cy Y center position of the pie.
  623. @param radius The outer radius of the pie.
  624. @param innerRadius The inner radius of the pie.
  625. @param angleStart Starting angle of the pie in radians.
  626. @param angleLength The pie size in clockwise direction with `2*PI` being full circle.
  627. @param nsegments Amount of segments used to draw the pie. When `0`, amount of segments calculated automatically.
  628. **/
  629. public function drawPieInner( cx : Float, cy : Float, radius : Float, innerRadius : Float, angleStart:Float, angleLength:Float, nsegments = 0 ) {
  630. flush();
  631. if( Math.abs(angleLength) >= Math.PI * 2 + 1e-3 ) angleLength = Math.PI*2+1e-3;
  632. var cs = Math.cos(angleStart);
  633. var ss = Math.sin(angleStart);
  634. var ce = Math.cos(angleStart + angleLength);
  635. var se = Math.sin(angleStart + angleLength);
  636. lineTo(cx + cs * innerRadius, cy + ss * innerRadius);
  637. if( nsegments == 0 )
  638. nsegments = Math.ceil(Math.abs(radius * angleLength / 4));
  639. if( nsegments < 3 ) nsegments = 3;
  640. var angle = angleLength / (nsegments - 1);
  641. for( i in 0...nsegments ) {
  642. var a = i * angle + angleStart;
  643. lineTo(cx + Math.cos(a) * radius, cy + Math.sin(a) * radius);
  644. }
  645. lineTo(cx + ce * innerRadius, cy + se * innerRadius);
  646. for( i in 0...nsegments ) {
  647. var a = (nsegments - 1 - i) * angle + angleStart;
  648. lineTo(cx + Math.cos(a) * innerRadius, cy + Math.sin(a) * innerRadius);
  649. }
  650. flush();
  651. }
  652. /**
  653. Draws a rectangular pie centered at given position.
  654. @param cx X center position of the pie.
  655. @param cy Y center position of the pie.
  656. @param width Width of the pie.
  657. @param height Height of the pie.
  658. @param angleStart Starting angle of the pie in radians.
  659. @param angleLength The pie size in clockwise direction with `2*PI` being solid rectangle.
  660. @param nsegments Amount of segments used to draw the pie. When `0`, amount of segments calculated automatically.
  661. **/
  662. public function drawRectanglePie( cx : Float, cy : Float, width : Float, height : Float, angleStart:Float, angleLength:Float, nsegments = 0 ) {
  663. if(Math.abs(angleLength) >= Math.PI*2) {
  664. return drawRect(cx-(width/2), cy-(height/2), width, height);
  665. }
  666. flush();
  667. lineTo(cx, cy);
  668. if( nsegments == 0 )
  669. nsegments = Math.ceil(Math.abs(Math.max(width, height) * angleLength / 4));
  670. if( nsegments < 3 ) nsegments = 3;
  671. var angle = angleLength / (nsegments - 1);
  672. var square2 = Math.sqrt(2);
  673. for( i in 0...nsegments ) {
  674. var a = i * angle + angleStart;
  675. var _width = Math.cos(a) * (width/2+1) * square2;
  676. var _height = Math.sin(a) * (height/2+1) * square2;
  677. _width = Math.abs(_width) >= width/2 ? (Math.cos(a) < 0 ? width/2*-1 : width/2) : _width;
  678. _height = Math.abs(_height) >= height/2 ? (Math.sin(a) < 0 ? height/2*-1 : height/2) : _height;
  679. lineTo(cx + _width, cy + _height);
  680. }
  681. lineTo(cx, cy);
  682. flush();
  683. }
  684. /**
  685. * Draws a quadratic Bezier curve using the current line style from the current drawing position to (cx, cy) and using the control point that (bx, by) specifies.
  686. * IvanK Lib port ( http://lib.ivank.net )
  687. */
  688. public function curveTo( bx : Float, by : Float, cx : Float, cy : Float) {
  689. var ax = tmpPoints.length == 0 ? 0 :tmpPoints[ tmpPoints.length - 1 ].x;
  690. var ay = tmpPoints.length == 0 ? 0 :tmpPoints[ tmpPoints.length - 1 ].y;
  691. var t = 2 / 3;
  692. cubicCurveTo(ax + t * (bx - ax), ay + t * (by - ay), cx + t * (bx - cx), cy + t * (by - cy), cx, cy);
  693. }
  694. /**
  695. * Draws a cubic Bezier curve from the current drawing position to the specified anchor point.
  696. * IvanK Lib port ( http://lib.ivank.net )
  697. * @param bx control X for start point
  698. * @param by control Y for start point
  699. * @param cx control X for end point
  700. * @param cy control Y for end point
  701. * @param dx end X
  702. * @param dy end Y
  703. * @param nsegments = 40
  704. */
  705. public function cubicCurveTo( bx : Float, by : Float, cx : Float, cy : Float, dx : Float, dy : Float, nsegments = 40) {
  706. var ax = tmpPoints.length == 0 ? 0 : tmpPoints[tmpPoints.length - 1].x;
  707. var ay = tmpPoints.length == 0 ? 0 : tmpPoints[tmpPoints.length - 1].y;
  708. var tobx = bx - ax, toby = by - ay;
  709. var tocx = cx - bx, tocy = cy - by;
  710. var todx = dx - cx, tody = dy - cy;
  711. var step = 1 / nsegments;
  712. for (i in 1...nsegments) {
  713. var d = i * step;
  714. var px = ax + d * tobx, py = ay + d * toby;
  715. var qx = bx + d * tocx, qy = by + d * tocy;
  716. var rx = cx + d * todx, ry = cy + d * tody;
  717. var toqx = qx - px, toqy = qy - py;
  718. var torx = rx - qx, tory = ry - qy;
  719. var sx = px + d * toqx, sy = py + d * toqy;
  720. var tx = qx + d * torx, ty = qy + d * tory;
  721. var totx = tx - sx, toty = ty - sy;
  722. lineTo(sx + d * totx, sy + d * toty);
  723. }
  724. lineTo(dx, dy);
  725. }
  726. /**
  727. Draws a straight line from the current drawing position to the given position.
  728. **/
  729. public inline function lineTo( x : Float, y : Float ) {
  730. addVertex(x, y, curR, curG, curB, curA, x * ma + y * mc + mx, x * mb + y * md + my);
  731. }
  732. /**
  733. Advanced usage. Adds new vertex to the current polygon with given parameters and current line style.
  734. @param x Vertex X position
  735. @param y Vertex Y position
  736. @param r Red tint value of the vertex when performing fill operation.
  737. @param g Green tint value of the vertex when performing fill operation.
  738. @param b Blue tint value of the vertex when performing fill operation.
  739. @param a Alpha of the vertex when performing fill operation.
  740. @param u Normalized horizontal Texture position from the current Tile fill operation.
  741. @param v Normalized vertical Texture position from the current Tile fill operation.
  742. **/
  743. public function addVertex( x : Float, y : Float, r : Float, g : Float, b : Float, a : Float, u : Float = 0., v : Float = 0. ) {
  744. var half = lineSize / 2.0;
  745. if( x - half < xMin ) xMin = x - half;
  746. if( y - half < yMin ) yMin = y - half;
  747. if( x + half > xMax ) xMax = x + half;
  748. if( y + half > yMax ) yMax = y + half;
  749. if( x < xMinSize ) xMinSize = x;
  750. if( y < yMinSize ) yMinSize = y;
  751. if( x > xMaxSize ) xMaxSize = x;
  752. if( y > yMaxSize ) yMaxSize = y;
  753. if( doFill )
  754. content.add(x, y, u, v, r, g, b, a);
  755. var gp = new GPoint();
  756. gp.load(x, y, lineR, lineG, lineB, lineA);
  757. tmpPoints.push(gp);
  758. }
  759. override function draw(ctx:RenderContext) {
  760. if( !ctx.beginDrawBatchState(this) ) return;
  761. content.doRender(ctx);
  762. }
  763. override function sync(ctx:RenderContext) {
  764. super.sync(ctx);
  765. flush();
  766. content.flush();
  767. }
  768. }