babylon.rectangle2d.ts 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  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. let canvas = instanceInfo.owner.owner;
  29. var engine = canvas.engine;
  30. let depthFunction = 0;
  31. if (this.effectFill && this.effectBorder) {
  32. depthFunction = engine.getDepthFunction();
  33. engine.setDepthFunctionToLessOrEqual();
  34. }
  35. var curAlphaMode = engine.getAlphaMode();
  36. if (this.effectFill) {
  37. let partIndex = instanceInfo.partIndexFromId.get(Shape2D.SHAPE2D_FILLPARTID.toString());
  38. let pid = context.groupInfoPartData[partIndex];
  39. if (context.renderMode !== Render2DContext.RenderModeOpaque) {
  40. engine.setAlphaMode(Engine.ALPHA_COMBINE, true);
  41. }
  42. let effect = context.useInstancing ? this.effectFillInstanced : this.effectFill;
  43. engine.enableEffect(effect);
  44. engine.bindBuffersDirectly(this.fillVB, this.fillIB, [1], 4, effect);
  45. if (context.useInstancing) {
  46. if (!this.instancingFillAttributes) {
  47. this.instancingFillAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_FILLPARTID, effect);
  48. }
  49. let glBuffer = context.instancedBuffers ? context.instancedBuffers[partIndex] : pid._partBuffer;
  50. let count = context.instancedBuffers ? context.instancesCount : pid._partData.usedElementCount;
  51. canvas._addDrawCallCount(1, context.renderMode);
  52. engine.updateAndBindInstancesBuffer(glBuffer, null, this.instancingFillAttributes);
  53. engine.draw(true, 0, this.fillIndicesCount, count);
  54. engine.unbindInstanceAttributes();
  55. } else {
  56. canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode);
  57. for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
  58. this.setupUniforms(effect, partIndex, pid._partData, i);
  59. engine.draw(true, 0, this.fillIndicesCount);
  60. }
  61. }
  62. }
  63. if (this.effectBorder) {
  64. let partIndex = instanceInfo.partIndexFromId.get(Shape2D.SHAPE2D_BORDERPARTID.toString());
  65. let pid = context.groupInfoPartData[partIndex];
  66. if (context.renderMode !== Render2DContext.RenderModeOpaque) {
  67. engine.setAlphaMode(Engine.ALPHA_COMBINE, true);
  68. }
  69. let effect = context.useInstancing ? this.effectBorderInstanced : this.effectBorder;
  70. engine.enableEffect(effect);
  71. engine.bindBuffersDirectly(this.borderVB, this.borderIB, [1], 4, effect);
  72. if (context.useInstancing) {
  73. if (!this.instancingBorderAttributes) {
  74. this.instancingBorderAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_BORDERPARTID, effect);
  75. }
  76. let glBuffer = context.instancedBuffers ? context.instancedBuffers[partIndex] : pid._partBuffer;
  77. let count = context.instancedBuffers ? context.instancesCount : pid._partData.usedElementCount;
  78. canvas._addDrawCallCount(1, context.renderMode);
  79. engine.updateAndBindInstancesBuffer(glBuffer, null, this.instancingBorderAttributes);
  80. engine.draw(true, 0, this.borderIndicesCount, count);
  81. engine.unbindInstanceAttributes();
  82. } else {
  83. canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode);
  84. for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) {
  85. this.setupUniforms(effect, partIndex, pid._partData, i);
  86. engine.draw(true, 0, this.borderIndicesCount);
  87. }
  88. }
  89. }
  90. engine.setAlphaMode(curAlphaMode, true);
  91. if (this.effectFill && this.effectBorder) {
  92. engine.setDepthFunction(depthFunction);
  93. }
  94. return true;
  95. }
  96. public dispose(): boolean {
  97. if (!super.dispose()) {
  98. return false;
  99. }
  100. if (this.fillVB) {
  101. this._engine._releaseBuffer(this.fillVB);
  102. this.fillVB = null;
  103. }
  104. if (this.fillIB) {
  105. this._engine._releaseBuffer(this.fillIB);
  106. this.fillIB = null;
  107. }
  108. this.effectFill = null;
  109. this.effectFillInstanced = null;
  110. this.effectBorder = null;
  111. this.effectBorderInstanced = null;
  112. if (this.borderVB) {
  113. this._engine._releaseBuffer(this.borderVB);
  114. this.borderVB = null;
  115. }
  116. if (this.borderIB) {
  117. this._engine._releaseBuffer(this.borderIB);
  118. this.borderIB = null;
  119. }
  120. return true;
  121. }
  122. }
  123. export class Rectangle2DInstanceData extends Shape2DInstanceData {
  124. constructor(partId: number) {
  125. super(partId, 1);
  126. }
  127. @instanceData()
  128. get properties(): Vector3 {
  129. return null;
  130. }
  131. set properties(value: Vector3) {
  132. }
  133. }
  134. @className("Rectangle2D", "BABYLON")
  135. /**
  136. * The Rectangle Primitive type
  137. */
  138. export class Rectangle2D extends Shape2D {
  139. public static actualSizeProperty: Prim2DPropInfo;
  140. public static notRoundedProperty: Prim2DPropInfo;
  141. public static roundRadiusProperty: Prim2DPropInfo;
  142. @modelLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 2, pi => Rectangle2D.notRoundedProperty = pi)
  143. /**
  144. * Get if the rectangle is notRound (returns true) or rounded (returns false).
  145. * Don't use the setter, it's for internal purpose only
  146. */
  147. public get notRounded(): boolean {
  148. return this._notRounded;
  149. }
  150. public set notRounded(value: boolean) {
  151. this._notRounded = value;
  152. }
  153. @instanceLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 3, pi => Rectangle2D.roundRadiusProperty = pi)
  154. /**
  155. * Get/set the round Radius, a value of 0 for a sharp edges rectangle, otherwise the value will be used as the diameter of the round to apply on corder. The Rectangle2D.notRounded property will be updated accordingly.
  156. */
  157. public get roundRadius(): number {
  158. return this._roundRadius;
  159. }
  160. public set roundRadius(value: number) {
  161. this._roundRadius = value;
  162. this.notRounded = value === 0;
  163. this._positioningDirty();
  164. }
  165. private static _i0 = Vector2.Zero();
  166. private static _i1 = Vector2.Zero();
  167. private static _i2 = Vector2.Zero();
  168. protected levelIntersect(intersectInfo: IntersectInfo2D): boolean {
  169. // If we got there it mean the boundingInfo intersection succeed, if the rectangle has not roundRadius, it means it succeed!
  170. if (this.notRounded) {
  171. return true;
  172. }
  173. // 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.
  174. // 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)
  175. // 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.
  176. // First let remove the origin out the equation, to have the rectangle with an origin at bottom/left
  177. let size = this.size;
  178. Rectangle2D._i0.x = intersectInfo._localPickPosition.x;
  179. Rectangle2D._i0.y = intersectInfo._localPickPosition.y;
  180. let rr = this.roundRadius;
  181. let rrs = rr * rr;
  182. // Check if the point is in the bottom/left quarter area
  183. Rectangle2D._i1.x = rr;
  184. Rectangle2D._i1.y = rr;
  185. if (Rectangle2D._i0.x <= Rectangle2D._i1.x && Rectangle2D._i0.y <= Rectangle2D._i1.y) {
  186. // Compute the intersection point in the quarter local space
  187. Rectangle2D._i2.x = Rectangle2D._i0.x - Rectangle2D._i1.x;
  188. Rectangle2D._i2.y = Rectangle2D._i0.y - Rectangle2D._i1.y;
  189. // It's a hit if the squared distance is less/equal to the squared radius of the round circle
  190. return Rectangle2D._i2.lengthSquared() <= rrs;
  191. }
  192. // Check if the point is in the top/left quarter area
  193. Rectangle2D._i1.x = rr;
  194. Rectangle2D._i1.y = size.height - rr;
  195. if (Rectangle2D._i0.x <= Rectangle2D._i1.x && Rectangle2D._i0.y >= Rectangle2D._i1.y) {
  196. // Compute the intersection point in the quarter local space
  197. Rectangle2D._i2.x = Rectangle2D._i0.x - Rectangle2D._i1.x;
  198. Rectangle2D._i2.y = Rectangle2D._i0.y - Rectangle2D._i1.y;
  199. // It's a hit if the squared distance is less/equal to the squared radius of the round circle
  200. return Rectangle2D._i2.lengthSquared() <= rrs;
  201. }
  202. // Check if the point is in the top/right quarter area
  203. Rectangle2D._i1.x = size.width - rr;
  204. Rectangle2D._i1.y = size.height - rr;
  205. if (Rectangle2D._i0.x >= Rectangle2D._i1.x && Rectangle2D._i0.y >= Rectangle2D._i1.y) {
  206. // Compute the intersection point in the quarter local space
  207. Rectangle2D._i2.x = Rectangle2D._i0.x - Rectangle2D._i1.x;
  208. Rectangle2D._i2.y = Rectangle2D._i0.y - Rectangle2D._i1.y;
  209. // It's a hit if the squared distance is less/equal to the squared radius of the round circle
  210. return Rectangle2D._i2.lengthSquared() <= rrs;
  211. }
  212. // Check if the point is in the bottom/right quarter area
  213. Rectangle2D._i1.x = size.width - rr;
  214. Rectangle2D._i1.y = rr;
  215. if (Rectangle2D._i0.x >= Rectangle2D._i1.x && Rectangle2D._i0.y <= Rectangle2D._i1.y) {
  216. // Compute the intersection point in the quarter local space
  217. Rectangle2D._i2.x = Rectangle2D._i0.x - Rectangle2D._i1.x;
  218. Rectangle2D._i2.y = Rectangle2D._i0.y - Rectangle2D._i1.y;
  219. // It's a hit if the squared distance is less/equal to the squared radius of the round circle
  220. return Rectangle2D._i2.lengthSquared() <= rrs;
  221. }
  222. // At any other locations the point is guarantied to be inside
  223. return true;
  224. }
  225. protected updateLevelBoundingInfo(): boolean {
  226. BoundingInfo2D.CreateFromSizeToRef(this.actualSize, this._levelBoundingInfo);
  227. return true;
  228. }
  229. /**
  230. * Create an Rectangle 2D Shape primitive. May be a sharp rectangle (with sharp corners), or a rounded one.
  231. * @param settings a combination of settings, possible ones are
  232. * - 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)
  233. * - children: an array of direct children
  234. * - id a text identifier, for information purpose
  235. * - position: the X & Y positions relative to its parent. Alternatively the x and y settings can be set. Default is [0;0]
  236. * - rotation: the initial rotation (in radian) of the primitive. default is 0
  237. * - scale: the initial scale of the primitive. default is 1. You can alternatively use scaleX &| scaleY to apply non uniform scale
  238. * - dontInheritParentScale: if set the parent's scale won't be taken into consideration to compute the actualScale property
  239. * - alignToPixel: if true the primitive will be aligned to the target rendering device's pixel
  240. * - opacity: set the overall opacity of the primitive, 1 to be opaque (default), less than 1 to be transparent.
  241. * - zOrder: override the zOrder with the specified value
  242. * - origin: define the normalized origin point location, default [0.5;0.5]
  243. * - size: the size of the group. Alternatively the width and height settings can be set. Default will be [10;10].
  244. * - roundRadius: if the rectangle has rounded corner, set their radius, default is 0 (to get a sharp edges rectangle).
  245. * - fill: the brush used to draw the fill content of the rectangle, you can set null to draw nothing (but you will have to set a border brush), default is a SolidColorBrush of plain white. can also be a string value (see Canvas2D.GetBrushFromString)
  246. * - border: the brush used to draw the border of the rectangle, you can set null to draw nothing (but you will have to set a fill brush), default is null. can also be a string value (see Canvas2D.GetBrushFromString)
  247. * - borderThickness: the thickness of the drawn border, default is 1.
  248. * - isVisible: true if the primitive must be visible, false for hidden. Default is true.
  249. * - 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.
  250. * - 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.
  251. * - 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!
  252. * - levelCollision: this primitive is an actor of the Collision Manager and only this level will be used for collision (i.e. not the children). Use deepCollision if you want collision detection on the primitives and its children.
  253. * - deepCollision: this primitive is an actor of the Collision Manager, this level AND ALSO its children will be used for collision (note: you don't need to set the children as level/deepCollision).
  254. * - layoutData: a instance of a class implementing the ILayoutData interface that contain data to pass to the primitive parent's layout engine
  255. * - marginTop: top margin, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  256. * - marginLeft: left margin, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  257. * - marginRight: right margin, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  258. * - marginBottom: bottom margin, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  259. * - margin: top, left, right and bottom margin formatted as a single string (see PrimitiveThickness.fromString)
  260. * - marginHAlignment: one value of the PrimitiveAlignment type's static properties
  261. * - marginVAlignment: one value of the PrimitiveAlignment type's static properties
  262. * - marginAlignment: a string defining the alignment, see PrimitiveAlignment.fromString
  263. * - paddingTop: top padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  264. * - paddingLeft: left padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  265. * - paddingRight: right padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  266. * - paddingBottom: bottom padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
  267. * - padding: top, left, right and bottom padding formatted as a single string (see PrimitiveThickness.fromString)
  268. */
  269. constructor(settings ?: {
  270. parent ?: Prim2DBase,
  271. children ?: Array<Prim2DBase>,
  272. id ?: string,
  273. position ?: Vector2,
  274. x ?: number,
  275. y ?: number,
  276. rotation ?: number,
  277. scale ?: number,
  278. scaleX ?: number,
  279. scaleY ?: number,
  280. dontInheritParentScale?: boolean,
  281. alignToPixel ?: boolean,
  282. opacity ?: number,
  283. zOrder ?: number,
  284. origin ?: Vector2,
  285. size ?: Size,
  286. width ?: number,
  287. height ?: number,
  288. roundRadius ?: number,
  289. fill ?: IBrush2D | string,
  290. border ?: IBrush2D | string,
  291. borderThickness ?: number,
  292. isVisible ?: boolean,
  293. isPickable ?: boolean,
  294. isContainer ?: boolean,
  295. childrenFlatZOrder ?: boolean,
  296. levelCollision ?: boolean,
  297. deepCollision ?: boolean,
  298. layoutData ?: ILayoutData,
  299. marginTop ?: number | string,
  300. marginLeft ?: number | string,
  301. marginRight ?: number | string,
  302. marginBottom ?: number | string,
  303. margin ?: number | string,
  304. marginHAlignment ?: number,
  305. marginVAlignment ?: number,
  306. marginAlignment ?: string,
  307. paddingTop ?: number | string,
  308. paddingLeft ?: number | string,
  309. paddingRight ?: number | string,
  310. paddingBottom ?: number | string,
  311. padding ?: number | string,
  312. }) {
  313. // Avoid checking every time if the object exists
  314. if (settings == null) {
  315. settings = {};
  316. }
  317. super(settings);
  318. if (settings.size != null) {
  319. this.size = settings.size;
  320. }
  321. else if (settings.width || settings.height) {
  322. let size = new Size(settings.width, settings.height);
  323. this.size = size;
  324. }
  325. //let size = settings.size || (new Size((settings.width === null) ? null : (settings.width || 10), (settings.height === null) ? null : (settings.height || 10)));
  326. let roundRadius = (settings.roundRadius == null) ? 0 : settings.roundRadius;
  327. let borderThickness = (settings.borderThickness == null) ? 1 : settings.borderThickness;
  328. //this.size = size;
  329. this.roundRadius = roundRadius;
  330. this.borderThickness = borderThickness;
  331. }
  332. public static roundSubdivisions = 16;
  333. protected createModelRenderCache(modelKey: string): ModelRenderCache {
  334. let renderCache = new Rectangle2DRenderCache(this.owner.engine, modelKey);
  335. return renderCache;
  336. }
  337. protected updateTriArray() {
  338. // Not Rounded = sharp edge rect, the default implementation is the right one!
  339. if (this.notRounded) {
  340. super.updateTriArray();
  341. return;
  342. }
  343. // Rounded Corner? It's more complicated! :)
  344. let subDiv = Rectangle2D.roundSubdivisions * 4;
  345. if (this._primTriArray == null) {
  346. this._primTriArray = new Tri2DArray(subDiv);
  347. } else {
  348. this._primTriArray.clear(subDiv);
  349. }
  350. let size = this.actualSize;
  351. let w = size.width;
  352. let h = size.height;
  353. let r = this.roundRadius;
  354. let rsub0 = subDiv * 0.25;
  355. let rsub1 = subDiv * 0.50;
  356. let rsub2 = subDiv * 0.75;
  357. let center = new Vector2(0.5 * size.width, 0.5 * size.height);
  358. let twopi = Math.PI * 2;
  359. let nru = r / w;
  360. let nrv = r / h;
  361. let computePos = (index: number, p: Vector2) => {
  362. // right/bottom
  363. if (index < rsub0) {
  364. p.x = 1.0 - nru;
  365. p.y = nrv;
  366. }
  367. // left/bottom
  368. else if (index < rsub1) {
  369. p.x = nru;
  370. p.y = nrv;
  371. }
  372. // left/top
  373. else if (index < rsub2) {
  374. p.x = nru;
  375. p.y = 1.0 - nrv;
  376. }
  377. // right/top
  378. else {
  379. p.x = 1.0 - nru;
  380. p.y = 1.0 - nrv;
  381. }
  382. let angle = twopi - (index * twopi / (subDiv - 0.5));
  383. p.x += Math.cos(angle) * nru;
  384. p.y += Math.sin(angle) * nrv;
  385. p.x *= w;
  386. p.y *= h;
  387. }
  388. console.log("Genetre TriList for " + this.id);
  389. let first = Vector2.Zero();
  390. let cur = Vector2.Zero();
  391. computePos(0, first);
  392. let prev = first.clone();
  393. for (let index = 1; index < subDiv; index++) {
  394. computePos(index, cur);
  395. this._primTriArray.storeTriangle(index - 1, center, prev, cur);
  396. console.log(`${index-1}, ${center}, ${prev}, ${cur}`);
  397. prev.copyFrom(cur);
  398. }
  399. this._primTriArray.storeTriangle(subDiv-1, center, first, prev);
  400. console.log(`${subDiv-1}, ${center}, ${prev}, ${first}`);
  401. }
  402. protected setupModelRenderCache(modelRenderCache: ModelRenderCache) {
  403. let renderCache = <Rectangle2DRenderCache>modelRenderCache;
  404. let engine = this.owner.engine;
  405. // Need to create WebGL resources for fill part?
  406. if (this.fill) {
  407. let vbSize = ((this.notRounded ? 1 : Rectangle2D.roundSubdivisions) * 4) + 1;
  408. let vb = new Float32Array(vbSize);
  409. for (let i = 0; i < vbSize; i++) {
  410. vb[i] = i;
  411. }
  412. renderCache.fillVB = engine.createVertexBuffer(vb);
  413. let triCount = vbSize - 1;
  414. let ib = new Float32Array(triCount * 3);
  415. for (let i = 0; i < triCount; i++) {
  416. ib[i * 3 + 0] = 0;
  417. ib[i * 3 + 2] = i + 1;
  418. ib[i * 3 + 1] = i + 2;
  419. }
  420. ib[triCount * 3 - 2] = 1;
  421. renderCache.fillIB = engine.createIndexBuffer(ib);
  422. renderCache.fillIndicesCount = triCount * 3;
  423. // 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
  424. let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["index"], null, true);
  425. if (ei) {
  426. renderCache.effectFillInstanced = engine.createEffect("rect2d", ei.attributes, ei.uniforms, [], ei.defines, null);
  427. }
  428. // Get the non instanced version
  429. ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["index"], null, false);
  430. renderCache.effectFill = engine.createEffect("rect2d", ei.attributes, ei.uniforms, [], ei.defines, null);
  431. }
  432. // Need to create WebGL resource for border part?
  433. if (this.border) {
  434. let vbSize = (this.notRounded ? 1 : Rectangle2D.roundSubdivisions) * 4 * 2;
  435. let vb = new Float32Array(vbSize);
  436. for (let i = 0; i < vbSize; i++) {
  437. vb[i] = i;
  438. }
  439. renderCache.borderVB = engine.createVertexBuffer(vb);
  440. let triCount = vbSize;
  441. let rs = triCount / 2;
  442. let ib = new Float32Array(triCount * 3);
  443. for (let i = 0; i < rs; i++) {
  444. let r0 = i;
  445. let r1 = (i + 1) % rs;
  446. ib[i * 6 + 0] = rs + r1;
  447. ib[i * 6 + 1] = rs + r0;
  448. ib[i * 6 + 2] = r0;
  449. ib[i * 6 + 3] = r1;
  450. ib[i * 6 + 4] = rs + r1;
  451. ib[i * 6 + 5] = r0;
  452. }
  453. renderCache.borderIB = engine.createIndexBuffer(ib);
  454. renderCache.borderIndicesCount = triCount * 3;
  455. // 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
  456. let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_BORDERPARTID, ["index"], null, true);
  457. if (ei) {
  458. renderCache.effectBorderInstanced = engine.createEffect("rect2d", ei.attributes, ei.uniforms, [], ei.defines, null);
  459. }
  460. // Get the non instanced version
  461. ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_BORDERPARTID, ["index"], null, false);
  462. renderCache.effectBorder = engine.createEffect("rect2d", ei.attributes, ei.uniforms, [], ei.defines, null);
  463. }
  464. return renderCache;
  465. }
  466. // 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
  467. protected _getInitialContentAreaToRef(primSize: Size, initialContentPosition: Vector4, initialContentArea: Size) {
  468. // Fall back to default implementation if there's no round Radius
  469. if (this._notRounded) {
  470. super._getInitialContentAreaToRef(primSize, initialContentPosition, initialContentArea);
  471. } else {
  472. let rr = Math.round((this.roundRadius - (this.roundRadius/Math.sqrt(2))) * 1.3);
  473. initialContentPosition.x = initialContentPosition.y = rr;
  474. initialContentArea.width = Math.max(0, primSize.width - (rr * 2));
  475. initialContentArea.height = Math.max(0, primSize.height - (rr * 2));
  476. initialContentPosition.z = primSize.width - (initialContentPosition.x + initialContentArea.width);
  477. initialContentPosition.w = primSize.height - (initialContentPosition.y + initialContentArea.height);
  478. }
  479. }
  480. protected _getActualSizeFromContentToRef(primSize: Size, paddingOffset: Vector4, newPrimSize: Size) {
  481. // Fall back to default implementation if there's no round Radius
  482. if (this._notRounded) {
  483. super._getActualSizeFromContentToRef(primSize, paddingOffset, newPrimSize);
  484. } else {
  485. let rr = Math.round((this.roundRadius - (this.roundRadius / Math.sqrt(2))) * 1.3);
  486. newPrimSize.copyFrom(primSize);
  487. newPrimSize.width += rr * 2;
  488. newPrimSize.height += rr * 2;
  489. paddingOffset.x += rr;
  490. paddingOffset.y += rr;
  491. paddingOffset.z += rr;
  492. paddingOffset.w += rr;
  493. }
  494. }
  495. protected createInstanceDataParts(): InstanceDataBase[] {
  496. var res = new Array<InstanceDataBase>();
  497. if (this.border) {
  498. res.push(new Rectangle2DInstanceData(Shape2D.SHAPE2D_BORDERPARTID));
  499. }
  500. if (this.fill) {
  501. res.push(new Rectangle2DInstanceData(Shape2D.SHAPE2D_FILLPARTID));
  502. }
  503. return res;
  504. }
  505. private static _riv0 = new Vector2(0,0);
  506. protected refreshInstanceDataPart(part: InstanceDataBase): boolean {
  507. if (!super.refreshInstanceDataPart(part)) {
  508. return false;
  509. }
  510. //let s = Rectangle2D._riv0;
  511. //this.getActualGlobalScaleToRef(s);
  512. if (part.id === Shape2D.SHAPE2D_BORDERPARTID) {
  513. let d = <Rectangle2DInstanceData>part;
  514. let size = this.actualSize;
  515. d.properties = new Vector3(size.width/* * s.x*/, size.height/* * s.y*/, this.roundRadius || 0);
  516. }
  517. else if (part.id === Shape2D.SHAPE2D_FILLPARTID) {
  518. let d = <Rectangle2DInstanceData>part;
  519. let size = this.actualSize;
  520. d.properties = new Vector3(size.width/* * s.x*/, size.height/* * s.y*/, this.roundRadius || 0);
  521. }
  522. return true;
  523. }
  524. private _notRounded: boolean;
  525. private _roundRadius: number;
  526. }
  527. }