From c7b9db0d1bf3ae55c7e70207c636ced8606902ec Mon Sep 17 00:00:00 2001 From: pommicket Date: Wed, 6 Sep 2023 22:40:08 -0400 Subject: add some more widgets, cleanup --- guide-src/index.html | 122 ++---------------- guide-src/introduction.html | 118 +++++++++++++++++ guide-src/outline.txt | 1 + guide-src/widget-inputs.html | 3 +- index.html | 32 +---- pugl.js | 294 +++++++++++++++++++++++++++++++++++-------- style.css | 3 + things.txt | 3 + 8 files changed, 389 insertions(+), 187 deletions(-) create mode 100644 guide-src/introduction.html diff --git a/guide-src/index.html b/guide-src/index.html index 0e765b4..e590fc7 100644 --- a/guide-src/index.html +++ b/guide-src/index.html @@ -1,118 +1,20 @@ ---- introduction +--- about -

your first pugl

+

pugl

+pommicket's utility for gl shaders +

-when you load up pugl for the first time, you should be greeted with a “Buffer” widget. -try changing its “input” value to .pos.x. -

- -
-you should see a nice gradient like this: -here the color of each pixel is directly determined by its x coordinate. -specifically: .pos.x is −1 at the left side of the screen and +1 at the right side of the screen. -since the “Buffer” widget’s title is in yellow, -the pixel values will be drawn from it. 0 (or anything below 0) is black and 1 (or anything above 1) is white, -so we see a gradient from black to white starting from the center of the screen. + pugl is a tool that lets you create & share shaders without writing any code.

-now let’s try something a little more interesting. try adding a “Multiply” widget (by searching for it or -selecting it from the “math” section). set the “a” input to .pos.x and the “b” input to -.pos.y. then click on the “Multiply” text to set it as the active widget. -

- -
-you should now see a more interesting pattern where two of the corners of the screen are -white, and the other two corners are black: -

- -

vectors

- -

-well, black & white is pretty boring. let’s try making some colors! -one of the nice things about shaders is that they’re very good at dealing with vectors. -there’s a lot of mathematical theory behind vectors, but for our purposes all that really matters is that a vector is a list of numbers (called components). -in shaders you basically only deal with vectors with 2 to 4 components (referred to as x, y, z, w). -in graphics programming, colors are represented as vectors with 3 components, red, -green, and blue, which go from 0 to 1. -try putting 0,0.8,1 in a “Buffer” widget and making it active. -now the widget is outputting a vector with x=0, y=0.8, and z=1, so -you’ll get a nice greenish blue color! -

- -

-.pos is itself a vector, so you can just throw it into the Buffer input: -

-notice how the output is red on the right side of the screen (where the x component of .pos is high) -and green at the top of the screen (where the y component of .pos is high). -

- -

-most widgets like Multiply work on both numbers and vectors. try multiplying together -.pos and .pos.x: -

-this multiplies each of the components of .pos by .pos.x. -so the top-left corner is red, because (−1, 1) × −1 = (1, −1), so the top-left pixel gets a red value of 1 and a green value of −1. -

- -

multiple widgets

- -

-you can use the output of one widget to specify the input of another widget using its name. -try creating an “Add” widget with inputs mul1,0 and 0,0,.pos.x (assuming -your Multiply widget from the last section was called mul1). -

-now the left side looks the same as before, but the right side (where .pos.x is 1) is bluer! -

- -

putting everything together

- -

-alright let's use all this to make something cool. -we'll start by making a rainbow. -the “Hue shift” widget shifts the hue of a color through the rainbow. -so if we start with a color of red, and shift it by .pos.x, -we'll get a rainbow across the screen: -

- -
-

- -

-now let's animate this rainbow: create a new “Add” widget, -and set a=.pos.x, b=.time, -and use that as the shift instead of .pos.x: -

-now the rainbow moves across the screen over time! -

- -

-next we’ll use the widget that makes pugl unique ☺ -“Last frame”. this lets you grab pixel values from the previous frame -to use in the current frame. add a “Last frame” widget, -and set it as the active widget. notice how the rainbow freezes in place — -that’s because each pixel value is just being determined by what it was -on the last frame. -

+ to learn how to use pugl, check out the next section.

+

credits

+

-now add a “Weighted sum” widget, set “a weight” to 0.95, “b weight” to 0.05, -”a” to your last frame widget's output, and “b” to the hue shift's output. -this will output a value which is 95% like the previous frame's value, -and 5% like the shifting rainbow. -

