542 lines
16 KiB
JavaScript
542 lines
16 KiB
JavaScript
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
|