babylon.group2d.js 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902
  1. var __extends = (this && this.__extends) || function (d, b) {
  2. for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
  3. function __() { this.constructor = d; }
  4. d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  5. };
  6. var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  7. var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  8. if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  9. else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  10. return c > 3 && r && Object.defineProperty(target, key, r), r;
  11. };
  12. var BABYLON;
  13. (function (BABYLON) {
  14. var Group2D = (function (_super) {
  15. __extends(Group2D, _super);
  16. /**
  17. * Create an Logical or Renderable Group.
  18. * @param settings a combination of settings, possible ones are
  19. * - parent: the parent primitive/canvas, must be specified if the primitive is not constructed as a child of another one (i.e. as part of the children array setting)
  20. * - children: an array of direct children
  21. * - id a text identifier, for information purpose
  22. * - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0]
  23. * - rotation: the initial rotation (in radian) of the primitive. default is 0
  24. * - scale: the initial scale of the primitive. default is 1. You can alternatively use scaleX &| scaleY to apply non uniform scale
  25. * - opacity: set the overall opacity of the primitive, 1 to be opaque (default), less than 1 to be transparent.
  26. * - zOrder: override the zOrder with the specified value
  27. * - origin: define the normalized origin point location, default [0.5;0.5]
  28. * - size: the size of the group. Alternatively the width and height properties can be set. If null the size will be computed from its content, default is null.
  29. * - cacheBehavior: Define how the group should behave regarding the Canvas's cache strategy, default is Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY
  30. * - layoutEngine: either an instance of a layout engine based class (StackPanel.Vertical, StackPanel.Horizontal) or a string ('canvas' for Canvas layout, 'StackPanel' or 'HorizontalStackPanel' for horizontal Stack Panel layout, 'VerticalStackPanel' for vertical Stack Panel layout).
  31. * - isVisible: true if the group must be visible, false for hidden. Default is true.
  32. * - isPickable: if true the Primitive can be used with interaction mode and will issue Pointer Event. If false it will be ignored for interaction/intersection test. Default value is true.
  33. * - isContainer: if true the Primitive acts as a container for interaction, if the primitive is not pickable or doesn't intersection, no further test will be perform on its children. If set to false, children will always be considered for intersection/interaction. Default value is true.
  34. * - childrenFlatZOrder: if true all the children (direct and indirect) will share the same Z-Order. Use this when there's a lot of children which don't overlap. The drawing order IS NOT GUARANTED!
  35. * - marginTop: top margin, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  36. * - marginLeft: left margin, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  37. * - marginRight: right margin, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  38. * - marginBottom: bottom margin, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  39. * - margin: top, left, right and bottom margin formatted as a single string (see PrimitiveThickness.fromString)
  40. * - marginHAlignment: one value of the PrimitiveAlignment type's static properties
  41. * - marginVAlignment: one value of the PrimitiveAlignment type's static properties
  42. * - marginAlignment: a string defining the alignment, see PrimitiveAlignment.fromString
  43. * - paddingTop: top padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  44. * - paddingLeft: left padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  45. * - paddingRight: right padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  46. * - paddingBottom: bottom padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  47. * - padding: top, left, right and bottom padding formatted as a single string (see PrimitiveThickness.fromString)
  48. */
  49. function Group2D(settings) {
  50. if (settings == null) {
  51. settings = {};
  52. }
  53. if (settings.origin == null) {
  54. settings.origin = new BABYLON.Vector2(0, 0);
  55. }
  56. _super.call(this, settings);
  57. var size = (!settings.size && !settings.width && !settings.height) ? null : (settings.size || (new BABYLON.Size(settings.width || 0, settings.height || 0)));
  58. this._trackedNode = (settings.trackNode == null) ? null : settings.trackNode;
  59. if (this._trackedNode && this.owner) {
  60. this.owner._registerTrackedNode(this);
  61. }
  62. this._cacheBehavior = (settings.cacheBehavior == null) ? Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY : settings.cacheBehavior;
  63. this.size = size;
  64. this._viewportPosition = BABYLON.Vector2.Zero();
  65. }
  66. Group2D._createCachedCanvasGroup = function (owner) {
  67. var g = new Group2D({ parent: owner, id: "__cachedCanvasGroup__", position: BABYLON.Vector2.Zero(), origin: BABYLON.Vector2.Zero(), size: null, isVisible: true, isPickable: false });
  68. return g;
  69. };
  70. Group2D.prototype.applyCachedTexture = function (vertexData, material) {
  71. this._bindCacheTarget();
  72. if (vertexData) {
  73. var uv = vertexData.uvs;
  74. var nodeuv = this._renderableData._cacheNodeUVs;
  75. for (var i = 0; i < 4; i++) {
  76. uv[i * 2 + 0] = nodeuv[i].x;
  77. uv[i * 2 + 1] = nodeuv[i].y;
  78. }
  79. }
  80. if (material) {
  81. material.diffuseTexture = this._renderableData._cacheTexture;
  82. material.emissiveColor = new BABYLON.Color3(1, 1, 1);
  83. }
  84. this._renderableData._cacheTexture.hasAlpha = true;
  85. this._unbindCacheTarget();
  86. };
  87. Object.defineProperty(Group2D.prototype, "cachedRect", {
  88. /**
  89. * Allow you to access the information regarding the cached rectangle of the Group2D into the MapTexture.
  90. * If the `noWorldSpaceNode` options was used at the creation of a WorldSpaceCanvas, the rendering of the canvas must be made by the caller, so typically you want to bind the cacheTexture property to some material/mesh and you MUST use the Group2D.cachedUVs property to get the UV coordinates to use for your quad that will display the Canvas and NOT the PackedRect.UVs property which are incorrect because the allocated surface may be bigger (due to over-provisioning or shrinking without deallocating) than what the Group is actually using.
  91. */
  92. get: function () {
  93. if (!this._renderableData) {
  94. return null;
  95. }
  96. return this._renderableData._cacheNode;
  97. },
  98. enumerable: true,
  99. configurable: true
  100. });
  101. Object.defineProperty(Group2D.prototype, "cachedUVs", {
  102. /**
  103. * The UVs into the MapTexture that map the cached group
  104. */
  105. get: function () {
  106. if (!this._renderableData) {
  107. return null;
  108. }
  109. return this._renderableData._cacheNodeUVs;
  110. },
  111. enumerable: true,
  112. configurable: true
  113. });
  114. Object.defineProperty(Group2D.prototype, "cachedUVsChanged", {
  115. get: function () {
  116. if (!this._renderableData) {
  117. return null;
  118. }
  119. if (!this._renderableData._cacheNodeUVsChangedObservable) {
  120. this._renderableData._cacheNodeUVsChangedObservable = new BABYLON.Observable();
  121. }
  122. return this._renderableData._cacheNodeUVsChangedObservable;
  123. },
  124. enumerable: true,
  125. configurable: true
  126. });
  127. Object.defineProperty(Group2D.prototype, "cacheTexture", {
  128. /**
  129. * Access the texture that maintains a cached version of the Group2D.
  130. * This is useful only if you're not using a WorldSpaceNode for your WorldSpace Canvas and therefore need to perform the rendering yourself.
  131. */
  132. get: function () {
  133. if (!this._renderableData) {
  134. return null;
  135. }
  136. return this._renderableData._cacheTexture;
  137. },
  138. enumerable: true,
  139. configurable: true
  140. });
  141. /**
  142. * Call this method to remove this Group and its children from the Canvas
  143. */
  144. Group2D.prototype.dispose = function () {
  145. if (!_super.prototype.dispose.call(this)) {
  146. return false;
  147. }
  148. if (this._trackedNode != null) {
  149. this.owner._unregisterTrackedNode(this);
  150. this._trackedNode = null;
  151. }
  152. if (this._renderableData) {
  153. this._renderableData.dispose(this.owner.engine);
  154. this._renderableData = null;
  155. }
  156. return true;
  157. };
  158. Object.defineProperty(Group2D.prototype, "isRenderableGroup", {
  159. /**
  160. * @returns Returns true if the Group render content, false if it's a logical group only
  161. */
  162. get: function () {
  163. return this._isRenderableGroup;
  164. },
  165. enumerable: true,
  166. configurable: true
  167. });
  168. Object.defineProperty(Group2D.prototype, "isCachedGroup", {
  169. /**
  170. * @returns only meaningful for isRenderableGroup, will be true if the content of the Group is cached into a texture, false if it's rendered every time
  171. */
  172. get: function () {
  173. return this._isCachedGroup;
  174. },
  175. enumerable: true,
  176. configurable: true
  177. });
  178. Object.defineProperty(Group2D.prototype, "size", {
  179. get: function () {
  180. return this._size;
  181. },
  182. /**
  183. * Get/Set the size of the group. If null the size of the group will be determine from its content.
  184. * BEWARE: if the Group is a RenderableGroup and its content is cache the texture will be resized each time the group is getting bigger. For performance reason the opposite won't be true: the texture won't shrink if the group does.
  185. */
  186. set: function (val) {
  187. this._size = val;
  188. },
  189. enumerable: true,
  190. configurable: true
  191. });
  192. Object.defineProperty(Group2D.prototype, "viewportSize", {
  193. get: function () {
  194. return this._viewportSize;
  195. },
  196. enumerable: true,
  197. configurable: true
  198. });
  199. Object.defineProperty(Group2D.prototype, "actualSize", {
  200. get: function () {
  201. // The computed size will be floor on both width and height
  202. var actualSize;
  203. // Return the size if set by the user
  204. if (this._size) {
  205. actualSize = new BABYLON.Size(Math.ceil(this._size.width), Math.ceil(this._size.height));
  206. }
  207. else {
  208. var m = this.boundingInfo.max();
  209. actualSize = new BABYLON.Size(Math.ceil(m.x), Math.ceil(m.y));
  210. }
  211. // Compare the size with the one we previously had, if it differs we set the property dirty and trigger a GroupChanged to synchronize a displaySprite (if any)
  212. if (!actualSize.equals(this._actualSize)) {
  213. this.onPrimitivePropertyDirty(Group2D.actualSizeProperty.flagId);
  214. this._actualSize = actualSize;
  215. this.handleGroupChanged(Group2D.actualSizeProperty);
  216. }
  217. return actualSize;
  218. },
  219. enumerable: true,
  220. configurable: true
  221. });
  222. Object.defineProperty(Group2D.prototype, "cacheBehavior", {
  223. /**
  224. * Get/set the Cache Behavior, used in case the Canvas Cache Strategy is set to CACHESTRATEGY_ALLGROUPS. Can be either GROUPCACHEBEHAVIOR_CACHEINPARENTGROUP, GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE or GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY. See their documentation for more information.
  225. * It is critical to understand than you HAVE TO play with this behavior in order to achieve a good performance/memory ratio. Caching all groups would certainly be the worst strategy of all.
  226. */
  227. get: function () {
  228. return this._cacheBehavior;
  229. },
  230. enumerable: true,
  231. configurable: true
  232. });
  233. Group2D.prototype._addPrimToDirtyList = function (prim) {
  234. this._renderableData._primDirtyList.push(prim);
  235. };
  236. Group2D.prototype._renderCachedCanvas = function () {
  237. this.owner._addGroupRenderCount(1);
  238. this.updateCachedStates(true);
  239. var context = new BABYLON.PrepareRender2DContext();
  240. this._prepareGroupRender(context);
  241. this._groupRender();
  242. };
  243. Object.defineProperty(Group2D.prototype, "trackedNode", {
  244. /**
  245. * Get/set the Scene's Node that should be tracked, the group's position will follow the projected position of the Node.
  246. */
  247. get: function () {
  248. return this._trackedNode;
  249. },
  250. set: function (val) {
  251. if (val != null) {
  252. if (!this._isFlagSet(BABYLON.SmartPropertyPrim.flagTrackedGroup)) {
  253. this.owner._registerTrackedNode(this);
  254. }
  255. this._trackedNode = val;
  256. }
  257. else {
  258. if (this._isFlagSet(BABYLON.SmartPropertyPrim.flagTrackedGroup)) {
  259. this.owner._unregisterTrackedNode(this);
  260. }
  261. this._trackedNode = null;
  262. }
  263. },
  264. enumerable: true,
  265. configurable: true
  266. });
  267. Group2D.prototype.levelIntersect = function (intersectInfo) {
  268. // If we've made it so far it means the boundingInfo intersection test succeed, the Group2D is shaped the same, so we always return true
  269. return true;
  270. };
  271. Group2D.prototype.updateLevelBoundingInfo = function () {
  272. var size;
  273. // If the size is set by the user, the boundingInfo is computed from this value
  274. if (this.size) {
  275. size = this.size;
  276. }
  277. else {
  278. size = new BABYLON.Size(0, 0);
  279. }
  280. BABYLON.BoundingInfo2D.CreateFromSizeToRef(size, this._levelBoundingInfo);
  281. };
  282. // Method called only on renderable groups to prepare the rendering
  283. Group2D.prototype._prepareGroupRender = function (context) {
  284. var sortedDirtyList = null;
  285. // Update the Global Transformation and visibility status of the changed primitives
  286. if ((this._renderableData._primDirtyList.length > 0) || context.forceRefreshPrimitive) {
  287. sortedDirtyList = this._renderableData._primDirtyList.sort(function (a, b) { return a.hierarchyDepth - b.hierarchyDepth; });
  288. this.updateCachedStatesOf(sortedDirtyList, true);
  289. }
  290. // Setup the size of the rendering viewport
  291. // In non cache mode, we're rendering directly to the rendering canvas, in this case we have to detect if the canvas size changed since the previous iteration, if it's the case all primitives must be prepared again because their transformation must be recompute
  292. if (!this._isCachedGroup) {
  293. // Compute the WebGL viewport's location/size
  294. var t = this._globalTransform.getTranslation();
  295. var s = this.actualSize.clone();
  296. var rs = this.owner._renderingSize;
  297. s.height = Math.min(s.height, rs.height - t.y);
  298. s.width = Math.min(s.width, rs.width - t.x);
  299. var x = t.x;
  300. var y = t.y;
  301. // The viewport where we're rendering must be the size of the canvas if this one fit in the rendering screen or clipped to the screen dimensions if needed
  302. this._viewportPosition.x = x;
  303. this._viewportPosition.y = y;
  304. var vw = s.width;
  305. var vh = s.height;
  306. if (!this._viewportSize) {
  307. this._viewportSize = new BABYLON.Size(vw, vh);
  308. }
  309. else {
  310. if (this._viewportSize.width !== vw || this._viewportSize.height !== vh) {
  311. context.forceRefreshPrimitive = true;
  312. }
  313. this._viewportSize.width = vw;
  314. this._viewportSize.height = vh;
  315. }
  316. }
  317. else {
  318. var newSize = this.actualSize.clone();
  319. if (!newSize.equals(this._viewportSize)) {
  320. context.forceRefreshPrimitive = true;
  321. }
  322. this._viewportSize = newSize;
  323. }
  324. if ((this._renderableData._primDirtyList.length > 0) || context.forceRefreshPrimitive) {
  325. // If the group is cached, set the dirty flag to true because of the incoming changes
  326. this._cacheGroupDirty = this._isCachedGroup;
  327. // If it's a force refresh, prepare all the children
  328. if (context.forceRefreshPrimitive) {
  329. for (var _i = 0, _a = this._children; _i < _a.length; _i++) {
  330. var p = _a[_i];
  331. p._prepareRender(context);
  332. }
  333. }
  334. else {
  335. // Each primitive that changed at least once was added into the primDirtyList, we have to sort this level using
  336. // the hierarchyDepth in order to prepare primitives from top to bottom
  337. if (!sortedDirtyList) {
  338. sortedDirtyList = this._renderableData._primDirtyList.sort(function (a, b) { return a.hierarchyDepth - b.hierarchyDepth; });
  339. }
  340. sortedDirtyList.forEach(function (p) {
  341. // We need to check if prepare is needed because even if the primitive is in the dirtyList, its parent primitive may also have been modified, then prepared, then recurse on its children primitives (this one for instance) if the changes where impacting them.
  342. // For instance: a Rect's position change, the position of its children primitives will also change so a prepare will be call on them. If a child was in the dirtyList we will avoid a second prepare by making this check.
  343. if (!p.isDisposed && p._needPrepare()) {
  344. p._prepareRender(context);
  345. }
  346. });
  347. }
  348. // Everything is updated, clear the dirty list
  349. this._renderableData._primDirtyList.forEach(function (p) { return p._resetPropertiesDirty(); });
  350. this._renderableData._primDirtyList.splice(0);
  351. }
  352. // A renderable group has a list of direct children that are also renderable groups, we recurse on them to also prepare them
  353. this._renderableData._childrenRenderableGroups.forEach(function (g) {
  354. g._prepareGroupRender(context);
  355. });
  356. };
  357. Group2D.prototype._groupRender = function () {
  358. var _this = this;
  359. var engine = this.owner.engine;
  360. var failedCount = 0;
  361. // First recurse to children render group to render them (in their cache or on screen)
  362. for (var _i = 0, _a = this._renderableData._childrenRenderableGroups; _i < _a.length; _i++) {
  363. var childGroup = _a[_i];
  364. childGroup._groupRender();
  365. }
  366. // Render the primitives if needed: either if we don't cache the content or if the content is cached but has changed
  367. if (!this.isCachedGroup || this._cacheGroupDirty) {
  368. this.owner._addGroupRenderCount(1);
  369. if (this.isCachedGroup) {
  370. this._bindCacheTarget();
  371. }
  372. else {
  373. var curVP = engine.setDirectViewport(this._viewportPosition.x, this._viewportPosition.y, this._viewportSize.width, this._viewportSize.height);
  374. }
  375. var curAlphaTest = engine.getAlphaTesting() === true;
  376. var curDepthWrite = engine.getDepthWrite() === true;
  377. // ===================================================================
  378. // First pass, update the InstancedArray and render Opaque primitives
  379. // Disable Alpha Testing, Enable Depth Write
  380. engine.setAlphaTesting(false);
  381. engine.setDepthWrite(true);
  382. // For each different model of primitive to render
  383. var context_1 = new BABYLON.Render2DContext(BABYLON.Render2DContext.RenderModeOpaque);
  384. this._renderableData._renderGroupInstancesInfo.forEach(function (k, v) {
  385. // Prepare the context object, update the WebGL Instanced Array buffer if needed
  386. var renderCount = _this._prepareContext(engine, context_1, v);
  387. // If null is returned, there's no opaque data to render
  388. if (renderCount === null) {
  389. return;
  390. }
  391. // Submit render only if we have something to render (everything may be hidden and the floatarray empty)
  392. if (!_this.owner.supportInstancedArray || renderCount > 0) {
  393. // render all the instances of this model, if the render method returns true then our instances are no longer dirty
  394. var renderFailed = !v.modelRenderCache.render(v, context_1);
  395. // Update dirty flag/related
  396. v.opaqueDirty = renderFailed;
  397. failedCount += renderFailed ? 1 : 0;
  398. }
  399. });
  400. // =======================================================================
  401. // Second pass, update the InstancedArray and render AlphaTest primitives
  402. // Enable Alpha Testing, Enable Depth Write
  403. engine.setAlphaTesting(true);
  404. engine.setDepthWrite(true);
  405. // For each different model of primitive to render
  406. context_1 = new BABYLON.Render2DContext(BABYLON.Render2DContext.RenderModeAlphaTest);
  407. this._renderableData._renderGroupInstancesInfo.forEach(function (k, v) {
  408. // Prepare the context object, update the WebGL Instanced Array buffer if needed
  409. var renderCount = _this._prepareContext(engine, context_1, v);
  410. // If null is returned, there's no opaque data to render
  411. if (renderCount === null) {
  412. return;
  413. }
  414. // Submit render only if we have something to render (everything may be hidden and the floatarray empty)
  415. if (!_this.owner.supportInstancedArray || renderCount > 0) {
  416. // render all the instances of this model, if the render method returns true then our instances are no longer dirty
  417. var renderFailed = !v.modelRenderCache.render(v, context_1);
  418. // Update dirty flag/related
  419. v.opaqueDirty = renderFailed;
  420. failedCount += renderFailed ? 1 : 0;
  421. }
  422. });
  423. // =======================================================================
  424. // Third pass, transparent primitive rendering
  425. // Enable Alpha Testing, Disable Depth Write
  426. engine.setAlphaTesting(true);
  427. engine.setDepthWrite(false);
  428. // First Check if the transparent List change so we can update the TransparentSegment and PartData (sort if needed)
  429. if (this._renderableData._transparentListChanged) {
  430. this._updateTransparentData();
  431. }
  432. // From this point on we have up to date data to render, so let's go
  433. failedCount += this._renderTransparentData();
  434. // =======================================================================
  435. // Unbind target/restore viewport setting, clear dirty flag, and quit
  436. // The group's content is no longer dirty
  437. this._cacheGroupDirty = failedCount !== 0;
  438. if (this.isCachedGroup) {
  439. this._unbindCacheTarget();
  440. }
  441. else {
  442. if (curVP) {
  443. engine.setViewport(curVP);
  444. }
  445. }
  446. // Restore saved states
  447. engine.setAlphaTesting(curAlphaTest);
  448. engine.setDepthWrite(curDepthWrite);
  449. }
  450. };
  451. Group2D.prototype._setCacheGroupDirty = function () {
  452. this._cacheGroupDirty = true;
  453. };
  454. Group2D.prototype._updateTransparentData = function () {
  455. this.owner._addUpdateTransparentDataCount(1);
  456. var rd = this._renderableData;
  457. // Sort all the primitive from their depth, max (bottom) to min (top)
  458. rd._transparentPrimitives.sort(function (a, b) { return b._primitive.actualZOffset - a._primitive.actualZOffset; });
  459. var checkAndAddPrimInSegment = function (seg, tpiI) {
  460. var tpi = rd._transparentPrimitives[tpiI];
  461. // Fast rejection: if gii are different
  462. if (seg.groupInsanceInfo !== tpi._groupInstanceInfo) {
  463. return false;
  464. }
  465. //let tpiZ = tpi._primitive.actualZOffset;
  466. // We've made it so far, the tpi can be part of the segment, add it
  467. tpi._transparentSegment = seg;
  468. tpi._primitive._updateTransparentSegmentIndices(seg);
  469. return true;
  470. };
  471. // Free the existing TransparentSegments
  472. for (var _i = 0, _a = rd._transparentSegments; _i < _a.length; _i++) {
  473. var ts = _a[_i];
  474. ts.dispose(this.owner.engine);
  475. }
  476. rd._transparentSegments.splice(0);
  477. var prevSeg = null;
  478. for (var tpiI = 0; tpiI < rd._transparentPrimitives.length; tpiI++) {
  479. var tpi = rd._transparentPrimitives[tpiI];
  480. // Check if the Data in which the primitive is stored is not sorted properly
  481. if (tpi._groupInstanceInfo.transparentOrderDirty) {
  482. tpi._groupInstanceInfo.sortTransparentData();
  483. }
  484. // Reset the segment, we have to create/rebuild it
  485. tpi._transparentSegment = null;
  486. // If there's a previous valid segment, check if this prim can be part of it
  487. if (prevSeg) {
  488. checkAndAddPrimInSegment(prevSeg, tpiI);
  489. }
  490. // If we couldn't insert in the adjacent segments, he have to create one
  491. if (!tpi._transparentSegment) {
  492. var ts = new BABYLON.TransparentSegment();
  493. ts.groupInsanceInfo = tpi._groupInstanceInfo;
  494. var prim = tpi._primitive;
  495. ts.startZ = prim.actualZOffset;
  496. prim._updateTransparentSegmentIndices(ts);
  497. ts.endZ = ts.startZ;
  498. tpi._transparentSegment = ts;
  499. rd._transparentSegments.push(ts);
  500. }
  501. // Update prevSeg
  502. prevSeg = tpi._transparentSegment;
  503. }
  504. //rd._firstChangedPrim = null;
  505. rd._transparentListChanged = false;
  506. };
  507. Group2D.prototype._renderTransparentData = function () {
  508. var failedCount = 0;
  509. var context = new BABYLON.Render2DContext(BABYLON.Render2DContext.RenderModeTransparent);
  510. var rd = this._renderableData;
  511. var useInstanced = this.owner.supportInstancedArray;
  512. var length = rd._transparentSegments.length;
  513. for (var i = 0; i < length; i++) {
  514. context.instancedBuffers = null;
  515. var ts = rd._transparentSegments[i];
  516. var gii = ts.groupInsanceInfo;
  517. var mrc = gii.modelRenderCache;
  518. var engine = this.owner.engine;
  519. var count = ts.endDataIndex - ts.startDataIndex;
  520. // Use Instanced Array if it's supported and if there's at least 5 prims to draw.
  521. // We don't want to create an Instanced Buffer for less that 5 prims
  522. if (useInstanced && count >= 5) {
  523. if (!ts.partBuffers) {
  524. var buffers = new Array();
  525. for (var j = 0; j < gii.transparentData.length; j++) {
  526. var gitd = gii.transparentData[j];
  527. var dfa = gitd._partData;
  528. var data = dfa.pack();
  529. var stride = dfa.stride;
  530. var neededSize = count * stride * 4;
  531. var buffer = engine.createInstancesBuffer(neededSize); // Create + bind
  532. var segData = data.subarray(ts.startDataIndex * stride, ts.endDataIndex * stride);
  533. engine.updateArrayBuffer(segData);
  534. buffers.push(buffer);
  535. }
  536. ts.partBuffers = buffers;
  537. }
  538. else if (gii.transparentDirty) {
  539. for (var j = 0; j < gii.transparentData.length; j++) {
  540. var gitd = gii.transparentData[j];
  541. var dfa = gitd._partData;
  542. var data = dfa.pack();
  543. var stride = dfa.stride;
  544. var buffer = ts.partBuffers[j];
  545. var segData = data.subarray(ts.startDataIndex * stride, ts.endDataIndex * stride);
  546. engine.bindArrayBuffer(buffer);
  547. engine.updateArrayBuffer(segData);
  548. }
  549. }
  550. context.useInstancing = true;
  551. context.instancesCount = count;
  552. context.instancedBuffers = ts.partBuffers;
  553. context.groupInfoPartData = gii.transparentData;
  554. var renderFailed = !mrc.render(gii, context);
  555. failedCount += renderFailed ? 1 : 0;
  556. }
  557. else {
  558. context.useInstancing = false;
  559. context.partDataStartIndex = ts.startDataIndex;
  560. context.partDataEndIndex = ts.endDataIndex;
  561. context.groupInfoPartData = gii.transparentData;
  562. var renderFailed = !mrc.render(gii, context);
  563. failedCount += renderFailed ? 1 : 0;
  564. }
  565. }
  566. return failedCount;
  567. };
  568. Group2D.prototype._prepareContext = function (engine, context, gii) {
  569. var gipd = null;
  570. var setDirty;
  571. var getDirty;
  572. // Render Mode specifics
  573. switch (context.renderMode) {
  574. case BABYLON.Render2DContext.RenderModeOpaque:
  575. {
  576. if (!gii.hasOpaqueData) {
  577. return null;
  578. }
  579. setDirty = function (dirty) { gii.opaqueDirty = dirty; };
  580. getDirty = function () { return gii.opaqueDirty; };
  581. context.groupInfoPartData = gii.opaqueData;
  582. gipd = gii.opaqueData;
  583. break;
  584. }
  585. case BABYLON.Render2DContext.RenderModeAlphaTest:
  586. {
  587. if (!gii.hasAlphaTestData) {
  588. return null;
  589. }
  590. setDirty = function (dirty) { gii.alphaTestDirty = dirty; };
  591. getDirty = function () { return gii.alphaTestDirty; };
  592. context.groupInfoPartData = gii.alphaTestData;
  593. gipd = gii.alphaTestData;
  594. break;
  595. }
  596. default:
  597. throw new Error("_prepareContext is only for opaque or alphaTest");
  598. }
  599. var renderCount = 0;
  600. // This part will pack the dynamicfloatarray and update the instanced array WebGLBufffer
  601. // Skip it if instanced arrays are not supported
  602. if (this.owner.supportInstancedArray) {
  603. // Flag for instancing
  604. context.useInstancing = true;
  605. // Make sure all the WebGLBuffers of the Instanced Array are created/up to date for the parts to render.
  606. for (var i = 0; i < gipd.length; i++) {
  607. var pid = gipd[i];
  608. // If the instances of the model was changed, pack the data
  609. var array = pid._partData;
  610. var instanceData_1 = array.pack();
  611. renderCount += array.usedElementCount;
  612. // Compute the size the instance buffer should have
  613. var neededSize = array.usedElementCount * array.stride * 4;
  614. // Check if we have to (re)create the instancesBuffer because there's none or the size is too small
  615. if (!pid._partBuffer || (pid._partBufferSize < neededSize)) {
  616. if (pid._partBuffer) {
  617. engine.deleteInstancesBuffer(pid._partBuffer);
  618. }
  619. pid._partBuffer = engine.createInstancesBuffer(neededSize); // Create + bind
  620. pid._partBufferSize = neededSize;
  621. setDirty(false);
  622. // Update the WebGL buffer to match the new content of the instances data
  623. engine.updateArrayBuffer(instanceData_1);
  624. }
  625. else if (getDirty()) {
  626. // Update the WebGL buffer to match the new content of the instances data
  627. engine.bindArrayBuffer(pid._partBuffer);
  628. engine.updateArrayBuffer(instanceData_1);
  629. }
  630. }
  631. setDirty(false);
  632. }
  633. else {
  634. context.partDataStartIndex = 0;
  635. // Find the first valid object to get the count
  636. if (context.groupInfoPartData.length > 0) {
  637. var i = 0;
  638. while (!context.groupInfoPartData[i]) {
  639. i++;
  640. }
  641. context.partDataEndIndex = context.groupInfoPartData[i]._partData.usedElementCount;
  642. }
  643. }
  644. return renderCount;
  645. };
  646. Group2D.prototype._setRenderingScale = function (scale) {
  647. if (this._renderableData._renderingScale === scale) {
  648. return;
  649. }
  650. this._renderableData._renderingScale = scale;
  651. };
  652. Group2D.prototype._bindCacheTarget = function () {
  653. var curWidth;
  654. var curHeight;
  655. var rd = this._renderableData;
  656. var rs = rd._renderingScale;
  657. Group2D._s.width = Math.ceil(this.actualSize.width * rs);
  658. Group2D._s.height = Math.ceil(this.actualSize.height * rs);
  659. var sizeChanged = !Group2D._s.equals(rd._cacheSize);
  660. if (rd._cacheNode) {
  661. var size = rd._cacheNode.contentSize;
  662. // Check if we have to deallocate because the size is too small
  663. if ((size.width < Group2D._s.width) || (size.height < Group2D._s.height)) {
  664. // For Screen space: over-provisioning of 7% more to avoid frequent resizing for few pixels...
  665. // For World space: no over-provisioning
  666. var overprovisioning = this.owner.isScreenSpace ? 1.07 : 1;
  667. curWidth = Math.floor(Group2D._s.width * overprovisioning);
  668. curHeight = Math.floor(Group2D._s.height * overprovisioning);
  669. //console.log(`[${this._globalTransformProcessStep}] Resize group ${this.id}, width: ${curWidth}, height: ${curHeight}`);
  670. rd._cacheTexture.freeRect(rd._cacheNode);
  671. rd._cacheNode = null;
  672. }
  673. }
  674. if (!rd._cacheNode) {
  675. // Check if we have to allocate a rendering zone in the global cache texture
  676. var res = this.owner._allocateGroupCache(this, this.parent && this.parent.renderGroup, curWidth ? new BABYLON.Size(curWidth, curHeight) : null, rd._useMipMap, rd._anisotropicLevel);
  677. rd._cacheNode = res.node;
  678. rd._cacheTexture = res.texture;
  679. rd._cacheRenderSprite = res.sprite;
  680. sizeChanged = true;
  681. }
  682. if (sizeChanged) {
  683. rd._cacheSize.copyFrom(Group2D._s);
  684. rd._cacheNodeUVs = rd._cacheNode.getUVsForCustomSize(rd._cacheSize);
  685. this.scale = this._renderableData._renderingScale;
  686. if (rd._cacheNodeUVsChangedObservable && rd._cacheNodeUVsChangedObservable.hasObservers()) {
  687. rd._cacheNodeUVsChangedObservable.notifyObservers(rd._cacheNodeUVs);
  688. }
  689. this._setFlags(BABYLON.SmartPropertyPrim.flagWorldCacheChanged);
  690. }
  691. var n = rd._cacheNode;
  692. rd._cacheTexture.bindTextureForPosSize(n.pos, Group2D._s, true);
  693. };
  694. Group2D.prototype._unbindCacheTarget = function () {
  695. if (this._renderableData._cacheTexture) {
  696. this._renderableData._cacheTexture.unbindTexture();
  697. }
  698. };
  699. Group2D.prototype.handleGroupChanged = function (prop) {
  700. // This method is only for cachedGroup
  701. var rd = this._renderableData;
  702. if (!rd) {
  703. return;
  704. }
  705. var cachedSprite = rd._cacheRenderSprite;
  706. if (!this.isCachedGroup || !cachedSprite) {
  707. return;
  708. }
  709. // For now we only support these property changes
  710. // TODO: add more! :)
  711. if (prop.id === BABYLON.Prim2DBase.actualPositionProperty.id) {
  712. cachedSprite.actualPosition = this.actualPosition.clone();
  713. if (cachedSprite.position != null) {
  714. cachedSprite.position = cachedSprite.actualPosition.clone();
  715. }
  716. }
  717. else if (prop.id === BABYLON.Prim2DBase.rotationProperty.id) {
  718. cachedSprite.rotation = this.rotation;
  719. }
  720. else if (prop.id === BABYLON.Prim2DBase.scaleProperty.id) {
  721. cachedSprite.scale = this.scale;
  722. }
  723. else if (prop.id === BABYLON.Prim2DBase.originProperty.id) {
  724. cachedSprite.origin = this.origin.clone();
  725. }
  726. else if (prop.id === Group2D.actualSizeProperty.id) {
  727. cachedSprite.size = this.actualSize.clone();
  728. }
  729. };
  730. Group2D.prototype.detectGroupStates = function () {
  731. var isCanvas = this instanceof BABYLON.Canvas2D;
  732. var canvasStrat = this.owner.cachingStrategy;
  733. // In Don't Cache mode, only the canvas is renderable, all the other groups are logical. There are not a single cached group.
  734. if (canvasStrat === BABYLON.Canvas2D.CACHESTRATEGY_DONTCACHE) {
  735. this._isRenderableGroup = isCanvas;
  736. this._isCachedGroup = false;
  737. }
  738. else if (canvasStrat === BABYLON.Canvas2D.CACHESTRATEGY_CANVAS) {
  739. if (isCanvas) {
  740. this._isRenderableGroup = true;
  741. this._isCachedGroup = true;
  742. }
  743. else {
  744. this._isRenderableGroup = this.id === "__cachedCanvasGroup__";
  745. this._isCachedGroup = false;
  746. }
  747. }
  748. else if (canvasStrat === BABYLON.Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
  749. if (isCanvas) {
  750. this._isRenderableGroup = true;
  751. this._isCachedGroup = false;
  752. }
  753. else {
  754. if (this.hierarchyDepth === 1) {
  755. this._isRenderableGroup = true;
  756. this._isCachedGroup = true;
  757. }
  758. else {
  759. this._isRenderableGroup = false;
  760. this._isCachedGroup = false;
  761. }
  762. }
  763. }
  764. else if (canvasStrat === BABYLON.Canvas2D.CACHESTRATEGY_ALLGROUPS) {
  765. var gcb = this.cacheBehavior;
  766. if ((gcb === Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE) || (gcb === Group2D.GROUPCACHEBEHAVIOR_CACHEINPARENTGROUP)) {
  767. this._isRenderableGroup = gcb === Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE;
  768. this._isCachedGroup = false;
  769. }
  770. if (gcb === Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY) {
  771. this._isRenderableGroup = true;
  772. this._isCachedGroup = true;
  773. }
  774. }
  775. if (this._isRenderableGroup) {
  776. // Yes, we do need that check, trust me, unfortunately we can call _detectGroupStates many time on the same object...
  777. if (!this._renderableData) {
  778. this._renderableData = new RenderableGroupData();
  779. }
  780. }
  781. // If the group is tagged as renderable we add it to the renderable tree
  782. if (this._isCachedGroup) {
  783. var cur = this.parent;
  784. while (cur) {
  785. if (cur instanceof Group2D && cur._isRenderableGroup) {
  786. if (cur._renderableData._childrenRenderableGroups.indexOf(this) === -1) {
  787. cur._renderableData._childrenRenderableGroups.push(this);
  788. }
  789. break;
  790. }
  791. cur = cur.parent;
  792. }
  793. }
  794. };
  795. Group2D.GROUP2D_PROPCOUNT = BABYLON.Prim2DBase.PRIM2DBASE_PROPCOUNT + 5;
  796. /**
  797. * Default behavior, the group will use the caching strategy defined at the Canvas Level
  798. */
  799. Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY = 0;
  800. /**
  801. * When used, this group's content won't be cached, no matter which strategy used.
  802. * If the group is part of a WorldSpace Canvas, its content will be drawn in the Canvas cache bitmap.
  803. */
  804. Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE = 1;
  805. /**
  806. * When used, the group's content will be cached in the nearest cached parent group/canvas
  807. */
  808. Group2D.GROUPCACHEBEHAVIOR_CACHEINPARENTGROUP = 2;
  809. Group2D._s = BABYLON.Size.Zero();
  810. __decorate([
  811. BABYLON.instanceLevelProperty(BABYLON.Prim2DBase.PRIM2DBASE_PROPCOUNT + 1, function (pi) { return Group2D.sizeProperty = pi; }, false, true)
  812. ], Group2D.prototype, "size", null);
  813. __decorate([
  814. BABYLON.instanceLevelProperty(BABYLON.Prim2DBase.PRIM2DBASE_PROPCOUNT + 2, function (pi) { return Group2D.actualSizeProperty = pi; })
  815. ], Group2D.prototype, "actualSize", null);
  816. Group2D = __decorate([
  817. BABYLON.className("Group2D")
  818. ], Group2D);
  819. return Group2D;
  820. }(BABYLON.Prim2DBase));
  821. BABYLON.Group2D = Group2D;
  822. var RenderableGroupData = (function () {
  823. function RenderableGroupData() {
  824. this._primDirtyList = new Array();
  825. this._childrenRenderableGroups = new Array();
  826. this._renderGroupInstancesInfo = new BABYLON.StringDictionary();
  827. this._transparentPrimitives = new Array();
  828. this._transparentSegments = new Array();
  829. this._transparentListChanged = false;
  830. this._cacheNode = null;
  831. this._cacheTexture = null;
  832. this._cacheRenderSprite = null;
  833. this._renderingScale = 1;
  834. this._cacheNodeUVs = null;
  835. this._cacheNodeUVsChangedObservable = null;
  836. this._cacheSize = BABYLON.Size.Zero();
  837. this._useMipMap = false;
  838. this._anisotropicLevel = 1;
  839. }
  840. RenderableGroupData.prototype.dispose = function (engine) {
  841. if (this._cacheRenderSprite) {
  842. this._cacheRenderSprite.dispose();
  843. this._cacheRenderSprite = null;
  844. }
  845. if (this._cacheTexture && this._cacheNode) {
  846. this._cacheTexture.freeRect(this._cacheNode);
  847. this._cacheTexture = null;
  848. this._cacheNode = null;
  849. }
  850. if (this._primDirtyList) {
  851. this._primDirtyList.splice(0);
  852. this._primDirtyList = null;
  853. }
  854. if (this._renderGroupInstancesInfo) {
  855. this._renderGroupInstancesInfo.forEach(function (k, v) {
  856. v.dispose();
  857. });
  858. this._renderGroupInstancesInfo = null;
  859. }
  860. if (this._cacheNodeUVsChangedObservable) {
  861. this._cacheNodeUVsChangedObservable.clear();
  862. this._cacheNodeUVsChangedObservable = null;
  863. }
  864. if (this._transparentSegments) {
  865. for (var _i = 0, _a = this._transparentSegments; _i < _a.length; _i++) {
  866. var ts = _a[_i];
  867. ts.dispose(engine);
  868. }
  869. this._transparentSegments.splice(0);
  870. this._transparentSegments = null;
  871. }
  872. };
  873. RenderableGroupData.prototype.addNewTransparentPrimitiveInfo = function (prim, gii) {
  874. var tpi = new TransparentPrimitiveInfo();
  875. tpi._primitive = prim;
  876. tpi._groupInstanceInfo = gii;
  877. tpi._transparentSegment = null;
  878. this._transparentPrimitives.push(tpi);
  879. this._transparentListChanged = true;
  880. return tpi;
  881. };
  882. RenderableGroupData.prototype.removeTransparentPrimitiveInfo = function (tpi) {
  883. var index = this._transparentPrimitives.indexOf(tpi);
  884. if (index !== -1) {
  885. this._transparentPrimitives.splice(index, 1);
  886. this._transparentListChanged = true;
  887. }
  888. };
  889. RenderableGroupData.prototype.transparentPrimitiveZChanged = function (tpi) {
  890. this._transparentListChanged = true;
  891. //this.updateSmallestZChangedPrim(tpi);
  892. };
  893. return RenderableGroupData;
  894. }());
  895. BABYLON.RenderableGroupData = RenderableGroupData;
  896. var TransparentPrimitiveInfo = (function () {
  897. function TransparentPrimitiveInfo() {
  898. }
  899. return TransparentPrimitiveInfo;
  900. }());
  901. BABYLON.TransparentPrimitiveInfo = TransparentPrimitiveInfo;
  902. })(BABYLON || (BABYLON = {}));