Transformation Matrix for p5.Strands - #8984
Conversation
|
|
||
| const originalScale = fn.scale; | ||
| augmentFn(fn, p5, 'scale', function (...args) { | ||
| const t = args[0]; | ||
| if (!isStrandsTransform(t)) return originalScale.apply(this, args); | ||
| const scales = args.slice(1); | ||
| const uniform = scales.length === 1; // scale(t, s) scales every axis by s | ||
| const x = scales[0] ?? 1; | ||
| const y = scales[1] ?? (uniform ? x : 1); | ||
| const z = scales[2] ?? (uniform ? x : 1); | ||
| return transformStep(t, | ||
| [x, 0, 0, 0, y, 0, 0, 0, 1], | ||
| [x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, 1]); | ||
| }); | ||
|
|
||
| const originalRotate = fn.rotate; | ||
| augmentFn(fn, p5, 'rotate', function (...args) { | ||
| const t = args[0]; | ||
| if (!isStrandsTransform(t)) return originalRotate.apply(this, args); | ||
| const angle = args[1]; // 2D: rotate in-plane; 3D: rotate about the Z axis | ||
| const c = this.cos(angle); | ||
| const s = this.sin(angle); | ||
| const ns = s.mult(-1); // -sin | ||
| return transformStep(t, | ||
| [c, s, 0, ns, c, 0, 0, 0, 1], | ||
| [c, s, 0, 0, ns, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); | ||
| }); | ||
|
|
||
| augmentFn(fn, p5, 'skewX', function (...args) { | ||
| if (!strandsContext.active) { | ||
| p5._friendlyError(`It looks like you've called skewX outside of a shader's modify() function.`); | ||
| return; | ||
| } | ||
| const [t, angle] = args; | ||
| const k = this.tan(angle); // x' = x + tan(angle) * y | ||
| return transformStep(t, | ||
| [1, 0, 0, k, 1, 0, 0, 0, 1], | ||
| [1, 0, 0, 0, k, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); | ||
| }); | ||
|
|
||
| augmentFn(fn, p5, 'skewY', function (...args) { | ||
| if (!strandsContext.active) { | ||
| p5._friendlyError(`It looks like you've called skewY outside of a shader's modify() function.`); | ||
| return; | ||
| } | ||
| const [t, angle] = args; | ||
| const k = this.tan(angle); // y' = y + tan(angle) * x | ||
| return transformStep(t, | ||
| [1, k, 0, 0, 1, 0, 0, 0, 1], | ||
| [1, k, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); | ||
| }); | ||
|
|
||
| augmentFn(fn, p5, 'rotateAxisAngle', function (...args) { | ||
| if (!strandsContext.active) { | ||
| p5._friendlyError(`It looks like you've called rotateAxisAngle outside of a shader's modify() function.`); | ||
| return; | ||
| } | ||
| const [t, axis, angle] = args; | ||
| if (!t?.isStrandsNode || t.typeInfo().baseType !== BaseType.MAT || t.dimension !== 4) { | ||
| FES.userError('type error', | ||
| 'rotateAxisAngle() only works on a 3D transform created with transform3D().'); | ||
| } | ||
| const n = this.normalize(axis); | ||
| const x = n.x, y = n.y, z = n.z; | ||
| const c = this.cos(angle); | ||
| const s = this.sin(angle); | ||
| const omc = p5.strandsNode(1).sub(c); // 1 - cos |
There was a problem hiding this comment.
Hi @davepagurek ,
Just thinking out loud. In our non-strands modules, each area of functionality lives in its own file, for example, transform.js keeps the JSDoc, examples, and implementation together in one place.
I was thinking of following that same convention for the strands transforms: moving the matrix and transform helpers out of strands_api.js into a dedicated strands_transform.js (something better name for the file), with each function's docs kept alongside its implementation.
One thing to check: right now the strands functions are documented centrally in p5.strands.js rather than inline. Would you prefer keeping that, or colocating the transform docs with the code in the new file?
|
Just starte looking at this one, really exciting! I will work on some more complex sketches to test it out, but I appreciate the docs you've included out of the box. Would it be a good idea to add some bench and/or unit and/or visual tests here? I think visual and unit at minimum, given the scope of the PR, would be good... |
Ah, correct. I'm also getting feedback and testing it with more complex examples myself. For now, I'm trying to find ways to break the feature and catch any issues. Once I'm confident with the code, I'll add tests for them. |
| * | ||
| * function slide() { | ||
| * getWorldInputs((inputs) => { | ||
| * // translate() returns a new transform, so assign the result back. |
There was a problem hiding this comment.
Maybe add an inline URL here? Or add a note at the end of the transform() docs to mention strands + link to the transformPoint docs as the spot with more in depth explanation
There was a problem hiding this comment.
Or interleave some writing before the strands example saying the same thing
| * } | ||
| * | ||
| * function wobble() { | ||
| * getWorldInputs((inputs) => { |
There was a problem hiding this comment.
Can we use worldInputs.begin() and worldInputs.end() in the examples for consistency?
|
Some feedback:
|
|
oh and one more thing that I forgot: add on to the logging system for experimental features in #9109. It sounds like we don't want a whole new contributor doc, but we just want to add a SECTION to the strands doc, and then link to that section via a |
Resolves #8953
Changes:
Screenshots of the change:
PR Checklist
npm run lintpasses