babylon.ellipse2d.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. module BABYLON {
  2. export class Ellipse2DRenderCache extends ModelRenderCache {
  3. effectsReady: boolean = false;
  4. fillVB: WebGLBuffer = null;
  5. fillIB: WebGLBuffer = null;
  6. fillIndicesCount: number = 0;
  7. instancingFillAttributes: InstancingAttributeInfo[] = null;
  8. effectFillInstanced: Effect = null;
  9. effectFill: Effect = null;
  10. borderVB: WebGLBuffer = null;
  11. borderIB: WebGLBuffer = null;
  12. borderIndicesCount: number = 0;
  13. instancingBorderAttributes: InstancingAttributeInfo[] = null;
  14. effectBorderInstanced: Effect = null;
  15. effectBorder: 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. let 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(this.effectBorder, 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 Ellipse2DInstanceData extends Shape2DInstanceData {
  127. constructor(partId: number) {
  128. super(partId, 1);
  129. }
  130. @instanceData()
  131. get properties(): Vector3 {
  132. return null;
  133. }
  134. }
  135. @className("Ellipse2D")
  136. export class Ellipse2D extends Shape2D {
  137. public static sizeProperty: Prim2DPropInfo;
  138. public static subdivisionsProperty: Prim2DPropInfo;
  139. public get actualSize(): Size {
  140. return this.size;
  141. }
  142. @instanceLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 1, pi => Ellipse2D.sizeProperty = pi, false, true)
  143. public get size(): Size {
  144. return this._size;
  145. }
  146. public set size(value: Size) {
  147. this._size = value;
  148. }
  149. @modelLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 2, pi => Ellipse2D.subdivisionsProperty = pi)
  150. public get subdivisions(): number {
  151. return this._subdivisions;
  152. }
  153. public set subdivisions(value: number) {
  154. this._subdivisions = value;
  155. }
  156. protected levelIntersect(intersectInfo: IntersectInfo2D): boolean {
  157. let x = intersectInfo._localPickPosition.x;
  158. let y = intersectInfo._localPickPosition.y;
  159. let w = this.size.width/2;
  160. let h = this.size.height/2;
  161. return ((x * x) / (w * w) + (y * y) / (h * h)) <= 1;
  162. }
  163. protected updateLevelBoundingInfo() {
  164. BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo, this.origin);
  165. }
  166. protected setupEllipse2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, size: Size, subdivisions: number, fill: IBrush2D, border: IBrush2D, borderThickness: number, isVisible: boolean, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, vAlignment: number, hAlignment: number) {
  167. this.setupShape2D(owner, parent, id, position, origin, isVisible, fill, border, borderThickness, marginTop, marginLeft, marginRight, marginBottom, hAlignment, vAlignment);
  168. this.size = size;
  169. this.subdivisions = subdivisions;
  170. }
  171. /**
  172. * Create an Ellipse 2D Shape primitive
  173. * @param parent the parent primitive, must be a valid primitive (or the Canvas)
  174. * options:
  175. * - id: a text identifier, for information purpose
  176. * - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0]
  177. * - origin: define the normalized origin point location, default [0.5;0.5]
  178. * - size: the size of the group. Alternatively the width and height properties can be set. Default will be [10;10].
  179. * - subdivision: the number of subdivision to create the ellipse perimeter, default is 64.
  180. * - 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.
  181. * - 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.
  182. * - borderThickness: the thickness of the drawn border, default is 1.
  183. * - isVisible: true if the primitive must be visible, false for hidden. Default is true.
  184. * - 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.
  185. * - hAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
  186. * - vAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
  187. */
  188. public static Create(parent: Prim2DBase, options: { id?: string, position?: Vector2, x?: number, y?: number, origin?: Vector2, size?: Size, width?: number, height?: number, subdivisions?: number, fill?: IBrush2D, border?: IBrush2D, borderThickness?: number, isVisible?: boolean, marginTop?: number, marginLeft?: number, marginRight?: number, marginBottom?: number, vAlignment?: number, hAlignment?: number}): Ellipse2D {
  189. Prim2DBase.CheckParent(parent);
  190. let ellipse = new Ellipse2D();
  191. if (!options) {
  192. ellipse.setupEllipse2D(parent.owner, parent, null, Vector2.Zero(), null, new Size(10, 10), 64, Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF"), null, 1, true, null, null, null, null, null, null);
  193. } else {
  194. let fill: IBrush2D;
  195. if (options.fill === undefined) {
  196. fill = Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
  197. } else {
  198. fill = options.fill;
  199. }
  200. let pos = options.position || new Vector2(options.x || 0, options.y || 0);
  201. let size = options.size || (new Size(options.width || 10, options.height || 10));
  202. ellipse.setupEllipse2D(parent.owner, parent, options.id || null, pos, options.origin || null, size, options.subdivisions || 64, fill, options.border || null, options.borderThickness || 1, options.isVisible || true, options.marginTop || null, options.marginLeft || null, options.marginRight || null, options.marginBottom || null, options.vAlignment || null, options.hAlignment || null);
  203. }
  204. return ellipse;
  205. }
  206. protected createModelRenderCache(modelKey: string): ModelRenderCache {
  207. let renderCache = new Ellipse2DRenderCache(this.owner.engine, modelKey);
  208. return renderCache;
  209. }
  210. protected setupModelRenderCache(modelRenderCache: ModelRenderCache) {
  211. let renderCache = <Ellipse2DRenderCache>modelRenderCache;
  212. let engine = this.owner.engine;
  213. // Need to create WebGL resources for fill part?
  214. if (this.fill) {
  215. let vbSize = this.subdivisions + 1;
  216. let vb = new Float32Array(vbSize);
  217. for (let i = 0; i < vbSize; i++) {
  218. vb[i] = i;
  219. }
  220. renderCache.fillVB = engine.createVertexBuffer(vb);
  221. let triCount = vbSize - 1;
  222. let ib = new Float32Array(triCount * 3);
  223. for (let i = 0; i < triCount; i++) {
  224. ib[i * 3 + 0] = 0;
  225. ib[i * 3 + 2] = i + 1;
  226. ib[i * 3 + 1] = i + 2;
  227. }
  228. ib[triCount * 3 - 2] = 1;
  229. renderCache.fillIB = engine.createIndexBuffer(ib);
  230. renderCache.fillIndicesCount = triCount * 3;
  231. // 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
  232. let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["index"], true);
  233. if (ei) {
  234. renderCache.effectFillInstanced = engine.createEffect({ vertex: "ellipse2d", fragment: "ellipse2d" }, ei.attributes, ei.uniforms, [], ei.defines, null);
  235. }
  236. // Get the non instanced version
  237. ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["index"], false);
  238. renderCache.effectFill = engine.createEffect({ vertex: "ellipse2d", fragment: "ellipse2d" }, ei.attributes, ei.uniforms, [], ei.defines, null);
  239. }
  240. // Need to create WebGL resource for border part?
  241. if (this.border) {
  242. let vbSize = this.subdivisions * 2;
  243. let vb = new Float32Array(vbSize);
  244. for (let i = 0; i < vbSize; i++) {
  245. vb[i] = i;
  246. }
  247. renderCache.borderVB = engine.createVertexBuffer(vb);
  248. let triCount = vbSize;
  249. let rs = triCount / 2;
  250. let ib = new Float32Array(triCount * 3);
  251. for (let i = 0; i < rs; i++) {
  252. let r0 = i;
  253. let r1 = (i + 1) % rs;
  254. ib[i * 6 + 0] = rs + r1;
  255. ib[i * 6 + 1] = rs + r0;
  256. ib[i * 6 + 2] = r0;
  257. ib[i * 6 + 3] = r1;
  258. ib[i * 6 + 4] = rs + r1;
  259. ib[i * 6 + 5] = r0;
  260. }
  261. renderCache.borderIB = engine.createIndexBuffer(ib);
  262. renderCache.borderIndicesCount = (triCount* 3);
  263. // 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
  264. let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_BORDERPARTID, ["index"], true);
  265. if (ei) {
  266. renderCache.effectBorderInstanced = engine.createEffect("ellipse2d", ei.attributes, ei.uniforms, [], ei.defines, null);
  267. }
  268. // Get the non instanced version
  269. ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_BORDERPARTID, ["index"], false);
  270. renderCache.effectBorder = engine.createEffect("ellipse2d", ei.attributes, ei.uniforms, [], ei.defines, null);
  271. }
  272. return renderCache;
  273. }
  274. protected createInstanceDataParts(): InstanceDataBase[] {
  275. var res = new Array<InstanceDataBase>();
  276. if (this.border) {
  277. res.push(new Ellipse2DInstanceData(Shape2D.SHAPE2D_BORDERPARTID));
  278. }
  279. if (this.fill) {
  280. res.push(new Ellipse2DInstanceData(Shape2D.SHAPE2D_FILLPARTID));
  281. }
  282. return res;
  283. }
  284. protected refreshInstanceDataPart(part: InstanceDataBase): boolean {
  285. if (!super.refreshInstanceDataPart(part)) {
  286. return false;
  287. }
  288. if (part.id === Shape2D.SHAPE2D_BORDERPARTID) {
  289. let d = <Ellipse2DInstanceData>part;
  290. let size = this.size;
  291. d.properties = new Vector3(size.width, size.height, this.subdivisions);
  292. }
  293. else if (part.id === Shape2D.SHAPE2D_FILLPARTID) {
  294. let d = <Ellipse2DInstanceData>part;
  295. let size = this.size;
  296. d.properties = new Vector3(size.width, size.height, this.subdivisions);
  297. }
  298. return true;
  299. }
  300. private _size: Size;
  301. private _subdivisions: number;
  302. }
  303. }