-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
feat(strands): add color() support for beginner-friendly color inputs #8822
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
ad2ffcd
3df6b4a
4c63088
ddc668a
6684e17
6b641dc
ebc7fd5
b5955f1
2004c69
00c2b8c
c1d9b44
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -312,6 +312,154 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) { | |
| return result; | ||
| }); | ||
|
|
||
| const originalColor = fn.color; | ||
| augmentFn(fn, p5, 'color', function (...args) { | ||
| if (!strandsContext.active) { | ||
| return originalColor.apply(this, args); | ||
| } | ||
| // Reuse p5's parser - handles hex strings, rgb(), CSS named colors, numerics | ||
| const c = originalColor.apply(this, args); | ||
| // _getRGBA() returns [r, g, b, a] normalized to 0-1 | ||
| const rgba = c._getRGBA(); | ||
| const { id, dimension } = build.primitiveConstructorNode( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would |
||
| strandsContext, | ||
| { baseType: BaseType.FLOAT, dimension: null }, | ||
| rgba | ||
| ); | ||
| return createStrandsNode(id, dimension, strandsContext); | ||
| }); | ||
| const originalLerpColor = fn.lerpColor; | ||
| augmentFn(fn, p5, 'lerpColor', function (...args) { | ||
| if (!strandsContext.active) { | ||
| return originalLerpColor.apply(this, args); | ||
| } | ||
| // In strands, colors are vec4s - lerpColor maps directly to GLSL mix() | ||
| return fn.mix(...args); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Calling these functions on I think that means you'll need to pass in |
||
| }); | ||
| // Component accessors: extract scalar channels from a vec4 color | ||
| const originalRed = fn.red; | ||
| augmentFn(fn, p5, 'red', function (...args) { | ||
| if (!strandsContext.active) { | ||
| return originalRed.apply(this, args); | ||
| } | ||
| return p5.strandsNode(args[0]).x; | ||
| }); | ||
|
|
||
| const originalGreen = fn.green; | ||
| augmentFn(fn, p5, 'green', function (...args) { | ||
| if (!strandsContext.active) { | ||
| return originalGreen.apply(this, args); | ||
| } | ||
| return p5.strandsNode(args[0]).y; | ||
| }); | ||
|
|
||
| const originalBlue = fn.blue; | ||
| augmentFn(fn, p5, 'blue', function (...args) { | ||
| if (!strandsContext.active) { | ||
| return originalBlue.apply(this, args); | ||
| } | ||
| return p5.strandsNode(args[0]).z; | ||
| }); | ||
|
|
||
| const originalAlpha = fn.alpha; | ||
| augmentFn(fn, p5, 'alpha', function (...args) { | ||
| if (!strandsContext.active) { | ||
| return originalAlpha.apply(this, args); | ||
| } | ||
| return p5.strandsNode(args[0]).w; | ||
| }); | ||
|
|
||
| // HSB/HSL accessors — inject conversion helpers and extract channel | ||
| const _hsbSnippet = `vec3 _p5_rgb2hsb(vec3 c) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is GLSL-specific. Is it possible to either build this out of existing functionality, similar to what you were doing with |
||
| vec4 K = vec4(0.0, -1.0/3.0, 2.0/3.0, -1.0); | ||
| vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); | ||
| vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); | ||
| float d = q.x - min(q.w, q.y); | ||
| float e = 1.0e-10; | ||
| return vec3(abs(q.z+(q.w-q.y)/(6.0*d+e)), d/(q.x+e), q.x); | ||
| }`; | ||
|
|
||
| const _hslSnippet = `vec3 _p5_rgb2hsl(vec3 c) { | ||
| float maxC = max(c.r, max(c.g, c.b)); | ||
| float minC = min(c.r, min(c.g, c.b)); | ||
| float l = (maxC + minC) / 2.0; | ||
| float d = maxC - minC; | ||
| float s = d < 1e-10 ? 0.0 : d/(1.0-abs(2.0*l-1.0)); | ||
| float h = 0.0; | ||
| if (d > 1e-10) { | ||
| if (maxC == c.r) h = mod((c.g-c.b)/d, 6.0)/6.0; | ||
| else if (maxC == c.g) h = ((c.b-c.r)/d+2.0)/6.0; | ||
| else h = ((c.r-c.g)/d+4.0)/6.0; | ||
| } | ||
| return vec3(h, s, l); | ||
| }`; | ||
|
|
||
| function _injectSnippet(snippet) { | ||
| strandsContext.vertexDeclarations.add(snippet); | ||
| strandsContext.fragmentDeclarations.add(snippet); | ||
| strandsContext.computeDeclarations.add(snippet); | ||
| } | ||
|
|
||
| const originalHue = fn.hue; | ||
| augmentFn(fn, p5, 'hue', function (...args) { | ||
| if (!strandsContext.active) { | ||
| return originalHue.apply(this, args); | ||
| } | ||
| _injectSnippet(_hslSnippet); | ||
| const colorNode = p5.strandsNode(args[0]); | ||
| const { id, dimension } = build.functionCallNode( | ||
| strandsContext, '_p5_rgb2hsl', | ||
| [fn.vec3(colorNode.x, colorNode.y, colorNode.z)], | ||
| { overloads: [{ params: [DataType.float3], returnType: DataType.float3 }] } | ||
| ); | ||
| return createStrandsNode(id, dimension, strandsContext).x; | ||
| }); | ||
|
|
||
| const originalSaturation = fn.saturation; | ||
| augmentFn(fn, p5, 'saturation', function (...args) { | ||
| if (!strandsContext.active) { | ||
| return originalSaturation.apply(this, args); | ||
| } | ||
| _injectSnippet(_hslSnippet); | ||
| const colorNode = p5.strandsNode(args[0]); | ||
| const { id, dimension } = build.functionCallNode( | ||
| strandsContext, '_p5_rgb2hsl', | ||
| [fn.vec3(colorNode.x, colorNode.y, colorNode.z)], | ||
| { overloads: [{ params: [DataType.float3], returnType: DataType.float3 }] } | ||
| ); | ||
| return createStrandsNode(id, dimension, strandsContext).y; | ||
| }); | ||
|
|
||
| const originalBrightness = fn.brightness; | ||
| augmentFn(fn, p5, 'brightness', function (...args) { | ||
| if (!strandsContext.active) { | ||
| return originalBrightness.apply(this, args); | ||
| } | ||
| _injectSnippet(_hsbSnippet); | ||
| const colorNode = p5.strandsNode(args[0]); | ||
| const { id, dimension } = build.functionCallNode( | ||
| strandsContext, '_p5_rgb2hsb', | ||
| [fn.vec3(colorNode.x, colorNode.y, colorNode.z)], | ||
| { overloads: [{ params: [DataType.float3], returnType: DataType.float3 }] } | ||
| ); | ||
| return createStrandsNode(id, dimension, strandsContext).z; | ||
| }); | ||
|
|
||
| const originalLightness = fn.lightness; | ||
| augmentFn(fn, p5, 'lightness', function (...args) { | ||
| if (!strandsContext.active) { | ||
| return originalLightness.apply(this, args); | ||
| } | ||
| _injectSnippet(_hslSnippet); | ||
| const colorNode = p5.strandsNode(args[0]); | ||
| const { id, dimension } = build.functionCallNode( | ||
| strandsContext, '_p5_rgb2hsl', | ||
| [fn.vec3(colorNode.x, colorNode.y, colorNode.z)], | ||
| { overloads: [{ params: [DataType.float3], returnType: DataType.float3 }] } | ||
| ); | ||
| return createStrandsNode(id, dimension, strandsContext).z; | ||
| }); | ||
|
|
||
| augmentFn(fn, p5, 'getTexture', function (...rawArgs) { | ||
| if (strandsContext.active) { | ||
| const { id, dimension } = strandsContext.backend.createGetTextureCall(strandsContext, rawArgs); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice!