Skip to content

Commit 954476a

Browse files
committed
v0.0.21
1 parent 1d4f532 commit 954476a

38 files changed

+571
-119
lines changed

API.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Instantiate a drawing command using the specified shaders.
2525
- `vert` (string): vertex shader, using pragma specification defined below
2626
- `frag` (string): fragment shader
2727
- `debug`: Debug mode, which exposes additional properties for viewing triangle mesh
28-
- `insertCaps` (boolean, default: `false`) (*experimental*): Automatically insert a cap wherever a break is encountered, signaled by a position with `w = 0`. Only implemented for round joins. Allows drawing lines and caps with a single draw call, although caps may be lower resolution since they are constructed from the potentially-lower number of join vertices.
28+
- `insertCaps` (boolean, default: `false`) Automatically insert a cap wherever a break is encountered, signaled by a position with `w = 0`. Allows drawing lines and caps with a single draw call. *Use this option with care though*. If using miter joins and round caps with a high cap resolution, then *every segment instance* will have enough points to draw two caps, even if they never actually result in valid triangles.
2929

3030
Additional configuration parameters are forwarded to a `regl` command which wraps drawing.
3131

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
1+
## 0.0.21
2+
3+
### Features
4+
5+
- Clean up one of the worst parts of the shader code and get end cap insertion working with all combinations of joins and caps. :tada:
6+
17
## 0.0.20
28

9+
### Features
10+
311
- Add optional `extrapolate` keyword, as in `#pragma lines: extrapolate varying float name` to distinguish between varyings which are extrapolated outside the bounds of their respective segment endpoint values, and varyings which are clamped to the range of the segment. This can be used to dash caps and joins or to ensure colors are not extrapolated.
412

513
## 0.0.19
614

15+
### Features
16+
717
- Add `insertCaps` option to be explicit about when caps are automatically inserted
18+
19+
### Bugfixes
20+
821
- Switch to preferring `w = 0` instead of `NaN` since `NaN` detection is a bit unreliable in GLSL.
922

1023
## 0.0.18

dist/regl-gpu-lines.compat.js

Lines changed: 20 additions & 12 deletions
Large diffs are not rendered by default.