-try switching between the “Hue shift” and “Weighted sum” widgets -and note how the weighted sum is blurrier, because it's averaging -with the previous pixel value. +icon based on +Long-beakedEchidna.jpg from WikiMedia +CC BY-SA 3.0.

-now, we don’t have to use .pos as the position for getting -pixel values from the last frame. instead, let’s rotate .pos -by a small amount and use that as the position for the “Last frame” widget: -

-now we’re getting something interesting! -

- -

-be sure to check out the rest of this guide to learn all the things -you can do with pugl. have fun! 🐱 -

+everything else is licensed under WTFPLv2. diff --git a/guide-src/introduction.html b/guide-src/introduction.html new file mode 100644 index 0000000..5dd2bdc --- /dev/null +++ b/guide-src/introduction.html @@ -0,0 +1,118 @@ +--- introduction + +

your first pugl

+

+when you load up pugl for the first time, you should be greeted with a “Buffer” widget. +try changing its “input” value to .pos.x. +

+ +
+you should see a nice gradient like this: +here the color of each pixel is directly determined by its x coordinate. +specifically: .pos.x is −1 at the left side of the screen and +1 at the right side of the screen. +since the “Buffer” widget’s title is in yellow, +the pixel values will be drawn from it. 0 (or anything below 0) is black and 1 (or anything above 1) is white, +so we see a gradient from black to white starting from the center of the screen. +

+

+now let’s try something a little more interesting. try adding a “Multiply” widget (by searching for it or +selecting it from the “math” section). set the “a” input to .pos.x and the “b” input to +.pos.y. then click on the “Multiply” text to set it as the active widget. +

+ +
+you should now see a more interesting pattern where two of the corners of the screen are +white, and the other two corners are black: +

+ +

vectors

+ +

+well, black & white is pretty boring. let’s try making some colors! +one of the nice things about shaders is that they’re very good at dealing with vectors. +there’s a lot of mathematical theory behind vectors, but for our purposes all that really matters is that a vector is a list of numbers (called components). +in shaders you basically only deal with vectors with 2 to 4 components (referred to as x, y, z, w). +in graphics programming, colors are represented as vectors with 3 components, red, +green, and blue, which go from 0 to 1. +try putting 0,0.8,1 in a “Buffer” widget and making it active. +now the widget is outputting a 3D vector with x=0, y=0.8, and z=1, so +you’ll get a nice greenish blue color! +

+ +

+.pos is itself a vector, so you can just throw it into the Buffer input: +

+notice how the output is red on the right side of the screen (where the x component of .pos is high) +and green at the top of the screen (where the y component of .pos is high). +

+ +

+most widgets like Multiply work on both numbers and vectors. try multiplying together +.pos and .pos.x: +

+this multiplies each of the components of .pos by .pos.x. +so the top-left corner is red, because (−1, 1) × −1 = (1, −1), so the top-left pixel gets a red value of 1 and a green value of −1. +

+ +

multiple widgets

+ +

+you can use the output of one widget to specify the input of another widget using its name. +try creating an “Add” widget with inputs mul1,0 and 0,0,.pos.x (assuming +your Multiply widget from the last section was called mul1). +

+now the left side looks the same as before, but the right side (where .pos.x is 1) is bluer! +

+ +

putting everything together

+ +

+alright let's use all this to make something cool. +we'll start by making a rainbow. +the “Hue shift” widget shifts the hue of a color through the rainbow. +so if we start with a color of red, and shift it by .pos.x, +we'll get a rainbow across the screen: +

+ +
+

+ +

+now let's animate this rainbow: create a new “Add” widget, +and set a=.pos.x, b=.time, +and use that as the shift instead of .pos.x: +

+now the rainbow moves across the screen over time! +

+ +

+next we’ll use the widget that makes pugl unique ☺ +“Last frame”. this lets you grab pixel values from the previous frame +to use in the current frame. add a “Last frame” widget, +and set it as the active widget. notice how the rainbow freezes in place — +that’s because each pixel value is just being determined by what it was +on the last frame. +

+

+

+now add a “Weighted sum” widget, set “a weight” to 0.95, “b weight” to 0.05, +”a” to your last frame widget's output, and “b” to the hue shift's output. +this will output a value which is 95% like the previous frame's value, +and 5% like the shifting rainbow. +

+try switching between the “Hue shift” and “Weighted sum” widgets +and note how the weighted sum is blurrier, because it's averaging +with the previous pixel value. +

