Published
Edited
Feb 19, 2021
Importers
4 stars
Insert cell
Insert cell
Insert cell
Insert cell
map = () => `
float map(float n, float start1, float stop1, float start2, float stop2) {
return (n - start1) / (stop1 - start1) * (stop2 - start2) + start2;
}`
Insert cell
map01 = () => `
float map(float n, float start, float stop) {
return (n - start) / (stop - start);
}`
Insert cell
modularDist = () => `
float modularDist(float value, float modValue) {
return min(mod(value, modValue), abs(modValue - mod(value, modValue)));
}`
Insert cell
fsnoise = () => `
// from https://github.com/doxas/twigl
// fract sin noise
float fsnoise(vec2 c){
return fract(sin(dot(c, vec2(12.9898, 78.233))) * 43758.5453);
}

// fsnoise for macOS
float fsnoiseDigits(vec2 c){
return fract(sin(dot(c, vec2(0.129898, 0.78233))) * 437.585453);
}`
Insert cell
palette = (a, b, c, d) => {
const vec3 = arr => `vec3(${arr.map(n => n.toFixed(2)).join(',')})`
return `
// https://iquilezles.org/www/articles/palettes/palettes.htm
vec3 palette(float t){
return ${vec3(a)} + ${vec3(b)} * cos(2. * ${Math.PI} * (${vec3(c)} * t + ${vec3(d)}));
}`}
Insert cell
hsv = () => `
// from https://github.com/doxas/twigl
vec3 hsv(float h, float s, float v){
vec4 t = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(vec3(h) + t.xyz) * 6.0 - vec3(t.w));
return v * mix(vec3(t.x), clamp(p - vec3(t.x), 0.0, 1.0), s);
}`
Insert cell
rotate2D = () => `
// from https://github.com/doxas/twigl
mat2 rotate2D(float r){
return mat2(cos(r), sin(r), -sin(r), cos(r));
}`
Insert cell
rotate3D = () => `
// from https://github.com/doxas/twigl
mat3 rotate3D(float angle, vec3 axis){
vec3 a = normalize(axis);
float s = sin(angle);
float c = cos(angle);
float r = 1.0 - c;
return mat3(
a.x * a.x * r + c,
a.y * a.x * r + a.z * s,
a.z * a.x * r - a.y * s,
a.x * a.y * r - a.z * s,
a.y * a.y * r + c,
a.z * a.y * r + a.x * s,
a.x * a.z * r + a.y * s,
a.y * a.z * r - a.x * s,
a.z * a.z * r + c
);
}`
Insert cell
opBlend = () => `
// from https://github.com/SebLague/Ray-Marching/blob/f7e44c15a212dec53b244b1f53cdaf318f6ec700/Assets/Scripts/SDF/Raymarching.compute#L82
vec4 opBlend(float a, float b, vec3 colA, vec3 colB, float k) {
float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
float blendDst = lerp(b, a, h) - k * h * (1.0 - h);
vec3 blendCol = lerp(colB, colA, h);
return vec4(blendCol, blendDst);
}`
Insert cell
random = () => `
float random(float n) {
return fract(sin(n * 128.3218756321));
}`
Insert cell
Insert cell
Insert cell
Insert cell
matcap()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
easeBackInOut = () => include('glsl-easings/back-in-out.glsl')
Insert cell
easeBackIn = () => include('glsl-easings/back-in.glsl')
Insert cell
easeBackOut = () => include('glsl-easings/back-out.glsl')
Insert cell
easeBounceInOut = () => include('glsl-easings/bounce-in-out.glsl')
Insert cell
easeBounceIn = () => include('glsl-easings/bounce-in.glsl')
Insert cell
easeBounceOut = () => include('glsl-easings/bounce-out.glsl')
Insert cell
easeCircularInOut = () => include('glsl-easings/circular-in-out.glsl')
Insert cell
easeCircularIn = () => include('glsl-easings/circular-in.glsl')
Insert cell
easeCircularOut = () => include('glsl-easings/circular-out.glsl')
Insert cell
easeCubicInOut = () => include('glsl-easings/cubic-in-out.glsl')
Insert cell
easeCubicIn = () => include('glsl-easings/cubic-in.glsl')
Insert cell
easeCubicOut = () => include('glsl-easings/cubic-out.glsl')
Insert cell
easeElasticInOut = () => include('glsl-easings/elastic-in-out.glsl')
Insert cell
easeElasticIn = () => include('glsl-easings/elastic-in.glsl')
Insert cell
easeElasticOut = () => include('glsl-easings/elastic-out.glsl')
Insert cell
easeExponentialInOut = () => include('glsl-easings/exponential-in-out.glsl')
Insert cell
easeExponentialIn = () => include('glsl-easings/exponential-in.glsl')
Insert cell
easeExponentialOut = () => include('glsl-easings/exponential-out.glsl')
Insert cell
easeLinear = () => include('glsl-easings/linear.glsl')
Insert cell
easeQuadraticInOut = () => include('glsl-easings/quadratic-in-out.glsl')
Insert cell
easeQuadraticIn = () => include('glsl-easings/quadratic-in.glsl')
Insert cell
easeQuadraticOut = () => include('glsl-easings/quadratic-out.glsl')
Insert cell
easeQuarticInOut = () => include('glsl-easings/quartic-in-out.glsl')
Insert cell
easeQuarticIn = () => include('glsl-easings/quartic-in.glsl')
Insert cell
easeQuarticOut = () => include('glsl-easings/quartic-out.glsl')
Insert cell
easeQuinticInOut = () => include('glsl-easings/quintic-in-out.glsl')
Insert cell
easeQuinticIn = () => include('glsl-easings/quintic-in.glsl')
Insert cell
easeQuinticOut = () => include('glsl-easings/quintic-out.glsl')
Insert cell
easeSineInOut = () => include('glsl-easings/sine-in-out.glsl')
Insert cell
easeSineIn = () => include('glsl-easings/sine-in.glsl')
Insert cell
easeSineOut = () => include('glsl-easings/sine-out.glsl')
Insert cell
Insert cell
Insert cell
hg_sdf = () => `
////////////////////////////////////////////////////////////////
//
// HELPER FUNCTIONS/MACROS
//
////////////////////////////////////////////////////////////////

#define PI 3.14159265
#define TAU (2.*PI)
#define PHI (sqrt(5.)*0.5 + 0.5)

// Clamp to [0,1] - this operation is free under certain circumstances.
// For further information see
// http://www.humus.name/Articles/Persson_LowLevelThinking.pdf and
// http://www.humus.name/Articles/Persson_LowlevelShaderOptimization.pdf
#define saturate(x) clamp(x, 0., 1.)

// Sign function that doesn't return 0
float sgn(float x) {
return (x<0.)?-1.:1.;
}

vec2 sgn(vec2 v) {
return vec2((v.x<0.)?-1.:1., (v.y<0.)?-1.:1.);
}

float square (float x) {
return x*x;
}

vec2 square (vec2 x) {
return x*x;
}

vec3 square (vec3 x) {
return x*x;
}

float lengthSqr(vec3 x) {
return dot(x, x);
}


// Maximum/minumum elements of a vector
float vmax(vec2 v) {
return max(v.x, v.y);
}

float vmax(vec3 v) {
return max(max(v.x, v.y), v.z);
}

float vmax(vec4 v) {
return max(max(v.x, v.y), max(v.z, v.w));
}

float vmin(vec2 v) {
return min(v.x, v.y);
}

float vmin(vec3 v) {
return min(min(v.x, v.y), v.z);
}

float vmin(vec4 v) {
return min(min(v.x, v.y), min(v.z, v.w));
}


////////////////////////////////////////////////////////////////
//
// PRIMITIVE DISTANCE FUNCTIONS
//
////////////////////////////////////////////////////////////////
//
// Conventions:
//
// Everything that is a distance function is called fSomething.
// The first argument is always a point in 2 or 3-space called <p>.
// Unless otherwise noted, (if the object has an intrinsic "up"
// side or direction) the y axis is "up" and the object is
// centered at the origin.
//
////////////////////////////////////////////////////////////////

float fSphere(vec3 p, float r) {
return length(p) - r;
}

// Plane with normal n (n is normalized) at some distance from the origin
float fPlane(vec3 p, vec3 n, float distanceFromOrigin) {
return dot(p, n) + distanceFromOrigin;
}

// Cheap Box: distance to corners is overestimated
float fBoxCheap(vec3 p, vec3 b) { //cheap box
return vmax(abs(p) - b);
}

// Box: correct distance to corners
float fBox(vec3 p, vec3 b) {
vec3 d = abs(p) - b;
return length(max(d, vec3(0))) + vmax(min(d, vec3(0)));
}

// Same as above, but in two dimensions (an endless box)
float fBox2Cheap(vec2 p, vec2 b) {
return vmax(abs(p)-b);
}

float fBox2(vec2 p, vec2 b) {
vec2 d = abs(p) - b;
return length(max(d, vec2(0))) + vmax(min(d, vec2(0)));
}

// Endless "corner"
float fCorner (vec2 p) {
return length(max(p, vec2(0))) + vmax(min(p, vec2(0)));
}

// Blobby ball object. You've probably seen it somewhere. This is not a correct distance bound, beware.
float fBlob(vec3 p) {
p = abs(p);
if (p.x < max(p.y, p.z)) p = p.yzx;
if (p.x < max(p.y, p.z)) p = p.yzx;
float b = max(max(max(
dot(p, normalize(vec3(1))),
dot(p.xz, normalize(vec2(PHI+1., 1.)))),
dot(p.yx, normalize(vec2(1., PHI)))),
dot(p.xz, normalize(vec2(1., PHI))));
float l = length(p);
return l - 1.5 - 0.2 * (1.5 / 2.)* cos(min(sqrt(1.01 - b / l)*(PI / 0.25), PI));
}

// Cylinder standing upright on the xz plane
float fCylinder(vec3 p, float r, float height) {
float d = length(p.xz) - r;
d = max(d, abs(p.y) - height);
return d;
}

// Capsule: A Cylinder with round caps on both sides
float fCapsule(vec3 p, float r, float c) {
return mix(length(p.xz) - r, length(vec3(p.x, abs(p.y) - c, p.z)) - r, step(c, abs(p.y)));
}

// Distance to line segment between <a> and <b>, used for fCapsule() version 2below
float fLineSegment(vec3 p, vec3 a, vec3 b) {
vec3 ab = b - a;
float t = saturate(dot(p - a, ab) / dot(ab, ab));
return length((ab*t + a) - p);
}

// Capsule version 2: between two end points <a> and <b> with radius r
float fCapsule(vec3 p, vec3 a, vec3 b, float r) {
return fLineSegment(p, a, b) - r;
}

// Torus in the XZ-plane
float fTorus(vec3 p, float smallRadius, float largeRadius) {
return length(vec2(length(p.xz) - largeRadius, p.y)) - smallRadius;
}

// A circle line. Can also be used to make a torus by subtracting the smaller radius of the torus.
float fCircle(vec3 p, float r) {
float l = length(p.xz) - r;
return length(vec2(p.y, l));
}

// A circular disc with no thickness (i.e. a cylinder with no height).
// Subtract some value to make a flat disc with rounded edge.
float fDisc(vec3 p, float r) {
float l = length(p.xz) - r;
return l < 0. ? abs(p.y) : length(vec2(p.y, l));
}

// Hexagonal prism, circumcircle variant
float fHexagonCircumcircle(vec3 p, vec2 h) {
vec3 q = abs(p);
return max(q.y - h.y, max(q.x*sqrt(3.)*0.5 + q.z*0.5, q.z) - h.x);
//this is mathematically equivalent to this line, but less efficient:
//return max(q.y - h.y, max(dot(vec2(cos(PI/3.), sin(PI/3.)), q.zx), q.z) - h.x);
}

// Hexagonal prism, incircle variant
float fHexagonIncircle(vec3 p, vec2 h) {
return fHexagonCircumcircle(p, vec2(h.x*sqrt(3.)*0.5, h.y));
}

// Cone with correct distances to tip and base circle. Y is up, 0 is in the middle of the base.
float fCone(vec3 p, float radius, float height) {
vec2 q = vec2(length(p.xz), p.y);
vec2 tip = q - vec2(0., height);
vec2 mantleDir = normalize(vec2(height, radius));
float mantle = dot(tip, mantleDir);
float d = max(mantle, -q.y);
float projected = dot(tip, vec2(mantleDir.y, -mantleDir.x));
// distance to tip
if ((q.y > height) && (projected < 0.)) {
d = max(d, length(tip));
}
// distance to base ring
if ((q.x > radius) && (projected > length(vec2(height, radius)))) {
d = max(d, length(q - vec2(radius, 0.)));
}
return d;
}

//
// "Generalized Distance Functions" by Akleman and Chen.
// see the Paper at https://www.viz.tamu.edu/faculty/ergun/research/implicitmodeling/papers/sm99.pdf
//
// This set of constants is used to construct a large variety of geometric primitives.
// Indices are shifted by 1 compared to the paper because we start counting at Zero.
// Some of those are slow whenever a driver decides to not unroll the loop,
// which seems to happen for fIcosahedron und fTruncatedIcosahedron on nvidia 350.12 at least.
// Specialized implementations can well be faster in all cases.
//

vec3 GDFVectors[19];
void initGDFVectors() {
GDFVectors[0] = normalize(vec3(1, 0, 0));
GDFVectors[1] = normalize(vec3(0, 1, 0));
GDFVectors[2] = normalize(vec3(0, 0, 1));

GDFVectors[3] = normalize(vec3(1, 1, 1 ));
GDFVectors[4] = normalize(vec3(-1, 1, 1));
GDFVectors[5] = normalize(vec3(1, -1, 1));
GDFVectors[6] = normalize(vec3(1, 1, -1));

GDFVectors[7] = normalize(vec3(0., 1., PHI+1.));
GDFVectors[8] = normalize(vec3(0., -1., PHI+1.));
GDFVectors[9] = normalize(vec3(PHI+1., 0., 1.));
GDFVectors[10] = normalize(vec3(-PHI-1., 0., 1.));
GDFVectors[11] = normalize(vec3(1., PHI+1., 0.));
GDFVectors[12] = normalize(vec3(-1., PHI+1., 0.));

GDFVectors[13] = normalize(vec3(0., PHI, 1.));
GDFVectors[14] = normalize(vec3(0., -PHI, 1.));
GDFVectors[15] = normalize(vec3(1., 0., PHI));
GDFVectors[16] = normalize(vec3(-1., 0., PHI));
GDFVectors[17] = normalize(vec3(PHI, 1., 0.));
GDFVectors[18] = normalize(vec3(-PHI, 1., 0.));
}

// Version with variable exponent.
// This is slow and does not produce correct distances, but allows for bulging of objects.
float fOctahedron(vec3 p, float r, float e) {
float d = 0.;
for (int i = 3; i <= 6; ++i) d += pow(abs(dot(p, GDFVectors[i])), e);
return pow(d, 1./e) - r;
}

float fDodecahedron(vec3 p, float r, float e) {
float d = 0.;
for (int i = 13; i <= 18; ++i) d += pow(abs(dot(p, GDFVectors[i])), e);
return pow(d, 1./e) - r;
}

float fIcosahedron(vec3 p, float r, float e) {
float d = 0.;
for (int i = 3; i <= 12; ++i) d += pow(abs(dot(p, GDFVectors[i])), e);
return pow(d, 1./e) - r;
}

float fTruncatedOctahedron(vec3 p, float r, float e) {
float d = 0.;
for (int i = 0; i <= 6; ++i) d += pow(abs(dot(p, GDFVectors[i])), e);
return pow(d, 1./e) - r;
}

// Version with without exponent, creates objects with sharp edges and flat faces
float fTruncatedIcosahedron(vec3 p, float r, float e) {
float d = 0.;
for (int i = 3; i <= 18; ++i) d += pow(abs(dot(p, GDFVectors[i])), e);
return pow(d, 1./e) - r;
}

float fOctahedron(vec3 p, float r) {
float d = 0.;
for (int i = 3; i <= 6; ++i) d = max(d, abs(dot(p, GDFVectors[i])));
return d - r;
}

float fDodecahedron(vec3 p, float r) {
float d = 0.;
for (int i = 13; i <= 18; ++i) d = max(d, abs(dot(p, GDFVectors[i])));
return d - r;
}

float fIcosahedron(vec3 p, float r) {
float d = 0.;
for (int i = 3; i <= 12; ++i) d = max(d, abs(dot(p, GDFVectors[i])));
return d - r;
}

float fTruncatedOctahedron(vec3 p, float r) {
float d = 0.;
for (int i = 0; i <= 6; ++i) d = max(d, abs(dot(p, GDFVectors[i])));
return d - r;
}

float fTruncatedIcosahedron(vec3 p, float r) {
float d = 0.;
for (int i = 3; i <= 18; ++i) d = max(d, abs(dot(p, GDFVectors[i])));
return d - r;
}


////////////////////////////////////////////////////////////////
//
// DOMAIN MANIPULATION OPERATORS
//
////////////////////////////////////////////////////////////////
//
// Conventions:
//
// Everything that modifies the domain is named pSomething.
//
// Many operate only on a subset of the three dimensions. For those,
// you must choose the dimensions that you want manipulated
// by supplying e.g. <p.x> or <p.zx>
//
// <inout p> is always the first argument and modified in place.
//
// Many of the operators partition space into cells. An identifier
// or cell index is returned, if possible. This return value is
// intended to be optionally used e.g. as a random seed to change
// parameters of the distance functions inside the cells.
//
// Unless stated otherwise, for cell index 0, <p> is unchanged and cells
// are centered on the origin so objects don't have to be moved to fit.
//
//
////////////////////////////////////////////////////////////////

// Rotate around a coordinate axis (i.e. in a plane perpendicular to that axis) by angle <a>.
// Read like this: R(p.xz, a) rotates "x towards z".
// This is fast if <a> is a compile-time constant and slower (but still practical) if not.
void pR(inout vec2 p, float a) {
p = cos(a)*p + sin(a)*vec2(p.y, -p.x);
}

// Shortcut for 45-degrees rotation
void pR45(inout vec2 p) {
p = (p + vec2(p.y, -p.x))*sqrt(0.5);
}

// Repeat space along one axis. Use like this to repeat along the x axis:
// <float cell = pMod1(p.x,5);> - using the return value is optional.
float pMod1(inout float p, float size) {
float halfsize = size*0.5;
float c = floor((p + halfsize)/size);
p = mod(p + halfsize, size) - halfsize;
return c;
}

// Same, but mirror every second cell so they match at the boundaries
float pModMirror1(inout float p, float size) {
float halfsize = size*0.5;
float c = floor((p + halfsize)/size);
p = mod(p + halfsize,size) - halfsize;
p *= mod(c, 2.0)*2. - 1.;
return c;
}

// Repeat the domain only in positive direction. Everything in the negative half-space is unchanged.
float pModSingle1(inout float p, float size) {
float halfsize = size*0.5;
float c = floor((p + halfsize)/size);
if (p >= 0.)
p = mod(p + halfsize, size) - halfsize;
return c;
}

// Repeat only a few times: from indices <start> to <stop> (similar to above, but more flexible)
float pModInterval1(inout float p, float size, float start, float stop) {
float halfsize = size*0.5;
float c = floor((p + halfsize)/size);
p = mod(p+halfsize, size) - halfsize;
if (c > stop) { //yes, this might not be the best thing numerically.
p += size*(c - stop);
c = stop;
}
if (c <start) {
p += size*(c - start);
c = start;
}
return c;
}

// Repeat around the origin by a fixed angle.
// For easier use, num of repetitions is use to specify the angle.
float pModPolar(inout vec2 p, float repetitions) {
float angle = 2.*PI/repetitions;
float a = atan(p.y, p.x) + angle/2.;
float r = length(p);
float c = floor(a/angle);
a = mod(a,angle) - angle/2.;
p = vec2(cos(a), sin(a))*r;
// For an odd number of repetitions, fix cell index of the cell in -x direction
// (cell index would be e.g. -5 and 5 in the two halves of the cell):
if (abs(c) >= (repetitions/2.)) c = abs(c);
return c;
}

// Repeat in two dimensions
vec2 pMod2(inout vec2 p, vec2 size) {
vec2 c = floor((p + size*0.5)/size);
p = mod(p + size*0.5,size) - size*0.5;
return c;
}

// Same, but mirror every second cell so all boundaries match
vec2 pModMirror2(inout vec2 p, vec2 size) {
vec2 halfsize = size*0.5;
vec2 c = floor((p + halfsize)/size);
p = mod(p + halfsize, size) - halfsize;
p *= mod(c,vec2(2))*2. - vec2(1);
return c;
}

// Same, but mirror every second cell at the diagonal as well
vec2 pModGrid2(inout vec2 p, vec2 size) {
vec2 c = floor((p + size*0.5)/size);
p = mod(p + size*0.5, size) - size*0.5;
p *= mod(c,vec2(2))*2. - vec2(1);
p -= size/2.;
if (p.x > p.y) p.xy = p.yx;
return floor(c/2.);
}

// Repeat in three dimensions
vec3 pMod3(inout vec3 p, vec3 size) {
vec3 c = floor((p + size*0.5)/size);
p = mod(p + size*0.5, size) - size*0.5;
return c;
}

// Mirror at an axis-aligned plane which is at a specified distance <dist> from the origin.
float pMirror (inout float p, float dist) {
float s = sgn(p);
p = abs(p)-dist;
return s;
}

// Mirror in both dimensions and at the diagonal, yielding one eighth of the space.
// translate by dist before mirroring.
vec2 pMirrorOctant (inout vec2 p, vec2 dist) {
vec2 s = sgn(p);
pMirror(p.x, dist.x);
pMirror(p.y, dist.y);
if (p.y > p.x)
p.xy = p.yx;
return s;
}

// Reflect space at a plane
float pReflect(inout vec3 p, vec3 planeNormal, float offset) {
float t = dot(p, planeNormal)+offset;
if (t < 0.) {
p = p - (2.*t)*planeNormal;
}
return sgn(t);
}


////////////////////////////////////////////////////////////////
//
// OBJECT COMBINATION OPERATORS
//
////////////////////////////////////////////////////////////////
//
// We usually need the following boolean operators to combine two objects:
// Union: OR(a,b)
// Intersection: AND(a,b)
// Difference: AND(a,!b)
// (a and b being the distances to the objects).
//
// The trivial implementations are min(a,b) for union, max(a,b) for intersection
// and max(a,-b) for difference. To combine objects in more interesting ways to
// produce rounded edges, chamfers, stairs, etc. instead of plain sharp edges we
// can use combination operators. It is common to use some kind of "smooth minimum"
// instead of min(), but we don't like that because it does not preserve Lipschitz
// continuity in many cases.
//
// Naming convention: since they return a distance, they are called fOpSomething.
// The different flavours usually implement all the boolean operators above
// and are called fOpUnionRound, fOpIntersectionRound, etc.
//
// The basic idea: Assume the object surfaces intersect at a right angle. The two
// distances <a> and <b> constitute a new local two-dimensional coordinate system
// with the actual intersection as the origin. In this coordinate system, we can
// evaluate any 2D distance function we want in order to shape the edge.
//
// The operators below are just those that we found useful or interesting and should
// be seen as examples. There are infinitely more possible operators.
//
// They are designed to actually produce correct distances or distance bounds, unlike
// popular "smooth minimum" operators, on the condition that the gradients of the two
// SDFs are at right angles. When they are off by more than 30 degrees or so, the
// Lipschitz condition will no longer hold (i.e. you might get artifacts). The worst
// case is parallel surfaces that are close to each other.
//
// Most have a float argument <r> to specify the radius of the feature they represent.
// This should be much smaller than the object size.
//
// Some of them have checks like "if ((-a < r) && (-b < r))" that restrict
// their influence (and computation cost) to a certain area. You might
// want to lift that restriction or enforce it. We have left it as comments
// in some cases.
//
// usage example:
//
// float fTwoBoxes(vec3 p) {
// float box0 = fBox(p, vec3(1));
// float box1 = fBox(p-vec3(1), vec3(1));
// return fOpUnionChamfer(box0, box1, 0.2);
// }
//
////////////////////////////////////////////////////////////////


// The "Chamfer" flavour makes a 45-degree chamfered edge (the diagonal of a square of size <r>):
float fOpUnionChamfer(float a, float b, float r) {
return min(min(a, b), (a - r + b)*sqrt(0.5));
}

// Intersection has to deal with what is normally the inside of the resulting object
// when using union, which we normally don't care about too much. Thus, intersection
// implementations sometimes differ from union implementations.
float fOpIntersectionChamfer(float a, float b, float r) {
return max(max(a, b), (a + r + b)*sqrt(0.5));
}

// Difference can be built from Intersection or Union:
float fOpDifferenceChamfer (float a, float b, float r) {
return fOpIntersectionChamfer(a, -b, r);
}

// The "Round" variant uses a quarter-circle to join the two objects smoothly:
float fOpUnionRound(float a, float b, float r) {
vec2 u = max(vec2(r - a,r - b), vec2(0));
return max(r, min (a, b)) - length(u);
}

float fOpIntersectionRound(float a, float b, float r) {
vec2 u = max(vec2(r + a,r + b), vec2(0));
return min(-r, max (a, b)) + length(u);
}

float fOpDifferenceRound (float a, float b, float r) {
return fOpIntersectionRound(a, -b, r);
}


// The "Columns" flavour makes n-1 circular columns at a 45 degree angle:
float fOpUnionColumns(float a, float b, float r, float n) {
if ((a < r) && (b < r)) {
vec2 p = vec2(a, b);
float columnradius = r*sqrt(2.)/((n-1.)*2.+sqrt(2.));
pR45(p);
p.x -= sqrt(2.)/2.*r;
p.x += columnradius*sqrt(2.);
if (mod(n,2.) == 1.) {
p.y += columnradius;
}
// At this point, we have turned 45 degrees and moved at a point on the
// diagonal that we want to place the columns on.
// Now, repeat the domain along this direction and place a circle.
pMod1(p.y, columnradius*2.);
float result = length(p) - columnradius;
result = min(result, p.x);
result = min(result, a);
return min(result, b);
} else {
return min(a, b);
}
}

float fOpDifferenceColumns(float a, float b, float r, float n) {
a = -a;
float m = min(a, b);
//avoid the expensive computation where not needed (produces discontinuity though)
if ((a < r) && (b < r)) {
vec2 p = vec2(a, b);
float columnradius = r*sqrt(2.)/n/2.0;
columnradius = r*sqrt(2.)/((n-1.)*2.+sqrt(2.));

pR45(p);
p.y += columnradius;
p.x -= sqrt(2.)/2.*r;
p.x += -columnradius*sqrt(2.)/2.;

if (mod(n,2.) == 1.) {
p.y += columnradius;
}
pMod1(p.y,columnradius*2.);

float result = -length(p) + columnradius;
result = max(result, p.x);
result = min(result, a);
return -min(result, b);
} else {
return -m;
}
}

float fOpIntersectionColumns(float a, float b, float r, float n) {
return fOpDifferenceColumns(a,-b,r, n);
}

// The "Stairs" flavour produces n-1 steps of a staircase:
// much less stupid version by paniq
float fOpUnionStairs(float a, float b, float r, float n) {
float s = r/n;
float u = b-r;
return min(min(a,b), 0.5 * (u + a + abs ((mod (u - a + s, 2. * s)) - s)));
}

// We can just call Union since stairs are symmetric.
float fOpIntersectionStairs(float a, float b, float r, float n) {
return -fOpUnionStairs(-a, -b, r, n);
}

float fOpDifferenceStairs(float a, float b, float r, float n) {
return -fOpUnionStairs(-a, b, r, n);
}


// Similar to fOpUnionRound, but more lipschitz-y at acute angles
// (and less so at 90 degrees). Useful when fudging around too much
// by MediaMolecule, from Alex Evans' siggraph slides
float fOpUnionSoft(float a, float b, float r) {
float e = max(r - abs(a - b), 0.);
return min(a, b) - e*e*0.25/r;
}


// produces a cylindical pipe that runs along the intersection.
// No objects remain, only the pipe. This is not a boolean operator.
float fOpPipe(float a, float b, float r) {
return length(vec2(a, b)) - r;
}

// first object gets a v-shaped engraving where it intersect the second
float fOpEngrave(float a, float b, float r) {
return max(a, (a + r - abs(b))*sqrt(0.5));
}

// first object gets a capenter-style groove cut out
float fOpGroove(float a, float b, float ra, float rb) {
return max(a, min(a + ra, rb - abs(b)));
}

// first object gets a capenter-style tongue attached
float fOpTongue(float a, float b, float ra, float rb) {
return min(a, max(a - ra, abs(b) - rb));
}
`
Insert cell
Insert cell
Insert cell
phototoshop = () => `
/*
** Desaturation
*/
vec3 desaturate(vec3 color, float desaturation) {
vec3 grayXfer = vec3(0.3, 0.59, 0.11);
vec3 gray = vec3(dot(grayXfer, color));
return vec3(mix(color, gray, desaturation));
}

/*
** Hue, saturation, luminance
*/
vec3 RGBToHSL(vec3 color) {
vec3 hsl; // init to 0 to avoid warnings ? (and reverse if + remove first part)
float fmin = min(min(color.r, color.g), color.b); //Min. value of RGB
float fmax = max(max(color.r, color.g), color.b); //Max. value of RGB
float delta = fmax - fmin; //Delta RGB value

hsl.z = (fmax + fmin) / 2.0; // Luminance

if (delta == 0.0) { //This is a gray, no chroma...
hsl.x = 0.0; // Hue
hsl.y = 0.0; // Saturation
}
else { //Chromatic data...
if (hsl.z < 0.5) hsl.y = delta / (fmax + fmin); // Saturation
else hsl.y = delta / (2.0 - fmax - fmin); // Saturation
float deltaR = (((fmax - color.r) / 6.0) + (delta / 2.0)) / delta;
float deltaG = (((fmax - color.g) / 6.0) + (delta / 2.0)) / delta;
float deltaB = (((fmax - color.b) / 6.0) + (delta / 2.0)) / delta;

if (color.r == fmax ) hsl.x = deltaB - deltaG; // Hue
else if (color.g == fmax) hsl.x = (1.0 / 3.0) + deltaR - deltaB; // Hue
else if (color.b == fmax) hsl.x = (2.0 / 3.0) + deltaG - deltaR; // Hue

if (hsl.x < 0.0) hsl.x += 1.0; // Hue
else if (hsl.x > 1.0) hsl.x -= 1.0; // Hue
}

return hsl;
}

float HueToRGB(float f1, float f2, float hue) {
if (hue < 0.0) hue += 1.0;
else if (hue > 1.0) hue -= 1.0;
float res;
if ((6.0 * hue) < 1.0) res = f1 + (f2 - f1) * 6.0 * hue;
else if ((2.0 * hue) < 1.0) res = f2;
else if ((3.0 * hue) < 2.0) res = f1 + (f2 - f1) * ((2.0 / 3.0) - hue) * 6.0;
else res = f1;
return res;
}

vec3 HSLToRGB(vec3 hsl) {
vec3 rgb;
if (hsl.y == 0.0) rgb = vec3(hsl.z); // Luminance
else {
float f2;
if (hsl.z < 0.5) f2 = hsl.z * (1.0 + hsl.y);
else f2 = (hsl.z + hsl.y) - (hsl.y * hsl.z);
float f1 = 2.0 * hsl.z - f2;
rgb.r = HueToRGB(f1, f2, hsl.x + (1.0/3.0));
rgb.g = HueToRGB(f1, f2, hsl.x);
rgb.b= HueToRGB(f1, f2, hsl.x - (1.0/3.0));
}
return rgb;
}

/*
** Contrast, saturation, brightness
** Code of this function is from TGM's shader pack
** http://irrlicht.sourceforge.net/phpBB2/viewtopic.php?t=21057
*/
// For all settings: 1.0 = 100% 0.5=50% 1.5 = 150%
vec3 ContrastSaturationBrightness(vec3 color, float brt, float sat, float con) {
// Increase or decrease theese values to adjust r, g and b color channels seperately
const float AvgLumR = 0.5;
const float AvgLumG = 0.5;
const float AvgLumB = 0.5;
const vec3 LumCoeff = vec3(0.2125, 0.7154, 0.0721);
vec3 AvgLumin = vec3(AvgLumR, AvgLumG, AvgLumB);
vec3 brtColor = color * brt;
vec3 intensity = vec3(dot(brtColor, LumCoeff));
vec3 satColor = mix(intensity, brtColor, sat);
vec3 conColor = mix(AvgLumin, satColor, con);
return conColor;
}

/*
** Float blending modes
** Adapted from here: http://www.nathanm.com/photoshop-blending-math/
** But I modified the HardMix (wrong condition), Overlay, SoftLight, ColorDodge, ColorBurn, VividLight, PinLight (inverted layers) ones to have correct results
*/
#define BlendLinearDodgef BlendAddf
#define BlendLinearBurnf BlendSubstractf
#define BlendAddf(base, blend) min(base + blend, 1.0)
#define BlendSubstractf(base, blend) max(base + blend - 1.0, 0.0)
#define BlendLightenf(base, blend) max(blend, base)
#define BlendDarkenf(base, blend) min(blend, base)
#define BlendLinearLightf(base, blend) (blend < 0.5 ? BlendLinearBurnf(base, (2.0 * blend)) : BlendLinearDodgef(base, (2.0 * (blend - 0.5))))
#define BlendScreenf(base, blend) (1.0 - ((1.0 - base) * (1.0 - blend)))
#define BlendOverlayf(base, blend) (base < 0.5 ? (2.0 * base * blend) : (1.0 - 2.0 * (1.0 - base) * (1.0 - blend)))
#define BlendSoftLightf(base, blend) ((blend < 0.5) ? (2.0 * base * blend + base * base * (1.0 - 2.0 * blend)) : (sqrt(base) * (2.0 * blend - 1.0) + 2.0 * base * (1.0 - blend)))
#define BlendColorDodgef(base, blend) ((blend == 1.0) ? blend : min(base / (1.0 - blend), 1.0))
#define BlendColorBurnf(base, blend) ((blend == 0.0) ? blend : max((1.0 - ((1.0 - base) / blend)), 0.0))
#define BlendVividLightf(base, blend) ((blend < 0.5) ? BlendColorBurnf(base, (2.0 * blend)) : BlendColorDodgef(base, (2.0 * (blend - 0.5))))
#define BlendPinLightf(base, blend) ((blend < 0.5) ? BlendDarkenf(base, (2.0 * blend)) : BlendLightenf(base, (2.0 *(blend - 0.5))))
#define BlendHardMixf(base, blend) ((BlendVividLightf(base, blend) < 0.5) ? 0.0 : 1.0)
#define BlendReflectf(base, blend) ((blend == 1.0) ? blend : min(base * base / (1.0 - blend), 1.0))


/*
** Vector3 blending modes
*/
// Component wise blending
#define Blend(base, blend, funcf) vec3(funcf(base.r, blend.r), funcf(base.g, blend.g), funcf(base.b, blend.b))

#define BlendNormal(base, blend) (blend)
#define BlendLighten BlendLightenf
#define BlendDarken BlendDarkenf
#define BlendMultiply(base, blend) (base * blend)
#define BlendAverage(base, blend) ((base + blend) / 2.0)
#define BlendAdd(base, blend) min(base + blend, vec3(1.0))
#define BlendSubstract(base, blend) max(base + blend - vec3(1.0), vec3(0.0))
#define BlendDifference(base, blend) abs(base - blend)
#define BlendNegation(base, blend) (vec3(1.0) - abs(vec3(1.0) - base - blend))
#define BlendExclusion(base, blend) (base + blend - 2.0 * base * blend)
#define BlendScreen(base, blend) Blend(base, blend, BlendScreenf)
#define BlendOverlay(base, blend) Blend(base, blend, BlendOverlayf)
#define BlendSoftLight(base, blend) Blend(base, blend, BlendSoftLightf)
#define BlendHardLight(base, blend) BlendOverlay(blend, base)
#define BlendColorDodge(base, blend) Blend(base, blend, BlendColorDodgef)
#define BlendColorBurn(base, blend) Blend(base, blend, BlendColorBurnf)
#define BlendLinearDodge BlendAdd
#define BlendLinearBurn BlendSubstract
// Linear Light is another contrast-increasing mode
// If the blend color is darker than midgray, Linear Light darkens the image by decreasing the brightness. If the blend color is lighter than midgray, the result is a brighter image due to increased brightness.
#define BlendLinearLight(base, blend) Blend(base, blend, BlendLinearLightf)
#define BlendVividLight(base, blend) Blend(base, blend, BlendVividLightf)
#define BlendPinLight(base, blend) Blend(base, blend, BlendPinLightf)
#define BlendHardMix(base, blend) Blend(base, blend, BlendHardMixf)
#define BlendReflect(base, blend) Blend(base, blend, BlendReflectf)
#define BlendGlow(base, blend) BlendReflect(blend, base)
#define BlendPhoenix(base, blend) (min(base, blend) - max(base, blend) + vec3(1.0))
#define BlendOpacity(base, blend, F, O) (F(base, blend) * O + blend * (1.0 - O))


// Hue Blend mode creates the result color by combining the luminance and saturation of the base color with the hue of the blend color.
vec3 BlendHue(vec3 base, vec3 blend) {
vec3 baseHSL = RGBToHSL(base);
return HSLToRGB(vec3(RGBToHSL(blend).r, baseHSL.g, baseHSL.b));
}

// Saturation Blend mode creates the result color by combining the luminance and hue of the base color with the saturation of the blend color.
vec3 BlendSaturation(vec3 base, vec3 blend) {
vec3 baseHSL = RGBToHSL(base);
return HSLToRGB(vec3(baseHSL.r, RGBToHSL(blend).g, baseHSL.b));
}

// Color Mode keeps the brightness of the base color and applies both the hue and saturation of the blend color.
vec3 BlendColor(vec3 base, vec3 blend) {
vec3 blendHSL = RGBToHSL(blend);
return HSLToRGB(vec3(blendHSL.r, blendHSL.g, RGBToHSL(base).b));
}

// Luminosity Blend mode creates the result color by combining the hue and saturation of the base color with the luminance of the blend color.
vec3 BlendLuminosity(vec3 base, vec3 blend) {
vec3 baseHSL = RGBToHSL(base);
return HSLToRGB(vec3(baseHSL.r, baseHSL.g, RGBToHSL(blend).b));
}


/*
** Gamma correction
** Details: http://blog.mouaif.org/2009/01/22/photoshop-gamma-correction-shader/
*/
#define GammaCorrection(color, gamma) pow(color, 1.0 / gamma)

/*
** Levels control (input (+gamma), output)
** Details: http://blog.mouaif.org/2009/01/28/levels-control-shader/
*/
#define LevelsControlInputRange(color, minInput, maxInput) min(max(color - vec3(minInput), vec3(0.0)) / (vec3(maxInput) - vec3(minInput)), vec3(1.0))
#define LevelsControlInput(color, minInput, gamma, maxInput) GammaCorrection(LevelsControlInputRange(color, minInput, maxInput), gamma)
#define LevelsControlOutputRange(color, minOutput, maxOutput) mix(vec3(minOutput), vec3(maxOutput), color)
#define LevelsControl(color, minInput, gamma, maxInput, minOutput, maxOutput) LevelsControlOutputRange(LevelsControlInput(color, minInput, gamma, maxInput), minOutput, maxOutput)
`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more