dist/regl-gpu-lines.compat.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/regl-gpu-lines.js

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@
2929
const spec = isEndpoints ? endpointSpec : segmentSpec;
3030
const verts = ['B', 'C', 'D'];
3131
if (!isEndpoints) verts.unshift('A');
32+
33+
function computeCount(props) {
34+
return insertCaps ? isEndpoints // Cap has fixed number, join could either be a cap or a join
35+
? [props.capResolution, Math.max(props.capResolution, props.joinResolution)] // Both could be either a cap or a join
36+
: [Math.max(props.capResolution, props.joinResolution), Math.max(props.capResolution, props.joinResolution)] : isEndpoints // Draw a cap
37+
? [props.capResolution, props.joinResolution] // Draw two joins
38+
: [props.joinResolution, props.joinResolution];
39+
}
40+
3241
return regl({
3342
vert: `${meta.glsl}
3443
const float CAP_START = ${ORIENTATION$2.CAP_START}.0;
@@ -39,7 +48,7 @@ ${spec.glsl}
3948
attribute float index;
4049
${debug ? 'attribute float debugInstanceID;' : ''}
4150
42-
uniform vec3 joinRes;
51+
uniform vec2 vertexCount, capJoinRes;
4352
uniform vec2 resolution, capScale;
4453
uniform float miterLimit;
4554
${meta.orientation || !isEndpoints ? '' : 'uniform float orientation;'}
@@ -69,14 +78,21 @@ void main() {
6978
${verts.map(vert => `vec4 p${vert} = ${meta.position.generate(vert)};`).join('\n')}
7079
7180
// Check for invalid vertices
72-
if (invalid(pB) || invalid(pC)${insertCaps ? '' : `${isEndpoints ? '' : '|| invalid(pA)'} || invalid(pD)`}) {
81+
if (invalid(pB) || invalid(pC)) {
7382
gl_Position = vec4(1,1,1,0);
7483
return;
7584
}
7685
86+
float mirrorIndex = 2.0 * vertexCount.x + 3.0;
87+
float totalVertexCount = mirrorIndex + 2.0 + 2.0 * vertexCount.y;
88+
7789
// If we're past the first half-join and half of the segment, then we swap all vertices and start
7890
// over from the opposite end.
79-
bool isMirrored = index > joinRes.x * 2.0 + 3.0;
91+
bool isMirrored = index > mirrorIndex;
92+
93+
// When rendering dedicated endoints, this allows us to insert an end cap *alone* (without the attached
94+
// segment and join)
95+
${isEndpoints ? 'if (invalid(pD) && isMirrored) { gl_Position = vec4(0); return; }' : ''}
8096
8197
// Convert to screen-pixel coordinates
8298
// Save w so we can perspective re-multiply at the end to get varyings depth-correct
@@ -86,8 +102,6 @@ void main() {
86102
// If it's a cap, mirror A back onto C to accomplish a round
87103
${isEndpoints ? `vec4 pA = pC;` : ''}
88104
89-
vec2 res = isMirrored ? joinRes.yx : joinRes.xy;
90-
91105
float mirrorSign = isMirrored ? -1.0 : 1.0;
92106
if (isMirrored) {
93107
vec4 tmp;
@@ -97,14 +111,14 @@ void main() {
97111
98112
${isEndpoints ? `bool isCap = !isMirrored;` : `bool isCap = false;`};
99113
100-
if (invalid(pA)) { pA = pC; isCap = true; }
101-
if (invalid(pD)) { pD = pB; }
114+
if (invalid(pA)) { ${insertCaps ? 'pA = pC; isCap = true;' : 'pA = 2.0 * pB - pC;'} }
115+
if (invalid(pD)) { ${insertCaps ? 'pD = pB;' : 'pD = 2.0 * pC - pB;'} }
102116
103117
float width = isMirrored ? ${meta.width.generate('C')} : ${meta.width.generate('B')};
104118
105119
// Invalidate triangles too far in front of or behind the camera plane
106120
if (max(abs(pB.z), abs(pC.z)) > 1.0) {
107-
gl_Position = vec4(1,1,1,0);
121+
gl_Position = vec4(0);
108122
return;
109123
}
110124
@@ -141,10 +155,10 @@ void main() {
141155
vec2 miter = bIsHairpin ? -tBC : 0.5 * (nAB + nBC) * dirB;
142156
143157
// The second half of the triangle strip instance is just the first, reversed, and with vertices swapped!
144-
float i = index < 2.0 * joinRes.x + 4.0 ? index : 2.0 * (res.x + res.y) + 5.0 - index;
158+
float i = index <= mirrorIndex ? index : totalVertexCount - index;
145159
146160
// Chop off the join to get at the segment part index
147-
float iSeg = i - 2.0 * res.x;
161+
float iSeg = i - 2.0 * (isMirrored ? vertexCount.y : vertexCount.x);
148162
149163
// After the first half-join, repeat two vertices of the segment strip in order to get the orientation correct
150164
// for the next join. These are wasted vertices, but they enable using a triangle strip. for two joins which
@@ -173,12 +187,12 @@ void main() {
173187
if (isRound || isCap) {
174188
// Round joins
175189
xBasis = dirB * vec2(yBasis.y, -yBasis.x);
176-
float divisor = ${isEndpoints ? 'res.x' : 'min(res.x, isCap ? joinRes.z : res.x)'} * 2.0;
177-
float theta = -0.5 * (acos(cosB) * (min(i, divisor) / divisor) - pi) * (isCap ? 2.0 : 1.0);
190+
float cnt = (isCap ? capJoinRes.x : capJoinRes.y) * 2.0;
191+
float theta = -0.5 * (acos(cosB) * (min(i, cnt) / cnt) - pi) * (isCap ? 2.0 : 1.0);
178192
xy = vec2(cos(theta), sin(theta));
179193
180194
if (isCap) {
181-
if (xy.y > 0.5) xy *= capScale;
195+
if (xy.y > 0.001) xy *= capScale;
182196
lineCoord.xy = xy.yx * lineCoord.y;
183197
}
184198
} else {
@@ -231,17 +245,18 @@ void main() {
231245
...spec.attrs
232246
},
233247
uniforms: {
234-
joinRes: (ctx, props) => [// First half-join is actually a cap if we're drawing endpoints
235-
isEndpoints ? props.capResolution : props.joinResolution, // Second half-join is always a join
236-
props.joinResolution, // The resolution of inserted caps
237-
props.capType === 'square' ? props.capResolution : props.capType === 'none' ? 0 : props.joinResolution],
248+
vertexCount: (ctx, props) => computeCount(props),
249+
capJoinRes: (ctx, props) => [props.capResolution, props.joinResolution],
238250
miterLimit: (ctx, props) => props.miterLimit * props.miterLimit,
239251
orientation: regl.prop('orientation'),
240252
capScale: regl.prop('capScale')
241253
},
242254
primitive: 'triangle strip',
243255
instances: isEndpoints ? (ctx, props) => props.splitCaps ? props.orientation === ORIENTATION$2.CAP_START ? Math.ceil(props.count / 2) : Math.floor(props.count / 2) : props.count : (ctx, props) => props.count - 3,
244-
count: isRound ? (ctx, props) => 6 + 2 * (props.joinResolution + (isEndpoints ? props.capResolution : props.joinResolution)) : (ctx, props) => 6 + 2 * (1 + (isEndpoints ? props.capResolution : 1)) + 10
256+
count: (ctx, props) => {
257+
const count = computeCount(props);
258+
return 6 + 2 * (count[0] + count[1]);
259+
}
245260
});
246261
}
247262

dist/regl-gpu-lines.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/border.html

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@
3333
#pragma lines: width = getWidth(xy);
3434
3535
uniform float width;
36+
uniform vec2 translate, scale;
3637
3738
// Implement the functions above
38-
vec4 getPosition(vec2 xy) { return vec4(xy, 0, 1); }
39+
vec4 getPosition(vec2 xy) { return vec4(xy * scale + translate, 0, 1); }
3940
float getWidth(vec2 xy) { return width; }
4041
float getX(vec2 xy) { return xy.x; }`,
4142
frag: `
@@ -46,33 +47,33 @@
4647
// Convert the line coord into an SDF
4748
float sdf = length(lineCoord.xy) * width;
4849
49-
// Use the y-value of the line coord to distinguish between sides of the line
50-
vec3 borderColor = lineCoord.y < 0.0 ? vec3(1, 0.5, 0.5) : vec3(0.5, 0.5, 1);
50+
vec3 borderColor = 0.5 + 0.5 * vec3(lineCoord.xy, 0);
5151
5252
// Apply a border with 1px transition
5353
gl_FragColor = vec4(
54-
mix(vec3(0), borderColor, smoothstep(width - borderWidth - 0.5, width - borderWidth + 0.5, sdf)),
54+
mix(vec3(0), borderColor, smoothstep(width - borderWidth - 1.0, width - borderWidth + 1.0, sdf)),
5555
1);
5656
}`,
5757

5858
// Additional regl command properties are valid
5959
uniforms: {
6060
width: (ctx, props) => ctx.pixelRatio * props.width,
6161
borderWidth: (ctx, props) => ctx.pixelRatio * props.borderWidth,
62+
translate: regl.prop('translate'),
63+
scale: regl.prop('scale')
6264
},
65+
depth: {enable: false}
6366
});
6467

