babylon.rectangle2d.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. module BABYLON {
  2. export class Rectangle2DRenderCache extends ModelRenderCache {
  3. effectsReady: boolean = false;
  4. fillVB: WebGLBuffer = null;
  5. fillIB: WebGLBuffer = null;
  6. fillIndicesCount: number = 0;
  7. instancingFillAttributes: InstancingAttributeInfo[] = null;
  8. effectFill: Effect = null;
  9. effectFillInstanced: Effect = null;
  10. borderVB: WebGLBuffer = null;
  11. borderIB: WebGLBuffer = null;
  12. borderIndicesCount: number = 0;
  13. instancingBorderAttributes: InstancingAttributeInfo[] = null;
  14. effectBorder: Effect = null;
  15. effectBorderInstanced: Effect = null;
  16. constructor(engine: Engine, modelKey: string) {
  17. super(engine, modelKey);
  18. }
  19. render(instanceInfo: GroupInstanceInfo, context: Render2DContext): boolean {
  20. // Do nothing if the shader is still loading/preparing
  21. if (!this.effectsReady) {
  22. if ((this.effectFill && (!this.effectFill.isReady() || (this.effectFillInstanced && !this.effectFillInstanced.isReady()))) ||
  23. (this.effectBorder && (!this.effectBorder.isReady() || (this.effectBorderInstanced && !this.effectBorderInstanced.isReady())))) {
  24. return false;
  25. }
  26. this.effectsReady = true;
  27. }
  28. var engine = instanceInfo.owner.owner.engine;
  29. let depthFunction = 0;
  30. if (this.effectFill && this.effectBorder) {
  31. depthFunction = engine.getDepthFunction();
  32. engine.setDepthFunctionToLessOrEqual();
  33. }
  34. var curAlphaMode = engine.getAlphaMode();
  35. if (this.effectFill) {
  36. let partIndex = instanceInfo.partIndexFromId.get(Shape2D.SHAPE2D_FILLPARTID.toString());
  37. let pid = context.groupInfoPartData[partIndex];
  38. if (context.renderMode !== Render2DContext.RenderModeOpaque) {
  39. engine.setAlphaMode(Engine.ALPHA_COMBINE);
  40. }
  41. let effect = context.useInstancing ? this.effectFillInstanced : this.effectFill;
  42. engine.enableEffect(effect);
  43. engine.bindBuffersDirectly(this.fillVB, this.fillIB, [1], 4, effect);
  44. if (context.useInstancing) {
  45. if (!this.instancingFillAttributes) {
  46. this.instancingFillAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_FILLPARTID, effect);
  47. }
  48. engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingFillAttributes);
  49. engine.draw(true, 0, this.fillIndicesCount, pid._partData.usedElementCount);
  50. engine.unbindInstanceAttributes();
  51. } else {
  52. for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
  53. this.setupUniforms(effect, partIndex, pid._partData, i);
  54. engine.draw(true, 0, this.fillIndicesCount);
  55. }
  56. }
  57. }
  58. if (this.effectBorder) {
  59. let partIndex = instanceInfo.partIndexFromId.get(Shape2D.SHAPE2D_BORDERPARTID.toString());
  60. let pid = context.groupInfoPartData[partIndex];
  61. if (context.renderMode !== Render2DContext.RenderModeOpaque) {
  62. engine.setAlphaMode(Engine.ALPHA_COMBINE);
  63. }
  64. let effect = context.useInstancing ? this.effectBorderInstanced : this.effectBorder;
  65. engine.enableEffect(effect);
  66. engine.bindBuffersDirectly(this.borderVB, this.borderIB, [1], 4, effect);
  67. if (context.useInstancing) {
  68. if (!this.instancingBorderAttributes) {
  69. this.instancingBorderAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_BORDERPARTID, effect);
  70. }
  71. engine.updateAndBindInstancesBuffer(pid._partBuffer, null, this.instancingBorderAttributes);
  72. engine.draw(true, 0, this.borderIndicesCount, pid._partData.usedElementCount);
  73. engine.unbindInstanceAttributes();
  74. } else {
  75. for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
  76. this.setupUniforms(effect, partIndex, pid._partData, i);
  77. engine.draw(true, 0, this.borderIndicesCount);
  78. }
  79. }
  80. }
  81. engine.setAlphaMode(curAlphaMode);
  82. if (this.effectFill && this.effectBorder) {
  83. engine.setDepthFunction(depthFunction);
  84. }
  85. return true;
  86. }
  87. public dispose(): boolean {
  88. if (!super.dispose()) {
  89. return false;
  90. }
  91. if (this.fillVB) {
  92. this._engine._releaseBuffer(this.fillVB);
  93. this.fillVB = null;
  94. }
  95. if (this.fillIB) {
  96. this._engine._releaseBuffer(this.fillIB);
  97. this.fillIB = null;
  98. }
  99. if (this.effectFill) {
  100. this._engine._releaseEffect(this.effectFill);
  101. this.effectFill = null;
  102. }
  103. if (this.effectFillInstanced) {
  104. this._engine._releaseEffect(this.effectFillInstanced);
  105. this.effectFillInstanced = null;
  106. }
  107. if (this.borderVB) {
  108. this._engine._releaseBuffer(this.borderVB);
  109. this.borderVB = null;
  110. }
  111. if (this.borderIB) {
  112. this._engine._releaseBuffer(this.borderIB);
  113. this.borderIB = null;
  114. }
  115. if (this.effectBorder) {
  116. this._engine._releaseEffect(this.effectBorder);
  117. this.effectBorder = null;
  118. }
  119. if (this.effectBorderInstanced) {
  120. this._engine._releaseEffect(this.effectBorderInstanced);
  121. this.effectBorderInstanced = null;
  122. }
  123. return true;
  124. }
  125. }
  126. export class Rectangle2DInstanceData extends Shape2DInstanceData {
  127. constructor(partId: number) {
  128. super(partId, 1);
  129. }
  130. @instanceData()
  131. get properties(): Vector3 {
  132. return null;
  133. }
  134. }
  135. @className("Rectangle2D")
  136. export class Rectangle2D extends Shape2D {
  137. public static actualSizeProperty: Prim2DPropInfo;
  138. public static notRoundedProperty: Prim2DPropInfo;
  139. public static roundRadiusProperty: Prim2DPropInfo;
  140. @instanceLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 1, pi => Rectangle2D.actualSizeProperty = pi, false, true)
  141. public get actualSize(): Size {
  142. if (this._actualSize) {
  143. return this._actualSize;
  144. }
  145. return this.size;
  146. }
  147. public set actualSize(value: Size) {
  148. this._actualSize = value;
  149. }
  150. @modelLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 2, pi => Rectangle2D.notRoundedProperty = pi)
  151. public get notRounded(): boolean {
  152. return this._notRounded;
  153. }
  154. public set notRounded(value: boolean) {
  155. this._notRounded = value;
  156. }
  157. @instanceLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 3, pi => Rectangle2D.roundRadiusProperty = pi)
  158. public get roundRadius(): number {
  159. return this._roundRadius;
  160. }
  161. public set roundRadius(value: number) {
  162. this._roundRadius = value;
  163. this.notRounded = value === 0;
  164. this._positioningDirty();
  165. }
  166. private static _i0 = Vector2.Zero();
  167. private static _i1 = Vector2.Zero();
  168. private static _i2 = Vector2.Zero();
  169. protected levelIntersect(intersectInfo: IntersectInfo2D): boolean {
  170. // If we got there it mean the boundingInfo intersection succeed, if the rectangle has not roundRadius, it means it succeed!
  171. if (this.notRounded) {
  172. return true;
  173. }
  174. // If we got so far it means the bounding box at least passed, so we know it's inside the bounding rectangle, but it can be outside the roundedRectangle.
  175. // The easiest way is to check if the point is inside on of the four corners area (a little square of roundRadius size at the four corners)
  176. // If it's the case for one, check if the mouse is located in the quarter that we care about (the one who is visible) then finally make a distance check with the roundRadius radius to see if it's inside the circle quarter or outside.
  177. // First let remove the origin out the equation, to have the rectangle with an origin at bottom/left
  178. let size = this.size;
  179. Rectangle2D._i0.x = intersectInfo._localPickPosition.x;
  180. Rectangle2D._i0.y = intersectInfo._localPickPosition.y;
  181. let rr = this.roundRadius;
  182. let rrs = rr * rr;
  183. // Check if the point is in the bottom/left quarter area
  184. Rectangle2D._i1.x = rr;
  185. Rectangle2D._i1.y = rr;
  186. if (Rectangle2D._i0.x <= Rectangle2D._i1.x && Rectangle2D._i0.y <= Rectangle2D._i1.y) {
  187. // Compute the intersection point in the quarter local space
  188. Rectangle2D._i2.x = Rectangle2D._i0.x - Rectangle2D._i1.x;
  189. Rectangle2D._i2.y = Rectangle2D._i0.y - Rectangle2D._i1.y;
  190. // It's a hit if the squared distance is less/equal to the squared radius of the round circle
  191. return Rectangle2D._i2.lengthSquared() <= rrs;
  192. }
  193. // Check if the point is in the top/left quarter area
  194. Rectangle2D._i1.x = rr;
  195. Rectangle2D._i1.y = size.height - rr;
  196. if (Rectangle2D._i0.x <= Rectangle2D._i1.x && Rectangle2D._i0.y >= Rectangle2D._i1.y) {
  197. // Compute the intersection point in the quarter local space
  198. Rectangle2D._i2.x = Rectangle2D._i0.x - Rectangle2D._i1.x;
  199. Rectangle2D._i2.y = Rectangle2D._i0.y - Rectangle2D._i1.y;
  200. // It's a hit if the squared distance is less/equal to the squared radius of the round circle
  201. return Rectangle2D._i2.lengthSquared() <= rrs;
  202. }
  203. // Check if the point is in the top/right quarter area
  204. Rectangle2D._i1.x = size.width - rr;
  205. Rectangle2D._i1.y = size.height - rr;
  206. if (Rectangle2D._i0.x >= Rectangle2D._i1.x && Rectangle2D._i0.y >= Rectangle2D._i1.y) {
  207. // Compute the intersection point in the quarter local space
  208. Rectangle2D._i2.x = Rectangle2D._i0.x - Rectangle2D._i1.x;
  209. Rectangle2D._i2.y = Rectangle2D._i0.y - Rectangle2D._i1.y;
  210. // It's a hit if the squared distance is less/equal to the squared radius of the round circle
  211. return Rectangle2D._i2.lengthSquared() <= rrs;
  212. }
  213. // Check if the point is in the bottom/right quarter area
  214. Rectangle2D._i1.x = size.width - rr;
  215. Rectangle2D._i1.y = rr;
  216. if (Rectangle2D._i0.x >= Rectangle2D._i1.x && Rectangle2D._i0.y <= Rectangle2D._i1.y) {
  217. // Compute the intersection point in the quarter local space
  218. Rectangle2D._i2.x = Rectangle2D._i0.x - Rectangle2D._i1.x;
  219. Rectangle2D._i2.y = Rectangle2D._i0.y - Rectangle2D._i1.y;
  220. // It's a hit if the squared distance is less/equal to the squared radius of the round circle
  221. return Rectangle2D._i2.lengthSquared() <= rrs;
  222. }
  223. // At any other locations the point is guarantied to be inside
  224. return true;
  225. }
  226. protected updateLevelBoundingInfo() {
  227. BoundingInfo2D.CreateFromSizeToRef(this.actualSize, this._levelBoundingInfo);
  228. }
  229. /**
  230. * Create an Rectangle 2D Shape primitive. May be a sharp rectangle (with sharp corners), or a rounded one.
  231. * @param parent the parent primitive, must be a valid primitive (or the Canvas)
  232. * options:
  233. * - id a text identifier, for information purpose
  234. * - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0]
  235. * - origin: define the normalized origin point location, default [0.5;0.5]
  236. * - size: the size of the group. Alternatively the width and height properties can be set. Default will be [10;10].
  237. * - roundRadius: if the rectangle has rounded corner, set their radius, default is 0 (to get a sharp rectangle).
  238. * - fill: the brush used to draw the fill content of the ellipse, you can set null to draw nothing (but you will have to set a border brush), default is a SolidColorBrush of plain white.
  239. * - border: the brush used to draw the border of the ellipse, you can set null to draw nothing (but you will have to set a fill brush), default is null.
  240. * - borderThickness: the thickness of the drawn border, default is 1.
  241. * - isVisible: true if the primitive must be visible, false for hidden. Default is true.
  242. * - marginTop/Left/Right/Bottom: define the margin for the corresponding edge, if all of them are null, margin is not used in layout computing. Default Value is null for each.
  243. * - hAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
  244. * - vAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
  245. */
  246. constructor(settings ?: {
  247. parent ?: Prim2DBase,
  248. children ?: Array<Prim2DBase>,
  249. id ?: string,
  250. position ?: Vector2,
  251. x ?: number,
  252. y ?: number,
  253. rotation ?: number,
  254. scale ?: number,
  255. origin ?: Vector2,
  256. size ?: Size,
  257. width ?: number,
  258. height ?: number,
  259. roundRadius ?: number,
  260. fill ?: IBrush2D | string,
  261. border ?: IBrush2D | string,
  262. borderThickness ?: number,
  263. isVisible ?: boolean,
  264. marginTop ?: number | string,
  265. marginLeft ?: number | string,
  266. marginRight ?: number | string,
  267. marginBottom ?: number | string,
  268. margin ?: number | string,
  269. marginHAlignment ?: number,
  270. marginVAlignment ?: number,
  271. marginAlignment ?: string,
  272. paddingTop ?: number | string,
  273. paddingLeft ?: number | string,
  274. paddingRight ?: number | string,
  275. paddingBottom ?: number | string,
  276. padding ?: string,
  277. paddingHAlignment?: number,
  278. paddingVAlignment?: number,
  279. }) {
  280. // Avoid checking every time if the object exists
  281. if (settings == null) {
  282. settings = {};
  283. }
  284. super(settings);
  285. if (settings.size != null) {
  286. this.size = settings.size;
  287. }
  288. else if (settings.width || settings.height) {
  289. let size = new Size(settings.width, settings.height);
  290. this.size = size;
  291. }
  292. //let size = settings.size || (new Size((settings.width === null) ? null : (settings.width || 10), (settings.height === null) ? null : (settings.height || 10)));
  293. let roundRadius = (settings.roundRadius == null) ? 0 : settings.roundRadius;
  294. let borderThickness = (settings.borderThickness == null) ? 1 : settings.borderThickness;
  295. //this.size = size;
  296. this.roundRadius = roundRadius;
  297. this.borderThickness = borderThickness;
  298. }
  299. public static roundSubdivisions = 16;
  300. protected createModelRenderCache(modelKey: string): ModelRenderCache {
  301. let renderCache = new Rectangle2DRenderCache(this.owner.engine, modelKey);
  302. return renderCache;
  303. }
  304. protected setupModelRenderCache(modelRenderCache: ModelRenderCache) {
  305. let renderCache = <Rectangle2DRenderCache>modelRenderCache;
  306. let engine = this.owner.engine;
  307. // Need to create WebGL resources for fill part?
  308. if (this.fill) {
  309. let vbSize = ((this.notRounded ? 1 : Rectangle2D.roundSubdivisions) * 4) + 1;
  310. let vb = new Float32Array(vbSize);
  311. for (let i = 0; i < vbSize; i++) {
  312. vb[i] = i;
  313. }
  314. renderCache.fillVB = engine.createVertexBuffer(vb);
  315. let triCount = vbSize - 1;
  316. let ib = new Float32Array(triCount * 3);
  317. for (let i = 0; i < triCount; i++) {
  318. ib[i * 3 + 0] = 0;
  319. ib[i * 3 + 2] = i + 1;
  320. ib[i * 3 + 1] = i + 2;
  321. }
  322. ib[triCount * 3 - 2] = 1;
  323. renderCache.fillIB = engine.createIndexBuffer(ib);
  324. renderCache.fillIndicesCount = triCount * 3;
  325. // Get the instanced version of the effect, if the engine does not support it, null is return and we'll only draw on by one
  326. let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["index"], true);
  327. if (ei) {
  328. renderCache.effectFillInstanced = engine.createEffect("rect2d", ei.attributes, ei.uniforms, [], ei.defines, null);
  329. }
  330. // Get the non instanced version
  331. ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["index"], false);
  332. renderCache.effectFill = engine.createEffect("rect2d", ei.attributes, ei.uniforms, [], ei.defines, null);
  333. }
  334. // Need to create WebGL resource for border part?
  335. if (this.border) {
  336. let vbSize = (this.notRounded ? 1 : Rectangle2D.roundSubdivisions) * 4 * 2;
  337. let vb = new Float32Array(vbSize);
  338. for (let i = 0; i < vbSize; i++) {
  339. vb[i] = i;
  340. }
  341. renderCache.borderVB = engine.createVertexBuffer(vb);
  342. let triCount = vbSize;
  343. let rs = triCount / 2;
  344. let ib = new Float32Array(triCount * 3);
  345. for (let i = 0; i < rs; i++) {
  346. let r0 = i;
  347. let r1 = (i + 1) % rs;
  348. ib[i * 6 + 0] = rs + r1;
  349. ib[i * 6 + 1] = rs + r0;
  350. ib[i * 6 + 2] = r0;
  351. ib[i * 6 + 3] = r1;
  352. ib[i * 6 + 4] = rs + r1;
  353. ib[i * 6 + 5] = r0;
  354. }
  355. renderCache.borderIB = engine.createIndexBuffer(ib);
  356. renderCache.borderIndicesCount = triCount * 3;
  357. // Get the instanced version of the effect, if the engine does not support it, null is return and we'll only draw on by one
  358. let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_BORDERPARTID, ["index"], true);
  359. if (ei) {
  360. renderCache.effectBorderInstanced = engine.createEffect("rect2d", ei.attributes, ei.uniforms, [], ei.defines, null);
  361. }
  362. // Get the non instanced version
  363. ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_BORDERPARTID, ["index"], false);
  364. renderCache.effectBorder = engine.createEffect("rect2d", ei.attributes, ei.uniforms, [], ei.defines, null);
  365. }
  366. return renderCache;
  367. }
  368. // We override this method because if there's a roundRadius set, we will reduce the initial Content Area to make sure the computed area won't intersect with the shape contour. The formula is simple: we shrink the incoming size by the amount of the roundRadius
  369. protected _getInitialContentAreaToRef(primSize: Size, initialContentPosition: Vector2, initialContentArea: Size) {
  370. // Fall back to default implementation if there's no round Radius
  371. if (this._notRounded) {
  372. super._getInitialContentAreaToRef(primSize, initialContentPosition, initialContentArea);
  373. } else {
  374. let rr = Math.round((this.roundRadius - (this.roundRadius/Math.sqrt(2))) * 1.3);
  375. initialContentPosition.x = initialContentPosition.y = rr;
  376. initialContentArea.width = Math.max(0, primSize.width - (rr * 2));
  377. initialContentArea.height = Math.max(0, primSize.height - (rr * 2));
  378. }
  379. }
  380. protected createInstanceDataParts(): InstanceDataBase[] {
  381. var res = new Array<InstanceDataBase>();
  382. if (this.border) {
  383. res.push(new Rectangle2DInstanceData(Shape2D.SHAPE2D_BORDERPARTID));
  384. }
  385. if (this.fill) {
  386. res.push(new Rectangle2DInstanceData(Shape2D.SHAPE2D_FILLPARTID));
  387. }
  388. return res;
  389. }
  390. protected refreshInstanceDataPart(part: InstanceDataBase): boolean {
  391. if (!super.refreshInstanceDataPart(part)) {
  392. return false;
  393. }
  394. if (part.id === Shape2D.SHAPE2D_BORDERPARTID) {
  395. let d = <Rectangle2DInstanceData>part;
  396. let size = this.actualSize;
  397. d.properties = new Vector3(size.width, size.height, this.roundRadius || 0);
  398. }
  399. else if (part.id === Shape2D.SHAPE2D_FILLPARTID) {
  400. let d = <Rectangle2DInstanceData>part;
  401. let size = this.actualSize;
  402. d.properties = new Vector3(size.width, size.height, this.roundRadius || 0);
  403. }
  404. return true;
  405. }
  406. private _notRounded: boolean;
  407. private _roundRadius: number;
  408. }
  409. }