Files
cocos-engine/cocos/2d/assembler/graphics/webgl/graphics-assembler.ts
Santy-Wang 23de9188ba V3.5 docs (#10898)
* adjust api docs and delete useless annotation

* delete useless annotation
2022-04-27 15:22:22 +08:00

683 lines
21 KiB
TypeScript

/*
Copyright (c) 2017-2020 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import { Color, Vec3 } from '../../../../core/math';
import { IAssembler } from '../../../renderer/base';
import { MeshRenderData } from '../../../renderer/render-data';
import { IBatcher } from '../../../renderer/i-batcher';
import { Graphics } from '../../../components';
import { LineCap, LineJoin, PointFlags } from '../types';
import { earcut as Earcut } from './earcut';
import { Impl, Point } from './impl';
const MAX_VERTEX = 65535;
const MAX_INDICES = MAX_VERTEX * 2;
const PI = Math.PI;
const min = Math.min;
const max = Math.max;
const ceil = Math.ceil;
const acos = Math.acos;
const cos = Math.cos;
const sin = Math.sin;
const atan2 = Math.atan2;
const attrBytes = 8;
let _renderData: MeshRenderData | null = null;
let _impl: Impl | null = null;
const _curColor = new Color();
const vec3_temps: Vec3[] = [];
for (let i = 0; i < 4; i++) {
vec3_temps.push(new Vec3());
}
function curveDivs (r: number, arc: number, tol: number) {
const da = acos(r / (r + tol)) * 2.0;
return max(2, ceil(arc / da));
}
function clamp (v: number, minNum: number, maxNum: number) {
if (v < minNum) {
return minNum;
} else if (v > maxNum) {
return maxNum;
}
return v;
}
/**
* graphics 组装器
* 可通过 `UI.graphicsAssembler` 获取该组装器。
*/
export const graphicsAssembler: IAssembler = {
useModel: true,
updateRenderData (graphics: Graphics) {
},
fillBuffers (graphics: Graphics, renderer: IBatcher) {
// this.renderIA!(graphics, renderer);
},
renderIA (graphics: Graphics, renderer: IBatcher) {
},
getRenderData (graphics: Graphics, vertexCount: number) {
if (!_impl) {
return null;
}
const renderDataList = _impl.getRenderDataList();
let renderData = renderDataList[_impl.dataOffset];
if (!renderData) {
return null;
}
let meshBuffer = renderData;
const maxVertexCount = meshBuffer ? meshBuffer.vertexStart + vertexCount : 0;
if (maxVertexCount > MAX_VERTEX || maxVertexCount * 3 > MAX_INDICES) {
++_impl.dataOffset;
if (_impl.dataOffset < renderDataList.length) {
renderData = renderDataList[_impl.dataOffset];
} else {
renderData = _impl.requestRenderData();
renderDataList[_impl.dataOffset] = renderData;
}
meshBuffer = renderData;
}
if (meshBuffer && meshBuffer.vertexCount < maxVertexCount) {
meshBuffer.request(vertexCount, vertexCount * 3);
}
return renderData;
},
stroke (graphics: Graphics) {
Color.copy(_curColor, graphics.strokeColor);
// graphics.node.getWorldMatrix(_currMatrix);
if (!graphics.impl) {
return;
}
this._flattenPaths!(graphics.impl);
this._expandStroke!(graphics);
graphics.impl.updatePathOffset = true;
this.end(graphics);
},
fill (graphics: Graphics) {
Color.copy(_curColor, graphics.fillColor);
// graphics.node.getWorldMatrix(_currMatrix);
this._expandFill!(graphics);
if (graphics.impl) {
graphics.impl.updatePathOffset = true;
}
this.end(graphics);
},
end (graphics: Graphics) {
graphics.markForUpdateRenderData();
},
_expandStroke (graphics: Graphics) {
const w = graphics.lineWidth * 0.5;
const lineCap = graphics.lineCap;
const lineJoin = graphics.lineJoin;
const miterLimit = graphics.miterLimit;
_impl = graphics.impl;
if (!_impl) {
return;
}
const nCap = curveDivs(w, PI, _impl.tessTol);
this._calculateJoins(_impl, w, lineJoin, miterLimit);
const paths = _impl.paths;
// Calculate max vertex usage.
let vertexCount = 0;
for (let i = _impl.pathOffset, l = _impl.pathLength; i < l; i++) {
const path = paths[i];
const pointsLength = path.points.length;
if (lineJoin === LineJoin.ROUND) {
vertexCount += (pointsLength + path.bevel * (nCap + 2) + 1) * 2;
} else {
vertexCount += (pointsLength + path.bevel * 5 + 1) * 2;
} // plus one for loop
if (!path.closed) {
// space for caps
if (lineCap === LineCap.ROUND) {
vertexCount += (nCap * 2 + 2) * 2;
} else {
vertexCount += (3 + 3) * 2;
}
}
}
const meshBuffer: MeshRenderData | null = _renderData = this.getRenderData!(graphics, vertexCount);
if (!meshBuffer) {
return;
}
const vData = meshBuffer.vData;
const iData = meshBuffer.iData;
for (let i = _impl.pathOffset, l = _impl.pathLength; i < l; i++) {
const path = paths[i];
const pts = path.points;
const pointsLength = pts.length;
const offset = meshBuffer.vertexStart;
let p0: Point;
let p1: Point;
let start = 0;
let end = 0;
const loop = path.closed;
if (loop) {
// Looping
p0 = pts[pointsLength - 1];
p1 = pts[0];
start = 0;
end = pointsLength;
} else {
// Add cap
p0 = pts[0];
p1 = pts[1];
start = 1;
end = pointsLength - 1;
}
p1 = p1 || p0;
if (!loop) {
// Add cap
const dPos = new Point(p1.x, p1.y);
dPos.subtract(p0);
dPos.normalize();
const dx = dPos.x;
const dy = dPos.y;
if (lineCap === LineCap.BUTT) {
this._buttCapStart!(p0, dx, dy, w, 0);
} else if (lineCap === LineCap.SQUARE) {
this._buttCapStart!(p0, dx, dy, w, w);
} else if (lineCap === LineCap.ROUND) {
this._roundCapStart!(p0, dx, dy, w, nCap);
}
}
for (let j = start; j < end; ++j) {
if (lineJoin === LineJoin.ROUND) {
this._roundJoin(p0, p1, w, w, nCap);
} else if ((p1.flags & (PointFlags.PT_BEVEL | PointFlags.PT_INNERBEVEL)) !== 0) {
this._bevelJoin(p0, p1, w, w);
} else {
this._vSet!(p1.x + p1.dmx * w, p1.y + p1.dmy * w, 1);
this._vSet!(p1.x - p1.dmx * w, p1.y - p1.dmy * w, -1);
}
p0 = p1;
p1 = pts[j + 1];
}
if (loop) {
// Loop it
const vDataOffset = offset * attrBytes;
this._vSet(vData[vDataOffset], vData[vDataOffset + 1], 1);
this._vSet(vData[vDataOffset + attrBytes], vData[vDataOffset + attrBytes + 1], -1);
} else {
// Add cap
const dPos = new Point(p1.x, p1.y);
dPos.subtract(p0);
dPos.normalize();
const dx = dPos.x;
const dy = dPos.y;
if (lineCap === LineCap.BUTT) {
this._buttCapEnd!(p1, dx, dy, w, 0);
} else if (lineCap === LineCap.SQUARE) {
this._buttCapEnd!(p1, dx, dy, w, w);
} else if (lineCap === LineCap.ROUND) {
this._roundCapEnd!(p1, dx, dy, w, nCap);
}
}
// stroke indices
let indicesOffset = meshBuffer.indexStart;
for (let begin = offset + 2, over = meshBuffer.vertexStart; begin < over; begin++) {
iData[indicesOffset++] = begin - 2;
iData[indicesOffset++] = begin - 1;
iData[indicesOffset++] = begin;
}
meshBuffer.indexStart = indicesOffset;
}
_renderData = null;
_impl = null;
},
_expandFill (graphics: Graphics) {
_impl = graphics.impl;
if (!_impl) {
return;
}
const paths = _impl.paths;
// Calculate max vertex usage.
let vertexCount = 0;
for (let i = _impl.pathOffset, l = _impl.pathLength; i < l; i++) {
const path = paths[i];
const pointsLength = path.points.length;
vertexCount += pointsLength;
}
const renderData: MeshRenderData | null = _renderData = this.getRenderData!(graphics, vertexCount);
if (!renderData) {
return;
}
const meshBuffer = renderData;
const vData = meshBuffer.vData;
const iData = meshBuffer.iData;
for (let i = _impl.pathOffset, l = _impl.pathLength; i < l; i++) {
const path = paths[i];
const pts = path.points;
const pointsLength = pts.length;
if (pointsLength === 0) {
continue;
}
// Calculate shape vertices.
const vertexOffset = renderData.vertexStart;
for (let j = 0; j < pointsLength; ++j) {
this._vSet!(pts[j].x, pts[j].y);
}
let indicesOffset = renderData.indexStart;
if (path.complex) {
const earcutData: number[] = [];
for (let j = vertexOffset, end = renderData.vertexStart; j < end; j++) {
let vDataOffset = j * attrBytes;
earcutData.push(vData[vDataOffset++]);
earcutData.push(vData[vDataOffset++]);
earcutData.push(vData[vDataOffset++]);
}
const newIndices = Earcut(earcutData, null, 3);
if (!newIndices || newIndices.length === 0) {
continue;
}
for (let j = 0, nIndices = newIndices.length; j < nIndices; j++) {
iData[indicesOffset++] = newIndices[j] + vertexOffset;
}
} else {
const first = vertexOffset;
for (let start = vertexOffset + 2, end = meshBuffer.vertexStart; start < end; start++) {
iData[indicesOffset++] = first;
iData[indicesOffset++] = start - 1;
iData[indicesOffset++] = start;
}
}
meshBuffer.indexStart = indicesOffset;
}
_renderData = null;
_impl = null;
},
_calculateJoins (impl: Impl, w: number, lineJoin: LineJoin, miterLimit: number) {
let iw = 0.0;
if (w > 0.0) {
iw = 1 / w;
}
// Calculate which joins needs extra vertices to append, and gather vertex count.
const paths = impl.paths;
for (let i = impl.pathOffset, l = impl.pathLength; i < l; i++) {
const path = paths[i];
const pts = path.points;
const ptsLength = pts.length;
let p0 = pts[ptsLength - 1];
let p1 = pts[0];
let nLeft = 0;
path.bevel = 0;
for (let j = 0; j < ptsLength; j++) {
let dmr2 = 0;
let cross = 0;
let limit = 0;
// perp normals
const dlx0 = p0.dy;
const dly0 = -p0.dx;
const dlx1 = p1.dy;
const dly1 = -p1.dx;
// Calculate extrusions
p1.dmx = (dlx0 + dlx1) * 0.5;
p1.dmy = (dly0 + dly1) * 0.5;
dmr2 = p1.dmx * p1.dmx + p1.dmy * p1.dmy;
if (dmr2 > 0.000001) {
let scale = 1 / dmr2;
if (scale > 600) {
scale = 600;
}
p1.dmx *= scale;
p1.dmy *= scale;
}
// Keep track of left turns.
cross = p1.dx * p0.dy - p0.dx * p1.dy;
if (cross > 0) {
nLeft++;
p1.flags |= PointFlags.PT_LEFT;
}
// Calculate if we should use bevel or miter for inner join.
limit = max(11, min(p0.len, p1.len) * iw);
if (dmr2 * limit * limit < 1) {
p1.flags |= PointFlags.PT_INNERBEVEL;
}
// Check to see if the corner needs to be beveled.
if (p1.flags & PointFlags.PT_CORNER) {
if (dmr2 * miterLimit * miterLimit < 1
|| lineJoin === LineJoin.BEVEL
|| lineJoin === LineJoin.ROUND) {
p1.flags |= PointFlags.PT_BEVEL;
}
}
if ((p1.flags & (PointFlags.PT_BEVEL | PointFlags.PT_INNERBEVEL)) !== 0) {
path.bevel++;
}
p0 = p1;
p1 = pts[j + 1];
}
}
},
_flattenPaths (impl: Impl) {
const paths = impl.paths;
for (let i = impl.pathOffset, l = impl.pathLength; i < l; i++) {
const path = paths[i];
const pts = path.points;
let p0 = pts[pts.length - 1];
let p1 = pts[0];
if (pts.length > 2 && p0.equals(p1)) {
path.closed = true;
pts.pop();
p0 = pts[pts.length - 1];
}
for (let j = 0, size = pts.length; j < size; j++) {
// Calculate segment direction and length
const dPos = new Point(p1.x, p1.y);
dPos.subtract(p0);
p0.len = dPos.length();
if (dPos.x || dPos.y) {
dPos.normalize();
}
p0.dx = dPos.x;
p0.dy = dPos.y;
// Advance
p0 = p1;
p1 = pts[j + 1];
}
}
},
_chooseBevel (bevel: number, p0: Point, p1: Point, w: number) {
const x = p1.x;
const y = p1.y;
let x0 = 0;
let y0 = 0;
let x1 = 0;
let y1 = 0;
if (bevel !== 0) {
x0 = x + p0.dy * w;
y0 = y - p0.dx * w;
x1 = x + p1.dy * w;
y1 = y - p1.dx * w;
} else {
x0 = x1 = x + p1.dmx * w;
y0 = y1 = y + p1.dmy * w;
}
return [x0, y0, x1, y1];
},
_buttCapStart (p: Point, dx: number, dy: number, w: number, d: number) {
const px = p.x - dx * d;
const py = p.y - dy * d;
const dlx = dy;
const dly = -dx;
this._vSet!(px + dlx * w, py + dly * w, 1);
this._vSet!(px - dlx * w, py - dly * w, -1);
},
_buttCapEnd (p: Point, dx: number, dy: number, w: number, d: number) {
const px = p.x + dx * d;
const py = p.y + dy * d;
const dlx = dy;
const dly = -dx;
this._vSet!(px + dlx * w, py + dly * w, 1);
this._vSet!(px - dlx * w, py - dly * w, -1);
},
_roundCapStart (p: Point, dx: number, dy: number, w: number, nCap: number) {
const px = p.x;
const py = p.y;
const dlx = dy;
const dly = -dx;
for (let i = 0; i < nCap; i++) {
const a = i / (nCap - 1) * PI;
const ax = cos(a) * w;
const ay = sin(a) * w;
this._vSet!(px - dlx * ax - dx * ay, py - dly * ax - dy * ay, 1);
this._vSet!(px, py, 0);
}
this._vSet!(px + dlx * w, py + dly * w, 1);
this._vSet!(px - dlx * w, py - dly * w, -1);
},
_roundCapEnd (p: Point, dx: number, dy: number, w: number, nCap: number) {
const px = p.x;
const py = p.y;
const dlx = dy;
const dly = -dx;
this._vSet!(px + dlx * w, py + dly * w, 1);
this._vSet!(px - dlx * w, py - dly * w, -1);
for (let i = 0; i < nCap; i++) {
const a = i / (nCap - 1) * PI;
const ax = cos(a) * w;
const ay = sin(a) * w;
this._vSet!(px, py, 0);
this._vSet!(px - dlx * ax + dx * ay, py - dly * ax + dy * ay, 1);
}
},
_roundJoin (p0: Point, p1: Point, lw: number, rw: number, nCap: number) {
const dlx0 = p0.dy;
const dly0 = -p0.dx;
const dlx1 = p1.dy;
const dly1 = -p1.dx;
const p1x = p1.x;
const p1y = p1.y;
if ((p1.flags & PointFlags.PT_LEFT) !== 0) {
const out = this._chooseBevel!(p1.flags & PointFlags.PT_INNERBEVEL, p0, p1, lw);
const lx0 = out[0];
const ly0 = out[1];
const lx1 = out[2];
const ly1 = out[3];
const a0 = atan2(-dly0, -dlx0);
let a1 = atan2(-dly1, -dlx1);
if (a1 > a0) { a1 -= PI * 2; }
this._vSet!(lx0, ly0, 1);
this._vSet!(p1x - dlx0 * rw, p1.y - dly0 * rw, -1);
const n = clamp(ceil((a0 - a1) / PI) * nCap, 2, nCap);
for (let i = 0; i < n; i++) {
const u = i / (n - 1);
const a = a0 + u * (a1 - a0);
const rx = p1x + cos(a) * rw;
const ry = p1y + sin(a) * rw;
this._vSet!(p1x, p1y, 0);
this._vSet!(rx, ry, -1);
}
this._vSet!(lx1, ly1, 1);
this._vSet!(p1x - dlx1 * rw, p1y - dly1 * rw, -1);
} else {
const out = this._chooseBevel!(p1.flags & PointFlags.PT_INNERBEVEL, p0, p1, -rw);
const rx0 = out[0];
const ry0 = out[1];
const rx1 = out[2];
const ry1 = out[3];
const a0 = atan2(dly0, dlx0);
let a1 = atan2(dly1, dlx1);
if (a1 < a0) { a1 += PI * 2; }
this._vSet!(p1x + dlx0 * rw, p1y + dly0 * rw, 1);
this._vSet!(rx0, ry0, -1);
const n = clamp(ceil((a1 - a0) / PI) * nCap, 2, nCap);
for (let i = 0; i < n; i++) {
const u = i / (n - 1);
const a = a0 + u * (a1 - a0);
const lx = p1x + cos(a) * lw;
const ly = p1y + sin(a) * lw;
this._vSet!(lx, ly, 1);
this._vSet!(p1x, p1y, 0);
}
this._vSet!(p1x + dlx1 * rw, p1y + dly1 * rw, 1);
this._vSet!(rx1, ry1, -1);
}
},
_bevelJoin (p0: Point, p1: Point, lw: number, rw: number) {
let rx0 = 0;
let ry0 = 0;
let rx1 = 0;
let ry1 = 0;
let lx0 = 0;
let ly0 = 0;
let lx1 = 0;
let ly1 = 0;
const dlx0 = p0.dy;
const dly0 = -p0.dx;
const dlx1 = p1.dy;
const dly1 = -p1.dx;
if (p1.flags & PointFlags.PT_LEFT) {
const out = this._chooseBevel!(p1.flags & PointFlags.PT_INNERBEVEL, p0, p1, lw);
lx0 = out[0];
ly0 = out[1];
lx1 = out[2];
ly1 = out[3];
this._vSet!(lx0, ly0, 1);
this._vSet!(p1.x - dlx0 * rw, p1.y - dly0 * rw, -1);
this._vSet!(lx1, ly1, 1);
this._vSet!(p1.x - dlx1 * rw, p1.y - dly1 * rw, -1);
} else {
const out = this._chooseBevel!(p1.flags & PointFlags.PT_INNERBEVEL, p0, p1, -rw);
rx0 = out[0];
ry0 = out[1];
rx1 = out[2];
ry1 = out[3];
this._vSet!(p1.x + dlx0 * lw, p1.y + dly0 * lw, 1);
this._vSet!(rx0, ry0, -1);
this._vSet!(p1.x + dlx1 * lw, p1.y + dly1 * lw, 1);
this._vSet!(rx1, ry1, -1);
}
},
_vSet (x: number, y: number, distance = 0) {
if (!_renderData) {
return;
}
const meshBuffer = _renderData;
let dataOffset = meshBuffer.vertexStart * attrBytes;
const vData = meshBuffer.vData;
// vec3.set(_tempVec3, x, y, 0);
// vec3.transformMat4(_tempVec3, _tempVec3, _currMatrix);
vData[dataOffset++] = x;
vData[dataOffset++] = y;
vData[dataOffset++] = 0;
Color.toArray(vData, _curColor, dataOffset);
dataOffset += 4;
vData[dataOffset++] = distance;
meshBuffer.vertexStart++;
},
};