Source: extras/TilingSprite.js

extras/TilingSprite.js

  1. import * as core from '../core';
  2. import CanvasTinter from '../core/sprites/canvas/CanvasTinter';
  3. const tempPoint = new core.Point();
  4. /**
  5. * A tiling sprite is a fast way of rendering a tiling image
  6. *
  7. * @class
  8. * @extends PIXI.Sprite
  9. * @memberof PIXI.extras
  10. */
  11. export default class TilingSprite extends core.Sprite
  12. {
  13. /**
  14. * @param {PIXI.Texture} texture - the texture of the tiling sprite
  15. * @param {number} [width=100] - the width of the tiling sprite
  16. * @param {number} [height=100] - the height of the tiling sprite
  17. */
  18. constructor(texture, width = 100, height = 100)
  19. {
  20. super(texture);
  21. /**
  22. * Tile transform
  23. *
  24. * @member {PIXI.TransformStatic}
  25. */
  26. this.tileTransform = new core.TransformStatic();
  27. // /// private
  28. /**
  29. * The with of the tiling sprite
  30. *
  31. * @member {number}
  32. * @private
  33. */
  34. this._width = width;
  35. /**
  36. * The height of the tiling sprite
  37. *
  38. * @member {number}
  39. * @private
  40. */
  41. this._height = height;
  42. /**
  43. * Canvas pattern
  44. *
  45. * @type {CanvasPattern}
  46. * @private
  47. */
  48. this._canvasPattern = null;
  49. /**
  50. * transform that is applied to UV to get the texture coords
  51. *
  52. * @member {PIXI.TextureMatrix}
  53. */
  54. this.uvTransform = texture.transform || new core.TextureMatrix(texture);
  55. /**
  56. * Plugin that is responsible for rendering this element.
  57. * Allows to customize the rendering process without overriding '_renderWebGL' method.
  58. *
  59. * @member {string}
  60. * @default 'tilingSprite'
  61. */
  62. this.pluginName = 'tilingSprite';
  63. /**
  64. * Whether or not anchor affects uvs
  65. *
  66. * @member {boolean}
  67. * @default false
  68. */
  69. this.uvRespectAnchor = false;
  70. }
  71. /**
  72. * Changes frame clamping in corresponding textureTransform, shortcut
  73. * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas
  74. *
  75. * @default 0.5
  76. * @member {number}
  77. */
  78. get clampMargin()
  79. {
  80. return this.uvTransform.clampMargin;
  81. }
  82. set clampMargin(value) // eslint-disable-line require-jsdoc
  83. {
  84. this.uvTransform.clampMargin = value;
  85. this.uvTransform.update(true);
  86. }
  87. /**
  88. * The scaling of the image that is being tiled
  89. *
  90. * @member {PIXI.ObservablePoint}
  91. */
  92. get tileScale()
  93. {
  94. return this.tileTransform.scale;
  95. }
  96. set tileScale(value) // eslint-disable-line require-jsdoc
  97. {
  98. this.tileTransform.scale.copy(value);
  99. }
  100. /**
  101. * The offset of the image that is being tiled
  102. *
  103. * @member {PIXI.ObservablePoint}
  104. */
  105. get tilePosition()
  106. {
  107. return this.tileTransform.position;
  108. }
  109. set tilePosition(value) // eslint-disable-line require-jsdoc
  110. {
  111. this.tileTransform.position.copy(value);
  112. }
  113. /**
  114. * @private
  115. */
  116. _onTextureUpdate()
  117. {
  118. if (this.uvTransform)
  119. {
  120. this.uvTransform.texture = this._texture;
  121. }
  122. this.cachedTint = 0xFFFFFF;
  123. }
  124. /**
  125. * Renders the object using the WebGL renderer
  126. *
  127. * @private
  128. * @param {PIXI.WebGLRenderer} renderer - The renderer
  129. */
  130. _renderWebGL(renderer)
  131. {
  132. // tweak our texture temporarily..
  133. const texture = this._texture;
  134. if (!texture || !texture.valid)
  135. {
  136. return;
  137. }
  138. this.tileTransform.updateLocalTransform();
  139. this.uvTransform.update();
  140. renderer.setObjectRenderer(renderer.plugins[this.pluginName]);
  141. renderer.plugins[this.pluginName].render(this);
  142. }
  143. /**
  144. * Renders the object using the Canvas renderer
  145. *
  146. * @private
  147. * @param {PIXI.CanvasRenderer} renderer - a reference to the canvas renderer
  148. */
  149. _renderCanvas(renderer)
  150. {
  151. const texture = this._texture;
  152. if (!texture.baseTexture.hasLoaded)
  153. {
  154. return;
  155. }
  156. const context = renderer.context;
  157. const transform = this.worldTransform;
  158. const resolution = renderer.resolution;
  159. const isTextureRotated = texture.rotate === 2;
  160. const baseTexture = texture.baseTexture;
  161. const baseTextureResolution = baseTexture.resolution;
  162. const modX = ((this.tilePosition.x / this.tileScale.x) % texture.orig.width) * baseTextureResolution;
  163. const modY = ((this.tilePosition.y / this.tileScale.y) % texture.orig.height) * baseTextureResolution;
  164. // create a nice shiny pattern!
  165. if (this._textureID !== this._texture._updateID || this.cachedTint !== this.tint)
  166. {
  167. this._textureID = this._texture._updateID;
  168. // cut an object from a spritesheet..
  169. const tempCanvas = new core.CanvasRenderTarget(texture.orig.width,
  170. texture.orig.height,
  171. baseTextureResolution);
  172. // Tint the tiling sprite
  173. if (this.tint !== 0xFFFFFF)
  174. {
  175. this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint);
  176. tempCanvas.context.drawImage(this.tintedTexture, 0, 0);
  177. }
  178. else
  179. {
  180. const sx = texture._frame.x * baseTextureResolution;
  181. const sy = texture._frame.y * baseTextureResolution;
  182. const sWidth = texture._frame.width * baseTextureResolution;
  183. const sHeight = texture._frame.height * baseTextureResolution;
  184. const dWidth = (texture.trim ? texture.trim.width : texture.orig.width) * baseTextureResolution;
  185. const dHeight = (texture.trim ? texture.trim.height : texture.orig.height) * baseTextureResolution;
  186. const dx = (texture.trim ? texture.trim.x : 0) * baseTextureResolution;
  187. const dy = (texture.trim ? texture.trim.y : 0) * baseTextureResolution;
  188. if (isTextureRotated)
  189. {
  190. // Apply rotation and transform
  191. tempCanvas.context.rotate(-Math.PI / 2);
  192. tempCanvas.context.translate(-dHeight, 0);
  193. tempCanvas.context.drawImage(baseTexture.source,
  194. sx, sy,
  195. sWidth, sHeight,
  196. -dy, dx,
  197. dHeight, dWidth);
  198. }
  199. else
  200. {
  201. tempCanvas.context.drawImage(baseTexture.source,
  202. sx, sy,
  203. sWidth, sHeight,
  204. dx, dy,
  205. dWidth, dHeight);
  206. }
  207. }
  208. this.cachedTint = this.tint;
  209. this._canvasPattern = tempCanvas.context.createPattern(tempCanvas.canvas, 'repeat');
  210. }
  211. // set context state..
  212. context.globalAlpha = this.worldAlpha;
  213. context.setTransform(transform.a * resolution,
  214. transform.b * resolution,
  215. transform.c * resolution,
  216. transform.d * resolution,
  217. transform.tx * resolution,
  218. transform.ty * resolution);
  219. renderer.setBlendMode(this.blendMode);
  220. // fill the pattern!
  221. context.fillStyle = this._canvasPattern;
  222. // TODO - this should be rolled into the setTransform above..
  223. context.scale(this.tileScale.x / baseTextureResolution, this.tileScale.y / baseTextureResolution);
  224. const anchorX = this.anchor.x * -this._width * baseTextureResolution;
  225. const anchorY = this.anchor.y * -this._height * baseTextureResolution;
  226. if (this.uvRespectAnchor)
  227. {
  228. context.translate(modX, modY);
  229. context.fillRect(-modX + anchorX, -modY + anchorY,
  230. this._width / this.tileScale.x * baseTextureResolution,
  231. this._height / this.tileScale.y * baseTextureResolution);
  232. }
  233. else
  234. {
  235. context.translate(modX + anchorX, modY + anchorY);
  236. context.fillRect(-modX, -modY,
  237. this._width / this.tileScale.x * baseTextureResolution,
  238. this._height / this.tileScale.y * baseTextureResolution);
  239. }
  240. }
  241. /**
  242. * Updates the bounds of the tiling sprite.
  243. *
  244. * @private
  245. */
  246. _calculateBounds()
  247. {
  248. const minX = this._width * -this._anchor._x;
  249. const minY = this._height * -this._anchor._y;
  250. const maxX = this._width * (1 - this._anchor._x);
  251. const maxY = this._height * (1 - this._anchor._y);
  252. this._bounds.addFrame(this.transform, minX, minY, maxX, maxY);
  253. }
  254. /**
  255. * Gets the local bounds of the sprite object.
  256. *
  257. * @param {PIXI.Rectangle} rect - The output rectangle.
  258. * @return {PIXI.Rectangle} The bounds.
  259. */
  260. getLocalBounds(rect)
  261. {
  262. // we can do a fast local bounds if the sprite has no children!
  263. if (this.children.length === 0)
  264. {
  265. this._bounds.minX = this._width * -this._anchor._x;
  266. this._bounds.minY = this._height * -this._anchor._y;
  267. this._bounds.maxX = this._width * (1 - this._anchor._x);
  268. this._bounds.maxY = this._height * (1 - this._anchor._y);
  269. if (!rect)
  270. {
  271. if (!this._localBoundsRect)
  272. {
  273. this._localBoundsRect = new core.Rectangle();
  274. }
  275. rect = this._localBoundsRect;
  276. }
  277. return this._bounds.getRectangle(rect);
  278. }
  279. return super.getLocalBounds.call(this, rect);
  280. }
  281. /**
  282. * Checks if a point is inside this tiling sprite.
  283. *
  284. * @param {PIXI.Point} point - the point to check
  285. * @return {boolean} Whether or not the sprite contains the point.
  286. */
  287. containsPoint(point)
  288. {
  289. this.worldTransform.applyInverse(point, tempPoint);
  290. const width = this._width;
  291. const height = this._height;
  292. const x1 = -width * this.anchor._x;
  293. if (tempPoint.x >= x1 && tempPoint.x < x1 + width)
  294. {
  295. const y1 = -height * this.anchor._y;
  296. if (tempPoint.y >= y1 && tempPoint.y < y1 + height)
  297. {
  298. return true;
  299. }
  300. }
  301. return false;
  302. }
  303. /**
  304. * Destroys this sprite and optionally its texture and children
  305. *
  306. * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options
  307. * have been set to that value
  308. * @param {boolean} [options.children=false] - if set to true, all the children will have their destroy
  309. * method called as well. 'options' will be passed on to those calls.
  310. * @param {boolean} [options.texture=false] - Should it destroy the current texture of the sprite as well
  311. * @param {boolean} [options.baseTexture=false] - Should it destroy the base texture of the sprite as well
  312. */
  313. destroy(options)
  314. {
  315. super.destroy(options);
  316. this.tileTransform = null;
  317. this.uvTransform = null;
  318. }
  319. /**
  320. * Helper function that creates a new tiling sprite based on the source you provide.
  321. * The source can be - frame id, image url, video url, canvas element, video element, base texture
  322. *
  323. * @static
  324. * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source - Source to create texture from
  325. * @param {number} width - the width of the tiling sprite
  326. * @param {number} height - the height of the tiling sprite
  327. * @return {PIXI.Texture} The newly created texture
  328. */
  329. static from(source, width, height)
  330. {
  331. return new TilingSprite(core.Texture.from(source), width, height);
  332. }
  333. /**
  334. * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId
  335. * The frame ids are created when a Texture packer file has been loaded
  336. *
  337. * @static
  338. * @param {string} frameId - The frame Id of the texture in the cache
  339. * @param {number} width - the width of the tiling sprite
  340. * @param {number} height - the height of the tiling sprite
  341. * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId
  342. */
  343. static fromFrame(frameId, width, height)
  344. {
  345. const texture = core.utils.TextureCache[frameId];
  346. if (!texture)
  347. {
  348. throw new Error(`The frameId "${frameId}" does not exist in the texture cache ${this}`);
  349. }
  350. return new TilingSprite(texture, width, height);
  351. }
  352. /**
  353. * Helper function that creates a sprite that will contain a texture based on an image url
  354. * If the image is not in the texture cache it will be loaded
  355. *
  356. * @static
  357. * @param {string} imageId - The image url of the texture
  358. * @param {number} width - the width of the tiling sprite
  359. * @param {number} height - the height of the tiling sprite
  360. * @param {boolean} [crossorigin] - if you want to specify the cross-origin parameter
  361. * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - if you want to specify the scale mode,
  362. * see {@link PIXI.SCALE_MODES} for possible values
  363. * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id
  364. */
  365. static fromImage(imageId, width, height, crossorigin, scaleMode)
  366. {
  367. return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode), width, height);
  368. }
  369. /**
  370. * The width of the sprite, setting this will actually modify the scale to achieve the value set
  371. *
  372. * @member {number}
  373. */
  374. get width()
  375. {
  376. return this._width;
  377. }
  378. set width(value) // eslint-disable-line require-jsdoc
  379. {
  380. this._width = value;
  381. }
  382. /**
  383. * The height of the TilingSprite, setting this will actually modify the scale to achieve the value set
  384. *
  385. * @member {number}
  386. */
  387. get height()
  388. {
  389. return this._height;
  390. }
  391. set height(value) // eslint-disable-line require-jsdoc
  392. {
  393. this._height = value;
  394. }
  395. }