module BABYLON { export class Ellipse2DRenderCache extends ModelRenderCache { effectsReady: boolean = false; fillVB: WebGLBuffer = null; fillIB: WebGLBuffer = null; fillIndicesCount: number = 0; instancingFillAttributes: InstancingAttributeInfo[] = null; effectFillInstanced: Effect = null; effectFill: Effect = null; borderVB: WebGLBuffer = null; borderIB: WebGLBuffer = null; borderIndicesCount: number = 0; instancingBorderAttributes: InstancingAttributeInfo[] = null; effectBorderInstanced: Effect = null; effectBorder: Effect = null; constructor(engine: Engine, modelKey: string) { super(engine, modelKey); } render(instanceInfo: GroupInstanceInfo, context: Render2DContext): boolean { // Do nothing if the shader is still loading/preparing if (!this.effectsReady) { if ((this.effectFill && (!this.effectFill.isReady() || (this.effectFillInstanced && !this.effectFillInstanced.isReady()))) || (this.effectBorder && (!this.effectBorder.isReady() || (this.effectBorderInstanced && !this.effectBorderInstanced.isReady())))) { return false; } this.effectsReady = true; } let canvas = instanceInfo.owner.owner; var engine = canvas.engine; let depthFunction = 0; if (this.effectFill && this.effectBorder) { depthFunction = engine.getDepthFunction(); engine.setDepthFunctionToLessOrEqual(); } let curAlphaMode = engine.getAlphaMode(); if (this.effectFill) { let partIndex = instanceInfo.partIndexFromId.get(Shape2D.SHAPE2D_FILLPARTID.toString()); let pid = context.groupInfoPartData[partIndex]; if (context.renderMode !== Render2DContext.RenderModeOpaque) { engine.setAlphaMode(Engine.ALPHA_COMBINE, true); } let effect = context.useInstancing ? this.effectFillInstanced : this.effectFill; engine.enableEffect(effect); engine.bindBuffersDirectly(this.fillVB, this.fillIB, [1], 4, effect); if (context.useInstancing) { if (!this.instancingFillAttributes) { this.instancingFillAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_FILLPARTID, effect); } let glBuffer = context.instancedBuffers ? context.instancedBuffers[partIndex] : pid._partBuffer; let count = context.instancedBuffers ? context.instancesCount : pid._partData.usedElementCount; canvas._addDrawCallCount(1, context.renderMode); engine.updateAndBindInstancesBuffer(glBuffer, null, this.instancingFillAttributes); engine.draw(true, 0, this.fillIndicesCount, count); engine.unbindInstanceAttributes(); } else { canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode); for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) { this.setupUniforms(effect, partIndex, pid._partData, i); engine.draw(true, 0, this.fillIndicesCount); } } } if (this.effectBorder) { let partIndex = instanceInfo.partIndexFromId.get(Shape2D.SHAPE2D_BORDERPARTID.toString()); let pid = context.groupInfoPartData[partIndex]; if (context.renderMode !== Render2DContext.RenderModeOpaque) { engine.setAlphaMode(Engine.ALPHA_COMBINE, true); } let effect = context.useInstancing ? this.effectBorderInstanced : this.effectBorder; engine.enableEffect(effect); engine.bindBuffersDirectly(this.borderVB, this.borderIB, [1], 4, effect); if (context.useInstancing) { if (!this.instancingBorderAttributes) { this.instancingBorderAttributes = this.loadInstancingAttributes(Shape2D.SHAPE2D_BORDERPARTID, effect); } let glBuffer = context.instancedBuffers ? context.instancedBuffers[partIndex] : pid._partBuffer; let count = context.instancedBuffers ? context.instancesCount : pid._partData.usedElementCount; canvas._addDrawCallCount(1, context.renderMode); engine.updateAndBindInstancesBuffer(glBuffer, null, this.instancingBorderAttributes); engine.draw(true, 0, this.borderIndicesCount, count); engine.unbindInstanceAttributes(); } else { canvas._addDrawCallCount(context.partDataEndIndex - context.partDataStartIndex, context.renderMode); for (let i = context.partDataStartIndex; i < context.partDataEndIndex; i++) { this.setupUniforms(effect, partIndex, pid._partData, i); engine.draw(true, 0, this.borderIndicesCount); } } } engine.setAlphaMode(curAlphaMode, true); if (this.effectFill && this.effectBorder) { engine.setDepthFunction(depthFunction); } return true; } public dispose(): boolean { if (!super.dispose()) { return false; } if (this.fillVB) { this._engine._releaseBuffer(this.fillVB); this.fillVB = null; } if (this.fillIB) { this._engine._releaseBuffer(this.fillIB); this.fillIB = null; } this.effectFill = null; this.effectFillInstanced = null; this.effectBorder = null; this.effectBorderInstanced = null; if (this.borderVB) { this._engine._releaseBuffer(this.borderVB); this.borderVB = null; } if (this.borderIB) { this._engine._releaseBuffer(this.borderIB); this.borderIB = null; } return true; } } export class Ellipse2DInstanceData extends Shape2DInstanceData { constructor(partId: number) { super(partId, 1); } @instanceData() get properties(): Vector3 { return null; } } @className("Ellipse2D") /** * Ellipse Primitive class */ export class Ellipse2D extends Shape2D { public static acutalSizeProperty: Prim2DPropInfo; public static subdivisionsProperty: Prim2DPropInfo; @instanceLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 1, pi => Ellipse2D.acutalSizeProperty = pi, false, true) /** * Get/Set the size of the ellipse */ public get actualSize(): Size { if (this._actualSize) { return this._actualSize; } return this.size; } public set actualSize(value: Size) { this._actualSize = value; } @modelLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 2, pi => Ellipse2D.subdivisionsProperty = pi) /** * Get/set the number of subdivisions used to draw the ellipsis. Default is 64. */ public get subdivisions(): number { return this._subdivisions; } public set subdivisions(value: number) { this._subdivisions = value; } protected levelIntersect(intersectInfo: IntersectInfo2D): boolean { let w = this.size.width / 2; let h = this.size.height / 2; let x = intersectInfo._localPickPosition.x-w; let y = intersectInfo._localPickPosition.y-h; return ((x * x) / (w * w) + (y * y) / (h * h)) <= 1; } protected updateLevelBoundingInfo() { BoundingInfo2D.CreateFromSizeToRef(this.actualSize, this._levelBoundingInfo); } /** * Create an Ellipse 2D Shape primitive * @param settings a combination of settings, possible ones are * - 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) * - children: an array of direct children * - id: a text identifier, for information purpose * - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0] * - rotation: the initial rotation (in radian) of the primitive. default is 0 * - scale: the initial scale of the primitive. default is 1. You can alternatively use scaleX &| scaleY to apply non uniform scale * - opacity: set the overall opacity of the primitive, 1 to be opaque (default), less than 1 to be transparent. * - origin: define the normalized origin point location, default [0.5;0.5] * - size: the size of the group. Alternatively the width and height properties can be set. Default will be [10;10]. * - subdivision: the number of subdivision to create the ellipse perimeter, default is 64. * - 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. can also be a string value (see Canvas2D.GetBrushFromString) * - 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. can be a string value (see Canvas2D.GetBrushFromString) * - borderThickness: the thickness of the drawn border, default is 1. * - isVisible: true if the group must be visible, false for hidden. Default is true. * - 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! * - marginTop: top margin, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString) * - marginLeft: left margin, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString) * - marginRight: right margin, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString) * - marginBottom: bottom margin, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString) * - margin: top, left, right and bottom margin formatted as a single string (see PrimitiveThickness.fromString) * - marginHAlignment: one value of the PrimitiveAlignment type's static properties * - marginVAlignment: one value of the PrimitiveAlignment type's static properties * - marginAlignment: a string defining the alignment, see PrimitiveAlignment.fromString * - paddingTop: top padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString) * - paddingLeft: left padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString) * - paddingRight: right padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString) * - paddingBottom: bottom padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString) * - padding: top, left, right and bottom padding formatted as a single string (see PrimitiveThickness.fromString) */ constructor(settings?: { parent ?: Prim2DBase, children ?: Array, id ?: string, position ?: Vector2, x ?: number, y ?: number, rotation ?: number, scale ?: number, scaleX ?: number, scaleY ?: number, opacity ?: number, origin ?: Vector2, size ?: Size, width ?: number, height ?: number, subdivisions ?: number, fill ?: IBrush2D | string, border ?: IBrush2D | string, borderThickness ?: number, isVisible ?: boolean, childrenFlatZOrder?: boolean, marginTop ?: number | string, marginLeft ?: number | string, marginRight ?: number | string, marginBottom ?: number | string, margin ?: number | string, marginHAlignment ?: number, marginVAlignment ?: number, marginAlignment ?: string, paddingTop ?: number | string, paddingLeft ?: number | string, paddingRight ?: number | string, paddingBottom ?: number | string, padding ?: string, }) { // Avoid checking every time if the object exists if (settings == null) { settings = {}; } super(settings); if (settings.size != null) { this.size = settings.size; } else if (settings.width || settings.height) { let size = new Size(settings.width, settings.height); this.size = size; } let sub = (settings.subdivisions == null) ? 64 : settings.subdivisions; this.subdivisions = sub; } protected createModelRenderCache(modelKey: string): ModelRenderCache { let renderCache = new Ellipse2DRenderCache(this.owner.engine, modelKey); return renderCache; } protected setupModelRenderCache(modelRenderCache: ModelRenderCache) { let renderCache = modelRenderCache; let engine = this.owner.engine; // Need to create WebGL resources for fill part? if (this.fill) { let vbSize = this.subdivisions + 1; let vb = new Float32Array(vbSize); for (let i = 0; i < vbSize; i++) { vb[i] = i; } renderCache.fillVB = engine.createVertexBuffer(vb); let triCount = vbSize - 1; let ib = new Float32Array(triCount * 3); for (let i = 0; i < triCount; i++) { ib[i * 3 + 0] = 0; ib[i * 3 + 2] = i + 1; ib[i * 3 + 1] = i + 2; } ib[triCount * 3 - 2] = 1; renderCache.fillIB = engine.createIndexBuffer(ib); renderCache.fillIndicesCount = triCount * 3; // 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 let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["index"], true); if (ei) { renderCache.effectFillInstanced = engine.createEffect({ vertex: "ellipse2d", fragment: "ellipse2d" }, ei.attributes, ei.uniforms, [], ei.defines, null); } // Get the non instanced version ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_FILLPARTID, ["index"], false); renderCache.effectFill = engine.createEffect({ vertex: "ellipse2d", fragment: "ellipse2d" }, ei.attributes, ei.uniforms, [], ei.defines, null); } // Need to create WebGL resource for border part? if (this.border) { let vbSize = this.subdivisions * 2; let vb = new Float32Array(vbSize); for (let i = 0; i < vbSize; i++) { vb[i] = i; } renderCache.borderVB = engine.createVertexBuffer(vb); let triCount = vbSize; let rs = triCount / 2; let ib = new Float32Array(triCount * 3); for (let i = 0; i < rs; i++) { let r0 = i; let r1 = (i + 1) % rs; ib[i * 6 + 0] = rs + r1; ib[i * 6 + 1] = rs + r0; ib[i * 6 + 2] = r0; ib[i * 6 + 3] = r1; ib[i * 6 + 4] = rs + r1; ib[i * 6 + 5] = r0; } renderCache.borderIB = engine.createIndexBuffer(ib); renderCache.borderIndicesCount = (triCount* 3); // 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 let ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_BORDERPARTID, ["index"], true); if (ei) { renderCache.effectBorderInstanced = engine.createEffect("ellipse2d", ei.attributes, ei.uniforms, [], ei.defines, null); } // Get the non instanced version ei = this.getDataPartEffectInfo(Shape2D.SHAPE2D_BORDERPARTID, ["index"], false); renderCache.effectBorder = engine.createEffect("ellipse2d", ei.attributes, ei.uniforms, [], ei.defines, null); } return renderCache; } protected createInstanceDataParts(): InstanceDataBase[] { var res = new Array(); if (this.border) { res.push(new Ellipse2DInstanceData(Shape2D.SHAPE2D_BORDERPARTID)); } if (this.fill) { res.push(new Ellipse2DInstanceData(Shape2D.SHAPE2D_FILLPARTID)); } return res; } protected refreshInstanceDataPart(part: InstanceDataBase): boolean { if (!super.refreshInstanceDataPart(part)) { return false; } if (part.id === Shape2D.SHAPE2D_BORDERPARTID) { let d = part; let size = this.actualSize; d.properties = new Vector3(size.width, size.height, this.subdivisions); } else if (part.id === Shape2D.SHAPE2D_FILLPARTID) { let d = part; let size = this.actualSize; d.properties = new Vector3(size.width, size.height, this.subdivisions); } return true; } private _subdivisions: number; } }