@lightningjs/renderer 2.9.0-beta1 → 2.9.0-beta3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/COPYING +1 -0
  2. package/LICENSE +202 -202
  3. package/NOTICE +3 -3
  4. package/README.md +147 -147
  5. package/dist/src/core/CoreNode.d.ts +2 -1
  6. package/dist/src/core/CoreNode.js +54 -43
  7. package/dist/src/core/CoreNode.js.map +1 -1
  8. package/dist/src/core/CoreTextureManager.d.ts +61 -15
  9. package/dist/src/core/CoreTextureManager.js +179 -104
  10. package/dist/src/core/CoreTextureManager.js.map +1 -1
  11. package/dist/src/core/Stage.d.ts +7 -0
  12. package/dist/src/core/Stage.js +33 -1
  13. package/dist/src/core/Stage.js.map +1 -1
  14. package/dist/src/core/TextureMemoryManager.js +2 -5
  15. package/dist/src/core/TextureMemoryManager.js.map +1 -1
  16. package/dist/src/core/lib/ImageWorker.js +31 -28
  17. package/dist/src/core/lib/ImageWorker.js.map +1 -1
  18. package/dist/src/core/lib/WebGlContextWrapper.d.ts +1 -0
  19. package/dist/src/core/lib/WebGlContextWrapper.js +2 -0
  20. package/dist/src/core/lib/WebGlContextWrapper.js.map +1 -1
  21. package/dist/src/core/renderers/CoreContextTexture.d.ts +1 -0
  22. package/dist/src/core/renderers/CoreContextTexture.js +1 -0
  23. package/dist/src/core/renderers/CoreContextTexture.js.map +1 -1
  24. package/dist/src/core/renderers/canvas/CanvasCoreRenderer.d.ts +1 -1
  25. package/dist/src/core/renderers/canvas/CanvasCoreRenderer.js +20 -7
  26. package/dist/src/core/renderers/canvas/CanvasCoreRenderer.js.map +1 -1
  27. package/dist/src/core/renderers/canvas/CanvasCoreTexture.d.ts +3 -3
  28. package/dist/src/core/renderers/canvas/CanvasCoreTexture.js +8 -10
  29. package/dist/src/core/renderers/canvas/CanvasCoreTexture.js.map +1 -1
  30. package/dist/src/core/renderers/webgl/WebGlCoreCtxSubTexture.js +3 -1
  31. package/dist/src/core/renderers/webgl/WebGlCoreCtxSubTexture.js.map +1 -1
  32. package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.d.ts +1 -2
  33. package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.js +36 -35
  34. package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.js.map +1 -1
  35. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.d.ts +0 -1
  36. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js +7 -17
  37. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js.map +1 -1
  38. package/dist/src/core/renderers/webgl/internal/RendererUtils.js +5 -3
  39. package/dist/src/core/renderers/webgl/internal/RendererUtils.js.map +1 -1
  40. package/dist/src/core/renderers/webgl/shaders/DefaultShader.js +45 -45
  41. package/dist/src/core/renderers/webgl/shaders/DefaultShaderBatched.js +61 -61
  42. package/dist/src/core/renderers/webgl/shaders/DynamicShader.js +93 -93
  43. package/dist/src/core/renderers/webgl/shaders/RoundedRectangle.js +63 -63
  44. package/dist/src/core/renderers/webgl/shaders/SdfShader.js +62 -62
  45. package/dist/src/core/renderers/webgl/shaders/effects/BorderBottomEffect.js +15 -15
  46. package/dist/src/core/renderers/webgl/shaders/effects/BorderEffect.js +6 -6
  47. package/dist/src/core/renderers/webgl/shaders/effects/BorderLeftEffect.js +15 -15
  48. package/dist/src/core/renderers/webgl/shaders/effects/BorderRightEffect.js +15 -15
  49. package/dist/src/core/renderers/webgl/shaders/effects/BorderTopEffect.js +15 -15
  50. package/dist/src/core/renderers/webgl/shaders/effects/FadeOutEffect.js +42 -42
  51. package/dist/src/core/renderers/webgl/shaders/effects/GlitchEffect.js +44 -44
  52. package/dist/src/core/renderers/webgl/shaders/effects/GrayscaleEffect.js +3 -3
  53. package/dist/src/core/renderers/webgl/shaders/effects/HolePunchEffect.js +22 -22
  54. package/dist/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.js +28 -28
  55. package/dist/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.js +10 -10
  56. package/dist/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.js +37 -37
  57. package/dist/src/core/renderers/webgl/shaders/effects/RadiusEffect.js +19 -19
  58. package/dist/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.js +3 -5
  59. package/dist/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.js.map +1 -1
  60. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js +3 -3
  61. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js.map +1 -1
  62. package/dist/src/core/textures/ColorTexture.d.ts +1 -1
  63. package/dist/src/core/textures/ColorTexture.js +2 -1
  64. package/dist/src/core/textures/ColorTexture.js.map +1 -1
  65. package/dist/src/core/textures/ImageTexture.d.ts +8 -1
  66. package/dist/src/core/textures/ImageTexture.js +42 -1
  67. package/dist/src/core/textures/ImageTexture.js.map +1 -1
  68. package/dist/src/core/textures/NoiseTexture.d.ts +1 -1
  69. package/dist/src/core/textures/NoiseTexture.js +2 -1
  70. package/dist/src/core/textures/NoiseTexture.js.map +1 -1
  71. package/dist/src/core/textures/RenderTexture.d.ts +1 -1
  72. package/dist/src/core/textures/RenderTexture.js +2 -1
  73. package/dist/src/core/textures/RenderTexture.js.map +1 -1
  74. package/dist/src/core/textures/SubTexture.d.ts +1 -1
  75. package/dist/src/core/textures/SubTexture.js +21 -4
  76. package/dist/src/core/textures/SubTexture.js.map +1 -1
  77. package/dist/src/core/textures/Texture.d.ts +43 -21
  78. package/dist/src/core/textures/Texture.js +105 -33
  79. package/dist/src/core/textures/Texture.js.map +1 -1
  80. package/dist/src/main-api/Inspector.js +1 -1
  81. package/dist/src/main-api/Inspector.js.map +1 -1
  82. package/dist/src/main-api/Renderer.d.ts +18 -0
  83. package/dist/src/main-api/Renderer.js +6 -4
  84. package/dist/src/main-api/Renderer.js.map +1 -1
  85. package/dist/tsconfig.dist.tsbuildinfo +1 -1
  86. package/exports/canvas.ts +39 -39
  87. package/exports/index.ts +89 -89
  88. package/exports/inspector.ts +24 -24
  89. package/exports/utils.ts +44 -44
  90. package/exports/webgl.ts +38 -38
  91. package/package.json +1 -1
  92. package/scripts/please-use-pnpm.js +13 -13
  93. package/src/common/CommonTypes.ts +139 -139
  94. package/src/common/EventEmitter.ts +77 -77
  95. package/src/common/IAnimationController.ts +92 -92
  96. package/src/common/IEventEmitter.ts +28 -28
  97. package/src/core/CoreNode.test.ts +95 -95
  98. package/src/core/CoreNode.ts +2282 -2258
  99. package/src/core/CoreShaderManager.ts +292 -292
  100. package/src/core/CoreTextNode.ts +450 -450
  101. package/src/core/CoreTextureManager.ts +522 -432
  102. package/src/core/Stage.ts +699 -652
  103. package/src/core/TextureMemoryManager.ts +277 -279
  104. package/src/core/animations/AnimationManager.ts +38 -38
  105. package/src/core/animations/CoreAnimation.ts +340 -340
  106. package/src/core/animations/CoreAnimationController.ts +157 -157
  107. package/src/core/lib/ContextSpy.ts +41 -41
  108. package/src/core/lib/ImageWorker.ts +270 -267
  109. package/src/core/lib/Matrix3d.ts +244 -244
  110. package/src/core/lib/RenderCoords.ts +86 -86
  111. package/src/core/lib/WebGlContextWrapper.ts +1322 -1320
  112. package/src/core/lib/textureCompression.ts +152 -152
  113. package/src/core/lib/textureSvg.ts +78 -78
  114. package/src/core/lib/utils.ts +306 -306
  115. package/src/core/platform.ts +61 -61
  116. package/src/core/renderers/CoreContextTexture.ts +43 -42
  117. package/src/core/renderers/CoreRenderOp.ts +22 -22
  118. package/src/core/renderers/CoreRenderer.ts +114 -114
  119. package/src/core/renderers/CoreShader.ts +41 -41
  120. package/src/core/renderers/canvas/CanvasCoreRenderer.ts +364 -349
  121. package/src/core/renderers/canvas/CanvasCoreTexture.ts +150 -143
  122. package/src/core/renderers/canvas/internal/C2DShaderUtils.ts +231 -231
  123. package/src/core/renderers/canvas/internal/ColorUtils.ts +69 -69
  124. package/src/core/renderers/canvas/shaders/UnsupportedShader.ts +48 -48
  125. package/src/core/renderers/webgl/WebGlCoreCtxRenderTexture.ts +79 -79
  126. package/src/core/renderers/webgl/WebGlCoreCtxSubTexture.ts +50 -48
  127. package/src/core/renderers/webgl/WebGlCoreCtxTexture.ts +298 -297
  128. package/src/core/renderers/webgl/WebGlCoreRenderOp.ts +125 -125
  129. package/src/core/renderers/webgl/WebGlCoreRenderer.ts +805 -817
  130. package/src/core/renderers/webgl/WebGlCoreShader.ts +362 -362
  131. package/src/core/renderers/webgl/internal/BufferCollection.ts +54 -54
  132. package/src/core/renderers/webgl/internal/RendererUtils.ts +5 -3
  133. package/src/core/renderers/webgl/internal/ShaderUtils.ts +143 -143
  134. package/src/core/renderers/webgl/internal/WebGlUtils.ts +35 -35
  135. package/src/core/renderers/webgl/shaders/DefaultShader.ts +93 -93
  136. package/src/core/renderers/webgl/shaders/DefaultShaderBatched.ts +132 -132
  137. package/src/core/renderers/webgl/shaders/DynamicShader.ts +580 -580
  138. package/src/core/renderers/webgl/shaders/RoundedRectangle.ts +167 -167
  139. package/src/core/renderers/webgl/shaders/SdfShader.ts +204 -204
  140. package/src/core/renderers/webgl/shaders/effects/BorderBottomEffect.ts +101 -101
  141. package/src/core/renderers/webgl/shaders/effects/BorderEffect.ts +87 -87
  142. package/src/core/renderers/webgl/shaders/effects/BorderLeftEffect.ts +101 -101
  143. package/src/core/renderers/webgl/shaders/effects/BorderRightEffect.ts +101 -101
  144. package/src/core/renderers/webgl/shaders/effects/BorderTopEffect.ts +101 -101
  145. package/src/core/renderers/webgl/shaders/effects/EffectUtils.ts +159 -159
  146. package/src/core/renderers/webgl/shaders/effects/FadeOutEffect.ts +127 -127
  147. package/src/core/renderers/webgl/shaders/effects/GlitchEffect.ts +148 -148
  148. package/src/core/renderers/webgl/shaders/effects/GrayscaleEffect.ts +67 -67
  149. package/src/core/renderers/webgl/shaders/effects/HolePunchEffect.ts +157 -157
  150. package/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.ts +171 -171
  151. package/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.ts +168 -168
  152. package/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.ts +187 -187
  153. package/src/core/renderers/webgl/shaders/effects/RadiusEffect.ts +110 -110
  154. package/src/core/renderers/webgl/shaders/effects/ShaderEffect.ts +196 -196
  155. package/src/core/text-rendering/TextRenderingUtils.ts +36 -36
  156. package/src/core/text-rendering/TextTextureRendererUtils.ts +263 -263
  157. package/src/core/text-rendering/TrFontManager.ts +183 -183
  158. package/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.ts +169 -171
  159. package/src/core/text-rendering/font-face-types/SdfTrFontFace/internal/FontShaper.ts +139 -139
  160. package/src/core/text-rendering/font-face-types/SdfTrFontFace/internal/SdfFontShaper.test.ts +173 -173
  161. package/src/core/text-rendering/font-face-types/SdfTrFontFace/internal/SdfFontShaper.ts +171 -171
  162. package/src/core/text-rendering/font-face-types/TrFontFace.ts +187 -187
  163. package/src/core/text-rendering/font-face-types/WebTrFontFace.ts +89 -89
  164. package/src/core/text-rendering/renderers/CanvasTextRenderer.ts +509 -508
  165. package/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts +798 -798
  166. package/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.ts +853 -853
  167. package/src/core/text-rendering/renderers/SdfTextRenderer/internal/PeekableGenerator.test.ts +48 -48
  168. package/src/core/text-rendering/renderers/SdfTextRenderer/internal/PeekableGenerator.ts +66 -66
  169. package/src/core/text-rendering/renderers/SdfTextRenderer/internal/SpecialCodepoints.ts +52 -52
  170. package/src/core/text-rendering/renderers/SdfTextRenderer/internal/constants.ts +32 -32
  171. package/src/core/text-rendering/renderers/SdfTextRenderer/internal/getStartConditions.ts +117 -117
  172. package/src/core/text-rendering/renderers/SdfTextRenderer/internal/getUnicodeCodepoints.test.ts +133 -133
  173. package/src/core/text-rendering/renderers/SdfTextRenderer/internal/getUnicodeCodepoints.ts +38 -38
  174. package/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts +403 -403
  175. package/src/core/text-rendering/renderers/SdfTextRenderer/internal/measureText.test.ts +49 -49
  176. package/src/core/text-rendering/renderers/SdfTextRenderer/internal/measureText.ts +52 -52
  177. package/src/core/text-rendering/renderers/SdfTextRenderer/internal/setRenderWindow.test.ts +205 -205
  178. package/src/core/text-rendering/renderers/SdfTextRenderer/internal/setRenderWindow.ts +93 -93
  179. package/src/core/text-rendering/renderers/SdfTextRenderer/internal/util.ts +40 -40
  180. package/src/core/text-rendering/renderers/TextRenderer.ts +557 -557
  181. package/src/core/textures/ColorTexture.ts +102 -100
  182. package/src/core/textures/ImageTexture.ts +378 -333
  183. package/src/core/textures/NoiseTexture.ts +104 -101
  184. package/src/core/textures/RenderTexture.ts +85 -83
  185. package/src/core/textures/SubTexture.ts +171 -146
  186. package/src/core/textures/Texture.ts +407 -318
  187. package/src/core/utils.ts +227 -227
  188. package/src/env.d.ts +7 -7
  189. package/src/main-api/DynamicShaderController.ts +104 -104
  190. package/src/main-api/INode.ts +101 -101
  191. package/src/main-api/Inspector.ts +505 -505
  192. package/src/main-api/Renderer.ts +693 -670
  193. package/src/main-api/ShaderController.ts +80 -80
  194. package/src/main-api/utils.ts +45 -45
  195. package/src/utils.ts +248 -248
  196. package/dist/exports/core-api.d.ts +0 -74
  197. package/dist/exports/core-api.js +0 -96
  198. package/dist/exports/core-api.js.map +0 -1
  199. package/dist/exports/main-api.d.ts +0 -30
  200. package/dist/exports/main-api.js +0 -45
  201. package/dist/exports/main-api.js.map +0 -1
  202. package/dist/src/core/CoreExtension.d.ts +0 -12
  203. package/dist/src/core/CoreExtension.js +0 -29
  204. package/dist/src/core/CoreExtension.js.map +0 -1
  205. package/dist/src/main-api/ICoreDriver.d.ts +0 -24
  206. package/dist/src/main-api/ICoreDriver.js +0 -20
  207. package/dist/src/main-api/ICoreDriver.js.map +0 -1
  208. package/dist/src/main-api/RendererMain.d.ts +0 -378
  209. package/dist/src/main-api/RendererMain.js +0 -367
  210. package/dist/src/main-api/RendererMain.js.map +0 -1
  211. package/dist/src/main-api/texture-usage-trackers/FinalizationRegistryTextureUsageTracker.d.ts +0 -9
  212. package/dist/src/main-api/texture-usage-trackers/FinalizationRegistryTextureUsageTracker.js +0 -38
  213. package/dist/src/main-api/texture-usage-trackers/FinalizationRegistryTextureUsageTracker.js.map +0 -1
  214. package/dist/src/main-api/texture-usage-trackers/ManualCountTextureUsageTracker.d.ts +0 -56
  215. package/dist/src/main-api/texture-usage-trackers/ManualCountTextureUsageTracker.js +0 -101
  216. package/dist/src/main-api/texture-usage-trackers/ManualCountTextureUsageTracker.js.map +0 -1
  217. package/dist/src/main-api/texture-usage-trackers/TextureUsageTracker.d.ts +0 -32
  218. package/dist/src/main-api/texture-usage-trackers/TextureUsageTracker.js +0 -28
  219. package/dist/src/main-api/texture-usage-trackers/TextureUsageTracker.js.map +0 -1
  220. package/dist/src/render-drivers/main/MainCoreDriver.d.ts +0 -21
  221. package/dist/src/render-drivers/main/MainCoreDriver.js +0 -115
  222. package/dist/src/render-drivers/main/MainCoreDriver.js.map +0 -1
  223. package/dist/src/render-drivers/main/MainOnlyNode.d.ts +0 -101
  224. package/dist/src/render-drivers/main/MainOnlyNode.js +0 -425
  225. package/dist/src/render-drivers/main/MainOnlyNode.js.map +0 -1
  226. package/dist/src/render-drivers/main/MainOnlyTextNode.d.ts +0 -47
  227. package/dist/src/render-drivers/main/MainOnlyTextNode.js +0 -204
  228. package/dist/src/render-drivers/main/MainOnlyTextNode.js.map +0 -1
  229. package/dist/src/render-drivers/threadx/NodeStruct.d.ts +0 -93
  230. package/dist/src/render-drivers/threadx/NodeStruct.js +0 -290
  231. package/dist/src/render-drivers/threadx/NodeStruct.js.map +0 -1
  232. package/dist/src/render-drivers/threadx/SharedNode.d.ts +0 -40
  233. package/dist/src/render-drivers/threadx/SharedNode.js +0 -61
  234. package/dist/src/render-drivers/threadx/SharedNode.js.map +0 -1
  235. package/dist/src/render-drivers/threadx/TextNodeStruct.d.ts +0 -44
  236. package/dist/src/render-drivers/threadx/TextNodeStruct.js +0 -203
  237. package/dist/src/render-drivers/threadx/TextNodeStruct.js.map +0 -1
  238. package/dist/src/render-drivers/threadx/ThreadXCoreDriver.d.ts +0 -25
  239. package/dist/src/render-drivers/threadx/ThreadXCoreDriver.js +0 -232
  240. package/dist/src/render-drivers/threadx/ThreadXCoreDriver.js.map +0 -1
  241. package/dist/src/render-drivers/threadx/ThreadXMainAnimationController.d.ts +0 -24
  242. package/dist/src/render-drivers/threadx/ThreadXMainAnimationController.js +0 -113
  243. package/dist/src/render-drivers/threadx/ThreadXMainAnimationController.js.map +0 -1
  244. package/dist/src/render-drivers/threadx/ThreadXMainNode.d.ts +0 -46
  245. package/dist/src/render-drivers/threadx/ThreadXMainNode.js +0 -160
  246. package/dist/src/render-drivers/threadx/ThreadXMainNode.js.map +0 -1
  247. package/dist/src/render-drivers/threadx/ThreadXMainTextNode.d.ts +0 -28
  248. package/dist/src/render-drivers/threadx/ThreadXMainTextNode.js +0 -55
  249. package/dist/src/render-drivers/threadx/ThreadXMainTextNode.js.map +0 -1
  250. package/dist/src/render-drivers/threadx/ThreadXRendererMessage.d.ts +0 -70
  251. package/dist/src/render-drivers/threadx/ThreadXRendererMessage.js +0 -32
  252. package/dist/src/render-drivers/threadx/ThreadXRendererMessage.js.map +0 -1
  253. package/dist/src/render-drivers/threadx/worker/ThreadXRendererNode.d.ts +0 -19
  254. package/dist/src/render-drivers/threadx/worker/ThreadXRendererNode.js +0 -184
  255. package/dist/src/render-drivers/threadx/worker/ThreadXRendererNode.js.map +0 -1
  256. package/dist/src/render-drivers/threadx/worker/ThreadXRendererTextNode.d.ts +0 -27
  257. package/dist/src/render-drivers/threadx/worker/ThreadXRendererTextNode.js +0 -109
  258. package/dist/src/render-drivers/threadx/worker/ThreadXRendererTextNode.js.map +0 -1
  259. package/dist/src/render-drivers/threadx/worker/renderer.d.ts +0 -1
  260. package/dist/src/render-drivers/threadx/worker/renderer.js +0 -147
  261. package/dist/src/render-drivers/threadx/worker/renderer.js.map +0 -1
  262. package/dist/src/render-drivers/utils.d.ts +0 -12
  263. package/dist/src/render-drivers/utils.js +0 -74
  264. package/dist/src/render-drivers/utils.js.map +0 -1
  265. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -1,2258 +1,2282 @@