+

+now, we don’t have to use .pos as the position for getting +pixel values from the last frame. instead, let’s rotate .pos +by a small amount and use that as the position for the “Last frame” widget: +

+now we’re getting something interesting! +

+ +

+be sure to check out the rest of this guide to learn all the things +you can do with pugl. have fun! 🐱 +

diff --git a/guide-src/outline.txt b/guide-src/outline.txt index df12f5d..a2d8357 100644 --- a/guide-src/outline.txt +++ b/guide-src/outline.txt @@ -1,4 +1,5 @@ index.html +introduction.html widget-inputs.html development: development /index.html diff --git a/guide-src/widget-inputs.html b/guide-src/widget-inputs.html index f0e8bdb..048c75f 100644 --- a/guide-src/widget-inputs.html +++ b/guide-src/widget-inputs.html @@ -25,7 +25,7 @@ all of pugl's built-in values begin with a . to distinguish them from your widgets. here they all are. below, float refers to a plain old number, -vec2 is a 2-component vector, etc. +vec2 is a 2D vector, etc. @@ -37,5 +37,6 @@ below, float refers to a plain old number, +
.mouse01vec2the position of the mouse ranging from (0, 0) to (+1, +1).
.pifloatπ (3.1415…).
.2pifloat2π (6.2831…).
.efloat𝑒 (2.7182…).
diff --git a/index.html b/index.html index 16a7c50..8bbbeb8 100644 --- a/index.html +++ b/index.html @@ -37,9 +37,11 @@ copy link Copied link! - + + + @@ -98,30 +100,6 @@
error
- -

pugl

- pommicket's utility for gl shaders -
-

- pugl is a tool that lets you create & share shaders without writing any code. -

-

- to learn how to use pugl, check out the guide. -

-

credits

-
-

- icon based on - Long-beakedEchidna.jpg from WikiMedia - CC BY-SA 3.0. -

-

- everything else is licensed under WTFPLv2. -

-
- -
-

saved creations


