import { Point, State, Color, BLEND_MODES, Texture, Polygon, PI_2, Rectangle, RoundedRectangle, Circle, Ellipse, SHAPES, utils, MSAA_QUALITY, DRAW_MODES } from '@pixi/core'; import { curves, LINE_CAP, LINE_JOIN, Graphics, graphicsUtils } from '@pixi/graphics'; import { SmoothGraphicsGeometry } from './SmoothGraphicsGeometry.mjs'; import { Container } from '@pixi/display'; import { FillStyle } from './core/FillStyle.mjs'; import { LineStyle, LINE_SCALE_MODE } from './core/LineStyle.mjs'; import { SmoothGraphicsShader } from './SmoothShader.mjs'; import { settings } from './settings.mjs'; const UnsmoothGraphics = Graphics; const { BezierUtils, QuadraticUtils, ArcUtils } = graphicsUtils; const DEFAULT_SHADERS = {}; const _SmoothGraphics = class extends Container { constructor(geometry = null) { super(); this._geometry = geometry || new SmoothGraphicsGeometry(); this._geometry.refCount++; this.shader = null; this.shaderSettings = { maxStyles: settings.SHADER_MAX_STYLES, maxTextures: settings.SHADER_MAX_TEXTURES, pixelLine: settings.PIXEL_LINE }; this.state = State.for2d(); this._fillStyle = new FillStyle(); this._lineStyle = new LineStyle(); this._matrix = null; this._holeMode = false; this.currentPath = null; this.batches = []; this.batchTint = -1; this.batchDirty = -1; this.vertexData = null; this.pluginName = "smooth"; this._transformID = -1; this._tintColor = new Color(16777215); this.blendMode = BLEND_MODES.NORMAL; } get geometry() { return this._geometry; } clone() { this.finishPoly(); return new _SmoothGraphics(this._geometry); } set blendMode(value) { this.state.blendMode = value; } get blendMode() { return this.state.blendMode; } get tint() { return this._tintColor.value; } set tint(value) { this._tintColor.setValue(value); } get fill() { return this._fillStyle; } get line() { return this._lineStyle; } lineStyle(options = null, color = 0, alpha = 1, alignment = 0.5, scaleMode = settings.LINE_SCALE_MODE) { if (typeof options === "number") { if (typeof scaleMode === "boolean") { scaleMode = scaleMode ? LINE_SCALE_MODE.NONE : LINE_SCALE_MODE.NORMAL; } options = { width: options, color, alpha, alignment, scaleMode }; } else { const native = options.native; if (native !== void 0) { options.scaleMode = native ? LINE_SCALE_MODE.NONE : LINE_SCALE_MODE.NORMAL; } } return this.lineTextureStyle(options); } lineTextureStyle(options) { options = Object.assign({ width: 0, texture: Texture.WHITE, color: options && options.texture ? 16777215 : 0, alpha: 1, matrix: null, alignment: 0.5, native: false, cap: LINE_CAP.BUTT, join: LINE_JOIN.MITER, miterLimit: 10, shader: null, scaleMode: settings.LINE_SCALE_MODE }, options); this.normalizeColor(options); if (this.currentPath) { this.startPoly(); } const visible = options.width > 0 && options.alpha > 0; if (!visible) { this._lineStyle.reset(); } else { if (options.matrix) { options.matrix = options.matrix.clone(); options.matrix.invert(); } Object.assign(this._lineStyle, { visible }, options); } return this; } startPoly() { if (this.currentPath) { const points = this.currentPath.points; const len = this.currentPath.points.length; if (len > 2) { this.drawShape(this.currentPath); this.currentPath = new Polygon(); this.currentPath.closeStroke = false; this.currentPath.points.push(points[len - 2], points[len - 1]); } } else { this.currentPath = new Polygon(); this.currentPath.closeStroke = false; } } finishPoly() { if (this.currentPath) { if (this.currentPath.points.length > 2) { this.drawShape(this.currentPath); this.currentPath = null; } else { this.currentPath.points.length = 0; } } } moveTo(x, y) { this.startPoly(); this.currentPath.points[0] = x; this.currentPath.points[1] = y; return this; } lineTo(x, y) { if (!this.currentPath) { this.moveTo(0, 0); } const points = this.currentPath.points; const fromX = points[points.length - 2]; const fromY = points[points.length - 1]; if (fromX !== x || fromY !== y) { points.push(x, y); } return this; } _initCurve(x = 0, y = 0) { if (this.currentPath) { if (this.currentPath.points.length === 0) { this.currentPath.points = [x, y]; } } else { this.moveTo(x, y); } } quadraticCurveTo(cpX, cpY, toX, toY) { this._initCurve(); const points = this.currentPath.points; if (points.length === 0) { this.moveTo(0, 0); } QuadraticUtils.curveTo(cpX, cpY, toX, toY, points); return this; } bezierCurveTo(cpX, cpY, cpX2, cpY2, toX, toY) { this._initCurve(); BezierUtils.curveTo(cpX, cpY, cpX2, cpY2, toX, toY, this.currentPath.points); return this; } arcTo(x1, y1, x2, y2, radius) { this._initCurve(x1, y1); const points = this.currentPath.points; const result = ArcUtils.curveTo(x1, y1, x2, y2, radius, points); if (result) { const { cx, cy, radius: radius2, startAngle, endAngle, anticlockwise } = result; this.arc(cx, cy, radius2, startAngle, endAngle, anticlockwise); } return this; } arc(cx, cy, radius, startAngle, endAngle, anticlockwise = false) { if (startAngle === endAngle) { return this; } if (!anticlockwise && endAngle <= startAngle) { endAngle += PI_2; } else if (anticlockwise && startAngle <= endAngle) { startAngle += PI_2; } const sweep = endAngle - startAngle; if (sweep === 0) { return this; } const startX = cx + Math.cos(startAngle) * radius; const startY = cy + Math.sin(startAngle) * radius; const eps = this._geometry.closePointEps; let points = this.currentPath ? this.currentPath.points : null; if (points) { const xDiff = Math.abs(points[points.length - 2] - startX); const yDiff = Math.abs(points[points.length - 1] - startY); if (xDiff < eps && yDiff < eps) ; else { points.push(startX, startY); } } else { this.moveTo(startX, startY); points = this.currentPath.points; } ArcUtils.arc(startX, startY, cx, cy, radius, startAngle, endAngle, anticlockwise, points); return this; } beginFill(color = 0, alpha = 1, smooth = false) { return this.beginTextureFill({ texture: Texture.WHITE, color, alpha, smooth }); } normalizeColor(options) { const temp = Color.shared.setValue(options.color ?? 0); options.color = temp.toNumber(); options.alpha ?? (options.alpha = temp.alpha); } beginTextureFill(options) { options = Object.assign({ texture: Texture.WHITE, color: 16777215, alpha: 1, matrix: null, smooth: false }, options); this.normalizeColor(options); if (this.currentPath) { this.startPoly(); } const visible = options.alpha > 0; if (!visible) { this._fillStyle.reset(); } else { if (options.matrix) { options.matrix = options.matrix.clone(); options.matrix.invert(); } Object.assign(this._fillStyle, { visible }, options); } return this; } endFill() { this.finishPoly(); this._fillStyle.reset(); return this; } drawRect(x, y, width, height) { return this.drawShape(new Rectangle(x, y, width, height)); } drawRoundedRect(x, y, width, height, radius) { return this.drawShape(new RoundedRectangle(x, y, width, height, radius)); } drawCircle(x, y, radius) { return this.drawShape(new Circle(x, y, radius)); } drawEllipse(x, y, width, height) { return this.drawShape(new Ellipse(x, y, width, height)); } drawPolygon(...path) { let points; let closeStroke = true; const poly = path[0]; if (poly.points) { closeStroke = poly.closeStroke; points = poly.points; } else if (Array.isArray(path[0])) { points = path[0]; } else { points = path; } const shape = new Polygon(points); shape.closeStroke = closeStroke; this.drawShape(shape); return this; } drawShape(shape) { if (!this._holeMode) { this._geometry.drawShape( shape, this._fillStyle.clone(), this._lineStyle.clone(), this._matrix ); } else { this._geometry.drawHole(shape, this._matrix); } return this; } clear() { this._geometry.clear(); this._lineStyle.reset(); this._fillStyle.reset(); this._boundsID++; this._matrix = null; this._holeMode = false; this.currentPath = null; return this; } isFastRect() { const data = this._geometry.graphicsData; return data.length === 1 && data[0].shape.type === SHAPES.RECT && !data[0].matrix && !data[0].holes.length && !(data[0].lineStyle.visible && data[0].lineStyle.width); } _renderCanvas(renderer) { UnsmoothGraphics.prototype._renderCanvas.call(this, renderer); } _render(renderer) { this.finishPoly(); const geometry = this._geometry; const hasuint32 = renderer.context.supports.uint32Indices; geometry.checkInstancing(renderer.geometry.hasInstance, hasuint32); geometry.updateBatches(this.shaderSettings); if (geometry.batchable) { if (this.batchDirty !== geometry.batchDirty) { this._populateBatches(); } this._renderBatched(renderer); } else { renderer.batch.flush(); this._renderDirect(renderer); } } _populateBatches() { const geometry = this._geometry; const blendMode = this.blendMode; const len = geometry.batches.length; this.batchTint = -1; this._transformID = -1; this.batchDirty = geometry.batchDirty; this.batches.length = len; this.vertexData = new Float32Array(geometry.points); for (let i = 0; i < len; i++) { const gI = geometry.batches[i]; const color = gI.style.color; const vertexData = new Float32Array( this.vertexData.buffer, gI.attribStart * 4 * 2, gI.attribSize * 2 ); const batch = { vertexData, blendMode, // indices, // uvs, _batchRGB: utils.hex2rgb(color), _tintRGB: color, _texture: gI.style.texture, alpha: gI.style.alpha, worldAlpha: 1 }; this.batches[i] = batch; } } _renderBatched(renderer) { if (!this.batches.length) { return; } renderer.batch.setObjectRenderer(renderer.plugins[this.pluginName]); this.calculateVertices(); this.calculateTints(); for (let i = 0, l = this.batches.length; i < l; i++) { const batch = this.batches[i]; batch.worldAlpha = this.worldAlpha * batch.alpha; renderer.plugins[this.pluginName].render(batch); } } _renderDirect(renderer) { const directShader = this._resolveDirectShader(renderer); let shader = directShader; const geometry = this._geometry; const worldAlpha = this.worldAlpha; const uniforms = shader.uniforms; const drawCalls = geometry.drawCalls; uniforms.translationMatrix = this.transform.worldTransform; Color.shared.setValue(this._tintColor).premultiply(worldAlpha).toArray(uniforms.tint); uniforms.resolution = renderer.renderTexture.current ? renderer.renderTexture.current.resolution : renderer.resolution; const projTrans = renderer.projection.transform; if (projTrans) { const scale = Math.sqrt(projTrans.a * projTrans.a + projTrans.b * projTrans.b); uniforms.resolution *= scale; } const multisample = renderer.renderTexture.current ? renderer.renderTexture.current.multisample : renderer.multisample; uniforms.expand = (multisample !== MSAA_QUALITY.NONE ? 2 : 1) / uniforms.resolution; renderer.shader.bind(shader); renderer.geometry.bind(geometry, shader); renderer.state.set(this.state); shader = null; for (let i = 0, l = drawCalls.length; i < l; i++) { const drawCall = geometry.drawCalls[i]; const shaderChange = shader !== drawCall.shader; if (shaderChange) { shader = drawCall.shader; if (shader) { shader.uniforms.translationMatrix = this.transform.worldTransform; if (shader.uniforms.tint) { shader.uniforms.tint[0] = uniforms.tint[0]; shader.uniforms.tint[1] = uniforms.tint[1]; shader.uniforms.tint[2] = uniforms.tint[2]; shader.uniforms.tint[3] = uniforms.tint[3]; } } } const { texArray, styleArray, size, start } = drawCall; const groupTextureCount = texArray.count; const shaderHere = shader || directShader; const texs = shaderHere.uniforms.styleTextureId; const mats = shaderHere.uniforms.styleMatrix; const lines = shaderHere.uniforms.styleLine; for (let i2 = 0; i2 < styleArray.count; i2++) { texs[i2] = styleArray.textureIds[i2]; lines[i2 * 2] = styleArray.lines[i2 * 2]; lines[i2 * 2 + 1] = styleArray.lines[i2 * 2 + 1]; const m = styleArray.matrices[i2]; mats[i2 * 6] = m.a; mats[i2 * 6 + 1] = m.c; mats[i2 * 6 + 2] = m.tx; mats[i2 * 6 + 3] = m.b; mats[i2 * 6 + 4] = m.d; mats[i2 * 6 + 5] = m.ty; } const sizes = shaderHere.uniforms.samplerSize; for (let i2 = 0; i2 < groupTextureCount; i2++) { sizes[i2 * 2] = texArray.elements[i2].width; sizes[i2 * 2 + 1] = texArray.elements[i2].height; } renderer.shader.bind(shaderHere); if (shaderChange) { renderer.geometry.bind(geometry); } for (let j = 0; j < groupTextureCount; j++) { renderer.texture.bind(texArray.elements[j], j); } renderer.geometry.draw(DRAW_MODES.TRIANGLES, size, start); } } _resolveDirectShader(_renderer) { let shader = this.shader; const pluginName = this.pluginName; if (!shader) { if (!DEFAULT_SHADERS[pluginName]) { DEFAULT_SHADERS[pluginName] = new SmoothGraphicsShader(this.shaderSettings); } shader = DEFAULT_SHADERS[pluginName]; } return shader; } _calculateBounds() { this.finishPoly(); const geometry = this._geometry; if (!geometry.graphicsData.length) { return; } const { minX, minY, maxX, maxY } = geometry.bounds; this._bounds.addFrame(this.transform, minX, minY, maxX, maxY); } containsPoint(point) { this.worldTransform.applyInverse(point, _SmoothGraphics._TEMP_POINT); return this._geometry.containsPoint(_SmoothGraphics._TEMP_POINT); } calculateTints() { if (this.batchTint !== this.tint) { this.batchTint = this._tintColor.toNumber(); for (let i = 0; i < this.batches.length; i++) { const batch = this.batches[i]; batch._tintRGB = Color.shared.setValue(this._tintColor).multiply(batch._batchRGB).toLittleEndianNumber(); } } } calculateVertices() { const wtID = this.transform._worldID; if (this._transformID === wtID) { return; } this._transformID = wtID; const wt = this.transform.worldTransform; const a = wt.a; const b = wt.b; const c = wt.c; const d = wt.d; const tx = wt.tx; const ty = wt.ty; const data = this._geometry.points; const vertexData = this.vertexData; let count = 0; for (let i = 0; i < data.length; i += 2) { const x = data[i]; const y = data[i + 1]; vertexData[count++] = a * x + c * y + tx; vertexData[count++] = d * y + b * x + ty; } } closePath() { const currentPath = this.currentPath; if (currentPath) { currentPath.closeStroke = true; } return this; } setMatrix(matrix) { this._matrix = matrix; return this; } beginHole() { this.finishPoly(); this._holeMode = true; return this; } endHole() { this.finishPoly(); this._holeMode = false; return this; } destroy(options) { this._geometry.refCount--; if (this._geometry.refCount === 0) { this._geometry.dispose(); } this._matrix = null; this.currentPath = null; this._lineStyle.destroy(); this._lineStyle = null; this._fillStyle.destroy(); this._fillStyle = null; this._geometry = null; this.shader = null; this.vertexData = null; this.batches.length = 0; this.batches = null; super.destroy(options); } }; let SmoothGraphics = _SmoothGraphics; SmoothGraphics.curves = curves; SmoothGraphics._TEMP_POINT = new Point(); export { SmoothGraphics }; //# sourceMappingURL=SmoothGraphics.mjs.map