589 lines
18 KiB
JavaScript
589 lines
18 KiB
JavaScript
import { SmoothGraphicsData } from './core/SmoothGraphicsData.mjs';
|
|
import { FILL_COMMANDS } from './shapes/index.mjs';
|
|
import { Point, Geometry, Buffer, TYPES, Texture, SHAPES, WRAP_MODES, Matrix, Color } from '@pixi/core';
|
|
import { Bounds } from '@pixi/display';
|
|
import { BuildData } from './core/BuildData.mjs';
|
|
import { SegmentPacker } from './core/SegmentPacker.mjs';
|
|
import { BatchPart } from './core/BatchPart.mjs';
|
|
import { matrixEquals, BatchDrawCall } from './core/BatchDrawCall.mjs';
|
|
|
|
const BATCH_POOL = [];
|
|
const DRAW_CALL_POOL = [];
|
|
const tmpPoint = new Point();
|
|
const tmpBounds = new Bounds();
|
|
class SmoothGraphicsGeometry extends Geometry {
|
|
constructor() {
|
|
super();
|
|
this.indicesUint16 = null;
|
|
this.initAttributes(false);
|
|
this.buildData = new BuildData();
|
|
this.graphicsData = [];
|
|
this.dirty = 0;
|
|
this.batchDirty = -1;
|
|
this.cacheDirty = -1;
|
|
this.clearDirty = 0;
|
|
this.drawCalls = [];
|
|
this.batches = [];
|
|
this.shapeBuildIndex = 0;
|
|
this.shapeBatchIndex = 0;
|
|
this._bounds = new Bounds();
|
|
this.boundsDirty = -1;
|
|
this.boundsPadding = 0;
|
|
this.batchable = false;
|
|
this.indicesUint16 = null;
|
|
this.packer = null;
|
|
this.packSize = 0;
|
|
this.pack32index = null;
|
|
}
|
|
get points() {
|
|
return this.buildData.verts;
|
|
}
|
|
get closePointEps() {
|
|
return this.buildData.closePointEps;
|
|
}
|
|
initAttributes(_static) {
|
|
this._buffer = new Buffer(null, _static, false);
|
|
this._bufferFloats = new Float32Array();
|
|
this._bufferUint = new Uint32Array();
|
|
this._indexBuffer = new Buffer(null, _static, true);
|
|
this.addAttribute("aPrev", this._buffer, 2, false, TYPES.FLOAT).addAttribute("aPoint1", this._buffer, 2, false, TYPES.FLOAT).addAttribute("aPoint2", this._buffer, 2, false, TYPES.FLOAT).addAttribute("aNext", this._buffer, 2, false, TYPES.FLOAT).addAttribute("aTravel", this._buffer, 1, false, TYPES.FLOAT).addAttribute("aVertexJoint", this._buffer, 1, false, TYPES.FLOAT).addAttribute("aStyleId", this._buffer, 1, false, TYPES.FLOAT).addAttribute("aColor", this._buffer, 4, true, TYPES.UNSIGNED_BYTE).addIndex(this._indexBuffer);
|
|
this.strideFloats = 12;
|
|
}
|
|
checkInstancing(instanced, allow32Indices) {
|
|
if (this.packer) {
|
|
return;
|
|
}
|
|
this.packer = new SegmentPacker();
|
|
this.pack32index = allow32Indices;
|
|
}
|
|
/**
|
|
* Get the current bounds of the graphic geometry.
|
|
*
|
|
* @member {PIXI.Bounds}
|
|
* @readonly
|
|
*/
|
|
get bounds() {
|
|
if (this.boundsDirty !== this.dirty) {
|
|
this.boundsDirty = this.dirty;
|
|
this.calculateBounds();
|
|
}
|
|
return this._bounds;
|
|
}
|
|
/**
|
|
* Call if you changed graphicsData manually.
|
|
* Empties all batch buffers.
|
|
*/
|
|
invalidate() {
|
|
this.boundsDirty = -1;
|
|
this.dirty++;
|
|
this.batchDirty++;
|
|
this.shapeBuildIndex = 0;
|
|
this.shapeBatchIndex = 0;
|
|
this.packSize = 0;
|
|
this.buildData.clear();
|
|
for (let i = 0; i < this.drawCalls.length; i++) {
|
|
this.drawCalls[i].clear();
|
|
DRAW_CALL_POOL.push(this.drawCalls[i]);
|
|
}
|
|
this.drawCalls.length = 0;
|
|
for (let i = 0; i < this.batches.length; i++) {
|
|
const batchPart = this.batches[i];
|
|
batchPart.reset();
|
|
BATCH_POOL.push(batchPart);
|
|
}
|
|
this.batches.length = 0;
|
|
}
|
|
clear() {
|
|
if (this.graphicsData.length > 0) {
|
|
this.invalidate();
|
|
this.clearDirty++;
|
|
this.graphicsData.length = 0;
|
|
}
|
|
return this;
|
|
}
|
|
drawShape(shape, fillStyle = null, lineStyle = null, matrix = null) {
|
|
const data = new SmoothGraphicsData(shape, fillStyle, lineStyle, matrix);
|
|
this.graphicsData.push(data);
|
|
this.dirty++;
|
|
return this;
|
|
}
|
|
drawHole(shape, matrix = null) {
|
|
if (!this.graphicsData.length) {
|
|
return null;
|
|
}
|
|
const data = new SmoothGraphicsData(shape, null, null, matrix);
|
|
const lastShape = this.graphicsData[this.graphicsData.length - 1];
|
|
data.lineStyle = lastShape.lineStyle;
|
|
lastShape.holes.push(data);
|
|
this.dirty++;
|
|
return this;
|
|
}
|
|
destroy() {
|
|
super.destroy();
|
|
for (let i = 0; i < this.graphicsData.length; ++i) {
|
|
this.graphicsData[i].destroy();
|
|
}
|
|
this.buildData.destroy();
|
|
this.buildData = null;
|
|
this.indexBuffer.destroy();
|
|
this.indexBuffer = null;
|
|
this.graphicsData.length = 0;
|
|
this.graphicsData = null;
|
|
this.drawCalls.length = 0;
|
|
this.drawCalls = null;
|
|
this.batches.length = 0;
|
|
this.batches = null;
|
|
this._bounds = null;
|
|
}
|
|
/**
|
|
* Check to see if a point is contained within this geometry.
|
|
*
|
|
* @param {PIXI.IPointData} point - Point to check if it's contained.
|
|
* @return {Boolean} `true` if the point is contained within geometry.
|
|
*/
|
|
containsPoint(point) {
|
|
const graphicsData = this.graphicsData;
|
|
for (let i = 0; i < graphicsData.length; ++i) {
|
|
const data = graphicsData[i];
|
|
if (!data.fillStyle.visible) {
|
|
continue;
|
|
}
|
|
if (data.shape) {
|
|
if (data.matrix) {
|
|
data.matrix.applyInverse(point, tmpPoint);
|
|
} else {
|
|
tmpPoint.copyFrom(point);
|
|
}
|
|
if (data.shape.contains(tmpPoint.x, tmpPoint.y)) {
|
|
let hitHole = false;
|
|
if (data.holes) {
|
|
for (let i2 = 0; i2 < data.holes.length; i2++) {
|
|
const hole = data.holes[i2];
|
|
if (hole.shape.contains(tmpPoint.x, tmpPoint.y)) {
|
|
hitHole = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!hitHole) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
updatePoints() {
|
|
}
|
|
updateBufferSize() {
|
|
this._buffer.update(new Float32Array());
|
|
}
|
|
updateBuild() {
|
|
const { graphicsData, buildData } = this;
|
|
const len = graphicsData.length;
|
|
for (let i = this.shapeBuildIndex; i < len; i++) {
|
|
const data = graphicsData[i];
|
|
data.strokeStart = 0;
|
|
data.strokeLen = 0;
|
|
data.fillStart = 0;
|
|
data.fillLen = 0;
|
|
const { fillStyle, lineStyle, holes } = data;
|
|
if (!fillStyle.visible && !lineStyle.visible) {
|
|
continue;
|
|
}
|
|
const command = FILL_COMMANDS[data.type];
|
|
data.clearPath();
|
|
command.path(data, buildData);
|
|
if (data.matrix) {
|
|
this.transformPoints(data.points, data.matrix);
|
|
}
|
|
data.clearBuild();
|
|
if (data.points.length <= 2) {
|
|
continue;
|
|
}
|
|
if (fillStyle.visible || lineStyle.visible) {
|
|
this.processHoles(holes);
|
|
}
|
|
if (fillStyle.visible) {
|
|
data.fillAA = data.fillStyle.smooth && data.fillStyle.texture === Texture.WHITE && holes.length === 0 && !(data.closeStroke && data.lineStyle.visible && !data.lineStyle.shader && data.lineStyle.alpha >= 0.99 && data.lineStyle.width * Math.min(data.lineStyle.alignment, 1 - data.lineStyle.alignment) >= 0.495);
|
|
data.fillStart = buildData.joints.length;
|
|
if (holes.length) {
|
|
FILL_COMMANDS[SHAPES.POLY].fill(data, buildData);
|
|
} else {
|
|
command.fill(data, buildData);
|
|
}
|
|
data.fillLen = buildData.joints.length - data.fillStart;
|
|
}
|
|
if (lineStyle.visible) {
|
|
data.strokeStart = buildData.joints.length;
|
|
command.line(data, buildData);
|
|
for (let i2 = 0; i2 < holes.length; i2++) {
|
|
const hole = holes[i2];
|
|
FILL_COMMANDS[hole.type].line(hole, buildData);
|
|
}
|
|
data.strokeLen = buildData.joints.length - data.strokeStart;
|
|
}
|
|
}
|
|
this.shapeBuildIndex = len;
|
|
}
|
|
updateBatches(shaderSettings) {
|
|
if (!this.graphicsData.length) {
|
|
this.batchable = true;
|
|
return;
|
|
}
|
|
this.updateBuild();
|
|
if (!this.validateBatching()) {
|
|
return;
|
|
}
|
|
const { buildData, graphicsData } = this;
|
|
const len = graphicsData.length;
|
|
this.cacheDirty = this.dirty;
|
|
let batchPart = null;
|
|
let currentStyle = null;
|
|
if (this.batches.length > 0) {
|
|
batchPart = this.batches[this.batches.length - 1];
|
|
currentStyle = batchPart.style;
|
|
}
|
|
for (let i = this.shapeBatchIndex; i < len; i++) {
|
|
const data = graphicsData[i];
|
|
const fillStyle = data.fillStyle;
|
|
const lineStyle = data.lineStyle;
|
|
if (data.matrix) {
|
|
this.transformPoints(data.points, data.matrix);
|
|
}
|
|
if (!fillStyle.visible && !lineStyle.visible) {
|
|
continue;
|
|
}
|
|
for (let j = 0; j < 2; j++) {
|
|
const style = j === 0 ? fillStyle : lineStyle;
|
|
if (!style.visible)
|
|
continue;
|
|
const nextTexture = style.texture.baseTexture;
|
|
const attribOld = buildData.vertexSize;
|
|
const indexOld = buildData.indexSize;
|
|
nextTexture.wrapMode = WRAP_MODES.REPEAT;
|
|
if (j === 0) {
|
|
this.packer.updateBufferSize(data.fillStart, data.fillLen, data.triangles.length, buildData);
|
|
} else {
|
|
this.packer.updateBufferSize(data.strokeStart, data.strokeLen, data.triangles.length, buildData);
|
|
}
|
|
const attribSize = buildData.vertexSize;
|
|
if (attribSize === attribOld)
|
|
continue;
|
|
if (batchPart && !this._compareStyles(currentStyle, style)) {
|
|
batchPart.end(indexOld, attribOld);
|
|
batchPart = null;
|
|
}
|
|
if (!batchPart) {
|
|
batchPart = BATCH_POOL.pop() || new BatchPart();
|
|
batchPart.begin(style, indexOld, attribOld);
|
|
this.batches.push(batchPart);
|
|
currentStyle = style;
|
|
}
|
|
if (j === 0) {
|
|
batchPart.jointEnd = data.fillStart + data.fillLen;
|
|
} else {
|
|
batchPart.jointEnd = data.strokeStart + data.strokeLen;
|
|
}
|
|
}
|
|
}
|
|
this.shapeBatchIndex = len;
|
|
if (batchPart) {
|
|
batchPart.end(buildData.indexSize, buildData.vertexSize);
|
|
}
|
|
if (this.batches.length === 0) {
|
|
this.batchable = true;
|
|
return;
|
|
}
|
|
this.batchable = this.isBatchable();
|
|
if (this.batchable) {
|
|
this.packBatches();
|
|
} else {
|
|
this.buildDrawCalls(shaderSettings);
|
|
this.updatePack();
|
|
}
|
|
}
|
|
updatePack() {
|
|
const { vertexSize, indexSize } = this.buildData;
|
|
if (this.packSize === vertexSize) {
|
|
return;
|
|
}
|
|
const { strideFloats, packer, buildData, batches } = this;
|
|
const buffer = this._buffer;
|
|
const index = this._indexBuffer;
|
|
const floatsSize = vertexSize * strideFloats;
|
|
if (buffer.data.length !== floatsSize) {
|
|
const arrBuf = new ArrayBuffer(floatsSize * 4);
|
|
this._bufferFloats = new Float32Array(arrBuf);
|
|
this._bufferUint = new Uint32Array(arrBuf);
|
|
buffer.data = this._bufferFloats;
|
|
}
|
|
if (index.data.length !== indexSize) {
|
|
if (vertexSize > 65535 && this.pack32index) {
|
|
index.data = new Uint32Array(indexSize);
|
|
} else {
|
|
index.data = new Uint16Array(indexSize);
|
|
}
|
|
}
|
|
packer.beginPack(buildData, this._bufferFloats, this._bufferUint, index.data);
|
|
let j = 0;
|
|
for (let i = 0; i < this.graphicsData.length; i++) {
|
|
const data = this.graphicsData[i];
|
|
if (data.fillLen) {
|
|
while (batches[j].jointEnd <= data.fillStart) {
|
|
j++;
|
|
}
|
|
packer.packInterleavedGeometry(
|
|
data.fillStart,
|
|
data.fillLen,
|
|
data.triangles,
|
|
batches[j].styleId,
|
|
batches[j].rgba
|
|
);
|
|
}
|
|
if (data.strokeLen) {
|
|
while (batches[j].jointEnd <= data.strokeStart) {
|
|
j++;
|
|
}
|
|
packer.packInterleavedGeometry(
|
|
data.strokeStart,
|
|
data.strokeLen,
|
|
data.triangles,
|
|
batches[j].styleId,
|
|
batches[j].rgba
|
|
);
|
|
}
|
|
}
|
|
buffer.update();
|
|
index.update();
|
|
this.packSize = vertexSize;
|
|
}
|
|
/**
|
|
* Affinity check
|
|
*
|
|
* @param {PIXI.FillStyle | PIXI.LineStyle} styleA
|
|
* @param {PIXI.FillStyle | PIXI.LineStyle} styleB
|
|
*/
|
|
_compareStyles(styleA, styleB) {
|
|
if (!styleA || !styleB) {
|
|
return false;
|
|
}
|
|
if (styleA.texture.baseTexture !== styleB.texture.baseTexture) {
|
|
return false;
|
|
}
|
|
if (styleA.color + styleA.alpha !== styleB.color + styleB.alpha) {
|
|
return false;
|
|
}
|
|
if (styleA.shader !== styleB.shader) {
|
|
return false;
|
|
}
|
|
if (styleA.width !== styleB.width) {
|
|
return false;
|
|
}
|
|
if (styleA.scaleMode !== styleB.scaleMode) {
|
|
return false;
|
|
}
|
|
if (styleA.alignment !== styleB.alignment) {
|
|
return false;
|
|
}
|
|
const mat1 = styleA.matrix || Matrix.IDENTITY;
|
|
const mat2 = styleB.matrix || Matrix.IDENTITY;
|
|
return matrixEquals(mat1, mat2);
|
|
}
|
|
/**
|
|
* Test geometry for batching process.
|
|
*
|
|
* @protected
|
|
*/
|
|
validateBatching() {
|
|
if (this.dirty === this.cacheDirty || !this.graphicsData.length) {
|
|
return false;
|
|
}
|
|
for (let i = 0, l = this.graphicsData.length; i < l; i++) {
|
|
const data = this.graphicsData[i];
|
|
const fill = data.fillStyle;
|
|
const line = data.lineStyle;
|
|
if (fill && !fill.texture.baseTexture.valid)
|
|
return false;
|
|
if (line && !line.texture.baseTexture.valid)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
/**
|
|
* Offset the indices so that it works with the batcher.
|
|
*
|
|
* @protected
|
|
*/
|
|
packBatches() {
|
|
this.batchDirty++;
|
|
const batches = this.batches;
|
|
for (let i = 0, l = batches.length; i < l; i++) {
|
|
const batch = batches[i];
|
|
for (let j = 0; j < batch.size; j++) {
|
|
const index = batch.start + j;
|
|
this.indicesUint16[index] = this.indicesUint16[index] - batch.attribStart;
|
|
}
|
|
}
|
|
}
|
|
isBatchable() {
|
|
return false;
|
|
}
|
|
/**
|
|
* Converts intermediate batches data to drawCalls.
|
|
*
|
|
* @protected
|
|
*/
|
|
buildDrawCalls(shaderSettings) {
|
|
for (let i = 0; i < this.drawCalls.length; i++) {
|
|
this.drawCalls[i].clear();
|
|
DRAW_CALL_POOL.push(this.drawCalls[i]);
|
|
}
|
|
this.drawCalls.length = 0;
|
|
let currentGroup = DRAW_CALL_POOL.pop() || new BatchDrawCall();
|
|
currentGroup.begin(shaderSettings, null);
|
|
let index = 0;
|
|
this.drawCalls.push(currentGroup);
|
|
for (let i = 0; i < this.batches.length; i++) {
|
|
const batchData = this.batches[i];
|
|
const style = batchData.style;
|
|
if (batchData.attribSize === 0) {
|
|
continue;
|
|
}
|
|
let styleId = -1;
|
|
const mat = style.getTextureMatrix();
|
|
if (currentGroup.check(style.shader)) {
|
|
styleId = currentGroup.add(
|
|
style.texture,
|
|
mat,
|
|
style.width,
|
|
style.alignment || 0,
|
|
style.packLineScale()
|
|
);
|
|
}
|
|
if (styleId < 0) {
|
|
currentGroup = DRAW_CALL_POOL.pop() || new BatchDrawCall();
|
|
this.drawCalls.push(currentGroup);
|
|
currentGroup.begin(shaderSettings, style.shader);
|
|
currentGroup.start = index;
|
|
styleId = currentGroup.add(
|
|
style.texture,
|
|
mat,
|
|
style.width,
|
|
style.alignment || 0,
|
|
style.packLineScale()
|
|
);
|
|
}
|
|
currentGroup.size += batchData.size;
|
|
index += batchData.size;
|
|
const { color, alpha } = style;
|
|
const bgr = Color.shared.setValue(color).toLittleEndianNumber();
|
|
batchData.rgba = Color.shared.setValue(bgr).toPremultiplied(alpha);
|
|
batchData.styleId = styleId;
|
|
}
|
|
}
|
|
processHoles(holes) {
|
|
for (let i = 0; i < holes.length; i++) {
|
|
const hole = holes[i];
|
|
const command = FILL_COMMANDS[hole.type];
|
|
hole.clearPath();
|
|
command.path(hole, this.buildData);
|
|
if (hole.matrix) {
|
|
this.transformPoints(hole.points, hole.matrix);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Update the local bounds of the object. Expensive to use performance-wise.
|
|
*
|
|
* @protected
|
|
*/
|
|
calculateBounds() {
|
|
const bounds = this._bounds;
|
|
const sequenceBounds = tmpBounds;
|
|
let curMatrix = Matrix.IDENTITY;
|
|
this._bounds.clear();
|
|
sequenceBounds.clear();
|
|
for (let i = 0; i < this.graphicsData.length; i++) {
|
|
const data = this.graphicsData[i];
|
|
const shape = data.shape;
|
|
const type = data.type;
|
|
const lineStyle = data.lineStyle;
|
|
const nextMatrix = data.matrix || Matrix.IDENTITY;
|
|
let lineWidth = 0;
|
|
if (lineStyle && lineStyle.visible) {
|
|
lineWidth = lineStyle.width;
|
|
if (type !== SHAPES.POLY || data.fillStyle.visible) {
|
|
lineWidth *= Math.max(0, lineStyle.alignment);
|
|
} else {
|
|
lineWidth *= Math.max(lineStyle.alignment, 1 - lineStyle.alignment);
|
|
}
|
|
}
|
|
if (curMatrix !== nextMatrix) {
|
|
if (!sequenceBounds.isEmpty()) {
|
|
bounds.addBoundsMatrix(sequenceBounds, curMatrix);
|
|
sequenceBounds.clear();
|
|
}
|
|
curMatrix = nextMatrix;
|
|
}
|
|
if (type === SHAPES.RECT || type === SHAPES.RREC) {
|
|
const rect = shape;
|
|
sequenceBounds.addFramePad(
|
|
rect.x,
|
|
rect.y,
|
|
rect.x + rect.width,
|
|
rect.y + rect.height,
|
|
lineWidth,
|
|
lineWidth
|
|
);
|
|
} else if (type === SHAPES.CIRC) {
|
|
const circle = shape;
|
|
sequenceBounds.addFramePad(
|
|
circle.x,
|
|
circle.y,
|
|
circle.x,
|
|
circle.y,
|
|
circle.radius + lineWidth,
|
|
circle.radius + lineWidth
|
|
);
|
|
} else if (type === SHAPES.ELIP) {
|
|
const ellipse = shape;
|
|
sequenceBounds.addFramePad(
|
|
ellipse.x,
|
|
ellipse.y,
|
|
ellipse.x,
|
|
ellipse.y,
|
|
ellipse.width + lineWidth,
|
|
ellipse.height + lineWidth
|
|
);
|
|
} else {
|
|
const poly = shape;
|
|
bounds.addVerticesMatrix(curMatrix, poly.points, 0, poly.points.length, lineWidth, lineWidth);
|
|
}
|
|
}
|
|
if (!sequenceBounds.isEmpty()) {
|
|
bounds.addBoundsMatrix(sequenceBounds, curMatrix);
|
|
}
|
|
bounds.pad(this.boundsPadding, this.boundsPadding);
|
|
}
|
|
/**
|
|
* Transform points using matrix.
|
|
*
|
|
* @protected
|
|
* @param {number[]} points - Points to transform
|
|
* @param {PIXI.Matrix} matrix - Transform matrix
|
|
*/
|
|
transformPoints(points, matrix) {
|
|
for (let i = 0; i < points.length / 2; i++) {
|
|
const x = points[i * 2];
|
|
const y = points[i * 2 + 1];
|
|
points[i * 2] = matrix.a * x + matrix.c * y + matrix.tx;
|
|
points[i * 2 + 1] = matrix.b * x + matrix.d * y + matrix.ty;
|
|
}
|
|
}
|
|
}
|
|
SmoothGraphicsGeometry.BATCHABLE_SIZE = 100;
|
|
|
|
export { BATCH_POOL, DRAW_CALL_POOL, SmoothGraphicsGeometry };
|
|
//# sourceMappingURL=SmoothGraphicsGeometry.mjs.map
|