babylon.canvas2d.ts 99 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032
  1. module BABYLON {
  2. // This class contains data that lifetime is bounding to the Babylon Engine object
  3. export class Canvas2DEngineBoundData {
  4. public GetOrAddModelCache<TInstData>(key: string, factory: (key: string) => ModelRenderCache): ModelRenderCache {
  5. return this._modelCache.getOrAddWithFactory(key, factory);
  6. }
  7. private _modelCache: StringDictionary<ModelRenderCache> = new StringDictionary<ModelRenderCache>();
  8. public DisposeModelRenderCache(modelRenderCache: ModelRenderCache): boolean {
  9. if (!modelRenderCache.isDisposed) {
  10. return false;
  11. }
  12. this._modelCache.remove(modelRenderCache.modelKey);
  13. return true;
  14. }
  15. }
  16. @className("Canvas2D", "BABYLON")
  17. /**
  18. * The Canvas2D main class.
  19. * This class is extended in both ScreenSpaceCanvas2D and WorldSpaceCanvas2D which are designed only for semantic use.
  20. * User creates a Screen or WorldSpace canvas which is a 2D surface area that will draw the primitives that were added as children.
  21. */
  22. export abstract class Canvas2D extends Group2D {
  23. /**
  24. * In this strategy only the direct children groups of the Canvas will be cached, their whole content (whatever the sub groups they have) into a single bitmap.
  25. * This strategy doesn't allow primitives added directly as children of the Canvas.
  26. * You typically want to use this strategy of a screenSpace fullscreen canvas: you don't want a bitmap cache taking the whole screen resolution but still want the main contents (say UI in the topLeft and rightBottom for instance) to be efficiently cached.
  27. */
  28. public static CACHESTRATEGY_TOPLEVELGROUPS = 1;
  29. /**
  30. * In this strategy each group will have its own cache bitmap (except if a given group explicitly defines the DONTCACHEOVERRIDE or CACHEINPARENTGROUP behaviors).
  31. * This strategy is typically used if the canvas has some groups that are frequently animated. Unchanged ones will have a steady cache and the others will be refreshed when they change, reducing the redraw operation count to their content only.
  32. * When using this strategy, group instances can rely on the DONTCACHEOVERRIDE or CACHEINPARENTGROUP behaviors to minimize the amount of cached bitmaps.
  33. * Note that in this mode the Canvas itself is not cached, it only contains the sprites of its direct children group to render, there's no point to cache the whole canvas, sprites will be rendered pretty efficiently, the memory cost would be too great for the value of it.
  34. */
  35. public static CACHESTRATEGY_ALLGROUPS = 2;
  36. /**
  37. * In this strategy the whole canvas is cached into a single bitmap containing every primitives it owns, at the exception of the ones that are owned by a group having the DONTCACHEOVERRIDE behavior (these primitives will be directly drawn to the viewport at each render for screenSpace Canvas or be part of the Canvas cache bitmap for worldSpace Canvas).
  38. */
  39. public static CACHESTRATEGY_CANVAS = 3;
  40. /**
  41. * This strategy is used to recompose/redraw the canvas entirely at each viewport render.
  42. * Use this strategy if memory is a concern above rendering performances and/or if the canvas is frequently animated (hence reducing the benefits of caching).
  43. * Note that you can't use this strategy for WorldSpace Canvas, they need at least a top level group caching.
  44. */
  45. public static CACHESTRATEGY_DONTCACHE = 4;
  46. /**
  47. * Observable Mask to be notified before rendering is made
  48. */
  49. public static RENDEROBSERVABLE_PRE = 1;
  50. /**
  51. * Observable Mask to be notified after rendering is made
  52. */
  53. public static RENDEROBSERVABLE_POST = 2;
  54. private static _INSTANCES : Array<Canvas2D> = [];
  55. constructor(scene: Scene, settings?: {
  56. id ?: string,
  57. children ?: Array<Prim2DBase>,
  58. size ?: Size,
  59. renderingPhase ?: { camera: Camera, renderingGroupID: number },
  60. designSize ?: Size,
  61. designUseHorizAxis ?: boolean,
  62. isScreenSpace ?: boolean,
  63. cachingStrategy ?: number,
  64. enableInteraction ?: boolean,
  65. enableCollisionManager ?: boolean,
  66. customCollisionManager ?: (owner: Canvas2D, enableBorders: boolean) => PrimitiveCollisionManagerBase,
  67. collisionManagerUseBorders ?: boolean,
  68. origin ?: Vector2,
  69. isVisible ?: boolean,
  70. backgroundRoundRadius ?: number,
  71. backgroundFill ?: IBrush2D | string,
  72. backgroundBorder ?: IBrush2D | string,
  73. backgroundBorderThickNess ?: number,
  74. }) {
  75. super(settings);
  76. this._drawCallsOpaqueCounter = new PerfCounter();
  77. this._drawCallsAlphaTestCounter = new PerfCounter();
  78. this._drawCallsTransparentCounter = new PerfCounter();
  79. this._groupRenderCounter = new PerfCounter();
  80. this._updateTransparentDataCounter = new PerfCounter();
  81. this._cachedGroupRenderCounter = new PerfCounter();
  82. this._updateCachedStateCounter = new PerfCounter();
  83. this._updateLayoutCounter = new PerfCounter();
  84. this._updatePositioningCounter = new PerfCounter();
  85. this._updateLocalTransformCounter = new PerfCounter();
  86. this._updateGlobalTransformCounter = new PerfCounter();
  87. this._boundingInfoRecomputeCounter = new PerfCounter();
  88. this._cachedCanvasGroup = null;
  89. this._renderingGroupObserver = null;
  90. this._beforeRenderObserver = null;
  91. this._afterRenderObserver = null;
  92. this._profileInfoText = null;
  93. Prim2DBase._isCanvasInit = false;
  94. if (!settings) {
  95. settings = {};
  96. }
  97. if (this._cachingStrategy !== Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
  98. this._background = new Rectangle2D({ parent: this, id: "###CANVAS BACKGROUND###", size: settings.size }); //TODO CHECK when size is null
  99. this._background.zOrder = 1.0;
  100. this._background.isPickable = false;
  101. this._background.origin = Vector2.Zero();
  102. this._background.levelVisible = false;
  103. if (settings.backgroundRoundRadius != null) {
  104. this.backgroundRoundRadius = settings.backgroundRoundRadius;
  105. }
  106. if (settings.backgroundBorder != null) {
  107. if (typeof (settings.backgroundBorder) === "string") {
  108. this.backgroundBorder = Canvas2D.GetBrushFromString(<string>settings.backgroundBorder);
  109. } else {
  110. this.backgroundBorder = <IBrush2D>settings.backgroundBorder;
  111. }
  112. }
  113. if (settings.backgroundBorderThickNess != null) {
  114. this.backgroundBorderThickness = settings.backgroundBorderThickNess;
  115. }
  116. if (settings.backgroundFill != null) {
  117. if (typeof (settings.backgroundFill) === "string") {
  118. this.backgroundFill = Canvas2D.GetBrushFromString(<string>settings.backgroundFill);
  119. } else {
  120. this.backgroundFill = <IBrush2D>settings.backgroundFill;
  121. }
  122. }
  123. // Put a handler to resize the background whenever the canvas is resizing
  124. this.propertyChanged.add((e, s) => {
  125. if (e.propertyName === "size") {
  126. this._background.size = this.size;
  127. }
  128. }, Group2D.sizeProperty.flagId);
  129. this._background._patchHierarchy(this);
  130. }
  131. let engine = scene.getEngine();
  132. this.__engineData = engine.getOrAddExternalDataWithFactory("__BJSCANVAS2D__", k => new Canvas2DEngineBoundData());
  133. this._primPointerInfo = new PrimitivePointerInfo();
  134. this._capturedPointers = new StringDictionary<Prim2DBase>();
  135. this._pickStartingPosition = Vector2.Zero();
  136. this._hierarchyLevelMaxSiblingCount = 50;
  137. this._hierarchyDepth = 0;
  138. this._zOrder = 0;
  139. this._zMax = 1;
  140. this._scene = scene;
  141. this._engine = engine;
  142. this._renderingSize = new Size(0, 0);
  143. this._designSize = settings.designSize || null;
  144. this._designUseHorizAxis = settings.designUseHorizAxis === true;
  145. if (!this._trackedGroups) {
  146. this._trackedGroups = new Array<Group2D>();
  147. }
  148. this._maxAdaptiveWorldSpaceCanvasSize = null;
  149. this._groupCacheMaps = new StringDictionary<MapTexture[]>();
  150. this._patchHierarchy(this);
  151. let enableInteraction = (settings.enableInteraction == null) ? true : settings.enableInteraction;
  152. this._fitRenderingDevice = !settings.size;
  153. if (!settings.size) {
  154. settings.size = new Size(engine.getRenderWidth(), engine.getRenderHeight());
  155. }
  156. // Register scene dispose to also dispose the canvas when it'll happens
  157. scene.onDisposeObservable.add((d, s) => {
  158. this.dispose();
  159. });
  160. if (this._isScreenSpace) {
  161. if (settings.renderingPhase) {
  162. if (!settings.renderingPhase.camera || settings.renderingPhase.renderingGroupID==null) {
  163. throw Error("You have to specify a valid camera and renderingGroup");
  164. }
  165. this._renderingGroupObserver = this._scene.onRenderingGroupObservable.add((e, s) => {
  166. if ((this._scene.activeCamera === settings.renderingPhase.camera) && (e.renderStage===RenderingGroupInfo.STAGE_POSTTRANSPARENT)) {
  167. this._engine.clear(null, false, true, true);
  168. this._render();
  169. }
  170. }, Math.pow(2, settings.renderingPhase.renderingGroupID));
  171. } else {
  172. this._afterRenderObserver = this._scene.onAfterRenderObservable.add((d, s) => {
  173. this._engine.clear(null, false, true, true);
  174. this._render();
  175. });
  176. }
  177. } else {
  178. this._beforeRenderObserver = this._scene.onBeforeRenderObservable.add((d, s) => {
  179. this._render();
  180. });
  181. }
  182. this._supprtInstancedArray = this._engine.getCaps().instancedArrays !== null;
  183. //this._supprtInstancedArray = false; // TODO REMOVE!!!
  184. // Setup the canvas for interaction (or not)
  185. this._setupInteraction(enableInteraction);
  186. // Initialize the Primitive Collision Manager
  187. if (settings.enableCollisionManager) {
  188. let enableBorders = settings.collisionManagerUseBorders;
  189. this._primitiveCollisionManager = (settings.customCollisionManager==null) ? PrimitiveCollisionManagerBase.allocBasicPCM(this, enableBorders) : settings.customCollisionManager(this, enableBorders);
  190. }
  191. // Register this instance
  192. Canvas2D._INSTANCES.push(this);
  193. }
  194. public get drawCallsOpaqueCounter(): PerfCounter {
  195. return this._drawCallsOpaqueCounter;
  196. }
  197. public get drawCallsAlphaTestCounter(): PerfCounter {
  198. return this._drawCallsAlphaTestCounter;
  199. }
  200. public get drawCallsTransparentCounter(): PerfCounter {
  201. return this._drawCallsTransparentCounter;
  202. }
  203. public get groupRenderCounter(): PerfCounter {
  204. return this._groupRenderCounter;
  205. }
  206. public get updateTransparentDataCounter(): PerfCounter {
  207. return this._updateTransparentDataCounter;
  208. }
  209. public get cachedGroupRenderCounter(): PerfCounter {
  210. return this._cachedGroupRenderCounter;
  211. }
  212. public get updateCachedStateCounter(): PerfCounter {
  213. return this._updateCachedStateCounter;
  214. }
  215. public get updateLayoutCounter(): PerfCounter {
  216. return this._updateLayoutCounter;
  217. }
  218. public get updatePositioningCounter(): PerfCounter {
  219. return this._updatePositioningCounter;
  220. }
  221. public get updateLocalTransformCounter(): PerfCounter {
  222. return this._updateLocalTransformCounter;
  223. }
  224. public get updateGlobalTransformCounter(): PerfCounter {
  225. return this._updateGlobalTransformCounter;
  226. }
  227. public get boundingInfoRecomputeCounter(): PerfCounter {
  228. return this._boundingInfoRecomputeCounter;
  229. }
  230. public static get instances() : Array<Canvas2D> {
  231. return Canvas2D._INSTANCES;
  232. }
  233. public get primitiveCollisionManager(): PrimitiveCollisionManagerBase {
  234. return this._primitiveCollisionManager;
  235. }
  236. protected _canvasPreInit(settings: any) {
  237. let cachingStrategy = (settings.cachingStrategy == null) ? Canvas2D.CACHESTRATEGY_DONTCACHE : settings.cachingStrategy;
  238. this._cachingStrategy = cachingStrategy;
  239. this._isScreenSpace = (settings.isScreenSpace == null) ? true : settings.isScreenSpace;
  240. }
  241. public static _zMinDelta: number = 1 / (Math.pow(2, 24) - 1);
  242. private _setupInteraction(enable: boolean) {
  243. // No change detection
  244. if (enable === this._interactionEnabled) {
  245. return;
  246. }
  247. // Set the new state
  248. this._interactionEnabled = enable;
  249. // ScreenSpace mode
  250. if (this._isScreenSpace) {
  251. // Disable interaction
  252. if (!enable) {
  253. if (this._scenePrePointerObserver) {
  254. this.scene.onPrePointerObservable.remove(this._scenePrePointerObserver);
  255. this._scenePrePointerObserver = null;
  256. }
  257. return;
  258. }
  259. // Enable Interaction
  260. // Register the observable
  261. this._scenePrePointerObserver = this.scene.onPrePointerObservable.add((e, s) => {
  262. if (this.isVisible === false) {
  263. return;
  264. }
  265. let hs = 1 / this.engine.getHardwareScalingLevel();
  266. let localPos = e.localPosition.multiplyByFloats(hs, hs);
  267. this._handlePointerEventForInteraction(e, localPos, s);
  268. });
  269. }
  270. // World Space Mode
  271. else {
  272. let scene = this.scene;
  273. if (enable) {
  274. scene.constantlyUpdateMeshUnderPointer = true;
  275. this._scenePointerObserver = scene.onPointerObservable.add((e, s) => {
  276. if (this.isVisible === false) {
  277. return;
  278. }
  279. if (e.pickInfo.hit && e.pickInfo.pickedMesh === this._worldSpaceNode && this.worldSpaceToNodeLocal) {
  280. let localPos = this.worldSpaceToNodeLocal(e.pickInfo.pickedPoint);
  281. this._handlePointerEventForInteraction(e, localPos, s);
  282. } else if (this._actualIntersectionList && this._actualIntersectionList.length > 0) {
  283. this._handlePointerEventForInteraction(e, null, s);
  284. }
  285. });
  286. }
  287. // Disable
  288. else {
  289. if (this._scenePointerObserver) {
  290. this.scene.onPointerObservable.remove(this._scenePointerObserver);
  291. this._scenePointerObserver = null;
  292. }
  293. }
  294. }
  295. }
  296. /**
  297. * If you set your own WorldSpaceNode to display the Canvas2D you have to provide your own implementation of this method which computes the local position in the Canvas based on the given 3D World one.
  298. * Beware that you have to take under consideration the origin and unitScaleFactor in your calculations! Good luck!
  299. */
  300. public worldSpaceToNodeLocal = (worldPos: Vector3): Vector2 => {
  301. let node = this._worldSpaceNode;
  302. if (!node) {
  303. return;
  304. }
  305. let mtx = node.getWorldMatrix().clone();
  306. mtx.invert();
  307. let usf = this.unitScaleFactor;
  308. let v = Vector3.TransformCoordinates(worldPos, mtx);
  309. let res = new Vector2(v.x, v.y);
  310. let size = this.actualSize;
  311. res.x += (size.width/usf) * 0.5; // res is centered, make it relative to bottom/left
  312. res.y += (size.height/usf) * 0.5;
  313. res.x *= usf; // multiply by the unitScaleFactor, which defines if the canvas is nth time bigger than the original world plane
  314. res.y *= usf;
  315. return res;
  316. }
  317. /**
  318. * If you use a custom WorldSpaceCanvasNode you have to override this property to update the UV of your object to reflect the changes due to a resizing of the cached bitmap
  319. */
  320. public worldSpaceCacheChanged = () => {
  321. let plane = <Mesh>this.worldSpaceCanvasNode;
  322. let vd = VertexData.ExtractFromMesh(plane); //new VertexData();
  323. vd.uvs = new Float32Array(8);
  324. let material = <StandardMaterial>plane.material;
  325. let tex = this._renderableData._cacheTexture;
  326. if (material.diffuseTexture !== tex) {
  327. material.diffuseTexture = tex;
  328. tex.hasAlpha = true;
  329. }
  330. let nodeuv = this._renderableData._cacheNodeUVs;
  331. for (let i = 0; i < 4; i++) {
  332. vd.uvs[i * 2 + 0] = nodeuv[i].x;
  333. vd.uvs[i * 2 + 1] = nodeuv[i].y;
  334. }
  335. vd.applyToMesh(plane);
  336. }
  337. /**
  338. * Internal method, you should use the Prim2DBase version instead
  339. */
  340. public _setPointerCapture(pointerId: number, primitive: Prim2DBase): boolean {
  341. if (this.isPointerCaptured(pointerId)) {
  342. return false;
  343. }
  344. // Try to capture the pointer on the HTML side
  345. try {
  346. this.engine.getRenderingCanvas().setPointerCapture(pointerId);
  347. } catch (e) {
  348. //Nothing to do with the error. Execution will continue.
  349. }
  350. this._primPointerInfo.updateRelatedTarget(primitive, Vector2.Zero());
  351. this._bubbleNotifyPrimPointerObserver(primitive, PrimitivePointerInfo.PointerGotCapture, null);
  352. this._capturedPointers.add(pointerId.toString(), primitive);
  353. return true;
  354. }
  355. /**
  356. * Internal method, you should use the Prim2DBase version instead
  357. */
  358. public _releasePointerCapture(pointerId: number, primitive: Prim2DBase): boolean {
  359. if (this._capturedPointers.get(pointerId.toString()) !== primitive) {
  360. return false;
  361. }
  362. // Try to release the pointer on the HTML side
  363. try {
  364. this.engine.getRenderingCanvas().releasePointerCapture(pointerId);
  365. } catch (e) {
  366. //Nothing to do with the error. Execution will continue.
  367. }
  368. this._primPointerInfo.updateRelatedTarget(primitive, Vector2.Zero());
  369. this._bubbleNotifyPrimPointerObserver(primitive, PrimitivePointerInfo.PointerLostCapture, null);
  370. this._capturedPointers.remove(pointerId.toString());
  371. return true;
  372. }
  373. /**
  374. * Determine if the given pointer is captured or not
  375. * @param pointerId the Id of the pointer
  376. * @return true if it's captured, false otherwise
  377. */
  378. public isPointerCaptured(pointerId: number): boolean {
  379. return this._capturedPointers.contains(pointerId.toString());
  380. }
  381. private getCapturedPrimitive(pointerId: number): Prim2DBase {
  382. // Avoid unnecessary lookup
  383. if (this._capturedPointers.count === 0) {
  384. return null;
  385. }
  386. return this._capturedPointers.get(pointerId.toString());
  387. }
  388. private static _interInfo = new IntersectInfo2D();
  389. private _handlePointerEventForInteraction(eventData: PointerInfoBase, localPosition: Vector2, eventState: EventState) {
  390. // Dispose check
  391. if (this.isDisposed) {
  392. return;
  393. }
  394. // Update the this._primPointerInfo structure we'll send to observers using the PointerEvent data
  395. if (localPosition) {
  396. if (!this._updatePointerInfo(eventData, localPosition)) {
  397. return;
  398. }
  399. } else {
  400. this._primPointerInfo.canvasPointerPos = null;
  401. }
  402. let capturedPrim = this.getCapturedPrimitive(this._primPointerInfo.pointerId);
  403. // Make sure the intersection list is up to date, we maintain this list either in response of a mouse event (here) or before rendering the canvas.
  404. // Why before rendering the canvas? because some primitives may move and get away/under the mouse cursor (which is not moving). So we need to update at both location in order to always have an accurate list, which is needed for the hover state change.
  405. this._updateIntersectionList(localPosition ? this._primPointerInfo.canvasPointerPos : null, capturedPrim !== null, true);
  406. // Update the over status, same as above, it's could be done here or during rendering, but will be performed only once per render frame
  407. this._updateOverStatus(true);
  408. // Check if we have nothing to raise
  409. if (!this._actualOverPrimitive && !capturedPrim) {
  410. return;
  411. }
  412. // Update the relatedTarget info with the over primitive or the captured one (if any)
  413. let targetPrim = capturedPrim || this._actualOverPrimitive.prim;
  414. let targetPointerPos = capturedPrim ? this._primPointerInfo.canvasPointerPos.subtract(new Vector2(targetPrim.globalTransform.m[12], targetPrim.globalTransform.m[13])) : this._actualOverPrimitive.intersectionLocation;
  415. this._primPointerInfo.updateRelatedTarget(targetPrim, targetPointerPos);
  416. // Analyze the pointer event type and fire proper events on the primitive
  417. let skip = false;
  418. if (eventData.type === PointerEventTypes.POINTERWHEEL) {
  419. skip = !this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerMouseWheel, eventData);
  420. } else if (eventData.type === PointerEventTypes.POINTERMOVE) {
  421. skip = !this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerMove, eventData);
  422. } else if (eventData.type === PointerEventTypes.POINTERDOWN) {
  423. skip = !this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerDown, eventData);
  424. } else if (eventData.type === PointerEventTypes.POINTERUP) {
  425. skip = !this._bubbleNotifyPrimPointerObserver(targetPrim, PrimitivePointerInfo.PointerUp, eventData);
  426. }
  427. eventState.skipNextObservers = skip;
  428. }
  429. private _updatePointerInfo(eventData: PointerInfoBase, localPosition: Vector2): boolean {
  430. let s = this.scale;
  431. let pii = this._primPointerInfo;
  432. pii.cancelBubble = false;
  433. if (!pii.canvasPointerPos) {
  434. pii.canvasPointerPos = Vector2.Zero();
  435. }
  436. var camera = this._scene.cameraToUseForPointers || this._scene.activeCamera;
  437. if (!camera || !camera.viewport) {
  438. return false;
  439. }
  440. var engine = this._scene.getEngine();
  441. if (this._isScreenSpace) {
  442. var cameraViewport = camera.viewport;
  443. var viewport = cameraViewport.toGlobal(engine.getRenderWidth(), engine.getRenderHeight());
  444. // Moving coordinates to local viewport world
  445. var x = localPosition.x - viewport.x;
  446. var y = localPosition.y - viewport.y;
  447. pii.canvasPointerPos.x = (x - this.actualPosition.x) / s;
  448. pii.canvasPointerPos.y = (engine.getRenderHeight() - y - this.actualPosition.y) / s;
  449. } else {
  450. pii.canvasPointerPos.x = localPosition.x / s;
  451. pii.canvasPointerPos.y = localPosition.y / s;
  452. }
  453. //console.log(`UpdatePointerInfo for ${this.id}, X:${pii.canvasPointerPos.x}, Y:${pii.canvasPointerPos.y}`);
  454. pii.mouseWheelDelta = 0;
  455. if (eventData.type === PointerEventTypes.POINTERWHEEL) {
  456. var event = <MouseWheelEvent>eventData.event;
  457. if (event.wheelDelta) {
  458. pii.mouseWheelDelta = event.wheelDelta / (PrimitivePointerInfo.MouseWheelPrecision * 40);
  459. } else if (event.detail) {
  460. pii.mouseWheelDelta = -event.detail / PrimitivePointerInfo.MouseWheelPrecision;
  461. }
  462. } else {
  463. var pe = <PointerEvent>eventData.event;
  464. pii.ctrlKey = pe.ctrlKey;
  465. pii.altKey = pe.altKey;
  466. pii.shiftKey = pe.shiftKey;
  467. pii.metaKey = pe.metaKey;
  468. pii.button = pe.button;
  469. pii.buttons = pe.buttons;
  470. pii.pointerId = pe.pointerId;
  471. pii.width = pe.width;
  472. pii.height = pe.height;
  473. pii.presssure = pe.pressure;
  474. pii.tilt.x = pe.tiltX;
  475. pii.tilt.y = pe.tiltY;
  476. pii.isCaptured = this.getCapturedPrimitive(pe.pointerId) !== null;
  477. }
  478. return true;
  479. }
  480. private _updateIntersectionList(mouseLocalPos: Vector2, isCapture: boolean, force: boolean) {
  481. if (!force && (this.scene.getRenderId() === this._intersectionRenderId)) {
  482. return;
  483. }
  484. let ii = Canvas2D._interInfo;
  485. let outCase = mouseLocalPos == null;
  486. if (!outCase) {
  487. // A little safe guard, it might happens than the event is triggered before the first render and nothing is computed, this simple check will make sure everything will be fine
  488. if (!this._globalTransform) {
  489. this.updateCachedStates(true);
  490. }
  491. ii.pickPosition.x = mouseLocalPos.x;
  492. ii.pickPosition.y = mouseLocalPos.y;
  493. ii.findFirstOnly = false;
  494. // Fast rejection: test if the mouse pointer is outside the canvas's bounding Info
  495. if (!isCapture && !this.levelBoundingInfo.doesIntersect(ii.pickPosition)) {
  496. // Reset intersection info as we don't hit anything
  497. ii.intersectedPrimitives = new Array<PrimitiveIntersectedInfo>();
  498. ii.topMostIntersectedPrimitive = null;
  499. } else {
  500. // The pointer is inside the Canvas, do an intersection test
  501. this.intersect(ii);
  502. // Sort primitives to get them from top to bottom
  503. ii.intersectedPrimitives = ii.intersectedPrimitives.sort((a, b) => a.prim.actualZOffset - b.prim.actualZOffset);
  504. }
  505. }
  506. {
  507. // Update prev/actual intersection info, fire "overPrim" property change if needed
  508. this._previousIntersectionList = this._actualIntersectionList;
  509. this._actualIntersectionList = outCase ? new Array<PrimitiveIntersectedInfo>() : ii.intersectedPrimitives;
  510. this._previousOverPrimitive = this._actualOverPrimitive;
  511. this._actualOverPrimitive = outCase ? null : ii.topMostIntersectedPrimitive;
  512. let prev = (this._previousOverPrimitive != null) ? this._previousOverPrimitive.prim : null;
  513. let actual = (this._actualOverPrimitive != null) ? this._actualOverPrimitive.prim : null;
  514. if (prev !== actual) {
  515. this.onPropertyChanged("overPrim", this._previousOverPrimitive ? this._previousOverPrimitive.prim : null, this._actualOverPrimitive ? this._actualOverPrimitive.prim : null);
  516. }
  517. }
  518. this._intersectionRenderId = this.scene.getRenderId();
  519. }
  520. // Based on the previousIntersectionList and the actualInstersectionList we can determined which primitives are being hover state or loosing it
  521. private _updateOverStatus(force: boolean) {
  522. if ((!force && (this.scene.getRenderId() === this._hoverStatusRenderId)) || !this._actualIntersectionList) {
  523. return;
  524. }
  525. if (this._previousIntersectionList == null) {
  526. this._previousIntersectionList = [];
  527. }
  528. // Detect a change of over
  529. let prevPrim = this._previousOverPrimitive ? this._previousOverPrimitive.prim : null;
  530. let actualPrim = this._actualOverPrimitive ? this._actualOverPrimitive.prim : null;
  531. if (prevPrim !== actualPrim) {
  532. // Detect if the current pointer is captured, only fire event if they belong to the capture primitive
  533. let capturedPrim = this.getCapturedPrimitive(this._primPointerInfo.pointerId);
  534. // See the NOTE section of: https://www.w3.org/TR/pointerevents/#setting-pointer-capture
  535. if (capturedPrim) {
  536. if (capturedPrim === prevPrim) {
  537. this._primPointerInfo.updateRelatedTarget(prevPrim, this._previousOverPrimitive.intersectionLocation);
  538. this._bubbleNotifyPrimPointerObserver(prevPrim, PrimitivePointerInfo.PointerOut, null);
  539. } else if (capturedPrim === actualPrim) {
  540. this._primPointerInfo.updateRelatedTarget(actualPrim, this._actualOverPrimitive.intersectionLocation);
  541. this._bubbleNotifyPrimPointerObserver(actualPrim, PrimitivePointerInfo.PointerOver, null);
  542. }
  543. } else {
  544. // Check for Out & Leave
  545. for (let prev of this._previousIntersectionList) {
  546. if (!Tools.first(this._actualIntersectionList, (pii) => pii.prim === prev.prim)) {
  547. this._primPointerInfo.updateRelatedTarget(prev.prim, prev.intersectionLocation);
  548. this._bubbleNotifyPrimPointerObserver(prev.prim, PrimitivePointerInfo.PointerOut, null);
  549. }
  550. }
  551. // Check for Over & Enter
  552. for (let actual of this._actualIntersectionList) {
  553. if (!Tools.first(this._previousIntersectionList, (pii) => pii.prim === actual.prim)) {
  554. this._primPointerInfo.updateRelatedTarget(actual.prim, actual.intersectionLocation);
  555. this._bubbleNotifyPrimPointerObserver(actual.prim, PrimitivePointerInfo.PointerOver, null);
  556. }
  557. }
  558. }
  559. }
  560. this._hoverStatusRenderId = this.scene.getRenderId();
  561. }
  562. private _updatePrimPointerPos(prim: Prim2DBase) {
  563. if (this._primPointerInfo.isCaptured) {
  564. this._primPointerInfo.primitivePointerPos = this._primPointerInfo.relatedTargetPointerPos;
  565. } else {
  566. for (let pii of this._actualIntersectionList) {
  567. if (pii.prim === prim) {
  568. this._primPointerInfo.primitivePointerPos = pii.intersectionLocation;
  569. return;
  570. }
  571. }
  572. }
  573. }
  574. private _notifDebugMode = false;
  575. private _debugExecObserver(prim: Prim2DBase, mask: number) {
  576. if (!this._notifDebugMode) {
  577. return;
  578. }
  579. let debug = "";
  580. for (let i = 0; i < prim.hierarchyDepth; i++) {
  581. debug += " ";
  582. }
  583. let pii = this._primPointerInfo;
  584. debug += `[RID:${this.scene.getRenderId()}] [${prim.hierarchyDepth}] event:${PrimitivePointerInfo.getEventTypeName(mask)}, id: ${prim.id} (${Tools.getClassName(prim)}), primPos: ${pii.primitivePointerPos.toString()}, canvasPos: ${pii.canvasPointerPos.toString()}, relatedTarget: ${pii.relatedTarget.id}`;
  585. console.log(debug);
  586. }
  587. private _bubbleNotifyPrimPointerObserver(prim: Prim2DBase, mask: number, eventData: PointerInfoBase): boolean {
  588. let ppi = this._primPointerInfo;
  589. let event = eventData ? eventData.event : null;
  590. let cur = prim;
  591. while (cur && !cur.isDisposed) {
  592. this._updatePrimPointerPos(cur);
  593. // For the first level we have to fire Enter or Leave for corresponding Over or Out
  594. if (cur === prim) {
  595. // Fire the proper notification
  596. if (mask === PrimitivePointerInfo.PointerOver) {
  597. this._debugExecObserver(prim, PrimitivePointerInfo.PointerEnter);
  598. prim._pointerEventObservable.notifyObservers(ppi, PrimitivePointerInfo.PointerEnter);
  599. }
  600. // Trigger a PointerLeave corresponding to the PointerOut
  601. else if (mask === PrimitivePointerInfo.PointerOut) {
  602. this._debugExecObserver(prim, PrimitivePointerInfo.PointerLeave);
  603. prim._pointerEventObservable.notifyObservers(ppi, PrimitivePointerInfo.PointerLeave);
  604. }
  605. }
  606. // Exec the observers
  607. this._debugExecObserver(cur, mask);
  608. if (!cur._pointerEventObservable.notifyObservers(ppi, mask) && eventData instanceof PointerInfoPre) {
  609. eventData.skipOnPointerObservable = true;
  610. return false;
  611. }
  612. this._triggerActionManager(cur, ppi, mask, event);
  613. // Bubble canceled? If we're not executing PointerOver or PointerOut, quit immediately
  614. // If it's PointerOver/Out we have to trigger PointerEnter/Leave no matter what
  615. if (ppi.cancelBubble) {
  616. return false;
  617. }
  618. // Loop to the parent
  619. cur = cur.parent;
  620. }
  621. return true;
  622. }
  623. private _triggerActionManager(prim: Prim2DBase, ppi: PrimitivePointerInfo, mask: number, eventData) {
  624. // A little safe guard, it might happens than the event is triggered before the first render and nothing is computed, this simple check will make sure everything will be fine
  625. if (!this._globalTransform) {
  626. this.updateCachedStates(true);
  627. }
  628. // Process Trigger related to PointerDown
  629. if ((mask & PrimitivePointerInfo.PointerDown) !== 0) {
  630. // On pointer down, record the current position and time to be able to trick PickTrigger and LongPressTrigger
  631. this._pickStartingPosition = ppi.primitivePointerPos.clone();
  632. this._pickStartingTime = new Date().getTime();
  633. this._pickedDownPrim = null;
  634. if (prim.actionManager) {
  635. this._pickedDownPrim = prim;
  636. if (prim.actionManager.hasPickTriggers) {
  637. let actionEvent = ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData);
  638. switch (eventData.button) {
  639. case 0:
  640. prim.actionManager.processTrigger(ActionManager.OnLeftPickTrigger, actionEvent);
  641. break;
  642. case 1:
  643. prim.actionManager.processTrigger(ActionManager.OnCenterPickTrigger, actionEvent);
  644. break;
  645. case 2:
  646. prim.actionManager.processTrigger(ActionManager.OnRightPickTrigger, actionEvent);
  647. break;
  648. }
  649. prim.actionManager.processTrigger(ActionManager.OnPickDownTrigger, actionEvent);
  650. }
  651. if (prim.actionManager.hasSpecificTrigger(ActionManager.OnLongPressTrigger)) {
  652. window.setTimeout(() => {
  653. let ppi = this._primPointerInfo;
  654. let capturedPrim = this.getCapturedPrimitive(ppi.pointerId);
  655. this._updateIntersectionList(ppi.canvasPointerPos, capturedPrim !== null, true);
  656. this._updateOverStatus(false);
  657. let ii = new IntersectInfo2D();
  658. ii.pickPosition = ppi.canvasPointerPos.clone();
  659. ii.findFirstOnly = false;
  660. this.intersect(ii);
  661. if (ii.isPrimIntersected(prim) !== null) {
  662. if (prim.actionManager) {
  663. if (this._pickStartingTime !== 0 && ((new Date().getTime() - this._pickStartingTime) > ActionManager.LongPressDelay) && (Math.abs(this._pickStartingPosition.x - ii.pickPosition.x) < ActionManager.DragMovementThreshold && Math.abs(this._pickStartingPosition.y - ii.pickPosition.y) < ActionManager.DragMovementThreshold)) {
  664. this._pickStartingTime = 0;
  665. prim.actionManager.processTrigger(ActionManager.OnLongPressTrigger, ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData));
  666. }
  667. }
  668. }
  669. }, ActionManager.LongPressDelay);
  670. }
  671. }
  672. }
  673. // Process Triggers related to Pointer Up
  674. else if ((mask & PrimitivePointerInfo.PointerUp) !== 0) {
  675. this._pickStartingTime = 0;
  676. let actionEvent = ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData);
  677. if (prim.actionManager) {
  678. // OnPickUpTrigger
  679. prim.actionManager.processTrigger(ActionManager.OnPickUpTrigger, actionEvent);
  680. // OnPickTrigger
  681. if (Math.abs(this._pickStartingPosition.x - ppi.canvasPointerPos.x) < ActionManager.DragMovementThreshold && Math.abs(this._pickStartingPosition.y - ppi.canvasPointerPos.y) < ActionManager.DragMovementThreshold) {
  682. prim.actionManager.processTrigger(ActionManager.OnPickTrigger, actionEvent);
  683. }
  684. }
  685. // OnPickOutTrigger
  686. if (this._pickedDownPrim && this._pickedDownPrim.actionManager && (this._pickedDownPrim !== prim)) {
  687. this._pickedDownPrim.actionManager.processTrigger(ActionManager.OnPickOutTrigger, actionEvent);
  688. }
  689. }
  690. else if ((mask & PrimitivePointerInfo.PointerOver) !== 0) {
  691. if (prim.actionManager) {
  692. let actionEvent = ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData);
  693. prim.actionManager.processTrigger(ActionManager.OnPointerOverTrigger, actionEvent);
  694. }
  695. }
  696. else if ((mask & PrimitivePointerInfo.PointerOut) !== 0) {
  697. if (prim.actionManager) {
  698. let actionEvent = ActionEvent.CreateNewFromPrimitive(prim, ppi.primitivePointerPos, eventData);
  699. prim.actionManager.processTrigger(ActionManager.OnPointerOutTrigger, actionEvent);
  700. }
  701. }
  702. }
  703. /**
  704. * Don't forget to call the dispose method when you're done with the Canvas instance.
  705. * But don't worry, if you dispose its scene, the canvas will be automatically disposed too.
  706. */
  707. public dispose(): boolean {
  708. if (!super.dispose()) {
  709. return false;
  710. }
  711. if (this._profilingCanvas) {
  712. this._profilingCanvas.dispose();
  713. this._profilingCanvas = null;
  714. }
  715. if (this.interactionEnabled) {
  716. this._setupInteraction(false);
  717. }
  718. if (this._renderingGroupObserver) {
  719. this._scene.onRenderingGroupObservable.remove(this._renderingGroupObserver);
  720. this._renderingGroupObserver = null;
  721. }
  722. if (this._beforeRenderObserver) {
  723. this._scene.onBeforeRenderObservable.remove(this._beforeRenderObserver);
  724. this._beforeRenderObserver = null;
  725. }
  726. if (this._afterRenderObserver) {
  727. this._scene.onAfterRenderObservable.remove(this._afterRenderObserver);
  728. this._afterRenderObserver = null;
  729. }
  730. if (this._groupCacheMaps) {
  731. this._groupCacheMaps.forEach((k, m) => m.forEach(e => e.dispose()));
  732. this._groupCacheMaps = null;
  733. }
  734. // Unregister this instance
  735. let index = Canvas2D._INSTANCES.indexOf(this);
  736. if (index > -1) {
  737. Canvas2D._INSTANCES.splice(index, 1);
  738. }
  739. return true;
  740. }
  741. /**
  742. * Accessor to the Scene that owns the Canvas
  743. * @returns The instance of the Scene object
  744. */
  745. public get scene(): Scene {
  746. return this._scene;
  747. }
  748. /**
  749. * Accessor to the Engine that drives the Scene used by this Canvas
  750. * @returns The instance of the Engine object
  751. */
  752. public get engine(): Engine {
  753. return this._engine;
  754. }
  755. /**
  756. * And observable called during the Canvas rendering process.
  757. * This observable is called twice per render, each time with a different mask:
  758. * - 1: before render is executed
  759. * - 2: after render is executed
  760. */
  761. public get renderObservable(): Observable<Canvas2D> {
  762. if (!this._renderObservable) {
  763. this._renderObservable = new Observable<Canvas2D>();
  764. }
  765. return this._renderObservable;
  766. }
  767. /**
  768. * Accessor of the Caching Strategy used by this Canvas.
  769. * See Canvas2D.CACHESTRATEGY_xxxx static members for more information
  770. * @returns the value corresponding to the used strategy.
  771. */
  772. public get cachingStrategy(): number {
  773. return this._cachingStrategy;
  774. }
  775. /**
  776. * Return true if the Canvas is a Screen Space one, false if it's a World Space one.
  777. * @returns {}
  778. */
  779. public get isScreenSpace(): boolean {
  780. return this._isScreenSpace;
  781. }
  782. /**
  783. * Only valid for World Space Canvas, returns the scene node that displays the canvas
  784. */
  785. public get worldSpaceCanvasNode(): Node {
  786. return this._worldSpaceNode;
  787. }
  788. public set worldSpaceCanvasNode(val: Node) {
  789. this._worldSpaceNode = val;
  790. }
  791. /**
  792. * Check if the WebGL Instanced Array extension is supported or not
  793. */
  794. public get supportInstancedArray() {
  795. return this._supprtInstancedArray;
  796. }
  797. /**
  798. * Property that defines the fill object used to draw the background of the Canvas.
  799. * Note that Canvas with a Caching Strategy of
  800. * @returns If the background is not set, null will be returned, otherwise a valid fill object is returned.
  801. */
  802. public get backgroundFill(): IBrush2D {
  803. if (!this._background || !this._background.isVisible) {
  804. return null;
  805. }
  806. return this._background.fill;
  807. }
  808. public set backgroundFill(value: IBrush2D) {
  809. this.checkBackgroundAvailability();
  810. if (value === this._background.fill) {
  811. return;
  812. }
  813. this._background.fill = value;
  814. this._background.levelVisible = true;
  815. }
  816. /**
  817. * Property that defines the border object used to draw the background of the Canvas.
  818. * @returns If the background is not set, null will be returned, otherwise a valid border object is returned.
  819. */
  820. public get backgroundBorder(): IBrush2D {
  821. if (!this._background || !this._background.isVisible) {
  822. return null;
  823. }
  824. return this._background.border;
  825. }
  826. public set backgroundBorder(value: IBrush2D) {
  827. this.checkBackgroundAvailability();
  828. if (value === this._background.border) {
  829. return;
  830. }
  831. this._background.border = value;
  832. this._background.levelVisible = true;
  833. }
  834. /**
  835. * Property that defines the thickness of the border object used to draw the background of the Canvas.
  836. * @returns If the background is not set, null will be returned, otherwise a valid number matching the thickness is returned.
  837. */
  838. public get backgroundBorderThickness(): number {
  839. if (!this._background || !this._background.isVisible) {
  840. return null;
  841. }
  842. return this._background.borderThickness;
  843. }
  844. public set backgroundBorderThickness(value: number) {
  845. this.checkBackgroundAvailability();
  846. if (value === this._background.borderThickness) {
  847. return;
  848. }
  849. this._background.borderThickness = value;
  850. }
  851. /**
  852. * You can set the roundRadius of the background
  853. * @returns The current roundRadius
  854. */
  855. public get backgroundRoundRadius(): number {
  856. if (!this._background || !this._background.isVisible) {
  857. return null;
  858. }
  859. return this._background.roundRadius;
  860. }
  861. public set backgroundRoundRadius(value: number) {
  862. this.checkBackgroundAvailability();
  863. if (value === this._background.roundRadius) {
  864. return;
  865. }
  866. this._background.roundRadius = value;
  867. this._background.levelVisible = true;
  868. }
  869. /**
  870. * Enable/Disable interaction for this Canvas
  871. * When enabled the Prim2DBase.pointerEventObservable property will notified when appropriate events occur
  872. */
  873. public get interactionEnabled(): boolean {
  874. return this._interactionEnabled;
  875. }
  876. public set interactionEnabled(enable: boolean) {
  877. this._setupInteraction(enable);
  878. }
  879. public get fitRenderingDevice(): boolean {
  880. return this._fitRenderingDevice;
  881. }
  882. public get designSize(): Size {
  883. return this._designSize;
  884. }
  885. public get designSizeUseHorizAxis(): boolean {
  886. return this._designUseHorizAxis;
  887. }
  888. public set designSizeUseHorizeAxis(value: boolean) {
  889. this._designUseHorizAxis = value;
  890. }
  891. /**
  892. * Return
  893. */
  894. public get overPrim(): Prim2DBase {
  895. if (this._actualIntersectionList && this._actualIntersectionList.length>0) {
  896. return this._actualIntersectionList[0].prim;
  897. }
  898. return null;
  899. }
  900. /**
  901. * Access the babylon.js' engine bound data, do not invoke this method, it's for internal purpose only
  902. * @returns {}
  903. */
  904. public get _engineData(): Canvas2DEngineBoundData {
  905. return this.__engineData;
  906. }
  907. public get unitScaleFactor(): number {
  908. return this._unitScaleFactor;
  909. }
  910. public createCanvasProfileInfoCanvas(): Canvas2D {
  911. if (this._profilingCanvas) {
  912. return this._profilingCanvas;
  913. }
  914. let canvas = new ScreenSpaceCanvas2D(this.scene, {
  915. id: "ProfileInfoCanvas", cachingStrategy: Canvas2D.CACHESTRATEGY_DONTCACHE, children:
  916. [
  917. new Rectangle2D({
  918. id: "ProfileBorder", border: "#FFFFFFFF", borderThickness: 2, roundRadius: 5, fill: "#C04040C0", marginAlignment: "h: left, v: top", margin: "10", padding: "10", children:
  919. [
  920. new Text2D("Stats", { id: "ProfileInfoText", marginAlignment: "h: left, v: top", fontName: "12pt Lucida Console", fontSignedDistanceField: true })
  921. ]
  922. })
  923. ]
  924. });
  925. this._profileInfoText = <Text2D>canvas.findById("ProfileInfoText");
  926. this._profilingCanvas = canvas;
  927. return canvas;
  928. }
  929. /**
  930. * Instanced Array will be create if there's at least this number of parts/prim that can fit into it
  931. */
  932. public minPartCountToUseInstancedArray = 5;
  933. private checkBackgroundAvailability() {
  934. if (this._cachingStrategy === Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
  935. throw Error("Can't use Canvas Background with the caching strategy TOPLEVELGROUPS");
  936. }
  937. }
  938. private _initPerfMetrics() {
  939. this._drawCallsOpaqueCounter.fetchNewFrame();
  940. this._drawCallsAlphaTestCounter.fetchNewFrame();
  941. this._drawCallsTransparentCounter.fetchNewFrame();
  942. this._groupRenderCounter.fetchNewFrame();
  943. this._updateTransparentDataCounter.fetchNewFrame();
  944. this._cachedGroupRenderCounter.fetchNewFrame();
  945. this._updateCachedStateCounter.fetchNewFrame();
  946. this._updateLayoutCounter.fetchNewFrame();
  947. this._updatePositioningCounter.fetchNewFrame();
  948. this._updateLocalTransformCounter.fetchNewFrame();
  949. this._updateGlobalTransformCounter.fetchNewFrame();
  950. this._boundingInfoRecomputeCounter.fetchNewFrame();
  951. }
  952. private _fetchPerfMetrics() {
  953. this._drawCallsOpaqueCounter.addCount(0, true);
  954. this._drawCallsAlphaTestCounter.addCount(0, true);
  955. this._drawCallsTransparentCounter.addCount(0, true);
  956. this._groupRenderCounter.addCount(0, true);
  957. this._updateTransparentDataCounter.addCount(0, true);
  958. this._cachedGroupRenderCounter.addCount(0, true);
  959. this._updateCachedStateCounter.addCount(0, true);
  960. this._updateLayoutCounter.addCount(0, true);
  961. this._updatePositioningCounter.addCount(0, true);
  962. this._updateLocalTransformCounter.addCount(0, true);
  963. this._updateGlobalTransformCounter.addCount(0, true);
  964. this._boundingInfoRecomputeCounter.addCount(0, true);
  965. }
  966. private _updateProfileCanvas() {
  967. if (this._profileInfoText == null) {
  968. return;
  969. }
  970. let format = (v: number) => (Math.round(v*100)/100).toString();
  971. let p = `Draw Calls:\n` +
  972. ` - Opaque: ${format(this.drawCallsOpaqueCounter.current)}, (avg:${format(this.drawCallsOpaqueCounter.lastSecAverage)}, t:${format(this.drawCallsOpaqueCounter.total)})\n` +
  973. ` - AlphaTest: ${format(this.drawCallsAlphaTestCounter.current)}, (avg:${format(this.drawCallsAlphaTestCounter.lastSecAverage)}, t:${format(this.drawCallsAlphaTestCounter.total)})\n` +
  974. ` - Transparent: ${format(this.drawCallsTransparentCounter.current)}, (avg:${format(this.drawCallsTransparentCounter.lastSecAverage)}, t:${format(this.drawCallsTransparentCounter.total)})\n` +
  975. `Group Render: ${this.groupRenderCounter.current}, (avg:${format(this.groupRenderCounter.lastSecAverage)}, t:${format(this.groupRenderCounter.total)})\n` +
  976. `Update Transparent Data: ${this.updateTransparentDataCounter.current}, (avg:${format(this.updateTransparentDataCounter.lastSecAverage)}, t:${format(this.updateTransparentDataCounter.total)})\n` +
  977. `Cached Group Render: ${this.cachedGroupRenderCounter.current}, (avg:${format(this.cachedGroupRenderCounter.lastSecAverage)}, t:${format(this.cachedGroupRenderCounter.total)})\n` +
  978. `Update Cached States: ${this.updateCachedStateCounter.current}, (avg:${format(this.updateCachedStateCounter.lastSecAverage)}, t:${format(this.updateCachedStateCounter.total)})\n` +
  979. ` - Update Layout: ${this.updateLayoutCounter.current}, (avg:${format(this.updateLayoutCounter.lastSecAverage)}, t:${format(this.updateLayoutCounter.total)})\n` +
  980. ` - Update Positioning: ${this.updatePositioningCounter.current}, (avg:${format(this.updatePositioningCounter.lastSecAverage)}, t:${format(this.updatePositioningCounter.total)})\n` +
  981. ` - Update Local Trans: ${this.updateLocalTransformCounter.current}, (avg:${format(this.updateLocalTransformCounter.lastSecAverage)}, t:${format(this.updateLocalTransformCounter.total)})\n` +
  982. ` - Update Global Trans: ${this.updateGlobalTransformCounter.current}, (avg:${format(this.updateGlobalTransformCounter.lastSecAverage)}, t:${format(this.updateGlobalTransformCounter.total)})\n` +
  983. ` - BoundingInfo Recompute: ${this.boundingInfoRecomputeCounter.current}, (avg:${format(this.boundingInfoRecomputeCounter.lastSecAverage)}, t:${format(this.boundingInfoRecomputeCounter.total)})`;
  984. this._profileInfoText.text = p;
  985. }
  986. public _addDrawCallCount(count: number, renderMode: number) {
  987. switch (renderMode) {
  988. case Render2DContext.RenderModeOpaque:
  989. this._drawCallsOpaqueCounter.addCount(count, false);
  990. return;
  991. case Render2DContext.RenderModeAlphaTest:
  992. this._drawCallsAlphaTestCounter.addCount(count, false);
  993. return;
  994. case Render2DContext.RenderModeTransparent:
  995. this._drawCallsTransparentCounter.addCount(count, false);
  996. return;
  997. }
  998. }
  999. public _addGroupRenderCount(count: number) {
  1000. this._groupRenderCounter.addCount(count, false);
  1001. }
  1002. public _addUpdateTransparentDataCount(count: number) {
  1003. this._updateTransparentDataCounter.addCount(count, false);
  1004. }
  1005. public addCachedGroupRenderCounter(count: number) {
  1006. this._cachedGroupRenderCounter.addCount(count, false);
  1007. }
  1008. public addUpdateCachedStateCounter(count: number) {
  1009. this._updateCachedStateCounter.addCount(count, false);
  1010. }
  1011. public addUpdateLayoutCounter(count: number) {
  1012. this._updateLayoutCounter.addCount(count, false);
  1013. }
  1014. public addUpdatePositioningCounter(count: number) {
  1015. this._updatePositioningCounter.addCount(count, false);
  1016. }
  1017. public addupdateLocalTransformCounter(count: number) {
  1018. this._updateLocalTransformCounter.addCount(count, false);
  1019. }
  1020. public addUpdateGlobalTransformCounter(count: number) {
  1021. this._updateGlobalTransformCounter.addCount(count, false);
  1022. }
  1023. private _renderObservable: Observable<Canvas2D>;
  1024. private __engineData: Canvas2DEngineBoundData;
  1025. private _interactionEnabled: boolean;
  1026. private _primPointerInfo: PrimitivePointerInfo;
  1027. private _updateRenderId: number;
  1028. private _intersectionRenderId: number;
  1029. private _hoverStatusRenderId: number;
  1030. private _pickStartingPosition: Vector2;
  1031. private _pickedDownPrim: Prim2DBase;
  1032. private _pickStartingTime: number;
  1033. private _previousIntersectionList: Array<PrimitiveIntersectedInfo>;
  1034. private _actualIntersectionList: Array<PrimitiveIntersectedInfo>;
  1035. private _previousOverPrimitive: PrimitiveIntersectedInfo;
  1036. private _actualOverPrimitive: PrimitiveIntersectedInfo;
  1037. private _capturedPointers: StringDictionary<Prim2DBase>;
  1038. private _scenePrePointerObserver: Observer<PointerInfoPre>;
  1039. private _scenePointerObserver: Observer<PointerInfo>;
  1040. protected _worldSpaceNode: Node;
  1041. private _mapCounter = 0;
  1042. private _background: Rectangle2D;
  1043. private _scene: Scene;
  1044. private _engine: Engine;
  1045. private _fitRenderingDevice: boolean;
  1046. private _isScreenSpace: boolean;
  1047. private _cachedCanvasGroup: Group2D;
  1048. private _cachingStrategy: number;
  1049. private _hierarchyLevelMaxSiblingCount: number;
  1050. private _groupCacheMaps: StringDictionary<MapTexture[]>;
  1051. private _renderingGroupObserver: Observer<RenderingGroupInfo>;
  1052. private _beforeRenderObserver: Observer<Scene>;
  1053. private _afterRenderObserver: Observer<Scene>;
  1054. private _supprtInstancedArray: boolean;
  1055. protected _unitScaleFactor: number;
  1056. private _trackedGroups: Array<Group2D>;
  1057. protected _trackNode: Node;
  1058. protected _trackNodeOffset :Vector3;
  1059. protected _trackNodeBillboard : boolean;
  1060. protected _maxAdaptiveWorldSpaceCanvasSize: number;
  1061. private _designSize: Size;
  1062. private _designUseHorizAxis: boolean;
  1063. public _primitiveCollisionManager: PrimitiveCollisionManagerBase;
  1064. public _renderingSize: Size;
  1065. private _drawCallsOpaqueCounter : PerfCounter;
  1066. private _drawCallsAlphaTestCounter : PerfCounter;
  1067. private _drawCallsTransparentCounter : PerfCounter;
  1068. private _groupRenderCounter : PerfCounter;
  1069. private _updateTransparentDataCounter: PerfCounter;
  1070. private _cachedGroupRenderCounter : PerfCounter;
  1071. private _updateCachedStateCounter : PerfCounter;
  1072. private _updateLayoutCounter : PerfCounter;
  1073. private _updatePositioningCounter : PerfCounter;
  1074. private _updateGlobalTransformCounter: PerfCounter;
  1075. private _updateLocalTransformCounter : PerfCounter;
  1076. private _boundingInfoRecomputeCounter: PerfCounter;
  1077. private _profilingCanvas: Canvas2D;
  1078. private _profileInfoText: Text2D;
  1079. private static _v = Vector3.Zero(); // Must stay zero
  1080. private static _m = Matrix.Identity();
  1081. private static _mI = Matrix.Identity(); // Must stay identity
  1082. private static tS = Vector3.Zero();
  1083. private static tT = Vector3.Zero();
  1084. private static tR = Quaternion.Identity();
  1085. private _updateTrackedNodes() {
  1086. // Get the used camera
  1087. let cam = this.scene.cameraToUseForPointers || this.scene.activeCamera;
  1088. // Compute some matrix stuff
  1089. cam.getViewMatrix().multiplyToRef(cam.getProjectionMatrix(), Canvas2D._m);
  1090. let rh = this.engine.getRenderHeight();
  1091. let v = cam.viewport.toGlobal(this.engine.getRenderWidth(), rh);
  1092. // Compute the screen position of each group that track a given scene node
  1093. for (let group of this._trackedGroups) {
  1094. if (group.isDisposed) {
  1095. continue;
  1096. }
  1097. let node = group.trackedNode;
  1098. let worldMtx = node.getWorldMatrix();
  1099. let proj = Vector3.Project(Canvas2D._v, worldMtx, Canvas2D._m, v);
  1100. // Set the visibility state accordingly, if the position is outside the frustum (well on the Z planes only...) set the group to hidden
  1101. group.levelVisible = proj.z >= 0 && proj.z < 1.0;
  1102. let s = this.scale;
  1103. group.x = Math.round(proj.x/s);
  1104. group.y = Math.round((rh - proj.y)/s);
  1105. }
  1106. // If it's a WorldSpaceCanvas and it's tracking a node, let's update the WSC transformation data
  1107. if (this._trackNode) {
  1108. let rot: Quaternion = null;
  1109. let scale: Vector3 = null;
  1110. let worldmtx = this._trackNode.getWorldMatrix();
  1111. let pos = worldmtx.getTranslation().add(this._trackNodeOffset);
  1112. let wsc = <WorldSpaceCanvas2D><any>this;
  1113. let wsn = wsc.worldSpaceCanvasNode;
  1114. if (this._trackNodeBillboard) {
  1115. let viewMtx = cam.getViewMatrix().clone().invert();
  1116. viewMtx.decompose(Canvas2D.tS, Canvas2D.tR, Canvas2D.tT);
  1117. rot = Canvas2D.tR.clone();
  1118. }
  1119. worldmtx.decompose(Canvas2D.tS, Canvas2D.tR, Canvas2D.tT);
  1120. let mtx = Matrix.Compose(Canvas2D.tS, Canvas2D.tR, Vector3.Zero());
  1121. pos = worldmtx.getTranslation().add(Vector3.TransformCoordinates(this._trackNodeOffset, mtx));
  1122. if (Canvas2D.tS.lengthSquared() !== 1) {
  1123. scale = Canvas2D.tS.clone();
  1124. }
  1125. if (!this._trackNodeBillboard) {
  1126. rot = Canvas2D.tR.clone();
  1127. }
  1128. if (wsn instanceof AbstractMesh) {
  1129. wsn.position = pos;
  1130. wsn.rotationQuaternion = rot;
  1131. if (scale) {
  1132. wsn.scaling = scale;
  1133. }
  1134. } else {
  1135. throw new Error("Can't Track another Scene Node Type than AbstractMesh right now, call me lazy!");
  1136. }
  1137. }
  1138. }
  1139. /**
  1140. * Call this method change you want to have layout related data computed and up to date (layout area, primitive area, local/global transformation matrices)
  1141. */
  1142. public updateCanvasLayout(forceRecompute: boolean) {
  1143. this._updateCanvasState(forceRecompute);
  1144. }
  1145. private _updateAdaptiveSizeWorldCanvas() {
  1146. if (this._globalTransformStep < 2) {
  1147. return;
  1148. }
  1149. let n: AbstractMesh = <AbstractMesh>this.worldSpaceCanvasNode;
  1150. let bi = n.getBoundingInfo().boundingBox;
  1151. let v = bi.vectorsWorld;
  1152. let cam = this.scene.cameraToUseForPointers || this.scene.activeCamera;
  1153. cam.getViewMatrix().multiplyToRef(cam.getProjectionMatrix(), Canvas2D._m);
  1154. let vp = cam.viewport.toGlobal(this.engine.getRenderWidth(), this.engine.getRenderHeight());
  1155. let projPoints = new Array<Vector3>(4);
  1156. for (let i = 0; i < 4; i++) {
  1157. projPoints[i] = Vector3.Project(v[i], Canvas2D._mI, Canvas2D._m, vp);
  1158. }
  1159. let left = projPoints[3].subtract(projPoints[0]).length();
  1160. let top = projPoints[3].subtract(projPoints[1]).length();
  1161. let right = projPoints[1].subtract(projPoints[2]).length();
  1162. let bottom = projPoints[2].subtract(projPoints[0]).length();
  1163. let w = Math.round(Math.max(top, bottom));
  1164. let h = Math.round(Math.max(right, left));
  1165. let isW = w > h;
  1166. // Basically if it's under 256 we use 256, otherwise we take the biggest power of 2
  1167. let edge = Math.max(w, h);
  1168. if (edge < 256) {
  1169. edge = 256;
  1170. } else {
  1171. edge = Math.pow(2, Math.ceil(Math.log(edge) / Math.log(2)));
  1172. }
  1173. // Clip values if needed
  1174. edge = Math.min(edge, this._maxAdaptiveWorldSpaceCanvasSize);
  1175. let newScale = edge / ((isW) ? this.size.width : this.size.height);
  1176. if (newScale !== this.scale) {
  1177. let scale = newScale;
  1178. // console.log(`New adaptive scale for Canvas ${this.id}, w: ${w}, h: ${h}, scale: ${scale}, edge: ${edge}, isW: ${isW}`);
  1179. this._setRenderingScale(scale);
  1180. }
  1181. }
  1182. private _updateCanvasState(forceRecompute: boolean) {
  1183. // Check if the update has already been made for this render Frame
  1184. if (!forceRecompute && this.scene.getRenderId() === this._updateRenderId) {
  1185. return;
  1186. }
  1187. // Detect a change of rendering size
  1188. let renderingSizeChanged = false;
  1189. let newWidth = this.engine.getRenderWidth();
  1190. if (newWidth !== this._renderingSize.width) {
  1191. renderingSizeChanged = true;
  1192. }
  1193. this._renderingSize.width = newWidth;
  1194. let newHeight = this.engine.getRenderHeight();
  1195. if (newHeight !== this._renderingSize.height) {
  1196. renderingSizeChanged = true;
  1197. }
  1198. this._renderingSize.height = newHeight;
  1199. // If the canvas fit the rendering size and it changed, update
  1200. if (renderingSizeChanged && this._fitRenderingDevice) {
  1201. this.actualSize = this._renderingSize.clone();
  1202. this.size = this._renderingSize.clone();
  1203. if (this._background) {
  1204. this._background.size = this.size;
  1205. }
  1206. // Dirty the Layout at the Canvas level to recompute as the size changed
  1207. this._setLayoutDirty();
  1208. }
  1209. // If there's a design size, update the scale according to the renderingSize
  1210. if (this._designSize) {
  1211. let scale: number;
  1212. if (this._designUseHorizAxis) {
  1213. scale = this._renderingSize.width / this._designSize.width;
  1214. } else {
  1215. scale = this._renderingSize.height / this._designSize.height;
  1216. }
  1217. this.size = this._designSize.clone();
  1218. this.actualSize = this._designSize.clone();
  1219. this.scale = scale;
  1220. }
  1221. var context = new PrepareRender2DContext();
  1222. ++this._globalTransformProcessStep;
  1223. this.updateCachedStates(false);
  1224. this._prepareGroupRender(context);
  1225. this._updateRenderId = this.scene.getRenderId();
  1226. }
  1227. /**
  1228. * Method that renders the Canvas, you should not invoke
  1229. */
  1230. private _render() {
  1231. this._initPerfMetrics();
  1232. if (this._renderObservable && this._renderObservable.hasObservers()) {
  1233. this._renderObservable.notifyObservers(this, Canvas2D.RENDEROBSERVABLE_PRE);
  1234. }
  1235. this._updateCanvasState(false);
  1236. this._updateTrackedNodes();
  1237. // Nothing to do is the Canvas is not visible
  1238. if (this.isVisible === false) {
  1239. return;
  1240. }
  1241. if (!this._isScreenSpace) {
  1242. this._updateAdaptiveSizeWorldCanvas();
  1243. }
  1244. this._updateCanvasState(false);
  1245. if (this._primitiveCollisionManager) {
  1246. this._primitiveCollisionManager._update();
  1247. }
  1248. if (this._primPointerInfo.canvasPointerPos) {
  1249. this._updateIntersectionList(this._primPointerInfo.canvasPointerPos, false, false);
  1250. this._updateOverStatus(false);
  1251. }
  1252. this.engine.setState(false, undefined, true);
  1253. this._groupRender();
  1254. if (!this._isScreenSpace) {
  1255. if (this._isFlagSet(SmartPropertyPrim.flagWorldCacheChanged)) {
  1256. this.worldSpaceCacheChanged();
  1257. this._clearFlags(SmartPropertyPrim.flagWorldCacheChanged);
  1258. }
  1259. }
  1260. // If the canvas is cached at canvas level, we must manually render the sprite that will display its content
  1261. if (this._cachingStrategy === Canvas2D.CACHESTRATEGY_CANVAS && this._cachedCanvasGroup) {
  1262. this._cachedCanvasGroup._renderCachedCanvas();
  1263. }
  1264. this._fetchPerfMetrics();
  1265. this._updateProfileCanvas();
  1266. if (this._renderObservable && this._renderObservable.hasObservers()) {
  1267. this._renderObservable.notifyObservers(this, Canvas2D.RENDEROBSERVABLE_POST);
  1268. }
  1269. }
  1270. /**
  1271. * Internal method that allocate a cache for the given group.
  1272. * Caching is made using a collection of MapTexture where many groups have their bitmap cache stored inside.
  1273. * @param group The group to allocate the cache of.
  1274. * @return custom type with the PackedRect instance giving information about the cache location into the texture and also the MapTexture instance that stores the cache.
  1275. */
  1276. public _allocateGroupCache(group: Group2D, parent: Group2D, minSize?: Size, useMipMap: boolean = false, anisotropicLevel: number = 1): { node: PackedRect, texture: MapTexture, sprite: Sprite2D } {
  1277. let key = `${useMipMap ? "MipMap" : "NoMipMap"}_${anisotropicLevel}`;
  1278. let rd = group._renderableData;
  1279. let noResizeScale = rd._noResizeOnScale;
  1280. let isCanvas = parent == null;
  1281. let scale: Vector2;
  1282. if (noResizeScale) {
  1283. scale = isCanvas ? Canvas2D._unS : group.parent.actualScale;
  1284. } else {
  1285. scale = group.actualScale;
  1286. }
  1287. // Determine size
  1288. let size = group.actualSize;
  1289. size = new Size(Math.ceil(size.width * scale.x), Math.ceil(size.height * scale.y));
  1290. let originalSize = size.clone();
  1291. if (minSize) {
  1292. size.width = Math.max(minSize.width, size.width);
  1293. size.height = Math.max(minSize.height, size.height);
  1294. }
  1295. let mapArray = this._groupCacheMaps.getOrAddWithFactory(key, () => new Array<MapTexture>());
  1296. // Try to find a spot in one of the cached texture
  1297. let res = null;
  1298. var map: MapTexture;
  1299. for (var _map of mapArray) {
  1300. map = _map;
  1301. let node = map.allocateRect(size);
  1302. if (node) {
  1303. res = { node: node, texture: map }
  1304. break;
  1305. }
  1306. }
  1307. // Couldn't find a map that could fit the rect, create a new map for it
  1308. if (!res) {
  1309. let mapSize = new Size(Canvas2D._groupTextureCacheSize, Canvas2D._groupTextureCacheSize);
  1310. // Check if the predefined size would fit, other create a custom size using the nearest bigger power of 2
  1311. if (size.width > mapSize.width || size.height > mapSize.height) {
  1312. mapSize.width = Math.pow(2, Math.ceil(Math.log(size.width) / Math.log(2)));
  1313. mapSize.height = Math.pow(2, Math.ceil(Math.log(size.height) / Math.log(2)));
  1314. }
  1315. let id = `groupsMapChache${this._mapCounter++}forCanvas${this.id}`;
  1316. map = new MapTexture(id, this._scene, mapSize, useMipMap ? Texture.TRILINEAR_SAMPLINGMODE : Texture.BILINEAR_SAMPLINGMODE, useMipMap);
  1317. map.hasAlpha = true;
  1318. map.anisotropicFilteringLevel = 4;
  1319. mapArray.splice(0, 0, map);
  1320. let node = map.allocateRect(size);
  1321. res = { node: node, texture: map }
  1322. }
  1323. // Check if we have to create a Sprite that will display the content of the Canvas which is cached.
  1324. // Don't do it in case of the group being a worldspace canvas (because its texture is bound to a WorldSpaceCanvas node)
  1325. if (group !== <any>this || this._isScreenSpace) {
  1326. let node: PackedRect = res.node;
  1327. // Special case if the canvas is entirely cached: create a group that will have a single sprite it will be rendered specifically at the very end of the rendering process
  1328. let sprite: Sprite2D;
  1329. if (this._cachingStrategy === Canvas2D.CACHESTRATEGY_CANVAS) {
  1330. if (this._cachedCanvasGroup) {
  1331. this._cachedCanvasGroup.dispose();
  1332. }
  1333. this._cachedCanvasGroup = Group2D._createCachedCanvasGroup(this);
  1334. sprite = new Sprite2D(map, { parent: this._cachedCanvasGroup, id: "__cachedCanvasSprite__", spriteSize: originalSize, spriteLocation: node.pos });
  1335. sprite.zOrder = 1;
  1336. sprite.origin = Vector2.Zero();
  1337. }
  1338. // Create a Sprite that will be used to render this cache, the "__cachedSpriteOfGroup__" starting id is a hack to bypass exception throwing in case of the Canvas doesn't normally allows direct primitives
  1339. else {
  1340. sprite = new Sprite2D(map, { parent: parent, id: `__cachedSpriteOfGroup__${group.id}`, x: group.actualPosition.x, y: group.actualPosition.y, spriteSize: originalSize, spriteLocation: node.pos, dontInheritParentScale: true });
  1341. sprite.origin = group.origin.clone();
  1342. sprite.addExternalData("__cachedGroup__", group);
  1343. sprite.pointerEventObservable.add((e, s) => {
  1344. if (group.pointerEventObservable !== null) {
  1345. group.pointerEventObservable.notifyObservers(e, s.mask);
  1346. }
  1347. });
  1348. res.sprite = sprite;
  1349. }
  1350. if (sprite && noResizeScale) {
  1351. let relScale = isCanvas ? group.actualScale : group.actualScale.divide(group.parent.actualScale);
  1352. sprite.scaleX = relScale.x;
  1353. sprite.scaleY = relScale.y;
  1354. }
  1355. }
  1356. return res;
  1357. }
  1358. /**
  1359. * Define the default size used for both the width and height of a MapTexture to allocate.
  1360. * Note that some MapTexture might be bigger than this size if the first node to allocate is bigger in width or height
  1361. */
  1362. private static _groupTextureCacheSize = 1024;
  1363. /**
  1364. * Internal method used to register a Scene Node to track position for the given group
  1365. * Do not invoke this method, for internal purpose only.
  1366. * @param group the group to track its associated Scene Node
  1367. */
  1368. public _registerTrackedNode(group: Group2D) {
  1369. if (group._isFlagSet(SmartPropertyPrim.flagTrackedGroup)) {
  1370. return;
  1371. }
  1372. if (!this._trackedGroups) {
  1373. this._trackedGroups = new Array<Group2D>();
  1374. }
  1375. this._trackedGroups.push(group);
  1376. group._setFlags(SmartPropertyPrim.flagTrackedGroup);
  1377. }
  1378. /**
  1379. * Internal method used to unregister a tracked Scene Node
  1380. * Do not invoke this method, it's for internal purpose only.
  1381. * @param group the group to unregister its tracked Scene Node from.
  1382. */
  1383. public _unregisterTrackedNode(group: Group2D) {
  1384. if (!group._isFlagSet(SmartPropertyPrim.flagTrackedGroup)) {
  1385. return;
  1386. }
  1387. let i = this._trackedGroups.indexOf(group);
  1388. if (i !== -1) {
  1389. this._trackedGroups.splice(i, 1);
  1390. }
  1391. group._clearFlags(SmartPropertyPrim.flagTrackedGroup);
  1392. }
  1393. /**
  1394. * Get a Solid Color Brush instance matching the given color.
  1395. * @param color The color to retrieve
  1396. * @return A shared instance of the SolidColorBrush2D class that use the given color
  1397. */
  1398. public static GetSolidColorBrush(color: Color4): IBrush2D {
  1399. return Canvas2D._solidColorBrushes.getOrAddWithFactory(color.toHexString(), () => new SolidColorBrush2D(color.clone(), true));
  1400. }
  1401. /**
  1402. * Get a Solid Color Brush instance matching the given color expressed as a CSS formatted hexadecimal value.
  1403. * @param color The color to retrieve
  1404. * @return A shared instance of the SolidColorBrush2D class that uses the given color
  1405. */
  1406. public static GetSolidColorBrushFromHex(hexValue: string): IBrush2D {
  1407. return Canvas2D._solidColorBrushes.getOrAddWithFactory(hexValue, () => new SolidColorBrush2D(Color4.FromHexString(hexValue), true));
  1408. }
  1409. /**
  1410. * Get a Gradient Color Brush
  1411. * @param color1 starting color
  1412. * @param color2 engine color
  1413. * @param translation translation vector to apply. default is [0;0]
  1414. * @param rotation rotation in radian to apply to the brush, initial direction is top to bottom. rotation is counter clockwise. default is 0.
  1415. * @param scale scaling factor to apply. default is 1.
  1416. */
  1417. public static GetGradientColorBrush(color1: Color4, color2: Color4, translation: Vector2 = Vector2.Zero(), rotation: number = 0, scale: number = 1): IBrush2D {
  1418. return Canvas2D._gradientColorBrushes.getOrAddWithFactory(GradientColorBrush2D.BuildKey(color1, color2, translation, rotation, scale), () => new GradientColorBrush2D(color1, color2, translation, rotation, scale, true));
  1419. }
  1420. /**
  1421. * Create a solid or gradient brush from a string value.
  1422. * @param brushString should be either
  1423. * - "solid: #RRGGBBAA" or "#RRGGBBAA"
  1424. * - "gradient: #FF808080, #FFFFFFF[, [10:20], 180, 1]" for color1, color2, translation, rotation (degree), scale. The last three are optionals, but if specified must be is this order. "gradient:" can be omitted.
  1425. */
  1426. public static GetBrushFromString(brushString: string): IBrush2D {
  1427. // Note: yes, I hate/don't know RegEx.. Feel free to add your contribution to the cause!
  1428. brushString = brushString.trim();
  1429. let split = brushString.split(",");
  1430. // Solid, formatted as: "[solid:]#FF808080"
  1431. if (split.length === 1) {
  1432. let value: string = null;
  1433. if (brushString.indexOf("solid:") === 0) {
  1434. value = brushString.substr(6).trim();
  1435. } else if (brushString.indexOf("#") === 0) {
  1436. value = brushString;
  1437. } else {
  1438. return null;
  1439. }
  1440. return Canvas2D.GetSolidColorBrushFromHex(value);
  1441. }
  1442. // Gradient, formatted as: "[gradient:]#FF808080, #FFFFFFF[, [10:20], 180, 1]" [10:20] is a real formatting expected not a EBNF notation
  1443. // Order is: gradient start, gradient end, translation, rotation (degree), scale
  1444. else {
  1445. if (split[0].indexOf("gradient:") === 0) {
  1446. split[0] = split[0].substr(9).trim();
  1447. }
  1448. try {
  1449. let start = Color4.FromHexString(split[0].trim());
  1450. let end = Color4.FromHexString(split[1].trim());
  1451. let t: Vector2 = Vector2.Zero();
  1452. if (split.length > 2) {
  1453. let v = split[2].trim();
  1454. if (v.charAt(0) !== "[" || v.charAt(v.length - 1) !== "]") {
  1455. return null;
  1456. }
  1457. let sep = v.indexOf(":");
  1458. let x = parseFloat(v.substr(1, sep));
  1459. let y = parseFloat(v.substr(sep + 1, v.length - (sep + 1)));
  1460. t = new Vector2(x, y);
  1461. }
  1462. let r: number = 0;
  1463. if (split.length > 3) {
  1464. r = Tools.ToRadians(parseFloat(split[3].trim()));
  1465. }
  1466. let s: number = 1;
  1467. if (split.length > 4) {
  1468. s = parseFloat(split[4].trim());
  1469. }
  1470. return Canvas2D.GetGradientColorBrush(start, end, t, r, s);
  1471. } catch (e) {
  1472. return null;
  1473. }
  1474. }
  1475. }
  1476. private static _solidColorBrushes: StringDictionary<IBrush2D> = new StringDictionary<IBrush2D>();
  1477. private static _gradientColorBrushes: StringDictionary<IBrush2D> = new StringDictionary<IBrush2D>();
  1478. }
  1479. @className("WorldSpaceCanvas2D", "BABYLON")
  1480. /**
  1481. * Class to create a WorldSpace Canvas2D.
  1482. */
  1483. export class WorldSpaceCanvas2D extends Canvas2D {
  1484. /**
  1485. * Create a new 2D WorldSpace Rendering Canvas, it is a 2D rectangle that has a size (width/height) and a world transformation information to place it in the world space.
  1486. * This kind of canvas can't have its Primitives directly drawn in the Viewport, they need to be cached in a bitmap at some point, as a consequence the DONT_CACHE strategy is unavailable. For now only CACHESTRATEGY_CANVAS is supported, but the remaining strategies will be soon.
  1487. * @param scene the Scene that owns the Canvas
  1488. * @param size the dimension of the Canvas in World Space
  1489. * @param settings a combination of settings, possible ones are
  1490. * - children: an array of direct children primitives
  1491. * - id: a text identifier, for information purpose only, default is null.
  1492. * - unitScaleFactor: if specified the created canvas will be with a width of size.width*unitScaleFactor and a height of size.height.unitScaleFactor. If not specified, the unit of 1 is used. You can use this setting when you're dealing with a 3D world with small coordinates and you need a Canvas having bigger coordinates (typically to display text with better quality).
  1493. * - worldPosition the position of the Canvas in World Space, default is [0,0,0]
  1494. * - worldRotation the rotation of the Canvas in World Space, default is Quaternion.Identity()
  1495. * - trackNode: if you want the WorldSpaceCanvas to track the position/rotation/scale of a given Scene Node, use this setting to specify the Node to track
  1496. * - trackNodeOffset: if you use trackNode you may want to specify a 3D Offset to apply to shift the Canvas
  1497. * - trackNodeBillboard: if true the WorldSpaceCanvas will always face the screen
  1498. * - sideOrientation: Unexpected behavior occur if the value is different from Mesh.DEFAULTSIDE right now, so please use this one, which is the default.
  1499. * - cachingStrategy Must be CACHESTRATEGY_CANVAS for now, which is the default.
  1500. * - enableInteraction: if true the pointer events will be listened and rerouted to the appropriate primitives of the Canvas2D through the Prim2DBase.onPointerEventObservable observable property. Default is false (the opposite of ScreenSpace).
  1501. * - isVisible: true if the canvas must be visible, false for hidden. Default is true.
  1502. * - backgroundRoundRadius: the round radius of the background, either backgroundFill or backgroundBorder must be specified.
  1503. * - backgroundFill: the brush to use to create a background fill for the canvas. can be a string value (see Canvas2D.GetBrushFromString) or a IBrush2D instance.
  1504. * - backgroundBorder: the brush to use to create a background border for the canvas. can be a string value (see Canvas2D.GetBrushFromString) or a IBrush2D instance.
  1505. * - backgroundBorderThickness: if a backgroundBorder is specified, its thickness can be set using this property
  1506. * - customWorldSpaceNode: if specified the Canvas will be rendered in this given Node. But it's the responsibility of the caller to set the "worldSpaceToNodeLocal" property to compute the hit of the mouse ray into the node (in world coordinate system) as well as rendering the cached bitmap in the node itself. The properties cachedRect and cachedTexture of Group2D will give you what you need to do that.
  1507. * - maxAdaptiveCanvasSize: set the max size (width and height) of the bitmap that will contain the cached version of the WorldSpace Canvas. Default is 1024 or less if it's not supported. In any case the value you give will be clipped by the maximum that WebGL supports on the running device. You can set any size, more than 1024 if you want, but testing proved it's a good max value for non "retina" like screens.
  1508. * - paddingTop: top padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  1509. * - paddingLeft: left padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  1510. * - paddingRight: right padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  1511. * - paddingBottom: bottom padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  1512. * - padding: top, left, right and bottom padding formatted as a single string (see PrimitiveThickness.fromString)
  1513. */
  1514. constructor(scene: Scene, size: Size, settings?: {
  1515. children ?: Array<Prim2DBase>,
  1516. id ?: string,
  1517. unitScaleFactor ?: number,
  1518. worldPosition ?: Vector3,
  1519. worldRotation ?: Quaternion,
  1520. trackNode ?: Node,
  1521. trackNodeOffset ?: Vector3,
  1522. trackNodeBillboard ?: boolean,
  1523. sideOrientation ?: number,
  1524. cachingStrategy ?: number,
  1525. enableInteraction ?: boolean,
  1526. isVisible ?: boolean,
  1527. backgroundRoundRadius ?: number,
  1528. backgroundFill ?: IBrush2D | string,
  1529. backgroundBorder ?: IBrush2D | string,
  1530. backgroundBorderThickNess?: number,
  1531. customWorldSpaceNode ?: Node,
  1532. maxAdaptiveCanvasSize ?: number,
  1533. paddingTop ?: number | string,
  1534. paddingLeft ?: number | string,
  1535. paddingRight ?: number | string,
  1536. paddingBottom ?: number | string,
  1537. padding ?: string,
  1538. }) {
  1539. Prim2DBase._isCanvasInit = true;
  1540. let s = <any>settings;
  1541. s.isScreenSpace = false;
  1542. if (settings.unitScaleFactor != null) {
  1543. s.size = size.multiplyByFloats(settings.unitScaleFactor, settings.unitScaleFactor);
  1544. } else {
  1545. s.size = size.clone();
  1546. }
  1547. settings.cachingStrategy = (settings.cachingStrategy == null) ? Canvas2D.CACHESTRATEGY_CANVAS : settings.cachingStrategy;
  1548. if (settings.cachingStrategy !== Canvas2D.CACHESTRATEGY_CANVAS) {
  1549. throw new Error("Right now only the CACHESTRATEGY_CANVAS cache Strategy is supported for WorldSpace Canvas. More will come soon!");
  1550. }
  1551. super(scene, settings);
  1552. Prim2DBase._isCanvasInit = false;
  1553. this._unitScaleFactor = (settings.unitScaleFactor != null) ? settings.unitScaleFactor : 1;
  1554. this._renderableData._useMipMap = true;
  1555. this._renderableData._anisotropicLevel = 8;
  1556. //if (cachingStrategy === Canvas2D.CACHESTRATEGY_DONTCACHE) {
  1557. // throw new Error("CACHESTRATEGY_DONTCACHE cache Strategy can't be used for WorldSpace Canvas");
  1558. //}
  1559. this._trackNode = (settings.trackNode != null) ? settings.trackNode : null;
  1560. this._trackNodeOffset = (settings.trackNodeOffset != null) ? settings.trackNodeOffset : Vector3.Zero();
  1561. this._trackNodeBillboard = (settings.trackNodeBillboard != null) ? settings.trackNodeBillboard : true;
  1562. let createWorldSpaceNode = !settings || (settings.customWorldSpaceNode == null);
  1563. this._customWorldSpaceNode = !createWorldSpaceNode;
  1564. let id = settings ? settings.id || null : null;
  1565. // Set the max size of texture allowed for the adaptive render of the world space canvas cached bitmap
  1566. let capMaxTextSize = this.engine.getCaps().maxRenderTextureSize;
  1567. let defaultTextSize = (Math.min(capMaxTextSize, 1024)); // Default is 1K if allowed otherwise the max allowed
  1568. if (settings.maxAdaptiveCanvasSize == null) {
  1569. this._maxAdaptiveWorldSpaceCanvasSize = defaultTextSize;
  1570. } else {
  1571. // We still clip the given value with the max allowed, the user may not be aware of these limitations
  1572. this._maxAdaptiveWorldSpaceCanvasSize = Math.min(settings.maxAdaptiveCanvasSize, capMaxTextSize);
  1573. }
  1574. if (createWorldSpaceNode) {
  1575. let plane = new WorldSpaceCanvas2DNode(id, scene, this);
  1576. let vertexData = VertexData.CreatePlane({
  1577. width: size.width,
  1578. height: size.height,
  1579. sideOrientation: settings && settings.sideOrientation || Mesh.DEFAULTSIDE
  1580. });
  1581. let mtl = new StandardMaterial(id + "_Material", scene);
  1582. this.applyCachedTexture(vertexData, mtl);
  1583. vertexData.applyToMesh(plane, true);
  1584. mtl.specularColor = new Color3(0, 0, 0);
  1585. mtl.disableLighting = true;
  1586. mtl.useAlphaFromDiffuseTexture = true;
  1587. if (settings && settings.sideOrientation) {
  1588. mtl.backFaceCulling = (settings.sideOrientation === Mesh.DEFAULTSIDE || settings.sideOrientation === Mesh.FRONTSIDE);
  1589. }
  1590. plane.position = settings && settings.worldPosition || Vector3.Zero();
  1591. plane.rotationQuaternion = settings && settings.worldRotation || Quaternion.Identity();
  1592. plane.material = mtl;
  1593. this._worldSpaceNode = plane;
  1594. } else {
  1595. this._worldSpaceNode = settings.customWorldSpaceNode;
  1596. this.applyCachedTexture(null, null);
  1597. }
  1598. this.propertyChanged.add((e, st) => {
  1599. if (e.propertyName !== "isVisible") {
  1600. return;
  1601. }
  1602. let mesh = this._worldSpaceNode as AbstractMesh;
  1603. if (mesh) {
  1604. mesh.isVisible = e.newValue;
  1605. }
  1606. }, Prim2DBase.isVisibleProperty.flagId);
  1607. }
  1608. public dispose(): boolean {
  1609. if (!super.dispose()) {
  1610. return false;
  1611. }
  1612. if (!this._customWorldSpaceNode && this._worldSpaceNode) {
  1613. this._worldSpaceNode.dispose();
  1614. this._worldSpaceNode = null;
  1615. }
  1616. }
  1617. public get trackNode(): Node {
  1618. return this._trackNode;
  1619. }
  1620. public set trackNode(value: Node) {
  1621. if (this._trackNode === value) {
  1622. return;
  1623. }
  1624. this._trackNode = value;
  1625. }
  1626. public get trackNodeOffset(): Vector3 {
  1627. return this._trackNodeOffset;
  1628. }
  1629. public set trackNodeOffset(value: Vector3) {
  1630. if (!this._trackNodeOffset) {
  1631. this._trackNodeOffset = value.clone();
  1632. } else {
  1633. this._trackNodeOffset.copyFrom(value);
  1634. }
  1635. }
  1636. public get trackNodeBillboard(): boolean {
  1637. return this._trackNodeBillboard;
  1638. }
  1639. public set trackNodeBillboard(value: boolean) {
  1640. this._trackNodeBillboard = value;
  1641. }
  1642. private _customWorldSpaceNode: boolean;
  1643. }
  1644. @className("ScreenSpaceCanvas2D", "BABYLON")
  1645. /**
  1646. * Class to create a ScreenSpace Canvas2D
  1647. */
  1648. export class ScreenSpaceCanvas2D extends Canvas2D {
  1649. /**
  1650. * Create a new 2D ScreenSpace Rendering Canvas, it is a 2D rectangle that has a size (width/height) and a position relative to the bottom/left corner of the screen.
  1651. * ScreenSpace Canvas will be drawn in the Viewport as a 2D Layer lying to the top of the 3D Scene. Typically used for traditional UI.
  1652. * All caching strategies will be available.
  1653. * PLEASE NOTE: the origin of a Screen Space Canvas is set to [0;0] (bottom/left) which is different than the default origin of a Primitive which is centered [0.5;0.5]
  1654. * @param scene the Scene that owns the Canvas
  1655. * @param settings a combination of settings, possible ones are
  1656. * - children: an array of direct children primitives
  1657. * - id: a text identifier, for information purpose only
  1658. * - x: the position along the x axis (horizontal), relative to the left edge of the viewport. you can alternatively use the position setting.
  1659. * - y: the position along the y axis (vertically), relative to the bottom edge of the viewport. you can alternatively use the position setting.
  1660. * - position: the position of the canvas, relative from the bottom/left of the scene's viewport. Alternatively you can set the x and y properties directly. Default value is [0, 0]
  1661. * - width: the width of the Canvas. you can alternatively use the size setting.
  1662. * - height: the height of the Canvas. you can alternatively use the size setting.
  1663. * - size: the Size of the canvas. Alternatively the width and height properties can be set. If null two behaviors depend on the cachingStrategy: if it's CACHESTRATEGY_CACHECANVAS then it will always auto-fit the rendering device, in all the other modes it will fit the content of the Canvas
  1664. * - renderingPhase: you can specify for which camera and which renderGroup this canvas will render to enable interleaving of 3D/2D content through the use of renderinGroup. As a rendering Group is rendered for each camera, you have to specify in the scope of which camera you want the canvas' render to be made. Default behavior will render the Canvas at the very end of the render loop.
  1665. * - designSize: if you want to set the canvas content based on fixed coordinates whatever the final canvas dimension would be, set this. For instance a designSize of 360*640 will give you the possibility to specify all the children element in this frame. The Canvas' true size will be the HTMLCanvas' size: for instance it could be 720*1280, then a uniform scale of 2 will be applied on the Canvas to keep the absolute coordinates working as expecting. If the ratios of the designSize and the true Canvas size are not the same, then the scale is computed following the designUseHorizAxis member by using either the size of the horizontal axis or the vertical axis.
  1666. * - designUseHorizAxis: you can set this member if you use designSize to specify which axis is priority to compute the scale when the ratio of the canvas' size is different from the designSize's one.
  1667. * - cachingStrategy: either CACHESTRATEGY_TOPLEVELGROUPS, CACHESTRATEGY_ALLGROUPS, CACHESTRATEGY_CANVAS, CACHESTRATEGY_DONTCACHE. Please refer to their respective documentation for more information. Default is Canvas2D.CACHESTRATEGY_DONTCACHE
  1668. * - enableInteraction: if true the pointer events will be listened and rerouted to the appropriate primitives of the Canvas2D through the Prim2DBase.onPointerEventObservable observable property. Default is true.
  1669. * - isVisible: true if the canvas must be visible, false for hidden. Default is true.
  1670. * - backgroundRoundRadius: the round radius of the background, either backgroundFill or backgroundBorder must be specified.
  1671. * - backgroundFill: the brush to use to create a background fill for the canvas. can be a string value (see BABYLON.Canvas2D.GetBrushFromString) or a IBrush2D instance.
  1672. * - backgroundBorder: the brush to use to create a background border for the canvas. can be a string value (see BABYLON.Canvas2D.GetBrushFromString) or a IBrush2D instance.
  1673. * - backgroundBorderThickness: if a backgroundBorder is specified, its thickness can be set using this property
  1674. * - customWorldSpaceNode: if specified the Canvas will be rendered in this given Node. But it's the responsibility of the caller to set the "worldSpaceToNodeLocal" property to compute the hit of the mouse ray into the node (in world coordinate system) as well as rendering the cached bitmap in the node itself. The properties cachedRect and cachedTexture of Group2D will give you what you need to do that.
  1675. * - paddingTop: top padding, can be a number (will be pixels) or a string (see BABYLON.PrimitiveThickness.fromString)
  1676. * - paddingLeft: left padding, can be a number (will be pixels) or a string (see BABYLON.PrimitiveThickness.fromString)
  1677. * - paddingRight: right padding, can be a number (will be pixels) or a string (see BABYLON.PrimitiveThickness.fromString)
  1678. * - paddingBottom: bottom padding, can be a number (will be pixels) or a string (see BABYLON.PrimitiveThickness.fromString)
  1679. * - padding: top, left, right and bottom padding formatted as a single string (see BABYLON.PrimitiveThickness.fromString)
  1680. */
  1681. constructor(scene: Scene, settings?: {
  1682. children ?: Array<Prim2DBase>,
  1683. id ?: string,
  1684. x ?: number,
  1685. y ?: number,
  1686. position ?: Vector2,
  1687. origin ?: Vector2,
  1688. width ?: number,
  1689. height ?: number,
  1690. size ?: Size,
  1691. renderingPhase ?: {camera: Camera, renderingGroupID: number },
  1692. designSize ?: Size,
  1693. designUseHorizAxis ?: boolean,
  1694. cachingStrategy ?: number,
  1695. cacheBehavior ?: number,
  1696. enableInteraction ?: boolean,
  1697. enableCollisionManager ?: boolean,
  1698. customCollisionManager ?: (owner: Canvas2D, enableBorders: boolean) => PrimitiveCollisionManagerBase,
  1699. collisionManagerUseBorders ?: boolean,
  1700. isVisible ?: boolean,
  1701. backgroundRoundRadius ?: number,
  1702. backgroundFill ?: IBrush2D | string,
  1703. backgroundBorder ?: IBrush2D | string,
  1704. backgroundBorderThickNess ?: number,
  1705. paddingTop ?: number | string,
  1706. paddingLeft ?: number | string,
  1707. paddingRight ?: number | string,
  1708. paddingBottom ?: number | string,
  1709. padding ?: string,
  1710. }) {
  1711. Prim2DBase._isCanvasInit = true;
  1712. super(scene, settings);
  1713. }
  1714. }
  1715. }