-
Notifications
You must be signed in to change notification settings - Fork 0
/
rainbowsand.sttf
1 lines (1 loc) · 100 KB
/
rainbowsand.sttf
1
{"links":[{"end":"Image","filter":"Linear","slot":3,"start":"Keyboard","wrapMode":"Clamp"},{"end":"RenderOutput","filter":"Linear","slot":0,"start":"Image","wrapMode":"Repeat"},{"end":"Buffer A","filter":"Linear","slot":3,"start":"Keyboard","wrapMode":"Clamp"},{"end":"Buffer B","filter":"Linear","slot":3,"start":"Keyboard","wrapMode":"Clamp"},{"end":"Buffer C","filter":"Linear","slot":3,"start":"Keyboard","wrapMode":"Clamp"},{"end":"Buffer D","filter":"Linear","slot":3,"start":"Keyboard","wrapMode":"Clamp"},{"end":"Image","filter":"Nearest","slot":0,"start":"Buffer A","wrapMode":"Clamp"},{"end":"Buffer A","filter":"Linear","slot":0,"start":"LastFrame","wrapMode":"Clamp"},{"end":"Buffer B","filter":"Linear","slot":0,"start":"Buffer A","wrapMode":"Clamp"},{"end":"Buffer C","filter":"Linear","slot":0,"start":"Buffer B","wrapMode":"Clamp"},{"end":"Buffer D","filter":"Linear","slot":0,"start":"Buffer C","wrapMode":"Clamp"}],"metadata":{"Author":"fenix","Description":"Will people like a shader that is interactive-only? What if it has a snazzy UI and content-aware anti-aliasing?\n* Dbl click on tool bar to: erase all, erase walls, change palette, select preset wall config\n* Shift to disable anti-aliasing","Name":"Rainbow Sand Playground","ShaderToyURL":"https://www.shadertoy.com/view/stdyRr"},"nodes":[{"class":"RenderOutput","name":"RenderOutput"},{"class":"GLSLShader","name":"Image","source":"// options\nconst float WALL_SIZE = 0.0025;\nconst float SPAWN_SIZE = 0.01;\nconst int COLOR_CHANGE_FRAMES = 200;\n\n// tool bar selections\n#define S_RAINBOW 7.0\n#define S_WALL 8.0\n#define S_ERASE 9.0\n#define SELECT_CHOICES 10.0\n\n// special pixel values\nconst vec4 EMPTY = vec4(0);\nconst vec4 WALL = vec4(1);\n\nfloat length2(vec2 v)\n{\n return dot(v, v);\n}\n\nfloat square(float x)\n{\n return x * x;\n}\n\nconst int NUM_PALLETTES = 3;\n\nvec4 rainbow(int i, int palette)\n{\n switch(palette)\n {\n case 0: // actual rainbow\n switch(i)\n {\n case 0:\n return vec4(1.0, 0.0, 0.0, 2.0);\n case 1:\n return vec4(1.0, 0.5, 0.0, 3.0);\n case 2:\n return vec4(1.0, 1.0, 0.0, 4.0);\n case 3:\n return vec4(0.0, 1.0, 0.0, 5.0);\n case 4:\n return vec4(0.0, 0.0, 1.0, 6.0);\n case 5:\n return vec4(0.25, 0.0, 0.5, 7.0);\n case 6:\n return vec4(0.5, 0.0, 0.7, 8.0);\n }\n case 1: // grey scale\n switch(i)\n {\n case 0:\n return vec4(0.1, 0.1, 0.1, 9.0);\n case 1:\n return vec4(0.25, 0.25, 0.25, 10.0);\n case 2:\n return vec4(0.4, 0.4, 0.4, 11.0);\n case 3:\n return vec4(0.55, 0.55, 0.55, 12.0);\n case 4:\n return vec4(0.7, 0.7, 0.7, 13.0);\n case 5:\n return vec4(0.85, 0.85, 0.85, 14.0);\n case 6:\n return vec4(0.99, 0.99, 0.99, 15.0);\n }\n case 2: // natural colors (draw a mountain scene!)\n switch(i)\n {\n case 0:\n return vec4(222.0 / 256.0, 204.0 / 256.0, 166.0 / 256.0, 16.0);\n case 1:\n return vec4(164.0 / 256.0, 167.0 / 256.0, 38.0 / 256.0, 17.0);\n case 2:\n return vec4(25.0 / 256.0, 121.0 / 256.0, 39.0 / 256.0, 18.0);\n case 3:\n return vec4(100.0 / 256.0, 100.0 / 256.0, 110.0 / 256.0, 19.0);\n case 4:\n return vec4(112.0 / 256.0, 100.0 / 256.0, 84.0 / 256.0, 20.0);\n case 5:\n return vec4(148.0 / 256.0, 91.0 / 256.0, 20.0 / 256.0, 21.0);\n case 6:\n return vec4(56.0 / 256.0, 29.0 / 256.0, 10.0 / 256.0, 22.0);\n }\n } \n}\n\nvec4 colorByFrame(int frame, int pallette)\n{ \n // compute the current color for the color-cycling rainbow spawn selection\n int colorIndex = (frame / COLOR_CHANGE_FRAMES) % 7;\n int nextColorIndex = (colorIndex + 1) % 7;\n int blendIndex = frame % COLOR_CHANGE_FRAMES;\n float blend = float(blendIndex) / float(COLOR_CHANGE_FRAMES);\n return mix(rainbow(colorIndex, pallette), rainbow(nextColorIndex, pallette), blend);\n}\n\nfloat hash( int k )\n{\n uint n = uint(k);\n\tn = (n << 13U) ^ n;\n n = n * (n * n * 15731U + 789221U) + 1376312589U;\n return uintBitsToFloat( (n>>9U) | 0x3f800000U ) - 1.0;\n}\n\nfloat linePointDist2(in vec2 newPos, in vec2 oldPos, in vec2 fragCoord, in vec3 resolution)\n{\n vec2 pDelta = (fragCoord - oldPos);\n vec2 delta = newPos - oldPos;\n float deltaLen2 = dot(delta, delta);\n\n // find the closest point on the line segment from old to new\n vec2 closest;\n if (deltaLen2 > 0.0000001)\n {\n float deltaInvSqrt = inversesqrt(deltaLen2);\n vec2 deltaNorm = delta * deltaInvSqrt;\n closest = oldPos + deltaNorm * max(0.0, min(1.0 / deltaInvSqrt, dot(deltaNorm, pDelta)));\n }\n else\n {\n // line was very short anyway\n closest = oldPos;\n }\n\n // distance to closest point on line segment\n vec2 closestDelta = closest - fragCoord;\n closestDelta *= resolution.xy / resolution.y;\n return length2(closestDelta);\n}\n\nfloat dist2hopper(vec2 fragCoord, vec3 res, vec2 pos, vec2 dim, float opening)\n{\n float dist2 = linePointDist2(vec2(pos.x - dim.x, pos.y) * res.xy, vec2(pos.x - dim.x, pos.y + dim.y) * res.xy, fragCoord, res);\n dist2 = min(dist2, linePointDist2(vec2(pos.x + dim.x, pos.y) * res.xy, vec2(pos.x + dim.x, pos.y + dim.y) * res.xy, fragCoord, res));\n dist2 = min(dist2, linePointDist2(vec2(pos.x + dim.x, pos.y) * res.xy, vec2(pos.x + opening, pos.y) * res.xy, fragCoord, res));\n dist2 = min(dist2, linePointDist2(vec2(pos.x - dim.x, pos.y) * res.xy, vec2(pos.x - opening, pos.y) * res.xy, fragCoord, res));\n return dist2;\n}\n\n// https://iquilezles.org/articles/distfunctions2d/\nfloat sdArc( in vec2 p, in vec2 sc, in float ra, float rb )\n{\n // sc is the sin/cos of the arc's aperture\n p.x = abs(p.x);\n return ((sc.y*p.x>sc.x*p.y) ? length(p-sc*ra) : \n abs(length(p)-ra)) - rb;\n}\n\n#define rnd( x) fract(1000.*sin(345.2345*x))\n#define id( x,y) floor(x)+100.*floor(y)\n\n// from maze 2 by FabriceNeyret2\n// https://www.shadertoy.com/view/4sSXWR\nfloat maze(vec2 u) {\n float n = id(u.x,u.y); u = fract(u);\n return 1.-smoothstep(.1,.15,((rnd(n)>.5)?u.x:u.y));\n}\n\n// from Exercise: basic truchet tiling by endymion\n// https://www.shadertoy.com/view/WlcfWf\nfloat truchet(in vec3 res, in int frame, in vec2 fragCoord) {\n vec2 uv = (fragCoord.xy - 0.5 * res.xy) / res.y;\n \n // Zoom;\n float scale = 10.0;\n uv *= scale; \n \n // Tiles from -.5 to .5\n vec2 gv = fract(uv) - .5;\n \n // Rotate\n vec2 id = floor(uv);\n float r = hash(int(id.x + 37.5 * id.y + 9.0 * float(frame)));\n if (r < .5) gv.x *= -1.;\n \n // Curves\n float sgn = sign(gv.x + gv.y);\n sgn = sgn == 0. ? 1. : sgn;\n float dist = abs(abs(gv.x + gv.y) - .5);\n dist = length(gv - sgn * .5) - .5;\n float width = 0.002 * scale;\n return smoothstep(scale/res.y, -scale/res.y, abs(dist) - width);\n}\n\nvoid handleDoubleClick(float selection, vec2 fragCoord, vec3 res, int iFrame, inout vec4 fragColor)\n{\n bool shouldBeWall = false;\n float dist2 = 1e6;\n \n // wall scenes you can select by double clicking on the specific color tabs\n switch(int(selection))\n {\n case 0:\n // big V\n fragCoord.x = abs(fragCoord.x - res.x * 0.125) + res.x * 0.125;\n dist2 = linePointDist2(vec2(0.13, 0.1) * res.xy, vec2(0.215, 0.4) * res.xy, fragCoord, res);\n shouldBeWall = dist2 < square(res.x * WALL_SIZE);\n\n break;\n \n case 1:\n // cascading hoppers\n fragCoord.x = res.x * 0.125 - abs(fragCoord.x - res.x * 0.125);\n dist2 = dist2hopper(fragCoord, res, vec2(0.035, 0.35), vec2(0.025, 0.05), 0.003);\n dist2 = min(dist2, dist2hopper(fragCoord, res, vec2(0.055, 0.25), vec2(0.025, 0.05), 0.003));\n dist2 = min(dist2, dist2hopper(fragCoord, res, vec2(0.075, 0.15), vec2(0.025, 0.05), 0.003));\n dist2 = min(dist2, dist2hopper(fragCoord, res, vec2(0.125, 0.05), vec2(0.06, 0.05), 0.003));\n shouldBeWall = dist2 < square(res.x * WALL_SIZE);\n\n break;\n \n case 2:\n // horizontal lines\n const float HEIGHT = 0.09;\n fragCoord.x = abs(fragCoord.x - res.x * 0.125) + res.x * 0.125;\n fragCoord.y = mod(fragCoord.y, res.x * HEIGHT * 1.25);\n dist2 = linePointDist2(vec2(0.1, HEIGHT) * res.xy, vec2(0.15, HEIGHT) * res.xy, fragCoord, res);\n dist2 = min(dist2, linePointDist2(vec2(0.175, HEIGHT) * res.xy, vec2(0.225, HEIGHT) * res.xy, fragCoord, res));\n dist2 = min(dist2, linePointDist2(vec2(0.2, HEIGHT + HEIGHT) * res.xy, vec2(0.1375, HEIGHT + HEIGHT) * res.xy, fragCoord, res));\n shouldBeWall = dist2 < square(res.x * WALL_SIZE);\n\n break;\n\n case 3:\n // slopes\n fragCoord.x = abs(fragCoord.x - res.x * 0.125) + res.x * 0.125;\n fragCoord.y = mod(fragCoord.y, res.x * HEIGHT * 1.25);\n dist2 = linePointDist2(vec2(0.125, HEIGHT * 1.2) * res.xy, vec2(0.15, HEIGHT) * res.xy, fragCoord, res);\n dist2 = min(dist2, linePointDist2(vec2(0.175, HEIGHT) * res.xy, vec2(0.225, HEIGHT * 1.2) * res.xy, fragCoord, res));\n dist2 = min(dist2, linePointDist2(vec2(0.2, HEIGHT * 2.2) * res.xy, vec2(0.1375, HEIGHT * 2.0) * res.xy, fragCoord, res));\n shouldBeWall = dist2 < square(res.x * WALL_SIZE);\n\n break;\n \n case 4:\n // bowls\n fragCoord.x = abs(fragCoord.x - res.x * 0.125) + res.x * 0.125;\n fragCoord.y = res.y * 0.125 - fragCoord.y;\n const float theta = 2.0;\n const vec2 sc = vec2(sin(theta), cos(theta));\n float dist = sdArc((fragCoord / res.x) - vec2(0.2, 0.0), sc, 0.02, 0.0);\n dist = min(dist, sdArc((fragCoord / res.x) - vec2(0.15, -0.14), sc, 0.02, 0.0));\n dist = min(dist, sdArc((fragCoord / res.x) - vec2(0.175, -0.07), sc, 0.02, 0.0));\n dist = min(dist, sdArc((fragCoord / res.x) - vec2(0.2, -0.14), sc, 0.02, 0.0));\n dist = min(dist, sdArc((fragCoord / res.x) - vec2(0.15, 0.0), sc, 0.02, 0.0));\n shouldBeWall = dist < WALL_SIZE;\n\n break;\n\n case 5:\n // maze \n fragCoord.x += mod(float(iFrame % 1000) * 10.0, res.x * 10.0);\n const float MAZE_SCALE = 1.6;\n dist = maze(MAZE_SCALE * vec2(square(res.x / res.y), 1.0)*fragCoord/(res.xy * 0.125));\n shouldBeWall = dist > 0.060 * res.x * WALL_SIZE;\n\n break;\n \n case 6:\n // truchet\n fragCoord.x *= res.x / res.y;\n dist = truchet(res, iFrame, fragCoord);\n shouldBeWall = dist > 0.060 * res.x * WALL_SIZE;\n\n break;\n }\n\n if (shouldBeWall)\n fragColor = WALL;\n else if (fragColor == WALL)\n fragColor = EMPTY;\n}\n\nvoid spawnSand(int frame, vec2 fragCoord, vec3 res, vec2 oldMouse, vec4 newMouse, float selection, int pallette, inout vec4 fragColor)\n{\n if (newMouse.z > 0.0 && newMouse.y < res.y * 0.9)\n {\n // compute the distance to the line segment from oldMouse to newMouse\n // using a capsule instead of a sphere prevents gaps when the mouse is moved quickly\n vec2 spawnBegin = oldMouse / vec2(4.0, 2.0);\n vec2 spawnEnd = newMouse.xy / vec2(4.0, 2.0);\n if (newMouse.w > 0.0) spawnBegin = spawnEnd;\n float dist2 = linePointDist2(spawnBegin, spawnEnd, fragCoord, res);\n \n if (selection == S_WALL)\n {\n if (dist2 < square(res.x * WALL_SIZE))\n {\n // wall selection (white)\n fragColor = WALL;\n }\n }\n else if (fragColor == EMPTY && dist2 < square(res.x * SPAWN_SIZE))\n {\n float noise = (0.55 + 0.45 * hash(frame * int(fragCoord.x) * int(fragCoord.y)));\n if (selection < S_RAINBOW)\n {\n // specific color selection\n fragColor = rainbow(int(selection), pallette) * vec4(noise, noise, noise, 1.0);\n }\n else\n {\n // rainbow selection\n fragColor = colorByFrame(frame, pallette) * vec4(noise, noise, noise, 1.0);\n }\n }\n }\n}\n\nvoid removeSand(int frame, vec2 fragCoord, vec3 res, vec2 oldMouse, vec4 newMouse, inout vec4 fragColor)\n{\n if (newMouse.z > 0.0)\n {\n vec2 removeBegin = oldMouse / vec2(4.0, 2.0);\n vec2 removeEnd = newMouse.xy / vec2(4.0, 2.0);\n if (newMouse.w > 0.0) removeBegin = removeEnd;\n float dist2 = linePointDist2(removeBegin, removeEnd, fragCoord, res);\n \n if (dist2 < square(res.x * 0.01))\n {\n // erase selection (black)\n fragColor = EMPTY;\n }\n }\n}\n\nvoid evolveByCells(sampler2D sampler, int frame, ivec2 coord, ivec2 offset, ivec2 ires, out vec4 fragColor)\n{\n // compute coordinates for the four pixels in our cell\n ivec2 cellCoord = (coord - offset) / 2;\n ivec2 llCell = cellCoord * 2 + offset;\n ivec2 lrCell = llCell + ivec2(1, 0);\n ivec2 ulCell = llCell + ivec2(0, 1);\n ivec2 urCell = llCell + ivec2(1, 1);\n \n if (!all(lessThan(urCell, ivec2(ires.x / 4, ires.y / 2))) ||\n (offset != ivec2(0) && (coord.x == 0 || coord.y == 0)))\n {\n // don't move particles at the bottom of the screen or off the edge\n fragColor = texelFetch(sampler, coord, 0);\n return;\n }\n \n // fetch the members of our cell\n vec4 ulValue = texelFetch(sampler, ulCell, 0);\n vec4 urValue = texelFetch(sampler, urCell, 0);\n vec4 llValue = texelFetch(sampler, llCell, 0);\n vec4 lrValue = texelFetch(sampler, lrCell, 0);\n \n // figure out which are empty\n bvec4 cell = bvec4(ulValue != EMPTY, urValue != EMPTY, llValue != EMPTY, lrValue != EMPTY);\n \n // try to match a pattern that should fall\n if ((cell == bvec4(true, false,\n false, false) ||\n cell == bvec4(true, false,\n false, true)||\n cell == bvec4(true, true,\n false, true)) && ulValue != WALL)\n {\n // left side falls\n llValue = ulValue;\n ulValue = EMPTY;\n }\n else if ((cell == bvec4(false, true,\n false, false) ||\n cell == bvec4(false, true,\n true, false) ||\n cell == bvec4(true, true,\n true, false)) && urValue != WALL)\n {\n // right side falls\n lrValue = urValue;\n urValue = EMPTY;\n }\n else if (cell == bvec4(true, true,\n false, false))\n {\n // both sides fall\n if (urValue != WALL)\n {\n lrValue = urValue;\n urValue = EMPTY;\n }\n if (ulValue != WALL)\n {\n llValue = ulValue;\n ulValue = EMPTY;\n }\n }\n else if ((cell == bvec4(true, false,\n true, false)) && ulValue != WALL)\n {\n // left side collapses\n lrValue = ulValue;\n ulValue = EMPTY;\n }\n else if ((cell == bvec4(false, true,\n false, true)) && urValue != WALL)\n {\n // right side collapses\n llValue = urValue;\n urValue = EMPTY;\n }\n\n // record result\n if (coord == llCell)\n {\n fragColor = llValue;\n }\n else if (coord == lrCell)\n {\n fragColor = lrValue;\n }\n else if (coord == ulCell)\n {\n fragColor = ulValue;\n }\n else if (coord == urCell)\n {\n fragColor = urValue;\n }\n}\n\n#define keyClick(ascii) ( texelFetch(keySampler,ivec2(ascii,1),0).x > 0.)\n#define keyDown(ascii) ( texelFetch(keySampler,ivec2(ascii,0),0).x > 0.)\n\n#define KEY_SHIFT 16\n#define KEY_SPACE 32\n\nvec4 updateState(sampler2D sandSampler, int iFrame, vec4 iMouse, vec3 iResolution)\n{\n const float DOUBLE_CLICK_FRAMES = 40.0;\n vec4 oldState = texelFetch(sandSampler, ivec2(0), 0);\n if (iFrame == 0) oldState = vec4(0.0, 0.0, S_RAINBOW, 1.0); // init state\n int palette = int(oldState.y);\n float selection = oldState.z;\n float clickState = oldState.w;\n if (iMouse.z > 0.0 && iMouse.y > iResolution.y * 0.9)\n {\n float u = iMouse.x / iResolution.x;\n float newSelection = floor(u * SELECT_CHOICES);\n\n if (iMouse.w > 0.0)\n {\n if (clickState < 0.0 && clickState > -DOUBLE_CLICK_FRAMES && newSelection == selection)\n {\n // double click detected\n clickState = 0.0;\n \n if (newSelection == S_RAINBOW)\n {\n // change palette\n palette = (palette + 1) % NUM_PALLETTES;\n }\n }\n else\n {\n clickState = 1.0;\n }\n }\n else\n {\n // count frames held down\n ++clickState;\n }\n\n selection = newSelection;\n }\n else\n {\n if (clickState < 0.0)\n {\n // count frames released...\n --clickState;\n }\n else if (clickState > 1.0 && clickState < 1.0 + DOUBLE_CLICK_FRAMES)\n {\n // ...but only if it was held down only a short time.\n clickState = -1.0;\n }\n }\n \n return vec4(iMouse.x * iResolution.y + iMouse.y, float(palette), selection, clickState);\n}\n\nvoid bufferMainInternal( out vec4 fragColor, in vec2 fragCoord, in ivec2 offset, in sampler2D sandSampler, in sampler2D keySampler, int iFrame, vec3 iResolution, vec4 iMouse)\n{\n ivec2 ifc = ivec2(fragCoord);\n\n // handle persistent state\n if (ifc == ivec2(0, 0))\n {\n fragColor = updateState(sandSampler, iFrame, iMouse, iResolution);\n return;\n }\n \n vec4 state = texelFetch(sandSampler, ivec2(0), 0);\n int pallette = int(state.y);\n float selection = state.z;\n bool doubleClick = state.w == 0.0;\n\n // only use a quarter of the screen for simulation\n if (fragCoord.x > iResolution.x * 0.25 || fragCoord.y > iResolution.y * 0.5)\n {\n return;\n }\n \n // init scene\n if (iFrame == 0 ||\n keyDown(KEY_SPACE) ||\n (doubleClick && selection == S_ERASE))\n {\n fragColor = EMPTY;\n \n return;\n }\n\n // integration step\n evolveByCells(sandSampler, iFrame, ifc, offset, ivec2(iResolution.xy), fragColor);\n \n // user interaction\n float oldEncodedMouse = state.x;\n vec2 oldMouse = vec2(oldEncodedMouse / iResolution.y, mod(oldEncodedMouse, iResolution.y));\n if (selection == S_ERASE)\n removeSand(iFrame, fragCoord, iResolution, oldMouse, iMouse, fragColor);\n else\n spawnSand(iFrame, fragCoord, iResolution, oldMouse, iMouse, selection, pallette, fragColor);\n \n // perform double click action if requested\n if (doubleClick && selection != S_RAINBOW) handleDoubleClick(selection, fragCoord, iResolution, iFrame, fragColor);\n}\n\n#define bufferMain(X, Y, Z) bufferMainInternal(X, Y, Z, iChannel0, iChannel3, iFrame, iResolution, iMouse)\n// ---------------------------------------------------------------------------------------\n//\tCreated by fenix in 2022\n//\tLicense Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.\n//\n// Particles are capable of falling or sliding down a slope greater than 45 degrees. \n// Particle motion is performed within 2x2 cells. Buffer A's cells are in a natural\n// alignment: cell 0, 0 includes the four cells (0 ... 1, 0 ... 1). Buffer B is offset\n// by 1, 1 so it can advect particles across buffer A's cell boundaries. Buffers C and D\n// are just copies of A and B respectively to get twice the integration speed.\n//\n// v2: added auto-scroll based on deleting bottom row, removed horizontal black lines,\n// mouse function determined by clicked region\n// v3: better auto-scroll, new auto-spawner animation\n//\n// \"playground\" v1: all the things\n// * doubled pixels to increase activity at higher resolutions\n// * added content-aware antialiasing to combat resultant blockiness\n// * added walls\n// * tool bar allowing selection of particlar colors, wall, and erase functions\n// * selectable color palettes\n// * noise added to particle color\n// * double click detection\n// * wall presets\n// * \"sky\" instead of black void\n//\n// ---------------------------------------------------------------------------------------\n\n#define DISABLE_ANTIALIASING 0\n\nvec4 getPixel(ivec2 ifc)\n{\n // to eliminate gaps as particles fall, combine neighboring pixels vertically for rendering\n vec4 top = texelFetch(iChannel0, ivec2(ifc.x / 4, (ifc.y / 2) & ~1), 0);\n vec4 bottom = texelFetch(iChannel0, ivec2(ifc.x / 4, (ifc.y / 2) | 1), 0);\n return top == EMPTY ? bottom : top;\n}\n\n// are these colors different enough to apply anti-aliasing? (so that we blur only edges, not sand particles)\nfloat distantColor(vec4 a, vec4 b)\n{\n // w component is sand type id\n return floor(a.w) != floor(b.w) ? 1.0 : 0.0;\n}\n\nvec4 addSky(vec4 x, vec2 uv)\n{\n if (x != EMPTY) return x;\n \n return mix(vec4(0.8, 0.8, 0.8, 1.0), vec4(0.0, 0.0, uv.y * 0.8, 1.0), sqrt(uv.y));\n}\n\nvoid mainImage( out vec4 fragColor, in vec2 fragCoord )\n{\n fragCoord.x = fragCoord.x * (iResolution.x / (iResolution.x + 8.0)) + 4.0;\n ivec2 ifc = ivec2(fragCoord);\n vec2 uv = fragCoord / iResolution.xy;\n vec4 origPixel = getPixel(ifc);\n vec4 pixel = origPixel;\n \n#if !DISABLE_ANTIALIASING\n vec4 above = getPixel(ifc + ivec2(0, 2));\n vec4 below = getPixel(ifc + ivec2(0, -2));\n vec4 left = getPixel(ifc + ivec2(-4, 0));\n vec4 right = getPixel(ifc + ivec2(4, 0));\n\n float ulDist = distantColor(above, left);\n float urDist = distantColor(above, right);\n float llDist = distantColor(below, left);\n float lrDist = distantColor(below, right);\n float distAbove = distantColor(above, pixel);\n float distBelow = distantColor(below, pixel);\n float distLeft = distantColor(left, pixel);\n float distRight = distantColor(right, pixel);\n#endif\n \n const float DIST_THRESHOLD = 0.1;\n \n#if !DISABLE_ANTIALIASING\n fragColor = addSky(pixel, uv);\n vec2 subCell = mod(fragCoord, vec2(4.0, 4.0));\n if (ulDist + lrDist < urDist + llDist && ulDist + lrDist < 0.5)\n {\n // we can create a smooth diagonal line from ll to ur\n if (distAbove > distBelow && distAbove > 0.1)\n {\n float aa = smoothstep(0.2, 0.8, (4.0 + subCell.x - subCell.y) / 4.0);\n fragColor = mix(addSky(mix(above, left, 0.5), uv), addSky(pixel, uv), aa);\n }\n else if (distBelow > 0.1)\n {\n float aa = smoothstep(0.2, 0.8, (4.0 - subCell.x + subCell.y) / 4.0);\n fragColor = mix(addSky(mix(below, right, 0.5), uv), addSky(pixel, uv), aa);\n }\n }\n else if (urDist + llDist < 0.5)\n {\n // we can create a smooth diagonal line from ul to lr\n if (distAbove > distBelow && distAbove > 0.1)\n {\n float aa = smoothstep(0.2, 0.8, (8.0 - subCell.x - subCell.y) / 4.0);\n fragColor = mix(addSky(mix(above, right, 0.5), uv), addSky(pixel, uv), aa);\n }\n else if (distBelow > 0.1)\n {\n float aa = smoothstep(0.2, 0.8, (subCell.x + subCell.y) / 4.0);\n fragColor = mix(addSky(mix(below, left, 0.5), uv), addSky(pixel, uv), aa);\n }\n }\n else\n#endif\n {\n fragColor = addSky(pixel, uv);\n }\n \n // (debug) disable antialiasing\n bool disableAa = texelFetch(iChannel3,ivec2(KEY_SHIFT,0),0).x > 0.0;\n if (disableAa) fragColor = addSky(pixel, uv);\n \n // draw UI\n vec4 state = texelFetch(iChannel0, ivec2(0), 0);\n float selection = state.z;\n int pallette = int(state.y);\n if (fragCoord.y > iResolution.y * 0.9)\n {\n float u = fragCoord.x / iResolution.x;\n u *= SELECT_CHOICES;\n \n // anti-aliasing for UI (also creates curve for selection)\n float alpha = clamp(fragCoord.y - (iResolution.y * (0.95 - smoothstep(0.0, 0.2, abs(u - round(u))) * 0.025)), 0.0, 1.0);\n if (disableAa) alpha = floor(alpha);\n \n if (fragCoord.y > iResolution.y * 0.95 ||\n (floor(u) == selection && alpha > 0.0))\n {\n vec4 uiColor;\n if (u < S_RAINBOW)\n {\n // specific colors\n uiColor = rainbow(int(u), pallette);\n }\n else if (u < S_WALL)\n {\n // color cycle (rainbow)\n uiColor = colorByFrame(int(fragCoord.x / (iResolution.x * 0.02) * float(COLOR_CHANGE_FRAMES)), pallette);\n }\n else if (u < S_ERASE)\n {\n // walls\n uiColor = vec4(1);\n }\n else\n {\n // eraser\n uiColor = vec4(0);\n }\n fragColor = mix(fragColor, uiColor, alpha);\n }\n } \n}\n\n","type":"Image"},{"class":"Keyboard","name":"Keyboard"},{"class":"GLSLShader","name":"Buffer A","source":"// options\nconst float WALL_SIZE = 0.0025;\nconst float SPAWN_SIZE = 0.01;\nconst int COLOR_CHANGE_FRAMES = 200;\n\n// tool bar selections\n#define S_RAINBOW 7.0\n#define S_WALL 8.0\n#define S_ERASE 9.0\n#define SELECT_CHOICES 10.0\n\n// special pixel values\nconst vec4 EMPTY = vec4(0);\nconst vec4 WALL = vec4(1);\n\nfloat length2(vec2 v)\n{\n return dot(v, v);\n}\n\nfloat square(float x)\n{\n return x * x;\n}\n\nconst int NUM_PALLETTES = 3;\n\nvec4 rainbow(int i, int palette)\n{\n switch(palette)\n {\n case 0: // actual rainbow\n switch(i)\n {\n case 0:\n return vec4(1.0, 0.0, 0.0, 2.0);\n case 1:\n return vec4(1.0, 0.5, 0.0, 3.0);\n case 2:\n return vec4(1.0, 1.0, 0.0, 4.0);\n case 3:\n return vec4(0.0, 1.0, 0.0, 5.0);\n case 4:\n return vec4(0.0, 0.0, 1.0, 6.0);\n case 5:\n return vec4(0.25, 0.0, 0.5, 7.0);\n case 6:\n return vec4(0.5, 0.0, 0.7, 8.0);\n }\n case 1: // grey scale\n switch(i)\n {\n case 0:\n return vec4(0.1, 0.1, 0.1, 9.0);\n case 1:\n return vec4(0.25, 0.25, 0.25, 10.0);\n case 2:\n return vec4(0.4, 0.4, 0.4, 11.0);\n case 3:\n return vec4(0.55, 0.55, 0.55, 12.0);\n case 4:\n return vec4(0.7, 0.7, 0.7, 13.0);\n case 5:\n return vec4(0.85, 0.85, 0.85, 14.0);\n case 6:\n return vec4(0.99, 0.99, 0.99, 15.0);\n }\n case 2: // natural colors (draw a mountain scene!)\n switch(i)\n {\n case 0:\n return vec4(222.0 / 256.0, 204.0 / 256.0, 166.0 / 256.0, 16.0);\n case 1:\n return vec4(164.0 / 256.0, 167.0 / 256.0, 38.0 / 256.0, 17.0);\n case 2:\n return vec4(25.0 / 256.0, 121.0 / 256.0, 39.0 / 256.0, 18.0);\n case 3:\n return vec4(100.0 / 256.0, 100.0 / 256.0, 110.0 / 256.0, 19.0);\n case 4:\n return vec4(112.0 / 256.0, 100.0 / 256.0, 84.0 / 256.0, 20.0);\n case 5:\n return vec4(148.0 / 256.0, 91.0 / 256.0, 20.0 / 256.0, 21.0);\n case 6:\n return vec4(56.0 / 256.0, 29.0 / 256.0, 10.0 / 256.0, 22.0);\n }\n } \n}\n\nvec4 colorByFrame(int frame, int pallette)\n{ \n // compute the current color for the color-cycling rainbow spawn selection\n int colorIndex = (frame / COLOR_CHANGE_FRAMES) % 7;\n int nextColorIndex = (colorIndex + 1) % 7;\n int blendIndex = frame % COLOR_CHANGE_FRAMES;\n float blend = float(blendIndex) / float(COLOR_CHANGE_FRAMES);\n return mix(rainbow(colorIndex, pallette), rainbow(nextColorIndex, pallette), blend);\n}\n\nfloat hash( int k )\n{\n uint n = uint(k);\n\tn = (n << 13U) ^ n;\n n = n * (n * n * 15731U + 789221U) + 1376312589U;\n return uintBitsToFloat( (n>>9U) | 0x3f800000U ) - 1.0;\n}\n\nfloat linePointDist2(in vec2 newPos, in vec2 oldPos, in vec2 fragCoord, in vec3 resolution)\n{\n vec2 pDelta = (fragCoord - oldPos);\n vec2 delta = newPos - oldPos;\n float deltaLen2 = dot(delta, delta);\n\n // find the closest point on the line segment from old to new\n vec2 closest;\n if (deltaLen2 > 0.0000001)\n {\n float deltaInvSqrt = inversesqrt(deltaLen2);\n vec2 deltaNorm = delta * deltaInvSqrt;\n closest = oldPos + deltaNorm * max(0.0, min(1.0 / deltaInvSqrt, dot(deltaNorm, pDelta)));\n }\n else\n {\n // line was very short anyway\n closest = oldPos;\n }\n\n // distance to closest point on line segment\n vec2 closestDelta = closest - fragCoord;\n closestDelta *= resolution.xy / resolution.y;\n return length2(closestDelta);\n}\n\nfloat dist2hopper(vec2 fragCoord, vec3 res, vec2 pos, vec2 dim, float opening)\n{\n float dist2 = linePointDist2(vec2(pos.x - dim.x, pos.y) * res.xy, vec2(pos.x - dim.x, pos.y + dim.y) * res.xy, fragCoord, res);\n dist2 = min(dist2, linePointDist2(vec2(pos.x + dim.x, pos.y) * res.xy, vec2(pos.x + dim.x, pos.y + dim.y) * res.xy, fragCoord, res));\n dist2 = min(dist2, linePointDist2(vec2(pos.x + dim.x, pos.y) * res.xy, vec2(pos.x + opening, pos.y) * res.xy, fragCoord, res));\n dist2 = min(dist2, linePointDist2(vec2(pos.x - dim.x, pos.y) * res.xy, vec2(pos.x - opening, pos.y) * res.xy, fragCoord, res));\n return dist2;\n}\n\n// https://iquilezles.org/articles/distfunctions2d/\nfloat sdArc( in vec2 p, in vec2 sc, in float ra, float rb )\n{\n // sc is the sin/cos of the arc's aperture\n p.x = abs(p.x);\n return ((sc.y*p.x>sc.x*p.y) ? length(p-sc*ra) : \n abs(length(p)-ra)) - rb;\n}\n\n#define rnd( x) fract(1000.*sin(345.2345*x))\n#define id( x,y) floor(x)+100.*floor(y)\n\n// from maze 2 by FabriceNeyret2\n// https://www.shadertoy.com/view/4sSXWR\nfloat maze(vec2 u) {\n float n = id(u.x,u.y); u = fract(u);\n return 1.-smoothstep(.1,.15,((rnd(n)>.5)?u.x:u.y));\n}\n\n// from Exercise: basic truchet tiling by endymion\n// https://www.shadertoy.com/view/WlcfWf\nfloat truchet(in vec3 res, in int frame, in vec2 fragCoord) {\n vec2 uv = (fragCoord.xy - 0.5 * res.xy) / res.y;\n \n // Zoom;\n float scale = 10.0;\n uv *= scale; \n \n // Tiles from -.5 to .5\n vec2 gv = fract(uv) - .5;\n \n // Rotate\n vec2 id = floor(uv);\n float r = hash(int(id.x + 37.5 * id.y + 9.0 * float(frame)));\n if (r < .5) gv.x *= -1.;\n \n // Curves\n float sgn = sign(gv.x + gv.y);\n sgn = sgn == 0. ? 1. : sgn;\n float dist = abs(abs(gv.x + gv.y) - .5);\n dist = length(gv - sgn * .5) - .5;\n float width = 0.002 * scale;\n return smoothstep(scale/res.y, -scale/res.y, abs(dist) - width);\n}\n\nvoid handleDoubleClick(float selection, vec2 fragCoord, vec3 res, int iFrame, inout vec4 fragColor)\n{\n bool shouldBeWall = false;\n float dist2 = 1e6;\n \n // wall scenes you can select by double clicking on the specific color tabs\n switch(int(selection))\n {\n case 0:\n // big V\n fragCoord.x = abs(fragCoord.x - res.x * 0.125) + res.x * 0.125;\n dist2 = linePointDist2(vec2(0.13, 0.1) * res.xy, vec2(0.215, 0.4) * res.xy, fragCoord, res);\n shouldBeWall = dist2 < square(res.x * WALL_SIZE);\n\n break;\n \n case 1:\n // cascading hoppers\n fragCoord.x = res.x * 0.125 - abs(fragCoord.x - res.x * 0.125);\n dist2 = dist2hopper(fragCoord, res, vec2(0.035, 0.35), vec2(0.025, 0.05), 0.003);\n dist2 = min(dist2, dist2hopper(fragCoord, res, vec2(0.055, 0.25), vec2(0.025, 0.05), 0.003));\n dist2 = min(dist2, dist2hopper(fragCoord, res, vec2(0.075, 0.15), vec2(0.025, 0.05), 0.003));\n dist2 = min(dist2, dist2hopper(fragCoord, res, vec2(0.125, 0.05), vec2(0.06, 0.05), 0.003));\n shouldBeWall = dist2 < square(res.x * WALL_SIZE);\n\n break;\n \n case 2:\n // horizontal lines\n const float HEIGHT = 0.09;\n fragCoord.x = abs(fragCoord.x - res.x * 0.125) + res.x * 0.125;\n fragCoord.y = mod(fragCoord.y, res.x * HEIGHT * 1.25);\n dist2 = linePointDist2(vec2(0.1, HEIGHT) * res.xy, vec2(0.15, HEIGHT) * res.xy, fragCoord, res);\n dist2 = min(dist2, linePointDist2(vec2(0.175, HEIGHT) * res.xy, vec2(0.225, HEIGHT) * res.xy, fragCoord, res));\n dist2 = min(dist2, linePointDist2(vec2(0.2, HEIGHT + HEIGHT) * res.xy, vec2(0.1375, HEIGHT + HEIGHT) * res.xy, fragCoord, res));\n shouldBeWall = dist2 < square(res.x * WALL_SIZE);\n\n break;\n\n case 3:\n // slopes\n fragCoord.x = abs(fragCoord.x - res.x * 0.125) + res.x * 0.125;\n fragCoord.y = mod(fragCoord.y, res.x * HEIGHT * 1.25);\n dist2 = linePointDist2(vec2(0.125, HEIGHT * 1.2) * res.xy, vec2(0.15, HEIGHT) * res.xy, fragCoord, res);\n dist2 = min(dist2, linePointDist2(vec2(0.175, HEIGHT) * res.xy, vec2(0.225, HEIGHT * 1.2) * res.xy, fragCoord, res));\n dist2 = min(dist2, linePointDist2(vec2(0.2, HEIGHT * 2.2) * res.xy, vec2(0.1375, HEIGHT * 2.0) * res.xy, fragCoord, res));\n shouldBeWall = dist2 < square(res.x * WALL_SIZE);\n\n break;\n \n case 4:\n // bowls\n fragCoord.x = abs(fragCoord.x - res.x * 0.125) + res.x * 0.125;\n fragCoord.y = res.y * 0.125 - fragCoord.y;\n const float theta = 2.0;\n const vec2 sc = vec2(sin(theta), cos(theta));\n float dist = sdArc((fragCoord / res.x) - vec2(0.2, 0.0), sc, 0.02, 0.0);\n dist = min(dist, sdArc((fragCoord / res.x) - vec2(0.15, -0.14), sc, 0.02, 0.0));\n dist = min(dist, sdArc((fragCoord / res.x) - vec2(0.175, -0.07), sc, 0.02, 0.0));\n dist = min(dist, sdArc((fragCoord / res.x) - vec2(0.2, -0.14), sc, 0.02, 0.0));\n dist = min(dist, sdArc((fragCoord / res.x) - vec2(0.15, 0.0), sc, 0.02, 0.0));\n shouldBeWall = dist < WALL_SIZE;\n\n break;\n\n case 5:\n // maze \n fragCoord.x += mod(float(iFrame % 1000) * 10.0, res.x * 10.0);\n const float MAZE_SCALE = 1.6;\n dist = maze(MAZE_SCALE * vec2(square(res.x / res.y), 1.0)*fragCoord/(res.xy * 0.125));\n shouldBeWall = dist > 0.060 * res.x * WALL_SIZE;\n\n break;\n \n case 6:\n // truchet\n fragCoord.x *= res.x / res.y;\n dist = truchet(res, iFrame, fragCoord);\n shouldBeWall = dist > 0.060 * res.x * WALL_SIZE;\n\n break;\n }\n\n if (shouldBeWall)\n fragColor = WALL;\n else if (fragColor == WALL)\n fragColor = EMPTY;\n}\n\nvoid spawnSand(int frame, vec2 fragCoord, vec3 res, vec2 oldMouse, vec4 newMouse, float selection, int pallette, inout vec4 fragColor)\n{\n if (newMouse.z > 0.0 && newMouse.y < res.y * 0.9)\n {\n // compute the distance to the line segment from oldMouse to newMouse\n // using a capsule instead of a sphere prevents gaps when the mouse is moved quickly\n vec2 spawnBegin = oldMouse / vec2(4.0, 2.0);\n vec2 spawnEnd = newMouse.xy / vec2(4.0, 2.0);\n if (newMouse.w > 0.0) spawnBegin = spawnEnd;\n float dist2 = linePointDist2(spawnBegin, spawnEnd, fragCoord, res);\n \n if (selection == S_WALL)\n {\n if (dist2 < square(res.x * WALL_SIZE))\n {\n // wall selection (white)\n fragColor = WALL;\n }\n }\n else if (fragColor == EMPTY && dist2 < square(res.x * SPAWN_SIZE))\n {\n float noise = (0.55 + 0.45 * hash(frame * int(fragCoord.x) * int(fragCoord.y)));\n if (selection < S_RAINBOW)\n {\n // specific color selection\n fragColor = rainbow(int(selection), pallette) * vec4(noise, noise, noise, 1.0);\n }\n else\n {\n // rainbow selection\n fragColor = colorByFrame(frame, pallette) * vec4(noise, noise, noise, 1.0);\n }\n }\n }\n}\n\nvoid removeSand(int frame, vec2 fragCoord, vec3 res, vec2 oldMouse, vec4 newMouse, inout vec4 fragColor)\n{\n if (newMouse.z > 0.0)\n {\n vec2 removeBegin = oldMouse / vec2(4.0, 2.0);\n vec2 removeEnd = newMouse.xy / vec2(4.0, 2.0);\n if (newMouse.w > 0.0) removeBegin = removeEnd;\n float dist2 = linePointDist2(removeBegin, removeEnd, fragCoord, res);\n \n if (dist2 < square(res.x * 0.01))\n {\n // erase selection (black)\n fragColor = EMPTY;\n }\n }\n}\n\nvoid evolveByCells(sampler2D sampler, int frame, ivec2 coord, ivec2 offset, ivec2 ires, out vec4 fragColor)\n{\n // compute coordinates for the four pixels in our cell\n ivec2 cellCoord = (coord - offset) / 2;\n ivec2 llCell = cellCoord * 2 + offset;\n ivec2 lrCell = llCell + ivec2(1, 0);\n ivec2 ulCell = llCell + ivec2(0, 1);\n ivec2 urCell = llCell + ivec2(1, 1);\n \n if (!all(lessThan(urCell, ivec2(ires.x / 4, ires.y / 2))) ||\n (offset != ivec2(0) && (coord.x == 0 || coord.y == 0)))\n {\n // don't move particles at the bottom of the screen or off the edge\n fragColor = texelFetch(sampler, coord, 0);\n return;\n }\n \n // fetch the members of our cell\n vec4 ulValue = texelFetch(sampler, ulCell, 0);\n vec4 urValue = texelFetch(sampler, urCell, 0);\n vec4 llValue = texelFetch(sampler, llCell, 0);\n vec4 lrValue = texelFetch(sampler, lrCell, 0);\n \n // figure out which are empty\n bvec4 cell = bvec4(ulValue != EMPTY, urValue != EMPTY, llValue != EMPTY, lrValue != EMPTY);\n \n // try to match a pattern that should fall\n if ((cell == bvec4(true, false,\n false, false) ||\n cell == bvec4(true, false,\n false, true)||\n cell == bvec4(true, true,\n false, true)) && ulValue != WALL)\n {\n // left side falls\n llValue = ulValue;\n ulValue = EMPTY;\n }\n else if ((cell == bvec4(false, true,\n false, false) ||\n cell == bvec4(false, true,\n true, false) ||\n cell == bvec4(true, true,\n true, false)) && urValue != WALL)\n {\n // right side falls\n lrValue = urValue;\n urValue = EMPTY;\n }\n else if (cell == bvec4(true, true,\n false, false))\n {\n // both sides fall\n if (urValue != WALL)\n {\n lrValue = urValue;\n urValue = EMPTY;\n }\n if (ulValue != WALL)\n {\n llValue = ulValue;\n ulValue = EMPTY;\n }\n }\n else if ((cell == bvec4(true, false,\n true, false)) && ulValue != WALL)\n {\n // left side collapses\n lrValue = ulValue;\n ulValue = EMPTY;\n }\n else if ((cell == bvec4(false, true,\n false, true)) && urValue != WALL)\n {\n // right side collapses\n llValue = urValue;\n urValue = EMPTY;\n }\n\n // record result\n if (coord == llCell)\n {\n fragColor = llValue;\n }\n else if (coord == lrCell)\n {\n fragColor = lrValue;\n }\n else if (coord == ulCell)\n {\n fragColor = ulValue;\n }\n else if (coord == urCell)\n {\n fragColor = urValue;\n }\n}\n\n#define keyClick(ascii) ( texelFetch(keySampler,ivec2(ascii,1),0).x > 0.)\n#define keyDown(ascii) ( texelFetch(keySampler,ivec2(ascii,0),0).x > 0.)\n\n#define KEY_SHIFT 16\n#define KEY_SPACE 32\n\nvec4 updateState(sampler2D sandSampler, int iFrame, vec4 iMouse, vec3 iResolution)\n{\n const float DOUBLE_CLICK_FRAMES = 40.0;\n vec4 oldState = texelFetch(sandSampler, ivec2(0), 0);\n if (iFrame == 0) oldState = vec4(0.0, 0.0, S_RAINBOW, 1.0); // init state\n int palette = int(oldState.y);\n float selection = oldState.z;\n float clickState = oldState.w;\n if (iMouse.z > 0.0 && iMouse.y > iResolution.y * 0.9)\n {\n float u = iMouse.x / iResolution.x;\n float newSelection = floor(u * SELECT_CHOICES);\n\n if (iMouse.w > 0.0)\n {\n if (clickState < 0.0 && clickState > -DOUBLE_CLICK_FRAMES && newSelection == selection)\n {\n // double click detected\n clickState = 0.0;\n \n if (newSelection == S_RAINBOW)\n {\n // change palette\n palette = (palette + 1) % NUM_PALLETTES;\n }\n }\n else\n {\n clickState = 1.0;\n }\n }\n else\n {\n // count frames held down\n ++clickState;\n }\n\n selection = newSelection;\n }\n else\n {\n if (clickState < 0.0)\n {\n // count frames released...\n --clickState;\n }\n else if (clickState > 1.0 && clickState < 1.0 + DOUBLE_CLICK_FRAMES)\n {\n // ...but only if it was held down only a short time.\n clickState = -1.0;\n }\n }\n \n return vec4(iMouse.x * iResolution.y + iMouse.y, float(palette), selection, clickState);\n}\n\nvoid bufferMainInternal( out vec4 fragColor, in vec2 fragCoord, in ivec2 offset, in sampler2D sandSampler, in sampler2D keySampler, int iFrame, vec3 iResolution, vec4 iMouse)\n{\n ivec2 ifc = ivec2(fragCoord);\n\n // handle persistent state\n if (ifc == ivec2(0, 0))\n {\n fragColor = updateState(sandSampler, iFrame, iMouse, iResolution);\n return;\n }\n \n vec4 state = texelFetch(sandSampler, ivec2(0), 0);\n int pallette = int(state.y);\n float selection = state.z;\n bool doubleClick = state.w == 0.0;\n\n // only use a quarter of the screen for simulation\n if (fragCoord.x > iResolution.x * 0.25 || fragCoord.y > iResolution.y * 0.5)\n {\n return;\n }\n \n // init scene\n if (iFrame == 0 ||\n keyDown(KEY_SPACE) ||\n (doubleClick && selection == S_ERASE))\n {\n fragColor = EMPTY;\n \n return;\n }\n\n // integration step\n evolveByCells(sandSampler, iFrame, ifc, offset, ivec2(iResolution.xy), fragColor);\n \n // user interaction\n float oldEncodedMouse = state.x;\n vec2 oldMouse = vec2(oldEncodedMouse / iResolution.y, mod(oldEncodedMouse, iResolution.y));\n if (selection == S_ERASE)\n removeSand(iFrame, fragCoord, iResolution, oldMouse, iMouse, fragColor);\n else\n spawnSand(iFrame, fragCoord, iResolution, oldMouse, iMouse, selection, pallette, fragColor);\n \n // perform double click action if requested\n if (doubleClick && selection != S_RAINBOW) handleDoubleClick(selection, fragCoord, iResolution, iFrame, fragColor);\n}\n\n#define bufferMain(X, Y, Z) bufferMainInternal(X, Y, Z, iChannel0, iChannel3, iFrame, iResolution, iMouse)\nvoid mainImage( out vec4 fragColor, in vec2 fragCoord )\n{\n bufferMain(fragColor, fragCoord, ivec2(0));\n}\n\n","type":"Image"},{"class":"GLSLShader","name":"Buffer B","source":"// options\nconst float WALL_SIZE = 0.0025;\nconst float SPAWN_SIZE = 0.01;\nconst int COLOR_CHANGE_FRAMES = 200;\n\n// tool bar selections\n#define S_RAINBOW 7.0\n#define S_WALL 8.0\n#define S_ERASE 9.0\n#define SELECT_CHOICES 10.0\n\n// special pixel values\nconst vec4 EMPTY = vec4(0);\nconst vec4 WALL = vec4(1);\n\nfloat length2(vec2 v)\n{\n return dot(v, v);\n}\n\nfloat square(float x)\n{\n return x * x;\n}\n\nconst int NUM_PALLETTES = 3;\n\nvec4 rainbow(int i, int palette)\n{\n switch(palette)\n {\n case 0: // actual rainbow\n switch(i)\n {\n case 0:\n return vec4(1.0, 0.0, 0.0, 2.0);\n case 1:\n return vec4(1.0, 0.5, 0.0, 3.0);\n case 2:\n return vec4(1.0, 1.0, 0.0, 4.0);\n case 3:\n return vec4(0.0, 1.0, 0.0, 5.0);\n case 4:\n return vec4(0.0, 0.0, 1.0, 6.0);\n case 5:\n return vec4(0.25, 0.0, 0.5, 7.0);\n case 6:\n return vec4(0.5, 0.0, 0.7, 8.0);\n }\n case 1: // grey scale\n switch(i)\n {\n case 0:\n return vec4(0.1, 0.1, 0.1, 9.0);\n case 1:\n return vec4(0.25, 0.25, 0.25, 10.0);\n case 2:\n return vec4(0.4, 0.4, 0.4, 11.0);\n case 3:\n return vec4(0.55, 0.55, 0.55, 12.0);\n case 4:\n return vec4(0.7, 0.7, 0.7, 13.0);\n case 5:\n return vec4(0.85, 0.85, 0.85, 14.0);\n case 6:\n return vec4(0.99, 0.99, 0.99, 15.0);\n }\n case 2: // natural colors (draw a mountain scene!)\n switch(i)\n {\n case 0:\n return vec4(222.0 / 256.0, 204.0 / 256.0, 166.0 / 256.0, 16.0);\n case 1:\n return vec4(164.0 / 256.0, 167.0 / 256.0, 38.0 / 256.0, 17.0);\n case 2:\n return vec4(25.0 / 256.0, 121.0 / 256.0, 39.0 / 256.0, 18.0);\n case 3:\n return vec4(100.0 / 256.0, 100.0 / 256.0, 110.0 / 256.0, 19.0);\n case 4:\n return vec4(112.0 / 256.0, 100.0 / 256.0, 84.0 / 256.0, 20.0);\n case 5:\n return vec4(148.0 / 256.0, 91.0 / 256.0, 20.0 / 256.0, 21.0);\n case 6:\n return vec4(56.0 / 256.0, 29.0 / 256.0, 10.0 / 256.0, 22.0);\n }\n } \n}\n\nvec4 colorByFrame(int frame, int pallette)\n{ \n // compute the current color for the color-cycling rainbow spawn selection\n int colorIndex = (frame / COLOR_CHANGE_FRAMES) % 7;\n int nextColorIndex = (colorIndex + 1) % 7;\n int blendIndex = frame % COLOR_CHANGE_FRAMES;\n float blend = float(blendIndex) / float(COLOR_CHANGE_FRAMES);\n return mix(rainbow(colorIndex, pallette), rainbow(nextColorIndex, pallette), blend);\n}\n\nfloat hash( int k )\n{\n uint n = uint(k);\n\tn = (n << 13U) ^ n;\n n = n * (n * n * 15731U + 789221U) + 1376312589U;\n return uintBitsToFloat( (n>>9U) | 0x3f800000U ) - 1.0;\n}\n\nfloat linePointDist2(in vec2 newPos, in vec2 oldPos, in vec2 fragCoord, in vec3 resolution)\n{\n vec2 pDelta = (fragCoord - oldPos);\n vec2 delta = newPos - oldPos;\n float deltaLen2 = dot(delta, delta);\n\n // find the closest point on the line segment from old to new\n vec2 closest;\n if (deltaLen2 > 0.0000001)\n {\n float deltaInvSqrt = inversesqrt(deltaLen2);\n vec2 deltaNorm = delta * deltaInvSqrt;\n closest = oldPos + deltaNorm * max(0.0, min(1.0 / deltaInvSqrt, dot(deltaNorm, pDelta)));\n }\n else\n {\n // line was very short anyway\n closest = oldPos;\n }\n\n // distance to closest point on line segment\n vec2 closestDelta = closest - fragCoord;\n closestDelta *= resolution.xy / resolution.y;\n return length2(closestDelta);\n}\n\nfloat dist2hopper(vec2 fragCoord, vec3 res, vec2 pos, vec2 dim, float opening)\n{\n float dist2 = linePointDist2(vec2(pos.x - dim.x, pos.y) * res.xy, vec2(pos.x - dim.x, pos.y + dim.y) * res.xy, fragCoord, res);\n dist2 = min(dist2, linePointDist2(vec2(pos.x + dim.x, pos.y) * res.xy, vec2(pos.x + dim.x, pos.y + dim.y) * res.xy, fragCoord, res));\n dist2 = min(dist2, linePointDist2(vec2(pos.x + dim.x, pos.y) * res.xy, vec2(pos.x + opening, pos.y) * res.xy, fragCoord, res));\n dist2 = min(dist2, linePointDist2(vec2(pos.x - dim.x, pos.y) * res.xy, vec2(pos.x - opening, pos.y) * res.xy, fragCoord, res));\n return dist2;\n}\n\n// https://iquilezles.org/articles/distfunctions2d/\nfloat sdArc( in vec2 p, in vec2 sc, in float ra, float rb )\n{\n // sc is the sin/cos of the arc's aperture\n p.x = abs(p.x);\n return ((sc.y*p.x>sc.x*p.y) ? length(p-sc*ra) : \n abs(length(p)-ra)) - rb;\n}\n\n#define rnd( x) fract(1000.*sin(345.2345*x))\n#define id( x,y) floor(x)+100.*floor(y)\n\n// from maze 2 by FabriceNeyret2\n// https://www.shadertoy.com/view/4sSXWR\nfloat maze(vec2 u) {\n float n = id(u.x,u.y); u = fract(u);\n return 1.-smoothstep(.1,.15,((rnd(n)>.5)?u.x:u.y));\n}\n\n// from Exercise: basic truchet tiling by endymion\n// https://www.shadertoy.com/view/WlcfWf\nfloat truchet(in vec3 res, in int frame, in vec2 fragCoord) {\n vec2 uv = (fragCoord.xy - 0.5 * res.xy) / res.y;\n \n // Zoom;\n float scale = 10.0;\n uv *= scale; \n \n // Tiles from -.5 to .5\n vec2 gv = fract(uv) - .5;\n \n // Rotate\n vec2 id = floor(uv);\n float r = hash(int(id.x + 37.5 * id.y + 9.0 * float(frame)));\n if (r < .5) gv.x *= -1.;\n \n // Curves\n float sgn = sign(gv.x + gv.y);\n sgn = sgn == 0. ? 1. : sgn;\n float dist = abs(abs(gv.x + gv.y) - .5);\n dist = length(gv - sgn * .5) - .5;\n float width = 0.002 * scale;\n return smoothstep(scale/res.y, -scale/res.y, abs(dist) - width);\n}\n\nvoid handleDoubleClick(float selection, vec2 fragCoord, vec3 res, int iFrame, inout vec4 fragColor)\n{\n bool shouldBeWall = false;\n float dist2 = 1e6;\n \n // wall scenes you can select by double clicking on the specific color tabs\n switch(int(selection))\n {\n case 0:\n // big V\n fragCoord.x = abs(fragCoord.x - res.x * 0.125) + res.x * 0.125;\n dist2 = linePointDist2(vec2(0.13, 0.1) * res.xy, vec2(0.215, 0.4) * res.xy, fragCoord, res);\n shouldBeWall = dist2 < square(res.x * WALL_SIZE);\n\n break;\n \n case 1:\n // cascading hoppers\n fragCoord.x = res.x * 0.125 - abs(fragCoord.x - res.x * 0.125);\n dist2 = dist2hopper(fragCoord, res, vec2(0.035, 0.35), vec2(0.025, 0.05), 0.003);\n dist2 = min(dist2, dist2hopper(fragCoord, res, vec2(0.055, 0.25), vec2(0.025, 0.05), 0.003));\n dist2 = min(dist2, dist2hopper(fragCoord, res, vec2(0.075, 0.15), vec2(0.025, 0.05), 0.003));\n dist2 = min(dist2, dist2hopper(fragCoord, res, vec2(0.125, 0.05), vec2(0.06, 0.05), 0.003));\n shouldBeWall = dist2 < square(res.x * WALL_SIZE);\n\n break;\n \n case 2:\n // horizontal lines\n const float HEIGHT = 0.09;\n fragCoord.x = abs(fragCoord.x - res.x * 0.125) + res.x * 0.125;\n fragCoord.y = mod(fragCoord.y, res.x * HEIGHT * 1.25);\n dist2 = linePointDist2(vec2(0.1, HEIGHT) * res.xy, vec2(0.15, HEIGHT) * res.xy, fragCoord, res);\n dist2 = min(dist2, linePointDist2(vec2(0.175, HEIGHT) * res.xy, vec2(0.225, HEIGHT) * res.xy, fragCoord, res));\n dist2 = min(dist2, linePointDist2(vec2(0.2, HEIGHT + HEIGHT) * res.xy, vec2(0.1375, HEIGHT + HEIGHT) * res.xy, fragCoord, res));\n shouldBeWall = dist2 < square(res.x * WALL_SIZE);\n\n break;\n\n case 3:\n // slopes\n fragCoord.x = abs(fragCoord.x - res.x * 0.125) + res.x * 0.125;\n fragCoord.y = mod(fragCoord.y, res.x * HEIGHT * 1.25);\n dist2 = linePointDist2(vec2(0.125, HEIGHT * 1.2) * res.xy, vec2(0.15, HEIGHT) * res.xy, fragCoord, res);\n dist2 = min(dist2, linePointDist2(vec2(0.175, HEIGHT) * res.xy, vec2(0.225, HEIGHT * 1.2) * res.xy, fragCoord, res));\n dist2 = min(dist2, linePointDist2(vec2(0.2, HEIGHT * 2.2) * res.xy, vec2(0.1375, HEIGHT * 2.0) * res.xy, fragCoord, res));\n shouldBeWall = dist2 < square(res.x * WALL_SIZE);\n\n break;\n \n case 4:\n // bowls\n fragCoord.x = abs(fragCoord.x - res.x * 0.125) + res.x * 0.125;\n fragCoord.y = res.y * 0.125 - fragCoord.y;\n const float theta = 2.0;\n const vec2 sc = vec2(sin(theta), cos(theta));\n float dist = sdArc((fragCoord / res.x) - vec2(0.2, 0.0), sc, 0.02, 0.0);\n dist = min(dist, sdArc((fragCoord / res.x) - vec2(0.15, -0.14), sc, 0.02, 0.0));\n dist = min(dist, sdArc((fragCoord / res.x) - vec2(0.175, -0.07), sc, 0.02, 0.0));\n dist = min(dist, sdArc((fragCoord / res.x) - vec2(0.2, -0.14), sc, 0.02, 0.0));\n dist = min(dist, sdArc((fragCoord / res.x) - vec2(0.15, 0.0), sc, 0.02, 0.0));\n shouldBeWall = dist < WALL_SIZE;\n\n break;\n\n case 5:\n // maze \n fragCoord.x += mod(float(iFrame % 1000) * 10.0, res.x * 10.0);\n const float MAZE_SCALE = 1.6;\n dist = maze(MAZE_SCALE * vec2(square(res.x / res.y), 1.0)*fragCoord/(res.xy * 0.125));\n shouldBeWall = dist > 0.060 * res.x * WALL_SIZE;\n\n break;\n \n case 6:\n // truchet\n fragCoord.x *= res.x / res.y;\n dist = truchet(res, iFrame, fragCoord);\n shouldBeWall = dist > 0.060 * res.x * WALL_SIZE;\n\n break;\n }\n\n if (shouldBeWall)\n fragColor = WALL;\n else if (fragColor == WALL)\n fragColor = EMPTY;\n}\n\nvoid spawnSand(int frame, vec2 fragCoord, vec3 res, vec2 oldMouse, vec4 newMouse, float selection, int pallette, inout vec4 fragColor)\n{\n if (newMouse.z > 0.0 && newMouse.y < res.y * 0.9)\n {\n // compute the distance to the line segment from oldMouse to newMouse\n // using a capsule instead of a sphere prevents gaps when the mouse is moved quickly\n vec2 spawnBegin = oldMouse / vec2(4.0, 2.0);\n vec2 spawnEnd = newMouse.xy / vec2(4.0, 2.0);\n if (newMouse.w > 0.0) spawnBegin = spawnEnd;\n float dist2 = linePointDist2(spawnBegin, spawnEnd, fragCoord, res);\n \n if (selection == S_WALL)\n {\n if (dist2 < square(res.x * WALL_SIZE))\n {\n // wall selection (white)\n fragColor = WALL;\n }\n }\n else if (fragColor == EMPTY && dist2 < square(res.x * SPAWN_SIZE))\n {\n float noise = (0.55 + 0.45 * hash(frame * int(fragCoord.x) * int(fragCoord.y)));\n if (selection < S_RAINBOW)\n {\n // specific color selection\n fragColor = rainbow(int(selection), pallette) * vec4(noise, noise, noise, 1.0);\n }\n else\n {\n // rainbow selection\n fragColor = colorByFrame(frame, pallette) * vec4(noise, noise, noise, 1.0);\n }\n }\n }\n}\n\nvoid removeSand(int frame, vec2 fragCoord, vec3 res, vec2 oldMouse, vec4 newMouse, inout vec4 fragColor)\n{\n if (newMouse.z > 0.0)\n {\n vec2 removeBegin = oldMouse / vec2(4.0, 2.0);\n vec2 removeEnd = newMouse.xy / vec2(4.0, 2.0);\n if (newMouse.w > 0.0) removeBegin = removeEnd;\n float dist2 = linePointDist2(removeBegin, removeEnd, fragCoord, res);\n \n if (dist2 < square(res.x * 0.01))\n {\n // erase selection (black)\n fragColor = EMPTY;\n }\n }\n}\n\nvoid evolveByCells(sampler2D sampler, int frame, ivec2 coord, ivec2 offset, ivec2 ires, out vec4 fragColor)\n{\n // compute coordinates for the four pixels in our cell\n ivec2 cellCoord = (coord - offset) / 2;\n ivec2 llCell = cellCoord * 2 + offset;\n ivec2 lrCell = llCell + ivec2(1, 0);\n ivec2 ulCell = llCell + ivec2(0, 1);\n ivec2 urCell = llCell + ivec2(1, 1);\n \n if (!all(lessThan(urCell, ivec2(ires.x / 4, ires.y / 2))) ||\n (offset != ivec2(0) && (coord.x == 0 || coord.y == 0)))\n {\n // don't move particles at the bottom of the screen or off the edge\n fragColor = texelFetch(sampler, coord, 0);\n return;\n }\n \n // fetch the members of our cell\n vec4 ulValue = texelFetch(sampler, ulCell, 0);\n vec4 urValue = texelFetch(sampler, urCell, 0);\n vec4 llValue = texelFetch(sampler, llCell, 0);\n vec4 lrValue = texelFetch(sampler, lrCell, 0);\n \n // figure out which are empty\n bvec4 cell = bvec4(ulValue != EMPTY, urValue != EMPTY, llValue != EMPTY, lrValue != EMPTY);\n \n // try to match a pattern that should fall\n if ((cell == bvec4(true, false,\n false, false) ||\n cell == bvec4(true, false,\n false, true)||\n cell == bvec4(true, true,\n false, true)) && ulValue != WALL)\n {\n // left side falls\n llValue = ulValue;\n ulValue = EMPTY;\n }\n else if ((cell == bvec4(false, true,\n false, false) ||\n cell == bvec4(false, true,\n true, false) ||\n cell == bvec4(true, true,\n true, false)) && urValue != WALL)\n {\n // right side falls\n lrValue = urValue;\n urValue = EMPTY;\n }\n else if (cell == bvec4(true, true,\n false, false))\n {\n // both sides fall\n if (urValue != WALL)\n {\n lrValue = urValue;\n urValue = EMPTY;\n }\n if (ulValue != WALL)\n {\n llValue = ulValue;\n ulValue = EMPTY;\n }\n }\n else if ((cell == bvec4(true, false,\n true, false)) && ulValue != WALL)\n {\n // left side collapses\n lrValue = ulValue;\n ulValue = EMPTY;\n }\n else if ((cell == bvec4(false, true,\n false, true)) && urValue != WALL)\n {\n // right side collapses\n llValue = urValue;\n urValue = EMPTY;\n }\n\n // record result\n if (coord == llCell)\n {\n fragColor = llValue;\n }\n else if (coord == lrCell)\n {\n fragColor = lrValue;\n }\n else if (coord == ulCell)\n {\n fragColor = ulValue;\n }\n else if (coord == urCell)\n {\n fragColor = urValue;\n }\n}\n\n#define keyClick(ascii) ( texelFetch(keySampler,ivec2(ascii,1),0).x > 0.)\n#define keyDown(ascii) ( texelFetch(keySampler,ivec2(ascii,0),0).x > 0.)\n\n#define KEY_SHIFT 16\n#define KEY_SPACE 32\n\nvec4 updateState(sampler2D sandSampler, int iFrame, vec4 iMouse, vec3 iResolution)\n{\n const float DOUBLE_CLICK_FRAMES = 40.0;\n vec4 oldState = texelFetch(sandSampler, ivec2(0), 0);\n if (iFrame == 0) oldState = vec4(0.0, 0.0, S_RAINBOW, 1.0); // init state\n int palette = int(oldState.y);\n float selection = oldState.z;\n float clickState = oldState.w;\n if (iMouse.z > 0.0 && iMouse.y > iResolution.y * 0.9)\n {\n float u = iMouse.x / iResolution.x;\n float newSelection = floor(u * SELECT_CHOICES);\n\n if (iMouse.w > 0.0)\n {\n if (clickState < 0.0 && clickState > -DOUBLE_CLICK_FRAMES && newSelection == selection)\n {\n // double click detected\n clickState = 0.0;\n \n if (newSelection == S_RAINBOW)\n {\n // change palette\n palette = (palette + 1) % NUM_PALLETTES;\n }\n }\n else\n {\n clickState = 1.0;\n }\n }\n else\n {\n // count frames held down\n ++clickState;\n }\n\n selection = newSelection;\n }\n else\n {\n if (clickState < 0.0)\n {\n // count frames released...\n --clickState;\n }\n else if (clickState > 1.0 && clickState < 1.0 + DOUBLE_CLICK_FRAMES)\n {\n // ...but only if it was held down only a short time.\n clickState = -1.0;\n }\n }\n \n return vec4(iMouse.x * iResolution.y + iMouse.y, float(palette), selection, clickState);\n}\n\nvoid bufferMainInternal( out vec4 fragColor, in vec2 fragCoord, in ivec2 offset, in sampler2D sandSampler, in sampler2D keySampler, int iFrame, vec3 iResolution, vec4 iMouse)\n{\n ivec2 ifc = ivec2(fragCoord);\n\n // handle persistent state\n if (ifc == ivec2(0, 0))\n {\n fragColor = updateState(sandSampler, iFrame, iMouse, iResolution);\n return;\n }\n \n vec4 state = texelFetch(sandSampler, ivec2(0), 0);\n int pallette = int(state.y);\n float selection = state.z;\n bool doubleClick = state.w == 0.0;\n\n // only use a quarter of the screen for simulation\n if (fragCoord.x > iResolution.x * 0.25 || fragCoord.y > iResolution.y * 0.5)\n {\n return;\n }\n \n // init scene\n if (iFrame == 0 ||\n keyDown(KEY_SPACE) ||\n (doubleClick && selection == S_ERASE))\n {\n fragColor = EMPTY;\n \n return;\n }\n\n // integration step\n evolveByCells(sandSampler, iFrame, ifc, offset, ivec2(iResolution.xy), fragColor);\n \n // user interaction\n float oldEncodedMouse = state.x;\n vec2 oldMouse = vec2(oldEncodedMouse / iResolution.y, mod(oldEncodedMouse, iResolution.y));\n if (selection == S_ERASE)\n removeSand(iFrame, fragCoord, iResolution, oldMouse, iMouse, fragColor);\n else\n spawnSand(iFrame, fragCoord, iResolution, oldMouse, iMouse, selection, pallette, fragColor);\n \n // perform double click action if requested\n if (doubleClick && selection != S_RAINBOW) handleDoubleClick(selection, fragCoord, iResolution, iFrame, fragColor);\n}\n\n#define bufferMain(X, Y, Z) bufferMainInternal(X, Y, Z, iChannel0, iChannel3, iFrame, iResolution, iMouse)\nvoid mainImage( out vec4 fragColor, in vec2 fragCoord )\n{\n bufferMain(fragColor, fragCoord, ivec2(1));\n}\n\n","type":"Image"},{"class":"GLSLShader","name":"Buffer C","source":"// options\nconst float WALL_SIZE = 0.0025;\nconst float SPAWN_SIZE = 0.01;\nconst int COLOR_CHANGE_FRAMES = 200;\n\n// tool bar selections\n#define S_RAINBOW 7.0\n#define S_WALL 8.0\n#define S_ERASE 9.0\n#define SELECT_CHOICES 10.0\n\n// special pixel values\nconst vec4 EMPTY = vec4(0);\nconst vec4 WALL = vec4(1);\n\nfloat length2(vec2 v)\n{\n return dot(v, v);\n}\n\nfloat square(float x)\n{\n return x * x;\n}\n\nconst int NUM_PALLETTES = 3;\n\nvec4 rainbow(int i, int palette)\n{\n switch(palette)\n {\n case 0: // actual rainbow\n switch(i)\n {\n case 0:\n return vec4(1.0, 0.0, 0.0, 2.0);\n case 1:\n return vec4(1.0, 0.5, 0.0, 3.0);\n case 2:\n return vec4(1.0, 1.0, 0.0, 4.0);\n case 3:\n return vec4(0.0, 1.0, 0.0, 5.0);\n case 4:\n return vec4(0.0, 0.0, 1.0, 6.0);\n case 5:\n return vec4(0.25, 0.0, 0.5, 7.0);\n case 6:\n return vec4(0.5, 0.0, 0.7, 8.0);\n }\n case 1: // grey scale\n switch(i)\n {\n case 0:\n return vec4(0.1, 0.1, 0.1, 9.0);\n case 1:\n return vec4(0.25, 0.25, 0.25, 10.0);\n case 2:\n return vec4(0.4, 0.4, 0.4, 11.0);\n case 3:\n return vec4(0.55, 0.55, 0.55, 12.0);\n case 4:\n return vec4(0.7, 0.7, 0.7, 13.0);\n case 5:\n return vec4(0.85, 0.85, 0.85, 14.0);\n case 6:\n return vec4(0.99, 0.99, 0.99, 15.0);\n }\n case 2: // natural colors (draw a mountain scene!)\n switch(i)\n {\n case 0:\n return vec4(222.0 / 256.0, 204.0 / 256.0, 166.0 / 256.0, 16.0);\n case 1:\n return vec4(164.0 / 256.0, 167.0 / 256.0, 38.0 / 256.0, 17.0);\n case 2:\n return vec4(25.0 / 256.0, 121.0 / 256.0, 39.0 / 256.0, 18.0);\n case 3:\n return vec4(100.0 / 256.0, 100.0 / 256.0, 110.0 / 256.0, 19.0);\n case 4:\n return vec4(112.0 / 256.0, 100.0 / 256.0, 84.0 / 256.0, 20.0);\n case 5:\n return vec4(148.0 / 256.0, 91.0 / 256.0, 20.0 / 256.0, 21.0);\n case 6:\n return vec4(56.0 / 256.0, 29.0 / 256.0, 10.0 / 256.0, 22.0);\n }\n } \n}\n\nvec4 colorByFrame(int frame, int pallette)\n{ \n // compute the current color for the color-cycling rainbow spawn selection\n int colorIndex = (frame / COLOR_CHANGE_FRAMES) % 7;\n int nextColorIndex = (colorIndex + 1) % 7;\n int blendIndex = frame % COLOR_CHANGE_FRAMES;\n float blend = float(blendIndex) / float(COLOR_CHANGE_FRAMES);\n return mix(rainbow(colorIndex, pallette), rainbow(nextColorIndex, pallette), blend);\n}\n\nfloat hash( int k )\n{\n uint n = uint(k);\n\tn = (n << 13U) ^ n;\n n = n * (n * n * 15731U + 789221U) + 1376312589U;\n return uintBitsToFloat( (n>>9U) | 0x3f800000U ) - 1.0;\n}\n\nfloat linePointDist2(in vec2 newPos, in vec2 oldPos, in vec2 fragCoord, in vec3 resolution)\n{\n vec2 pDelta = (fragCoord - oldPos);\n vec2 delta = newPos - oldPos;\n float deltaLen2 = dot(delta, delta);\n\n // find the closest point on the line segment from old to new\n vec2 closest;\n if (deltaLen2 > 0.0000001)\n {\n float deltaInvSqrt = inversesqrt(deltaLen2);\n vec2 deltaNorm = delta * deltaInvSqrt;\n closest = oldPos + deltaNorm * max(0.0, min(1.0 / deltaInvSqrt, dot(deltaNorm, pDelta)));\n }\n else\n {\n // line was very short anyway\n closest = oldPos;\n }\n\n // distance to closest point on line segment\n vec2 closestDelta = closest - fragCoord;\n closestDelta *= resolution.xy / resolution.y;\n return length2(closestDelta);\n}\n\nfloat dist2hopper(vec2 fragCoord, vec3 res, vec2 pos, vec2 dim, float opening)\n{\n float dist2 = linePointDist2(vec2(pos.x - dim.x, pos.y) * res.xy, vec2(pos.x - dim.x, pos.y + dim.y) * res.xy, fragCoord, res);\n dist2 = min(dist2, linePointDist2(vec2(pos.x + dim.x, pos.y) * res.xy, vec2(pos.x + dim.x, pos.y + dim.y) * res.xy, fragCoord, res));\n dist2 = min(dist2, linePointDist2(vec2(pos.x + dim.x, pos.y) * res.xy, vec2(pos.x + opening, pos.y) * res.xy, fragCoord, res));\n dist2 = min(dist2, linePointDist2(vec2(pos.x - dim.x, pos.y) * res.xy, vec2(pos.x - opening, pos.y) * res.xy, fragCoord, res));\n return dist2;\n}\n\n// https://iquilezles.org/articles/distfunctions2d/\nfloat sdArc( in vec2 p, in vec2 sc, in float ra, float rb )\n{\n // sc is the sin/cos of the arc's aperture\n p.x = abs(p.x);\n return ((sc.y*p.x>sc.x*p.y) ? length(p-sc*ra) : \n abs(length(p)-ra)) - rb;\n}\n\n#define rnd( x) fract(1000.*sin(345.2345*x))\n#define id( x,y) floor(x)+100.*floor(y)\n\n// from maze 2 by FabriceNeyret2\n// https://www.shadertoy.com/view/4sSXWR\nfloat maze(vec2 u) {\n float n = id(u.x,u.y); u = fract(u);\n return 1.-smoothstep(.1,.15,((rnd(n)>.5)?u.x:u.y));\n}\n\n// from Exercise: basic truchet tiling by endymion\n// https://www.shadertoy.com/view/WlcfWf\nfloat truchet(in vec3 res, in int frame, in vec2 fragCoord) {\n vec2 uv = (fragCoord.xy - 0.5 * res.xy) / res.y;\n \n // Zoom;\n float scale = 10.0;\n uv *= scale; \n \n // Tiles from -.5 to .5\n vec2 gv = fract(uv) - .5;\n \n // Rotate\n vec2 id = floor(uv);\n float r = hash(int(id.x + 37.5 * id.y + 9.0 * float(frame)));\n if (r < .5) gv.x *= -1.;\n \n // Curves\n float sgn = sign(gv.x + gv.y);\n sgn = sgn == 0. ? 1. : sgn;\n float dist = abs(abs(gv.x + gv.y) - .5);\n dist = length(gv - sgn * .5) - .5;\n float width = 0.002 * scale;\n return smoothstep(scale/res.y, -scale/res.y, abs(dist) - width);\n}\n\nvoid handleDoubleClick(float selection, vec2 fragCoord, vec3 res, int iFrame, inout vec4 fragColor)\n{\n bool shouldBeWall = false;\n float dist2 = 1e6;\n \n // wall scenes you can select by double clicking on the specific color tabs\n switch(int(selection))\n {\n case 0:\n // big V\n fragCoord.x = abs(fragCoord.x - res.x * 0.125) + res.x * 0.125;\n dist2 = linePointDist2(vec2(0.13, 0.1) * res.xy, vec2(0.215, 0.4) * res.xy, fragCoord, res);\n shouldBeWall = dist2 < square(res.x * WALL_SIZE);\n\n break;\n \n case 1:\n // cascading hoppers\n fragCoord.x = res.x * 0.125 - abs(fragCoord.x - res.x * 0.125);\n dist2 = dist2hopper(fragCoord, res, vec2(0.035, 0.35), vec2(0.025, 0.05), 0.003);\n dist2 = min(dist2, dist2hopper(fragCoord, res, vec2(0.055, 0.25), vec2(0.025, 0.05), 0.003));\n dist2 = min(dist2, dist2hopper(fragCoord, res, vec2(0.075, 0.15), vec2(0.025, 0.05), 0.003));\n dist2 = min(dist2, dist2hopper(fragCoord, res, vec2(0.125, 0.05), vec2(0.06, 0.05), 0.003));\n shouldBeWall = dist2 < square(res.x * WALL_SIZE);\n\n break;\n \n case 2:\n // horizontal lines\n const float HEIGHT = 0.09;\n fragCoord.x = abs(fragCoord.x - res.x * 0.125) + res.x * 0.125;\n fragCoord.y = mod(fragCoord.y, res.x * HEIGHT * 1.25);\n dist2 = linePointDist2(vec2(0.1, HEIGHT) * res.xy, vec2(0.15, HEIGHT) * res.xy, fragCoord, res);\n dist2 = min(dist2, linePointDist2(vec2(0.175, HEIGHT) * res.xy, vec2(0.225, HEIGHT) * res.xy, fragCoord, res));\n dist2 = min(dist2, linePointDist2(vec2(0.2, HEIGHT + HEIGHT) * res.xy, vec2(0.1375, HEIGHT + HEIGHT) * res.xy, fragCoord, res));\n shouldBeWall = dist2 < square(res.x * WALL_SIZE);\n\n break;\n\n case 3:\n // slopes\n fragCoord.x = abs(fragCoord.x - res.x * 0.125) + res.x * 0.125;\n fragCoord.y = mod(fragCoord.y, res.x * HEIGHT * 1.25);\n dist2 = linePointDist2(vec2(0.125, HEIGHT * 1.2) * res.xy, vec2(0.15, HEIGHT) * res.xy, fragCoord, res);\n dist2 = min(dist2, linePointDist2(vec2(0.175, HEIGHT) * res.xy, vec2(0.225, HEIGHT * 1.2) * res.xy, fragCoord, res));\n dist2 = min(dist2, linePointDist2(vec2(0.2, HEIGHT * 2.2) * res.xy, vec2(0.1375, HEIGHT * 2.0) * res.xy, fragCoord, res));\n shouldBeWall = dist2 < square(res.x * WALL_SIZE);\n\n break;\n \n case 4:\n // bowls\n fragCoord.x = abs(fragCoord.x - res.x * 0.125) + res.x * 0.125;\n fragCoord.y = res.y * 0.125 - fragCoord.y;\n const float theta = 2.0;\n const vec2 sc = vec2(sin(theta), cos(theta));\n float dist = sdArc((fragCoord / res.x) - vec2(0.2, 0.0), sc, 0.02, 0.0);\n dist = min(dist, sdArc((fragCoord / res.x) - vec2(0.15, -0.14), sc, 0.02, 0.0));\n dist = min(dist, sdArc((fragCoord / res.x) - vec2(0.175, -0.07), sc, 0.02, 0.0));\n dist = min(dist, sdArc((fragCoord / res.x) - vec2(0.2, -0.14), sc, 0.02, 0.0));\n dist = min(dist, sdArc((fragCoord / res.x) - vec2(0.15, 0.0), sc, 0.02, 0.0));\n shouldBeWall = dist < WALL_SIZE;\n\n break;\n\n case 5:\n // maze \n fragCoord.x += mod(float(iFrame % 1000) * 10.0, res.x * 10.0);\n const float MAZE_SCALE = 1.6;\n dist = maze(MAZE_SCALE * vec2(square(res.x / res.y), 1.0)*fragCoord/(res.xy * 0.125));\n shouldBeWall = dist > 0.060 * res.x * WALL_SIZE;\n\n break;\n \n case 6:\n // truchet\n fragCoord.x *= res.x / res.y;\n dist = truchet(res, iFrame, fragCoord);\n shouldBeWall = dist > 0.060 * res.x * WALL_SIZE;\n\n break;\n }\n\n if (shouldBeWall)\n fragColor = WALL;\n else if (fragColor == WALL)\n fragColor = EMPTY;\n}\n\nvoid spawnSand(int frame, vec2 fragCoord, vec3 res, vec2 oldMouse, vec4 newMouse, float selection, int pallette, inout vec4 fragColor)\n{\n if (newMouse.z > 0.0 && newMouse.y < res.y * 0.9)\n {\n // compute the distance to the line segment from oldMouse to newMouse\n // using a capsule instead of a sphere prevents gaps when the mouse is moved quickly\n vec2 spawnBegin = oldMouse / vec2(4.0, 2.0);\n vec2 spawnEnd = newMouse.xy / vec2(4.0, 2.0);\n if (newMouse.w > 0.0) spawnBegin = spawnEnd;\n float dist2 = linePointDist2(spawnBegin, spawnEnd, fragCoord, res);\n \n if (selection == S_WALL)\n {\n if (dist2 < square(res.x * WALL_SIZE))\n {\n // wall selection (white)\n fragColor = WALL;\n }\n }\n else if (fragColor == EMPTY && dist2 < square(res.x * SPAWN_SIZE))\n {\n float noise = (0.55 + 0.45 * hash(frame * int(fragCoord.x) * int(fragCoord.y)));\n if (selection < S_RAINBOW)\n {\n // specific color selection\n fragColor = rainbow(int(selection), pallette) * vec4(noise, noise, noise, 1.0);\n }\n else\n {\n // rainbow selection\n fragColor = colorByFrame(frame, pallette) * vec4(noise, noise, noise, 1.0);\n }\n }\n }\n}\n\nvoid removeSand(int frame, vec2 fragCoord, vec3 res, vec2 oldMouse, vec4 newMouse, inout vec4 fragColor)\n{\n if (newMouse.z > 0.0)\n {\n vec2 removeBegin = oldMouse / vec2(4.0, 2.0);\n vec2 removeEnd = newMouse.xy / vec2(4.0, 2.0);\n if (newMouse.w > 0.0) removeBegin = removeEnd;\n float dist2 = linePointDist2(removeBegin, removeEnd, fragCoord, res);\n \n if (dist2 < square(res.x * 0.01))\n {\n // erase selection (black)\n fragColor = EMPTY;\n }\n }\n}\n\nvoid evolveByCells(sampler2D sampler, int frame, ivec2 coord, ivec2 offset, ivec2 ires, out vec4 fragColor)\n{\n // compute coordinates for the four pixels in our cell\n ivec2 cellCoord = (coord - offset) / 2;\n ivec2 llCell = cellCoord * 2 + offset;\n ivec2 lrCell = llCell + ivec2(1, 0);\n ivec2 ulCell = llCell + ivec2(0, 1);\n ivec2 urCell = llCell + ivec2(1, 1);\n \n if (!all(lessThan(urCell, ivec2(ires.x / 4, ires.y / 2))) ||\n (offset != ivec2(0) && (coord.x == 0 || coord.y == 0)))\n {\n // don't move particles at the bottom of the screen or off the edge\n fragColor = texelFetch(sampler, coord, 0);\n return;\n }\n \n // fetch the members of our cell\n vec4 ulValue = texelFetch(sampler, ulCell, 0);\n vec4 urValue = texelFetch(sampler, urCell, 0);\n vec4 llValue = texelFetch(sampler, llCell, 0);\n vec4 lrValue = texelFetch(sampler, lrCell, 0);\n \n // figure out which are empty\n bvec4 cell = bvec4(ulValue != EMPTY, urValue != EMPTY, llValue != EMPTY, lrValue != EMPTY);\n \n // try to match a pattern that should fall\n if ((cell == bvec4(true, false,\n false, false) ||\n cell == bvec4(true, false,\n false, true)||\n cell == bvec4(true, true,\n false, true)) && ulValue != WALL)\n {\n // left side falls\n llValue = ulValue;\n ulValue = EMPTY;\n }\n else if ((cell == bvec4(false, true,\n false, false) ||\n cell == bvec4(false, true,\n true, false) ||\n cell == bvec4(true, true,\n true, false)) && urValue != WALL)\n {\n // right side falls\n lrValue = urValue;\n urValue = EMPTY;\n }\n else if (cell == bvec4(true, true,\n false, false))\n {\n // both sides fall\n if (urValue != WALL)\n {\n lrValue = urValue;\n urValue = EMPTY;\n }\n if (ulValue != WALL)\n {\n llValue = ulValue;\n ulValue = EMPTY;\n }\n }\n else if ((cell == bvec4(true, false,\n true, false)) && ulValue != WALL)\n {\n // left side collapses\n lrValue = ulValue;\n ulValue = EMPTY;\n }\n else if ((cell == bvec4(false, true,\n false, true)) && urValue != WALL)\n {\n // right side collapses\n llValue = urValue;\n urValue = EMPTY;\n }\n\n // record result\n if (coord == llCell)\n {\n fragColor = llValue;\n }\n else if (coord == lrCell)\n {\n fragColor = lrValue;\n }\n else if (coord == ulCell)\n {\n fragColor = ulValue;\n }\n else if (coord == urCell)\n {\n fragColor = urValue;\n }\n}\n\n#define keyClick(ascii) ( texelFetch(keySampler,ivec2(ascii,1),0).x > 0.)\n#define keyDown(ascii) ( texelFetch(keySampler,ivec2(ascii,0),0).x > 0.)\n\n#define KEY_SHIFT 16\n#define KEY_SPACE 32\n\nvec4 updateState(sampler2D sandSampler, int iFrame, vec4 iMouse, vec3 iResolution)\n{\n const float DOUBLE_CLICK_FRAMES = 40.0;\n vec4 oldState = texelFetch(sandSampler, ivec2(0), 0);\n if (iFrame == 0) oldState = vec4(0.0, 0.0, S_RAINBOW, 1.0); // init state\n int palette = int(oldState.y);\n float selection = oldState.z;\n float clickState = oldState.w;\n if (iMouse.z > 0.0 && iMouse.y > iResolution.y * 0.9)\n {\n float u = iMouse.x / iResolution.x;\n float newSelection = floor(u * SELECT_CHOICES);\n\n if (iMouse.w > 0.0)\n {\n if (clickState < 0.0 && clickState > -DOUBLE_CLICK_FRAMES && newSelection == selection)\n {\n // double click detected\n clickState = 0.0;\n \n if (newSelection == S_RAINBOW)\n {\n // change palette\n palette = (palette + 1) % NUM_PALLETTES;\n }\n }\n else\n {\n clickState = 1.0;\n }\n }\n else\n {\n // count frames held down\n ++clickState;\n }\n\n selection = newSelection;\n }\n else\n {\n if (clickState < 0.0)\n {\n // count frames released...\n --clickState;\n }\n else if (clickState > 1.0 && clickState < 1.0 + DOUBLE_CLICK_FRAMES)\n {\n // ...but only if it was held down only a short time.\n clickState = -1.0;\n }\n }\n \n return vec4(iMouse.x * iResolution.y + iMouse.y, float(palette), selection, clickState);\n}\n\nvoid bufferMainInternal( out vec4 fragColor, in vec2 fragCoord, in ivec2 offset, in sampler2D sandSampler, in sampler2D keySampler, int iFrame, vec3 iResolution, vec4 iMouse)\n{\n ivec2 ifc = ivec2(fragCoord);\n\n // handle persistent state\n if (ifc == ivec2(0, 0))\n {\n fragColor = updateState(sandSampler, iFrame, iMouse, iResolution);\n return;\n }\n \n vec4 state = texelFetch(sandSampler, ivec2(0), 0);\n int pallette = int(state.y);\n float selection = state.z;\n bool doubleClick = state.w == 0.0;\n\n // only use a quarter of the screen for simulation\n if (fragCoord.x > iResolution.x * 0.25 || fragCoord.y > iResolution.y * 0.5)\n {\n return;\n }\n \n // init scene\n if (iFrame == 0 ||\n keyDown(KEY_SPACE) ||\n (doubleClick && selection == S_ERASE))\n {\n fragColor = EMPTY;\n \n return;\n }\n\n // integration step\n evolveByCells(sandSampler, iFrame, ifc, offset, ivec2(iResolution.xy), fragColor);\n \n // user interaction\n float oldEncodedMouse = state.x;\n vec2 oldMouse = vec2(oldEncodedMouse / iResolution.y, mod(oldEncodedMouse, iResolution.y));\n if (selection == S_ERASE)\n removeSand(iFrame, fragCoord, iResolution, oldMouse, iMouse, fragColor);\n else\n spawnSand(iFrame, fragCoord, iResolution, oldMouse, iMouse, selection, pallette, fragColor);\n \n // perform double click action if requested\n if (doubleClick && selection != S_RAINBOW) handleDoubleClick(selection, fragCoord, iResolution, iFrame, fragColor);\n}\n\n#define bufferMain(X, Y, Z) bufferMainInternal(X, Y, Z, iChannel0, iChannel3, iFrame, iResolution, iMouse)\nvoid mainImage( out vec4 fragColor, in vec2 fragCoord )\n{\n bufferMain(fragColor, fragCoord, ivec2(0));\n}\n\n","type":"Image"},{"class":"GLSLShader","name":"Buffer D","source":"// options\nconst float WALL_SIZE = 0.0025;\nconst float SPAWN_SIZE = 0.01;\nconst int COLOR_CHANGE_FRAMES = 200;\n\n// tool bar selections\n#define S_RAINBOW 7.0\n#define S_WALL 8.0\n#define S_ERASE 9.0\n#define SELECT_CHOICES 10.0\n\n// special pixel values\nconst vec4 EMPTY = vec4(0);\nconst vec4 WALL = vec4(1);\n\nfloat length2(vec2 v)\n{\n return dot(v, v);\n}\n\nfloat square(float x)\n{\n return x * x;\n}\n\nconst int NUM_PALLETTES = 3;\n\nvec4 rainbow(int i, int palette)\n{\n switch(palette)\n {\n case 0: // actual rainbow\n switch(i)\n {\n case 0:\n return vec4(1.0, 0.0, 0.0, 2.0);\n case 1:\n return vec4(1.0, 0.5, 0.0, 3.0);\n case 2:\n return vec4(1.0, 1.0, 0.0, 4.0);\n case 3:\n return vec4(0.0, 1.0, 0.0, 5.0);\n case 4:\n return vec4(0.0, 0.0, 1.0, 6.0);\n case 5:\n return vec4(0.25, 0.0, 0.5, 7.0);\n case 6:\n return vec4(0.5, 0.0, 0.7, 8.0);\n }\n case 1: // grey scale\n switch(i)\n {\n case 0:\n return vec4(0.1, 0.1, 0.1, 9.0);\n case 1:\n return vec4(0.25, 0.25, 0.25, 10.0);\n case 2:\n return vec4(0.4, 0.4, 0.4, 11.0);\n case 3:\n return vec4(0.55, 0.55, 0.55, 12.0);\n case 4:\n return vec4(0.7, 0.7, 0.7, 13.0);\n case 5:\n return vec4(0.85, 0.85, 0.85, 14.0);\n case 6:\n return vec4(0.99, 0.99, 0.99, 15.0);\n }\n case 2: // natural colors (draw a mountain scene!)\n switch(i)\n {\n case 0:\n return vec4(222.0 / 256.0, 204.0 / 256.0, 166.0 / 256.0, 16.0);\n case 1:\n return vec4(164.0 / 256.0, 167.0 / 256.0, 38.0 / 256.0, 17.0);\n case 2:\n return vec4(25.0 / 256.0, 121.0 / 256.0, 39.0 / 256.0, 18.0);\n case 3:\n return vec4(100.0 / 256.0, 100.0 / 256.0, 110.0 / 256.0, 19.0);\n case 4:\n return vec4(112.0 / 256.0, 100.0 / 256.0, 84.0 / 256.0, 20.0);\n case 5:\n return vec4(148.0 / 256.0, 91.0 / 256.0, 20.0 / 256.0, 21.0);\n case 6:\n return vec4(56.0 / 256.0, 29.0 / 256.0, 10.0 / 256.0, 22.0);\n }\n } \n}\n\nvec4 colorByFrame(int frame, int pallette)\n{ \n // compute the current color for the color-cycling rainbow spawn selection\n int colorIndex = (frame / COLOR_CHANGE_FRAMES) % 7;\n int nextColorIndex = (colorIndex + 1) % 7;\n int blendIndex = frame % COLOR_CHANGE_FRAMES;\n float blend = float(blendIndex) / float(COLOR_CHANGE_FRAMES);\n return mix(rainbow(colorIndex, pallette), rainbow(nextColorIndex, pallette), blend);\n}\n\nfloat hash( int k )\n{\n uint n = uint(k);\n\tn = (n << 13U) ^ n;\n n = n * (n * n * 15731U + 789221U) + 1376312589U;\n return uintBitsToFloat( (n>>9U) | 0x3f800000U ) - 1.0;\n}\n\nfloat linePointDist2(in vec2 newPos, in vec2 oldPos, in vec2 fragCoord, in vec3 resolution)\n{\n vec2 pDelta = (fragCoord - oldPos);\n vec2 delta = newPos - oldPos;\n float deltaLen2 = dot(delta, delta);\n\n // find the closest point on the line segment from old to new\n vec2 closest;\n if (deltaLen2 > 0.0000001)\n {\n float deltaInvSqrt = inversesqrt(deltaLen2);\n vec2 deltaNorm = delta * deltaInvSqrt;\n closest = oldPos + deltaNorm * max(0.0, min(1.0 / deltaInvSqrt, dot(deltaNorm, pDelta)));\n }\n else\n {\n // line was very short anyway\n closest = oldPos;\n }\n\n // distance to closest point on line segment\n vec2 closestDelta = closest - fragCoord;\n closestDelta *= resolution.xy / resolution.y;\n return length2(closestDelta);\n}\n\nfloat dist2hopper(vec2 fragCoord, vec3 res, vec2 pos, vec2 dim, float opening)\n{\n float dist2 = linePointDist2(vec2(pos.x - dim.x, pos.y) * res.xy, vec2(pos.x - dim.x, pos.y + dim.y) * res.xy, fragCoord, res);\n dist2 = min(dist2, linePointDist2(vec2(pos.x + dim.x, pos.y) * res.xy, vec2(pos.x + dim.x, pos.y + dim.y) * res.xy, fragCoord, res));\n dist2 = min(dist2, linePointDist2(vec2(pos.x + dim.x, pos.y) * res.xy, vec2(pos.x + opening, pos.y) * res.xy, fragCoord, res));\n dist2 = min(dist2, linePointDist2(vec2(pos.x - dim.x, pos.y) * res.xy, vec2(pos.x - opening, pos.y) * res.xy, fragCoord, res));\n return dist2;\n}\n\n// https://iquilezles.org/articles/distfunctions2d/\nfloat sdArc( in vec2 p, in vec2 sc, in float ra, float rb )\n{\n // sc is the sin/cos of the arc's aperture\n p.x = abs(p.x);\n return ((sc.y*p.x>sc.x*p.y) ? length(p-sc*ra) : \n abs(length(p)-ra)) - rb;\n}\n\n#define rnd( x) fract(1000.*sin(345.2345*x))\n#define id( x,y) floor(x)+100.*floor(y)\n\n// from maze 2 by FabriceNeyret2\n// https://www.shadertoy.com/view/4sSXWR\nfloat maze(vec2 u) {\n float n = id(u.x,u.y); u = fract(u);\n return 1.-smoothstep(.1,.15,((rnd(n)>.5)?u.x:u.y));\n}\n\n// from Exercise: basic truchet tiling by endymion\n// https://www.shadertoy.com/view/WlcfWf\nfloat truchet(in vec3 res, in int frame, in vec2 fragCoord) {\n vec2 uv = (fragCoord.xy - 0.5 * res.xy) / res.y;\n \n // Zoom;\n float scale = 10.0;\n uv *= scale; \n \n // Tiles from -.5 to .5\n vec2 gv = fract(uv) - .5;\n \n // Rotate\n vec2 id = floor(uv);\n float r = hash(int(id.x + 37.5 * id.y + 9.0 * float(frame)));\n if (r < .5) gv.x *= -1.;\n \n // Curves\n float sgn = sign(gv.x + gv.y);\n sgn = sgn == 0. ? 1. : sgn;\n float dist = abs(abs(gv.x + gv.y) - .5);\n dist = length(gv - sgn * .5) - .5;\n float width = 0.002 * scale;\n return smoothstep(scale/res.y, -scale/res.y, abs(dist) - width);\n}\n\nvoid handleDoubleClick(float selection, vec2 fragCoord, vec3 res, int iFrame, inout vec4 fragColor)\n{\n bool shouldBeWall = false;\n float dist2 = 1e6;\n \n // wall scenes you can select by double clicking on the specific color tabs\n switch(int(selection))\n {\n case 0:\n // big V\n fragCoord.x = abs(fragCoord.x - res.x * 0.125) + res.x * 0.125;\n dist2 = linePointDist2(vec2(0.13, 0.1) * res.xy, vec2(0.215, 0.4) * res.xy, fragCoord, res);\n shouldBeWall = dist2 < square(res.x * WALL_SIZE);\n\n break;\n \n case 1:\n // cascading hoppers\n fragCoord.x = res.x * 0.125 - abs(fragCoord.x - res.x * 0.125);\n dist2 = dist2hopper(fragCoord, res, vec2(0.035, 0.35), vec2(0.025, 0.05), 0.003);\n dist2 = min(dist2, dist2hopper(fragCoord, res, vec2(0.055, 0.25), vec2(0.025, 0.05), 0.003));\n dist2 = min(dist2, dist2hopper(fragCoord, res, vec2(0.075, 0.15), vec2(0.025, 0.05), 0.003));\n dist2 = min(dist2, dist2hopper(fragCoord, res, vec2(0.125, 0.05), vec2(0.06, 0.05), 0.003));\n shouldBeWall = dist2 < square(res.x * WALL_SIZE);\n\n break;\n \n case 2:\n // horizontal lines\n const float HEIGHT = 0.09;\n fragCoord.x = abs(fragCoord.x - res.x * 0.125) + res.x * 0.125;\n fragCoord.y = mod(fragCoord.y, res.x * HEIGHT * 1.25);\n dist2 = linePointDist2(vec2(0.1, HEIGHT) * res.xy, vec2(0.15, HEIGHT) * res.xy, fragCoord, res);\n dist2 = min(dist2, linePointDist2(vec2(0.175, HEIGHT) * res.xy, vec2(0.225, HEIGHT) * res.xy, fragCoord, res));\n dist2 = min(dist2, linePointDist2(vec2(0.2, HEIGHT + HEIGHT) * res.xy, vec2(0.1375, HEIGHT + HEIGHT) * res.xy, fragCoord, res));\n shouldBeWall = dist2 < square(res.x * WALL_SIZE);\n\n break;\n\n case 3:\n // slopes\n fragCoord.x = abs(fragCoord.x - res.x * 0.125) + res.x * 0.125;\n fragCoord.y = mod(fragCoord.y, res.x * HEIGHT * 1.25);\n dist2 = linePointDist2(vec2(0.125, HEIGHT * 1.2) * res.xy, vec2(0.15, HEIGHT) * res.xy, fragCoord, res);\n dist2 = min(dist2, linePointDist2(vec2(0.175, HEIGHT) * res.xy, vec2(0.225, HEIGHT * 1.2) * res.xy, fragCoord, res));\n dist2 = min(dist2, linePointDist2(vec2(0.2, HEIGHT * 2.2) * res.xy, vec2(0.1375, HEIGHT * 2.0) * res.xy, fragCoord, res));\n shouldBeWall = dist2 < square(res.x * WALL_SIZE);\n\n break;\n \n case 4:\n // bowls\n fragCoord.x = abs(fragCoord.x - res.x * 0.125) + res.x * 0.125;\n fragCoord.y = res.y * 0.125 - fragCoord.y;\n const float theta = 2.0;\n const vec2 sc = vec2(sin(theta), cos(theta));\n float dist = sdArc((fragCoord / res.x) - vec2(0.2, 0.0), sc, 0.02, 0.0);\n dist = min(dist, sdArc((fragCoord / res.x) - vec2(0.15, -0.14), sc, 0.02, 0.0));\n dist = min(dist, sdArc((fragCoord / res.x) - vec2(0.175, -0.07), sc, 0.02, 0.0));\n dist = min(dist, sdArc((fragCoord / res.x) - vec2(0.2, -0.14), sc, 0.02, 0.0));\n dist = min(dist, sdArc((fragCoord / res.x) - vec2(0.15, 0.0), sc, 0.02, 0.0));\n shouldBeWall = dist < WALL_SIZE;\n\n break;\n\n case 5:\n // maze \n fragCoord.x += mod(float(iFrame % 1000) * 10.0, res.x * 10.0);\n const float MAZE_SCALE = 1.6;\n dist = maze(MAZE_SCALE * vec2(square(res.x / res.y), 1.0)*fragCoord/(res.xy * 0.125));\n shouldBeWall = dist > 0.060 * res.x * WALL_SIZE;\n\n break;\n \n case 6:\n // truchet\n fragCoord.x *= res.x / res.y;\n dist = truchet(res, iFrame, fragCoord);\n shouldBeWall = dist > 0.060 * res.x * WALL_SIZE;\n\n break;\n }\n\n if (shouldBeWall)\n fragColor = WALL;\n else if (fragColor == WALL)\n fragColor = EMPTY;\n}\n\nvoid spawnSand(int frame, vec2 fragCoord, vec3 res, vec2 oldMouse, vec4 newMouse, float selection, int pallette, inout vec4 fragColor)\n{\n if (newMouse.z > 0.0 && newMouse.y < res.y * 0.9)\n {\n // compute the distance to the line segment from oldMouse to newMouse\n // using a capsule instead of a sphere prevents gaps when the mouse is moved quickly\n vec2 spawnBegin = oldMouse / vec2(4.0, 2.0);\n vec2 spawnEnd = newMouse.xy / vec2(4.0, 2.0);\n if (newMouse.w > 0.0) spawnBegin = spawnEnd;\n float dist2 = linePointDist2(spawnBegin, spawnEnd, fragCoord, res);\n \n if (selection == S_WALL)\n {\n if (dist2 < square(res.x * WALL_SIZE))\n {\n // wall selection (white)\n fragColor = WALL;\n }\n }\n else if (fragColor == EMPTY && dist2 < square(res.x * SPAWN_SIZE))\n {\n float noise = (0.55 + 0.45 * hash(frame * int(fragCoord.x) * int(fragCoord.y)));\n if (selection < S_RAINBOW)\n {\n // specific color selection\n fragColor = rainbow(int(selection), pallette) * vec4(noise, noise, noise, 1.0);\n }\n else\n {\n // rainbow selection\n fragColor = colorByFrame(frame, pallette) * vec4(noise, noise, noise, 1.0);\n }\n }\n }\n}\n\nvoid removeSand(int frame, vec2 fragCoord, vec3 res, vec2 oldMouse, vec4 newMouse, inout vec4 fragColor)\n{\n if (newMouse.z > 0.0)\n {\n vec2 removeBegin = oldMouse / vec2(4.0, 2.0);\n vec2 removeEnd = newMouse.xy / vec2(4.0, 2.0);\n if (newMouse.w > 0.0) removeBegin = removeEnd;\n float dist2 = linePointDist2(removeBegin, removeEnd, fragCoord, res);\n \n if (dist2 < square(res.x * 0.01))\n {\n // erase selection (black)\n fragColor = EMPTY;\n }\n }\n}\n\nvoid evolveByCells(sampler2D sampler, int frame, ivec2 coord, ivec2 offset, ivec2 ires, out vec4 fragColor)\n{\n // compute coordinates for the four pixels in our cell\n ivec2 cellCoord = (coord - offset) / 2;\n ivec2 llCell = cellCoord * 2 + offset;\n ivec2 lrCell = llCell + ivec2(1, 0);\n ivec2 ulCell = llCell + ivec2(0, 1);\n ivec2 urCell = llCell + ivec2(1, 1);\n \n if (!all(lessThan(urCell, ivec2(ires.x / 4, ires.y / 2))) ||\n (offset != ivec2(0) && (coord.x == 0 || coord.y == 0)))\n {\n // don't move particles at the bottom of the screen or off the edge\n fragColor = texelFetch(sampler, coord, 0);\n return;\n }\n \n // fetch the members of our cell\n vec4 ulValue = texelFetch(sampler, ulCell, 0);\n vec4 urValue = texelFetch(sampler, urCell, 0);\n vec4 llValue = texelFetch(sampler, llCell, 0);\n vec4 lrValue = texelFetch(sampler, lrCell, 0);\n \n // figure out which are empty\n bvec4 cell = bvec4(ulValue != EMPTY, urValue != EMPTY, llValue != EMPTY, lrValue != EMPTY);\n \n // try to match a pattern that should fall\n if ((cell == bvec4(true, false,\n false, false) ||\n cell == bvec4(true, false,\n false, true)||\n cell == bvec4(true, true,\n false, true)) && ulValue != WALL)\n {\n // left side falls\n llValue = ulValue;\n ulValue = EMPTY;\n }\n else if ((cell == bvec4(false, true,\n false, false) ||\n cell == bvec4(false, true,\n true, false) ||\n cell == bvec4(true, true,\n true, false)) && urValue != WALL)\n {\n // right side falls\n lrValue = urValue;\n urValue = EMPTY;\n }\n else if (cell == bvec4(true, true,\n false, false))\n {\n // both sides fall\n if (urValue != WALL)\n {\n lrValue = urValue;\n urValue = EMPTY;\n }\n if (ulValue != WALL)\n {\n llValue = ulValue;\n ulValue = EMPTY;\n }\n }\n else if ((cell == bvec4(true, false,\n true, false)) && ulValue != WALL)\n {\n // left side collapses\n lrValue = ulValue;\n ulValue = EMPTY;\n }\n else if ((cell == bvec4(false, true,\n false, true)) && urValue != WALL)\n {\n // right side collapses\n llValue = urValue;\n urValue = EMPTY;\n }\n\n // record result\n if (coord == llCell)\n {\n fragColor = llValue;\n }\n else if (coord == lrCell)\n {\n fragColor = lrValue;\n }\n else if (coord == ulCell)\n {\n fragColor = ulValue;\n }\n else if (coord == urCell)\n {\n fragColor = urValue;\n }\n}\n\n#define keyClick(ascii) ( texelFetch(keySampler,ivec2(ascii,1),0).x > 0.)\n#define keyDown(ascii) ( texelFetch(keySampler,ivec2(ascii,0),0).x > 0.)\n\n#define KEY_SHIFT 16\n#define KEY_SPACE 32\n\nvec4 updateState(sampler2D sandSampler, int iFrame, vec4 iMouse, vec3 iResolution)\n{\n const float DOUBLE_CLICK_FRAMES = 40.0;\n vec4 oldState = texelFetch(sandSampler, ivec2(0), 0);\n if (iFrame == 0) oldState = vec4(0.0, 0.0, S_RAINBOW, 1.0); // init state\n int palette = int(oldState.y);\n float selection = oldState.z;\n float clickState = oldState.w;\n if (iMouse.z > 0.0 && iMouse.y > iResolution.y * 0.9)\n {\n float u = iMouse.x / iResolution.x;\n float newSelection = floor(u * SELECT_CHOICES);\n\n if (iMouse.w > 0.0)\n {\n if (clickState < 0.0 && clickState > -DOUBLE_CLICK_FRAMES && newSelection == selection)\n {\n // double click detected\n clickState = 0.0;\n \n if (newSelection == S_RAINBOW)\n {\n // change palette\n palette = (palette + 1) % NUM_PALLETTES;\n }\n }\n else\n {\n clickState = 1.0;\n }\n }\n else\n {\n // count frames held down\n ++clickState;\n }\n\n selection = newSelection;\n }\n else\n {\n if (clickState < 0.0)\n {\n // count frames released...\n --clickState;\n }\n else if (clickState > 1.0 && clickState < 1.0 + DOUBLE_CLICK_FRAMES)\n {\n // ...but only if it was held down only a short time.\n clickState = -1.0;\n }\n }\n \n return vec4(iMouse.x * iResolution.y + iMouse.y, float(palette), selection, clickState);\n}\n\nvoid bufferMainInternal( out vec4 fragColor, in vec2 fragCoord, in ivec2 offset, in sampler2D sandSampler, in sampler2D keySampler, int iFrame, vec3 iResolution, vec4 iMouse)\n{\n ivec2 ifc = ivec2(fragCoord);\n\n // handle persistent state\n if (ifc == ivec2(0, 0))\n {\n fragColor = updateState(sandSampler, iFrame, iMouse, iResolution);\n return;\n }\n \n vec4 state = texelFetch(sandSampler, ivec2(0), 0);\n int pallette = int(state.y);\n float selection = state.z;\n bool doubleClick = state.w == 0.0;\n\n // only use a quarter of the screen for simulation\n if (fragCoord.x > iResolution.x * 0.25 || fragCoord.y > iResolution.y * 0.5)\n {\n return;\n }\n \n // init scene\n if (iFrame == 0 ||\n keyDown(KEY_SPACE) ||\n (doubleClick && selection == S_ERASE))\n {\n fragColor = EMPTY;\n \n return;\n }\n\n // integration step\n evolveByCells(sandSampler, iFrame, ifc, offset, ivec2(iResolution.xy), fragColor);\n \n // user interaction\n float oldEncodedMouse = state.x;\n vec2 oldMouse = vec2(oldEncodedMouse / iResolution.y, mod(oldEncodedMouse, iResolution.y));\n if (selection == S_ERASE)\n removeSand(iFrame, fragCoord, iResolution, oldMouse, iMouse, fragColor);\n else\n spawnSand(iFrame, fragCoord, iResolution, oldMouse, iMouse, selection, pallette, fragColor);\n \n // perform double click action if requested\n if (doubleClick && selection != S_RAINBOW) handleDoubleClick(selection, fragCoord, iResolution, iFrame, fragColor);\n}\n\n#define bufferMain(X, Y, Z) bufferMainInternal(X, Y, Z, iChannel0, iChannel3, iFrame, iResolution, iMouse)\nvoid mainImage( out vec4 fragColor, in vec2 fragCoord )\n{\n bufferMain(fragColor, fragCoord, ivec2(1));\n}\n\n","type":"Image"},{"class":"LastFrame","name":"LastFrame","ref":"Buffer D","type":"Image"}]}