diff --git a/pugl.js b/pugl.js index f5de8b2..3c40494 100644 --- a/pugl.js +++ b/pugl.js @@ -76,7 +76,7 @@ const builtin_widgets = [ ${type} buffer(${type} x) { return x; }` - ).join('\n'), + ).join(''), ` //! .name: Slider //! .category: basic @@ -94,7 +94,8 @@ float slider(float x, float min_val, float max_val) { } `, ` -//! .name: Mix (lerp) +//! .name: Mix +//! .alt: lerp //! .category: basic //! .id: mix //! .description: weighted average of two inputs @@ -115,7 +116,7 @@ ${type} mix_(${type} a, ${type} b, ${type} x, int c) { return mix(a, b, x); } ` - ).join('\n'), + ).join(''), ` //! .name: Last frame //! .category: basic @@ -145,7 +146,7 @@ vec3 last_frame(vec2 pos, int wrap, int samp) { ` //! .name: Weighted sum //! .alt: weighted add -//! .category: math +//! .category: arithmetic //! .description: add two numbers or vectors with weights //! aw.name: a weight //! aw.default: 1 @@ -159,10 +160,10 @@ ${type} wtadd(${type} a, float aw, ${type} b, float bw) { return a * aw + b * bw; } ` - ).join('\n'), + ).join(''), ` //! .name: Add -//! .category: math +//! .category: arithmetic //! .description: add two numbers or vectors ` + @@ -172,10 +173,10 @@ ${type} add(${type} a, ${type} b) { return a + b; } ` - ).join('\n'), + ).join(''), ` //! .name: Subtract -//! .category: math +//! .category: arithmetic //! .description: subtract one number or vector from another ` + @@ -185,10 +186,10 @@ ${type} sub(${type} a, ${type} b) { return a - b; } ` - ).join('\n'), + ).join(''), ` //! .name: Multiply -//! .category: math +//! .category: arithmetic //! .description: multiply two numbers, scale a vector by a number, or perform component-wise multiplication between vectors ` + GLSL_FLOAT_TYPES.map( @@ -197,10 +198,10 @@ ${type} mul(${type} a, ${type} b) { return a * b; } ` - ).join('\n'), + ).join(''), ` //! .name: Divide -//! .category: math +//! .category: arithmetic //! .description: divide one number or vector by another ` + GLSL_FLOAT_TYPES.map( @@ -209,12 +210,14 @@ ${type} div(${type} a, ${type} b) { return a / b; } ` - ).join('\n'), + ).join(''), ` //! .name: Power -//! .category: math +//! .category: arithmetic //! .id: pow //! .description: take one number to the power of another +//! a.id: a +//! b.default: 0.5 ` + GLSL_FLOAT_TYPES.map( (type) => ` @@ -222,10 +225,10 @@ ${type} pow_(${type} a, ${type} b) { return pow(a, b); } ` - ).join('\n'), + ).join(''), ` //! .name: Modulo -//! .category: math +//! .category: arithmetic //! .id: mod //! .description: wrap a value at a certain limit //! a.name: a @@ -237,7 +240,7 @@ ${type} mod_(${type} a, ${type} b) { return mod(a, b); } ` - ).join('\n'), + ).join(''), ` //! .name: Square //! .category: geometry @@ -271,9 +274,9 @@ ${type2} square(${type} pos, ${type2} inside, ${type2} outside, ${type} size) { } ` ) - .join('\n'); + .join(''); }) - .join('\n'), + .join(''), ` //! .name: Circle //! .category: geometry @@ -295,7 +298,7 @@ ${type2} circle(${type} pos, ${type2} inside, ${type2} outside, ${type} size) { return dot(pos, pos) < 1.0 ? inside : outside; } ` - ).join('\n'), + ).join(''), ` //! .name: Comparator //! .category: basic @@ -319,7 +322,7 @@ ${type} compare(float cmp1, float cmp2, ${type} less, ${type} greater) { return cmp1 < cmp2 ? less : greater; } ` - ).join('\n'), + ).join(''), ` //! .name: Sine wave //! .category: curves @@ -363,7 +366,7 @@ ${type} sine_wave(int type, ${type} t, ${type} period, ${type} amp, ${type} phas return amp * v + center; } ` - ).join('\n'), + ).join(''), ` //! .name: Rotate 2D //! .category: geometry @@ -463,7 +466,7 @@ ${type} brightcont(${type} color, ${type} brightness, ${type} contrast) { return clamp((contrast + 1.0) / (1.0 - contrast) * (color - 0.5) + (brightness + 0.5), 0.0, 1.0); } ` - ).join('\n'), + ).join(''), ` //! .name: Clamp //! .category: basic @@ -483,7 +486,7 @@ ${type} clamp_(${type} x, ${type} minimum, ${type} maximum) { return clamp(x, minimum, maximum); } ` - ).join('\n'), + ).join(''), ` //! .name: Rotate 3D //! .id: rot3 @@ -530,7 +533,7 @@ ${type} remap(${type} x, ${type} a1, ${type} b1, ${type} a2, ${type} b2) { return (x - a1) / (b1 - a1) * (b2 - a2) + a2; } ` - ).join('\n'), + ).join(''), ` //! .name: Smoothstep //! .id: smoothstep @@ -557,11 +560,11 @@ ${type} smoothst(${type} t, ${type} t1, ${type} t2, ${type} out1, ${type} out2) return mix(out1, out2, smoothstep(t1, t2, t)); } ` - ).join('\n'), + ).join(''), ` //! .name: Arctangent //! .id: arctan2 -//! .category: math +//! .category: trigonometry //! .description: The arctangent function (radians) with 2 parameters (set x = 1 for normal arctangent) //! y.id: y //! x.id: x @@ -574,25 +577,53 @@ ${type} arctan2(${type} y, ${type} x) { return atan(y, x); } ` - ).join('\n'), + ).join(''), + ` +//! .name: Cosine +//! .id: cos +//! .category: trigonometry +//! .description: The cosine function (radians) + +` + + GLSL_FLOAT_TYPES.map( + (type) => ` +${type} cos_(${type} x) { + return cos(x); +} +` + ).join(''), + ` +//! .name: Sine +//! .id: sine +//! .category: trigonometry +//! .description: The sine function (radians) + +` + + GLSL_FLOAT_TYPES.map( + (type) => ` +${type} sin_(${type} x) { + return sin(x); +} +` + ).join(''), ` //! .name: Tangent //! .id: tan -//! .category: math +//! .category: trigonometry //! .description: The tangent function (radians) ` + GLSL_FLOAT_TYPES.map( (type) => ` -${type} tang(${type} x) { +${type} tan_(${type} x) { return tan(x); } ` - ).join('\n'), + ).join(''), ` //! .name: Arcsine //! .id: arcsin -//! .category: math +//! .category: trigonometry //! .description: The arcsine function (radians) — input will be clamped to [−1, 1] ` + @@ -602,7 +633,114 @@ ${type} arcsin(${type} x) { return asin(clamp(x, -1.0, 1.0)); } ` - ).join('\n'), + ).join(''), + ` +//! .name: Arccosine +//! .id: arccos +//! .category: trigonometry +//! .description: The arccosine function (radians) — input will be clamped to [−1, 1] + +` + + GLSL_FLOAT_TYPES.map( + (type) => ` +${type} arccos(${type} x) { + return acos(clamp(x, -1.0, 1.0)); +} +` + ).join(''), + ` +//! .name: Hyperbolic cosine +//! .alt: cosh +//! .id: cosh +//! .category: trigonometry +//! .description: The hyperbolic cosine function + +` + + GLSL_FLOAT_TYPES.map( + (type) => ` +${type} cosh_(${type} x) { + return cosh(x); +} +` + ).join(''), + ` +//! .name: Hyperbolic sine +//! .alt: sinh +//! .id: sinh +//! .category: trigonometry +//! .description: The hyperbolic sine function + +` + + GLSL_FLOAT_TYPES.map( + (type) => ` +${type} sinh_(${type} x) { + return sinh(x); +} +` + ).join(''), + ` +//! .name: Hyperbolic tangent +//! .alt: tanh +//! .id: tanh +//! .category: trigonometry +//! .description: The hyperbolic tangent function (radians) + +` + + GLSL_FLOAT_TYPES.map( + (type) => ` +${type} tanh_(${type} x) { + return tanh(x); +} +` + ).join(''), + ` +//! .name: Hyperbolic arccosine +//! .alt: acosh +//! .alt: arccosh +//! .id: arccosh +//! .category: trigonometry +//! .description: The hyperbolic arccosine function (radians) — input will be clamped to [1, ∞] + +` + + GLSL_FLOAT_TYPES.map( + (type) => ` +${type} arccosh(${type} x) { + return acosh(max(x, 1.0)); +} +` + ).join(''), + ` +//! .name: Hyperbolic arcsine +//! .alt: asinh +//! .alt: arcsinh +//! .id: arcsinh +//! .category: trigonometry +//! .description: The hyperbolic arcsine function (radians) + +` + + GLSL_FLOAT_TYPES.map( + (type) => ` +${type} arcsinh(${type} x) { + return asinh(x); +} +` + ).join(''), + ` +//! .name: Hyperbolic arctangent +//! .alt: atanh +//! .alt: arctanh +//! .id: arctanh +//! .category: trigonometry +//! .description: The hyperbolic arctangent function (radians) — input will be clamped to [-1, 1] + +` + + GLSL_FLOAT_TYPES.map( + (type) => ` +${type} arctanh(${type} x) { + return atanh(clamp(x, -1.0, 1.0)); +} +` + ).join(''), ` //! .name: Sigmoid //! .id: sigmoid @@ -620,9 +758,9 @@ ${type} sigmoid(${type} x, ${type} a, ${type} b, ${type} sharpness) { return mix(a, b, 1.0 / (1.0 + exp(-sharpness * x))); } ` - ).join('\n'), + ).join(''), ` -//! .name: Staircase (floor) +//! .name: Staircase //! .id: floor //! .category: curves //! .description: The floor function — largest integer less than x @@ -640,7 +778,7 @@ ${type} floorf(${type} x, ${type} stepw, ${type} steph, ${type} phase) { return floor(x / stepw + phase) * steph; } ` - ).join('\n'), + ).join(''), ` //! .name: Sine noise //! .category: noise @@ -733,7 +871,7 @@ float norm(vec4 x) { return length(x); } (type) => ` float dist(${type} x, ${type} y) { return distance(x, y); } ` - ).join('\n'), + ).join(''), ` //! .name: Dot product //! .description: the dot product between two vectors @@ -745,7 +883,17 @@ float dist(${type} x, ${type} y) { return distance(x, y); } (type) => ` float dot_prod(${type} x, ${type} y) { return dot(x, y); } ` - ).join('\n'), + ).join(''), + ` +//! .name: Cross product +//! .description: the cross product between two 3D vectors +//! .category: geometry +//! .id: cross + +vec3 cross_(vec3 x, vec3 y) { + return cross(x, y); +} +`, ` //! .name: White noise //! .description: Uniform distribution over [0, 1) @@ -969,27 +1117,75 @@ float worley(vec3 p, vec3 freq) { ` //! .name: Minimum //! .description: minimum of two values -//! .category: math +//! .category: basic //! .id: min ` + GLSL_FLOAT_TYPES.map( (type) => ` -${type} _min(${type} a, ${type} b) { +${type} min_(${type} a, ${type} b) { return min(a, b); }` - ).join('\n'), + ).join(''), ` //! .name: Maximum //! .description: maximum of two values -//! .category: math +//! .category: basic //! .id: max ` + GLSL_FLOAT_TYPES.map( (type) => ` -${type} _max(${type} a, ${type} b) { +${type} max_(${type} a, ${type} b) { return max(a, b); }` - ).join('\n'), + ).join(''), + ` +//! .name: Absolute value +//! .id: abs +//! .description: absolute value of number +//! .category: basic +` + + GLSL_FLOAT_TYPES.map( + (type) => ` +${type} abs_(${type} x) { + return abs(x); +}` + ).join(''), + ` +//! .name: Floor +//! .id: floor_ +//! .description: the floor function — greatest integer smaller than x +//! .category: basic +` + + GLSL_FLOAT_TYPES.map( + (type) => ` +${type} floor_(${type} x) { + return floor(x); +}` + ).join(''), + ` +//! .name: Ceiling +//! .id: ceil +//! .description: the ceiling function — smallest integer greater than x +//! .category: basic +` + + GLSL_FLOAT_TYPES.map( + (type) => ` +${type} ceil_(${type} x) { + return ceil(x); +}` + ).join(''), + ` +//! .name: Round +//! .id: round +//! .description: round to nearest integer +//! .category: basic +` + + GLSL_FLOAT_TYPES.map( + (type) => ` +${type} round_(${type} x) { + return round(x); +}` + ).join(''), ]; function get_creation_title() { @@ -1334,6 +1530,8 @@ for (const code of builtin_widgets) { const result = parse_widget_definition(code); if (result && result.error) { console.error(result.error); + } else if (widget_info.has(result.id)) { + console.error('duplicate widget id', result.id); } else { widget_info.set(result.id, result); } @@ -1952,6 +2150,8 @@ ${this.code.join('')} case '.2π': case '.2pi': return { code: '(6.2831853)', type: 'float' }; + case '.e': + return { code: '(2.7182818)', type: 'float' }; default: return { error: `no such builtin: ${input}` }; } @@ -2523,10 +2723,6 @@ function startup() { } }); - document.getElementById('about-button').addEventListener('click', () => { - document.getElementById('about-dialog').showModal(); - }); - document.getElementById('list-creations').addEventListener('click', () => { const container = document.getElementById('creations'); container.innerHTML = ''; @@ -2603,7 +2799,7 @@ function startup() { }); document.getElementById('code-form').addEventListener('submit', () => { - import_widgets(code_input.value); + new_creation(code_input.value); }); pause_element = document.getElementById('pause'); diff --git a/style.css b/style.css index 78debd7..2f15ea4 100644 --- a/style.css +++ b/style.css @@ -338,6 +338,9 @@ input[type='number'] { .inline-block { display: inline-block; } +.no-text-decoration { + text-decoration: none; +} .no-wrap { whitespace: no-wrap; } diff --git a/things.txt b/things.txt index e27e313..bc1692d 100644 --- a/things.txt +++ b/things.txt @@ -10,3 +10,6 @@ floor;n:floor1;ix:.pos01.x;istepw:0.1;isteph:0.1,0.1,0.2;iphase:0;;_out=floor1 noisy: buffer;n:output;iinput:sn1,sn2,sn3;;rot2;n:rot21;iv:.pos;itheta:mul1.w;cdir:0;;circle;n:circle1;ipos:.pos;iinside:rot21,0;ioutside:#000;isize:0.8;;noise_sin;n:sn1;ix:rot21.xy,mul1.x;;remap;n:remap1;ix:.pos;ia1:-1;ib1:1;ia2:0;ib2:6.28;;noise_sin;n:sn2;ix:rot21.xy,mul1.y;;noise_sin;n:sn3;ix:rot21.xy,mul1.z;;mul;n:mul1;ia:.time;ib:0.2,0.4,0.6,0.1;;prev;n:prev1;ipos:.pos;cwrap:0;csample:0;;_out=output + +simple cross product: +_title=cross;;cross;n:cross1;ix:.pos.xy,1;iy:0.2,.pos.xy;;_out=cross1 -- cgit v1.2.3