q5 3.0.6 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/deno.json +1 -1
- package/package.json +1 -1
- package/q5.d.ts +48 -2
- package/q5.js +675 -384
- package/q5.min.js +2 -2
package/q5.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* q5.js
|
|
3
|
-
* @version 3.
|
|
3
|
+
* @version 3.1
|
|
4
4
|
* @author quinton-ashley
|
|
5
5
|
* @contributors evanalulu, Tezumie, ormaq, Dukemz, LingDong-
|
|
6
6
|
* @license LGPL-3.0
|
|
@@ -385,7 +385,7 @@ function createCanvas(w, h, opt) {
|
|
|
385
385
|
}
|
|
386
386
|
}
|
|
387
387
|
|
|
388
|
-
Q5.version = Q5.VERSION = '3.
|
|
388
|
+
Q5.version = Q5.VERSION = '3.1';
|
|
389
389
|
|
|
390
390
|
if (typeof document == 'object') {
|
|
391
391
|
document.addEventListener('DOMContentLoaded', () => {
|
|
@@ -4926,8 +4926,8 @@ struct Q5 {
|
|
|
4926
4926
|
frameLayout,
|
|
4927
4927
|
frameSampler,
|
|
4928
4928
|
frameBindGroup,
|
|
4929
|
-
colorIndex =
|
|
4930
|
-
colorStackIndex =
|
|
4929
|
+
colorIndex = 2,
|
|
4930
|
+
colorStackIndex = 12,
|
|
4931
4931
|
prevFramePL = 0,
|
|
4932
4932
|
framePL = 0;
|
|
4933
4933
|
|
|
@@ -4945,6 +4945,7 @@ struct Q5 {
|
|
|
4945
4945
|
|
|
4946
4946
|
// prettier-ignore
|
|
4947
4947
|
colorStack.set([
|
|
4948
|
+
0, 0, 0, 0, // transparent
|
|
4948
4949
|
0, 0, 0, 1, // black
|
|
4949
4950
|
1, 1, 1, 1 // white
|
|
4950
4951
|
]);
|
|
@@ -5166,12 +5167,13 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
5166
5167
|
doStroke = true,
|
|
5167
5168
|
fillSet = false,
|
|
5168
5169
|
strokeSet = false,
|
|
5169
|
-
strokeIdx =
|
|
5170
|
-
fillIdx =
|
|
5171
|
-
tintIdx =
|
|
5170
|
+
strokeIdx = 1,
|
|
5171
|
+
fillIdx = 2,
|
|
5172
|
+
tintIdx = 2,
|
|
5172
5173
|
globalAlpha = 1,
|
|
5173
5174
|
sw = 1, // stroke weight
|
|
5174
|
-
hsw = 0.5, // half the stroke weight
|
|
5175
|
+
hsw = 0.5, // half of the stroke weight
|
|
5176
|
+
qsw = 0.25, // quarter of the stroke weight
|
|
5175
5177
|
scaledHSW = 0.5;
|
|
5176
5178
|
|
|
5177
5179
|
$.fill = (r, g, b, a) => {
|
|
@@ -5202,6 +5204,7 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
5202
5204
|
v = Math.abs(v);
|
|
5203
5205
|
sw = v;
|
|
5204
5206
|
hsw = v / 2;
|
|
5207
|
+
qsw = v / 4;
|
|
5205
5208
|
scaledHSW = hsw * _scale;
|
|
5206
5209
|
};
|
|
5207
5210
|
|
|
@@ -5560,7 +5563,11 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
5560
5563
|
$.pop();
|
|
5561
5564
|
} else {
|
|
5562
5565
|
addColor(r, g, b, a);
|
|
5563
|
-
|
|
5566
|
+
let lx = -c.hw,
|
|
5567
|
+
rx = c.hw,
|
|
5568
|
+
ty = -c.hh,
|
|
5569
|
+
by = c.hh;
|
|
5570
|
+
addQuad(lx, ty, rx, ty, rx, by, lx, by, colorIndex, 0);
|
|
5564
5571
|
}
|
|
5565
5572
|
};
|
|
5566
5573
|
|
|
@@ -5695,6 +5702,8 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
5695
5702
|
}
|
|
5696
5703
|
}
|
|
5697
5704
|
|
|
5705
|
+
// prepare to render text
|
|
5706
|
+
|
|
5698
5707
|
if (charStack.length) {
|
|
5699
5708
|
// calculate total buffer size for text data
|
|
5700
5709
|
let totalTextSize = 0;
|
|
@@ -5741,9 +5750,33 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
5741
5750
|
});
|
|
5742
5751
|
}
|
|
5743
5752
|
|
|
5753
|
+
// prepare to render rectangles
|
|
5754
|
+
|
|
5755
|
+
// prettier-ignore
|
|
5756
|
+
Q5.device.queue.writeBuffer(
|
|
5757
|
+
rectBuffer,
|
|
5758
|
+
0,
|
|
5759
|
+
rectStack.buffer,
|
|
5760
|
+
rectStack.byteOffset,
|
|
5761
|
+
rectStackIdx * 4
|
|
5762
|
+
);
|
|
5763
|
+
|
|
5764
|
+
// prepare to render ellipses
|
|
5765
|
+
|
|
5766
|
+
// prettier-ignore
|
|
5767
|
+
Q5.device.queue.writeBuffer(
|
|
5768
|
+
ellipseBuffer,
|
|
5769
|
+
0,
|
|
5770
|
+
ellipseStack.buffer,
|
|
5771
|
+
ellipseStack.byteOffset,
|
|
5772
|
+
ellipseStackIdx * 4
|
|
5773
|
+
);
|
|
5774
|
+
|
|
5744
5775
|
let drawVertOffset = 0,
|
|
5745
5776
|
imageVertOffset = 0,
|
|
5746
5777
|
textCharOffset = 0,
|
|
5778
|
+
rectIdx = 0,
|
|
5779
|
+
ellipseIdx = 0,
|
|
5747
5780
|
curPipelineIndex = -1;
|
|
5748
5781
|
|
|
5749
5782
|
for (let i = 0; i < drawStack.length; i += 2) {
|
|
@@ -5762,9 +5795,25 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
5762
5795
|
|
|
5763
5796
|
curPipelineIndex = drawStack[i];
|
|
5764
5797
|
pass.setPipeline($._pipelines[curPipelineIndex]);
|
|
5798
|
+
|
|
5799
|
+
if (curPipelineIndex == 5) {
|
|
5800
|
+
pass.setIndexBuffer(rectIndexBuffer, 'uint16');
|
|
5801
|
+
pass.setBindGroup(1, rectBindGroup);
|
|
5802
|
+
} else if (curPipelineIndex == 6) {
|
|
5803
|
+
pass.setIndexBuffer(ellipseIndexBuffer, 'uint16');
|
|
5804
|
+
pass.setBindGroup(1, ellipseBindGroup);
|
|
5805
|
+
}
|
|
5765
5806
|
}
|
|
5766
5807
|
|
|
5767
|
-
if (curPipelineIndex ==
|
|
5808
|
+
if (curPipelineIndex == 6) {
|
|
5809
|
+
// draw an ellipse
|
|
5810
|
+
pass.drawIndexed(18, v, 0, 0, ellipseIdx);
|
|
5811
|
+
ellipseIdx += v;
|
|
5812
|
+
} else if (curPipelineIndex == 5) {
|
|
5813
|
+
// draw a rectangle
|
|
5814
|
+
pass.drawIndexed(6, v, 0, 0, rectIdx);
|
|
5815
|
+
rectIdx += v;
|
|
5816
|
+
} else if (curPipelineIndex == 4 || curPipelineIndex >= 4000) {
|
|
5768
5817
|
// draw text
|
|
5769
5818
|
let o = drawStack[i + 2];
|
|
5770
5819
|
pass.setBindGroup(1, fontsArr[o].bindGroup);
|
|
@@ -5826,8 +5875,8 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
5826
5875
|
|
|
5827
5876
|
// clear the stacks for the next frame
|
|
5828
5877
|
drawStack.splice(0, drawStack.length);
|
|
5829
|
-
colorIndex =
|
|
5830
|
-
colorStackIndex =
|
|
5878
|
+
colorIndex = 2;
|
|
5879
|
+
colorStackIndex = 12;
|
|
5831
5880
|
matrices = [matrices[0]];
|
|
5832
5881
|
matricesIdxStack = [];
|
|
5833
5882
|
|
|
@@ -5841,6 +5890,10 @@ fn fragMain(f: FragParams ) -> @location(0) vec4f {
|
|
|
5841
5890
|
vidFrames = 0;
|
|
5842
5891
|
charStack = [];
|
|
5843
5892
|
textStack = [];
|
|
5893
|
+
rectStack = new Float32Array(Q5.MAX_RECTS * 16);
|
|
5894
|
+
rectStackIdx = 0;
|
|
5895
|
+
ellipseStack = new Float32Array(Q5.MAX_ELLIPSES * 16);
|
|
5896
|
+
ellipseStackIdx = 0;
|
|
5844
5897
|
|
|
5845
5898
|
// destroy buffers
|
|
5846
5899
|
Q5.device.queue.onSubmittedWorkDone().then(() => {
|
|
@@ -5903,8 +5956,7 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
5903
5956
|
let shapesVertStack = new Float32Array($._isGraphics ? 1000 : 1e7),
|
|
5904
5957
|
shapesVertIdx = 0;
|
|
5905
5958
|
const TAU = Math.PI * 2,
|
|
5906
|
-
HALF_PI = Math.PI / 2
|
|
5907
|
-
THREE_HALF_PI = Math.PI * 1.5;
|
|
5959
|
+
HALF_PI = Math.PI / 2;
|
|
5908
5960
|
|
|
5909
5961
|
let shapesVertBuffLayout = {
|
|
5910
5962
|
arrayStride: 16, // 4 floats * 4 bytes
|
|
@@ -5915,14 +5967,14 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
5915
5967
|
]
|
|
5916
5968
|
};
|
|
5917
5969
|
|
|
5918
|
-
let
|
|
5970
|
+
let shapesPipelineLayout = Q5.device.createPipelineLayout({
|
|
5919
5971
|
label: 'shapesPipelineLayout',
|
|
5920
5972
|
bindGroupLayouts: $._bindGroupLayouts
|
|
5921
5973
|
});
|
|
5922
5974
|
|
|
5923
5975
|
$._pipelineConfigs[1] = {
|
|
5924
5976
|
label: 'shapesPipeline',
|
|
5925
|
-
layout:
|
|
5977
|
+
layout: shapesPipelineLayout,
|
|
5926
5978
|
vertex: {
|
|
5927
5979
|
module: shapesShader,
|
|
5928
5980
|
entryPoint: 'vertexMain',
|
|
@@ -5949,344 +6001,6 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
5949
6001
|
shapesVertIdx = i;
|
|
5950
6002
|
};
|
|
5951
6003
|
|
|
5952
|
-
const addRect = (x1, y1, x2, y2, x3, y3, x4, y4, ci, ti) => {
|
|
5953
|
-
let v = shapesVertStack,
|
|
5954
|
-
i = shapesVertIdx;
|
|
5955
|
-
|
|
5956
|
-
v[i++] = x1;
|
|
5957
|
-
v[i++] = y1;
|
|
5958
|
-
v[i++] = ci;
|
|
5959
|
-
v[i++] = ti;
|
|
5960
|
-
|
|
5961
|
-
v[i++] = x2;
|
|
5962
|
-
v[i++] = y2;
|
|
5963
|
-
v[i++] = ci;
|
|
5964
|
-
v[i++] = ti;
|
|
5965
|
-
|
|
5966
|
-
v[i++] = x4;
|
|
5967
|
-
v[i++] = y4;
|
|
5968
|
-
v[i++] = ci;
|
|
5969
|
-
v[i++] = ti;
|
|
5970
|
-
|
|
5971
|
-
v[i++] = x3;
|
|
5972
|
-
v[i++] = y3;
|
|
5973
|
-
v[i++] = ci;
|
|
5974
|
-
v[i++] = ti;
|
|
5975
|
-
|
|
5976
|
-
shapesVertIdx = i;
|
|
5977
|
-
drawStack.push(shapesPL, 4);
|
|
5978
|
-
};
|
|
5979
|
-
|
|
5980
|
-
const addArc = (x, y, a, b, startAngle, endAngle, n, ci, ti) => {
|
|
5981
|
-
let angleRange = endAngle - startAngle;
|
|
5982
|
-
let angleIncrement = angleRange / n;
|
|
5983
|
-
let t = startAngle;
|
|
5984
|
-
|
|
5985
|
-
let v = shapesVertStack,
|
|
5986
|
-
i = shapesVertIdx;
|
|
5987
|
-
|
|
5988
|
-
for (let j = 0; j <= n; j++) {
|
|
5989
|
-
// add center vertex
|
|
5990
|
-
v[i++] = x;
|
|
5991
|
-
v[i++] = y;
|
|
5992
|
-
v[i++] = ci;
|
|
5993
|
-
v[i++] = ti;
|
|
5994
|
-
|
|
5995
|
-
// calculate perimeter vertex
|
|
5996
|
-
let vx = x + a * Math.cos(t);
|
|
5997
|
-
let vy = y + b * Math.sin(t);
|
|
5998
|
-
|
|
5999
|
-
// add perimeter vertex
|
|
6000
|
-
v[i++] = vx;
|
|
6001
|
-
v[i++] = vy;
|
|
6002
|
-
v[i++] = ci;
|
|
6003
|
-
v[i++] = ti;
|
|
6004
|
-
|
|
6005
|
-
t += angleIncrement;
|
|
6006
|
-
}
|
|
6007
|
-
|
|
6008
|
-
shapesVertIdx = i;
|
|
6009
|
-
drawStack.push(shapesPL, (n + 1) * 2);
|
|
6010
|
-
};
|
|
6011
|
-
|
|
6012
|
-
const addArcStroke = (x, y, outerA, outerB, innerA, innerB, startAngle, endAngle, n, ci, ti) => {
|
|
6013
|
-
let angleRange = endAngle - startAngle;
|
|
6014
|
-
let angleIncrement = angleRange / n;
|
|
6015
|
-
let t = startAngle;
|
|
6016
|
-
|
|
6017
|
-
let v = shapesVertStack,
|
|
6018
|
-
i = shapesVertIdx;
|
|
6019
|
-
|
|
6020
|
-
for (let j = 0; j <= n; j++) {
|
|
6021
|
-
// Outer vertex
|
|
6022
|
-
let vxOuter = x + outerA * Math.cos(t);
|
|
6023
|
-
let vyOuter = y + outerB * Math.sin(t);
|
|
6024
|
-
|
|
6025
|
-
// Inner vertex
|
|
6026
|
-
let vxInner = x + innerA * Math.cos(t);
|
|
6027
|
-
let vyInner = y + innerB * Math.sin(t);
|
|
6028
|
-
|
|
6029
|
-
// Add vertices for triangle strip
|
|
6030
|
-
v[i++] = vxOuter;
|
|
6031
|
-
v[i++] = vyOuter;
|
|
6032
|
-
v[i++] = ci;
|
|
6033
|
-
v[i++] = ti;
|
|
6034
|
-
|
|
6035
|
-
v[i++] = vxInner;
|
|
6036
|
-
v[i++] = vyInner;
|
|
6037
|
-
v[i++] = ci;
|
|
6038
|
-
v[i++] = ti;
|
|
6039
|
-
|
|
6040
|
-
t += angleIncrement;
|
|
6041
|
-
}
|
|
6042
|
-
|
|
6043
|
-
shapesVertIdx = i;
|
|
6044
|
-
drawStack.push(shapesPL, (n + 1) * 2);
|
|
6045
|
-
};
|
|
6046
|
-
|
|
6047
|
-
let _rectMode = 'corner';
|
|
6048
|
-
|
|
6049
|
-
$.rectMode = (x) => (_rectMode = x);
|
|
6050
|
-
|
|
6051
|
-
$.rect = (x, y, w, h, rr = 0) => {
|
|
6052
|
-
h ??= w;
|
|
6053
|
-
let [l, r, t, b] = calcBox(x, y, w, h, _rectMode);
|
|
6054
|
-
let ci, ti;
|
|
6055
|
-
if (matrixDirty) saveMatrix();
|
|
6056
|
-
ti = matrixIdx;
|
|
6057
|
-
|
|
6058
|
-
if (!rr) {
|
|
6059
|
-
if (doFill) {
|
|
6060
|
-
ci = fillIdx;
|
|
6061
|
-
addRect(l, t, r, t, r, b, l, b, ci, ti);
|
|
6062
|
-
}
|
|
6063
|
-
|
|
6064
|
-
if (doStroke) {
|
|
6065
|
-
ci = strokeIdx;
|
|
6066
|
-
|
|
6067
|
-
// Calculate stroke positions
|
|
6068
|
-
let lsw = l - hsw,
|
|
6069
|
-
rsw = r + hsw,
|
|
6070
|
-
tsw = t + hsw,
|
|
6071
|
-
bsw = b - hsw,
|
|
6072
|
-
lpsw = l + hsw,
|
|
6073
|
-
rpsw = r - hsw,
|
|
6074
|
-
tpsw = t - hsw,
|
|
6075
|
-
bpsw = b + hsw;
|
|
6076
|
-
|
|
6077
|
-
addRect(lsw, tpsw, rsw, tpsw, rsw, tsw, lsw, tsw, ci, ti); // Top
|
|
6078
|
-
addRect(lsw, bsw, rsw, bsw, rsw, bpsw, lsw, bpsw, ci, ti); // Bottom
|
|
6079
|
-
|
|
6080
|
-
// Adjust side strokes to avoid overlapping corners
|
|
6081
|
-
tsw = t - hsw;
|
|
6082
|
-
bsw = b + hsw;
|
|
6083
|
-
|
|
6084
|
-
addRect(lsw, tsw, lpsw, tsw, lpsw, bsw, lsw, bsw, ci, ti); // Left
|
|
6085
|
-
addRect(rpsw, tsw, rsw, tsw, rsw, bsw, rpsw, bsw, ci, ti); // Right
|
|
6086
|
-
}
|
|
6087
|
-
return;
|
|
6088
|
-
}
|
|
6089
|
-
|
|
6090
|
-
l += rr;
|
|
6091
|
-
r -= rr;
|
|
6092
|
-
t += rr;
|
|
6093
|
-
b -= rr;
|
|
6094
|
-
|
|
6095
|
-
// Clamp radius
|
|
6096
|
-
rr = Math.min(rr, Math.min(w, h) / 2);
|
|
6097
|
-
|
|
6098
|
-
let n = getArcSegments(rr * _scale);
|
|
6099
|
-
|
|
6100
|
-
let trr = t - rr,
|
|
6101
|
-
brr = b + rr,
|
|
6102
|
-
lrr = l - rr,
|
|
6103
|
-
rrr = r + rr;
|
|
6104
|
-
|
|
6105
|
-
if (doFill) {
|
|
6106
|
-
ci = fillIdx;
|
|
6107
|
-
// Corner arcs
|
|
6108
|
-
addArc(l, t, rr, -rr, HALF_PI, Math.PI, n, ci, ti); // top-left
|
|
6109
|
-
addArc(r, t, rr, -rr, HALF_PI, 0, n, ci, ti); // top-right
|
|
6110
|
-
addArc(r, b, rr, -rr, 0, -HALF_PI, n, ci, ti); // bottom-right
|
|
6111
|
-
addArc(l, b, rr, -rr, -HALF_PI, -Math.PI, n, ci, ti); // bottom-left
|
|
6112
|
-
|
|
6113
|
-
addRect(l, trr, r, trr, r, brr, l, brr, ci, ti); // center
|
|
6114
|
-
addRect(l, t, lrr, t, lrr, b, l, b, ci, ti); // Left
|
|
6115
|
-
addRect(rrr, t, r, t, r, b, rrr, b, ci, ti); // Right
|
|
6116
|
-
}
|
|
6117
|
-
|
|
6118
|
-
if (doStroke) {
|
|
6119
|
-
ci = strokeIdx;
|
|
6120
|
-
|
|
6121
|
-
let outerA = rr + hsw,
|
|
6122
|
-
outerB = rr + hsw,
|
|
6123
|
-
innerA = rr - hsw,
|
|
6124
|
-
innerB = rr - hsw;
|
|
6125
|
-
|
|
6126
|
-
// Corner arc strokes
|
|
6127
|
-
addArcStroke(l, t, outerA, -outerB, innerA, -innerB, Math.PI, HALF_PI, n, ci, ti); // top-left
|
|
6128
|
-
addArcStroke(r, t, outerA, -outerB, innerA, -innerB, HALF_PI, 0, n, ci, ti); // top-right
|
|
6129
|
-
addArcStroke(r, b, outerA, -outerB, innerA, -innerB, 0, -HALF_PI, n, ci, ti); // bottom-right
|
|
6130
|
-
addArcStroke(l, b, outerA, -outerB, innerA, -innerB, -HALF_PI, -Math.PI, n, ci, ti); // bottom-left
|
|
6131
|
-
|
|
6132
|
-
let lrrMin = lrr - hsw,
|
|
6133
|
-
lrrMax = lrr + hsw,
|
|
6134
|
-
rrrMin = rrr - hsw,
|
|
6135
|
-
rrrMax = rrr + hsw,
|
|
6136
|
-
trrMin = trr - hsw,
|
|
6137
|
-
trrMax = trr + hsw,
|
|
6138
|
-
brrMin = brr - hsw,
|
|
6139
|
-
brrMax = brr + hsw;
|
|
6140
|
-
|
|
6141
|
-
// Side strokes - positioned outside
|
|
6142
|
-
addRect(lrrMin, t, lrrMax, t, lrrMax, b, lrrMin, b, ci, ti); // Left
|
|
6143
|
-
addRect(rrrMin, t, rrrMax, t, rrrMax, b, rrrMin, b, ci, ti); // Right
|
|
6144
|
-
addRect(l, trrMin, r, trrMin, r, trrMax, l, trrMax, ci, ti); // Top
|
|
6145
|
-
addRect(l, brrMin, r, brrMin, r, brrMax, l, brrMax, ci, ti); // Bottom
|
|
6146
|
-
}
|
|
6147
|
-
};
|
|
6148
|
-
|
|
6149
|
-
$.square = (x, y, s) => $.rect(x, y, s, s);
|
|
6150
|
-
|
|
6151
|
-
$.plane = (x, y, w, h) => {
|
|
6152
|
-
h ??= w;
|
|
6153
|
-
let [l, r, t, b] = calcBox(x, y, w, h, 'center');
|
|
6154
|
-
if (matrixDirty) saveMatrix();
|
|
6155
|
-
addRect(l, t, r, t, r, b, l, b, fillIdx, matrixIdx);
|
|
6156
|
-
};
|
|
6157
|
-
|
|
6158
|
-
// prettier-ignore
|
|
6159
|
-
const getArcSegments = (d) =>
|
|
6160
|
-
d < 2 ? 6 :
|
|
6161
|
-
d < 3 ? 8 :
|
|
6162
|
-
d < 5 ? 10 :
|
|
6163
|
-
d < 8 ? 12 :
|
|
6164
|
-
d < 10 ? 14 :
|
|
6165
|
-
d < 11 ? 16 :
|
|
6166
|
-
d < 12 ? 18 :
|
|
6167
|
-
d < 14 ? 20 :
|
|
6168
|
-
d < 17 ? 22 :
|
|
6169
|
-
d < 21 ? 24 :
|
|
6170
|
-
d < 24 ? 26 :
|
|
6171
|
-
d < 28 ? 28 :
|
|
6172
|
-
d < 32 ? 30 :
|
|
6173
|
-
d < 36 ? 32 :
|
|
6174
|
-
d < 42 ? 34 :
|
|
6175
|
-
d < 48 ? 36 :
|
|
6176
|
-
d < 49 ? 38 :
|
|
6177
|
-
d < 57 ? 40 :
|
|
6178
|
-
d < 75 ? 44 :
|
|
6179
|
-
d < 100 ? 48 :
|
|
6180
|
-
d < 130 ? 52 :
|
|
6181
|
-
d < 176 ? 56 :
|
|
6182
|
-
d < 230 ? 60 :
|
|
6183
|
-
d < 292 ? 64 :
|
|
6184
|
-
d < 600 ? 70 :
|
|
6185
|
-
d < 900 ? 80 :
|
|
6186
|
-
d < 1200 ? 90 :
|
|
6187
|
-
100;
|
|
6188
|
-
|
|
6189
|
-
let _ellipseMode = 'center';
|
|
6190
|
-
|
|
6191
|
-
$.ellipseMode = (x) => (_ellipseMode = x);
|
|
6192
|
-
|
|
6193
|
-
function applyEllipseMode(x, y, w, h) {
|
|
6194
|
-
h ??= w;
|
|
6195
|
-
let a, b;
|
|
6196
|
-
if (_ellipseMode == $.CENTER) {
|
|
6197
|
-
a = w / 2;
|
|
6198
|
-
b = h / 2;
|
|
6199
|
-
} else if (_ellipseMode == $.RADIUS) {
|
|
6200
|
-
a = w;
|
|
6201
|
-
b = h;
|
|
6202
|
-
} else if (_ellipseMode == $.CORNER) {
|
|
6203
|
-
x += w / 2;
|
|
6204
|
-
y += h / 2;
|
|
6205
|
-
a = w / 2;
|
|
6206
|
-
b = h / 2;
|
|
6207
|
-
} else if (_ellipseMode == $.CORNERS) {
|
|
6208
|
-
x = (x + w) / 2;
|
|
6209
|
-
y = (y + h) / 2;
|
|
6210
|
-
a = (w - x) / 2;
|
|
6211
|
-
b = (h - y) / 2;
|
|
6212
|
-
}
|
|
6213
|
-
return [x, y, a, b];
|
|
6214
|
-
}
|
|
6215
|
-
|
|
6216
|
-
$.ellipse = (x, y, w, h) => {
|
|
6217
|
-
let a, b;
|
|
6218
|
-
[x, y, a, b] = applyEllipseMode(x, y, w, h);
|
|
6219
|
-
|
|
6220
|
-
let n = getArcSegments(Math.max(Math.abs(a), Math.abs(b)) * _scale);
|
|
6221
|
-
|
|
6222
|
-
if (matrixDirty) saveMatrix();
|
|
6223
|
-
let ti = matrixIdx;
|
|
6224
|
-
|
|
6225
|
-
if (doFill) {
|
|
6226
|
-
addArc(x, y, a, b, 0, TAU, n, fillIdx, ti);
|
|
6227
|
-
}
|
|
6228
|
-
if (doStroke) {
|
|
6229
|
-
// Draw the stroke as a ring using triangle strips
|
|
6230
|
-
addArcStroke(x, y, a + hsw, b + hsw, a - hsw, b - hsw, 0, TAU, n, strokeIdx, ti);
|
|
6231
|
-
}
|
|
6232
|
-
};
|
|
6233
|
-
|
|
6234
|
-
$.circle = (x, y, d) => $.ellipse(x, y, d, d);
|
|
6235
|
-
|
|
6236
|
-
$.arc = (x, y, w, h, start, stop) => {
|
|
6237
|
-
if (start === stop) return $.ellipse(x, y, w, h);
|
|
6238
|
-
|
|
6239
|
-
// Convert angles if needed
|
|
6240
|
-
if ($._angleMode) {
|
|
6241
|
-
start = $.radians(start);
|
|
6242
|
-
stop = $.radians(stop);
|
|
6243
|
-
}
|
|
6244
|
-
|
|
6245
|
-
// Normalize angles
|
|
6246
|
-
start %= TAU;
|
|
6247
|
-
stop %= TAU;
|
|
6248
|
-
if (start < 0) start += TAU;
|
|
6249
|
-
if (stop < 0) stop += TAU;
|
|
6250
|
-
if (start > stop) stop += TAU;
|
|
6251
|
-
if (start == stop) return $.ellipse(x, y, w, h);
|
|
6252
|
-
|
|
6253
|
-
// Calculate position based on ellipseMode
|
|
6254
|
-
let a, b;
|
|
6255
|
-
[x, y, a, b] = applyEllipseMode(x, y, w, h);
|
|
6256
|
-
|
|
6257
|
-
if (matrixDirty) saveMatrix();
|
|
6258
|
-
let ti = matrixIdx;
|
|
6259
|
-
let n = getArcSegments(Math.max(Math.abs(a), Math.abs(b)) * _scale);
|
|
6260
|
-
|
|
6261
|
-
// Draw fill
|
|
6262
|
-
if (doFill) {
|
|
6263
|
-
addArc(x, y, a, b, start, stop, n, fillIdx, ti);
|
|
6264
|
-
}
|
|
6265
|
-
|
|
6266
|
-
// Draw stroke
|
|
6267
|
-
if (doStroke) {
|
|
6268
|
-
addArcStroke(x, y, a + hsw, b + hsw, a - hsw, b - hsw, start, stop, n, strokeIdx, ti);
|
|
6269
|
-
if (_strokeCap == 'round') {
|
|
6270
|
-
addArc(x + a * Math.cos(start), y + b * Math.sin(start), hsw, hsw, 0, TAU, n, strokeIdx, ti);
|
|
6271
|
-
addArc(x + a * Math.cos(stop), y + b * Math.sin(stop), hsw, hsw, 0, TAU, n, strokeIdx, ti);
|
|
6272
|
-
}
|
|
6273
|
-
}
|
|
6274
|
-
};
|
|
6275
|
-
|
|
6276
|
-
$.point = (x, y) => {
|
|
6277
|
-
if (matrixDirty) saveMatrix();
|
|
6278
|
-
let ti = matrixIdx,
|
|
6279
|
-
ci = strokeIdx;
|
|
6280
|
-
|
|
6281
|
-
if (scaledHSW < 1) {
|
|
6282
|
-
let [l, r, t, b] = calcBox(x, y, sw, sw, 'corner');
|
|
6283
|
-
addRect(l, t, r, t, r, b, l, b, ci, ti);
|
|
6284
|
-
} else {
|
|
6285
|
-
let n = getArcSegments(scaledHSW);
|
|
6286
|
-
addArc(x, y, hsw, hsw, 0, TAU, n, ci, ti);
|
|
6287
|
-
}
|
|
6288
|
-
};
|
|
6289
|
-
|
|
6290
6004
|
let _strokeCap = 'round',
|
|
6291
6005
|
_strokeJoin = 'round';
|
|
6292
6006
|
|
|
@@ -6297,29 +6011,6 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
6297
6011
|
_strokeJoin = 'none';
|
|
6298
6012
|
};
|
|
6299
6013
|
|
|
6300
|
-
$.line = (x1, y1, x2, y2) => {
|
|
6301
|
-
if (matrixDirty) saveMatrix();
|
|
6302
|
-
let ti = matrixIdx,
|
|
6303
|
-
ci = strokeIdx;
|
|
6304
|
-
|
|
6305
|
-
// calculate the direction vector and length
|
|
6306
|
-
let dx = x2 - x1,
|
|
6307
|
-
dy = y2 - y1,
|
|
6308
|
-
length = Math.hypot(dx, dy);
|
|
6309
|
-
|
|
6310
|
-
// calculate the perpendicular vector for line thickness
|
|
6311
|
-
let px = -(dy / length) * hsw,
|
|
6312
|
-
py = (dx / length) * hsw;
|
|
6313
|
-
|
|
6314
|
-
addRect(x1 + px, y1 + py, x1 - px, y1 - py, x2 - px, y2 - py, x2 + px, y2 + py, ci, ti);
|
|
6315
|
-
|
|
6316
|
-
if (scaledHSW > 1 && _strokeCap != 'square') {
|
|
6317
|
-
let n = getArcSegments(scaledHSW);
|
|
6318
|
-
addArc(x1, y1, hsw, hsw, 0, TAU, n, ci, ti);
|
|
6319
|
-
addArc(x2, y2, hsw, hsw, 0, TAU, n, ci, ti);
|
|
6320
|
-
}
|
|
6321
|
-
};
|
|
6322
|
-
|
|
6323
6014
|
let curveSegments = 20;
|
|
6324
6015
|
$.curveDetail = (v) => (curveSegments = v);
|
|
6325
6016
|
|
|
@@ -6482,23 +6173,18 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
6482
6173
|
}
|
|
6483
6174
|
|
|
6484
6175
|
if (doStroke) {
|
|
6485
|
-
let n = getArcSegments(scaledHSW),
|
|
6486
|
-
ti = matrixIdx,
|
|
6487
|
-
ogStrokeCap = _strokeCap;
|
|
6488
|
-
_strokeCap = 'square';
|
|
6489
6176
|
// draw lines between vertices
|
|
6490
6177
|
for (let i = 0; i < shapeVertCount - 1; i++) {
|
|
6491
6178
|
let v1 = i * 4;
|
|
6492
6179
|
let v2 = (i + 1) * 4;
|
|
6493
6180
|
$.line(sv[v1], sv[v1 + 1], sv[v2], sv[v2 + 1]);
|
|
6494
6181
|
|
|
6495
|
-
|
|
6182
|
+
// addEllipse(sv[v1], sv[v1 + 1], qsw, qsw, 0, TAU, hsw, 0);
|
|
6496
6183
|
}
|
|
6497
6184
|
let v1 = (shapeVertCount - 1) * 4;
|
|
6498
6185
|
let v2 = 0;
|
|
6499
6186
|
if (close) $.line(sv[v1], sv[v1 + 1], sv[v2], sv[v2 + 1]);
|
|
6500
|
-
|
|
6501
|
-
_strokeCap = ogStrokeCap;
|
|
6187
|
+
// addEllipse(sv[v1], sv[v1 + 1], qsw, qsw, 0, TAU, hsw, 0);
|
|
6502
6188
|
}
|
|
6503
6189
|
|
|
6504
6190
|
// reset for the next shape
|
|
@@ -6540,6 +6226,608 @@ fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
|
6540
6226
|
$.endShape(true);
|
|
6541
6227
|
};
|
|
6542
6228
|
|
|
6229
|
+
function addQuad(x1, y1, x2, y2, x3, y3, x4, y4, ci, ti) {
|
|
6230
|
+
addVert(x1, y1, ci, ti); // v0
|
|
6231
|
+
addVert(x2, y2, ci, ti); // v1
|
|
6232
|
+
addVert(x4, y4, ci, ti); // v3
|
|
6233
|
+
addVert(x3, y3, ci, ti); // v2
|
|
6234
|
+
drawStack.push(shapesPL, 4);
|
|
6235
|
+
}
|
|
6236
|
+
|
|
6237
|
+
$.plane = (x, y, w, h) => {
|
|
6238
|
+
h ??= w;
|
|
6239
|
+
let [l, r, t, b] = calcBox(x, y, w, h, 'center');
|
|
6240
|
+
if (matrixDirty) saveMatrix();
|
|
6241
|
+
addQuad(l, t, r, t, r, b, l, b, fillIdx, matrixIdx);
|
|
6242
|
+
};
|
|
6243
|
+
|
|
6244
|
+
/* RECT */
|
|
6245
|
+
|
|
6246
|
+
let rectPL = 5;
|
|
6247
|
+
|
|
6248
|
+
$._rectShaderCode =
|
|
6249
|
+
$._baseShaderCode +
|
|
6250
|
+
/* wgsl */ `
|
|
6251
|
+
struct Rect {
|
|
6252
|
+
center: vec2f,
|
|
6253
|
+
extents: vec2f,
|
|
6254
|
+
roundedRadius: f32,
|
|
6255
|
+
strokeWeight: f32,
|
|
6256
|
+
fillIndex: f32,
|
|
6257
|
+
strokeIndex: f32,
|
|
6258
|
+
matrixIndex: f32,
|
|
6259
|
+
padding0: f32, // can't use vec3f for alignment
|
|
6260
|
+
padding1: vec2f,
|
|
6261
|
+
padding2: vec4f
|
|
6262
|
+
};
|
|
6263
|
+
|
|
6264
|
+
struct VertexParams {
|
|
6265
|
+
@builtin(vertex_index) vertIndex: u32,
|
|
6266
|
+
@builtin(instance_index) instIndex: u32
|
|
6267
|
+
};
|
|
6268
|
+
|
|
6269
|
+
struct FragParams {
|
|
6270
|
+
@builtin(position) position: vec4f,
|
|
6271
|
+
@location(0) local: vec2f,
|
|
6272
|
+
@location(1) extents: vec2f,
|
|
6273
|
+
@location(2) roundedRadius: f32,
|
|
6274
|
+
@location(3) strokeWeight: f32,
|
|
6275
|
+
@location(4) fill: vec4f,
|
|
6276
|
+
@location(5) stroke: vec4f,
|
|
6277
|
+
@location(6) blend: vec4f
|
|
6278
|
+
};
|
|
6279
|
+
|
|
6280
|
+
@group(0) @binding(0) var<uniform> q: Q5;
|
|
6281
|
+
@group(0) @binding(1) var<storage> transforms: array<mat4x4<f32>>;
|
|
6282
|
+
@group(0) @binding(2) var<storage> colors : array<vec4f>;
|
|
6283
|
+
|
|
6284
|
+
@group(1) @binding(0) var<storage, read> rects: array<Rect>;
|
|
6285
|
+
|
|
6286
|
+
const quad = array(
|
|
6287
|
+
vec2f(-1.0, -1.0),
|
|
6288
|
+
vec2f( 1.0, -1.0),
|
|
6289
|
+
vec2f(-1.0, 1.0),
|
|
6290
|
+
vec2f( 1.0, 1.0)
|
|
6291
|
+
);
|
|
6292
|
+
const transparent = vec4f(0.0);
|
|
6293
|
+
|
|
6294
|
+
fn transformVertex(pos: vec2f, matrixIndex: f32) -> vec4f {
|
|
6295
|
+
var vert = vec4f(pos, 0.0, 1.0);
|
|
6296
|
+
vert = transforms[i32(matrixIndex)] * vert;
|
|
6297
|
+
vert.x /= q.halfWidth;
|
|
6298
|
+
vert.y /= q.halfHeight;
|
|
6299
|
+
return vert;
|
|
6300
|
+
}
|
|
6301
|
+
|
|
6302
|
+
@vertex
|
|
6303
|
+
fn vertexMain(v: VertexParams) -> FragParams {
|
|
6304
|
+
let rect = rects[v.instIndex];
|
|
6305
|
+
|
|
6306
|
+
let halfStrokeSize = vec2f(rect.strokeWeight * 0.5);
|
|
6307
|
+
let quadSize = rect.extents + halfStrokeSize;
|
|
6308
|
+
let pos = (quad[v.vertIndex] * quadSize) + rect.center;
|
|
6309
|
+
|
|
6310
|
+
let local = pos - rect.center;
|
|
6311
|
+
|
|
6312
|
+
var f: FragParams;
|
|
6313
|
+
f.position = transformVertex(pos, rect.matrixIndex);
|
|
6314
|
+
|
|
6315
|
+
f.local = local;
|
|
6316
|
+
f.extents = rect.extents;
|
|
6317
|
+
f.roundedRadius = rect.roundedRadius;
|
|
6318
|
+
f.strokeWeight = rect.strokeWeight;
|
|
6319
|
+
|
|
6320
|
+
let fill = colors[i32(rect.fillIndex)];
|
|
6321
|
+
let stroke = colors[i32(rect.strokeIndex)];
|
|
6322
|
+
f.fill = fill;
|
|
6323
|
+
f.stroke = stroke;
|
|
6324
|
+
|
|
6325
|
+
// Source-over blend: stroke over fill (pre-multiplied alpha)
|
|
6326
|
+
if (fill.a != 0.0 && stroke.a != 1.0) {
|
|
6327
|
+
let outAlpha = stroke.a + fill.a * (1.0 - stroke.a);
|
|
6328
|
+
let outColor = stroke.rgb * stroke.a + fill.rgb * fill.a * (1.0 - stroke.a);
|
|
6329
|
+
f.blend = vec4f(outColor / max(outAlpha, 1e-5), outAlpha);
|
|
6330
|
+
}
|
|
6331
|
+
return f;
|
|
6332
|
+
}
|
|
6333
|
+
|
|
6334
|
+
fn sdRoundRect(p: vec2f, extents: vec2f, radius: f32) -> f32 {
|
|
6335
|
+
let q = abs(p) - extents + vec2f(radius);
|
|
6336
|
+
return length(max(q, vec2f(0.0))) - radius + min(max(q.x, q.y), 0.0);
|
|
6337
|
+
}
|
|
6338
|
+
|
|
6339
|
+
@fragment
|
|
6340
|
+
fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
6341
|
+
let dist = select(
|
|
6342
|
+
max(abs(f.local.x) - f.extents.x, abs(f.local.y) - f.extents.y), // sharp
|
|
6343
|
+
sdRoundRect(f.local, f.extents, f.roundedRadius), // rounded
|
|
6344
|
+
f.roundedRadius > 0.0
|
|
6345
|
+
);
|
|
6346
|
+
|
|
6347
|
+
// fill only
|
|
6348
|
+
if (f.fill.a != 0.0 && f.strokeWeight == 0.0) {
|
|
6349
|
+
if (dist <= 0.0) {
|
|
6350
|
+
return f.fill;
|
|
6351
|
+
}
|
|
6352
|
+
return transparent;
|
|
6353
|
+
}
|
|
6354
|
+
|
|
6355
|
+
let halfStroke = f.strokeWeight * 0.5;
|
|
6356
|
+
let inner = dist + halfStroke;
|
|
6357
|
+
|
|
6358
|
+
if (f.fill.a != 0.0) {
|
|
6359
|
+
if (inner <= 0.0) {
|
|
6360
|
+
return f.fill;
|
|
6361
|
+
}
|
|
6362
|
+
if (dist <= 0.0 && f.stroke.a != 1.0) {
|
|
6363
|
+
return f.blend;
|
|
6364
|
+
}
|
|
6365
|
+
}
|
|
6366
|
+
|
|
6367
|
+
let outer = dist - halfStroke;
|
|
6368
|
+
|
|
6369
|
+
if (outer <= 0.0 && inner >= 0.0) {
|
|
6370
|
+
return f.stroke;
|
|
6371
|
+
}
|
|
6372
|
+
|
|
6373
|
+
return transparent;
|
|
6374
|
+
}
|
|
6375
|
+
`;
|
|
6376
|
+
|
|
6377
|
+
let rectShader = Q5.device.createShaderModule({
|
|
6378
|
+
label: 'rectShader',
|
|
6379
|
+
code: $._rectShaderCode
|
|
6380
|
+
});
|
|
6381
|
+
|
|
6382
|
+
let rectIndices = new Uint16Array([0, 1, 2, 2, 1, 3]);
|
|
6383
|
+
|
|
6384
|
+
let rectIndexBuffer = Q5.device.createBuffer({
|
|
6385
|
+
size: rectIndices.byteLength,
|
|
6386
|
+
usage: GPUBufferUsage.INDEX,
|
|
6387
|
+
mappedAtCreation: true
|
|
6388
|
+
});
|
|
6389
|
+
new Uint16Array(rectIndexBuffer.getMappedRange()).set(rectIndices);
|
|
6390
|
+
rectIndexBuffer.unmap();
|
|
6391
|
+
|
|
6392
|
+
let rectBindGroupLayout = Q5.device.createBindGroupLayout({
|
|
6393
|
+
entries: [
|
|
6394
|
+
{
|
|
6395
|
+
binding: 0,
|
|
6396
|
+
visibility: GPUShaderStage.VERTEX,
|
|
6397
|
+
buffer: { type: 'read-only-storage' }
|
|
6398
|
+
}
|
|
6399
|
+
]
|
|
6400
|
+
});
|
|
6401
|
+
|
|
6402
|
+
let rectPipelineLayout = Q5.device.createPipelineLayout({
|
|
6403
|
+
label: 'rectPipelineLayout',
|
|
6404
|
+
bindGroupLayouts: [...$._bindGroupLayouts, rectBindGroupLayout]
|
|
6405
|
+
});
|
|
6406
|
+
|
|
6407
|
+
$._pipelineConfigs[5] = {
|
|
6408
|
+
label: 'rectPipeline',
|
|
6409
|
+
layout: rectPipelineLayout,
|
|
6410
|
+
vertex: {
|
|
6411
|
+
module: rectShader,
|
|
6412
|
+
entryPoint: 'vertexMain',
|
|
6413
|
+
buffers: []
|
|
6414
|
+
},
|
|
6415
|
+
fragment: {
|
|
6416
|
+
module: rectShader,
|
|
6417
|
+
entryPoint: 'fragMain',
|
|
6418
|
+
targets: [
|
|
6419
|
+
{
|
|
6420
|
+
format: 'bgra8unorm',
|
|
6421
|
+
blend: $.blendConfigs['source-over']
|
|
6422
|
+
}
|
|
6423
|
+
]
|
|
6424
|
+
},
|
|
6425
|
+
primitive: { topology: 'triangle-list' },
|
|
6426
|
+
multisample: { count: 4 }
|
|
6427
|
+
};
|
|
6428
|
+
|
|
6429
|
+
$._pipelines[5] = Q5.device.createRenderPipeline($._pipelineConfigs[5]);
|
|
6430
|
+
|
|
6431
|
+
let rectStack = new Float32Array(Q5.MAX_RECTS * 16);
|
|
6432
|
+
let rectStackIdx = 0;
|
|
6433
|
+
|
|
6434
|
+
let rectBuffer = Q5.device.createBuffer({
|
|
6435
|
+
size: rectStack.byteLength,
|
|
6436
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
6437
|
+
});
|
|
6438
|
+
|
|
6439
|
+
let rectBindGroup = Q5.device.createBindGroup({
|
|
6440
|
+
layout: rectBindGroupLayout,
|
|
6441
|
+
entries: [{ binding: 0, resource: { buffer: rectBuffer } }]
|
|
6442
|
+
});
|
|
6443
|
+
|
|
6444
|
+
function addRect(x, y, hw, hh, roundedRadius, strokeW, fillRect) {
|
|
6445
|
+
let s = rectStack,
|
|
6446
|
+
i = rectStackIdx;
|
|
6447
|
+
|
|
6448
|
+
s[i] = x;
|
|
6449
|
+
s[i + 1] = y;
|
|
6450
|
+
s[i + 2] = hw;
|
|
6451
|
+
s[i + 3] = hh;
|
|
6452
|
+
s[i + 4] = roundedRadius;
|
|
6453
|
+
s[i + 5] = strokeW;
|
|
6454
|
+
s[i + 6] = fillRect;
|
|
6455
|
+
s[i + 7] = strokeIdx;
|
|
6456
|
+
s[i + 8] = matrixIdx;
|
|
6457
|
+
|
|
6458
|
+
rectStackIdx += 16;
|
|
6459
|
+
drawStack.push(rectPL, 1);
|
|
6460
|
+
}
|
|
6461
|
+
|
|
6462
|
+
let _rectMode = 'corner';
|
|
6463
|
+
|
|
6464
|
+
$.rectMode = (x) => (_rectMode = x);
|
|
6465
|
+
|
|
6466
|
+
function applyRectMode(x, y, w, h) {
|
|
6467
|
+
let hw = w / 2,
|
|
6468
|
+
hh = h / 2;
|
|
6469
|
+
if (_rectMode != 'center') {
|
|
6470
|
+
if (_rectMode == 'corner') {
|
|
6471
|
+
x += hw;
|
|
6472
|
+
y += hh;
|
|
6473
|
+
} else if (_rectMode == 'radius') {
|
|
6474
|
+
hw = w;
|
|
6475
|
+
hh = h;
|
|
6476
|
+
} else if (_rectMode == 'corners') {
|
|
6477
|
+
hw = (x - w) / 2;
|
|
6478
|
+
hh = (y - h) / 2;
|
|
6479
|
+
x += hw;
|
|
6480
|
+
y += hh;
|
|
6481
|
+
}
|
|
6482
|
+
}
|
|
6483
|
+
return [x, y, hw, hh];
|
|
6484
|
+
}
|
|
6485
|
+
|
|
6486
|
+
$.rect = (x, y, w, h, rr = 0) => {
|
|
6487
|
+
if (matrixDirty) saveMatrix();
|
|
6488
|
+
|
|
6489
|
+
let hw, hh;
|
|
6490
|
+
[x, y, hw, hh] = applyRectMode(x, y, w, h);
|
|
6491
|
+
|
|
6492
|
+
addRect(x, y, hw, hh, rr, doStroke ? sw : 0, doFill ? fillIdx : 0);
|
|
6493
|
+
};
|
|
6494
|
+
|
|
6495
|
+
$.square = (x, y, s) => $.rect(x, y, s, s);
|
|
6496
|
+
|
|
6497
|
+
function addCapsule(x1, y1, x2, y2, r, strokeW, fillCapsule) {
|
|
6498
|
+
let dx = x2 - x1,
|
|
6499
|
+
dy = y2 - y1,
|
|
6500
|
+
len = Math.hypot(dx, dy);
|
|
6501
|
+
|
|
6502
|
+
if (len === 0) return;
|
|
6503
|
+
|
|
6504
|
+
let angle = Math.atan2(dy, dx),
|
|
6505
|
+
cx = (x1 + x2) / 2,
|
|
6506
|
+
cy = (y1 + y2) / 2;
|
|
6507
|
+
|
|
6508
|
+
if ($._angleMode) angle *= $._RADTODEG;
|
|
6509
|
+
|
|
6510
|
+
$.pushMatrix();
|
|
6511
|
+
$.translate(cx, cy);
|
|
6512
|
+
$.rotate(angle);
|
|
6513
|
+
|
|
6514
|
+
if (matrixDirty) saveMatrix();
|
|
6515
|
+
|
|
6516
|
+
addRect(0, 0, len / 2 + r, r, r, strokeW, fillCapsule);
|
|
6517
|
+
|
|
6518
|
+
$.popMatrix();
|
|
6519
|
+
}
|
|
6520
|
+
|
|
6521
|
+
$.capsule = (x1, y1, x2, y2, r) => {
|
|
6522
|
+
addCapsule(x1, y1, x2, y2, r, doStroke ? sw : 0, doFill ? fillIdx : 0);
|
|
6523
|
+
};
|
|
6524
|
+
|
|
6525
|
+
$.line = (x1, y1, x2, y2) => {
|
|
6526
|
+
if (doStroke) addCapsule(x1, y1, x2, y2, qsw, hsw, 0);
|
|
6527
|
+
};
|
|
6528
|
+
|
|
6529
|
+
/* ELLIPSE */
|
|
6530
|
+
|
|
6531
|
+
let ellipsePL = 6;
|
|
6532
|
+
|
|
6533
|
+
$._ellipseShaderCode =
|
|
6534
|
+
$._baseShaderCode +
|
|
6535
|
+
/* wgsl */ `
|
|
6536
|
+
struct Ellipse {
|
|
6537
|
+
center: vec2f,
|
|
6538
|
+
size: vec2f,
|
|
6539
|
+
startAngle: f32,
|
|
6540
|
+
endAngle: f32,
|
|
6541
|
+
strokeWeight: f32,
|
|
6542
|
+
fillIndex: f32,
|
|
6543
|
+
strokeIndex: f32,
|
|
6544
|
+
matrixIndex: f32,
|
|
6545
|
+
padding0: vec2f,
|
|
6546
|
+
padding1: vec4f
|
|
6547
|
+
};
|
|
6548
|
+
|
|
6549
|
+
struct VertexParams {
|
|
6550
|
+
@builtin(vertex_index) vertIndex: u32,
|
|
6551
|
+
@builtin(instance_index) instIndex: u32
|
|
6552
|
+
};
|
|
6553
|
+
|
|
6554
|
+
struct FragParams {
|
|
6555
|
+
@builtin(position) position: vec4f,
|
|
6556
|
+
@location(0) outerEdge: vec2f,
|
|
6557
|
+
@location(1) fillEdge: vec2f,
|
|
6558
|
+
@location(2) innerEdge: vec2f,
|
|
6559
|
+
@location(3) strokeWeight: f32,
|
|
6560
|
+
@location(4) fill: vec4f,
|
|
6561
|
+
@location(5) stroke: vec4f,
|
|
6562
|
+
@location(6) blend: vec4f
|
|
6563
|
+
};
|
|
6564
|
+
|
|
6565
|
+
@group(0) @binding(0) var<uniform> q: Q5;
|
|
6566
|
+
@group(0) @binding(1) var<storage> transforms: array<mat4x4<f32>>;
|
|
6567
|
+
@group(0) @binding(2) var<storage> colors : array<vec4f>;
|
|
6568
|
+
|
|
6569
|
+
@group(1) @binding(0) var<storage, read> ellipses: array<Ellipse>;
|
|
6570
|
+
|
|
6571
|
+
const PI = 3.141592653589793;
|
|
6572
|
+
const segments = 6.0;
|
|
6573
|
+
const expansion = 1.0 / cos(PI / segments);
|
|
6574
|
+
const antiAlias = 0.015625; // 1/64
|
|
6575
|
+
const transparent = vec4f(0.0);
|
|
6576
|
+
|
|
6577
|
+
fn transformVertex(pos: vec2f, matrixIndex: f32) -> vec4f {
|
|
6578
|
+
var vert = vec4f(pos, 0.0, 1.0);
|
|
6579
|
+
vert = transforms[i32(matrixIndex)] * vert;
|
|
6580
|
+
vert.x /= q.halfWidth;
|
|
6581
|
+
vert.y /= q.halfHeight;
|
|
6582
|
+
return vert;
|
|
6583
|
+
}
|
|
6584
|
+
|
|
6585
|
+
@vertex
|
|
6586
|
+
fn vertexMain(v: VertexParams) -> FragParams {
|
|
6587
|
+
let ellipse = ellipses[v.instIndex];
|
|
6588
|
+
var pos = ellipse.center;
|
|
6589
|
+
var local = vec2f(0.0);
|
|
6590
|
+
let start = ellipse.startAngle;
|
|
6591
|
+
let end = ellipse.endAngle;
|
|
6592
|
+
let arc = end - start;
|
|
6593
|
+
let halfStrokeSize = vec2f(ellipse.strokeWeight * 0.5);
|
|
6594
|
+
|
|
6595
|
+
let fanSize = (ellipse.size + halfStrokeSize) * expansion;
|
|
6596
|
+
|
|
6597
|
+
if (v.vertIndex != 0) {
|
|
6598
|
+
let theta = start + (f32(v.vertIndex - 1) / segments) * arc;
|
|
6599
|
+
local = vec2f(cos(theta), sin(theta));
|
|
6600
|
+
pos = ellipse.center + local * fanSize;
|
|
6601
|
+
}
|
|
6602
|
+
|
|
6603
|
+
let dist = pos - ellipse.center;
|
|
6604
|
+
|
|
6605
|
+
var f: FragParams;
|
|
6606
|
+
f.position = transformVertex(pos, ellipse.matrixIndex);
|
|
6607
|
+
f.outerEdge = dist / (ellipse.size + halfStrokeSize);
|
|
6608
|
+
f.fillEdge = dist / ellipse.size;
|
|
6609
|
+
f.innerEdge = dist / (ellipse.size - halfStrokeSize);
|
|
6610
|
+
f.strokeWeight = ellipse.strokeWeight;
|
|
6611
|
+
|
|
6612
|
+
let fill = colors[i32(ellipse.fillIndex)];
|
|
6613
|
+
let stroke = colors[i32(ellipse.strokeIndex)];
|
|
6614
|
+
f.fill = fill;
|
|
6615
|
+
f.stroke = stroke;
|
|
6616
|
+
|
|
6617
|
+
// Source-over blend: stroke over fill (pre-multiplied alpha)
|
|
6618
|
+
if (fill.a != 0.0 && stroke.a != 1.0) {
|
|
6619
|
+
let outAlpha = stroke.a + fill.a * (1.0 - stroke.a);
|
|
6620
|
+
let outColor = stroke.rgb * stroke.a + fill.rgb * fill.a * (1.0 - stroke.a);
|
|
6621
|
+
f.blend = vec4f(outColor / max(outAlpha, 1e-5), outAlpha);
|
|
6622
|
+
}
|
|
6623
|
+
return f;
|
|
6624
|
+
}
|
|
6625
|
+
|
|
6626
|
+
@fragment
|
|
6627
|
+
fn fragMain(f: FragParams) -> @location(0) vec4f {
|
|
6628
|
+
let fillEdge = length(f.fillEdge);
|
|
6629
|
+
|
|
6630
|
+
// disable AA for very thin strokes
|
|
6631
|
+
let aa = select(antiAlias, 0.0, f.strokeWeight <= 1.0);
|
|
6632
|
+
|
|
6633
|
+
if (f.fill.a != 0.0 && f.strokeWeight == 0.0) {
|
|
6634
|
+
if (fillEdge > 1.0) {
|
|
6635
|
+
return transparent;
|
|
6636
|
+
}
|
|
6637
|
+
let fillAlpha = 1.0 - smoothstep(1.0 - aa, 1.0, fillEdge);
|
|
6638
|
+
return vec4f(f.fill.rgb, f.fill.a * fillAlpha);
|
|
6639
|
+
}
|
|
6640
|
+
|
|
6641
|
+
let innerEdge = length(f.innerEdge);
|
|
6642
|
+
|
|
6643
|
+
if (f.fill.a != 0.0 && fillEdge < 1.0) {
|
|
6644
|
+
if (innerEdge < 1.0) {
|
|
6645
|
+
return f.fill;
|
|
6646
|
+
}
|
|
6647
|
+
let tInner = smoothstep(1.0, 1.0 + aa, innerEdge);
|
|
6648
|
+
let tOuter = smoothstep(1.0 - aa, 1.0, fillEdge);
|
|
6649
|
+
if (f.stroke.a != 1.0) {
|
|
6650
|
+
let fillBlend = mix(f.fill, f.blend, tInner);
|
|
6651
|
+
return mix(fillBlend, f.stroke, tOuter);
|
|
6652
|
+
} else {
|
|
6653
|
+
let fillBlend = mix(f.fill, f.stroke, tInner);
|
|
6654
|
+
return mix(fillBlend, f.stroke, tOuter);
|
|
6655
|
+
}
|
|
6656
|
+
}
|
|
6657
|
+
|
|
6658
|
+
if (innerEdge < 1.0) {
|
|
6659
|
+
return transparent;
|
|
6660
|
+
}
|
|
6661
|
+
|
|
6662
|
+
let outerEdge = length(f.outerEdge);
|
|
6663
|
+
let outerAlpha = 1.0 - smoothstep(1.0 - aa, 1.0, outerEdge);
|
|
6664
|
+
let innerAlpha = smoothstep(1.0, 1.0 + aa, innerEdge);
|
|
6665
|
+
let strokeAlpha = innerAlpha * outerAlpha;
|
|
6666
|
+
return vec4f(f.stroke.rgb, f.stroke.a * strokeAlpha);
|
|
6667
|
+
}
|
|
6668
|
+
`;
|
|
6669
|
+
|
|
6670
|
+
let ellipseShader = Q5.device.createShaderModule({
|
|
6671
|
+
label: 'ellipseShader',
|
|
6672
|
+
code: $._ellipseShaderCode
|
|
6673
|
+
});
|
|
6674
|
+
|
|
6675
|
+
let fanIndices = new Uint16Array([0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 5, 6, 0, 6, 7]);
|
|
6676
|
+
|
|
6677
|
+
let ellipseIndexBuffer = Q5.device.createBuffer({
|
|
6678
|
+
size: fanIndices.byteLength,
|
|
6679
|
+
usage: GPUBufferUsage.INDEX,
|
|
6680
|
+
mappedAtCreation: true
|
|
6681
|
+
});
|
|
6682
|
+
new Uint16Array(ellipseIndexBuffer.getMappedRange()).set(fanIndices);
|
|
6683
|
+
ellipseIndexBuffer.unmap();
|
|
6684
|
+
|
|
6685
|
+
let ellipseBindGroupLayout = Q5.device.createBindGroupLayout({
|
|
6686
|
+
entries: [
|
|
6687
|
+
{
|
|
6688
|
+
binding: 0,
|
|
6689
|
+
visibility: GPUShaderStage.VERTEX,
|
|
6690
|
+
buffer: { type: 'read-only-storage' }
|
|
6691
|
+
}
|
|
6692
|
+
]
|
|
6693
|
+
});
|
|
6694
|
+
|
|
6695
|
+
let ellipsePipelineLayout = Q5.device.createPipelineLayout({
|
|
6696
|
+
label: 'ellipsePipelineLayout',
|
|
6697
|
+
bindGroupLayouts: [...$._bindGroupLayouts, ellipseBindGroupLayout]
|
|
6698
|
+
});
|
|
6699
|
+
|
|
6700
|
+
$._pipelineConfigs[6] = {
|
|
6701
|
+
label: 'ellipsePipeline',
|
|
6702
|
+
layout: ellipsePipelineLayout,
|
|
6703
|
+
vertex: {
|
|
6704
|
+
module: ellipseShader,
|
|
6705
|
+
entryPoint: 'vertexMain',
|
|
6706
|
+
buffers: []
|
|
6707
|
+
},
|
|
6708
|
+
fragment: {
|
|
6709
|
+
module: ellipseShader,
|
|
6710
|
+
entryPoint: 'fragMain',
|
|
6711
|
+
targets: [
|
|
6712
|
+
{
|
|
6713
|
+
format: 'bgra8unorm',
|
|
6714
|
+
blend: $.blendConfigs['source-over']
|
|
6715
|
+
}
|
|
6716
|
+
]
|
|
6717
|
+
},
|
|
6718
|
+
primitive: { topology: 'triangle-list' },
|
|
6719
|
+
multisample: { count: 4 }
|
|
6720
|
+
};
|
|
6721
|
+
|
|
6722
|
+
$._pipelines[6] = Q5.device.createRenderPipeline($._pipelineConfigs[6]);
|
|
6723
|
+
|
|
6724
|
+
let ellipseStack = new Float32Array(Q5.MAX_ELLIPSES * 16);
|
|
6725
|
+
let ellipseStackIdx = 0;
|
|
6726
|
+
|
|
6727
|
+
let ellipseBuffer = Q5.device.createBuffer({
|
|
6728
|
+
size: ellipseStack.byteLength,
|
|
6729
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
6730
|
+
});
|
|
6731
|
+
|
|
6732
|
+
let ellipseBindGroup = Q5.device.createBindGroup({
|
|
6733
|
+
layout: ellipseBindGroupLayout,
|
|
6734
|
+
entries: [{ binding: 0, resource: { buffer: ellipseBuffer } }]
|
|
6735
|
+
});
|
|
6736
|
+
|
|
6737
|
+
function addEllipse(x, y, a, b, start, stop, strokeW, fillEllipse) {
|
|
6738
|
+
let s = ellipseStack,
|
|
6739
|
+
i = ellipseStackIdx;
|
|
6740
|
+
|
|
6741
|
+
s[i] = x;
|
|
6742
|
+
s[i + 1] = y;
|
|
6743
|
+
s[i + 2] = a;
|
|
6744
|
+
s[i + 3] = b;
|
|
6745
|
+
s[i + 4] = start;
|
|
6746
|
+
s[i + 5] = stop;
|
|
6747
|
+
s[i + 6] = strokeW;
|
|
6748
|
+
s[i + 7] = fillEllipse ? fillIdx : 0;
|
|
6749
|
+
s[i + 8] = strokeIdx;
|
|
6750
|
+
s[i + 9] = matrixIdx;
|
|
6751
|
+
|
|
6752
|
+
ellipseStackIdx += 16;
|
|
6753
|
+
drawStack.push(ellipsePL, 1);
|
|
6754
|
+
}
|
|
6755
|
+
|
|
6756
|
+
let _ellipseMode = 'center';
|
|
6757
|
+
|
|
6758
|
+
$.ellipseMode = (x) => (_ellipseMode = x);
|
|
6759
|
+
|
|
6760
|
+
function applyEllipseMode(x, y, w, h) {
|
|
6761
|
+
h ??= w;
|
|
6762
|
+
let a, b;
|
|
6763
|
+
if (_ellipseMode == 'center') {
|
|
6764
|
+
a = w / 2;
|
|
6765
|
+
b = h / 2;
|
|
6766
|
+
} else if (_ellipseMode == 'radius') {
|
|
6767
|
+
a = w;
|
|
6768
|
+
b = h;
|
|
6769
|
+
} else if (_ellipseMode == 'corner') {
|
|
6770
|
+
x += w / 2;
|
|
6771
|
+
y += h / 2;
|
|
6772
|
+
a = w / 2;
|
|
6773
|
+
b = h / 2;
|
|
6774
|
+
} else if (_ellipseMode == 'corners') {
|
|
6775
|
+
x = (x + w) / 2;
|
|
6776
|
+
y = (y + h) / 2;
|
|
6777
|
+
a = (w - x) / 2;
|
|
6778
|
+
b = (h - y) / 2;
|
|
6779
|
+
}
|
|
6780
|
+
return [x, y, a, b];
|
|
6781
|
+
}
|
|
6782
|
+
|
|
6783
|
+
$.ellipse = (x, y, w, h) => {
|
|
6784
|
+
let a, b;
|
|
6785
|
+
[x, y, a, b] = applyEllipseMode(x, y, w, h);
|
|
6786
|
+
|
|
6787
|
+
if (matrixDirty) saveMatrix();
|
|
6788
|
+
|
|
6789
|
+
addEllipse(x, y, a, b, 0, TAU, sw, doFill);
|
|
6790
|
+
};
|
|
6791
|
+
|
|
6792
|
+
$.circle = (x, y, d) => $.ellipse(x, y, d, d);
|
|
6793
|
+
|
|
6794
|
+
$.arc = (x, y, w, h, start, stop) => {
|
|
6795
|
+
if (start === stop) return $.ellipse(x, y, w, h);
|
|
6796
|
+
|
|
6797
|
+
// Convert angles if needed
|
|
6798
|
+
if ($._angleMode) {
|
|
6799
|
+
start = $.radians(start);
|
|
6800
|
+
stop = $.radians(stop);
|
|
6801
|
+
}
|
|
6802
|
+
|
|
6803
|
+
// Normalize angles
|
|
6804
|
+
start %= TAU;
|
|
6805
|
+
stop %= TAU;
|
|
6806
|
+
if (start < 0) start += TAU;
|
|
6807
|
+
if (stop < 0) stop += TAU;
|
|
6808
|
+
if (start > stop) stop += TAU;
|
|
6809
|
+
if (start == stop) return $.ellipse(x, y, w, h);
|
|
6810
|
+
|
|
6811
|
+
// Calculate position based on ellipseMode
|
|
6812
|
+
let a, b;
|
|
6813
|
+
[x, y, a, b] = applyEllipseMode(x, y, w, h);
|
|
6814
|
+
|
|
6815
|
+
if (matrixDirty) saveMatrix();
|
|
6816
|
+
|
|
6817
|
+
addEllipse(x, y, a, b, start, stop, sw, doFill);
|
|
6818
|
+
};
|
|
6819
|
+
|
|
6820
|
+
$.point = (x, y) => {
|
|
6821
|
+
if (matrixDirty) saveMatrix();
|
|
6822
|
+
|
|
6823
|
+
if (scaledHSW < 1) {
|
|
6824
|
+
addRect(x, y, hsw, hsw, 0, sw, 0);
|
|
6825
|
+
} else {
|
|
6826
|
+
// dimensions of the point needs to be set to half the stroke weight
|
|
6827
|
+
addEllipse(x, y, hsw, hsw, 0, TAU, sw, 0);
|
|
6828
|
+
}
|
|
6829
|
+
};
|
|
6830
|
+
|
|
6543
6831
|
/* IMAGE */
|
|
6544
6832
|
|
|
6545
6833
|
let imagePL = 2,
|
|
@@ -7675,6 +7963,9 @@ Q5.DILATE = 6;
|
|
|
7675
7963
|
Q5.ERODE = 7;
|
|
7676
7964
|
Q5.BLUR = 8;
|
|
7677
7965
|
|
|
7966
|
+
Q5.MAX_RECTS = 200200;
|
|
7967
|
+
Q5.MAX_ELLIPSES = 200200;
|
|
7968
|
+
|
|
7678
7969
|
Q5.initWebGPU = async () => {
|
|
7679
7970
|
if (!navigator.gpu) {
|
|
7680
7971
|
console.warn('q5 WebGPU not supported on this browser! Use Google Chrome or Edge.');
|