6568
// Construct an array of xy pairs
66-
const n = 21;
69+
const n = 11;
6770
const xy = [...Array(n).keys()]
6871
.map(i => (i / (n - 1) * 2.0 - 1.0) * 0.8)
69-
.map(t => [t, 0.5 * Math.sin(8.0 * t)]);
72+
.map(t => [t, 0.5 * Math.sin(54.0 * t)]);
7073

7174
// Set up the data to be drawn. Note that we preallocate buffers and don't create
7275
// them on every draw call.
7376
const lineData = {
74-
join: 'round',
75-
cap: 'round',
7677
vertexCount: xy.length,
7778
vertexAttributes: {
7879
xy: regl.buffer(xy)
@@ -82,14 +83,35 @@
8283
xy: regl.buffer([xy.slice(0, 3), xy.slice(-3).reverse()])
8384
},
8485

85-
width: 25,
86-
borderWidth: 10
86+
width: 35,
87+
borderWidth: 10,
88+
miterLimit: 3.0,
89+
scale: [1, 1]
8790
};
8891

92+
window.addEventListener('mousemove', e => {
93+
lineData.scale = [
94+
e.offsetX / window.innerWidth * 2 - 1,
95+
-e.offsetY / window.innerHeight * 2 + 1
96+
];
97+
draw();
98+
});
99+
100+
89101
function draw () {
90102
regl.poll();
91103
regl.clear({color: [0.2, 0.2, 0.2, 1]});
92-
drawLines(lineData);
104+
drawLines([{
105+
...lineData,
106+
translate: [0, -0.4],
107+
cap: 'round',
108+
join: 'round'
109+
}, {
110+
...lineData,
111+
translate: [0, 0.4],
112+
cap: 'round',
113+
join: 'miter'
114+
}]);
93115
}
94116

