Source: core/graphics/Graphics.js

core/graphics/Graphics.js

  1. import Container from '../display/Container';
  2. import RenderTexture from '../textures/RenderTexture';
  3. import Texture from '../textures/Texture';
  4. import GraphicsData from './GraphicsData';
  5. import Sprite from '../sprites/Sprite';
  6. import { Matrix, Point, Rectangle, RoundedRectangle, Ellipse, Polygon, Circle } from '../math';
  7. import { hex2rgb, rgb2hex } from '../utils';
  8. import { SHAPES, BLEND_MODES, PI_2 } from '../const';
  9. import Bounds from '../display/Bounds';
  10. import bezierCurveTo from './utils/bezierCurveTo';
  11. import CanvasRenderer from '../renderers/canvas/CanvasRenderer';
  12. let canvasRenderer;
  13. const tempMatrix = new Matrix();
  14. const tempPoint = new Point();
  15. const tempColor1 = new Float32Array(4);
  16. const tempColor2 = new Float32Array(4);
  17. /**
  18. * The Graphics class contains methods used to draw primitive shapes such as lines, circles and
  19. * rectangles to the display, and to color and fill them.
  20. *
  21. * @class
  22. * @extends PIXI.Container
  23. * @memberof PIXI
  24. */
  25. export default class Graphics extends Container
  26. {
  27. /**
  28. *
  29. * @param {boolean} [nativeLines=false] - If true the lines will be draw using LINES instead of TRIANGLE_STRIP
  30. */
  31. constructor(nativeLines = false)
  32. {
  33. super();
  34. /**
  35. * The alpha value used when filling the Graphics object.
  36. *
  37. * @member {number}
  38. * @default 1
  39. */
  40. this.fillAlpha = 1;
  41. /**
  42. * The width (thickness) of any lines drawn.
  43. *
  44. * @member {number}
  45. * @default 0
  46. */
  47. this.lineWidth = 0;
  48. /**
  49. * If true the lines will be draw using LINES instead of TRIANGLE_STRIP
  50. *
  51. * @member {boolean}
  52. */
  53. this.nativeLines = nativeLines;
  54. /**
  55. * The color of any lines drawn.
  56. *
  57. * @member {string}
  58. * @default 0
  59. */
  60. this.lineColor = 0;
  61. /**
  62. * The alignment of any lines drawn (0.5 = middle, 1 = outter, 0 = inner).
  63. *
  64. * @member {number}
  65. * @default 0.5
  66. */
  67. this.lineAlignment = 0.5;
  68. /**
  69. * Graphics data
  70. *
  71. * @member {PIXI.GraphicsData[]}
  72. * @private
  73. */
  74. this.graphicsData = [];
  75. /**
  76. * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to
  77. * reset the tint.
  78. *
  79. * @member {number}
  80. * @default 0xFFFFFF
  81. */
  82. this.tint = 0xFFFFFF;
  83. /**
  84. * The previous tint applied to the graphic shape. Used to compare to the current tint and
  85. * check if theres change.
  86. *
  87. * @member {number}
  88. * @private
  89. * @default 0xFFFFFF
  90. */
  91. this._prevTint = 0xFFFFFF;
  92. /**
  93. * The blend mode to be applied to the graphic shape. Apply a value of
  94. * `PIXI.BLEND_MODES.NORMAL` to reset the blend mode.
  95. *
  96. * @member {number}
  97. * @default PIXI.BLEND_MODES.NORMAL;
  98. * @see PIXI.BLEND_MODES
  99. */
  100. this.blendMode = BLEND_MODES.NORMAL;
  101. /**
  102. * Current path
  103. *
  104. * @member {PIXI.GraphicsData}
  105. * @private
  106. */
  107. this.currentPath = null;
  108. /**
  109. * Array containing some WebGL-related properties used by the WebGL renderer.
  110. *
  111. * @member {object<number, object>}
  112. * @private
  113. */
  114. // TODO - _webgl should use a prototype object, not a random undocumented object...
  115. this._webGL = {};
  116. /**
  117. * Whether this shape is being used as a mask.
  118. *
  119. * @member {boolean}
  120. */
  121. this.isMask = false;
  122. /**
  123. * The bounds' padding used for bounds calculation.
  124. *
  125. * @member {number}
  126. */
  127. this.boundsPadding = 0;
  128. /**
  129. * A cache of the local bounds to prevent recalculation.
  130. *
  131. * @member {PIXI.Rectangle}
  132. * @private
  133. */
  134. this._localBounds = new Bounds();
  135. /**
  136. * Used to detect if the graphics object has changed. If this is set to true then the graphics
  137. * object will be recalculated.
  138. *
  139. * @member {boolean}
  140. * @private
  141. */
  142. this.dirty = 0;
  143. /**
  144. * Used to detect if we need to do a fast rect check using the id compare method
  145. * @type {Number}
  146. */
  147. this.fastRectDirty = -1;
  148. /**
  149. * Used to detect if we clear the graphics webGL data
  150. * @type {Number}
  151. */
  152. this.clearDirty = 0;
  153. /**
  154. * Used to detect if we we need to recalculate local bounds
  155. * @type {Number}
  156. */
  157. this.boundsDirty = -1;
  158. /**
  159. * Used to detect if the cached sprite object needs to be updated.
  160. *
  161. * @member {boolean}
  162. * @private
  163. */
  164. this.cachedSpriteDirty = false;
  165. this._spriteRect = null;
  166. this._fastRect = false;
  167. this._prevRectTint = null;
  168. this._prevRectFillColor = null;
  169. /**
  170. * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite.
  171. * This is useful if your graphics element does not change often, as it will speed up the rendering
  172. * of the object in exchange for taking up texture memory. It is also useful if you need the graphics
  173. * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if
  174. * you are constantly redrawing the graphics element.
  175. *
  176. * @name cacheAsBitmap
  177. * @member {boolean}
  178. * @memberof PIXI.Graphics#
  179. * @default false
  180. */
  181. }
  182. /**
  183. * Creates a new Graphics object with the same values as this one.
  184. * Note that the only the properties of the object are cloned, not its transform (position,scale,etc)
  185. *
  186. * @return {PIXI.Graphics} A clone of the graphics object
  187. */
  188. clone()
  189. {
  190. const clone = new Graphics();
  191. clone.renderable = this.renderable;
  192. clone.fillAlpha = this.fillAlpha;
  193. clone.lineWidth = this.lineWidth;
  194. clone.lineColor = this.lineColor;
  195. clone.lineAlignment = this.lineAlignment;
  196. clone.tint = this.tint;
  197. clone.blendMode = this.blendMode;
  198. clone.isMask = this.isMask;
  199. clone.boundsPadding = this.boundsPadding;
  200. clone.dirty = 0;
  201. clone.cachedSpriteDirty = this.cachedSpriteDirty;
  202. // copy graphics data
  203. for (let i = 0; i < this.graphicsData.length; ++i)
  204. {
  205. clone.graphicsData.push(this.graphicsData[i].clone());
  206. }
  207. clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1];
  208. clone.updateLocalBounds();
  209. return clone;
  210. }
  211. /**
  212. * Calculate length of quadratic curve
  213. * @see {@link http://www.malczak.linuxpl.com/blog/quadratic-bezier-curve-length/}
  214. * for the detailed explanation of math behind this.
  215. *
  216. * @private
  217. * @param {number} fromX - x-coordinate of curve start point
  218. * @param {number} fromY - y-coordinate of curve start point
  219. * @param {number} cpX - x-coordinate of curve control point
  220. * @param {number} cpY - y-coordinate of curve control point
  221. * @param {number} toX - x-coordinate of curve end point
  222. * @param {number} toY - y-coordinate of curve end point
  223. * @return {number} Length of quadratic curve
  224. */
  225. _quadraticCurveLength(fromX, fromY, cpX, cpY, toX, toY)
  226. {
  227. const ax = fromX - (2.0 * cpX) + toX;
  228. const ay = fromY - (2.0 * cpY) + toY;
  229. const bx = (2.0 * cpX) - (2.0 * fromX);
  230. const by = (2.0 * cpY) - (2.0 * fromY);
  231. const a = 4.0 * ((ax * ax) + (ay * ay));
  232. const b = 4.0 * ((ax * bx) + (ay * by));
  233. const c = (bx * bx) + (by * by);
  234. const s = 2.0 * Math.sqrt(a + b + c);
  235. const a2 = Math.sqrt(a);
  236. const a32 = 2.0 * a * a2;
  237. const c2 = 2.0 * Math.sqrt(c);
  238. const ba = b / a2;
  239. return (
  240. (a32 * s)
  241. + (a2 * b * (s - c2))
  242. + (
  243. ((4.0 * c * a) - (b * b))
  244. * Math.log(((2.0 * a2) + ba + s) / (ba + c2))
  245. )
  246. )
  247. / (4.0 * a32);
  248. }
  249. /**
  250. * Calculate length of bezier curve.
  251. * Analytical solution is impossible, since it involves an integral that does not integrate in general.
  252. * Therefore numerical solution is used.
  253. *
  254. * @private
  255. * @param {number} fromX - Starting point x
  256. * @param {number} fromY - Starting point y
  257. * @param {number} cpX - Control point x
  258. * @param {number} cpY - Control point y
  259. * @param {number} cpX2 - Second Control point x
  260. * @param {number} cpY2 - Second Control point y
  261. * @param {number} toX - Destination point x
  262. * @param {number} toY - Destination point y
  263. * @return {number} Length of bezier curve
  264. */
  265. _bezierCurveLength(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY)
  266. {
  267. const n = 10;
  268. let result = 0.0;
  269. let t = 0.0;
  270. let t2 = 0.0;
  271. let t3 = 0.0;
  272. let nt = 0.0;
  273. let nt2 = 0.0;
  274. let nt3 = 0.0;
  275. let x = 0.0;
  276. let y = 0.0;
  277. let dx = 0.0;
  278. let dy = 0.0;
  279. let prevX = fromX;
  280. let prevY = fromY;
  281. for (let i = 1; i <= n; ++i)
  282. {
  283. t = i / n;
  284. t2 = t * t;
  285. t3 = t2 * t;
  286. nt = (1.0 - t);
  287. nt2 = nt * nt;
  288. nt3 = nt2 * nt;
  289. x = (nt3 * fromX) + (3.0 * nt2 * t * cpX) + (3.0 * nt * t2 * cpX2) + (t3 * toX);
  290. y = (nt3 * fromY) + (3.0 * nt2 * t * cpY) + (3 * nt * t2 * cpY2) + (t3 * toY);
  291. dx = prevX - x;
  292. dy = prevY - y;
  293. prevX = x;
  294. prevY = y;
  295. result += Math.sqrt((dx * dx) + (dy * dy));
  296. }
  297. return result;
  298. }
  299. /**
  300. * Calculate number of segments for the curve based on its length to ensure its smoothness.
  301. *
  302. * @private
  303. * @param {number} length - length of curve
  304. * @return {number} Number of segments
  305. */
  306. _segmentsCount(length)
  307. {
  308. let result = Math.ceil(length / Graphics.CURVES.maxLength);
  309. if (result < Graphics.CURVES.minSegments)
  310. {
  311. result = Graphics.CURVES.minSegments;
  312. }
  313. else if (result > Graphics.CURVES.maxSegments)
  314. {
  315. result = Graphics.CURVES.maxSegments;
  316. }
  317. return result;
  318. }
  319. /**
  320. * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo()
  321. * method or the drawCircle() method.
  322. *
  323. * @param {number} [lineWidth=0] - width of the line to draw, will update the objects stored style
  324. * @param {number} [color=0] - color of the line to draw, will update the objects stored style
  325. * @param {number} [alpha=1] - alpha of the line to draw, will update the objects stored style
  326. * @param {number} [alignment=0.5] - alignment of the line to draw, (0 = inner, 0.5 = middle, 1 = outter)
  327. * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
  328. */
  329. lineStyle(lineWidth = 0, color = 0, alpha = 1, alignment = 0.5)
  330. {
  331. this.lineWidth = lineWidth;
  332. this.lineColor = color;
  333. this.lineAlpha = alpha;
  334. this.lineAlignment = alignment;
  335. if (this.currentPath)
  336. {
  337. if (this.currentPath.shape.points.length)
  338. {
  339. // halfway through a line? start a new one!
  340. const shape = new Polygon(this.currentPath.shape.points.slice(-2));
  341. shape.closed = false;
  342. this.drawShape(shape);
  343. }
  344. else
  345. {
  346. // otherwise its empty so lets just set the line properties
  347. this.currentPath.lineWidth = this.lineWidth;
  348. this.currentPath.lineColor = this.lineColor;
  349. this.currentPath.lineAlpha = this.lineAlpha;
  350. this.currentPath.lineAlignment = this.lineAlignment;
  351. }
  352. }
  353. return this;
  354. }
  355. /**
  356. * Moves the current drawing position to x, y.
  357. *
  358. * @param {number} x - the X coordinate to move to
  359. * @param {number} y - the Y coordinate to move to
  360. * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
  361. */
  362. moveTo(x, y)
  363. {
  364. const shape = new Polygon([x, y]);
  365. shape.closed = false;
  366. this.drawShape(shape);
  367. return this;
  368. }
  369. /**
  370. * Draws a line using the current line style from the current drawing position to (x, y);
  371. * The current drawing position is then set to (x, y).
  372. *
  373. * @param {number} x - the X coordinate to draw to
  374. * @param {number} y - the Y coordinate to draw to
  375. * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
  376. */
  377. lineTo(x, y)
  378. {
  379. const points = this.currentPath.shape.points;
  380. const fromX = points[points.length - 2];
  381. const fromY = points[points.length - 1];
  382. if (fromX !== x || fromY !== y)
  383. {
  384. points.push(x, y);
  385. this.dirty++;
  386. }
  387. return this;
  388. }
  389. /**
  390. * Calculate the points for a quadratic bezier curve and then draws it.
  391. * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c
  392. *
  393. * @param {number} cpX - Control point x
  394. * @param {number} cpY - Control point y
  395. * @param {number} toX - Destination point x
  396. * @param {number} toY - Destination point y
  397. * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
  398. */
  399. quadraticCurveTo(cpX, cpY, toX, toY)
  400. {
  401. if (this.currentPath)
  402. {
  403. if (this.currentPath.shape.points.length === 0)
  404. {
  405. this.currentPath.shape.points = [0, 0];
  406. }
  407. }
  408. else
  409. {
  410. this.moveTo(0, 0);
  411. }
  412. const points = this.currentPath.shape.points;
  413. let xa = 0;
  414. let ya = 0;
  415. if (points.length === 0)
  416. {
  417. this.moveTo(0, 0);
  418. }
  419. const fromX = points[points.length - 2];
  420. const fromY = points[points.length - 1];
  421. const n = Graphics.CURVES.adaptive
  422. ? this._segmentsCount(this._quadraticCurveLength(fromX, fromY, cpX, cpY, toX, toY))
  423. : 20;
  424. for (let i = 1; i <= n; ++i)
  425. {
  426. const j = i / n;
  427. xa = fromX + ((cpX - fromX) * j);
  428. ya = fromY + ((cpY - fromY) * j);
  429. points.push(xa + (((cpX + ((toX - cpX) * j)) - xa) * j),
  430. ya + (((cpY + ((toY - cpY) * j)) - ya) * j));
  431. }
  432. this.dirty++;
  433. return this;
  434. }
  435. /**
  436. * Calculate the points for a bezier curve and then draws it.
  437. *
  438. * @param {number} cpX - Control point x
  439. * @param {number} cpY - Control point y
  440. * @param {number} cpX2 - Second Control point x
  441. * @param {number} cpY2 - Second Control point y
  442. * @param {number} toX - Destination point x
  443. * @param {number} toY - Destination point y
  444. * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
  445. */
  446. bezierCurveTo(cpX, cpY, cpX2, cpY2, toX, toY)
  447. {
  448. if (this.currentPath)
  449. {
  450. if (this.currentPath.shape.points.length === 0)
  451. {
  452. this.currentPath.shape.points = [0, 0];
  453. }
  454. }
  455. else
  456. {
  457. this.moveTo(0, 0);
  458. }
  459. const points = this.currentPath.shape.points;
  460. const fromX = points[points.length - 2];
  461. const fromY = points[points.length - 1];
  462. points.length -= 2;
  463. const n = Graphics.CURVES.adaptive
  464. ? this._segmentsCount(this._bezierCurveLength(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY))
  465. : 20;
  466. bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, n, points);
  467. this.dirty++;
  468. return this;
  469. }
  470. /**
  471. * The arcTo() method creates an arc/curve between two tangents on the canvas.
  472. *
  473. * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google!
  474. *
  475. * @param {number} x1 - The x-coordinate of the beginning of the arc
  476. * @param {number} y1 - The y-coordinate of the beginning of the arc
  477. * @param {number} x2 - The x-coordinate of the end of the arc
  478. * @param {number} y2 - The y-coordinate of the end of the arc
  479. * @param {number} radius - The radius of the arc
  480. * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
  481. */
  482. arcTo(x1, y1, x2, y2, radius)
  483. {
  484. if (this.currentPath)
  485. {
  486. if (this.currentPath.shape.points.length === 0)
  487. {
  488. this.currentPath.shape.points.push(x1, y1);
  489. }
  490. }
  491. else
  492. {
  493. this.moveTo(x1, y1);
  494. }
  495. const points = this.currentPath.shape.points;
  496. const fromX = points[points.length - 2];
  497. const fromY = points[points.length - 1];
  498. const a1 = fromY - y1;
  499. const b1 = fromX - x1;
  500. const a2 = y2 - y1;
  501. const b2 = x2 - x1;
  502. const mm = Math.abs((a1 * b2) - (b1 * a2));
  503. if (mm < 1.0e-8 || radius === 0)
  504. {
  505. if (points[points.length - 2] !== x1 || points[points.length - 1] !== y1)
  506. {
  507. points.push(x1, y1);
  508. }
  509. }
  510. else
  511. {
  512. const dd = (a1 * a1) + (b1 * b1);
  513. const cc = (a2 * a2) + (b2 * b2);
  514. const tt = (a1 * a2) + (b1 * b2);
  515. const k1 = radius * Math.sqrt(dd) / mm;
  516. const k2 = radius * Math.sqrt(cc) / mm;
  517. const j1 = k1 * tt / dd;
  518. const j2 = k2 * tt / cc;
  519. const cx = (k1 * b2) + (k2 * b1);
  520. const cy = (k1 * a2) + (k2 * a1);
  521. const px = b1 * (k2 + j1);
  522. const py = a1 * (k2 + j1);
  523. const qx = b2 * (k1 + j2);
  524. const qy = a2 * (k1 + j2);
  525. const startAngle = Math.atan2(py - cy, px - cx);
  526. const endAngle = Math.atan2(qy - cy, qx - cx);
  527. this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1);
  528. }
  529. this.dirty++;
  530. return this;
  531. }
  532. /**
  533. * The arc method creates an arc/curve (used to create circles, or parts of circles).
  534. *
  535. * @param {number} cx - The x-coordinate of the center of the circle
  536. * @param {number} cy - The y-coordinate of the center of the circle
  537. * @param {number} radius - The radius of the circle
  538. * @param {number} startAngle - The starting angle, in radians (0 is at the 3 o'clock position
  539. * of the arc's circle)
  540. * @param {number} endAngle - The ending angle, in radians
  541. * @param {boolean} [anticlockwise=false] - Specifies whether the drawing should be
  542. * counter-clockwise or clockwise. False is default, and indicates clockwise, while true
  543. * indicates counter-clockwise.
  544. * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
  545. */
  546. arc(cx, cy, radius, startAngle, endAngle, anticlockwise = false)
  547. {
  548. if (startAngle === endAngle)
  549. {
  550. return this;
  551. }
  552. if (!anticlockwise && endAngle <= startAngle)
  553. {
  554. endAngle += PI_2;
  555. }
  556. else if (anticlockwise && startAngle <= endAngle)
  557. {
  558. startAngle += PI_2;
  559. }
  560. const sweep = endAngle - startAngle;
  561. const segs = Graphics.CURVES.adaptive
  562. ? this._segmentsCount(Math.abs(sweep) * radius)
  563. : Math.ceil(Math.abs(sweep) / PI_2) * 40;
  564. if (sweep === 0)
  565. {
  566. return this;
  567. }
  568. const startX = cx + (Math.cos(startAngle) * radius);
  569. const startY = cy + (Math.sin(startAngle) * radius);
  570. // If the currentPath exists, take its points. Otherwise call `moveTo` to start a path.
  571. let points = this.currentPath ? this.currentPath.shape.points : null;
  572. if (points)
  573. {
  574. // We check how far our start is from the last existing point
  575. const xDiff = Math.abs(points[points.length - 2] - startX);
  576. const yDiff = Math.abs(points[points.length - 1] - startY);
  577. if (xDiff < 0.001 && yDiff < 0.001)
  578. {
  579. // If the point is very close, we don't add it, since this would lead to artifacts
  580. // during tesselation due to floating point imprecision.
  581. }
  582. else
  583. {
  584. points.push(startX, startY);
  585. }
  586. }
  587. else
  588. {
  589. this.moveTo(startX, startY);
  590. points = this.currentPath.shape.points;
  591. }
  592. const theta = sweep / (segs * 2);
  593. const theta2 = theta * 2;
  594. const cTheta = Math.cos(theta);
  595. const sTheta = Math.sin(theta);
  596. const segMinus = segs - 1;
  597. const remainder = (segMinus % 1) / segMinus;
  598. for (let i = 0; i <= segMinus; ++i)
  599. {
  600. const real = i + (remainder * i);
  601. const angle = ((theta) + startAngle + (theta2 * real));
  602. const c = Math.cos(angle);
  603. const s = -Math.sin(angle);
  604. points.push(
  605. (((cTheta * c) + (sTheta * s)) * radius) + cx,
  606. (((cTheta * -s) + (sTheta * c)) * radius) + cy
  607. );
  608. }
  609. this.dirty++;
  610. return this;
  611. }
  612. /**
  613. * Specifies a simple one-color fill that subsequent calls to other Graphics methods
  614. * (such as lineTo() or drawCircle()) use when drawing.
  615. *
  616. * @param {number} [color=0] - the color of the fill
  617. * @param {number} [alpha=1] - the alpha of the fill
  618. * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
  619. */
  620. beginFill(color = 0, alpha = 1)
  621. {
  622. this.filling = true;
  623. this.fillColor = color;
  624. this.fillAlpha = alpha;
  625. if (this.currentPath)
  626. {
  627. if (this.currentPath.shape.points.length <= 2)
  628. {
  629. this.currentPath.fill = this.filling;
  630. this.currentPath.fillColor = this.fillColor;
  631. this.currentPath.fillAlpha = this.fillAlpha;
  632. }
  633. }
  634. return this;
  635. }
  636. /**
  637. * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method.
  638. *
  639. * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
  640. */
  641. endFill()
  642. {
  643. this.filling = false;
  644. this.fillColor = null;
  645. this.fillAlpha = 1;
  646. return this;
  647. }
  648. /**
  649. *
  650. * @param {number} x - The X coord of the top-left of the rectangle
  651. * @param {number} y - The Y coord of the top-left of the rectangle
  652. * @param {number} width - The width of the rectangle
  653. * @param {number} height - The height of the rectangle
  654. * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
  655. */
  656. drawRect(x, y, width, height)
  657. {
  658. this.drawShape(new Rectangle(x, y, width, height));
  659. return this;
  660. }
  661. /**
  662. *
  663. * @param {number} x - The X coord of the top-left of the rectangle
  664. * @param {number} y - The Y coord of the top-left of the rectangle
  665. * @param {number} width - The width of the rectangle
  666. * @param {number} height - The height of the rectangle
  667. * @param {number} radius - Radius of the rectangle corners
  668. * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
  669. */
  670. drawRoundedRect(x, y, width, height, radius)
  671. {
  672. this.drawShape(new RoundedRectangle(x, y, width, height, radius));
  673. return this;
  674. }
  675. /**
  676. * Draws a circle.
  677. *
  678. * @param {number} x - The X coordinate of the center of the circle
  679. * @param {number} y - The Y coordinate of the center of the circle
  680. * @param {number} radius - The radius of the circle
  681. * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
  682. */
  683. drawCircle(x, y, radius)
  684. {
  685. this.drawShape(new Circle(x, y, radius));
  686. return this;
  687. }
  688. /**
  689. * Draws an ellipse.
  690. *
  691. * @param {number} x - The X coordinate of the center of the ellipse
  692. * @param {number} y - The Y coordinate of the center of the ellipse
  693. * @param {number} width - The half width of the ellipse
  694. * @param {number} height - The half height of the ellipse
  695. * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
  696. */
  697. drawEllipse(x, y, width, height)
  698. {
  699. this.drawShape(new Ellipse(x, y, width, height));
  700. return this;
  701. }
  702. /**
  703. * Draws a polygon using the given path.
  704. *
  705. * @param {number[]|PIXI.Point[]|PIXI.Polygon} path - The path data used to construct the polygon.
  706. * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
  707. */
  708. drawPolygon(path)
  709. {
  710. // prevents an argument assignment deopt
  711. // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments
  712. let points = path;
  713. let closed = true;
  714. if (points instanceof Polygon)
  715. {
  716. closed = points.closed;
  717. points = points.points;
  718. }
  719. if (!Array.isArray(points))
  720. {
  721. // prevents an argument leak deopt
  722. // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments
  723. points = new Array(arguments.length);
  724. for (let i = 0; i < points.length; ++i)
  725. {
  726. points[i] = arguments[i]; // eslint-disable-line prefer-rest-params
  727. }
  728. }
  729. const shape = new Polygon(points);
  730. shape.closed = closed;
  731. this.drawShape(shape);
  732. return this;
  733. }
  734. /**
  735. * Draw a star shape with an abitrary number of points.
  736. *
  737. * @param {number} x - Center X position of the star
  738. * @param {number} y - Center Y position of the star
  739. * @param {number} points - The number of points of the star, must be > 1
  740. * @param {number} radius - The outer radius of the star
  741. * @param {number} [innerRadius] - The inner radius between points, default half `radius`
  742. * @param {number} [rotation=0] - The rotation of the star in radians, where 0 is vertical
  743. * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
  744. */
  745. drawStar(x, y, points, radius, innerRadius, rotation = 0)
  746. {
  747. innerRadius = innerRadius || radius / 2;
  748. const startAngle = (-1 * Math.PI / 2) + rotation;
  749. const len = points * 2;
  750. const delta = PI_2 / len;
  751. const polygon = [];
  752. for (let i = 0; i < len; i++)
  753. {
  754. const r = i % 2 ? innerRadius : radius;
  755. const angle = (i * delta) + startAngle;
  756. polygon.push(
  757. x + (r * Math.cos(angle)),
  758. y + (r * Math.sin(angle))
  759. );
  760. }
  761. return this.drawPolygon(polygon);
  762. }
  763. /**
  764. * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings.
  765. *
  766. * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
  767. */
  768. clear()
  769. {
  770. if (this.lineWidth || this.filling || this.graphicsData.length > 0)
  771. {
  772. this.lineWidth = 0;
  773. this.lineAlignment = 0.5;
  774. this.filling = false;
  775. this.boundsDirty = -1;
  776. this.canvasTintDirty = -1;
  777. this.dirty++;
  778. this.clearDirty++;
  779. this.graphicsData.length = 0;
  780. }
  781. this.currentPath = null;
  782. this._spriteRect = null;
  783. return this;
  784. }
  785. /**
  786. * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and
  787. * masked with gl.scissor.
  788. *
  789. * @returns {boolean} True if only 1 rect.
  790. */
  791. isFastRect()
  792. {
  793. return this.graphicsData.length === 1
  794. && this.graphicsData[0].shape.type === SHAPES.RECT
  795. && !this.graphicsData[0].lineWidth;
  796. }
  797. /**
  798. * Renders the object using the WebGL renderer
  799. *
  800. * @private
  801. * @param {PIXI.WebGLRenderer} renderer - The renderer
  802. */
  803. _renderWebGL(renderer)
  804. {
  805. // if the sprite is not visible or the alpha is 0 then no need to render this element
  806. if (this.dirty !== this.fastRectDirty)
  807. {
  808. this.fastRectDirty = this.dirty;
  809. this._fastRect = this.isFastRect();
  810. }
  811. // TODO this check can be moved to dirty?
  812. if (this._fastRect)
  813. {
  814. this._renderSpriteRect(renderer);
  815. }
  816. else
  817. {
  818. renderer.setObjectRenderer(renderer.plugins.graphics);
  819. renderer.plugins.graphics.render(this);
  820. }
  821. }
  822. /**
  823. * Renders a sprite rectangle.
  824. *
  825. * @private
  826. * @param {PIXI.WebGLRenderer} renderer - The renderer
  827. */
  828. _renderSpriteRect(renderer)
  829. {
  830. const rect = this.graphicsData[0].shape;
  831. if (!this._spriteRect)
  832. {
  833. this._spriteRect = new Sprite(new Texture(Texture.WHITE));
  834. }
  835. const sprite = this._spriteRect;
  836. const fillColor = this.graphicsData[0].fillColor;
  837. if (this.tint === 0xffffff)
  838. {
  839. sprite.tint = fillColor;
  840. }
  841. else if (this.tint !== this._prevRectTint || fillColor !== this._prevRectFillColor)
  842. {
  843. const t1 = tempColor1;
  844. const t2 = tempColor2;
  845. hex2rgb(fillColor, t1);
  846. hex2rgb(this.tint, t2);
  847. t1[0] *= t2[0];
  848. t1[1] *= t2[1];
  849. t1[2] *= t2[2];
  850. sprite.tint = rgb2hex(t1);
  851. this._prevRectTint = this.tint;
  852. this._prevRectFillColor = fillColor;
  853. }
  854. sprite.alpha = this.graphicsData[0].fillAlpha;
  855. sprite.worldAlpha = this.worldAlpha * sprite.alpha;
  856. sprite.blendMode = this.blendMode;
  857. sprite._texture._frame.width = rect.width;
  858. sprite._texture._frame.height = rect.height;
  859. sprite.transform.worldTransform = this.transform.worldTransform;
  860. sprite.anchor.set(-rect.x / rect.width, -rect.y / rect.height);
  861. sprite._onAnchorUpdate();
  862. sprite._renderWebGL(renderer);
  863. }
  864. /**
  865. * Renders the object using the Canvas renderer
  866. *
  867. * @private
  868. * @param {PIXI.CanvasRenderer} renderer - The renderer
  869. */
  870. _renderCanvas(renderer)
  871. {
  872. if (this.isMask === true)
  873. {
  874. return;
  875. }
  876. renderer.plugins.graphics.render(this);
  877. }
  878. /**
  879. * Retrieves the bounds of the graphic shape as a rectangle object
  880. *
  881. * @private
  882. */
  883. _calculateBounds()
  884. {
  885. if (this.boundsDirty !== this.dirty)
  886. {
  887. this.boundsDirty = this.dirty;
  888. this.updateLocalBounds();
  889. this.cachedSpriteDirty = true;
  890. }
  891. const lb = this._localBounds;
  892. this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY);
  893. }
  894. /**
  895. * Tests if a point is inside this graphics object
  896. *
  897. * @param {PIXI.Point} point - the point to test
  898. * @return {boolean} the result of the test
  899. */
  900. containsPoint(point)
  901. {
  902. this.worldTransform.applyInverse(point, tempPoint);
  903. const graphicsData = this.graphicsData;
  904. for (let i = 0; i < graphicsData.length; ++i)
  905. {
  906. const data = graphicsData[i];
  907. if (!data.fill)
  908. {
  909. continue;
  910. }
  911. // only deal with fills..
  912. if (data.shape)
  913. {
  914. if (data.shape.contains(tempPoint.x, tempPoint.y))
  915. {
  916. let hitHole = false;
  917. if (data.holes)
  918. {
  919. for (let i = 0; i < data.holes.length; i++)
  920. {
  921. const hole = data.holes[i];
  922. if (hole.contains(tempPoint.x, tempPoint.y))
  923. {
  924. hitHole = true;
  925. break;
  926. }
  927. }
  928. }
  929. if (!hitHole)
  930. {
  931. return true;
  932. }
  933. }
  934. }
  935. }
  936. return false;
  937. }
  938. /**
  939. * Update the bounds of the object
  940. *
  941. */
  942. updateLocalBounds()
  943. {
  944. let minX = Infinity;
  945. let maxX = -Infinity;
  946. let minY = Infinity;
  947. let maxY = -Infinity;
  948. if (this.graphicsData.length)
  949. {
  950. let shape = 0;
  951. let x = 0;
  952. let y = 0;
  953. let w = 0;
  954. let h = 0;
  955. for (let i = 0; i < this.graphicsData.length; i++)
  956. {
  957. const data = this.graphicsData[i];
  958. const type = data.type;
  959. const lineWidth = data.lineWidth;
  960. const lineAlignment = data.lineAlignment;
  961. const lineOffset = lineWidth * lineAlignment;
  962. shape = data.shape;
  963. if (type === SHAPES.RECT || type === SHAPES.RREC)
  964. {
  965. x = shape.x - lineOffset;
  966. y = shape.y - lineOffset;
  967. w = shape.width + (lineOffset * 2);
  968. h = shape.height + (lineOffset * 2);
  969. minX = x < minX ? x : minX;
  970. maxX = x + w > maxX ? x + w : maxX;
  971. minY = y < minY ? y : minY;
  972. maxY = y + h > maxY ? y + h : maxY;
  973. }
  974. else if (type === SHAPES.CIRC)
  975. {
  976. x = shape.x;
  977. y = shape.y;
  978. w = shape.radius + lineOffset;
  979. h = shape.radius + lineOffset;
  980. minX = x - w < minX ? x - w : minX;
  981. maxX = x + w > maxX ? x + w : maxX;
  982. minY = y - h < minY ? y - h : minY;
  983. maxY = y + h > maxY ? y + h : maxY;
  984. }
  985. else if (type === SHAPES.ELIP)
  986. {
  987. x = shape.x;
  988. y = shape.y;
  989. w = shape.width + lineOffset;
  990. h = shape.height + lineOffset;
  991. minX = x - w < minX ? x - w : minX;
  992. maxX = x + w > maxX ? x + w : maxX;
  993. minY = y - h < minY ? y - h : minY;
  994. maxY = y + h > maxY ? y + h : maxY;
  995. }
  996. else
  997. {
  998. // POLY
  999. const points = shape.points;
  1000. let x2 = 0;
  1001. let y2 = 0;
  1002. let dx = 0;
  1003. let dy = 0;
  1004. let rw = 0;
  1005. let rh = 0;
  1006. let cx = 0;
  1007. let cy = 0;
  1008. for (let j = 0; j + 2 < points.length; j += 2)
  1009. {
  1010. x = points[j];
  1011. y = points[j + 1];
  1012. x2 = points[j + 2];
  1013. y2 = points[j + 3];
  1014. dx = Math.abs(x2 - x);
  1015. dy = Math.abs(y2 - y);
  1016. h = lineOffset * 2;
  1017. w = Math.sqrt((dx * dx) + (dy * dy));
  1018. if (w < 1e-9)
  1019. {
  1020. continue;
  1021. }
  1022. rw = ((h / w * dy) + dx) / 2;
  1023. rh = ((h / w * dx) + dy) / 2;
  1024. cx = (x2 + x) / 2;
  1025. cy = (y2 + y) / 2;
  1026. minX = cx - rw < minX ? cx - rw : minX;
  1027. maxX = cx + rw > maxX ? cx + rw : maxX;
  1028. minY = cy - rh < minY ? cy - rh : minY;
  1029. maxY = cy + rh > maxY ? cy + rh : maxY;
  1030. }
  1031. }
  1032. }
  1033. }
  1034. else
  1035. {
  1036. minX = 0;
  1037. maxX = 0;
  1038. minY = 0;
  1039. maxY = 0;
  1040. }
  1041. const padding = this.boundsPadding;
  1042. this._localBounds.minX = minX - padding;
  1043. this._localBounds.maxX = maxX + padding;
  1044. this._localBounds.minY = minY - padding;
  1045. this._localBounds.maxY = maxY + padding;
  1046. }
  1047. /**
  1048. * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon.
  1049. *
  1050. * @param {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} shape - The shape object to draw.
  1051. * @return {PIXI.GraphicsData} The generated GraphicsData object.
  1052. */
  1053. drawShape(shape)
  1054. {
  1055. if (this.currentPath)
  1056. {
  1057. // check current path!
  1058. if (this.currentPath.shape.points.length <= 2)
  1059. {
  1060. this.graphicsData.pop();
  1061. }
  1062. }
  1063. this.currentPath = null;
  1064. const data = new GraphicsData(
  1065. this.lineWidth,
  1066. this.lineColor,
  1067. this.lineAlpha,
  1068. this.fillColor,
  1069. this.fillAlpha,
  1070. this.filling,
  1071. this.nativeLines,
  1072. shape,
  1073. this.lineAlignment
  1074. );
  1075. this.graphicsData.push(data);
  1076. if (data.type === SHAPES.POLY)
  1077. {
  1078. data.shape.closed = data.shape.closed;
  1079. this.currentPath = data;
  1080. }
  1081. this.dirty++;
  1082. return data;
  1083. }
  1084. /**
  1085. * Generates a canvas texture.
  1086. *
  1087. * @param {number} scaleMode - The scale mode of the texture.
  1088. * @param {number} resolution - The resolution of the texture.
  1089. * @return {PIXI.Texture} The new texture.
  1090. */
  1091. generateCanvasTexture(scaleMode, resolution = 1)
  1092. {
  1093. const bounds = this.getLocalBounds();
  1094. const canvasBuffer = RenderTexture.create(bounds.width, bounds.height, scaleMode, resolution);
  1095. if (!canvasRenderer)
  1096. {
  1097. canvasRenderer = new CanvasRenderer();
  1098. }
  1099. this.transform.updateLocalTransform();
  1100. this.transform.localTransform.copy(tempMatrix);
  1101. tempMatrix.invert();
  1102. tempMatrix.tx -= bounds.x;
  1103. tempMatrix.ty -= bounds.y;
  1104. canvasRenderer.render(this, canvasBuffer, true, tempMatrix);
  1105. const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics');
  1106. texture.baseTexture.resolution = resolution;
  1107. texture.baseTexture.update();
  1108. return texture;
  1109. }
  1110. /**
  1111. * Closes the current path.
  1112. *
  1113. * @return {PIXI.Graphics} Returns itself.
  1114. */
  1115. closePath()
  1116. {
  1117. // ok so close path assumes next one is a hole!
  1118. const currentPath = this.currentPath;
  1119. if (currentPath && currentPath.shape)
  1120. {
  1121. currentPath.shape.close();
  1122. }
  1123. return this;
  1124. }
  1125. /**
  1126. * Adds a hole in the current path.
  1127. *
  1128. * @return {PIXI.Graphics} Returns itself.
  1129. */
  1130. addHole()
  1131. {
  1132. // this is a hole!
  1133. const hole = this.graphicsData.pop();
  1134. this.currentPath = this.graphicsData[this.graphicsData.length - 1];
  1135. this.currentPath.addHole(hole.shape);
  1136. this.currentPath = null;
  1137. return this;
  1138. }
  1139. /**
  1140. * Destroys the Graphics object.
  1141. *
  1142. * @param {object|boolean} [options] - Options parameter. A boolean will act as if all
  1143. * options have been set to that value
  1144. * @param {boolean} [options.children=false] - if set to true, all the children will have
  1145. * their destroy method called as well. 'options' will be passed on to those calls.
  1146. * @param {boolean} [options.texture=false] - Only used for child Sprites if options.children is set to true
  1147. * Should it destroy the texture of the child sprite
  1148. * @param {boolean} [options.baseTexture=false] - Only used for child Sprites if options.children is set to true
  1149. * Should it destroy the base texture of the child sprite
  1150. */
  1151. destroy(options)
  1152. {
  1153. super.destroy(options);
  1154. // destroy each of the GraphicsData objects
  1155. for (let i = 0; i < this.graphicsData.length; ++i)
  1156. {
  1157. this.graphicsData[i].destroy();
  1158. }
  1159. // for each webgl data entry, destroy the WebGLGraphicsData
  1160. for (const id in this._webGL)
  1161. {
  1162. for (let j = 0; j < this._webGL[id].data.length; ++j)
  1163. {
  1164. this._webGL[id].data[j].destroy();
  1165. }
  1166. }
  1167. if (this._spriteRect)
  1168. {
  1169. this._spriteRect.destroy();
  1170. }
  1171. this.graphicsData = null;
  1172. this.currentPath = null;
  1173. this._webGL = null;
  1174. this._localBounds = null;
  1175. }
  1176. }
  1177. Graphics._SPRITE_TEXTURE = null;
  1178. /**
  1179. * Graphics curves resolution settings. If `adaptive` flag is set to `true`,
  1180. * the resolution is calculated based on the curve's length to ensure better visual quality.
  1181. * Adaptive draw works with `bezierCurveTo` and `quadraticCurveTo`.
  1182. *
  1183. * @static
  1184. * @constant
  1185. * @memberof PIXI.Graphics
  1186. * @name CURVES
  1187. * @type {object}
  1188. * @property {boolean} adaptive=false - flag indicating if the resolution should be adaptive
  1189. * @property {number} maxLength=10 - maximal length of a single segment of the curve (if adaptive = false, ignored)
  1190. * @property {number} minSegments=8 - minimal number of segments in the curve (if adaptive = false, ignored)
  1191. * @property {number} maxSegments=2048 - maximal number of segments in the curve (if adaptive = false, ignored)
  1192. */
  1193. Graphics.CURVES = {
  1194. adaptive: false,
  1195. maxLength: 10,
  1196. minSegments: 8,
  1197. maxSegments: 2048,
  1198. };