babylon.group2d.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  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. * Don't invoke directly, rely on Group2D.CreateXXX methods
  18. */
  19. function Group2D() {
  20. _super.call(this);
  21. this._primDirtyList = new Array();
  22. this._childrenRenderableGroups = new Array();
  23. this._renderGroupInstancesInfo = new BABYLON.StringDictionary();
  24. }
  25. Group2D.CreateGroup2D = function (parent, id, position, size, cacheBehabior) {
  26. if (cacheBehabior === void 0) { cacheBehabior = Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY; }
  27. BABYLON.Prim2DBase.CheckParent(parent);
  28. var g = new Group2D();
  29. g.setupGroup2D(parent.owner, parent, id, position, size, cacheBehabior);
  30. return g;
  31. };
  32. Group2D._createCachedCanvasGroup = function (owner) {
  33. var g = new Group2D();
  34. g.setupGroup2D(owner, null, "__cachedCanvasGroup__", BABYLON.Vector2.Zero());
  35. return g;
  36. };
  37. Group2D.prototype.applyCachedTexture = function (vertexData, material) {
  38. this._bindCacheTarget();
  39. var uv = vertexData.uvs;
  40. var nodeuv = this._cacheNode.UVs;
  41. for (var i = 0; i < 4; i++) {
  42. uv[i * 2 + 0] = nodeuv[i].x;
  43. uv[i * 2 + 1] = nodeuv[i].y;
  44. }
  45. material.diffuseTexture = this._cacheTexture;
  46. material.emissiveColor = new BABYLON.Color3(1, 1, 1);
  47. this._cacheTexture.hasAlpha = true;
  48. this._unbindCacheTarget();
  49. };
  50. /**
  51. * Call this method to remove this Group and its children from the Canvas
  52. */
  53. Group2D.prototype.dispose = function () {
  54. if (!_super.prototype.dispose.call(this)) {
  55. return false;
  56. }
  57. if (this._cacheRenderSprite) {
  58. this._cacheRenderSprite.dispose();
  59. this._cacheRenderSprite = null;
  60. }
  61. if (this._cacheTexture && this._cacheNode) {
  62. this._cacheTexture.freeRect(this._cacheNode);
  63. this._cacheTexture = null;
  64. this._cacheNode = null;
  65. }
  66. if (this._primDirtyList) {
  67. this._primDirtyList.splice(0);
  68. this._primDirtyList = null;
  69. }
  70. if (this._renderGroupInstancesInfo) {
  71. this._renderGroupInstancesInfo.forEach(function (k, v) {
  72. v.dispose();
  73. });
  74. this._renderGroupInstancesInfo = null;
  75. }
  76. return true;
  77. };
  78. Group2D.prototype.setupGroup2D = function (owner, parent, id, position, size, cacheBehavior) {
  79. if (cacheBehavior === void 0) { cacheBehavior = Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY; }
  80. this._cacheBehavior = cacheBehavior;
  81. this.setupPrim2DBase(owner, parent, id, position);
  82. this.size = size;
  83. this._viewportPosition = BABYLON.Vector2.Zero();
  84. };
  85. Object.defineProperty(Group2D.prototype, "isRenderableGroup", {
  86. /**
  87. * @returns Returns true if the Group render content, false if it's a logical group only
  88. */
  89. get: function () {
  90. return this._isRenderableGroup;
  91. },
  92. enumerable: true,
  93. configurable: true
  94. });
  95. Object.defineProperty(Group2D.prototype, "isCachedGroup", {
  96. /**
  97. * @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
  98. */
  99. get: function () {
  100. return this._isCachedGroup;
  101. },
  102. enumerable: true,
  103. configurable: true
  104. });
  105. Object.defineProperty(Group2D.prototype, "size", {
  106. get: function () {
  107. return this._size;
  108. },
  109. /**
  110. * Get/Set the size of the group. If null the size of the group will be determine from its content.
  111. * 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.
  112. */
  113. set: function (val) {
  114. this._size = val;
  115. },
  116. enumerable: true,
  117. configurable: true
  118. });
  119. Object.defineProperty(Group2D.prototype, "viewportSize", {
  120. get: function () {
  121. return this._viewportSize;
  122. },
  123. enumerable: true,
  124. configurable: true
  125. });
  126. Object.defineProperty(Group2D.prototype, "actualSize", {
  127. get: function () {
  128. // The computed size will be floor on both width and height
  129. var actualSize;
  130. // Return the size if set by the user
  131. if (this._size) {
  132. actualSize = new BABYLON.Size(Math.ceil(this._size.width), Math.ceil(this._size.height));
  133. }
  134. else {
  135. var m = this.boundingInfo.max();
  136. actualSize = new BABYLON.Size(Math.ceil(m.x), Math.ceil(m.y));
  137. }
  138. // Compare the size with the one we previously had, if it differ we set the property dirty and trigger a GroupChanged to synchronize a displaySprite (if any)
  139. if (!actualSize.equals(this._actualSize)) {
  140. this._instanceDirtyFlags |= Group2D.actualSizeProperty.flagId;
  141. this._actualSize = actualSize;
  142. this.handleGroupChanged(Group2D.actualSizeProperty);
  143. }
  144. return actualSize;
  145. },
  146. enumerable: true,
  147. configurable: true
  148. });
  149. Object.defineProperty(Group2D.prototype, "cacheBehavior", {
  150. /**
  151. * 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.
  152. * 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.
  153. */
  154. get: function () {
  155. return this._cacheBehavior;
  156. },
  157. enumerable: true,
  158. configurable: true
  159. });
  160. Group2D.prototype._addPrimToDirtyList = function (prim) {
  161. this._primDirtyList.push(prim);
  162. };
  163. Group2D.prototype._renderCachedCanvas = function (context) {
  164. this.updateGlobalTransVis(true);
  165. this._prepareGroupRender(context);
  166. this._groupRender(context);
  167. };
  168. Group2D.prototype.updateLevelBoundingInfo = function () {
  169. var size;
  170. // If the size is set by the user, the boundingInfo is computed from this value
  171. if (this.size) {
  172. size = this.size;
  173. }
  174. else {
  175. size = new BABYLON.Size(0, 0);
  176. }
  177. BABYLON.BoundingInfo2D.CreateFromSizeToRef(size, this._levelBoundingInfo);
  178. };
  179. // Method called only on renderable groups to prepare the rendering
  180. Group2D.prototype._prepareGroupRender = function (context) {
  181. var sortedDirtyList = null;
  182. // Update the Global Transformation and visibility status of the changed primitives
  183. if ((this._primDirtyList.length > 0) || context.forceRefreshPrimitive) {
  184. sortedDirtyList = this._primDirtyList.sort(function (a, b) { return a.hierarchyDepth - b.hierarchyDepth; });
  185. this.updateGlobalTransVisOf(sortedDirtyList, true);
  186. }
  187. // Setup the size of the rendering viewport
  188. // 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
  189. if (!this._isCachedGroup) {
  190. // Compute the WebGL viewport's location/size
  191. var t = this._globalTransform.getTranslation();
  192. var s = this.actualSize.clone();
  193. var rs = this.owner._renderingSize;
  194. s.height = Math.min(s.height, rs.height - t.y);
  195. s.width = Math.min(s.width, rs.width - t.x);
  196. var x = t.x;
  197. var y = t.y;
  198. // 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
  199. this._viewportPosition.x = x;
  200. this._viewportPosition.y = y;
  201. var vw = s.width;
  202. var vh = s.height;
  203. if (!this._viewportSize) {
  204. this._viewportSize = new BABYLON.Size(vw, vh);
  205. }
  206. else {
  207. if (this._viewportSize.width !== vw || this._viewportSize.height !== vh) {
  208. context.forceRefreshPrimitive = true;
  209. }
  210. this._viewportSize.width = vw;
  211. this._viewportSize.height = vh;
  212. }
  213. }
  214. else {
  215. var newSize = this.actualSize.clone();
  216. if (!newSize.equals(this._viewportSize)) {
  217. context.forceRefreshPrimitive = true;
  218. }
  219. this._viewportSize = newSize;
  220. }
  221. if ((this._primDirtyList.length > 0) || context.forceRefreshPrimitive) {
  222. // If the group is cached, set the dirty flag to true because of the incoming changes
  223. this._cacheGroupDirty = this._isCachedGroup;
  224. // If it's a force refresh, prepare all the children
  225. if (context.forceRefreshPrimitive) {
  226. for (var _i = 0, _a = this._children; _i < _a.length; _i++) {
  227. var p = _a[_i];
  228. p._prepareRender(context);
  229. }
  230. }
  231. else {
  232. // Each primitive that changed at least once was added into the primDirtyList, we have to sort this level using
  233. // the hierarchyDepth in order to prepare primitives from top to bottom
  234. if (!sortedDirtyList) {
  235. sortedDirtyList = this._primDirtyList.sort(function (a, b) { return a.hierarchyDepth - b.hierarchyDepth; });
  236. }
  237. sortedDirtyList.forEach(function (p) {
  238. // 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.
  239. // 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.
  240. if (!p.isDisposed && p.needPrepare()) {
  241. p._prepareRender(context);
  242. }
  243. });
  244. // Everything is updated, clear the dirty list
  245. this._primDirtyList.splice(0);
  246. }
  247. }
  248. // A renderable group has a list of direct children that are also renderable groups, we recurse on them to also prepare them
  249. this._childrenRenderableGroups.forEach(function (g) {
  250. g._prepareGroupRender(context);
  251. });
  252. };
  253. Group2D.prototype._groupRender = function (context) {
  254. var _this = this;
  255. var engine = this.owner.engine;
  256. var failedCount = 0;
  257. // First recurse to children render group to render them (in their cache or on screen)
  258. for (var _i = 0, _a = this._childrenRenderableGroups; _i < _a.length; _i++) {
  259. var childGroup = _a[_i];
  260. childGroup._groupRender(context);
  261. }
  262. // Render the primitives if needed: either if we don't cache the content or if the content is cached but has changed
  263. if (!this.isCachedGroup || this._cacheGroupDirty) {
  264. if (this.isCachedGroup) {
  265. this._bindCacheTarget();
  266. }
  267. else {
  268. var curVP = engine.setDirectViewport(this._viewportPosition.x, this._viewportPosition.y, this._viewportSize.width, this._viewportSize.height);
  269. }
  270. // For each different model of primitive to render
  271. var totalRenderCount_1 = 0;
  272. this._renderGroupInstancesInfo.forEach(function (k, v) {
  273. // This part will pack the dynamicfloatarray and update the instanced array WebGLBufffer
  274. // Skip it if instanced arrays are not supported
  275. if (_this.owner.supportInstancedArray) {
  276. for (var i = 0; i < v._instancesPartsData.length; i++) {
  277. // If the instances of the model was changed, pack the data
  278. var array = v._instancesPartsData[i];
  279. var instanceData_1 = array.pack();
  280. totalRenderCount_1 += array.usedElementCount;
  281. // Compute the size the instance buffer should have
  282. var neededSize = array.usedElementCount * array.stride * 4;
  283. // Check if we have to (re)create the instancesBuffer because there's none or the size is too small
  284. if (!v._instancesPartsBuffer[i] || (v._instancesPartsBufferSize[i] < neededSize)) {
  285. if (v._instancesPartsBuffer[i]) {
  286. engine.deleteInstancesBuffer(v._instancesPartsBuffer[i]);
  287. }
  288. v._instancesPartsBuffer[i] = engine.createInstancesBuffer(neededSize);
  289. v._instancesPartsBufferSize[i] = neededSize;
  290. v._dirtyInstancesData = false;
  291. // Update the WebGL buffer to match the new content of the instances data
  292. engine._gl.bufferSubData(engine._gl.ARRAY_BUFFER, 0, instanceData_1);
  293. }
  294. else if (v._dirtyInstancesData) {
  295. // Update the WebGL buffer to match the new content of the instances data
  296. engine._gl.bindBuffer(engine._gl.ARRAY_BUFFER, v._instancesPartsBuffer[i]);
  297. engine._gl.bufferSubData(engine._gl.ARRAY_BUFFER, 0, instanceData_1);
  298. v._dirtyInstancesData = false;
  299. }
  300. }
  301. }
  302. // Submit render only if we have something to render (everything may be hidden and the floatarray empty)
  303. if (!_this.owner.supportInstancedArray || totalRenderCount_1 > 0) {
  304. // render all the instances of this model, if the render method returns true then our instances are no longer dirty
  305. var renderFailed = !v._modelCache.render(v, context);
  306. // Update dirty flag/related
  307. v._dirtyInstancesData = renderFailed;
  308. failedCount += renderFailed ? 1 : 0;
  309. }
  310. });
  311. // The group's content is no longer dirty
  312. this._cacheGroupDirty = failedCount !== 0;
  313. if (this.isCachedGroup) {
  314. this._unbindCacheTarget();
  315. }
  316. else {
  317. if (curVP) {
  318. engine.setViewport(curVP);
  319. }
  320. }
  321. }
  322. };
  323. Group2D.prototype._bindCacheTarget = function () {
  324. var curWidth;
  325. var curHeight;
  326. if (this._cacheNode) {
  327. var size = this._cacheNode.contentSize;
  328. var groupWidth = Math.ceil(this.actualSize.width);
  329. var groupHeight = Math.ceil(this.actualSize.height);
  330. if ((size.width < groupWidth) || (size.height < groupHeight)) {
  331. curWidth = Math.floor(size.width * 1.07); // Grow 5% more to avoid frequent resizing for few pixels...
  332. curHeight = Math.floor(size.height * 1.07);
  333. //console.log(`[${this._globalTransformProcessStep}] Resize group ${this.id}, width: ${curWidth}, height: ${curHeight}`);
  334. this._cacheTexture.freeRect(this._cacheNode);
  335. this._cacheNode = null;
  336. }
  337. }
  338. if (!this._cacheNode) {
  339. // Check if we have to allocate a rendering zone in the global cache texture
  340. var res = this.owner._allocateGroupCache(this, this.renderGroup, curWidth ? new BABYLON.Size(curWidth, curHeight) : null);
  341. this._cacheNode = res.node;
  342. this._cacheTexture = res.texture;
  343. this._cacheRenderSprite = res.sprite;
  344. var size = this._cacheNode.contentSize;
  345. }
  346. var n = this._cacheNode;
  347. this._cacheTexture.bindTextureForPosSize(n.pos, this.actualSize, true);
  348. };
  349. Group2D.prototype._unbindCacheTarget = function () {
  350. if (this._cacheTexture) {
  351. this._cacheTexture.unbindTexture();
  352. }
  353. };
  354. Group2D.prototype.handleGroupChanged = function (prop) {
  355. // This method is only for cachedGroup
  356. if (!this.isCachedGroup || !this._cacheRenderSprite) {
  357. return;
  358. }
  359. // For now we only support these property changes
  360. // TODO: add more! :)
  361. if (prop.id === BABYLON.Prim2DBase.positionProperty.id) {
  362. this._cacheRenderSprite.position = this.position.clone();
  363. }
  364. else if (prop.id === BABYLON.Prim2DBase.rotationProperty.id) {
  365. this._cacheRenderSprite.rotation = this.rotation;
  366. }
  367. else if (prop.id === BABYLON.Prim2DBase.scaleProperty.id) {
  368. this._cacheRenderSprite.scale = this.scale;
  369. }
  370. else if (prop.id === Group2D.actualSizeProperty.id) {
  371. this._cacheRenderSprite.spriteSize = this.actualSize.clone();
  372. }
  373. };
  374. Group2D.prototype.detectGroupStates = function () {
  375. var isCanvas = this instanceof BABYLON.Canvas2D;
  376. var canvasStrat = this.owner.cachingStrategy;
  377. // In Don't Cache mode, only the canvas is renderable, all the other groups are logical. There are not a single cached group.
  378. if (canvasStrat === BABYLON.Canvas2D.CACHESTRATEGY_DONTCACHE) {
  379. this._isRenderableGroup = isCanvas;
  380. this._isCachedGroup = false;
  381. }
  382. else if (canvasStrat === BABYLON.Canvas2D.CACHESTRATEGY_CANVAS) {
  383. if (isCanvas) {
  384. this._isRenderableGroup = true;
  385. this._isCachedGroup = true;
  386. }
  387. else {
  388. this._isRenderableGroup = this.id === "__cachedCanvasGroup__";
  389. this._isCachedGroup = false;
  390. }
  391. }
  392. else if (canvasStrat === BABYLON.Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
  393. if (isCanvas) {
  394. this._isRenderableGroup = true;
  395. this._isCachedGroup = false;
  396. }
  397. else {
  398. if (this.hierarchyDepth === 1) {
  399. this._isRenderableGroup = true;
  400. this._isCachedGroup = true;
  401. }
  402. else {
  403. this._isRenderableGroup = false;
  404. this._isCachedGroup = false;
  405. }
  406. }
  407. }
  408. else if (canvasStrat === BABYLON.Canvas2D.CACHESTRATEGY_ALLGROUPS) {
  409. var gcb = this.cacheBehavior;
  410. if ((gcb === Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE) || (gcb === Group2D.GROUPCACHEBEHAVIOR_CACHEINPARENTGROUP)) {
  411. this._isRenderableGroup = gcb === Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE;
  412. this._isCachedGroup = false;
  413. }
  414. if (gcb === Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY) {
  415. this._isRenderableGroup = true;
  416. this._isCachedGroup = true;
  417. }
  418. }
  419. // If the group is tagged as renderable we add it to the renderable tree
  420. if (this._isCachedGroup) {
  421. var cur = this.parent;
  422. while (cur) {
  423. if (cur instanceof Group2D && cur._isRenderableGroup) {
  424. cur._childrenRenderableGroups.push(this);
  425. break;
  426. }
  427. cur = cur.parent;
  428. }
  429. }
  430. };
  431. Group2D.GROUP2D_PROPCOUNT = BABYLON.Prim2DBase.PRIM2DBASE_PROPCOUNT + 5;
  432. /**
  433. * Default behavior, the group will use the caching strategy defined at the Canvas Level
  434. */
  435. Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY = 0;
  436. /**
  437. * When used, this group's content won't be cached, no matter which strategy used.
  438. * If the group is part of a WorldSpace Canvas, its content will be drawn in the Canvas cache bitmap.
  439. */
  440. Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE = 1;
  441. /**
  442. * When used, the group's content will be cached in the nearest cached parent group/canvas
  443. */
  444. Group2D.GROUPCACHEBEHAVIOR_CACHEINPARENTGROUP = 2;
  445. __decorate([
  446. BABYLON.instanceLevelProperty(BABYLON.Prim2DBase.PRIM2DBASE_PROPCOUNT + 1, function (pi) { return Group2D.sizeProperty = pi; }, false, true)
  447. ], Group2D.prototype, "size", null);
  448. __decorate([
  449. BABYLON.instanceLevelProperty(BABYLON.Prim2DBase.PRIM2DBASE_PROPCOUNT + 2, function (pi) { return Group2D.actualSizeProperty = pi; })
  450. ], Group2D.prototype, "actualSize", null);
  451. Group2D = __decorate([
  452. BABYLON.className("Group2D")
  453. ], Group2D);
  454. return Group2D;
  455. }(BABYLON.Prim2DBase));
  456. BABYLON.Group2D = Group2D;
  457. })(BABYLON || (BABYLON = {}));