Source: core/display/DisplayObject.js

core/display/DisplayObject.js

  1. import EventEmitter from 'eventemitter3';
  2. import { TRANSFORM_MODE } from '../const';
  3. import settings from '../settings';
  4. import TransformStatic from './TransformStatic';
  5. import Transform from './Transform';
  6. import Bounds from './Bounds';
  7. import { Rectangle } from '../math';
  8. // _tempDisplayObjectParent = new DisplayObject();
  9. /**
  10. * The base class for all objects that are rendered on the screen.
  11. * This is an abstract class and should not be used on its own rather it should be extended.
  12. *
  13. * @class
  14. * @extends EventEmitter
  15. * @memberof PIXI
  16. */
  17. export default class DisplayObject extends EventEmitter
  18. {
  19. /**
  20. *
  21. */
  22. constructor()
  23. {
  24. super();
  25. const TransformClass = settings.TRANSFORM_MODE === TRANSFORM_MODE.STATIC ? TransformStatic : Transform;
  26. this.tempDisplayObjectParent = null;
  27. // TODO: need to create Transform from factory
  28. /**
  29. * World transform and local transform of this object.
  30. * This will become read-only later, please do not assign anything there unless you know what are you doing
  31. *
  32. * @member {PIXI.TransformBase}
  33. */
  34. this.transform = new TransformClass();
  35. /**
  36. * The opacity of the object.
  37. *
  38. * @member {number}
  39. */
  40. this.alpha = 1;
  41. /**
  42. * The visibility of the object. If false the object will not be drawn, and
  43. * the updateTransform function will not be called.
  44. *
  45. * Only affects recursive calls from parent. You can ask for bounds or call updateTransform manually
  46. *
  47. * @member {boolean}
  48. */
  49. this.visible = true;
  50. /**
  51. * Can this object be rendered, if false the object will not be drawn but the updateTransform
  52. * methods will still be called.
  53. *
  54. * Only affects recursive calls from parent. You can ask for bounds manually
  55. *
  56. * @member {boolean}
  57. */
  58. this.renderable = true;
  59. /**
  60. * The display object container that contains this display object.
  61. *
  62. * @member {PIXI.Container}
  63. * @readonly
  64. */
  65. this.parent = null;
  66. /**
  67. * The multiplied alpha of the displayObject
  68. *
  69. * @member {number}
  70. * @readonly
  71. */
  72. this.worldAlpha = 1;
  73. /**
  74. * The area the filter is applied to. This is used as more of an optimisation
  75. * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle
  76. *
  77. * Also works as an interaction mask
  78. *
  79. * @member {PIXI.Rectangle}
  80. */
  81. this.filterArea = null;
  82. this._filters = null;
  83. this._enabledFilters = null;
  84. /**
  85. * The bounds object, this is used to calculate and store the bounds of the displayObject
  86. *
  87. * @member {PIXI.Rectangle}
  88. * @private
  89. */
  90. this._bounds = new Bounds();
  91. this._boundsID = 0;
  92. this._lastBoundsID = -1;
  93. this._boundsRect = null;
  94. this._localBoundsRect = null;
  95. /**
  96. * The original, cached mask of the object
  97. *
  98. * @member {PIXI.Graphics|PIXI.Sprite}
  99. * @private
  100. */
  101. this._mask = null;
  102. /**
  103. * If the object has been destroyed via destroy(). If true, it should not be used.
  104. *
  105. * @member {boolean}
  106. * @private
  107. * @readonly
  108. */
  109. this._destroyed = false;
  110. /**
  111. * Fired when this DisplayObject is added to a Container.
  112. *
  113. * @event PIXI.DisplayObject#added
  114. * @param {PIXI.Container} container - The container added to.
  115. */
  116. /**
  117. * Fired when this DisplayObject is removed from a Container.
  118. *
  119. * @event PIXI.DisplayObject#removed
  120. * @param {PIXI.Container} container - The container removed from.
  121. */
  122. }
  123. /**
  124. * @private
  125. * @member {PIXI.DisplayObject}
  126. */
  127. get _tempDisplayObjectParent()
  128. {
  129. if (this.tempDisplayObjectParent === null)
  130. {
  131. this.tempDisplayObjectParent = new DisplayObject();
  132. }
  133. return this.tempDisplayObjectParent;
  134. }
  135. /**
  136. * Updates the object transform for rendering
  137. *
  138. * TODO - Optimization pass!
  139. */
  140. updateTransform()
  141. {
  142. this.transform.updateTransform(this.parent.transform);
  143. // multiply the alphas..
  144. this.worldAlpha = this.alpha * this.parent.worldAlpha;
  145. this._bounds.updateID++;
  146. }
  147. /**
  148. * recursively updates transform of all objects from the root to this one
  149. * internal function for toLocal()
  150. */
  151. _recursivePostUpdateTransform()
  152. {
  153. if (this.parent)
  154. {
  155. this.parent._recursivePostUpdateTransform();
  156. this.transform.updateTransform(this.parent.transform);
  157. }
  158. else
  159. {
  160. this.transform.updateTransform(this._tempDisplayObjectParent.transform);
  161. }
  162. }
  163. /**
  164. * Retrieves the bounds of the displayObject as a rectangle object.
  165. *
  166. * @param {boolean} skipUpdate - setting to true will stop the transforms of the scene graph from
  167. * being updated. This means the calculation returned MAY be out of date BUT will give you a
  168. * nice performance boost
  169. * @param {PIXI.Rectangle} rect - Optional rectangle to store the result of the bounds calculation
  170. * @return {PIXI.Rectangle} the rectangular bounding area
  171. */
  172. getBounds(skipUpdate, rect)
  173. {
  174. if (!skipUpdate)
  175. {
  176. if (!this.parent)
  177. {
  178. this.parent = this._tempDisplayObjectParent;
  179. this.updateTransform();
  180. this.parent = null;
  181. }
  182. else
  183. {
  184. this._recursivePostUpdateTransform();
  185. this.updateTransform();
  186. }
  187. }
  188. if (this._boundsID !== this._lastBoundsID)
  189. {
  190. this.calculateBounds();
  191. }
  192. if (!rect)
  193. {
  194. if (!this._boundsRect)
  195. {
  196. this._boundsRect = new Rectangle();
  197. }
  198. rect = this._boundsRect;
  199. }
  200. return this._bounds.getRectangle(rect);
  201. }
  202. /**
  203. * Retrieves the local bounds of the displayObject as a rectangle object
  204. *
  205. * @param {PIXI.Rectangle} [rect] - Optional rectangle to store the result of the bounds calculation
  206. * @return {PIXI.Rectangle} the rectangular bounding area
  207. */
  208. getLocalBounds(rect)
  209. {
  210. const transformRef = this.transform;
  211. const parentRef = this.parent;
  212. this.parent = null;
  213. this.transform = this._tempDisplayObjectParent.transform;
  214. if (!rect)
  215. {
  216. if (!this._localBoundsRect)
  217. {
  218. this._localBoundsRect = new Rectangle();
  219. }
  220. rect = this._localBoundsRect;
  221. }
  222. const bounds = this.getBounds(false, rect);
  223. this.parent = parentRef;
  224. this.transform = transformRef;
  225. return bounds;
  226. }
  227. /**
  228. * Calculates the global position of the display object
  229. *
  230. * @param {PIXI.Point} position - The world origin to calculate from
  231. * @param {PIXI.Point} [point] - A Point object in which to store the value, optional
  232. * (otherwise will create a new Point)
  233. * @param {boolean} [skipUpdate=false] - Should we skip the update transform.
  234. * @return {PIXI.Point} A point object representing the position of this object
  235. */
  236. toGlobal(position, point, skipUpdate = false)
  237. {
  238. if (!skipUpdate)
  239. {
  240. this._recursivePostUpdateTransform();
  241. // this parent check is for just in case the item is a root object.
  242. // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly
  243. // this is mainly to avoid a parent check in the main loop. Every little helps for performance :)
  244. if (!this.parent)
  245. {
  246. this.parent = this._tempDisplayObjectParent;
  247. this.displayObjectUpdateTransform();
  248. this.parent = null;
  249. }
  250. else
  251. {
  252. this.displayObjectUpdateTransform();
  253. }
  254. }
  255. // don't need to update the lot
  256. return this.worldTransform.apply(position, point);
  257. }
  258. /**
  259. * Calculates the local position of the display object relative to another point
  260. *
  261. * @param {PIXI.Point} position - The world origin to calculate from
  262. * @param {PIXI.DisplayObject} [from] - The DisplayObject to calculate the global position from
  263. * @param {PIXI.Point} [point] - A Point object in which to store the value, optional
  264. * (otherwise will create a new Point)
  265. * @param {boolean} [skipUpdate=false] - Should we skip the update transform
  266. * @return {PIXI.Point} A point object representing the position of this object
  267. */
  268. toLocal(position, from, point, skipUpdate)
  269. {
  270. if (from)
  271. {
  272. position = from.toGlobal(position, point, skipUpdate);
  273. }
  274. if (!skipUpdate)
  275. {
  276. this._recursivePostUpdateTransform();
  277. // this parent check is for just in case the item is a root object.
  278. // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly
  279. // this is mainly to avoid a parent check in the main loop. Every little helps for performance :)
  280. if (!this.parent)
  281. {
  282. this.parent = this._tempDisplayObjectParent;
  283. this.displayObjectUpdateTransform();
  284. this.parent = null;
  285. }
  286. else
  287. {
  288. this.displayObjectUpdateTransform();
  289. }
  290. }
  291. // simply apply the matrix..
  292. return this.worldTransform.applyInverse(position, point);
  293. }
  294. /**
  295. * Renders the object using the WebGL renderer
  296. *
  297. * @param {PIXI.WebGLRenderer} renderer - The renderer
  298. */
  299. renderWebGL(renderer) // eslint-disable-line no-unused-vars
  300. {
  301. // OVERWRITE;
  302. }
  303. /**
  304. * Renders the object using the Canvas renderer
  305. *
  306. * @param {PIXI.CanvasRenderer} renderer - The renderer
  307. */
  308. renderCanvas(renderer) // eslint-disable-line no-unused-vars
  309. {
  310. // OVERWRITE;
  311. }
  312. /**
  313. * Set the parent Container of this DisplayObject
  314. *
  315. * @param {PIXI.Container} container - The Container to add this DisplayObject to
  316. * @return {PIXI.Container} The Container that this DisplayObject was added to
  317. */
  318. setParent(container)
  319. {
  320. if (!container || !container.addChild)
  321. {
  322. throw new Error('setParent: Argument must be a Container');
  323. }
  324. container.addChild(this);
  325. return container;
  326. }
  327. /**
  328. * Convenience function to set the position, scale, skew and pivot at once.
  329. *
  330. * @param {number} [x=0] - The X position
  331. * @param {number} [y=0] - The Y position
  332. * @param {number} [scaleX=1] - The X scale value
  333. * @param {number} [scaleY=1] - The Y scale value
  334. * @param {number} [rotation=0] - The rotation
  335. * @param {number} [skewX=0] - The X skew value
  336. * @param {number} [skewY=0] - The Y skew value
  337. * @param {number} [pivotX=0] - The X pivot value
  338. * @param {number} [pivotY=0] - The Y pivot value
  339. * @return {PIXI.DisplayObject} The DisplayObject instance
  340. */
  341. setTransform(x = 0, y = 0, scaleX = 1, scaleY = 1, rotation = 0, skewX = 0, skewY = 0, pivotX = 0, pivotY = 0)
  342. {
  343. this.position.x = x;
  344. this.position.y = y;
  345. this.scale.x = !scaleX ? 1 : scaleX;
  346. this.scale.y = !scaleY ? 1 : scaleY;
  347. this.rotation = rotation;
  348. this.skew.x = skewX;
  349. this.skew.y = skewY;
  350. this.pivot.x = pivotX;
  351. this.pivot.y = pivotY;
  352. return this;
  353. }
  354. /**
  355. * Base destroy method for generic display objects. This will automatically
  356. * remove the display object from its parent Container as well as remove
  357. * all current event listeners and internal references. Do not use a DisplayObject
  358. * after calling `destroy`.
  359. *
  360. */
  361. destroy()
  362. {
  363. this.removeAllListeners();
  364. if (this.parent)
  365. {
  366. this.parent.removeChild(this);
  367. }
  368. this.transform = null;
  369. this.parent = null;
  370. this._bounds = null;
  371. this._currentBounds = null;
  372. this._mask = null;
  373. this.filterArea = null;
  374. this.interactive = false;
  375. this.interactiveChildren = false;
  376. this._destroyed = true;
  377. }
  378. /**
  379. * The position of the displayObject on the x axis relative to the local coordinates of the parent.
  380. * An alias to position.x
  381. *
  382. * @member {number}
  383. */
  384. get x()
  385. {
  386. return this.position.x;
  387. }
  388. set x(value) // eslint-disable-line require-jsdoc
  389. {
  390. this.transform.position.x = value;
  391. }
  392. /**
  393. * The position of the displayObject on the y axis relative to the local coordinates of the parent.
  394. * An alias to position.y
  395. *
  396. * @member {number}
  397. */
  398. get y()
  399. {
  400. return this.position.y;
  401. }
  402. set y(value) // eslint-disable-line require-jsdoc
  403. {
  404. this.transform.position.y = value;
  405. }
  406. /**
  407. * Current transform of the object based on world (parent) factors
  408. *
  409. * @member {PIXI.Matrix}
  410. * @readonly
  411. */
  412. get worldTransform()
  413. {
  414. return this.transform.worldTransform;
  415. }
  416. /**
  417. * Current transform of the object based on local factors: position, scale, other stuff
  418. *
  419. * @member {PIXI.Matrix}
  420. * @readonly
  421. */
  422. get localTransform()
  423. {
  424. return this.transform.localTransform;
  425. }
  426. /**
  427. * The coordinate of the object relative to the local coordinates of the parent.
  428. * Assignment by value since pixi-v4.
  429. *
  430. * @member {PIXI.Point|PIXI.ObservablePoint}
  431. */
  432. get position()
  433. {
  434. return this.transform.position;
  435. }
  436. set position(value) // eslint-disable-line require-jsdoc
  437. {
  438. this.transform.position.copy(value);
  439. }
  440. /**
  441. * The scale factor of the object.
  442. * Assignment by value since pixi-v4.
  443. *
  444. * @member {PIXI.Point|PIXI.ObservablePoint}
  445. */
  446. get scale()
  447. {
  448. return this.transform.scale;
  449. }
  450. set scale(value) // eslint-disable-line require-jsdoc
  451. {
  452. this.transform.scale.copy(value);
  453. }
  454. /**
  455. * The pivot point of the displayObject that it rotates around.
  456. * Assignment by value since pixi-v4.
  457. *
  458. * @member {PIXI.Point|PIXI.ObservablePoint}
  459. */
  460. get pivot()
  461. {
  462. return this.transform.pivot;
  463. }
  464. set pivot(value) // eslint-disable-line require-jsdoc
  465. {
  466. this.transform.pivot.copy(value);
  467. }
  468. /**
  469. * The skew factor for the object in radians.
  470. * Assignment by value since pixi-v4.
  471. *
  472. * @member {PIXI.ObservablePoint}
  473. */
  474. get skew()
  475. {
  476. return this.transform.skew;
  477. }
  478. set skew(value) // eslint-disable-line require-jsdoc
  479. {
  480. this.transform.skew.copy(value);
  481. }
  482. /**
  483. * The rotation of the object in radians.
  484. *
  485. * @member {number}
  486. */
  487. get rotation()
  488. {
  489. return this.transform.rotation;
  490. }
  491. set rotation(value) // eslint-disable-line require-jsdoc
  492. {
  493. this.transform.rotation = value;
  494. }
  495. /**
  496. * Indicates if the object is globally visible.
  497. *
  498. * @member {boolean}
  499. * @readonly
  500. */
  501. get worldVisible()
  502. {
  503. let item = this;
  504. do
  505. {
  506. if (!item.visible)
  507. {
  508. return false;
  509. }
  510. item = item.parent;
  511. } while (item);
  512. return true;
  513. }
  514. /**
  515. * Sets a mask for the displayObject. A mask is an object that limits the visibility of an
  516. * object to the shape of the mask applied to it. In PIXI a regular mask must be a
  517. * PIXI.Graphics or a PIXI.Sprite object. This allows for much faster masking in canvas as it
  518. * utilises shape clipping. To remove a mask, set this property to null.
  519. *
  520. * @todo For the moment, PIXI.CanvasRenderer doesn't support PIXI.Sprite as mask.
  521. *
  522. * @member {PIXI.Graphics|PIXI.Sprite}
  523. */
  524. get mask()
  525. {
  526. return this._mask;
  527. }
  528. set mask(value) // eslint-disable-line require-jsdoc
  529. {
  530. if (this._mask)
  531. {
  532. this._mask.renderable = true;
  533. this._mask.isMask = false;
  534. }
  535. this._mask = value;
  536. if (this._mask)
  537. {
  538. this._mask.renderable = false;
  539. this._mask.isMask = true;
  540. }
  541. }
  542. /**
  543. * Sets the filters for the displayObject.
  544. * * IMPORTANT: This is a webGL only feature and will be ignored by the canvas renderer.
  545. * To remove filters simply set this property to 'null'
  546. *
  547. * @member {PIXI.Filter[]}
  548. */
  549. get filters()
  550. {
  551. return this._filters && this._filters.slice();
  552. }
  553. set filters(value) // eslint-disable-line require-jsdoc
  554. {
  555. this._filters = value && value.slice();
  556. }
  557. }
  558. // performance increase to avoid using call.. (10x faster)
  559. DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform;