Skip to content

Commit 3a81708

Browse files
authored
Merge branch 'dev-2.0' into test/webgl-3d-primitives
2 parents 446645e + 6a61f7f commit 3a81708

8 files changed

Lines changed: 210 additions & 12 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
.nyc_output/*
44
# .vscode/settings.json
5+
# optional local preferences for vscode
6+
local.code-workspace
7+
58
node_modules/*
69

710
analyzer/

.vscode/extensions.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
// List of extensions which should be recommended for users of this workspace.
66
"recommendations": [
7-
"eg2.vscode-npm-script",
87
"yzhang.markdown-all-in-one",
98
"dbaeumer.vscode-eslint"
109
],

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"build": "rollup -c",
66
"dev": "vite preview/",
77
"dev:global": "concurrently -n build,server \"rollup -c -w\" \"npx vite preview/global/\"",
8-
"docs": "documentation build ./src/**/*.js ./src/**/**/*.js -o ./docs/data.json && node ./utils/convert.mjs",
8+
"docs": "documentation build ./src/**/*.js ./src/**/**/*.js --shallow -o ./docs/data.json && node ./utils/convert.mjs",
99
"bench": "vitest bench",
1010
"bench:report": "vitest bench --reporter=verbose",
1111
"test": "vitest",

src/strands/p5.strands.js

Lines changed: 109 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ function strands(p5, fn) {
3030
//////////////////////////////////////////////
3131
// Global Runtime
3232
//////////////////////////////////////////////
33-
function initStrandsContext(ctx, backend, { active = false, renderer = null, baseShader = null } = {}) {
33+
function initStrandsContext(
34+
ctx,
35+
backend,
36+
{ active = false, renderer = null, baseShader = null } = {},
37+
) {
3438
ctx.dag = createDirectedAcyclicGraph();
3539
ctx.cfg = createControlFlowGraph();
3640
ctx.uniforms = [];
@@ -78,11 +82,8 @@ function strands(p5, fn) {
7882

7983
const prev = {};
8084
for (const key of Object.getOwnPropertyNames(fn)) {
81-
const descriptor = Object.getOwnPropertyDescriptor(
82-
fn,
83-
key
84-
);
85-
if (descriptor && !descriptor.get && typeof fn[key] === 'function') {
85+
const descriptor = Object.getOwnPropertyDescriptor(fn, key);
86+
if (descriptor && !descriptor.get && typeof fn[key] === "function") {
8687
prev[key] = window[key];
8788
window[key] = fn[key].bind(pInst);
8889
}
@@ -104,7 +105,10 @@ function strands(p5, fn) {
104105

105106
p5.Shader.prototype.modify = function (shaderModifier, scope = {}) {
106107
try {
107-
if (shaderModifier instanceof Function || typeof shaderModifier === 'string') {
108+
if (
109+
shaderModifier instanceof Function ||
110+
typeof shaderModifier === "string"
111+
) {
108112
// Reset the context object every time modify is called;
109113
// const backend = glslBackend;
110114
initStrandsContext(strandsContext, this._renderer.strandsBackend, {
@@ -121,9 +125,10 @@ function strands(p5, fn) {
121125
if (options.parser) {
122126
// #7955 Wrap function declaration code in brackets so anonymous functions are not top level statements, which causes an error in acorn when parsing
123127
// https://github.com/acornjs/acorn/issues/1385
124-
const sourceString = typeof shaderModifier === 'string'
125-
? `(${shaderModifier})`
126-
: `(${shaderModifier.toString()})`;
128+
const sourceString =
129+
typeof shaderModifier === "string"
130+
? `(${shaderModifier})`
131+
: `(${shaderModifier.toString()})`;
127132
strandsCallback = transpileStrandsToJS(
128133
p5,
129134
sourceString,
@@ -275,6 +280,100 @@ if (typeof p5 !== "undefined") {
275280
* </div>
276281
*/
277282

