Skip to content

Commit 6ab7ddf

Browse files
committed
Merge branch 'dev-2.0' into dependencies-update
2 parents 9646e8e + 6a61f7f commit 6ab7ddf

16 files changed

Lines changed: 751 additions & 119 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-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"build": "rollup -c",
77
"dev": "vite preview/",
88
"dev:global": "concurrently -n build,server \"rollup -c -w\" \"npx vite preview/global/\"",
9-
"docs": "documentation build ./src/**/*.js ./src/**/**/*.js -o ./docs/data.json && node ./utils/convert.mjs",
9+
"docs": "documentation build ./src/**/*.js ./src/**/**/*.js --shallow -o ./docs/data.json && node ./utils/convert.mjs",
1010
"bench": "vitest bench",
1111
"bench:report": "vitest bench --reporter=verbose",
1212
"test": "vitest",
@@ -20,7 +20,7 @@
2020
"test/**/*.js": "eslint",
2121
"utils/**/*.{js,mjs}": "eslint"
2222
},
23-
"version": "2.2.0",
23+
"version": "2.2.1-rc.0",
2424
"dependencies": {
2525
"@davepagurek/bezier-path": "^0.0.7",
2626
"@japont/unicode-range": "^1.0.0",

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,
@@ -267,6 +272,100 @@ if (typeof p5 !== "undefined") {
267272
* }
268273
*/
269274

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

src/strands/strands_api.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,86 @@ import * as FES from './strands_FES'
2121
import { getNodeDataFromID } from './ir_dag'
2222
import { StrandsNode, createStrandsNode } from './strands_node'
2323

24+
const BUILTIN_GLOBAL_SPECS = {
25+
width: { typeInfo: DataType.float1, get: (p) => p.width },
26+
height: { typeInfo: DataType.float1, get: (p) => p.height },
27+
mouseX: { typeInfo: DataType.float1, get: (p) => p.mouseX },
28+
mouseY: { typeInfo: DataType.float1, get: (p) => p.mouseY },
29+
pmouseX: { typeInfo: DataType.float1, get: (p) => p.pmouseX },
30+
pmouseY: { typeInfo: DataType.float1, get: (p) => p.pmouseY },
31+
winMouseX: { typeInfo: DataType.float1, get: (p) => p.winMouseX },
32+
winMouseY: { typeInfo: DataType.float1, get: (p) => p.winMouseY },
33+
pwinMouseX: { typeInfo: DataType.float1, get: (p) => p.pwinMouseX },
34+
pwinMouseY: { typeInfo: DataType.float1, get: (p) => p.pwinMouseY },
35+
frameCount: { typeInfo: DataType.float1, get: (p) => p.frameCount },
36+
deltaTime: { typeInfo: DataType.float1, get: (p) => p.deltaTime },
37+
displayWidth: { typeInfo: DataType.float1, get: (p) => p.displayWidth },
38+
displayHeight: { typeInfo: DataType.float1, get: (p) => p.displayHeight },
39+
windowWidth: { typeInfo: DataType.float1, get: (p) => p.windowWidth },
40+
windowHeight: { typeInfo: DataType.float1, get: (p) => p.windowHeight },
41+
mouseIsPressed: { typeInfo: DataType.bool1, get: (p) => p.mouseIsPressed },
42+
}
43+
44+
function _getBuiltinGlobalsCache(strandsContext) {
45+
if (!strandsContext._builtinGlobals || strandsContext._builtinGlobals.dag !== strandsContext.dag) {
46+
strandsContext._builtinGlobals = {
47+
dag: strandsContext.dag,
48+
nodes: new Map(),
49+
uniformsAdded: new Set(),
50+
}
51+
}
52+
// return the cache
53+
return strandsContext._builtinGlobals
54+
}
55+
56+
function getBuiltinGlobalNode(strandsContext, name) {
57+
const spec = BUILTIN_GLOBAL_SPECS[name]
58+
if (!spec) return null
59+
60+
const cache = _getBuiltinGlobalsCache(strandsContext)
61+
const uniformName = `_p5_global_${name}`
62+
const cached = cache.nodes.get(uniformName)
63+
if (cached) return cached
64+
65+
if (!cache.uniformsAdded.has(uniformName)) {
66+
cache.uniformsAdded.add(uniformName)
67+
strandsContext.uniforms.push({
68+
name: uniformName,
69+
typeInfo: spec.typeInfo,
70+
defaultValue: () => {
71+
const p5Instance = strandsContext.renderer?._pInst || strandsContext.p5?.instance
72+
return p5Instance ? spec.get(p5Instance) : undefined
73+
},
74+
})
75+
}
76+
77+
const { id, dimension } = build.variableNode(strandsContext, spec.typeInfo, uniformName)
78+
const node = createStrandsNode(id, dimension, strandsContext)
79+
node._originalBuiltinName = name
80+
cache.nodes.set(uniformName, node)
81+
return node
82+
}
83+
84+
function installBuiltinGlobalAccessors(strandsContext) {
85+
if (strandsContext._builtinGlobalsAccessorsInstalled) return
86+
87+
const getRuntimeP5Instance = () => strandsContext.renderer?._pInst || strandsContext.p5?.instance
88+
89+
for (const name of Object.keys(BUILTIN_GLOBAL_SPECS)) {
90+
const spec = BUILTIN_GLOBAL_SPECS[name]
91+
Object.defineProperty(window, name, {
92+
get: () => {
93+
if (strandsContext.active) {
94+
return getBuiltinGlobalNode(strandsContext, name);
95+
}
96+
const inst = getRuntimeP5Instance()
97+
return spec.get(inst);
98+
},
99+
})
100+
}
101+
strandsContext._builtinGlobalsAccessorsInstalled = true
102+
}
103+
24104
//////////////////////////////////////////////
25105
// User nodes
26106
//////////////////////////////////////////////
@@ -419,6 +499,8 @@ function enforceReturnTypeMatch(strandsContext, expectedType, returned, hookName
419499
return returnedNodeID;
420500
}
421501
export function createShaderHooksFunctions(strandsContext, fn, shader) {
502+
installBuiltinGlobalAccessors(strandsContext)
503+
422504
// Add shader context to hooks before spreading
423505
const vertexHooksWithContext = Object.fromEntries(
424506
Object.entries(shader.hooks.vertex).map(([name, hook]) => [name, { ...hook, shaderContext: 'vertex' }])

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

src/webgl/p5.Framebuffer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class Framebuffer {
6262
this.renderer.framebuffers.add(this);
6363

6464
this._isClipApplied = false;
65+
this._useCanvasFormat = settings._useCanvasFormat || false;
6566

6667
this.dirty = { colorTexture: false, depthTexture: false };
6768

0 commit comments

Comments
 (0)