1
- /*
2
- * If not stated otherwise in this file or this component's LICENSE file the
3
- * following copyright and licenses apply:
4
- *
5
- * Copyright 2023 Comcast Cable Communications Management, LLC.
6
- *
7
- * Licensed under the Apache License, Version 2.0 (the License);
8
- * you may not use this file except in compliance with the License.
9
- * You may obtain a copy of the License at
10
- *
11
- * http://www.apache.org/licenses/LICENSE-2.0
12
- *
13
- * Unless required by applicable law or agreed to in writing, software
14
- * distributed under the License is distributed on an "AS IS" BASIS,
15
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
- * See the License for the specific language governing permissions and
17
- * limitations under the License.
18
- */
19
-
20
- import {
21
- assertTruthy,
22
- getNewId,
23
- mergeColorAlphaPremultiplied,
24
- } from '../utils.js';
25
- import type { TextureOptions } from './CoreTextureManager.js';
26
- import type { CoreRenderer } from './renderers/CoreRenderer.js';
27
- import type { Stage } from './Stage.js';
28
- import type {
29
- Texture,
30
- TextureFailedEventHandler,
31
- TextureFreedEventHandler,
32
- TextureLoadedEventHandler,
33
- } from './textures/Texture.js';
34
- import type {
35
- Dimensions,
36
- NodeTextureFailedPayload,
37
- NodeTextureFreedPayload,
38
- NodeTextureLoadedPayload,
39
- } from '../common/CommonTypes.js';
40
- import { EventEmitter } from '../common/EventEmitter.js';
41
- import {
42
- copyRect,
43
- intersectRect,
44
- type Bound,
45
- type RectWithValid,
46
- createBound,
47
- boundInsideBound,
48
- boundLargeThanBound,
49
- createPreloadBounds,
50
- } from './lib/utils.js';
51
- import { Matrix3d } from './lib/Matrix3d.js';
52
- import { RenderCoords } from './lib/RenderCoords.js';
53
- import type { AnimationSettings } from './animations/CoreAnimation.js';
54
- import type { IAnimationController } from '../common/IAnimationController.js';
55
- import { CoreAnimation } from './animations/CoreAnimation.js';
56
- import { CoreAnimationController } from './animations/CoreAnimationController.js';
57
- import type { BaseShaderController } from '../main-api/ShaderController.js';
58
-
59
- export enum CoreNodeRenderState {
60
- Init = 0,
61
- OutOfBounds = 2,
62
- InBounds = 4,
63
- InViewport = 8,
64
- }
65
-
66
- const CoreNodeRenderStateMap: Map<CoreNodeRenderState, string> = new Map();
67
- CoreNodeRenderStateMap.set(CoreNodeRenderState.Init, 'init');
68
- CoreNodeRenderStateMap.set(CoreNodeRenderState.OutOfBounds, 'outOfBounds');
69
- CoreNodeRenderStateMap.set(CoreNodeRenderState.InBounds, 'inBounds');
70
- CoreNodeRenderStateMap.set(CoreNodeRenderState.InViewport, 'inViewport');
71
-
72
- export enum UpdateType {
73
- /**
74
- * Child updates
75
- */
76
- Children = 1,
77
-
78
- /**
79
- * Scale/Rotate transform update
80
- *
81
- * @remarks
82
- * CoreNode Properties Updated:
83
- * - `scaleRotateTransform`
84
- */
85
- ScaleRotate = 2,
86
-
87
- /**
88
- * Translate transform update (x/y/width/height/pivot/mount)
89
- *
90
- * @remarks
91
- * CoreNode Properties Updated:
92
- * - `localTransform`
93
- */
94
- Local = 4,
95
-
96
- /**
97
- * Global Transform update
98
- *
99
- * @remarks
100
- * CoreNode Properties Updated:
101
- * - `globalTransform`
102
- * - `renderCoords`
103
- * - `renderBound`
104
- */
105
- Global = 8,
106
-
107
- /**
108
- * Clipping rect update
109
- *
110
- * @remarks
111
- * CoreNode Properties Updated:
112
- * - `clippingRect`
113
- */
114
- Clipping = 16,
115
-
116
- /**
117
- * Calculated ZIndex update
118
- *
119
- * @remarks
120
- * CoreNode Properties Updated:
121
- * - `calcZIndex`
122
- */
123
- CalculatedZIndex = 32,
124
-
125
- /**
126
- * Z-Index Sorted Children update
127
- *
128
- * @remarks
129
- * CoreNode Properties Updated:
130
- * - `children` (sorts children by their `calcZIndex`)
131
- */
132
- ZIndexSortedChildren = 64,
133
-
134
- /**
135
- * Premultiplied Colors update
136
- *
137
- * @remarks
138
- * CoreNode Properties Updated:
139
- * - `premultipliedColorTl`
140
- * - `premultipliedColorTr`
141
- * - `premultipliedColorBl`
142
- * - `premultipliedColorBr`
143
- */
144
- PremultipliedColors = 128,
145
-
146
- /**
147
- * World Alpha update
148
- *
149
- * @remarks
150
- * CoreNode Properties Updated:
151
- * - `worldAlpha` = `parent.worldAlpha` * `alpha`
152
- */
153
- WorldAlpha = 256,
154
-
155
- /**
156
- * Render State update
157
- *
158
- * @remarks
159
- * CoreNode Properties Updated:
160
- * - `renderState`
161
- */
162
- RenderState = 512,
163
-
164
- /**
165
- * Is Renderable update
166
- *
167
- * @remarks
168
- * CoreNode Properties Updated:
169
- * - `isRenderable`
170
- */
171
- IsRenderable = 1024,
172
-
173
- /**
174
- * Render Texture update
175
- */
176
- RenderTexture = 2048,
177
-
178
- /**
179
- * Track if parent has render texture
180
- */
181
- ParentRenderTexture = 4096,
182
-
183
- /**
184
- * Render Bounds update
185
- */
186
- RenderBounds = 8192,
187
-
188
- /**
189
- * None
190
- */
191
- None = 0,
192
-
193
- /**
194
- * All
195
- */
196
- All = 14335,
197
- }
198
-
199
- /**
200
- * A custom data map which can be stored on an CoreNode
201
- *
202
- * @remarks
203
- * This is a map of key-value pairs that can be stored on an INode. It is used
204
- * to store custom data that can be used by the application.
205
- * The data stored can only be of type string, number or boolean.
206
- */
207
- export type CustomDataMap = {
208
- [key: string]: string | number | boolean | undefined;
209
- };
210
-
211
- /**
212
- * Writable properties of a Node.
213
- */
214
- export interface CoreNodeProps {
215
- /**
216
- * The x coordinate of the Node's Mount Point.
217
- *
218
- * @remarks
219
- * See {@link mountX} and {@link mountY} for more information about setting
220
- * the Mount Point.
221
- *
222
- * @default `0`
223
- */
224
- x: number;
225
- /**
226
- * The y coordinate of the Node's Mount Point.
227
- *
228
- * @remarks
229
- * See {@link mountX} and {@link mountY} for more information about setting
230
- * the Mount Point.
231
- *
232
- * @default `0`
233
- */
234
- y: number;
235
- /**
236
- * The width of the Node.
237
- *
238
- * @default `0`
239
- */
240
- width: number;
241
- /**
242
- * The height of the Node.
243
- *
244
- * @default `0`
245
- */
246
- height: number;
247
- /**
248
- * The alpha opacity of the Node.
249
- *
250
- * @remarks
251
- * The alpha value is a number between 0 and 1, where 0 is fully transparent
252
- * and 1 is fully opaque.
253
- *
254
- * @default `1`
255
- */
256
- alpha: number;
257
- /**
258
- * Autosize mode
259
- *
260
- * @remarks
261
- * When enabled, when a texture is loaded into the Node, the Node will
262
- * automatically resize to the dimensions of the texture.
263
- *
264
- * Text Nodes are always autosized based on their text content regardless
265
- * of this mode setting.
266
- *
267
- * @default `false`
268
- */
269
- autosize: boolean;
270
- /**
271
- * Clipping Mode
272
- *
273
- * @remarks
274
- * Enable Clipping Mode when you want to prevent the drawing of a Node and
275
- * its descendants from overflowing outside of the Node's x/y/width/height
276
- * bounds.
277
- *
278
- * For WebGL, clipping is implemented using the high-performance WebGL
279
- * operation scissor. As a consequence, clipping does not work for
280
- * non-rectangular areas. So, if the element is rotated
281
- * (by itself or by any of its ancestors), clipping will not work as intended.
282
- *
283
- * TODO: Add support for non-rectangular clipping either automatically or
284
- * via Render-To-Texture.
285
- *
286
- * @default `false`
287
- */
288
- clipping: boolean;
289
- /**
290
- * The color of the Node.
291
- *
292
- * @remarks
293
- * The color value is a number in the format 0xRRGGBBAA, where RR is the red
294
- * component, GG is the green component, BB is the blue component, and AA is
295
- * the alpha component.
296
- *
297
- * Gradient colors may be set by setting the different color sub-properties:
298
- * {@link colorTop}, {@link colorBottom}, {@link colorLeft}, {@link colorRight},
299
- * {@link colorTl}, {@link colorTr}, {@link colorBr}, {@link colorBl} accordingly.
300
- *
301
- * @default `0xffffffff` (opaque white)
302
- */
303
- color: number;
304
- /**
305
- * The color of the top edge of the Node for gradient rendering.
306
- *
307
- * @remarks
308
- * See {@link color} for more information about color values and gradient
309
- * rendering.
310
- */
311
- colorTop: number;
312
- /**
313
- * The color of the bottom edge of the Node for gradient rendering.
314
- *
315
- * @remarks
316
- * See {@link color} for more information about color values and gradient
317
- * rendering.
318
- */
319
- colorBottom: number;
320
- /**
321
- * The color of the left edge of the Node for gradient rendering.
322
- *
323
- * @remarks
324
- * See {@link color} for more information about color values and gradient
325
- * rendering.
326
- */
327
- colorLeft: number;
328
- /**
329
- * The color of the right edge of the Node for gradient rendering.
330
- *
331
- * @remarks
332
- * See {@link color} for more information about color values and gradient
333
- * rendering.
334
- */
335
- colorRight: number;
336
- /**
337
- * The color of the top-left corner of the Node for gradient rendering.
338
- *
339
- * @remarks
340
- * See {@link color} for more information about color values and gradient
341
- * rendering.
342
- */
343
- colorTl: number;
344
- /**
345
- * The color of the top-right corner of the Node for gradient rendering.
346
- *
347
- * @remarks
348
- * See {@link color} for more information about color values and gradient
349
- * rendering.
350
- */
351
- colorTr: number;
352
- /**
353
- * The color of the bottom-right corner of the Node for gradient rendering.
354
- *
355
- * @remarks
356
- * See {@link color} for more information about color values and gradient
357
- * rendering.
358
- */
359
- colorBr: number;
360
- /**
361
- * The color of the bottom-left corner of the Node for gradient rendering.
362
- *
363
- * @remarks
364
- * See {@link color} for more information about color values and gradient
365
- * rendering.
366
- */
367
- colorBl: number;
368
- /**
369
- * The Node's parent Node.
370
- *
371
- * @remarks
372
- * The value `null` indicates that the Node has no parent. This may either be
373
- * because the Node is the root Node of the scene graph, or because the Node
374
- * has been removed from the scene graph.
375
- *
376
- * In order to make sure that a Node can be rendered on the screen, it must
377
- * be added to the scene graph by setting it's parent property to a Node that
378
- * is already in the scene graph such as the root Node.
379
- *
380
- * @default `null`
381
- */
382
- parent: CoreNode | null;
383
- /**
384
- * The Node's z-index.
385
- *
386
- * @remarks
387
- * TBD
388
- */
389
- zIndex: number;
390
- /**
391
- * The Node's Texture.
392
- *
393
- * @remarks
394
- * The `texture` defines a rasterized image that is contained within the
395
- * {@link width} and {@link height} dimensions of the Node. If null, the
396
- * Node will use an opaque white {@link ColorTexture} when being drawn, which
397
- * essentially enables colors (including gradients) to be drawn.
398
- *
399
- * If set, by default, the texture will be drawn, as is, stretched to the
400
- * dimensions of the Node. This behavior can be modified by setting the TBD
401
- * and TBD properties.
402
- *
403
- * To create a Texture in order to set it on this property, call
404
- * {@link RendererMain.createTexture}.
405
- *
406
- * If the {@link src} is set on a Node, the Node will use the
407
- * {@link ImageTexture} by default and the Node will simply load the image at
408
- * the specified URL.
409
- *
410
- * Note: If this is a Text Node, the Texture will be managed by the Node's
411
- * {@link TextRenderer} and should not be set explicitly.
412
- */
413
- texture: Texture | null;
414
-
415
- /**
416
- * Whether to prevent the node from being cleaned up
417
- * @default false
418
- */
419
- preventCleanup: boolean;
420
- /**
421
- * Options to associate with the Node's Texture
422
- */
423
- textureOptions: TextureOptions;
424
-
425
- /**
426
- * The Node's shader
427
- *
428
- * @remarks
429
- * The `shader` defines a {@link Shader} used to draw the Node. By default,
430
- * the Default Shader is used which simply draws the defined {@link texture}
431
- * or {@link color}(s) within the Node without any special effects.
432
- *
433
- * To create a Shader in order to set it on this property, call
434
- * {@link RendererMain.createShader}.
435
- *
436
- * Note: If this is a Text Node, the Shader will be managed by the Node's
437
- * {@link TextRenderer} and should not be set explicitly.
438
- */
439
- shader: BaseShaderController;
440
- /**
441
- * Image URL
442
- *
443
- * @remarks
444
- * When set, the Node's {@link texture} is automatically set to an
445
- * {@link ImageTexture} using the source image URL provided (with all other
446
- * settings being defaults)
447
- */
448
- src: string | null;
449
- zIndexLocked: number;
450
- /**
451
- * Scale to render the Node at
452
- *
453
- * @remarks
454
- * The scale value multiplies the provided {@link width} and {@link height}
455
- * of the Node around the Node's Pivot Point (defined by the {@link pivot}
456
- * props).
457
- *
458
- * Behind the scenes, setting this property sets both the {@link scaleX} and
459
- * {@link scaleY} props to the same value.
460
- *
461
- * NOTE: When the scaleX and scaleY props are explicitly set to different values,
462
- * this property returns `null`. Setting `null` on this property will have no
463
- * effect.
464
- *
465
- * @default 1.0
466
- */
467
- scale: number | null;
468
- /**
469
- * Scale to render the Node at (X-Axis)
470
- *
471
- * @remarks
472
- * The scaleX value multiplies the provided {@link width} of the Node around
473
- * the Node's Pivot Point (defined by the {@link pivot} props).
474
- *
475
- * @default 1.0
476
- */
477
- scaleX: number;
478
- /**
479
- * Scale to render the Node at (Y-Axis)
480
- *
481
- * @remarks
482
- * The scaleY value multiplies the provided {@link height} of the Node around
483
- * the Node's Pivot Point (defined by the {@link pivot} props).
484
- *
485
- * @default 1.0
486
- */
487
- scaleY: number;
488
- /**
489
- * Combined position of the Node's Mount Point
490
- *
491
- * @remarks
492
- * The value can be any number between `0.0` and `1.0`:
493
- * - `0.0` defines the Mount Point at the top-left corner of the Node.
494
- * - `0.5` defines it at the center of the Node.
495
- * - `1.0` defines it at the bottom-right corner of the node.
496
- *
497
- * Use the {@link mountX} and {@link mountY} props seperately for more control
498
- * of the Mount Point.
499
- *
500
- * When assigned, the same value is also passed to both the {@link mountX} and
501
- * {@link mountY} props.
502
- *
503
- * @default 0 (top-left)
504
- */
505
- mount: number;
506
- /**
507
- * X position of the Node's Mount Point
508
- *
509
- * @remarks
510
- * The value can be any number between `0.0` and `1.0`:
511
- * - `0.0` defines the Mount Point's X position as the left-most edge of the
512
- * Node
513
- * - `0.5` defines it as the horizontal center of the Node
514
- * - `1.0` defines it as the right-most edge of the Node.
515
- *
516
- * The combination of {@link mountX} and {@link mountY} define the Mount Point
517
- *
518
- * @default 0 (left-most edge)
519
- */
520
- mountX: number;
521
- /**
522
- * Y position of the Node's Mount Point
523
- *
524
- * @remarks
525
- * The value can be any number between `0.0` and `1.0`:
526
- * - `0.0` defines the Mount Point's Y position as the top-most edge of the
527
- * Node
528
- * - `0.5` defines it as the vertical center of the Node
529
- * - `1.0` defines it as the bottom-most edge of the Node.
530
- *
531
- * The combination of {@link mountX} and {@link mountY} define the Mount Point
532
- *
533
- * @default 0 (top-most edge)
534
- */
535
- mountY: number;
536
- /**
537
- * Combined position of the Node's Pivot Point
538
- *
539
- * @remarks
540
- * The value can be any number between `0.0` and `1.0`:
541
- * - `0.0` defines the Pivot Point at the top-left corner of the Node.
542
- * - `0.5` defines it at the center of the Node.
543
- * - `1.0` defines it at the bottom-right corner of the node.
544
- *
545
- * Use the {@link pivotX} and {@link pivotY} props seperately for more control
546
- * of the Pivot Point.
547
- *
548
- * When assigned, the same value is also passed to both the {@link pivotX} and
549
- * {@link pivotY} props.
550
- *
551
- * @default 0.5 (center)
552
- */
553
- pivot: number;
554
- /**
555
- * X position of the Node's Pivot Point
556
- *
557
- * @remarks
558
- * The value can be any number between `0.0` and `1.0`:
559
- * - `0.0` defines the Pivot Point's X position as the left-most edge of the
560
- * Node
561
- * - `0.5` defines it as the horizontal center of the Node
562
- * - `1.0` defines it as the right-most edge of the Node.
563
- *
564
- * The combination of {@link pivotX} and {@link pivotY} define the Pivot Point
565
- *
566
- * @default 0.5 (centered on x-axis)
567
- */
568
- pivotX: number;
569
- /**
570
- * Y position of the Node's Pivot Point
571
- *
572
- * @remarks
573
- * The value can be any number between `0.0` and `1.0`:
574
- * - `0.0` defines the Pivot Point's Y position as the top-most edge of the
575
- * Node
576
- * - `0.5` defines it as the vertical center of the Node
577
- * - `1.0` defines it as the bottom-most edge of the Node.
578
- *
579
- * The combination of {@link pivotX} and {@link pivotY} define the Pivot Point
580
- *
581
- * @default 0.5 (centered on y-axis)
582
- */
583
- pivotY: number;
584
- /**
585
- * Rotation of the Node (in Radians)
586
- *
587
- * @remarks
588
- * Sets the amount to rotate the Node by around it's Pivot Point (defined by
589
- * the {@link pivot} props). Positive values rotate the Node clockwise, while
590
- * negative values rotate it counter-clockwise.
591
- *
592
- * Example values:
593
- * - `-Math.PI / 2`: 90 degree rotation counter-clockwise
594
- * - `0`: No rotation
595
- * - `Math.PI / 2`: 90 degree rotation clockwise
596
- * - `Math.PI`: 180 degree rotation clockwise
597
- * - `3 * Math.PI / 2`: 270 degree rotation clockwise
598
- * - `2 * Math.PI`: 360 rotation clockwise
599
- */
600
- rotation: number;
601
-
602
- /**
603
- * Whether the Node is rendered to a texture
604
- *
605
- * @remarks
606
- * TBD
607
- *
608
- * @default false
609
- */
610
- rtt: boolean;
611
-
612
- /**
613
- * Node data element for custom data storage (optional)
614
- *
615
- * @remarks
616
- * This property is used to store custom data on the Node as a key/value data store.
617
- * Data values are limited to string, numbers, booleans. Strings will be truncated
618
- * to a 2048 character limit for performance reasons.
619
- *
620
- * This is not a data storage mechanism for large amounts of data please use a
621
- * dedicated data storage mechanism for that.
622
- *
623
- * The custom data will be reflected in the inspector as part of `data-*` attributes
624
- *
625
- * @default `undefined`
626
- */
627
- data?: CustomDataMap;
628
-
629
- /**
630
- * Image Type to explicitly set the image type that is being loaded
631
- *
632
- * @remarks
633
- * This property must be used with a `src` that points at an image. In some cases
634
- * the extension doesn't provide a reliable representation of the image type. In such
635
- * cases set the ImageType explicitly.
636
- *
637
- * `regular` is used for normal images such as png, jpg, etc
638
- * `compressed` is used for ETC1/ETC2 compressed images with a PVR or KTX container
639
- * `svg` is used for scalable vector graphics
640
- *
641
- * @default `undefined`
642
- */
643
- imageType?: 'regular' | 'compressed' | 'svg' | null;
644
-
645
- /**
646
- * She width of the rectangle from which the Image Texture will be extracted.
647
- * This value can be negative. If not provided, the image's source natural
648
- * width will be used.
649
- */
650
- srcWidth?: number;
651
- /**
652
- * The height of the rectangle from which the Image Texture will be extracted.
653
- * This value can be negative. If not provided, the image's source natural
654
- * height will be used.
655
- */
656
- srcHeight?: number;
657
- /**
658
- * The x coordinate of the reference point of the rectangle from which the Texture
659
- * will be extracted. `width` and `height` are provided. And only works when
660
- * createImageBitmap is available. Only works when createImageBitmap is supported on the browser.
661
- */
662
- srcX?: number;
663
- /**
664
- * The y coordinate of the reference point of the rectangle from which the Texture
665
- * will be extracted. Only used when source `srcWidth` width and `srcHeight` height
666
- * are provided. Only works when createImageBitmap is supported on the browser.
667
- */
668
- srcY?: number;
669
- /**
670
- * By enabling Strict bounds the renderer will not process & render child nodes of a node that is out of the visible area
671
- *
672
- * @remarks
673
- * When enabled out of bound nodes, i.e. nodes that are out of the visible area, will
674
- * **NOT** have their children processed and renderer anymore. This means the children of a out of bound
675
- * node will not receive update processing such as positioning updates and will not be drawn on screen.
676
- * As such the rest of the branch of the update tree that sits below this node will not be processed anymore
677
- *
678
- * This is a big performance gain but may be disabled in cases where the width of the parent node is
679
- * unknown and the render must process the child nodes regardless of the viewport status of the parent node
680
- *
681
- * @default false
682
- */
683
- strictBounds: boolean;
684
- }
685
-
686
- /**
687
- * Grab all the number properties of type T
688
- */
689
- type NumberProps<T> = {
690
- [Key in keyof T as NonNullable<T[Key]> extends number ? Key : never]: number;
691
- };
692
-
693
- /**
694
- * Properties of a Node used by the animate() function
695
- */
696
- export interface CoreNodeAnimateProps extends NumberProps<CoreNodeProps> {
697
- /**
698
- * Shader properties to animate
699
- */
700
- shaderProps: Record<string, number>;
701
- // TODO: textureProps: Record<string, number>;
702
- }
703
-
704
- /**
705
- * A visual Node in the Renderer scene graph.
706
- *
707
- * @remarks
708
- * CoreNode is an internally used class that represents a Renderer Node in the
709
- * scene graph. See INode.ts for the public APIs exposed to Renderer users
710
- * that include generic types for Shaders.
711
- */
712
- export class CoreNode extends EventEmitter {
713
- readonly children: CoreNode[] = [];
714
- protected _id: number = getNewId();
715
- readonly props: CoreNodeProps;
716
-
717
- public updateType = UpdateType.All;
718
- public childUpdateType = UpdateType.None;
719
-
720
- public globalTransform?: Matrix3d;
721
- public scaleRotateTransform?: Matrix3d;
722
- public localTransform?: Matrix3d;
723
- public renderCoords?: RenderCoords;
724
- public renderBound?: Bound;
725
- public strictBound?: Bound;
726
- public preloadBound?: Bound;
727
- public clippingRect: RectWithValid = {
728
- x: 0,
729
- y: 0,
730
- width: 0,
731
- height: 0,
732
- valid: false,
733
- };
734
- public isRenderable = false;
735
- public renderState: CoreNodeRenderState = CoreNodeRenderState.Init;
736
-
737
- public worldAlpha = 1;
738
- public premultipliedColorTl = 0;
739
- public premultipliedColorTr = 0;
740
- public premultipliedColorBl = 0;
741
- public premultipliedColorBr = 0;
742
- public calcZIndex = 0;
743
- public hasRTTupdates = false;
744
- public parentHasRenderTexture = false;
745
- public rttParent: CoreNode | null = null;
746
-
747
- constructor(readonly stage: Stage, props: CoreNodeProps) {
748
- super();
749
-
750
- this.props = {
751
- ...props,
752
- parent: null,
753
- texture: null,
754
- src: null,
755
- rtt: false,
756
- };
757
-
758
- // Assign props to instance
759
- this.parent = props.parent;
760
- this.texture = props.texture;
761
- this.src = props.src;
762
- this.rtt = props.rtt;
763
-
764
- this.setUpdateType(
765
- UpdateType.ScaleRotate |
766
- UpdateType.Local |
767
- UpdateType.RenderBounds |
768
- UpdateType.RenderState,
769
- );
770
- }
771
-
772
- //#region Textures
773
- loadTexture(): void {
774
- const { texture } = this.props;
775
- assertTruthy(texture);
776
-
777
- // If texture is already loaded / failed, trigger loaded event manually
778
- // so that users get a consistent event experience.
779
- // We do this in a microtask to allow listeners to be attached in the same
780
- // synchronous task after calling loadTexture()
781
- queueMicrotask(() => {
782
- texture.preventCleanup = this.props.preventCleanup;
783
- // Preload texture if required
784
- if (this.textureOptions.preload) {
785
- texture.ctxTexture.load();
786
- }
787
-
788
- texture.on('loaded', this.onTextureLoaded);
789
- texture.on('failed', this.onTextureFailed);
790
- texture.on('freed', this.onTextureFreed);
791
-
792
- // If the parent is a render texture, the initial texture status
793
- // will be set to freed until the texture is processed by the
794
- // Render RTT nodes. So we only need to listen fo changes and
795
- // no need to check the texture.state until we restructure how
796
- // textures are being processed.
797
- if (this.parentHasRenderTexture) {
798
- this.notifyParentRTTOfUpdate();
799
- return;
800
- }
801
-
802
- if (texture.state === 'loaded') {
803
- assertTruthy(texture.dimensions);
804
- this.onTextureLoaded(texture, texture.dimensions);
805
- } else if (texture.state === 'failed') {
806
- assertTruthy(texture.error);
807
- this.onTextureFailed(texture, texture.error);
808
- } else if (texture.state === 'freed') {
809
- this.onTextureFreed(texture);
810
- }
811
- });
812
- }
813
-
814
- unloadTexture(): void {
815
- if (this.texture !== null) {
816
- this.texture.off('loaded', this.onTextureLoaded);
817
- this.texture.off('failed', this.onTextureFailed);
818
- this.texture.off('freed', this.onTextureFreed);
819
- this.texture.setRenderableOwner(this, false);
820
- }
821
- }
822
-
823
- autosizeNode(dimensions: Dimensions) {
824
- if (this.autosize) {
825
- this.width = dimensions.width;
826
- this.height = dimensions.height;
827
- }
828
- }
829
-
830
- private onTextureLoaded: TextureLoadedEventHandler = (_, dimensions) => {
831
- this.autosizeNode(dimensions);
832
-
833
- // Texture was loaded. In case the RAF loop has already stopped, we request
834
- // a render to ensure the texture is rendered.
835
- this.stage.requestRender();
836
-
837
- // If parent has a render texture, flag that we need to update
838
- if (this.parentHasRenderTexture) {
839
- this.notifyParentRTTOfUpdate();
840
- }
841
-
842
- this.emit('loaded', {
843
- type: 'texture',
844
- dimensions,
845
- } satisfies NodeTextureLoadedPayload);
846
-
847
- // Trigger a local update if the texture is loaded and the resizeMode is 'contain'
848
- if (this.props.textureOptions?.resizeMode?.type === 'contain') {
849
- this.setUpdateType(UpdateType.Local);
850
- }
851
- };
852
-
853
- private onTextureFailed: TextureFailedEventHandler = (_, error) => {
854
- // If parent has a render texture, flag that we need to update
855
- if (this.parentHasRenderTexture) {
856
- this.notifyParentRTTOfUpdate();
857
- }
858
-
859
- this.emit('failed', {
860
- type: 'texture',
861
- error,
862
- } satisfies NodeTextureFailedPayload);
863
- };
864
-
865
- private onTextureFreed: TextureFreedEventHandler = () => {
866
- // If parent has a render texture, flag that we need to update
867
- if (this.parentHasRenderTexture) {
868
- this.notifyParentRTTOfUpdate();
869
- }
870
-
871
- this.emit('freed', {
872
- type: 'texture',
873
- } satisfies NodeTextureFreedPayload);
874
- };
875
- //#endregion Textures
876
-
877
- /**
878
- * Change types types is used to determine the scope of the changes being applied
879
- *
880
- * @remarks
881
- * See {@link UpdateType} for more information on each type
882
- *
883
- * @param type
884
- */
885
- setUpdateType(type: UpdateType): void {
886
- this.updateType |= type;
887
-
888
- const parent = this.props.parent;
889
- if (!parent) return;
890
-
891
- if ((parent.updateType & UpdateType.Children) === 0) {
892
- // Inform the parent if it doesn’t already have a child update
893
- parent.setUpdateType(UpdateType.Children);
894
- }
895
- }
896
-
897
- sortChildren() {
898
- this.children.sort((a, b) => a.calcZIndex - b.calcZIndex);
899
- }
900
-
901
- updateScaleRotateTransform() {
902
- const { rotation, scaleX, scaleY } = this.props;
903
-
904
- // optimize simple translation cases
905
- if (rotation === 0 && scaleX === 1 && scaleY === 1) {
906
- this.scaleRotateTransform = undefined;
907
- return;
908
- }
909
-
910
- this.scaleRotateTransform = Matrix3d.rotate(
911
- rotation,
912
- this.scaleRotateTransform,
913
- ).scale(scaleX, scaleY);
914
- }
915
-
916
- updateLocalTransform() {
917
- const { x, y, width, height } = this.props;
918
- const mountTranslateX = this.props.mountX * width;
919
- const mountTranslateY = this.props.mountY * height;
920
-
921
- if (this.scaleRotateTransform) {
922
- const pivotTranslateX = this.props.pivotX * width;
923
- const pivotTranslateY = this.props.pivotY * height;
924
-
925
- this.localTransform = Matrix3d.translate(
926
- x - mountTranslateX + pivotTranslateX,
927
- y - mountTranslateY + pivotTranslateY,
928
- this.localTransform,
929
- )
930
- .multiply(this.scaleRotateTransform)
931
- .translate(-pivotTranslateX, -pivotTranslateY);
932
- } else {
933
- this.localTransform = Matrix3d.translate(
934
- x - mountTranslateX,
935
- y - mountTranslateY,
936
- this.localTransform,
937
- );
938
- }
939
-
940
- // Handle 'contain' resize mode
941
- const texture = this.props.texture;
942
- if (
943
- texture &&
944
- texture.dimensions &&
945
- this.props.textureOptions?.resizeMode?.type === 'contain'
946
- ) {
947
- let resizeModeScaleX = 1;
948
- let resizeModeScaleY = 1;
949
- let extraX = 0;
950
- let extraY = 0;
951
- const { width: tw, height: th } = texture.dimensions;
952
- const txAspectRatio = tw / th;
953
- const nodeAspectRatio = width / height;
954
- if (txAspectRatio > nodeAspectRatio) {
955
- // Texture is wider than node
956
- // Center the node vertically (shift down by extraY)
957
- // Scale the node vertically to maintain original aspect ratio
958
- const scaleX = width / tw;
959
- const scaledTxHeight = th * scaleX;
960
- extraY = (height - scaledTxHeight) / 2;
961
- resizeModeScaleY = scaledTxHeight / height;
962
- } else {
963
- // Texture is taller than node (or equal)
964
- // Center the node horizontally (shift right by extraX)
965
- // Scale the node horizontally to maintain original aspect ratio
966
- const scaleY = height / th;
967
- const scaledTxWidth = tw * scaleY;
968
- extraX = (width - scaledTxWidth) / 2;
969
- resizeModeScaleX = scaledTxWidth / width;
970
- }
971
-
972
- // Apply the extra translation and scale to the local transform
973
- this.localTransform
974
- .translate(extraX, extraY)
975
- .scale(resizeModeScaleX, resizeModeScaleY);
976
- }
977
-
978
- this.setUpdateType(UpdateType.Global);
979
- }
980
-
981
- /**
982
- * @todo: test for correct calculation flag
983
- * @param delta
984
- */
985
- update(delta: number, parentClippingRect: RectWithValid): void {
986
- if (this.updateType & UpdateType.ScaleRotate) {
987
- this.updateScaleRotateTransform();
988
- this.setUpdateType(UpdateType.Local);
989
- }
990
-
991
- if (this.updateType & UpdateType.Local) {
992
- this.updateLocalTransform();
993
- this.setUpdateType(UpdateType.Global);
994
- }
995
-
996
- const parent = this.props.parent;
997
- let renderState = null;
998
-
999
- // Handle specific RTT updates at this node level
1000
- if (this.updateType & UpdateType.RenderTexture && this.rtt) {
1001
- // Only the RTT node itself triggers `renderToTexture`
1002
- this.hasRTTupdates = true;
1003
- this.stage.renderer?.renderToTexture(this);
1004
- }
1005
-
1006
- if (this.updateType & UpdateType.Global) {
1007
- assertTruthy(this.localTransform);
1008
-
1009
- this.globalTransform = Matrix3d.copy(
1010
- parent?.globalTransform || this.localTransform,
1011
- this.globalTransform,
1012
- );
1013
-
1014
- if (this.parentHasRenderTexture && this.props.parent?.rtt) {
1015
- this.globalTransform = Matrix3d.identity();
1016
- }
1017
-
1018
- if (parent) {
1019
- this.globalTransform.multiply(this.localTransform);
1020
- }
1021
-
1022
- this.calculateRenderCoords();
1023
- this.updateBoundingRect();
1024
-
1025
- this.setUpdateType(UpdateType.RenderState | UpdateType.Children);
1026
- this.childUpdateType |= UpdateType.Global;
1027
-
1028
- if (this.clipping === true) {
1029
- this.setUpdateType(UpdateType.Clipping | UpdateType.RenderBounds);
1030
- this.childUpdateType |= UpdateType.RenderBounds;
1031
- }
1032
- }
1033
-
1034
- if (this.updateType & UpdateType.RenderBounds) {
1035
- this.createRenderBounds();
1036
- this.setUpdateType(UpdateType.RenderState);
1037
- this.setUpdateType(UpdateType.Children);
1038
- }
1039
-
1040
- if (this.updateType & UpdateType.RenderState) {
1041
- renderState = this.checkRenderBounds();
1042
- this.setUpdateType(UpdateType.IsRenderable);
1043
-
1044
- // if we're not going out of bounds, update the render state
1045
- // this is done so the update loop can finish before we mark a node
1046
- // as out of bounds
1047
- if (renderState !== CoreNodeRenderState.OutOfBounds) {
1048
- this.updateRenderState(renderState);
1049
- }
1050
- }
1051
-
1052
- if (this.updateType & UpdateType.WorldAlpha) {
1053
- if (parent) {
1054
- this.worldAlpha = parent.worldAlpha * this.props.alpha;
1055
- } else {
1056
- this.worldAlpha = this.props.alpha;
1057
- }
1058
- this.setUpdateType(
1059
- UpdateType.Children |
1060
- UpdateType.PremultipliedColors |
1061
- UpdateType.IsRenderable,
1062
- );
1063
- this.childUpdateType |= UpdateType.WorldAlpha;
1064
- }
1065
-
1066
- if (this.updateType & UpdateType.IsRenderable) {
1067
- this.updateIsRenderable();
1068
- }
1069
-
1070
- if (this.updateType & UpdateType.Clipping) {
1071
- this.calculateClippingRect(parentClippingRect);
1072
- this.setUpdateType(UpdateType.Children);
1073
-
1074
- this.childUpdateType |= UpdateType.Clipping;
1075
- this.childUpdateType |= UpdateType.RenderBounds;
1076
- }
1077
-
1078
- if (this.updateType & UpdateType.PremultipliedColors) {
1079
- this.premultipliedColorTl = mergeColorAlphaPremultiplied(
1080
- this.props.colorTl,
1081
- this.worldAlpha,
1082
- true,
1083
- );
1084
-
1085
- // If all the colors are the same just sent them all to the same value
1086
- if (
1087
- this.props.colorTl === this.props.colorTr &&
1088
- this.props.colorBl === this.props.colorBr &&
1089
- this.props.colorTl === this.props.colorBl
1090
- ) {
1091
- this.premultipliedColorTr =
1092
- this.premultipliedColorBl =
1093
- this.premultipliedColorBr =
1094
- this.premultipliedColorTl;
1095
- } else {
1096
- this.premultipliedColorTr = mergeColorAlphaPremultiplied(
1097
- this.props.colorTr,
1098
- this.worldAlpha,
1099
- true,
1100
- );
1101
- this.premultipliedColorBl = mergeColorAlphaPremultiplied(
1102
- this.props.colorBl,
1103
- this.worldAlpha,
1104
- true,
1105
- );
1106
- this.premultipliedColorBr = mergeColorAlphaPremultiplied(
1107
- this.props.colorBr,
1108
- this.worldAlpha,
1109
- true,
1110
- );
1111
- }
1112
- }
1113
-
1114
- // No need to update zIndex if there is no parent
1115
- if (parent !== null && this.updateType & UpdateType.CalculatedZIndex) {
1116
- this.calculateZIndex();
1117
- // Tell parent to re-sort children
1118
- parent.setUpdateType(UpdateType.ZIndexSortedChildren);
1119
- }
1120
-
1121
- if (
1122
- this.props.strictBounds === true &&
1123
- this.renderState === CoreNodeRenderState.OutOfBounds
1124
- ) {
1125
- return;
1126
- }
1127
-
1128
- if (this.updateType & UpdateType.Children && this.children.length > 0) {
1129
- for (let i = 0, length = this.children.length; i < length; i++) {
1130
- const child = this.children[i] as CoreNode;
1131
-
1132
- child.setUpdateType(this.childUpdateType);
1133
-
1134
- if (child.updateType === 0) {
1135
- continue;
1136
- }
1137
-
1138
- let childClippingRect = this.clippingRect;
1139
- if (this.rtt === true) {
1140
- childClippingRect = {
1141
- x: 0,
1142
- y: 0,
1143
- width: 0,
1144
- height: 0,
1145
- valid: false,
1146
- };
1147
- }
1148
-
1149
- child.update(delta, childClippingRect);
1150
- }
1151
- }
1152
-
1153
- // If the node has an RTT parent and requires a texture re-render, inform the RTT parent
1154
- // if (this.parentHasRenderTexture && this.updateType & UpdateType.RenderTexture) {
1155
- // @TODO have a more scoped down updateType for RTT updates
1156
- if (this.parentHasRenderTexture && this.updateType > 0) {
1157
- this.notifyParentRTTOfUpdate();
1158
- }
1159
-
1160
- // Sorting children MUST happen after children have been updated so
1161
- // that they have the oppotunity to update their calculated zIndex.
1162
- if (this.updateType & UpdateType.ZIndexSortedChildren) {
1163
- // reorder z-index
1164
- this.sortChildren();
1165
- }
1166
-
1167
- // If we're out of bounds, apply the render state now
1168
- // this is done so nodes can finish their entire update loop before
1169
- // being marked as out of bounds
1170
- if (renderState === CoreNodeRenderState.OutOfBounds) {
1171
- this.updateRenderState(renderState);
1172
- this.updateIsRenderable();
1173
- }
1174
-
1175
- // reset update type
1176
- this.updateType = 0;
1177
- this.childUpdateType = 0;
1178
- }
1179
-
1180
- private findParentRTTNode(): CoreNode | null {
1181
- let rttNode: CoreNode | null = this.parent;
1182
- while (rttNode && !rttNode.rtt) {
1183
- rttNode = rttNode.parent;
1184
- }
1185
- return rttNode;
1186
- }
1187
-
1188
- private notifyParentRTTOfUpdate() {
1189
- if (this.parent === null) {
1190
- return;
1191
- }
1192
-
1193
- const rttNode = this.rttParent || this.findParentRTTNode();
1194
- if (!rttNode) {
1195
- return;
1196
- }
1197
-
1198
- // If an RTT node is found, mark it for re-rendering
1199
- rttNode.hasRTTupdates = true;
1200
- rttNode.setUpdateType(UpdateType.RenderTexture);
1201
-
1202
- // if rttNode is nested, also make it update its RTT parent
1203
- if (rttNode.parentHasRenderTexture === true) {
1204
- rttNode.notifyParentRTTOfUpdate();
1205
- }
1206
- }
1207
-
1208
- //check if CoreNode is renderable based on props
1209
- hasRenderableProperties(): boolean {
1210
- if (this.props.texture) {
1211
- return true;
1212
- }
1213
-
1214
- if (!this.props.width || !this.props.height) {
1215
- return false;
1216
- }
1217
-
1218
- if (this.props.shader !== this.stage.defShaderCtr) {
1219
- return true;
1220
- }
1221
-
1222
- if (this.props.clipping) {
1223
- return true;
1224
- }
1225
-
1226
- if (this.props.color !== 0) {
1227
- return true;
1228
- }
1229
-
1230
- // Consider removing these checks and just using the color property check above.
1231
- // Maybe add a forceRender prop for nodes that should always render.
1232
- if (this.props.colorTop !== 0) {
1233
- return true;
1234
- }
1235
-
1236
- if (this.props.colorBottom !== 0) {
1237
- return true;
1238
- }
1239
-
1240
- if (this.props.colorLeft !== 0) {
1241
- return true;
1242
- }
1243
-
1244
- if (this.props.colorRight !== 0) {
1245
- return true;
1246
- }
1247
-
1248
- if (this.props.colorTl !== 0) {
1249
- return true;
1250
- }
1251
-
1252
- if (this.props.colorTr !== 0) {
1253
- return true;
1254
- }
1255
-
1256
- if (this.props.colorBl !== 0) {
1257
- return true;
1258
- }
1259
-
1260
- if (this.props.colorBr !== 0) {
1261
- return true;
1262
- }
1263
- return false;
1264
- }
1265
-
1266
- checkRenderBounds(): CoreNodeRenderState {
1267
- assertTruthy(this.renderBound);
1268
- assertTruthy(this.strictBound);
1269
- assertTruthy(this.preloadBound);
1270
-
1271
- if (boundInsideBound(this.renderBound, this.strictBound)) {
1272
- return CoreNodeRenderState.InViewport;
1273
- }
1274
-
1275
- if (boundInsideBound(this.renderBound, this.preloadBound)) {
1276
- return CoreNodeRenderState.InBounds;
1277
- }
1278
-
1279
- // check if we're larger then our parent, we're definitely in the viewport
1280
- if (boundLargeThanBound(this.renderBound, this.strictBound)) {
1281
- return CoreNodeRenderState.InViewport;
1282
- }
1283
-
1284
- // if we are part of a parent render texture, we're always in bounds
1285
- if (this.parentHasRenderTexture === true) {
1286
- return CoreNodeRenderState.InBounds;
1287
- }
1288
-
1289
- // check if we dont have dimensions, take our parent's render state
1290
- if (
1291
- this.parent !== null &&
1292
- (this.props.width === 0 || this.props.height === 0)
1293
- ) {
1294
- return this.parent.renderState;
1295
- }
1296
-
1297
- return CoreNodeRenderState.OutOfBounds;
1298
- }
1299
-
1300
- updateBoundingRect() {
1301
- const { renderCoords, globalTransform: transform } = this;
1302
- assertTruthy(transform);
1303
- assertTruthy(renderCoords);
1304
-
1305
- const { tb, tc } = transform;
1306
- const { x1, y1, x3, y3 } = renderCoords;
1307
- if (tb === 0 || tc === 0) {
1308
- this.renderBound = createBound(x1, y1, x3, y3, this.renderBound);
1309
- } else {
1310
- const { x2, x4, y2, y4 } = renderCoords;
1311
- this.renderBound = createBound(
1312
- Math.min(x1, x2, x3, x4),
1313
- Math.min(y1, y2, y3, y4),
1314
- Math.max(x1, x2, x3, x4),
1315
- Math.max(y1, y2, y3, y4),
1316
- this.renderBound,
1317
- );
1318
- }
1319
- }
1320
-
1321
- createRenderBounds(): void {
1322
- assertTruthy(this.stage);
1323
-
1324
- if (this.parent !== null && this.parent.strictBound !== undefined) {
1325
- // we have a parent with a valid bound, copy it
1326
- const parentBound = this.parent.strictBound;
1327
- this.strictBound = createBound(
1328
- parentBound.x1,
1329
- parentBound.y1,
1330
- parentBound.x2,
1331
- parentBound.y2,
1332
- );
1333
-
1334
- this.preloadBound = createPreloadBounds(
1335
- this.strictBound,
1336
- this.stage.boundsMargin,
1337
- );
1338
- } else {
1339
- // no parent or parent does not have a bound, take the stage boundaries
1340
- this.strictBound = this.stage.strictBound;
1341
- this.preloadBound = this.stage.preloadBound;
1342
- }
1343
-
1344
- // if clipping is disabled, we're done
1345
- if (this.props.clipping === false) {
1346
- return;
1347
- }
1348
-
1349
- // only create local clipping bounds if node itself is in bounds
1350
- // this can only be done if we have a render bound already
1351
- if (this.renderBound === undefined) {
1352
- return;
1353
- }
1354
-
1355
- // if we're out of bounds, we're done
1356
- if (boundInsideBound(this.renderBound, this.strictBound) === false) {
1357
- return;
1358
- }
1359
-
1360
- // clipping is enabled and we are in bounds create our own bounds
1361
- const { x, y, width, height } = this.props;
1362
- const { tx, ty } = this.globalTransform || {};
1363
- const _x = tx ?? x;
1364
- const _y = ty ?? y;
1365
- this.strictBound = createBound(
1366
- _x,
1367
- _y,
1368
- _x + width,
1369
- _y + height,
1370
- this.strictBound,
1371
- );
1372
-
1373
- this.preloadBound = createPreloadBounds(
1374
- this.strictBound,
1375
- this.stage.boundsMargin,
1376
- );
1377
- }
1378
-
1379
- updateRenderState(renderState: CoreNodeRenderState) {
1380
- if (renderState === this.renderState) {
1381
- return;
1382
- }
1383
-
1384
- const previous = this.renderState;
1385
- this.renderState = renderState;
1386
- const event = CoreNodeRenderStateMap.get(renderState);
1387
- assertTruthy(event);
1388
- this.emit(event, {
1389
- previous,
1390
- current: renderState,
1391
- });
1392
- }
1393
-
1394
- /**
1395
- * This function updates the `isRenderable` property based on certain conditions.
1396
- *
1397
- * @returns
1398
- */
1399
- updateIsRenderable() {
1400
- let newIsRenderable;
1401
- if (this.worldAlpha === 0 || !this.hasRenderableProperties()) {
1402
- newIsRenderable = false;
1403
- } else {
1404
- newIsRenderable = this.renderState > CoreNodeRenderState.OutOfBounds;
1405
- }
1406
- if (this.isRenderable !== newIsRenderable) {
1407
- this.isRenderable = newIsRenderable;
1408
- this.onChangeIsRenderable(newIsRenderable);
1409
- }
1410
- }
1411
-
1412
- onChangeIsRenderable(isRenderable: boolean) {
1413
- this.texture?.setRenderableOwner(this, isRenderable);
1414
- }
1415
-
1416
- calculateRenderCoords() {
1417
- const { width, height, globalTransform: transform } = this;
1418
- assertTruthy(transform);
1419
- const { tx, ty, ta, tb, tc, td } = transform;
1420
- if (tb === 0 && tc === 0) {
1421
- const minX = tx;
1422
- const maxX = tx + width * ta;
1423
-
1424
- const minY = ty;
1425
- const maxY = ty + height * td;
1426
- this.renderCoords = RenderCoords.translate(
1427
- //top-left
1428
- minX,
1429
- minY,
1430
- //top-right
1431
- maxX,
1432
- minY,
1433
- //bottom-right
1434
- maxX,
1435
- maxY,
1436
- //bottom-left
1437
- minX,
1438
- maxY,
1439
- this.renderCoords,
1440
- );
1441
- } else {
1442
- this.renderCoords = RenderCoords.translate(
1443
- //top-left
1444
- tx,
1445
- ty,
1446
- //top-right
1447
- tx + width * ta,
1448
- ty + width * tc,
1449
- //bottom-right
1450
- tx + width * ta + height * tb,
1451
- ty + width * tc + height * td,
1452
- //bottom-left
1453
- tx + height * tb,
1454
- ty + height * td,
1455
- this.renderCoords,
1456
- );
1457
- }
1458
- }
1459
-
1460
- /**
1461
- * This function calculates the clipping rectangle for a node.
1462
- *
1463
- * The function then checks if the node is rotated. If the node requires clipping and is not rotated, a new clipping rectangle is created based on the node's global transform and dimensions.
1464
- * If a parent clipping rectangle exists, it is intersected with the node's clipping rectangle (if it exists), or replaces the node's clipping rectangle.
1465
- *
1466
- * Finally, the node's parentClippingRect and clippingRect properties are updated.
1467
- */
1468
- calculateClippingRect(parentClippingRect: RectWithValid) {
1469
- assertTruthy(this.globalTransform);
1470
- const { clippingRect, props, globalTransform: gt } = this;
1471
- const { clipping } = props;
1472
-
1473
- const isRotated = gt.tb !== 0 || gt.tc !== 0;
1474
-
1475
- if (clipping === true && isRotated === false) {
1476
- clippingRect.x = gt.tx;
1477
- clippingRect.y = gt.ty;
1478
- clippingRect.width = this.width * gt.ta;
1479
- clippingRect.height = this.height * gt.td;
1480
- clippingRect.valid = true;
1481
- } else {
1482
- clippingRect.valid = false;
1483
- }
1484
-
1485
- if (parentClippingRect.valid === true && clippingRect.valid === true) {
1486
- // Intersect parent clipping rect with node clipping rect
1487
- intersectRect(parentClippingRect, clippingRect, clippingRect);
1488
- } else if (parentClippingRect.valid === true) {
1489
- // Copy parent clipping rect
1490
- copyRect(parentClippingRect, clippingRect);
1491
- clippingRect.valid = true;
1492
- }
1493
- }
1494
-
1495
- calculateZIndex(): void {
1496
- const props = this.props;
1497
- const z = props.zIndex || 0;
1498
- const p = props.parent?.zIndex || 0;
1499
-
1500
- let zIndex = z;
1501
- if (props.parent?.zIndexLocked) {
1502
- zIndex = z < p ? z : p;
1503
- }
1504
- this.calcZIndex = zIndex;
1505
- }
1506
-
1507
- /**
1508
- * Destroy the node and cleanup all resources
1509
- */
1510
- destroy(): void {
1511
- this.unloadTexture();
1512
-
1513
- this.clippingRect.valid = false;
1514
- this.isRenderable = false;
1515
-
1516
- this.renderCoords = undefined;
1517
- this.renderBound = undefined;
1518
- this.strictBound = undefined;
1519
- this.preloadBound = undefined;
1520
- this.globalTransform = undefined;
1521
- this.scaleRotateTransform = undefined;
1522
- this.localTransform = undefined;
1523
-
1524
- this.props.texture = null;
1525
- this.props.shader = this.stage.defShaderCtr;
1526
-
1527
- while (this.children.length > 0) {
1528
- this.children[0]?.destroy();
1529
- }
1530
-
1531
- // This very action will also remove the node from the parent's children array
1532
- this.parent = null;
1533
-
1534
- if (this.rtt) {
1535
- this.stage.renderer.removeRTTNode(this);
1536
- }
1537
-
1538
- this.removeAllListeners();
1539
- }
1540
-
1541
- renderQuads(renderer: CoreRenderer): void {
1542
- // Prevent quad rendering if parent has a render texture
1543
- // and renderer is not currently rendering to a texture
1544
- if (this.parentHasRenderTexture) {
1545
- if (!renderer.renderToTextureActive) {
1546
- return;
1547
- }
1548
- // Prevent quad rendering if parent render texture is not the active render texture
1549
- if (this.parentRenderTexture !== renderer.activeRttNode) {
1550
- return;
1551
- }
1552
- }
1553
-
1554
- assertTruthy(this.globalTransform);
1555
- assertTruthy(this.renderCoords);
1556
-
1557
- // add to list of renderables to be sorted before rendering
1558
- renderer.addQuad({
1559
- width: this.props.width,
1560
- height: this.props.height,
1561
- colorTl: this.premultipliedColorTl,
1562
- colorTr: this.premultipliedColorTr,
1563
- colorBl: this.premultipliedColorBl,
1564
- colorBr: this.premultipliedColorBr,
1565
- texture: this.texture,
1566
- textureOptions: this.textureOptions,
1567
- zIndex: this.zIndex,
1568
- shader: this.shader.shader,
1569
- shaderProps: this.shader.getResolvedProps(),
1570
- alpha: this.worldAlpha,
1571
- clippingRect: this.clippingRect,
1572
- tx: this.globalTransform.tx,
1573
- ty: this.globalTransform.ty,
1574
- ta: this.globalTransform.ta,
1575
- tb: this.globalTransform.tb,
1576
- tc: this.globalTransform.tc,
1577
- td: this.globalTransform.td,
1578
- renderCoords: this.renderCoords,
1579
- rtt: this.rtt,
1580
- parentHasRenderTexture: this.parentHasRenderTexture,
1581
- framebufferDimensions: this.framebufferDimensions,
1582
- });
1583
- }
1584
-
1585
- //#region Properties
1586
- get id(): number {
1587
- return this._id;
1588
- }
1589
-
1590
- get data(): CustomDataMap | undefined {
1591
- return this.props.data;
1592
- }
1593
-
1594
- set data(d: CustomDataMap | undefined) {
1595
- this.props.data = d;
1596
- }
1597
-
1598
- get x(): number {
1599
- return this.props.x;
1600
- }
1601
-
1602
- set x(value: number) {
1603
- if (this.props.x !== value) {
1604
- this.props.x = value;
1605
- this.setUpdateType(UpdateType.Local);
1606
- }
1607
- }
1608
-
1609
- get absX(): number {
1610
- return (
1611
- this.props.x +
1612
- -this.props.width * this.props.mountX +
1613
- (this.props.parent?.absX || this.props.parent?.globalTransform?.tx || 0)
1614
- );
1615
- }
1616
-
1617
- get absY(): number {
1618
- return (
1619
- this.props.y +
1620
- -this.props.height * this.props.mountY +
1621
- (this.props.parent?.absY ?? 0)
1622
- );
1623
- }
1624
-
1625
- get y(): number {
1626
- return this.props.y;
1627
- }
1628
-
1629
- set y(value: number) {
1630
- if (this.props.y !== value) {
1631
- this.props.y = value;
1632
- this.setUpdateType(UpdateType.Local);
1633
- }
1634
- }
1635
-
1636
- get width(): number {
1637
- return this.props.width;
1638
- }
1639
-
1640
- set width(value: number) {
1641
- if (this.props.width !== value) {
1642
- this.props.width = value;
1643
- this.setUpdateType(UpdateType.Local);
1644
-
1645
- if (this.props.rtt) {
1646
- this.texture = this.stage.txManager.loadTexture('RenderTexture', {
1647
- width: this.width,
1648
- height: this.height,
1649
- });
1650
- this.textureOptions.preload = true;
1651
- this.setUpdateType(UpdateType.RenderTexture);
1652
- }
1653
- }
1654
- }
1655
-
1656
- get height(): number {
1657
- return this.props.height;
1658
- }
1659
-
1660
- set height(value: number) {
1661
- if (this.props.height !== value) {
1662
- this.props.height = value;
1663
- this.setUpdateType(UpdateType.Local);
1664
-
1665
- if (this.props.rtt) {
1666
- this.texture = this.stage.txManager.loadTexture('RenderTexture', {
1667
- width: this.width,
1668
- height: this.height,
1669
- });
1670
- this.textureOptions.preload = true;
1671
- this.setUpdateType(UpdateType.RenderTexture);
1672
- }
1673
- }
1674
- }
1675
-
1676
- get scale(): number {
1677
- // The CoreNode `scale` property is only used by Animations.
1678
- // Unlike INode, `null` should never be possibility for Animations.
1679
- return this.scaleX;
1680
- }
1681
-
1682
- set scale(value: number) {
1683
- // The CoreNode `scale` property is only used by Animations.
1684
- // Unlike INode, `null` should never be possibility for Animations.
1685
- this.scaleX = value;
1686
- this.scaleY = value;
1687
- }
1688
-
1689
- get scaleX(): number {
1690
- return this.props.scaleX;
1691
- }
1692
-
1693
- set scaleX(value: number) {
1694
- if (this.props.scaleX !== value) {
1695
- this.props.scaleX = value;
1696
- this.setUpdateType(UpdateType.ScaleRotate);
1697
- }
1698
- }
1699
-
1700
- get scaleY(): number {
1701
- return this.props.scaleY;
1702
- }
1703
-
1704
- set scaleY(value: number) {
1705
- if (this.props.scaleY !== value) {
1706
- this.props.scaleY = value;
1707
- this.setUpdateType(UpdateType.ScaleRotate);
1708
- }
1709
- }
1710
-
1711
- get mount(): number {
1712
- return this.props.mount;
1713
- }
1714
-
1715
- set mount(value: number) {
1716
- if (this.props.mountX !== value || this.props.mountY !== value) {
1717
- this.props.mountX = value;
1718
- this.props.mountY = value;
1719
- this.props.mount = value;
1720
- this.setUpdateType(UpdateType.Local);
1721
- }
1722
- }
1723
-
1724
- get mountX(): number {
1725
- return this.props.mountX;
1726
- }
1727
-
1728
- set mountX(value: number) {
1729
- if (this.props.mountX !== value) {
1730
- this.props.mountX = value;
1731
- this.setUpdateType(UpdateType.Local);
1732
- }
1733
- }
1734
-
1735
- get mountY(): number {
1736
- return this.props.mountY;
1737
- }
1738
-
1739
- set mountY(value: number) {
1740
- if (this.props.mountY !== value) {
1741
- this.props.mountY = value;
1742
- this.setUpdateType(UpdateType.Local);
1743
- }
1744
- }
1745
-
1746
- get pivot(): number {
1747
- return this.props.pivot;
1748
- }
1749
-
1750
- set pivot(value: number) {
1751
- if (this.props.pivotX !== value || this.props.pivotY !== value) {
1752
- this.props.pivotX = value;
1753
- this.props.pivotY = value;
1754
- this.props.pivot = value;
1755
- this.setUpdateType(UpdateType.Local);
1756
- }
1757
- }
1758
-
1759
- get pivotX(): number {
1760
- return this.props.pivotX;
1761
- }
1762
-
1763
- set pivotX(value: number) {
1764
- if (this.props.pivotX !== value) {
1765
- this.props.pivotX = value;
1766
- this.setUpdateType(UpdateType.Local);
1767
- }
1768
- }
1769
-
1770
- get pivotY(): number {
1771
- return this.props.pivotY;
1772
- }
1773
-
1774
- set pivotY(value: number) {
1775
- if (this.props.pivotY !== value) {
1776
- this.props.pivotY = value;
1777
- this.setUpdateType(UpdateType.Local);
1778
- }
1779
- }
1780
-
1781
- get rotation(): number {
1782
- return this.props.rotation;
1783
- }
1784
-
1785
- set rotation(value: number) {
1786
- if (this.props.rotation !== value) {
1787
- this.props.rotation = value;
1788
- this.setUpdateType(UpdateType.ScaleRotate);
1789
- }
1790
- }
1791
-
1792
- get alpha(): number {
1793
- return this.props.alpha;
1794
- }
1795
-
1796
- set alpha(value: number) {
1797
- this.props.alpha = value;
1798
- this.setUpdateType(
1799
- UpdateType.PremultipliedColors |
1800
- UpdateType.WorldAlpha |
1801
- UpdateType.Children |
1802
- UpdateType.IsRenderable,
1803
- );
1804
- this.childUpdateType |= UpdateType.WorldAlpha;
1805
- }
1806
-
1807
- get autosize(): boolean {
1808
- return this.props.autosize;
1809
- }
1810
-
1811
- set autosize(value: boolean) {
1812
- this.props.autosize = value;
1813
- }
1814
-
1815
- get clipping(): boolean {
1816
- return this.props.clipping;
1817
- }
1818
-
1819
- set clipping(value: boolean) {
1820
- this.props.clipping = value;
1821
- this.setUpdateType(
1822
- UpdateType.Clipping | UpdateType.RenderBounds | UpdateType.Children,
1823
- );
1824
- this.childUpdateType |= UpdateType.Global | UpdateType.Clipping;
1825
- }
1826
-
1827
- get color(): number {
1828
- return this.props.color;
1829
- }
1830
-
1831
- set color(value: number) {
1832
- this.colorTop = value;
1833
- this.colorBottom = value;
1834
- this.colorLeft = value;
1835
- this.colorRight = value;
1836
- this.props.color = value;
1837
-
1838
- this.setUpdateType(UpdateType.PremultipliedColors);
1839
- }
1840
-
1841
- get colorTop(): number {
1842
- return this.props.colorTop;
1843
- }
1844
-
1845
- set colorTop(value: number) {
1846
- if (this.props.colorTl !== value || this.props.colorTr !== value) {
1847
- this.colorTl = value;
1848
- this.colorTr = value;
1849
- }
1850
- this.props.colorTop = value;
1851
- this.setUpdateType(UpdateType.PremultipliedColors);
1852
- }
1853
-
1854
- get colorBottom(): number {
1855
- return this.props.colorBottom;
1856
- }
1857
-
1858
- set colorBottom(value: number) {
1859
- if (this.props.colorBl !== value || this.props.colorBr !== value) {
1860
- this.colorBl = value;
1861
- this.colorBr = value;
1862
- }
1863
- this.props.colorBottom = value;
1864
- this.setUpdateType(UpdateType.PremultipliedColors);
1865
- }
1866
-
1867
- get colorLeft(): number {
1868
- return this.props.colorLeft;
1869
- }
1870
-
1871
- set colorLeft(value: number) {
1872
- if (this.props.colorTl !== value || this.props.colorBl !== value) {
1873
- this.colorTl = value;
1874
- this.colorBl = value;
1875
- }
1876
- this.props.colorLeft = value;
1877
- this.setUpdateType(UpdateType.PremultipliedColors);
1878
- }
1879
-
1880
- get colorRight(): number {
1881
- return this.props.colorRight;
1882
- }
1883
-
1884
- set colorRight(value: number) {
1885
- if (this.props.colorTr !== value || this.props.colorBr !== value) {
1886
- this.colorTr = value;
1887
- this.colorBr = value;
1888
- }
1889
- this.props.colorRight = value;
1890
- this.setUpdateType(UpdateType.PremultipliedColors);
1891
- }
1892
-
1893
- get colorTl(): number {
1894
- return this.props.colorTl;
1895
- }
1896
-
1897
- set colorTl(value: number) {
1898
- this.props.colorTl = value;
1899
- this.setUpdateType(UpdateType.PremultipliedColors);
1900
- }
1901
-
1902
- get colorTr(): number {
1903
- return this.props.colorTr;
1904
- }
1905
-
1906
- set colorTr(value: number) {
1907
- this.props.colorTr = value;
1908
- this.setUpdateType(UpdateType.PremultipliedColors);
1909
- }
1910
-
1911
- get colorBl(): number {
1912
- return this.props.colorBl;
1913
- }
1914
-
1915
- set colorBl(value: number) {
1916
- this.props.colorBl = value;
1917
- this.setUpdateType(UpdateType.PremultipliedColors);
1918
- }
1919
-
1920
- get colorBr(): number {
1921
- return this.props.colorBr;
1922
- }
1923
-
1924
- set colorBr(value: number) {
1925
- this.props.colorBr = value;
1926
- this.setUpdateType(UpdateType.PremultipliedColors);
1927
- }
1928
-
1929
- // we're only interested in parent zIndex to test
1930
- // if we should use node zIndex is higher then parent zIndex
1931
- get zIndexLocked(): number {
1932
- return this.props.zIndexLocked || 0;
1933
- }
1934
-
1935
- set zIndexLocked(value: number) {
1936
- this.props.zIndexLocked = value;
1937
- this.setUpdateType(UpdateType.CalculatedZIndex | UpdateType.Children);
1938
- for (let i = 0, length = this.children.length; i < length; i++) {
1939
- this.children[i]!.setUpdateType(UpdateType.CalculatedZIndex);
1940
- }
1941
- }
1942
-
1943
- get zIndex(): number {
1944
- return this.props.zIndex;
1945
- }
1946
-
1947
- set zIndex(value: number) {
1948
- this.props.zIndex = value;
1949
- this.setUpdateType(UpdateType.CalculatedZIndex | UpdateType.Children);
1950
- for (let i = 0, length = this.children.length; i < length; i++) {
1951
- this.children[i]!.setUpdateType(UpdateType.CalculatedZIndex);
1952
- }
1953
- }
1954
-
1955
- get parent(): CoreNode | null {
1956
- return this.props.parent;
1957
- }
1958
-
1959
- set parent(newParent: CoreNode | null) {
1960
- const oldParent = this.props.parent;
1961
- if (oldParent === newParent) {
1962
- return;
1963
- }
1964
- this.props.parent = newParent;
1965
- if (oldParent) {
1966
- const index = oldParent.children.indexOf(this);
1967
- assertTruthy(
1968
- index !== -1,
1969
- "CoreNode.parent: Node not found in old parent's children!",
1970
- );
1971
- oldParent.children.splice(index, 1);
1972
- oldParent.setUpdateType(
1973
- UpdateType.Children | UpdateType.ZIndexSortedChildren,
1974
- );
1975
- }
1976
- if (newParent) {
1977
- newParent.children.push(this);
1978
- // Since this node has a new parent, to be safe, have it do a full update.
1979
- this.setUpdateType(UpdateType.All);
1980
- // Tell parent that it's children need to be updated and sorted.
1981
- newParent.setUpdateType(
1982
- UpdateType.Children | UpdateType.ZIndexSortedChildren,
1983
- );
1984
-
1985
- // If the new parent has an RTT enabled, apply RTT inheritance
1986
- if (newParent.rtt || newParent.parentHasRenderTexture) {
1987
- this.applyRTTInheritance(newParent);
1988
- }
1989
- }
1990
- this.updateScaleRotateTransform();
1991
-
1992
- // fetch render bounds from parent
1993
- this.setUpdateType(UpdateType.RenderBounds | UpdateType.Children);
1994
- }
1995
-
1996
- get preventCleanup(): boolean {
1997
- return this.props.preventCleanup;
1998
- }
1999
-
2000
- set preventCleanup(value: boolean) {
2001
- this.props.preventCleanup = value;
2002
- }
2003
-
2004
- get rtt(): boolean {
2005
- return this.props.rtt;
2006
- }
2007
-
2008
- set rtt(value: boolean) {
2009
- if (this.props.rtt === value) {
2010
- return;
2011
- }
2012
- this.props.rtt = value;
2013
-
2014
- if (value === true) {
2015
- this.initRenderTexture();
2016
- this.markChildrenWithRTT();
2017
- } else {
2018
- this.cleanupRenderTexture();
2019
- }
2020
-
2021
- this.setUpdateType(UpdateType.RenderTexture);
2022
-
2023
- if (this.parentHasRenderTexture === true) {
2024
- this.notifyParentRTTOfUpdate();
2025
- }
2026
- }
2027
- private initRenderTexture() {
2028
- this.texture = this.stage.txManager.loadTexture('RenderTexture', {
2029
- width: this.width,
2030
- height: this.height,
2031
- });
2032
- this.textureOptions.preload = true;
2033
- this.stage.renderer?.renderToTexture(this); // Only this RTT node
2034
- }
2035
-
2036
- private cleanupRenderTexture() {
2037
- this.unloadTexture();
2038
- this.clearRTTInheritance();
2039
-
2040
- this.stage.renderer?.removeRTTNode(this);
2041
- this.hasRTTupdates = false;
2042
- this.texture = null;
2043
- }
2044
-
2045
- private markChildrenWithRTT(node: CoreNode | null = null) {
2046
- const parent = node || this;
2047
-
2048
- for (const child of parent.children) {
2049
- child.setUpdateType(UpdateType.All);
2050
- child.parentHasRenderTexture = true;
2051
- child.markChildrenWithRTT();
2052
- }
2053
- }
2054
-
2055
- // Apply RTT inheritance when a node has an RTT-enabled parent
2056
- private applyRTTInheritance(parent: CoreNode) {
2057
- if (parent.rtt) {
2058
- // Only the RTT node should be added to `renderToTexture`
2059
- parent.setUpdateType(UpdateType.RenderTexture);
2060
- }
2061
-
2062
- // Propagate `parentHasRenderTexture` downwards
2063
- this.markChildrenWithRTT(parent);
2064
- }
2065
-
2066
- // Clear RTT inheritance when detaching from an RTT chain
2067
- private clearRTTInheritance() {
2068
- // if this node is RTT itself stop the propagation important for nested RTT nodes
2069
- // for the initial RTT node this is already handled in `set rtt`
2070
- if (this.rtt) {
2071
- return;
2072
- }
2073
-
2074
- for (const child of this.children) {
2075
- // force child to update everything as the RTT inheritance has changed
2076
- child.parentHasRenderTexture = false;
2077
- child.rttParent = null;
2078
- child.setUpdateType(UpdateType.All);
2079
- child.clearRTTInheritance();
2080
- }
2081
- }
2082
-
2083
- get shader(): BaseShaderController {
2084
- return this.props.shader;
2085
- }
2086
-
2087
- set shader(value: BaseShaderController) {
2088
- if (this.props.shader === value) {
2089
- return;
2090
- }
2091
-
2092
- this.props.shader = value;
2093
-
2094
- this.setUpdateType(UpdateType.IsRenderable);
2095
- }
2096
-
2097
- get src(): string | null {
2098
- return this.props.src;
2099
- }
2100
-
2101
- set src(imageUrl: string | null) {
2102
- if (this.props.src === imageUrl) {
2103
- return;
2104
- }
2105
-
2106
- this.props.src = imageUrl;
2107
-
2108
- if (!imageUrl) {
2109
- this.texture = null;
2110
- return;
2111
- }
2112
-
2113
- this.texture = this.stage.txManager.loadTexture('ImageTexture', {
2114
- src: imageUrl,
2115
- width: this.props.width,
2116
- height: this.props.height,
2117
- type: this.props.imageType,
2118
- sx: this.props.srcX,
2119
- sy: this.props.srcY,
2120
- sw: this.props.srcWidth,
2121
- sh: this.props.srcHeight,
2122
- });
2123
- }
2124
-
2125
- set imageType(type: 'regular' | 'compressed' | 'svg' | null) {
2126
- if (this.props.imageType === type) {
2127
- return;
2128
- }
2129
-
2130
- this.props.imageType = type;
2131
- }
2132
-
2133
- get imageType() {
2134
- return this.props.imageType || null;
2135
- }
2136
-
2137
- get srcHeight(): number | undefined {
2138
- return this.props.srcHeight;
2139
- }
2140
-
2141
- set srcHeight(value: number) {
2142
- this.props.srcHeight = value;
2143
- }
2144
-
2145
- get srcWidth(): number | undefined {
2146
- return this.props.srcWidth;
2147
- }
2148
-
2149
- set srcWidth(value: number) {
2150
- this.props.srcWidth = value;
2151
- }
2152
-
2153
- get srcX(): number | undefined {
2154
- return this.props.srcX;
2155
- }
2156
-
2157
- set srcX(value: number) {
2158
- this.props.srcX = value;
2159
- }
2160
-
2161
- get srcY(): number | undefined {
2162
- return this.props.srcY;
2163
- }
2164
-
2165
- set srcY(value: number) {
2166
- this.props.srcY = value;
2167
- }
2168
-
2169
- /**
2170
- * Returns the framebuffer dimensions of the node.
2171
- * If the node has a render texture, the dimensions are the same as the node's dimensions.
2172
- * If the node does not have a render texture, the dimensions are inherited from the parent.
2173
- * If the node parent has a render texture and the node is a render texture, the nodes dimensions are used.
2174
- */
2175
- get framebufferDimensions(): Dimensions {
2176
- if (this.parentHasRenderTexture && !this.rtt && this.parent) {
2177
- return this.parent.framebufferDimensions;
2178
- }
2179
- return { width: this.width, height: this.height };
2180
- }
2181
-
2182
- /**
2183
- * Returns the parent render texture node if it exists.
2184
- */
2185
- get parentRenderTexture(): CoreNode | null {
2186
- let parent = this.parent;
2187
- while (parent) {
2188
- if (parent.rtt) {
2189
- return parent;
2190
- }
2191
- parent = parent.parent;
2192
- }
2193
- return null;
2194
- }
2195
-
2196
- get texture(): Texture | null {
2197
- return this.props.texture;
2198
- }
2199
-
2200
- set texture(value: Texture | null) {
2201
- if (this.props.texture === value) {
2202
- return;
2203
- }
2204
- const oldTexture = this.props.texture;
2205
- if (oldTexture) {
2206
- oldTexture.setRenderableOwner(this, false);
2207
- this.unloadTexture();
2208
- }
2209
- this.props.texture = value;
2210
- if (value) {
2211
- value.setRenderableOwner(this, this.isRenderable);
2212
- this.loadTexture();
2213
- }
2214
- this.setUpdateType(UpdateType.IsRenderable);
2215
- }
2216
-
2217
- set textureOptions(value: TextureOptions) {
2218
- this.props.textureOptions = value;
2219
- }
2220
-
2221
- get textureOptions(): TextureOptions {
2222
- return this.props.textureOptions;
2223
- }
2224
-
2225
- get strictBounds(): boolean {
2226
- return this.props.strictBounds;
2227
- }
2228
-
2229
- set strictBounds(v) {
2230
- if (v === this.props.strictBounds) {
2231
- return;
2232
- }
2233
-
2234
- this.props.strictBounds = v;
2235
- this.setUpdateType(UpdateType.RenderBounds | UpdateType.Children);
2236
- this.childUpdateType |= UpdateType.RenderBounds | UpdateType.Children;
2237
- }
2238
-
2239
- animate(
2240
- props: Partial<CoreNodeAnimateProps>,
2241
- settings: Partial<AnimationSettings>,
2242
- ): IAnimationController {
2243
- const animation = new CoreAnimation(this, props, settings);
2244
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
2245
- const controller = new CoreAnimationController(
2246
- this.stage.animationManager,
2247
- animation,
2248
- );
2249
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
2250
- return controller;
2251
- }
2252
-
2253
- flush() {
2254
- // no-op
2255
- }
2256
-
2257
- //#endregion Properties
2258
- }
1
+ /*
2
+ * If not stated otherwise in this file or this component's LICENSE file the
3
+ * following copyright and licenses apply:
4
+ *
5
+ * Copyright 2023 Comcast Cable Communications Management, LLC.
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the License);
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */
19
+
20
+ import {
21
+ assertTruthy,
22
+ getNewId,
23
+ mergeColorAlphaPremultiplied,
24
+ } from '../utils.js';
25
+ import type { TextureOptions } from './CoreTextureManager.js';
26
+ import type { CoreRenderer } from './renderers/CoreRenderer.js';
27
+ import type { Stage } from './Stage.js';
28
+ import {
29
+ type Texture,
30
+ type TextureFailedEventHandler,
31
+ type TextureFreedEventHandler,
32
+ type TextureLoadedEventHandler,
33
+ } from './textures/Texture.js';
34
+ import type {
35
+ Dimensions,
36
+ NodeTextureFailedPayload,
37
+ NodeTextureFreedPayload,
38
+ NodeTextureLoadedPayload,
39
+ } from '../common/CommonTypes.js';
40
+ import { EventEmitter } from '../common/EventEmitter.js';
41
+ import {
42
+ copyRect,
43
+ intersectRect,
44
+ type Bound,
45
+ type RectWithValid,
46
+ createBound,
47
+ boundInsideBound,
48
+ boundLargeThanBound,
49
+ createPreloadBounds,
50
+ } from './lib/utils.js';
51
+ import { Matrix3d } from './lib/Matrix3d.js';
52
+ import { RenderCoords } from './lib/RenderCoords.js';
53
+ import type { AnimationSettings } from './animations/CoreAnimation.js';
54
+ import type { IAnimationController } from '../common/IAnimationController.js';
55
+ import { CoreAnimation } from './animations/CoreAnimation.js';
56
+ import { CoreAnimationController } from './animations/CoreAnimationController.js';
57
+ import type { BaseShaderController } from '../main-api/ShaderController.js';
58
+
59
+ export enum CoreNodeRenderState {
60
+ Init = 0,
61
+ OutOfBounds = 2,
62
+ InBounds = 4,
63
+ InViewport = 8,
64
+ }
65
+
66
+ const CoreNodeRenderStateMap: Map<CoreNodeRenderState, string> = new Map();
67
+ CoreNodeRenderStateMap.set(CoreNodeRenderState.Init, 'init');
68
+ CoreNodeRenderStateMap.set(CoreNodeRenderState.OutOfBounds, 'outOfBounds');
69
+ CoreNodeRenderStateMap.set(CoreNodeRenderState.InBounds, 'inBounds');
70
+ CoreNodeRenderStateMap.set(CoreNodeRenderState.InViewport, 'inViewport');
71
+
72
+ export enum UpdateType {
73
+ /**
74
+ * Child updates
75
+ */
76
+ Children = 1,
77
+
78
+ /**
79
+ * Scale/Rotate transform update
80
+ *
81
+ * @remarks
82
+ * CoreNode Properties Updated:
83
+ * - `scaleRotateTransform`
84
+ */
85
+ ScaleRotate = 2,
86
+
87
+ /**
88
+ * Translate transform update (x/y/width/height/pivot/mount)
89
+ *
90
+ * @remarks
91
+ * CoreNode Properties Updated:
92
+ * - `localTransform`
93
+ */
94
+ Local = 4,
95
+
96
+ /**
97
+ * Global Transform update
98
+ *
99
+ * @remarks
100
+ * CoreNode Properties Updated:
101
+ * - `globalTransform`
102
+ * - `renderCoords`
103
+ * - `renderBound`
104
+ */
105
+ Global = 8,
106
+
107
+ /**
108
+ * Clipping rect update
109
+ *
110
+ * @remarks
111
+ * CoreNode Properties Updated:
112
+ * - `clippingRect`
113
+ */
114
+ Clipping = 16,
115
+
116
+ /**
117
+ * Calculated ZIndex update
118
+ *
119
+ * @remarks
120
+ * CoreNode Properties Updated:
121
+ * - `calcZIndex`
122
+ */
123
+ CalculatedZIndex = 32,
124
+
125
+ /**
126
+ * Z-Index Sorted Children update
127
+ *
128
+ * @remarks
129
+ * CoreNode Properties Updated:
130
+ * - `children` (sorts children by their `calcZIndex`)
131
+ */
132
+ ZIndexSortedChildren = 64,
133
+
134
+ /**
135
+ * Premultiplied Colors update
136
+ *
137
+ * @remarks
138
+ * CoreNode Properties Updated:
139
+ * - `premultipliedColorTl`
140
+ * - `premultipliedColorTr`
141
+ * - `premultipliedColorBl`
142
+ * - `premultipliedColorBr`
143
+ */
144
+ PremultipliedColors = 128,
145
+
146
+ /**
147
+ * World Alpha update
148
+ *
149
+ * @remarks
150
+ * CoreNode Properties Updated:
151
+ * - `worldAlpha` = `parent.worldAlpha` * `alpha`
152
+ */
153
+ WorldAlpha = 256,
154
+
155
+ /**
156
+ * Render State update
157
+ *
158
+ * @remarks
159
+ * CoreNode Properties Updated:
160
+ * - `renderState`
161
+ */
162
+ RenderState = 512,
163
+
164
+ /**
165
+ * Is Renderable update
166
+ *
167
+ * @remarks
168
+ * CoreNode Properties Updated:
169
+ * - `isRenderable`
170
+ */
171
+ IsRenderable = 1024,
172
+
173
+ /**
174
+ * Render Texture update
175
+ */
176
+ RenderTexture = 2048,
177
+
178
+ /**
179
+ * Track if parent has render texture
180
+ */
181
+ ParentRenderTexture = 4096,
182
+
183
+ /**
184
+ * Render Bounds update
185
+ */
186
+ RenderBounds = 8192,
187
+
188
+ /**
189
+ * None
190
+ */
191
+ None = 0,
192
+
193
+ /**
194
+ * All
195
+ */
196
+ All = 14335,
197
+ }
198
+
199
+ /**
200
+ * A custom data map which can be stored on an CoreNode
201
+ *
202
+ * @remarks
203
+ * This is a map of key-value pairs that can be stored on an INode. It is used
204
+ * to store custom data that can be used by the application.
205
+ * The data stored can only be of type string, number or boolean.
206
+ */
207
+ export type CustomDataMap = {
208
+ [key: string]: string | number | boolean | undefined;
209
+ };
210
+
211
+ /**
212
+ * Writable properties of a Node.
213
+ */
214
+ export interface CoreNodeProps {
215
+ /**
216
+ * The x coordinate of the Node's Mount Point.
217
+ *
218
+ * @remarks
219
+ * See {@link mountX} and {@link mountY} for more information about setting
220
+ * the Mount Point.
221
+ *
222
+ * @default `0`
223
+ */
224
+ x: number;
225
+ /**
226
+ * The y coordinate of the Node's Mount Point.
227
+ *
228
+ * @remarks
229
+ * See {@link mountX} and {@link mountY} for more information about setting
230
+ * the Mount Point.
231
+ *
232
+ * @default `0`
233
+ */
234
+ y: number;
235
+ /**
236
+ * The width of the Node.
237
+ *
238
+ * @default `0`
239
+ */
240
+ width: number;
241
+ /**
242
+ * The height of the Node.
243
+ *
244
+ * @default `0`
245
+ */
246
+ height: number;
247
+ /**
248
+ * The alpha opacity of the Node.
249
+ *
250
+ * @remarks
251
+ * The alpha value is a number between 0 and 1, where 0 is fully transparent
252
+ * and 1 is fully opaque.
253
+ *
254
+ * @default `1`
255
+ */
256
+ alpha: number;
257
+ /**
258
+ * Autosize mode
259
+ *
260
+ * @remarks
261
+ * When enabled, when a texture is loaded into the Node, the Node will
262
+ * automatically resize to the dimensions of the texture.
263
+ *
264
+ * Text Nodes are always autosized based on their text content regardless
265
+ * of this mode setting.
266
+ *
267
+ * @default `false`
268
+ */
269
+ autosize: boolean;
270
+ /**
271
+ * Clipping Mode
272
+ *
273
+ * @remarks
274
+ * Enable Clipping Mode when you want to prevent the drawing of a Node and
275
+ * its descendants from overflowing outside of the Node's x/y/width/height
276
+ * bounds.
277
+ *
278
+ * For WebGL, clipping is implemented using the high-performance WebGL
279
+ * operation scissor. As a consequence, clipping does not work for
280
+ * non-rectangular areas. So, if the element is rotated
281
+ * (by itself or by any of its ancestors), clipping will not work as intended.
282
+ *
283
+ * TODO: Add support for non-rectangular clipping either automatically or
284
+ * via Render-To-Texture.
285
+ *
286
+ * @default `false`
287
+ */
288
+ clipping: boolean;
289
+ /**
290
+ * The color of the Node.
291
+ *
292
+ * @remarks
293
+ * The color value is a number in the format 0xRRGGBBAA, where RR is the red
294
+ * component, GG is the green component, BB is the blue component, and AA is
295
+ * the alpha component.
296
+ *
297
+ * Gradient colors may be set by setting the different color sub-properties:
298
+ * {@link colorTop}, {@link colorBottom}, {@link colorLeft}, {@link colorRight},
299
+ * {@link colorTl}, {@link colorTr}, {@link colorBr}, {@link colorBl} accordingly.
300
+ *
301
+ * @default `0xffffffff` (opaque white)
302
+ */
303
+ color: number;
304
+ /**
305
+ * The color of the top edge of the Node for gradient rendering.
306
+ *
307
+ * @remarks
308
+ * See {@link color} for more information about color values and gradient
309
+ * rendering.
310
+ */
311
+ colorTop: number;
312
+ /**
313
+ * The color of the bottom edge of the Node for gradient rendering.
314
+ *
315
+ * @remarks
316
+ * See {@link color} for more information about color values and gradient
317
+ * rendering.
318
+ */
319
+ colorBottom: number;
320
+ /**
321
+ * The color of the left edge of the Node for gradient rendering.
322
+ *
323
+ * @remarks
324
+ * See {@link color} for more information about color values and gradient
325
+ * rendering.
326
+ */
327
+ colorLeft: number;
328
+ /**
329
+ * The color of the right edge of the Node for gradient rendering.
330
+ *
331
+ * @remarks
332
+ * See {@link color} for more information about color values and gradient
333
+ * rendering.
334
+ */
335
+ colorRight: number;
336
+ /**
337
+ * The color of the top-left corner of the Node for gradient rendering.
338
+ *
339
+ * @remarks
340
+ * See {@link color} for more information about color values and gradient
341
+ * rendering.
342
+ */
343
+ colorTl: number;
344
+ /**
345
+ * The color of the top-right corner of the Node for gradient rendering.
346
+ *
347
+ * @remarks
348
+ * See {@link color} for more information about color values and gradient
349
+ * rendering.
350
+ */
351
+ colorTr: number;
352
+ /**
353
+ * The color of the bottom-right corner of the Node for gradient rendering.
354
+ *
355
+ * @remarks
356
+ * See {@link color} for more information about color values and gradient
357
+ * rendering.
358
+ */
359
+ colorBr: number;
360
+ /**
361
+ * The color of the bottom-left corner of the Node for gradient rendering.
362
+ *
363
+ * @remarks
364
+ * See {@link color} for more information about color values and gradient
365
+ * rendering.
366
+ */
367
+ colorBl: number;
368
+ /**
369
+ * The Node's parent Node.
370
+ *
371
+ * @remarks
372
+ * The value `null` indicates that the Node has no parent. This may either be
373
+ * because the Node is the root Node of the scene graph, or because the Node
374
+ * has been removed from the scene graph.
375
+ *
376
+ * In order to make sure that a Node can be rendered on the screen, it must
377
+ * be added to the scene graph by setting it's parent property to a Node that
378
+ * is already in the scene graph such as the root Node.
379
+ *
380
+ * @default `null`
381
+ */
382
+ parent: CoreNode | null;
383
+ /**
384
+ * The Node's z-index.
385
+ *
386
+ * @remarks
387
+ * TBD
388
+ */
389
+ zIndex: number;
390
+ /**
391
+ * The Node's Texture.
392
+ *
393
+ * @remarks
394
+ * The `texture` defines a rasterized image that is contained within the
395
+ * {@link width} and {@link height} dimensions of the Node. If null, the
396
+ * Node will use an opaque white {@link ColorTexture} when being drawn, which
397
+ * essentially enables colors (including gradients) to be drawn.
398
+ *
399
+ * If set, by default, the texture will be drawn, as is, stretched to the
400
+ * dimensions of the Node. This behavior can be modified by setting the TBD
401
+ * and TBD properties.
402
+ *
403
+ * To create a Texture in order to set it on this property, call
404
+ * {@link RendererMain.createTexture}.
405
+ *
406
+ * If the {@link src} is set on a Node, the Node will use the
407
+ * {@link ImageTexture} by default and the Node will simply load the image at
408
+ * the specified URL.
409
+ *
410
+ * Note: If this is a Text Node, the Texture will be managed by the Node's
411
+ * {@link TextRenderer} and should not be set explicitly.
412
+ */
413
+ texture: Texture | null;
414
+
415
+ /**
416
+ * Whether to prevent the node from being cleaned up
417
+ * @default false
418
+ */
419
+ preventCleanup: boolean;
420
+ /**
421
+ * Options to associate with the Node's Texture
422
+ */
423
+ textureOptions: TextureOptions;
424
+
425
+ /**
426
+ * The Node's shader
427
+ *
428
+ * @remarks
429
+ * The `shader` defines a {@link Shader} used to draw the Node. By default,
430
+ * the Default Shader is used which simply draws the defined {@link texture}
431
+ * or {@link color}(s) within the Node without any special effects.
432
+ *
433
+ * To create a Shader in order to set it on this property, call
434
+ * {@link RendererMain.createShader}.
435
+ *
436
+ * Note: If this is a Text Node, the Shader will be managed by the Node's
437
+ * {@link TextRenderer} and should not be set explicitly.
438
+ */
439
+ shader: BaseShaderController;
440
+ /**
441
+ * Image URL
442
+ *
443
+ * @remarks
444
+ * When set, the Node's {@link texture} is automatically set to an
445
+ * {@link ImageTexture} using the source image URL provided (with all other
446
+ * settings being defaults)
447
+ */
448
+ src: string | null;
449
+ zIndexLocked: number;
450
+ /**
451
+ * Scale to render the Node at
452
+ *
453
+ * @remarks
454
+ * The scale value multiplies the provided {@link width} and {@link height}
455
+ * of the Node around the Node's Pivot Point (defined by the {@link pivot}
456
+ * props).
457
+ *
458
+ * Behind the scenes, setting this property sets both the {@link scaleX} and
459
+ * {@link scaleY} props to the same value.
460
+ *
461
+ * NOTE: When the scaleX and scaleY props are explicitly set to different values,
462
+ * this property returns `null`. Setting `null` on this property will have no
463
+ * effect.
464
+ *
465
+ * @default 1.0
466
+ */
467
+ scale: number | null;
468
+ /**
469
+ * Scale to render the Node at (X-Axis)
470
+ *
471
+ * @remarks
472
+ * The scaleX value multiplies the provided {@link width} of the Node around
473
+ * the Node's Pivot Point (defined by the {@link pivot} props).
474
+ *
475
+ * @default 1.0
476
+ */
477
+ scaleX: number;
478
+ /**
479
+ * Scale to render the Node at (Y-Axis)
480
+ *
481
+ * @remarks
482
+ * The scaleY value multiplies the provided {@link height} of the Node around
483
+ * the Node's Pivot Point (defined by the {@link pivot} props).
484
+ *
485
+ * @default 1.0
486
+ */
487
+ scaleY: number;
488
+ /**
489
+ * Combined position of the Node's Mount Point
490
+ *
491
+ * @remarks
492
+ * The value can be any number between `0.0` and `1.0`:
493
+ * - `0.0` defines the Mount Point at the top-left corner of the Node.
494
+ * - `0.5` defines it at the center of the Node.
495
+ * - `1.0` defines it at the bottom-right corner of the node.
496
+ *
497
+ * Use the {@link mountX} and {@link mountY} props seperately for more control
498
+ * of the Mount Point.
499
+ *
500
+ * When assigned, the same value is also passed to both the {@link mountX} and
501
+ * {@link mountY} props.
502
+ *
503
+ * @default 0 (top-left)
504
+ */
505
+ mount: number;
506
+ /**
507
+ * X position of the Node's Mount Point
508
+ *
509
+ * @remarks
510
+ * The value can be any number between `0.0` and `1.0`:
511
+ * - `0.0` defines the Mount Point's X position as the left-most edge of the
512
+ * Node
513
+ * - `0.5` defines it as the horizontal center of the Node
514
+ * - `1.0` defines it as the right-most edge of the Node.
515
+ *
516
+ * The combination of {@link mountX} and {@link mountY} define the Mount Point
517
+ *
518
+ * @default 0 (left-most edge)
519
+ */
520
+ mountX: number;
521
+ /**
522
+ * Y position of the Node's Mount Point
523
+ *
524
+ * @remarks
525
+ * The value can be any number between `0.0` and `1.0`:
526
+ * - `0.0` defines the Mount Point's Y position as the top-most edge of the
527
+ * Node
528
+ * - `0.5` defines it as the vertical center of the Node
529
+ * - `1.0` defines it as the bottom-most edge of the Node.
530
+ *
531
+ * The combination of {@link mountX} and {@link mountY} define the Mount Point
532
+ *
533
+ * @default 0 (top-most edge)
534
+ */
535
+ mountY: number;
536
+ /**
537
+ * Combined position of the Node's Pivot Point
538
+ *
539
+ * @remarks
540
+ * The value can be any number between `0.0` and `1.0`:
541
+ * - `0.0` defines the Pivot Point at the top-left corner of the Node.
542
+ * - `0.5` defines it at the center of the Node.
543
+ * - `1.0` defines it at the bottom-right corner of the node.
544
+ *
545
+ * Use the {@link pivotX} and {@link pivotY} props seperately for more control
546
+ * of the Pivot Point.
547
+ *
548
+ * When assigned, the same value is also passed to both the {@link pivotX} and
549
+ * {@link pivotY} props.
550
+ *
551
+ * @default 0.5 (center)
552
+ */
553
+ pivot: number;
554
+ /**
555
+ * X position of the Node's Pivot Point
556
+ *
557
+ * @remarks
558
+ * The value can be any number between `0.0` and `1.0`:
559
+ * - `0.0` defines the Pivot Point's X position as the left-most edge of the
560
+ * Node
561
+ * - `0.5` defines it as the horizontal center of the Node
562
+ * - `1.0` defines it as the right-most edge of the Node.
563
+ *
564
+ * The combination of {@link pivotX} and {@link pivotY} define the Pivot Point
565
+ *
566
+ * @default 0.5 (centered on x-axis)
567
+ */
568
+ pivotX: number;
569
+ /**
570
+ * Y position of the Node's Pivot Point
571
+ *
572
+ * @remarks
573
+ * The value can be any number between `0.0` and `1.0`:
574
+ * - `0.0` defines the Pivot Point's Y position as the top-most edge of the
575
+ * Node
576
+ * - `0.5` defines it as the vertical center of the Node
577
+ * - `1.0` defines it as the bottom-most edge of the Node.
578
+ *
579
+ * The combination of {@link pivotX} and {@link pivotY} define the Pivot Point
580
+ *
581
+ * @default 0.5 (centered on y-axis)
582
+ */
583
+ pivotY: number;
584
+ /**
585
+ * Rotation of the Node (in Radians)
586
+ *
587
+ * @remarks
588
+ * Sets the amount to rotate the Node by around it's Pivot Point (defined by
589
+ * the {@link pivot} props). Positive values rotate the Node clockwise, while
590
+ * negative values rotate it counter-clockwise.
591
+ *
592
+ * Example values:
593
+ * - `-Math.PI / 2`: 90 degree rotation counter-clockwise
594
+ * - `0`: No rotation
595
+ * - `Math.PI / 2`: 90 degree rotation clockwise
596
+ * - `Math.PI`: 180 degree rotation clockwise
597
+ * - `3 * Math.PI / 2`: 270 degree rotation clockwise
598
+ * - `2 * Math.PI`: 360 rotation clockwise
599
+ */
600
+ rotation: number;
601
+
602
+ /**
603
+ * Whether the Node is rendered to a texture
604
+ *
605
+ * @remarks
606
+ * TBD
607
+ *
608
+ * @default false
609
+ */
610
+ rtt: boolean;
611
+
612
+ /**
613
+ * Node data element for custom data storage (optional)
614
+ *
615
+ * @remarks
616
+ * This property is used to store custom data on the Node as a key/value data store.
617
+ * Data values are limited to string, numbers, booleans. Strings will be truncated
618
+ * to a 2048 character limit for performance reasons.
619
+ *
620
+ * This is not a data storage mechanism for large amounts of data please use a
621
+ * dedicated data storage mechanism for that.
622
+ *
623
+ * The custom data will be reflected in the inspector as part of `data-*` attributes
624
+ *
625
+ * @default `undefined`
626
+ */
627
+ data?: CustomDataMap;
628
+
629
+ /**
630
+ * Image Type to explicitly set the image type that is being loaded
631
+ *
632
+ * @remarks
633
+ * This property must be used with a `src` that points at an image. In some cases
634
+ * the extension doesn't provide a reliable representation of the image type. In such
635
+ * cases set the ImageType explicitly.
636
+ *
637
+ * `regular` is used for normal images such as png, jpg, etc
638
+ * `compressed` is used for ETC1/ETC2 compressed images with a PVR or KTX container
639
+ * `svg` is used for scalable vector graphics
640
+ *
641
+ * @default `undefined`
642
+ */
643
+ imageType?: 'regular' | 'compressed' | 'svg' | null;
644
+
645
+ /**
646
+ * She width of the rectangle from which the Image Texture will be extracted.
647
+ * This value can be negative. If not provided, the image's source natural
648
+ * width will be used.
649
+ */
650
+ srcWidth?: number;
651
+ /**
652
+ * The height of the rectangle from which the Image Texture will be extracted.
653
+ * This value can be negative. If not provided, the image's source natural
654
+ * height will be used.
655
+ */
656
+ srcHeight?: number;
657
+ /**
658
+ * The x coordinate of the reference point of the rectangle from which the Texture
659
+ * will be extracted. `width` and `height` are provided. And only works when
660
+ * createImageBitmap is available. Only works when createImageBitmap is supported on the browser.
661
+ */
662
+ srcX?: number;
663
+ /**
664
+ * The y coordinate of the reference point of the rectangle from which the Texture
665
+ * will be extracted. Only used when source `srcWidth` width and `srcHeight` height
666
+ * are provided. Only works when createImageBitmap is supported on the browser.
667
+ */
668
+ srcY?: number;
669
+ /**
670
+ * By enabling Strict bounds the renderer will not process & render child nodes of a node that is out of the visible area
671
+ *
672
+ * @remarks
673
+ * When enabled out of bound nodes, i.e. nodes that are out of the visible area, will
674
+ * **NOT** have their children processed and renderer anymore. This means the children of a out of bound
675
+ * node will not receive update processing such as positioning updates and will not be drawn on screen.
676
+ * As such the rest of the branch of the update tree that sits below this node will not be processed anymore
677
+ *
678
+ * This is a big performance gain but may be disabled in cases where the width of the parent node is
679
+ * unknown and the render must process the child nodes regardless of the viewport status of the parent node
680
+ *
681
+ * @default false
682
+ */
683
+ strictBounds: boolean;
684
+ }
685
+
686
+ /**
687
+ * Grab all the number properties of type T
688
+ */
689
+ type NumberProps<T> = {
690
+ [Key in keyof T as NonNullable<T[Key]> extends number ? Key : never]: number;
691
+ };
692
+
693
+ /**
694
+ * Properties of a Node used by the animate() function
695
+ */
696
+ export interface CoreNodeAnimateProps extends NumberProps<CoreNodeProps> {
697
+ /**
698
+ * Shader properties to animate
699
+ */
700
+ shaderProps: Record<string, number>;
701
+ // TODO: textureProps: Record<string, number>;
702
+ }
703
+
704
+ /**
705
+ * A visual Node in the Renderer scene graph.
706
+ *
707
+ * @remarks
708
+ * CoreNode is an internally used class that represents a Renderer Node in the
709
+ * scene graph. See INode.ts for the public APIs exposed to Renderer users
710
+ * that include generic types for Shaders.
711
+ */
712
+ export class CoreNode extends EventEmitter {
713
+ readonly children: CoreNode[] = [];
714
+ protected _id: number = getNewId();
715
+ readonly props: CoreNodeProps;
716
+
717
+ public updateType = UpdateType.All;
718
+ public childUpdateType = UpdateType.None;
719
+
720
+ public globalTransform?: Matrix3d;
721
+ public scaleRotateTransform?: Matrix3d;
722
+ public localTransform?: Matrix3d;
723
+ public renderCoords?: RenderCoords;
724
+ public renderBound?: Bound;
725
+ public strictBound?: Bound;
726
+ public preloadBound?: Bound;
727
+ public clippingRect: RectWithValid = {
728
+ x: 0,
729
+ y: 0,
730
+ width: 0,
731
+ height: 0,
732
+ valid: false,
733
+ };
734
+ public isRenderable = false;
735
+ public renderState: CoreNodeRenderState = CoreNodeRenderState.Init;
736
+
737
+ public worldAlpha = 1;
738
+ public premultipliedColorTl = 0;
739
+ public premultipliedColorTr = 0;
740
+ public premultipliedColorBl = 0;
741
+ public premultipliedColorBr = 0;
742
+ public calcZIndex = 0;
743
+ public hasRTTupdates = false;
744
+ public parentHasRenderTexture = false;
745
+ public rttParent: CoreNode | null = null;
746
+
747
+ constructor(readonly stage: Stage, props: CoreNodeProps) {
748
+ super();
749
+
750
+ this.props = {
751
+ ...props,
752
+ parent: null,
753
+ texture: null,
754
+ src: null,
755
+ rtt: false,
756
+ };
757
+
758
+ // Assign props to instance
759
+ this.parent = props.parent;
760
+ this.texture = props.texture;
761
+ this.src = props.src;
762
+ this.rtt = props.rtt;
763
+
764
+ this.setUpdateType(
765
+ UpdateType.ScaleRotate |
766
+ UpdateType.Local |
767
+ UpdateType.RenderBounds |
768
+ UpdateType.RenderState,
769
+ );
770
+
771
+ this.createDefaultTexture();
772
+ }
773
+
774
+ //#region Textures
775
+ loadTexture(): void {
776
+ const { texture } = this.props;
777
+ assertTruthy(texture);
778
+
779
+ // If texture is already loaded / failed, trigger loaded event manually
780
+ // so that users get a consistent event experience.
781
+ // We do this in a microtask to allow listeners to be attached in the same
782
+ // synchronous task after calling loadTexture()
783
+ queueMicrotask(() => {
784
+ texture.preventCleanup = this.props.preventCleanup;
785
+ texture.on('loaded', this.onTextureLoaded);
786
+ texture.on('failed', this.onTextureFailed);
787
+ texture.on('freed', this.onTextureFreed);
788
+
789
+ // If the parent is a render texture, the initial texture status
790
+ // will be set to freed until the texture is processed by the
791
+ // Render RTT nodes. So we only need to listen fo changes and
792
+ // no need to check the texture.state until we restructure how
793
+ // textures are being processed.
794
+ if (this.parentHasRenderTexture) {
795
+ this.notifyParentRTTOfUpdate();
796
+ return;
797
+ }
798
+
799
+ if (texture.state === 'loaded') {
800
+ assertTruthy(texture.dimensions);
801
+ this.onTextureLoaded(texture, texture.dimensions);
802
+ } else if (texture.state === 'failed') {
803
+ assertTruthy(texture.error);
804
+ this.onTextureFailed(texture, texture.error);
805
+ } else if (texture.state === 'freed') {
806
+ this.onTextureFreed(texture);
807
+ }
808
+ });
809
+ }
810
+
811
+ createDefaultTexture(): void {
812
+ // load default texture if no texture is set
813
+ if (
814
+ this.stage.defaultTexture !== null &&
815
+ this.props.src === null &&
816
+ this.props.texture === null &&
817
+ this.props.rtt === false
818
+ ) {
819
+ this.texture = this.stage.defaultTexture;
820
+ }
821
+ }
822
+
823
+ unloadTexture(): void {
824
+ if (this.texture !== null) {
825
+ this.texture.off('loaded', this.onTextureLoaded);
826
+ this.texture.off('failed', this.onTextureFailed);
827
+ this.texture.off('freed', this.onTextureFreed);
828
+ this.texture.setRenderableOwner(this, false);
829
+ }
830
+ }
831
+
832
+ autosizeNode(dimensions: Dimensions) {
833
+ if (this.autosize) {
834
+ this.width = dimensions.width;
835
+ this.height = dimensions.height;
836
+ }
837
+ }
838
+
839
+ private onTextureLoaded: TextureLoadedEventHandler = (_, dimensions) => {
840
+ this.autosizeNode(dimensions);
841
+ this.setUpdateType(UpdateType.IsRenderable);
842
+
843
+ // Texture was loaded. In case the RAF loop has already stopped, we request
844
+ // a render to ensure the texture is rendered.
845
+ this.stage.requestRender();
846
+
847
+ // If parent has a render texture, flag that we need to update
848
+ if (this.parentHasRenderTexture) {
849
+ this.notifyParentRTTOfUpdate();
850
+ }
851
+
852
+ // ignore 1x1 pixel textures
853
+ if (dimensions.width > 1 && dimensions.height > 1) {
854
+ this.emit('loaded', {
855
+ type: 'texture',
856
+ dimensions,
857
+ } satisfies NodeTextureLoadedPayload);
858
+ }
859
+
860
+ // Trigger a local update if the texture is loaded and the resizeMode is 'contain'
861
+ if (this.props.textureOptions?.resizeMode?.type === 'contain') {
862
+ this.setUpdateType(UpdateType.Local);
863
+ }
864
+ };
865
+
866
+ private onTextureFailed: TextureFailedEventHandler = (_, error) => {
867
+ this.setUpdateType(UpdateType.IsRenderable);
868
+
869
+ // If parent has a render texture, flag that we need to update
870
+ if (this.parentHasRenderTexture) {
871
+ this.notifyParentRTTOfUpdate();
872
+ }
873
+
874
+ this.emit('failed', {
875
+ type: 'texture',
876
+ error,
877
+ } satisfies NodeTextureFailedPayload);
878
+ };
879
+
880
+ private onTextureFreed: TextureFreedEventHandler = () => {
881
+ this.setUpdateType(UpdateType.IsRenderable);
882
+
883
+ // If parent has a render texture, flag that we need to update
884
+ if (this.parentHasRenderTexture) {
885
+ this.notifyParentRTTOfUpdate();
886
+ }
887
+
888
+ this.emit('freed', {
889
+ type: 'texture',
890
+ } satisfies NodeTextureFreedPayload);
891
+ };
892
+ //#endregion Textures
893
+
894
+ /**
895
+ * Change types types is used to determine the scope of the changes being applied
896
+ *
897
+ * @remarks
898
+ * See {@link UpdateType} for more information on each type
899
+ *
900
+ * @param type
901
+ */
902
+ setUpdateType(type: UpdateType): void {
903
+ this.updateType |= type;
904
+
905
+ const parent = this.props.parent;
906
+ if (!parent) return;
907
+
908
+ if ((parent.updateType & UpdateType.Children) === 0) {
909
+ // Inform the parent if it doesn’t already have a child update
910
+ parent.setUpdateType(UpdateType.Children);
911
+ }
912
+ }
913
+
914
+ sortChildren() {
915
+ this.children.sort((a, b) => a.calcZIndex - b.calcZIndex);
916
+ }
917
+
918
+ updateScaleRotateTransform() {
919
+ const { rotation, scaleX, scaleY } = this.props;
920
+
921
+ // optimize simple translation cases
922
+ if (rotation === 0 && scaleX === 1 && scaleY === 1) {
923
+ this.scaleRotateTransform = undefined;
924
+ return;
925
+ }
926
+
927
+ this.scaleRotateTransform = Matrix3d.rotate(
928
+ rotation,
929
+ this.scaleRotateTransform,
930
+ ).scale(scaleX, scaleY);
931
+ }
932
+
933
+ updateLocalTransform() {
934
+ const { x, y, width, height } = this.props;
935
+ const mountTranslateX = this.props.mountX * width;
936
+ const mountTranslateY = this.props.mountY * height;
937
+
938
+ if (this.scaleRotateTransform) {
939
+ const pivotTranslateX = this.props.pivotX * width;
940
+ const pivotTranslateY = this.props.pivotY * height;
941
+
942
+ this.localTransform = Matrix3d.translate(
943
+ x - mountTranslateX + pivotTranslateX,
944
+ y - mountTranslateY + pivotTranslateY,
945
+ this.localTransform,
946
+ )
947
+ .multiply(this.scaleRotateTransform)
948
+ .translate(-pivotTranslateX, -pivotTranslateY);
949
+ } else {
950
+ this.localTransform = Matrix3d.translate(
951
+ x - mountTranslateX,
952
+ y - mountTranslateY,
953
+ this.localTransform,
954
+ );
955
+ }
956
+
957
+ // Handle 'contain' resize mode
958
+ const texture = this.props.texture;
959
+ if (
960
+ texture &&
961
+ texture.dimensions &&
962
+ this.props.textureOptions?.resizeMode?.type === 'contain'
963
+ ) {
964
+ let resizeModeScaleX = 1;
965
+ let resizeModeScaleY = 1;
966
+ let extraX = 0;
967
+ let extraY = 0;
968
+ const { width: tw, height: th } = texture.dimensions;
969
+ const txAspectRatio = tw / th;
970
+ const nodeAspectRatio = width / height;
971
+ if (txAspectRatio > nodeAspectRatio) {
972
+ // Texture is wider than node
973
+ // Center the node vertically (shift down by extraY)
974
+ // Scale the node vertically to maintain original aspect ratio
975
+ const scaleX = width / tw;
976
+ const scaledTxHeight = th * scaleX;
977
+ extraY = (height - scaledTxHeight) / 2;
978
+ resizeModeScaleY = scaledTxHeight / height;
979
+ } else {
980
+ // Texture is taller than node (or equal)
981
+ // Center the node horizontally (shift right by extraX)
982
+ // Scale the node horizontally to maintain original aspect ratio
983
+ const scaleY = height / th;
984
+ const scaledTxWidth = tw * scaleY;
985
+ extraX = (width - scaledTxWidth) / 2;
986
+ resizeModeScaleX = scaledTxWidth / width;
987
+ }
988
+
989
+ // Apply the extra translation and scale to the local transform
990
+ this.localTransform
991
+ .translate(extraX, extraY)
992
+ .scale(resizeModeScaleX, resizeModeScaleY);
993
+ }
994
+
995
+ this.setUpdateType(UpdateType.Global);
996
+ }
997
+
998
+ /**
999
+ * @todo: test for correct calculation flag
1000
+ * @param delta
1001
+ */
1002
+ update(delta: number, parentClippingRect: RectWithValid): void {
1003
+ if (this.updateType & UpdateType.ScaleRotate) {
1004
+ this.updateScaleRotateTransform();
1005
+ this.setUpdateType(UpdateType.Local);
1006
+ }
1007
+
1008
+ if (this.updateType & UpdateType.Local) {
1009
+ this.updateLocalTransform();
1010
+ this.setUpdateType(UpdateType.Global);
1011
+ }
1012
+
1013
+ const parent = this.props.parent;
1014
+ let renderState = null;
1015
+
1016
+ // Handle specific RTT updates at this node level
1017
+ if (this.updateType & UpdateType.RenderTexture && this.rtt) {
1018
+ // Only the RTT node itself triggers `renderToTexture`
1019
+ this.hasRTTupdates = true;
1020
+ this.stage.renderer?.renderToTexture(this);
1021
+ }
1022
+
1023
+ if (this.updateType & UpdateType.Global) {
1024
+ assertTruthy(this.localTransform);
1025
+
1026
+ this.globalTransform = Matrix3d.copy(
1027
+ parent?.globalTransform || this.localTransform,
1028
+ this.globalTransform,
1029
+ );
1030
+
1031
+ if (this.parentHasRenderTexture && this.props.parent?.rtt) {
1032
+ this.globalTransform = Matrix3d.identity();
1033
+ }
1034
+
1035
+ if (parent) {
1036
+ this.globalTransform.multiply(this.localTransform);
1037
+ }
1038
+
1039
+ this.calculateRenderCoords();
1040
+ this.updateBoundingRect();
1041
+
1042
+ this.setUpdateType(UpdateType.RenderState | UpdateType.Children);
1043
+ this.childUpdateType |= UpdateType.Global;
1044
+
1045
+ if (this.clipping === true) {
1046
+ this.setUpdateType(UpdateType.Clipping | UpdateType.RenderBounds);
1047
+ this.childUpdateType |= UpdateType.RenderBounds;
1048
+ }
1049
+ }
1050
+
1051
+ if (this.updateType & UpdateType.RenderBounds) {
1052
+ this.createRenderBounds();
1053
+ this.setUpdateType(UpdateType.RenderState);
1054
+ this.setUpdateType(UpdateType.Children);
1055
+ }
1056
+
1057
+ if (this.updateType & UpdateType.RenderState) {
1058
+ renderState = this.checkRenderBounds();
1059
+ this.setUpdateType(UpdateType.IsRenderable);
1060
+
1061
+ // if we're not going out of bounds, update the render state
1062
+ // this is done so the update loop can finish before we mark a node
1063
+ // as out of bounds
1064
+ if (renderState !== CoreNodeRenderState.OutOfBounds) {
1065
+ this.updateRenderState(renderState);
1066
+ }
1067
+ }
1068
+
1069
+ if (this.updateType & UpdateType.WorldAlpha) {
1070
+ if (parent) {
1071
+ this.worldAlpha = parent.worldAlpha * this.props.alpha;
1072
+ } else {
1073
+ this.worldAlpha = this.props.alpha;
1074
+ }
1075
+ this.setUpdateType(
1076
+ UpdateType.Children |
1077
+ UpdateType.PremultipliedColors |
1078
+ UpdateType.IsRenderable,
1079
+ );
1080
+ this.childUpdateType |= UpdateType.WorldAlpha;
1081
+ }
1082
+
1083
+ if (this.updateType & UpdateType.IsRenderable) {
1084
+ this.updateIsRenderable();
1085
+ }
1086
+
1087
+ if (this.updateType & UpdateType.Clipping) {
1088
+ this.calculateClippingRect(parentClippingRect);
1089
+ this.setUpdateType(UpdateType.Children);
1090
+
1091
+ this.childUpdateType |= UpdateType.Clipping;
1092
+ this.childUpdateType |= UpdateType.RenderBounds;
1093
+ }
1094
+
1095
+ if (this.updateType & UpdateType.PremultipliedColors) {
1096
+ this.premultipliedColorTl = mergeColorAlphaPremultiplied(
1097
+ this.props.colorTl,
1098
+ this.worldAlpha,
1099
+ true,
1100
+ );
1101
+
1102
+ // If all the colors are the same just sent them all to the same value
1103
+ if (
1104
+ this.props.colorTl === this.props.colorTr &&
1105
+ this.props.colorBl === this.props.colorBr &&
1106
+ this.props.colorTl === this.props.colorBl
1107
+ ) {
1108
+ this.premultipliedColorTr =
1109
+ this.premultipliedColorBl =
1110
+ this.premultipliedColorBr =
1111
+ this.premultipliedColorTl;
1112
+ } else {
1113
+ this.premultipliedColorTr = mergeColorAlphaPremultiplied(
1114
+ this.props.colorTr,
1115
+ this.worldAlpha,
1116
+ true,
1117
+ );
1118
+ this.premultipliedColorBl = mergeColorAlphaPremultiplied(
1119
+ this.props.colorBl,
1120
+ this.worldAlpha,
1121
+ true,
1122
+ );
1123
+ this.premultipliedColorBr = mergeColorAlphaPremultiplied(
1124
+ this.props.colorBr,
1125
+ this.worldAlpha,
1126
+ true,
1127
+ );
1128
+ }
1129
+ }
1130
+
1131
+ // No need to update zIndex if there is no parent
1132
+ if (parent !== null && this.updateType & UpdateType.CalculatedZIndex) {
1133
+ this.calculateZIndex();
1134
+ // Tell parent to re-sort children
1135
+ parent.setUpdateType(UpdateType.ZIndexSortedChildren);
1136
+ }
1137
+
1138
+ if (
1139
+ this.props.strictBounds === true &&
1140
+ this.renderState === CoreNodeRenderState.OutOfBounds
1141
+ ) {
1142
+ return;
1143
+ }
1144
+
1145
+ if (this.updateType & UpdateType.Children && this.children.length > 0) {
1146
+ for (let i = 0, length = this.children.length; i < length; i++) {
1147
+ const child = this.children[i] as CoreNode;
1148
+
1149
+ child.setUpdateType(this.childUpdateType);
1150
+
1151
+ if (child.updateType === 0) {
1152
+ continue;
1153
+ }
1154
+
1155
+ let childClippingRect = this.clippingRect;
1156
+ if (this.rtt === true) {
1157
+ childClippingRect = {
1158
+ x: 0,
1159
+ y: 0,
1160
+ width: 0,
1161
+ height: 0,
1162
+ valid: false,
1163
+ };
1164
+ }
1165
+
1166
+ child.update(delta, childClippingRect);
1167
+ }
1168
+ }
1169
+
1170
+ // If the node has an RTT parent and requires a texture re-render, inform the RTT parent
1171
+ // if (this.parentHasRenderTexture && this.updateType & UpdateType.RenderTexture) {
1172
+ // @TODO have a more scoped down updateType for RTT updates
1173
+ if (this.parentHasRenderTexture && this.updateType > 0) {
1174
+ this.notifyParentRTTOfUpdate();
1175
+ }
1176
+
1177
+ // Sorting children MUST happen after children have been updated so
1178
+ // that they have the oppotunity to update their calculated zIndex.
1179
+ if (this.updateType & UpdateType.ZIndexSortedChildren) {
1180
+ // reorder z-index
1181
+ this.sortChildren();
1182
+ }
1183
+
1184
+ // If we're out of bounds, apply the render state now
1185
+ // this is done so nodes can finish their entire update loop before
1186
+ // being marked as out of bounds
1187
+ if (renderState === CoreNodeRenderState.OutOfBounds) {
1188
+ this.updateRenderState(renderState);
1189
+ this.updateIsRenderable();
1190
+ }
1191
+
1192
+ // reset update type
1193
+ this.updateType = 0;
1194
+ this.childUpdateType = 0;
1195
+ }
1196
+
1197
+ private findParentRTTNode(): CoreNode | null {
1198
+ let rttNode: CoreNode | null = this.parent;
1199
+ while (rttNode && !rttNode.rtt) {
1200
+ rttNode = rttNode.parent;
1201
+ }
1202
+ return rttNode;
1203
+ }
1204
+
1205
+ private notifyParentRTTOfUpdate() {
1206
+ if (this.parent === null) {
1207
+ return;
1208
+ }
1209
+
1210
+ const rttNode = this.rttParent || this.findParentRTTNode();
1211
+ if (!rttNode) {
1212
+ return;
1213
+ }
1214
+
1215
+ // If an RTT node is found, mark it for re-rendering
1216
+ rttNode.hasRTTupdates = true;
1217
+ rttNode.setUpdateType(UpdateType.RenderTexture);
1218
+
1219
+ // if rttNode is nested, also make it update its RTT parent
1220
+ if (rttNode.parentHasRenderTexture === true) {
1221
+ rttNode.notifyParentRTTOfUpdate();
1222
+ }
1223
+ }
1224
+
1225
+ //check if CoreNode is renderable based on props
1226
+ hasRenderableProperties(): boolean {
1227
+ if (this.texture !== null) {
1228
+ if (this.texture.state === 'loaded') {
1229
+ return true;
1230
+ }
1231
+
1232
+ return false;
1233
+ }
1234
+
1235
+ if (!this.props.width || !this.props.height) {
1236
+ return false;
1237
+ }
1238
+
1239
+ if (this.props.shader !== this.stage.defShaderCtr) {
1240
+ return true;
1241
+ }
1242
+
1243
+ if (this.props.clipping === true) {
1244
+ return true;
1245
+ }
1246
+
1247
+ if (this.props.color !== 0) {
1248
+ return true;
1249
+ }
1250
+
1251
+ // Consider removing these checks and just using the color property check above.
1252
+ // Maybe add a forceRender prop for nodes that should always render.
1253
+ if (
1254
+ this.props.colorTop !== 0 ||
1255
+ this.props.colorBottom !== 0 ||
1256
+ this.props.colorLeft !== 0 ||
1257
+ this.props.colorRight !== 0 ||
1258
+ this.props.colorTl !== 0 ||
1259
+ this.props.colorTr !== 0 ||
1260
+ this.props.colorBl !== 0 ||
1261
+ this.props.colorBr !== 0
1262
+ ) {
1263
+ return true;
1264
+ }
1265
+
1266
+ return false;
1267
+ }
1268
+
1269
+ checkRenderBounds(): CoreNodeRenderState {
1270
+ assertTruthy(this.renderBound);
1271
+ assertTruthy(this.strictBound);
1272
+ assertTruthy(this.preloadBound);
1273
+
1274
+ if (boundInsideBound(this.renderBound, this.strictBound)) {
1275
+ return CoreNodeRenderState.InViewport;
1276
+ }
1277
+
1278
+ if (boundInsideBound(this.renderBound, this.preloadBound)) {
1279
+ return CoreNodeRenderState.InBounds;
1280
+ }
1281
+
1282
+ // check if we're larger then our parent, we're definitely in the viewport
1283
+ if (boundLargeThanBound(this.renderBound, this.strictBound)) {
1284
+ return CoreNodeRenderState.InViewport;
1285
+ }
1286
+
1287
+ // if we are part of a parent render texture, we're always in bounds
1288
+ if (this.parentHasRenderTexture === true) {
1289
+ return CoreNodeRenderState.InBounds;
1290
+ }
1291
+
1292
+ // check if we dont have dimensions, take our parent's render state
1293
+ if (
1294
+ this.parent !== null &&
1295
+ (this.props.width === 0 || this.props.height === 0)
1296
+ ) {
1297
+ return this.parent.renderState;
1298
+ }
1299
+
1300
+ return CoreNodeRenderState.OutOfBounds;
1301
+ }
1302
+
1303
+ updateBoundingRect() {
1304
+ const { renderCoords, globalTransform: transform } = this;
1305
+ assertTruthy(transform);
1306
+ assertTruthy(renderCoords);
1307
+
1308
+ const { tb, tc } = transform;
1309
+ const { x1, y1, x3, y3 } = renderCoords;
1310
+ if (tb === 0 || tc === 0) {
1311
+ this.renderBound = createBound(x1, y1, x3, y3, this.renderBound);
1312
+ } else {
1313
+ const { x2, x4, y2, y4 } = renderCoords;
1314
+ this.renderBound = createBound(
1315
+ Math.min(x1, x2, x3, x4),
1316
+ Math.min(y1, y2, y3, y4),
1317
+ Math.max(x1, x2, x3, x4),
1318
+ Math.max(y1, y2, y3, y4),
1319
+ this.renderBound,
1320
+ );
1321
+ }
1322
+ }
1323
+
1324
+ createRenderBounds(): void {
1325
+ assertTruthy(this.stage);
1326
+
1327
+ if (this.parent !== null && this.parent.strictBound !== undefined) {
1328
+ // we have a parent with a valid bound, copy it
1329
+ const parentBound = this.parent.strictBound;
1330
+ this.strictBound = createBound(
1331
+ parentBound.x1,
1332
+ parentBound.y1,
1333
+ parentBound.x2,
1334
+ parentBound.y2,
1335
+ );
1336
+
1337
+ this.preloadBound = createPreloadBounds(
1338
+ this.strictBound,
1339
+ this.stage.boundsMargin,
1340
+ );
1341
+ } else {
1342
+ // no parent or parent does not have a bound, take the stage boundaries
1343
+ this.strictBound = this.stage.strictBound;
1344
+ this.preloadBound = this.stage.preloadBound;
1345
+ }
1346
+
1347
+ // if clipping is disabled, we're done
1348
+ if (this.props.clipping === false) {
1349
+ return;
1350
+ }
1351
+
1352
+ // only create local clipping bounds if node itself is in bounds
1353
+ // this can only be done if we have a render bound already
1354
+ if (this.renderBound === undefined) {
1355
+ return;
1356
+ }
1357
+
1358
+ // if we're out of bounds, we're done
1359
+ if (boundInsideBound(this.renderBound, this.strictBound) === false) {
1360
+ return;
1361
+ }
1362
+
1363
+ // clipping is enabled and we are in bounds create our own bounds
1364
+ const { x, y, width, height } = this.props;
1365
+ const { tx, ty } = this.globalTransform || {};
1366
+ const _x = tx ?? x;
1367
+ const _y = ty ?? y;
1368
+ this.strictBound = createBound(
1369
+ _x,
1370
+ _y,
1371
+ _x + width,
1372
+ _y + height,
1373
+ this.strictBound,
1374
+ );
1375
+
1376
+ this.preloadBound = createPreloadBounds(
1377
+ this.strictBound,
1378
+ this.stage.boundsMargin,
1379
+ );
1380
+ }
1381
+
1382
+ updateRenderState(renderState: CoreNodeRenderState) {
1383
+ if (renderState === this.renderState) {
1384
+ return;
1385
+ }
1386
+
1387
+ const previous = this.renderState;
1388
+ this.renderState = renderState;
1389
+ const event = CoreNodeRenderStateMap.get(renderState);
1390
+ assertTruthy(event);
1391
+ this.emit(event, {
1392
+ previous,
1393
+ current: renderState,
1394
+ });
1395
+ }
1396
+
1397
+ /**
1398
+ * This function updates the `isRenderable` property based on certain conditions.
1399
+ *
1400
+ * @returns
1401
+ */
1402
+ updateIsRenderable() {
1403
+ let newIsRenderable: boolean;
1404
+ if (this.worldAlpha === 0 || !this.hasRenderableProperties()) {
1405
+ newIsRenderable = false;
1406
+ } else {
1407
+ newIsRenderable = this.renderState > CoreNodeRenderState.OutOfBounds;
1408
+ }
1409
+
1410
+ // If the texture is not loaded and the node is renderable, load the texture
1411
+ // this only needs to happen once or until the texture is no longer loaded
1412
+ if (
1413
+ this.texture !== null &&
1414
+ this.texture.state === 'freed' &&
1415
+ this.renderState > CoreNodeRenderState.OutOfBounds
1416
+ ) {
1417
+ this.stage.txManager.loadTexture(this.texture);
1418
+ }
1419
+
1420
+ if (this.isRenderable !== newIsRenderable) {
1421
+ this.isRenderable = newIsRenderable;
1422
+ this.onChangeIsRenderable(newIsRenderable);
1423
+ }
1424
+ }
1425
+
1426
+ onChangeIsRenderable(isRenderable: boolean) {
1427
+ this.texture?.setRenderableOwner(this, isRenderable);
1428
+ }
1429
+
1430
+ calculateRenderCoords() {
1431
+ const { width, height, globalTransform: transform } = this;
1432
+ assertTruthy(transform);
1433
+ const { tx, ty, ta, tb, tc, td } = transform;
1434
+ if (tb === 0 && tc === 0) {
1435
+ const minX = tx;
1436
+ const maxX = tx + width * ta;
1437
+
1438
+ const minY = ty;
1439
+ const maxY = ty + height * td;
1440
+ this.renderCoords = RenderCoords.translate(
1441
+ //top-left
1442
+ minX,
1443
+ minY,
1444
+ //top-right
1445
+ maxX,
1446
+ minY,
1447
+ //bottom-right
1448
+ maxX,
1449
+ maxY,
1450
+ //bottom-left
1451
+ minX,
1452
+ maxY,
1453
+ this.renderCoords,
1454
+ );
1455
+ } else {
1456
+ this.renderCoords = RenderCoords.translate(
1457
+ //top-left
1458
+ tx,
1459
+ ty,
1460
+ //top-right
1461
+ tx + width * ta,
1462
+ ty + width * tc,
1463
+ //bottom-right
1464
+ tx + width * ta + height * tb,
1465
+ ty + width * tc + height * td,
1466
+ //bottom-left
1467
+ tx + height * tb,
1468
+ ty + height * td,
1469
+ this.renderCoords,
1470
+ );
1471
+ }
1472
+ }
1473
+
1474
+ /**
1475
+ * This function calculates the clipping rectangle for a node.
1476
+ *
1477
+ * The function then checks if the node is rotated. If the node requires clipping and is not rotated, a new clipping rectangle is created based on the node's global transform and dimensions.
1478
+ * If a parent clipping rectangle exists, it is intersected with the node's clipping rectangle (if it exists), or replaces the node's clipping rectangle.
1479
+ *
1480
+ * Finally, the node's parentClippingRect and clippingRect properties are updated.
1481
+ */
1482
+ calculateClippingRect(parentClippingRect: RectWithValid) {
1483
+ assertTruthy(this.globalTransform);
1484
+ const { clippingRect, props, globalTransform: gt } = this;
1485
+ const { clipping } = props;
1486
+
1487
+ const isRotated = gt.tb !== 0 || gt.tc !== 0;
1488
+
1489
+ if (clipping === true && isRotated === false) {
1490
+ clippingRect.x = gt.tx;
1491
+ clippingRect.y = gt.ty;
1492
+ clippingRect.width = this.width * gt.ta;
1493
+ clippingRect.height = this.height * gt.td;
1494
+ clippingRect.valid = true;
1495
+ } else {
1496
+ clippingRect.valid = false;
1497
+ }
1498
+
1499
+ if (parentClippingRect.valid === true && clippingRect.valid === true) {
1500
+ // Intersect parent clipping rect with node clipping rect
1501
+ intersectRect(parentClippingRect, clippingRect, clippingRect);
1502
+ } else if (parentClippingRect.valid === true) {
1503
+ // Copy parent clipping rect
1504
+ copyRect(parentClippingRect, clippingRect);
1505
+ clippingRect.valid = true;
1506
+ }
1507
+ }
1508
+
1509
+ calculateZIndex(): void {
1510
+ const props = this.props;
1511
+ const z = props.zIndex || 0;
1512
+ const p = props.parent?.zIndex || 0;
1513
+
1514
+ let zIndex = z;
1515
+ if (props.parent?.zIndexLocked) {
1516
+ zIndex = z < p ? z : p;
1517
+ }
1518
+ this.calcZIndex = zIndex;
1519
+ }
1520
+
1521
+ /**
1522
+ * Destroy the node and cleanup all resources
1523
+ */
1524
+ destroy(): void {
1525
+ this.unloadTexture();
1526
+
1527
+ this.clippingRect.valid = false;
1528
+ this.isRenderable = false;
1529
+
1530
+ this.renderCoords = undefined;
1531
+ this.renderBound = undefined;
1532
+ this.strictBound = undefined;
1533
+ this.preloadBound = undefined;
1534
+ this.globalTransform = undefined;
1535
+ this.scaleRotateTransform = undefined;
1536
+ this.localTransform = undefined;
1537
+
1538
+ this.props.texture = null;
1539
+ this.props.shader = this.stage.defShaderCtr;
1540
+
1541
+ while (this.children.length > 0) {
1542
+ this.children[0]?.destroy();
1543
+ }
1544
+
1545
+ // This very action will also remove the node from the parent's children array
1546
+ this.parent = null;
1547
+
1548
+ if (this.rtt) {
1549
+ this.stage.renderer.removeRTTNode(this);
1550
+ }
1551
+
1552
+ this.removeAllListeners();
1553
+ }
1554
+
1555
+ renderQuads(renderer: CoreRenderer): void {
1556
+ // Prevent quad rendering if parent has a render texture
1557
+ // and renderer is not currently rendering to a texture
1558
+ if (this.parentHasRenderTexture) {
1559
+ if (!renderer.renderToTextureActive) {
1560
+ return;
1561
+ }
1562
+ // Prevent quad rendering if parent render texture is not the active render texture
1563
+ if (this.parentRenderTexture !== renderer.activeRttNode) {
1564
+ return;
1565
+ }
1566
+ }
1567
+
1568
+ assertTruthy(this.globalTransform);
1569
+ assertTruthy(this.renderCoords);
1570
+ assertTruthy(this.texture);
1571
+
1572
+ // add to list of renderables to be sorted before rendering
1573
+ renderer.addQuad({
1574
+ width: this.props.width,
1575
+ height: this.props.height,
1576
+ colorTl: this.premultipliedColorTl,
1577
+ colorTr: this.premultipliedColorTr,
1578
+ colorBl: this.premultipliedColorBl,
1579
+ colorBr: this.premultipliedColorBr,
1580
+ texture: this.texture,
1581
+ textureOptions: this.textureOptions,
1582
+ zIndex: this.zIndex,
1583
+ shader: this.shader.shader,
1584
+ shaderProps: this.shader.getResolvedProps(),
1585
+ alpha: this.worldAlpha,
1586
+ clippingRect: this.clippingRect,
1587
+ tx: this.globalTransform.tx,
1588
+ ty: this.globalTransform.ty,
1589
+ ta: this.globalTransform.ta,
1590
+ tb: this.globalTransform.tb,
1591
+ tc: this.globalTransform.tc,
1592
+ td: this.globalTransform.td,
1593
+ renderCoords: this.renderCoords,
1594
+ rtt: this.rtt,
1595
+ parentHasRenderTexture: this.parentHasRenderTexture,
1596
+ framebufferDimensions: this.framebufferDimensions,
1597
+ });
1598
+ }
1599
+
1600
+ //#region Properties
1601
+ get id(): number {
1602
+ return this._id;
1603
+ }
1604
+
1605
+ get data(): CustomDataMap | undefined {
1606
+ return this.props.data;
1607
+ }
1608
+
1609
+ set data(d: CustomDataMap | undefined) {
1610
+ this.props.data = d;
1611
+ }
1612
+
1613
+ get x(): number {
1614
+ return this.props.x;
1615
+ }
1616
+
1617
+ set x(value: number) {
1618
+ if (this.props.x !== value) {
1619
+ this.props.x = value;
1620
+ this.setUpdateType(UpdateType.Local);
1621
+ }
1622
+ }
1623
+
1624
+ get absX(): number {
1625
+ return (
1626
+ this.props.x +
1627
+ -this.props.width * this.props.mountX +
1628
+ (this.props.parent?.absX || this.props.parent?.globalTransform?.tx || 0)
1629
+ );
1630
+ }
1631
+
1632
+ get absY(): number {
1633
+ return (
1634
+ this.props.y +
1635
+ -this.props.height * this.props.mountY +
1636
+ (this.props.parent?.absY ?? 0)
1637
+ );
1638
+ }
1639
+
1640
+ get y(): number {
1641
+ return this.props.y;
1642
+ }
1643
+
1644
+ set y(value: number) {
1645
+ if (this.props.y !== value) {
1646
+ this.props.y = value;
1647
+ this.setUpdateType(UpdateType.Local);
1648
+ }
1649
+ }
1650
+
1651
+ get width(): number {
1652
+ return this.props.width;
1653
+ }
1654
+
1655
+ set width(value: number) {
1656
+ if (this.props.width !== value) {
1657
+ this.props.width = value;
1658
+ this.setUpdateType(UpdateType.Local);
1659
+
1660
+ if (this.props.rtt) {
1661
+ this.texture = this.stage.txManager.createTexture('RenderTexture', {
1662
+ width: this.width,
1663
+ height: this.height,
1664
+ });
1665
+
1666
+ this.setUpdateType(UpdateType.RenderTexture);
1667
+ }
1668
+ }
1669
+ }
1670
+
1671
+ get height(): number {
1672
+ return this.props.height;
1673
+ }
1674
+
1675
+ set height(value: number) {
1676
+ if (this.props.height !== value) {
1677
+ this.props.height = value;
1678
+ this.setUpdateType(UpdateType.Local);
1679
+
1680
+ if (this.props.rtt) {
1681
+ this.texture = this.stage.txManager.createTexture('RenderTexture', {
1682
+ width: this.width,
1683
+ height: this.height,
1684
+ });
1685
+
1686
+ this.setUpdateType(UpdateType.RenderTexture);
1687
+ }
1688
+ }
1689
+ }
1690
+
1691
+ get scale(): number {
1692
+ // The CoreNode `scale` property is only used by Animations.
1693
+ // Unlike INode, `null` should never be possibility for Animations.
1694
+ return this.scaleX;
1695
+ }
1696
+
1697
+ set scale(value: number) {
1698
+ // The CoreNode `scale` property is only used by Animations.
1699
+ // Unlike INode, `null` should never be possibility for Animations.
1700
+ this.scaleX = value;
1701
+ this.scaleY = value;
1702
+ }
1703
+
1704
+ get scaleX(): number {
1705
+ return this.props.scaleX;
1706
+ }
1707
+
1708
+ set scaleX(value: number) {
1709
+ if (this.props.scaleX !== value) {
1710
+ this.props.scaleX = value;
1711
+ this.setUpdateType(UpdateType.ScaleRotate);
1712
+ }
1713
+ }
1714
+
1715
+ get scaleY(): number {
1716
+ return this.props.scaleY;
1717
+ }
1718
+
1719
+ set scaleY(value: number) {
1720
+ if (this.props.scaleY !== value) {
1721
+ this.props.scaleY = value;
1722
+ this.setUpdateType(UpdateType.ScaleRotate);
1723
+ }
1724
+ }
1725
+
1726
+ get mount(): number {
1727
+ return this.props.mount;
1728
+ }
1729
+
1730
+ set mount(value: number) {
1731
+ if (this.props.mountX !== value || this.props.mountY !== value) {
1732
+ this.props.mountX = value;
1733
+ this.props.mountY = value;
1734
+ this.props.mount = value;
1735
+ this.setUpdateType(UpdateType.Local);
1736
+ }
1737
+ }
1738
+
1739
+ get mountX(): number {
1740
+ return this.props.mountX;
1741
+ }
1742
+
1743
+ set mountX(value: number) {
1744
+ if (this.props.mountX !== value) {
1745
+ this.props.mountX = value;
1746
+ this.setUpdateType(UpdateType.Local);
1747
+ }
1748
+ }
1749
+
1750
+ get mountY(): number {
1751
+ return this.props.mountY;
1752
+ }
1753
+
1754
+ set mountY(value: number) {
1755
+ if (this.props.mountY !== value) {
1756
+ this.props.mountY = value;
1757
+ this.setUpdateType(UpdateType.Local);
1758
+ }
1759
+ }
1760
+
1761
+ get pivot(): number {
1762
+ return this.props.pivot;
1763
+ }
1764
+
1765
+ set pivot(value: number) {
1766
+ if (this.props.pivotX !== value || this.props.pivotY !== value) {
1767
+ this.props.pivotX = value;
1768
+ this.props.pivotY = value;
1769
+ this.props.pivot = value;
1770
+ this.setUpdateType(UpdateType.Local);
1771
+ }
1772
+ }
1773
+
1774
+ get pivotX(): number {
1775
+ return this.props.pivotX;
1776
+ }
1777
+
1778
+ set pivotX(value: number) {
1779
+ if (this.props.pivotX !== value) {
1780
+ this.props.pivotX = value;
1781
+ this.setUpdateType(UpdateType.Local);
1782
+ }
1783
+ }
1784
+
1785
+ get pivotY(): number {
1786
+ return this.props.pivotY;
1787
+ }
1788
+
1789
+ set pivotY(value: number) {
1790
+ if (this.props.pivotY !== value) {
1791
+ this.props.pivotY = value;
1792
+ this.setUpdateType(UpdateType.Local);
1793
+ }
1794
+ }
1795
+
1796
+ get rotation(): number {
1797
+ return this.props.rotation;
1798
+ }
1799
+
1800
+ set rotation(value: number) {
1801
+ if (this.props.rotation !== value) {
1802
+ this.props.rotation = value;
1803
+ this.setUpdateType(UpdateType.ScaleRotate);
1804
+ }
1805
+ }
1806
+
1807
+ get alpha(): number {
1808
+ return this.props.alpha;
1809
+ }
1810
+
1811
+ set alpha(value: number) {
1812
+ this.props.alpha = value;
1813
+ this.setUpdateType(
1814
+ UpdateType.PremultipliedColors |
1815
+ UpdateType.WorldAlpha |
1816
+ UpdateType.Children |
1817
+ UpdateType.IsRenderable,
1818
+ );
1819
+ this.childUpdateType |= UpdateType.WorldAlpha;
1820
+ }
1821
+
1822
+ get autosize(): boolean {
1823
+ return this.props.autosize;
1824
+ }
1825
+
1826
+ set autosize(value: boolean) {
1827
+ this.props.autosize = value;
1828
+ }
1829
+
1830
+ get clipping(): boolean {
1831
+ return this.props.clipping;
1832
+ }
1833
+
1834
+ set clipping(value: boolean) {
1835
+ this.props.clipping = value;
1836
+ this.setUpdateType(
1837
+ UpdateType.Clipping | UpdateType.RenderBounds | UpdateType.Children,
1838
+ );
1839
+ this.childUpdateType |= UpdateType.Global | UpdateType.Clipping;
1840
+ }
1841
+
1842
+ get color(): number {
1843
+ return this.props.color;
1844
+ }
1845
+
1846
+ set color(value: number) {
1847
+ this.colorTop = value;
1848
+ this.colorBottom = value;
1849
+ this.colorLeft = value;
1850
+ this.colorRight = value;
1851
+ this.props.color = value;
1852
+
1853
+ this.setUpdateType(UpdateType.PremultipliedColors);
1854
+ }
1855
+
1856
+ get colorTop(): number {
1857
+ return this.props.colorTop;
1858
+ }
1859
+
1860
+ set colorTop(value: number) {
1861
+ if (this.props.colorTl !== value || this.props.colorTr !== value) {
1862
+ this.colorTl = value;
1863
+ this.colorTr = value;
1864
+ }
1865
+ this.props.colorTop = value;
1866
+ this.setUpdateType(UpdateType.PremultipliedColors);
1867
+ }
1868
+
1869
+ get colorBottom(): number {
1870
+ return this.props.colorBottom;
1871
+ }
1872
+
1873
+ set colorBottom(value: number) {
1874
+ if (this.props.colorBl !== value || this.props.colorBr !== value) {
1875
+ this.colorBl = value;
1876
+ this.colorBr = value;
1877
+ }
1878
+ this.props.colorBottom = value;
1879
+ this.setUpdateType(UpdateType.PremultipliedColors);
1880
+ }
1881
+
1882
+ get colorLeft(): number {
1883
+ return this.props.colorLeft;
1884
+ }
1885
+
1886
+ set colorLeft(value: number) {
1887
+ if (this.props.colorTl !== value || this.props.colorBl !== value) {
1888
+ this.colorTl = value;
1889
+ this.colorBl = value;
1890
+ }
1891
+ this.props.colorLeft = value;
1892
+ this.setUpdateType(UpdateType.PremultipliedColors);
1893
+ }
1894
+
1895
+ get colorRight(): number {
1896
+ return this.props.colorRight;
1897
+ }
1898
+
1899
+ set colorRight(value: number) {
1900
+ if (this.props.colorTr !== value || this.props.colorBr !== value) {
1901
+ this.colorTr = value;
1902
+ this.colorBr = value;
1903
+ }
1904
+ this.props.colorRight = value;
1905
+ this.setUpdateType(UpdateType.PremultipliedColors);
1906
+ }
1907
+
1908
+ get colorTl(): number {
1909
+ return this.props.colorTl;
1910
+ }
1911
+
1912
+ set colorTl(value: number) {
1913
+ this.props.colorTl = value;
1914
+ this.setUpdateType(UpdateType.PremultipliedColors);
1915
+ }
1916
+
1917
+ get colorTr(): number {
1918
+ return this.props.colorTr;
1919
+ }
1920
+
1921
+ set colorTr(value: number) {
1922
+ this.props.colorTr = value;
1923
+ this.setUpdateType(UpdateType.PremultipliedColors);
1924
+ }
1925
+
1926
+ get colorBl(): number {
1927
+ return this.props.colorBl;
1928
+ }
1929
+
1930
+ set colorBl(value: number) {
1931
+ this.props.colorBl = value;
1932
+ this.setUpdateType(UpdateType.PremultipliedColors);
1933
+ }
1934
+
1935
+ get colorBr(): number {
1936
+ return this.props.colorBr;
1937
+ }
1938
+
1939
+ set colorBr(value: number) {
1940
+ this.props.colorBr = value;
1941
+ this.setUpdateType(UpdateType.PremultipliedColors);
1942
+ }
1943
+
1944
+ // we're only interested in parent zIndex to test
1945
+ // if we should use node zIndex is higher then parent zIndex
1946
+ get zIndexLocked(): number {
1947
+ return this.props.zIndexLocked || 0;
1948
+ }
1949
+
1950
+ set zIndexLocked(value: number) {
1951
+ this.props.zIndexLocked = value;
1952
+ this.setUpdateType(UpdateType.CalculatedZIndex | UpdateType.Children);
1953
+ for (let i = 0, length = this.children.length; i < length; i++) {
1954
+ this.children[i]!.setUpdateType(UpdateType.CalculatedZIndex);
1955
+ }
1956
+ }
1957
+
1958
+ get zIndex(): number {
1959
+ return this.props.zIndex;
1960
+ }
1961
+
1962
+ set zIndex(value: number) {
1963
+ this.props.zIndex = value;
1964
+ this.setUpdateType(UpdateType.CalculatedZIndex | UpdateType.Children);
1965
+ for (let i = 0, length = this.children.length; i < length; i++) {
1966
+ this.children[i]!.setUpdateType(UpdateType.CalculatedZIndex);
1967
+ }
1968
+ }
1969
+
1970
+ get parent(): CoreNode | null {
1971
+ return this.props.parent;
1972
+ }
1973
+
1974
+ set parent(newParent: CoreNode | null) {
1975
+ const oldParent = this.props.parent;
1976
+ if (oldParent === newParent) {
1977
+ return;
1978
+ }
1979
+ this.props.parent = newParent;
1980
+ if (oldParent) {
1981
+ const index = oldParent.children.indexOf(this);
1982
+ assertTruthy(
1983
+ index !== -1,
1984
+ "CoreNode.parent: Node not found in old parent's children!",
1985
+ );
1986
+ oldParent.children.splice(index, 1);
1987
+ oldParent.setUpdateType(
1988
+ UpdateType.Children | UpdateType.ZIndexSortedChildren,
1989
+ );
1990
+ }
1991
+ if (newParent) {
1992
+ newParent.children.push(this);
1993
+ // Since this node has a new parent, to be safe, have it do a full update.
1994
+ this.setUpdateType(UpdateType.All);
1995
+ // Tell parent that it's children need to be updated and sorted.
1996
+ newParent.setUpdateType(
1997
+ UpdateType.Children | UpdateType.ZIndexSortedChildren,
1998
+ );
1999
+
2000
+ // If the new parent has an RTT enabled, apply RTT inheritance
2001
+ if (newParent.rtt || newParent.parentHasRenderTexture) {
2002
+ this.applyRTTInheritance(newParent);
2003
+ }
2004
+ }
2005
+ this.updateScaleRotateTransform();
2006
+
2007
+ // fetch render bounds from parent
2008
+ this.setUpdateType(UpdateType.RenderBounds | UpdateType.Children);
2009
+ }
2010
+
2011
+ get preventCleanup(): boolean {
2012
+ return this.props.preventCleanup;
2013
+ }
2014
+
2015
+ set preventCleanup(value: boolean) {
2016
+ this.props.preventCleanup = value;
2017
+ }
2018
+
2019
+ get rtt(): boolean {
2020
+ return this.props.rtt;
2021
+ }
2022
+
2023
+ set rtt(value: boolean) {
2024
+ if (this.props.rtt === value) {
2025
+ return;
2026
+ }
2027
+ this.props.rtt = value;
2028
+
2029
+ if (value === true) {
2030
+ this.initRenderTexture();
2031
+ this.markChildrenWithRTT();
2032
+ } else {
2033
+ this.cleanupRenderTexture();
2034
+ }
2035
+
2036
+ this.setUpdateType(UpdateType.RenderTexture);
2037
+
2038
+ if (this.parentHasRenderTexture === true) {
2039
+ this.notifyParentRTTOfUpdate();
2040
+ }
2041
+ }
2042
+ private initRenderTexture() {
2043
+ this.texture = this.stage.txManager.createTexture('RenderTexture', {
2044
+ width: this.width,
2045
+ height: this.height,
2046
+ });
2047
+
2048
+ // call load immediately to ensure the texture is created
2049
+ this.stage.txManager.loadTexture(this.texture, true);
2050
+
2051
+ this.stage.renderer?.renderToTexture(this); // Only this RTT node
2052
+ }
2053
+
2054
+ private cleanupRenderTexture() {
2055
+ this.unloadTexture();
2056
+ this.clearRTTInheritance();
2057
+
2058
+ this.stage.renderer?.removeRTTNode(this);
2059
+ this.hasRTTupdates = false;
2060
+ this.texture = null;
2061
+ }
2062
+
2063
+ private markChildrenWithRTT(node: CoreNode | null = null) {
2064
+ const parent = node || this;
2065
+
2066
+ for (const child of parent.children) {
2067
+ child.setUpdateType(UpdateType.All);
2068
+ child.parentHasRenderTexture = true;
2069
+ child.markChildrenWithRTT();
2070
+ }
2071
+ }
2072
+
2073
+ // Apply RTT inheritance when a node has an RTT-enabled parent
2074
+ private applyRTTInheritance(parent: CoreNode) {
2075
+ if (parent.rtt) {
2076
+ // Only the RTT node should be added to `renderToTexture`
2077
+ parent.setUpdateType(UpdateType.RenderTexture);
2078
+ }
2079
+
2080
+ // Propagate `parentHasRenderTexture` downwards
2081
+ this.markChildrenWithRTT(parent);
2082
+ }
2083
+
2084
+ // Clear RTT inheritance when detaching from an RTT chain
2085
+ private clearRTTInheritance() {
2086
+ // if this node is RTT itself stop the propagation important for nested RTT nodes
2087
+ // for the initial RTT node this is already handled in `set rtt`
2088
+ if (this.rtt) {
2089
+ return;
2090
+ }
2091
+
2092
+ for (const child of this.children) {
2093
+ // force child to update everything as the RTT inheritance has changed
2094
+ child.parentHasRenderTexture = false;
2095
+ child.rttParent = null;
2096
+ child.setUpdateType(UpdateType.All);
2097
+ child.clearRTTInheritance();
2098
+ }
2099
+ }
2100
+
2101
+ get shader(): BaseShaderController {
2102
+ return this.props.shader;
2103
+ }
2104
+
2105
+ set shader(value: BaseShaderController) {
2106
+ if (this.props.shader === value) {
2107
+ return;
2108
+ }
2109
+
2110
+ this.props.shader = value;
2111
+
2112
+ this.setUpdateType(UpdateType.IsRenderable);
2113
+ }
2114
+
2115
+ get src(): string | null {
2116
+ return this.props.src;
2117
+ }
2118
+
2119
+ set src(imageUrl: string | null) {
2120
+ if (this.props.src === imageUrl) {
2121
+ return;
2122
+ }
2123
+
2124
+ this.props.src = imageUrl;
2125
+
2126
+ if (!imageUrl) {
2127
+ this.texture = null;
2128
+ return;
2129
+ }
2130
+
2131
+ this.texture = this.stage.txManager.createTexture('ImageTexture', {
2132
+ src: imageUrl,
2133
+ width: this.props.width,
2134
+ height: this.props.height,
2135
+ type: this.props.imageType,
2136
+ sx: this.props.srcX,
2137
+ sy: this.props.srcY,
2138
+ sw: this.props.srcWidth,
2139
+ sh: this.props.srcHeight,
2140
+ });
2141
+ }
2142
+
2143
+ set imageType(type: 'regular' | 'compressed' | 'svg' | null) {
2144
+ if (this.props.imageType === type) {
2145
+ return;
2146
+ }
2147
+
2148
+ this.props.imageType = type;
2149
+ }
2150
+
2151
+ get imageType() {
2152
+ return this.props.imageType || null;
2153
+ }
2154
+
2155
+ get srcHeight(): number | undefined {
2156
+ return this.props.srcHeight;
2157
+ }
2158
+
2159
+ set srcHeight(value: number) {
2160
+ this.props.srcHeight = value;
2161
+ }
2162
+
2163
+ get srcWidth(): number | undefined {
2164
+ return this.props.srcWidth;
2165
+ }
2166
+
2167
+ set srcWidth(value: number) {
2168
+ this.props.srcWidth = value;
2169
+ }
2170
+
2171
+ get srcX(): number | undefined {
2172
+ return this.props.srcX;
2173
+ }
2174
+
2175
+ set srcX(value: number) {
2176
+ this.props.srcX = value;
2177
+ }
2178
+
2179
+ get srcY(): number | undefined {
2180
+ return this.props.srcY;
2181
+ }
2182
+
2183
+ set srcY(value: number) {
2184
+ this.props.srcY = value;
2185
+ }
2186
+
2187
+ /**
2188
+ * Returns the framebuffer dimensions of the node.
2189
+ * If the node has a render texture, the dimensions are the same as the node's dimensions.
2190
+ * If the node does not have a render texture, the dimensions are inherited from the parent.
2191
+ * If the node parent has a render texture and the node is a render texture, the nodes dimensions are used.
2192
+ */
2193
+ get framebufferDimensions(): Dimensions {
2194
+ if (this.parentHasRenderTexture && !this.rtt && this.parent) {
2195
+ return this.parent.framebufferDimensions;
2196
+ }
2197
+ return { width: this.width, height: this.height };
2198
+ }
2199
+
2200
+ /**
2201
+ * Returns the parent render texture node if it exists.
2202
+ */
2203
+ get parentRenderTexture(): CoreNode | null {
2204
+ let parent = this.parent;
2205
+ while (parent) {
2206
+ if (parent.rtt) {
2207
+ return parent;
2208
+ }
2209
+ parent = parent.parent;
2210
+ }
2211
+ return null;
2212
+ }
2213
+
2214
+ get texture(): Texture | null {
2215
+ return this.props.texture;
2216
+ }
2217
+
2218
+ set texture(value: Texture | null) {
2219
+ if (this.props.texture === value) {
2220
+ return;
2221
+ }
2222
+
2223
+ const oldTexture = this.props.texture;
2224
+ if (oldTexture) {
2225
+ oldTexture.setRenderableOwner(this, false);
2226
+ this.unloadTexture();
2227
+ }
2228
+
2229
+ this.props.texture = value;
2230
+ if (value !== null) {
2231
+ value.setRenderableOwner(this, this.isRenderable);
2232
+ this.loadTexture();
2233
+ } else {
2234
+ // If the texture is null, create a default texture
2235
+ this.createDefaultTexture();
2236
+ }
2237
+
2238
+ this.setUpdateType(UpdateType.IsRenderable);
2239
+ }
2240
+
2241
+ set textureOptions(value: TextureOptions) {
2242
+ this.props.textureOptions = value;
2243
+ }
2244
+
2245
+ get textureOptions(): TextureOptions {
2246
+ return this.props.textureOptions;
2247
+ }
2248
+
2249
+ get strictBounds(): boolean {
2250
+ return this.props.strictBounds;
2251
+ }
2252
+
2253
+ set strictBounds(v) {
2254
+ if (v === this.props.strictBounds) {
2255
+ return;
2256
+ }
2257
+
2258
+ this.props.strictBounds = v;
2259
+ this.setUpdateType(UpdateType.RenderBounds | UpdateType.Children);
2260
+ this.childUpdateType |= UpdateType.RenderBounds | UpdateType.Children;
2261
+ }
2262
+
2263
+ animate(
2264
+ props: Partial<CoreNodeAnimateProps>,
2265
+ settings: Partial<AnimationSettings>,
2266
+ ): IAnimationController {
2267
+ const animation = new CoreAnimation(this, props, settings);
2268
+
2269
+ const controller = new CoreAnimationController(
2270
+ this.stage.animationManager,
2271
+ animation,
2272
+ );
2273
+
2274
+ return controller;
2275
+ }
2276
+
2277
+ flush() {
2278
+ // no-op
2279
+ }
2280
+
2281
+ //#endregion Properties
2282
+ }