283+
/**
284+
* @method smoothstep
285+
* @description
286+
* A shader function that performs smooth Hermite interpolation between `0.0`
287+
* and `1.0`.
288+
*
289+
* This function is equivalent to the GLSL built-in
290+
* `smoothstep(edge0, edge1, x)` and is available inside p5.strands shader
291+
* callbacks. It is commonly used to create soft transitions, smooth edges,
292+
* fades, and anti-aliased effects.
293+
*
294+
* Smoothstep is useful when a threshold or cutoff is needed, but with a
295+
* gradual transition instead of a hard edge.
296+
*
297+
* - Returns `0.0` when `x` is less than or equal to `edge0`
298+
* - Returns `1.0` when `x` is greater than or equal to `edge1`
299+
* - Smoothly interpolates between `0.0` and `1.0` when `x` is between them
300+
*
301+
* @param {Number} edge0
302+
* Lower edge of the transition
303+
* @param {Number} edge1
304+
* Upper edge of the transition
305+
* @param {Number} x
306+
* Input value to interpolate
307+
*
308+
* @returns {Number}
309+
* A value between `0.0` and `1.0`
310+
*
311+
* @example
312+
* <div modernizr="webgl">
313+
* <code>
314+
* // Example 1: A soft vertical fade using smoothstep (no uniforms)
315+
*
316+
* let fadeShader;
317+
*
318+
* function fadeCallback() {
319+
* getColor((inputs) => {
320+
* // x goes from 0 → 1 across the canvas
321+
* let x = inputs.texCoord.x;
322+
*
323+
* // smoothstep creates a soft transition instead of a hard edge
324+
* let t = smoothstep(0.25, 0.35, x);
325+
*
326+
* // Use t directly as brightness
327+
* return [t, t, t, 1];
328+
* });
329+
* }
330+
*
331+
* function setup() {
332+
* createCanvas(300, 200, WEBGL);
333+
* fadeShader = baseFilterShader().modify(fadeCallback);
334+
* }
335+
*
336+
* function draw() {
337+
* background(0);
338+
* filter(fadeShader);
339+
* }
340+
* </code>
341+
* </div>
342+
*
343+
* @example
344+
* <div modernizr="webgl">
345+
* <code>
346+
* // Example 2: Animate the smooth transition using a uniform
347+
*
348+
* let animatedShader;
349+
*
350+
* function animatedFadeCallback() {
351+
* const time = uniformFloat(() => millis() * 0.001);
352+
*
353+
* getColor((inputs) => {
354+
* let x = inputs.texCoord.x;
355+
*
356+
* // Move the smoothstep band back and forth over time
357+
* let center = 0.5 + 0.25 * sin(time);
358+
* let t = smoothstep(center - 0.05, center + 0.05, x);
359+
*
360+
* return [t, t, t, 1];
361+
* });
362+
* }
363+
*
364+
* function setup() {
365+
* createCanvas(300, 200, WEBGL);
366+
* animatedShader = baseFilterShader().modify(animatedFadeCallback);
367+
* }
368+
*
369+
* function draw() {
370+
* background(0);
371+
* filter(animatedShader);
372+
* }
373+
* </code>
374+
* </div>
375+
*/
376+
278377
/**
279378
* @method beforeVertex
280379
* @private

src/webgl/ShapeBuilder.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,27 @@ export class ShapeBuilder {
313313
}
314314
}
315315

316+
// Normalize nearly identical consecutive vertices to prevent tessellation artifacts
317+
// This addresses numerical precision issues in libtess when consecutive vertices
318+
// have coordinates that are almost (but not exactly) equal (e.g., differing by ~1e-8)
319+
const epsilon = 1e-6;
320+
for (const contour of contours) {
321+
const stride = this.tessyVertexSize;
322+
for (let i = stride; i < contour.length; i += stride) {
323+
const prevX = contour[i - stride];
324+
const prevY = contour[i - stride + 1];
325+
const currX = contour[i];
326+
const currY = contour[i + 1];
327+
328+
if (Math.abs(currX - prevX) < epsilon) {
329+
contour[i] = prevX;
330+
}
331+
if (Math.abs(currY - prevY) < epsilon) {
332+
contour[i + 1] = prevY;
333+
}
334+
}
335+
}
336+
316337
const polyTriangles = this._triangulate(contours);
317338

318339
// If there were no valid faces, we still want to use the original vertices

test/unit/visual/cases/webgl.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,4 +1312,77 @@ visualSuite('WebGL', function() {
13121312
screenshot();
13131313
});
13141314
});
1315+
1316+
visualSuite('Tessellation', function() {
1317+
visualTest('Handles nearly identical consecutive vertices', function(p5, screenshot) {
1318+
p5.createCanvas(400, 400, p5.WEBGL);
1319+
1320+
const contours = [
1321+
[
1322+
[-3.8642425537109375, -6.120738636363637, 0],
1323+
[3.2025188099254267, -6.120738636363637, 0],
1324+
[3.2025188099254267, -4.345170454545455, 0],
1325+
[-3.8642425537109375, -4.345170454545455, 0],
1326+
[-3.8642425537109375, -6.120738636363637, 0]
1327+
],
1328+
[
1329+
[-1.8045834628018462, 4.177556818181818, 0],
1330+
[-1.8045834628018462, -9.387784090909093, 0],
1331+
[0.29058699174360836, -9.387784090909093, 0],
1332+
[0.2905869917436083, 3.609374411367136, 0],
1333+
[0.31044303036623855, 4.068235883781435, 0],
1334+
[0.38522861430307975, 4.522728865422799, 0],
1335+
[0.548044378107245, 4.941051136363637, 0],
1336+
[0.8364672032828204, 5.2932224887960775, 0],
1337+
[1.2227602871981542, 5.526988636363637, 0],
1338+
[1.6572258237923885, 5.634502949876295, 0],
1339+
[2.101666537198154, 5.669034090909091, 0],
1340+
[2.6695604948237173, 5.633568761673102, 0],
1341+
[3.0249619917436084, 5.5625, 0],
1342+
[3.4510983553799726, 7.4446022727272725, 0],
1343+
[2.8568950819856695, 7.613138889205699, 0],
1344+
[2.3751340936529037, 7.676962586830456, 0],
1345+
[1.8892600236717598, 7.693181792704519, 0],
1346+
[1.2922705720786674, 7.649533731133848, 0],
1347+
[0.7080836288276859, 7.519788939617751, 0],
1348+
[0.14854153719815422, 7.311434659090909, 0],
1349+
[-0.38643934048179873, 7.00959666478984, 0],
1350+
[-0.858113258144025, 6.61653855366859, 0],
1351+
[-1.25415732643821, 6.1484375, 0],
1352+
[-1.5108595282965422, 5.697682732328092, 0],
1353+
[-1.6824918355513252, 5.207533878495854, 0],
1354+
[-1.7762971052870198, 4.695933154267308, 0],
1355+
[-1.8045834628018462, 4.177556818181818, 0]
1356+
]
1357+
];
1358+
1359+
p5.background('red');
1360+
p5.push();
1361+
p5.stroke(0);
1362+
p5.fill('#EEE');
1363+
p5.scale(15);
1364+
p5.beginShape();
1365+
for (const contour of contours) {
1366+
p5.beginContour();
1367+
for (const v of contour) {
1368+
p5.vertex(...v);
1369+
}
1370+
p5.endContour();
1371+
}
1372+
p5.endShape();
1373+
1374+
p5.stroke(0, 255, 0);
1375+
p5.strokeWeight(5);
1376+
p5.beginShape(p5.POINTS);
1377+
for (const contour of contours) {
1378+
for (const v of contour) {
1379+
p5.vertex(...v);
1380+
}
1381+
}
1382+
p5.endShape();
1383+
p5.pop();
1384+
1385+
screenshot();
1386+
});
1387+
});
13151388
});
6.57 KB
Loading
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"numScreenshots": 1
3+
}

0 commit comments

Comments
 (0)