95117
draw();
@@ -133,9 +155,10 @@
133155
#pragma lines: width = getWidth(xy);
134156

135157
uniform float width;
158+
uniform vec2 translate, scale;
136159

137160
// Implement the functions above
138-
vec4 getPosition(vec2 xy) { return vec4(xy, 0, 1); }
161+
vec4 getPosition(vec2 xy) { return vec4(xy * scale + translate, 0, 1); }
139162
float getWidth(vec2 xy) { return width; }
140163
float getX(vec2 xy) { return xy.x; }`,
141164
frag: `
@@ -146,33 +169,33 @@
146169
// Convert the line coord into an SDF
147170
float sdf = length(lineCoord.xy) * width;
148171

149-
// Use the y-value of the line coord to distinguish between sides of the line
150-
vec3 borderColor = lineCoord.y < 0.0 ? vec3(1, 0.5, 0.5) : vec3(0.5, 0.5, 1);
172+
vec3 borderColor = 0.5 + 0.5 * vec3(lineCoord.xy, 0);
151173

152174
// Apply a border with 1px transition
153175
gl_FragColor = vec4(
154-
mix(vec3(0), borderColor, smoothstep(width - borderWidth - 0.5, width - borderWidth + 0.5, sdf)),
176+
mix(vec3(0), borderColor, smoothstep(width - borderWidth - 1.0, width - borderWidth + 1.0, sdf)),
155177
1);
156178
}`,
157179

158180
// Additional regl command properties are valid
159181
uniforms: {
160182
width: (ctx, props) => ctx.pixelRatio * props.width,
161183
borderWidth: (ctx, props) => ctx.pixelRatio * props.borderWidth,
184+
translate: regl.prop('translate'),
185+
scale: regl.prop('scale')
162186
},
187+
depth: {enable: false}
163188
});
164189

165190
// Construct an array of xy pairs
166-
const n = 21;
191+
const n = 11;
167192
const xy = [...Array(n).keys()]
168193
.map(i => (i / (n - 1) * 2.0 - 1.0) * 0.8)
169-
.map(t => [t, 0.5 * Math.sin(8.0 * t)]);
194+
.map(t => [t, 0.5 * Math.sin(54.0 * t)]);
170195

171196
// Set up the data to be drawn. Note that we preallocate buffers and don't create
172197
// them on every draw call.
173198
const lineData = {
174-
join: 'round',
175-
cap: 'round',
176199
vertexCount: xy.length,
177200
vertexAttributes: {
178201
xy: regl.buffer(xy)
@@ -182,14 +205,35 @@
182205
xy: regl.buffer([xy.slice(0, 3), xy.slice(-3).reverse()])
183206
},
184207

185-
width: 25,
186-
borderWidth: 10
208+
width: 35,
209+
borderWidth: 10,
210+
miterLimit: 3.0,
211+
scale: [1, 1]
187212
};
188213

214+
window.addEventListener('mousemove', e => {
215+
lineData.scale = [
216+
e.offsetX / window.innerWidth * 2 - 1,
217+
-e.offsetY / window.innerHeight * 2 + 1
218+
];
219+
draw();
220+
});
221+
222+
189223
function draw () {
190224
regl.poll();
191225
regl.clear({color: [0.2, 0.2, 0.2, 1]});
192-
drawLines(lineData);
226+
drawLines([{
227+
...lineData,
228+
translate: [0, -0.4],
229+
cap: 'round',
230+
join: 'round'
231+
}, {
232+
...lineData,
233+
translate: [0, 0.4],
234+
cap: 'round',
235+
join: 'miter'
236+
}]);
193237
}
194238

195239
draw();

docs/debug.html

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
const state = wrapGUI(State({
3232
lineConfig: State.Section({
3333
capResolution: State.Slider(8, {min: 1, max: 30, step: 1}),
34-
joinResolution: State.Slider(8, {min: 1, max: 30, step: 1}),
34+
joinResolution: State.Slider(6, {min: 1, max: 30, step: 1}),
3535
cap: State.Select('round', {options: ['round', 'square', 'none']}),
3636
join: State.Select('round', {options: ['round', 'miter', 'bevel']}),
3737
miterLimit: State.Slider(4, {min: 1, max: 8, step: 0.01}),
@@ -41,7 +41,7 @@
4141
flip: State.Slider(1, {min: -1, max: 1, step: 0.001}),
4242
}, {expanded: true}),
4343
line: State.Section({
44-
width: State.Slider(40, {min: 1, max: 100, step: 0.1}),
44+
width: State.Slider(70, {min: 1, max: 100, step: 0.1}),
4545
opacity: State.Slider(0.5, {min: 0, max: 1, step: 0.01}),
4646
}, {label: 'line', expanded: false}),
4747
border: State.Section({
@@ -361,6 +361,7 @@
361361
lineColor: [0, 0, 0, state.line.opacity],
362362
borderColor: [0, 0, 0, state.border.opacity],
363363
dashColor: [0, 0, 0, state.dash.opacity],
364+
//vertexCount: 0
364365
});
365366
}
366367

@@ -405,7 +406,7 @@
405406
const state = wrapGUI(State({
406407
lineConfig: State.Section({
407408
capResolution: State.Slider(8, {min: 1, max: 30, step: 1}),
408-
joinResolution: State.Slider(8, {min: 1, max: 30, step: 1}),
409+
joinResolution: State.Slider(6, {min: 1, max: 30, step: 1}),
409410
cap: State.Select('round', {options: ['round', 'square', 'none']}),
410411
join: State.Select('round', {options: ['round', 'miter', 'bevel']}),
411412
miterLimit: State.Slider(4, {min: 1, max: 8, step: 0.01}),
@@ -415,7 +416,7 @@
415416
flip: State.Slider(1, {min: -1, max: 1, step: 0.001}),
416417
}, {expanded: true}),
417418
line: State.Section({
418-
width: State.Slider(40, {min: 1, max: 100, step: 0.1}),
419+
width: State.Slider(70, {min: 1, max: 100, step: 0.1}),
419420
opacity: State.Slider(0.5, {min: 0, max: 1, step: 0.01}),
420421
}, {label: 'line', expanded: false}),
421422
border: State.Section({
@@ -735,6 +736,7 @@
735736
lineColor: [0, 0, 0, state.line.opacity],
736737
borderColor: [0, 0, 0, state.border.opacity],
737738
dashColor: [0, 0, 0, state.dash.opacity],
739+
//vertexCount: 0
738740
});
739741
}
740742

0 commit comments

Comments
 (0)