Skip to content

Instantly share code, notes, and snippets.

@marcello3d
Created May 20, 2022 21:15
Show Gist options
  • Save marcello3d/8942a7cb246191f2d28f1ae2043c0b91 to your computer and use it in GitHub Desktop.
Save marcello3d/8942a7cb246191f2d28f1ae2043c0b91 to your computer and use it in GitHub Desktop.
2d vector library in webgl by paniq (public domain) - https://www.shadertoy.com/view/lslXW8
// 2d vector graphics library (https://www.shadertoy.com/view/lslXW8)
// after Cairo API, with anti-aliasing
// by Leonard Ritter (@paniq)
// v0.16
// I release this into the public domain.
// some estimators have been lifted from other shaders and are not
// necessarily PD licensed, note the links in the source code comments below.
// 2020-12-02: 0.16
// * support for drawing concentric rings
// 2020-11-30: 0.15
// * support for drawing orthogonal grids
// * adjusted uv so corners are centered on pixels
// * small adjustment to line pixel width computation
// 2020-11-12: 0.14
// * added support for depth testing
// 2020-11-11: 0.13
// * fixed 2D graphs not filling
// * added circle_px()
// 2019-06-06: 0.12
// * split implementation and demo into common and image tab
// 2017-10-05: 0.11
// * anti-aliasing is gamma-correct
// 2017-10-01: 0.10
// * added experimental letter() function
// 2017-09-30: 0.9
// * save() is now a declarative macro
// 2017-09-11: 0.8
// * added ellipse()
// 2017-09-10: 0.7
// * paths painted with line_to/curve_to can be filled.
// 2017-09-09: 0.6
// * added rounded_rectangle()
// * added set_source_linear_gradient()
// * added set_source_radial_gradient()
// * added set_source_blend_mode()
// * added support for non-uniform scaling
// undefine if you are running on glslsandbox.com
// #define GLSLSANDBOX
#ifdef GLSLSANDBOX
#ifdef GL_ES
#endif
uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;
#define iTime time
#define iResolution resolution
#define iMouse mouse
#endif
// interface
//////////////////////////////////////////////////////////
// set color source for stroke / fill / clear
void set_source_rgba(vec4 c);
void set_source_rgba(float r, float g, float b, float a);
void set_source_rgb(vec3 c);
void set_source_rgb(float r, float g, float b);
void set_source_linear_gradient(vec3 color0, vec3 color1, vec2 p0, vec2 p1);
void set_source_linear_gradient(vec4 color0, vec4 color1, vec2 p0, vec2 p1);
void set_source_radial_gradient(vec3 color0, vec3 color1, vec2 p, float r);
void set_source_radial_gradient(vec4 color0, vec4 color1, vec2 p, float r);
void set_source(sampler2D image);
void set_source_depth(float depth);
// control how source changes are applied
const int Replace = 0; // default: replace the old source with the new one
const int Alpha = 1; // alpha-blend the new source on top of the old one
const int Multiply = 2; // multiply the new source with the old one
void set_source_blend_mode(int mode);
// if enabled, blends using premultiplied alpha instead of
// regular alpha blending.
void premultiply_alpha(bool enable);
// if enabled, use the depth value for testing;
// smaller values win
void depth_test(bool enable);
// set line width in normalized units for stroke
void set_line_width(float w);
// set line width in pixels for stroke
void set_line_width_px(float w);
// set blur strength for strokes in normalized units
void set_blur(float b);
// add a circle path at P with radius R
void circle(vec2 p, float r);
void circle(float x, float y, float r);
// add a circle path at P with pixel radius R
void circle_px(vec2 p, float r);
void circle_px(float x, float y, float r);
// add an ellipse path at P with radii RW and RH
void ellipse(vec2 p, vec2 r);
void ellipse(float x, float y, float rw, float rh);
// add a rectangle at O with size S
void rectangle(vec2 o, vec2 s);
void rectangle(float ox, float oy, float sx, float sy);
// add a rectangle at O with size S and rounded corner of radius R
void rounded_rectangle(vec2 o, vec2 s, float r);
void rounded_rectangle(float ox, float oy, float sx, float sy, float r);
// add an orthogonal grid with cell size S
void grid(vec2 s);
void grid(float w, float h);
void grid(float s);
// draw concentric rings around origin p, with spacing r and offset phase
void rings(vec2 p, float r, float phase);
// set starting point for curves and lines to P
void move_to(vec2 p);
void move_to(float x, float y);
// draw straight line from starting point to P,
// and set new starting point to P
void line_to(vec2 p);
void line_to(float x, float y);
// draw quadratic bezier curve from starting point
// over B1 to B2 and set new starting point to B2
void curve_to(vec2 b1, vec2 b2);
void curve_to(float b1x, float b1y, float b2x, float b2y);
// connect current starting point with first
// drawing point.
void close_path();
// clear screen in the current source color
void clear();
// fill paths and clear the path buffer
void fill();
// fill paths and preserve them for additional ops
void fill_preserve();
// stroke paths and clear the path buffer
void stroke_preserve();
// stroke paths and preserve them for additional ops
void stroke();
// clears the path buffer
void new_path();
// draw a letter with the given texture coordinate
void letter(sampler2D font_texture_source, ivec2 l);
void letter(sampler2D font_texture_source, int lx, int ly);
// return rgb color for given hue (0..1)
vec3 hue(float hue);
// return rgb color for given hue, saturation and lightness
vec3 hsl(float h, float s, float l);
vec4 hsl(float h, float s, float l, float a);
// rotate the context by A in radians
void rotate(float a);
// uniformly scale the context by S
void scale(float s);
// non-uniformly scale the context by S
void scale(vec2 s);
void scale(float sx, float sy);
// translate the context by offset P
void translate(vec2 p);
void translate(float x, float y);
// clear all transformations for the active context
void identity_matrix();
// transform the active context by the given matrix
void transform(mat3 mtx);
// set the transformation matrix for the active context
void set_matrix(mat3 mtx);
// return the active query position for in_fill/in_stroke
// by default, this is the mouse position
vec2 get_query();
// set the query position for subsequent calls to
// in_fill/in_stroke; clears the query path
void set_query(vec2 p);
// true if the query position is inside the current path
bool in_fill();
// true if the query position is inside the current stroke
bool in_stroke();
// return the transformed coordinate of the current pixel
vec2 get_origin();
// draw a 1D graph from coordinate p, result f(p.x),
// and gradient1D(f,p.x)
void graph(vec2 p, float f_x, float df_x);
// draw a 2D graph from coordinate p, result f(p),
// and gradient2D(f,p)
void graph(vec2 p, float f_x, vec2 df_x);
// adds a custom distance field as path
// this field will not be testable by queries
void add_field(float c);
// returns a gradient for 1D graph function f at position x
#define gradient1D(f,x) (f(x + get_gradient_eps()) - f(x - get_gradient_eps())) / (2.0*get_gradient_eps())
// returns a gradient for 2D graph function f at position x
#define gradient2D(f,x) vec2(f(x + vec2(get_gradient_eps(),0.0)) - f(x - vec2(get_gradient_eps(),0.0)),f(x + vec2(0.0,get_gradient_eps())) - f(x - vec2(0.0,get_gradient_eps()))) / (2.0*get_gradient_eps())
// draws a 1D graph at the current position
#define graph1D(f) { vec2 pp = get_origin(); graph(pp, f(pp.x), gradient1D(f,pp.x)); }
// draws a 2D graph at the current position
#define graph2D(f) { vec2 pp = get_origin(); graph(pp, f(pp), gradient2D(f,pp)); }
// represents the current drawing context
// you usually don't need to change anything here
struct Context {
// screen position, query position
vec4 position;
vec2 shape;
vec2 clip;
vec2 scale;
float line_width;
bool premultiply;
bool depth_test;
vec2 blur;
vec4 source;
vec2 start_pt;
vec2 last_pt;
int source_blend;
bool has_clip;
float source_z;
};
// save current stroke width, starting
// point and blend mode from active context.
Context _save();
#define save(name) Context name = _save();
// restore stroke width, starting point
// and blend mode to a context previously returned by save()
void restore(Context ctx);
// draws a half-transparent debug gradient for the
// active path
void debug_gradient();
void debug_clip_gradient();
// returns the gradient epsilon width
float get_gradient_eps();
// implementation
//////////////////////////////////////////////////////////
vec2 aspect;
vec2 uv;
vec2 position;
vec2 query_position;
float ScreenH;
float AA;
float AAINV;
//////////////////////////////////////////////////////////
float det(vec2 a, vec2 b) { return a.x*b.y-b.x*a.y; }
//////////////////////////////////////////////////////////
vec3 hue(float hue) {
return clamp(
abs(mod(hue * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0,
0.0, 1.0);
}
vec3 hsl(float h, float s, float l) {
vec3 rgb = hue(h);
return l + s * (rgb - 0.5) * (1.0 - abs(2.0 * l - 1.0));
}
vec4 hsl(float h, float s, float l, float a) {
return vec4(hsl(h,s,l),a);
}
//////////////////////////////////////////////////////////
#define DEFAULT_SHAPE_V 1e+20
#define DEFAULT_CLIP_V -1e+20
#define DEFAULT_DEPTH 1e+30
Context _stack;
void init (vec2 fragCoord, vec2 mouse, vec2 resolution) {
aspect = vec2(resolution.x / resolution.y, 1.0);
ScreenH = min(resolution.x,resolution.y);
AA = ScreenH*0.5;
AAINV = 1.0 / AA;
uv = (fragCoord.xy - 0.5) / resolution;
vec2 m = mouse / resolution;
position = (uv*2.0-1.0)*aspect;
query_position = (m*2.0-1.0)*aspect;
_stack = Context(
vec4(position, query_position),
vec2(DEFAULT_SHAPE_V),
vec2(DEFAULT_CLIP_V),
vec2(1.0),
1.0,
false,
false,
vec2(0.0,1.0),
vec4(vec3(0.0),1.0),
vec2(0.0),
vec2(0.0),
Replace,
false,
DEFAULT_DEPTH
);
}
vec3 _color = vec3(1);
float _depth = DEFAULT_DEPTH;
vec2 get_origin() {
return _stack.position.xy;
}
vec2 get_query() {
return _stack.position.zw;
}
void set_query(vec2 p) {
_stack.position.zw = p;
_stack.shape.y = DEFAULT_SHAPE_V;
_stack.clip.y = DEFAULT_CLIP_V;
}
Context _save() {
return _stack;
}
void restore(Context ctx) {
// preserve shape
vec2 shape = _stack.shape;
vec2 clip = _stack.clip;
bool has_clip = _stack.has_clip;
// preserve source
vec4 source = _stack.source;
_stack = ctx;
_stack.shape = shape;
_stack.clip = clip;
_stack.source = source;
_stack.has_clip = has_clip;
}
mat3 mat2x3_invert(mat3 s)
{
float d = det(s[0].xy,s[1].xy);
d = (d != 0.0)?(1.0 / d):d;
return mat3(
s[1].y*d, -s[0].y*d, 0.0,
-s[1].x*d, s[0].x*d, 0.0,
det(s[1].xy,s[2].xy)*d,
det(s[2].xy,s[0].xy)*d,
1.0);
}
void identity_matrix() {
_stack.position = vec4(position, query_position);
_stack.scale = vec2(1.0);
}
void set_matrix(mat3 mtx) {
mtx = mat2x3_invert(mtx);
_stack.position.xy = (mtx * vec3(position,1.0)).xy;
_stack.position.zw = (mtx * vec3(query_position,1.0)).xy;
_stack.scale = vec2(length(mtx[0].xy), length(mtx[1].xy));
}
void transform(mat3 mtx) {
mtx = mat2x3_invert(mtx);
_stack.position.xy = (mtx * vec3(_stack.position.xy,1.0)).xy;
_stack.position.zw = (mtx * vec3(_stack.position.zw,1.0)).xy;
_stack.scale *= vec2(length(mtx[0].xy), length(mtx[1].xy));
}
void rotate(float a) {
float cs = cos(a), sn = sin(a);
transform(mat3(
cs, sn, 0.0,
-sn, cs, 0.0,
0.0, 0.0, 1.0));
}
void scale(vec2 s) {
transform(mat3(s.x,0.0,0.0,0.0,s.y,0.0,0.0,0.0,1.0));
}
void scale(float sx, float sy) {
scale(vec2(sx, sy));
}
void scale(float s) {
scale(vec2(s));
}
void translate(vec2 p) {
transform(mat3(1.0,0.0,0.0,0.0,1.0,0.0,p.x,p.y,1.0));
}
void translate(float x, float y) { translate(vec2(x,y)); }
void clear() {
_color.rgb = mix(_color.rgb, _stack.source.rgb, _stack.source.a);
_depth = (_stack.source.a == 1.0)?_stack.source_z:_depth;
}
void blit(out vec4 dest) {
dest = vec4(pow(_color.rgb, vec3(1.0/2.2)), 1.0);
}
void blit(out vec3 dest) {
dest = _color.rgb;
}
void add_clip(vec2 d) {
d = d / _stack.scale;
_stack.clip = max(_stack.clip, d);
_stack.has_clip = true;
}
void add_field(vec2 d) {
d = d / _stack.scale;
_stack.shape = min(_stack.shape, d);
}
void add_field(float c) {
_stack.shape.x = min(_stack.shape.x, c);
}
void new_path() {
_stack.shape = vec2(DEFAULT_SHAPE_V);
_stack.clip = vec2(DEFAULT_CLIP_V);
_stack.has_clip = false;
}
void debug_gradient() {
vec2 d = _stack.shape;
_color.rgb = mix(_color.rgb,
hsl(d.x * 6.0,
1.0, (d.x>=0.0)?0.5:0.3),
0.5);
}
void debug_clip_gradient() {
vec2 d = _stack.clip;
_color.rgb = mix(_color.rgb,
hsl(d.x * 6.0,
1.0, (d.x>=0.0)?0.5:0.3),
0.5);
}
void set_blur(float b) {
if (b == 0.0) {
_stack.blur = vec2(0.0, 1.0);
} else {
_stack.blur = vec2(
b,
0.0);
}
}
void write_color(vec4 rgba, float w) {
if (_stack.depth_test) {
if ((w == 1.0) && (_stack.source_z <= _depth)) {
_depth = _stack.source_z;
} else if ((w == 0.0) || (_stack.source_z > _depth)) {
return;
}
}
float src_a = w * rgba.a;
float dst_a = _stack.premultiply?w:src_a;
_color.rgb = _color.rgb * (1.0 - src_a) + rgba.rgb * dst_a;
}
void depth_test(bool enable) {
_stack.depth_test = enable;
}
void premultiply_alpha(bool enable) {
_stack.premultiply = enable;
}
float min_uniform_scale() {
return min(_stack.scale.x, _stack.scale.y);
}
float uniform_scale_for_aa() {
return min(1.0, _stack.scale.x / _stack.scale.y);
}
float calc_aa_blur(float w) {
vec2 blur = _stack.blur;
w -= blur.x;
float wa = clamp(-w*AA*uniform_scale_for_aa(), 0.0, 1.0);
float wb = clamp(-w / blur.x + blur.y, 0.0, 1.0);
return wa * wb;
}
void fill_preserve() {
write_color(_stack.source, calc_aa_blur(_stack.shape.x));
if (_stack.has_clip) {
write_color(_stack.source, calc_aa_blur(_stack.clip.x));
}
}
void fill() {
fill_preserve();
new_path();
}
void set_line_width(float w) {
_stack.line_width = w;
}
void set_line_width_px(float w) {
_stack.line_width = w*min_uniform_scale() * AAINV;
}
float get_gradient_eps() {
return (1.0 / min_uniform_scale()) * AAINV;
}
vec2 stroke_shape() {
return abs(_stack.shape) - _stack.line_width/_stack.scale;
}
void stroke_preserve() {
float w = stroke_shape().x;
write_color(_stack.source, calc_aa_blur(w));
}
void stroke() {
stroke_preserve();
new_path();
}
bool in_fill() {
return (_stack.shape.y <= 0.0);
}
bool in_stroke() {
float w = stroke_shape().y;
return (w <= 0.0);
}
void set_source_rgba(vec4 c) {
//c.rgb *= c.rgb;
c *= c;
if (_stack.source_blend == Multiply) {
_stack.source *= c;
} else if (_stack.source_blend == Alpha) {
float src_a = c.a;
float dst_a = _stack.premultiply?1.0:src_a;
_stack.source =
vec4(_stack.source.rgb * (1.0 - src_a) + c.rgb * dst_a,
max(_stack.source.a, c.a));
} else {
_stack.source = c;
}
}
void set_source_depth(float depth) {
_stack.source_z = depth;
}
void set_source_rgba(float r, float g, float b, float a) {
set_source_rgba(vec4(r,g,b,a)); }
void set_source_rgb(vec3 c) {
set_source_rgba(vec4(c,1.0));
}
void set_source_rgb(float r, float g, float b) { set_source_rgb(vec3(r,g,b)); }
void set_source(sampler2D image) {
set_source_rgba(texture(image, _stack.position.xy));
}
void set_source_linear_gradient(vec4 color0, vec4 color1, vec2 p0, vec2 p1) {
vec2 pa = _stack.position.xy - p0;
vec2 ba = p1 - p0;
float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
set_source_rgba(mix(color0, color1, h));
}
void set_source_linear_gradient(vec3 color0, vec3 color1, vec2 p0, vec2 p1) {
set_source_linear_gradient(vec4(color0, 1.0), vec4(color1, 1.0), p0, p1);
}
void set_source_radial_gradient(vec4 color0, vec4 color1, vec2 p, float r) {
float h = clamp( length(_stack.position.xy - p) / r, 0.0, 1.0 );
set_source_rgba(mix(color0, color1, h));
}
void set_source_radial_gradient(vec3 color0, vec3 color1, vec2 p, float r) {
set_source_radial_gradient(vec4(color0, 1.0), vec4(color1, 1.0), p, r);
}
void set_source_blend_mode(int mode) {
_stack.source_blend = mode;
}
vec2 length2(vec4 a) {
return vec2(length(a.xy),length(a.zw));
}
vec2 dot2(vec4 a, vec2 b) {
return vec2(dot(a.xy,b),dot(a.zw,b));
}
void letter(sampler2D font_texture_source, ivec2 l) {
vec2 p = vec2(l);
vec3 tx;
vec2 ip;
float d;
int ic;
ip = vec2(l);
p += clamp(_stack.position.xy, 0.0, 1.0);
ic = 0x21 + int (mod (16. + ip.x + 2. * ip.y, 94.));
tx = texture (font_texture_source, mod ((vec2 (mod (float (ic), 16.),
15. - floor (float (ic) / 16.)) + fract (p)) * (1. / 16.), 1.)).gba - 0.5;
d = tx.b + 1. / 256.;
add_field(d / min_uniform_scale());
}
void letter(sampler2D font_texture_source, int lx, int ly) {
letter(font_texture_source, ivec2(lx,ly));
}
void rounded_rectangle(vec2 o, vec2 s, float r) {
s = (s * 0.5);
r = min(r, min(s.x, s.y));
o += s;
s -= r;
vec4 d = abs(o.xyxy - _stack.position) - s.xyxy;
vec4 dmin = min(d,0.0);
vec4 dmax = max(d,0.0);
vec2 df = max(dmin.xz, dmin.yw) + length2(dmax);
add_field(df - r);
}
void rounded_rectangle(float ox, float oy, float sx, float sy, float r) {
rounded_rectangle(vec2(ox,oy), vec2(sx,sy), r);
}
void rectangle(vec2 o, vec2 s) {
rounded_rectangle(o, s, 0.0);
}
void rectangle(float ox, float oy, float sx, float sy) {
rounded_rectangle(vec2(ox,oy), vec2(sx,sy), 0.0);
}
void grid(vec2 size) {
vec4 f = abs(fract(_stack.position/size.xyxy+0.5)-0.5)*size.xyxy;
add_field(vec2(min(f.x,f.y),min(f.z,f.w)));
}
void grid(float w, float h) {
grid(vec2(w,h));
}
void grid(float s) {
grid(vec2(s));
}
void rings(vec2 p, float r, float phase) {
vec4 q = _stack.position - p.xyxy;
vec2 f = abs(fract(vec2(length(q.xy),length(q.zw))/r-phase+0.5)-0.5)*r;
add_field(f);
}
void circle(vec2 p, float r) {
vec4 c = _stack.position - p.xyxy;
add_field(vec2(length(c.xy),length(c.zw)) - r);
}
void circle(float x, float y, float r) { circle(vec2(x,y),r); }
void circle_px(vec2 p, float r) {
circle(p, r/(0.5*ScreenH));
}
void circle_px(float x, float y, float r) {
circle_px(vec2(x,y), r);
}
// from https://www.shadertoy.com/view/4sS3zz
float sdEllipse( vec2 p, in vec2 ab )
{
p = abs( p ); if( p.x > p.y ){ p=p.yx; ab=ab.yx; }
float l = ab.y*ab.y - ab.x*ab.x;
if (l == 0.0) {
return length(p) - ab.x;
}
float m = ab.x*p.x/l;
float n = ab.y*p.y/l;
float m2 = m*m;
float n2 = n*n;
float c = (m2 + n2 - 1.0)/3.0;
float c3 = c*c*c;
float q = c3 + m2*n2*2.0;
float d = c3 + m2*n2;
float g = m + m*n2;
float co;
if( d<0.0 )
{
float p = acos(q/c3)/3.0;
float s = cos(p);
float t = sin(p)*sqrt(3.0);
float rx = sqrt( -c*(s + t + 2.0) + m2 );
float ry = sqrt( -c*(s - t + 2.0) + m2 );
co = ( ry + sign(l)*rx + abs(g)/(rx*ry) - m)/2.0;
}
else
{
float h = 2.0*m*n*sqrt( d );
float s = sign(q+h)*pow( abs(q+h), 1.0/3.0 );
float u = sign(q-h)*pow( abs(q-h), 1.0/3.0 );
float rx = -s - u - c*4.0 + 2.0*m2;
float ry = (s - u)*sqrt(3.0);
float rm = sqrt( rx*rx + ry*ry );
float p = ry/sqrt(rm-rx);
co = (p + 2.0*g/rm - m)/2.0;
}
float si = sqrt( 1.0 - co*co );
vec2 r = vec2( ab.x*co, ab.y*si );
return length(r - p ) * sign(p.y-r.y);
}
void ellipse(vec2 p, vec2 r) {
vec4 c = _stack.position - p.xyxy;
add_field(vec2(sdEllipse(c.xy, r), sdEllipse(c.zw, r)));
}
void ellipse(float x, float y, float rw, float rh) {
ellipse(vec2(x,y), vec2(rw, rh));
}
void move_to(vec2 p) {
_stack.start_pt = p;
_stack.last_pt = p;
}
void move_to(float x, float y) { move_to(vec2(x,y)); }
// stroke only
void line_to(vec2 p) {
vec4 pa = _stack.position - _stack.last_pt.xyxy;
vec2 ba = p - _stack.last_pt;
vec2 h = clamp(dot2(pa, ba)/dot(ba,ba), 0.0, 1.0);
vec2 s = sign(pa.xz*ba.y-pa.yw*ba.x);
vec2 d = length2(pa - ba.xyxy*h.xxyy);
add_field(d);
add_clip(d * s);
_stack.last_pt = p;
}
void line_to(float x, float y) { line_to(vec2(x,y)); }
void close_path() {
line_to(_stack.start_pt);
}
// from https://www.shadertoy.com/view/ltXSDB
// Test if point p crosses line (a, b), returns sign of result
float test_cross(vec2 a, vec2 b, vec2 p) {
return sign((b.y-a.y) * (p.x-a.x) - (b.x-a.x) * (p.y-a.y));
}
// Determine which side we're on (using barycentric parameterization)
float bezier_sign(vec2 A, vec2 B, vec2 C, vec2 p) {
vec2 a = C - A, b = B - A, c = p - A;
vec2 bary = vec2(c.x*b.y-b.x*c.y,a.x*c.y-c.x*a.y) / (a.x*b.y-b.x*a.y);
vec2 d = vec2(bary.y * 0.5, 0.0) + 1.0 - bary.x - bary.y;
return mix(sign(d.x * d.x - d.y), mix(-1.0, 1.0,
step(test_cross(A, B, p) * test_cross(B, C, p), 0.0)),
step((d.x - d.y), 0.0)) * test_cross(A, C, B);
}
// Solve cubic equation for roots
vec3 bezier_solve(float a, float b, float c) {
float p = b - a*a / 3.0, p3 = p*p*p;
float q = a * (2.0*a*a - 9.0*b) / 27.0 + c;
float d = q*q + 4.0*p3 / 27.0;
float offset = -a / 3.0;
if(d >= 0.0) {
float z = sqrt(d);
vec2 x = (vec2(z, -z) - q) / 2.0;
vec2 uv = sign(x)*pow(abs(x), vec2(1.0/3.0));
return vec3(offset + uv.x + uv.y);
}
float v = acos(-sqrt(-27.0 / p3) * q / 2.0) / 3.0;
float m = cos(v), n = sin(v)*1.732050808;
return vec3(m + m, -n - m, n - m) * sqrt(-p / 3.0) + offset;
}
// Find the signed distance from a point to a quadratic bezier curve
float bezier(vec2 A, vec2 B, vec2 C, vec2 p)
{
B = mix(B + vec2(1e-4), B, abs(sign(B * 2.0 - A - C)));
vec2 a = B - A, b = A - B * 2.0 + C, c = a * 2.0, d = A - p;
vec3 k = vec3(3.*dot(a,b),2.*dot(a,a)+dot(d,b),dot(d,a)) / dot(b,b);
vec3 t = clamp(bezier_solve(k.x, k.y, k.z), 0.0, 1.0);
vec2 pos = A + (c + b*t.x)*t.x;
float dis = length(pos - p);
pos = A + (c + b*t.y)*t.y;
dis = min(dis, length(pos - p));
pos = A + (c + b*t.z)*t.z;
dis = min(dis, length(pos - p));
return dis * bezier_sign(A, B, C, p);
}
void curve_to(vec2 b1, vec2 b2) {
vec2 shape = vec2(
bezier(_stack.last_pt, b1, b2, _stack.position.xy),
bezier(_stack.last_pt, b1, b2, _stack.position.zw));
add_field(abs(shape));
add_clip(shape);
_stack.last_pt = b2;
}
void curve_to(float b1x, float b1y, float b2x, float b2y) {
curve_to(vec2(b1x,b1y),vec2(b2x,b2y));
}
void graph(vec2 p, float f_x, float df_x) {
add_field(abs(f_x - p.y) / sqrt(1.0 + (df_x * df_x)));
}
void graph(vec2 p, float f_x, vec2 df_x) {
add_field(f_x / length(df_x));
}
// see Common tab for implementation
//////////////////////////////////////////////////////////
// DEMO
float myf(float x) {
return sin(x) * cos((x + iTime * 0.2) * 20.0);
}
float myf2(vec2 x) {
float r = length(x);
float a = atan(x.y,x.x);
return r - 1.0 + 0.5 * sin(3.0*a + 2.0*r*r);
}
void shield_shape() {
move_to(0.2, 0.2);
line_to(0.0, 0.3);
line_to(-0.2, 0.2);
curve_to(-0.2, -0.05, 0.0, -0.2);
curve_to(0.2, -0.05, 0.2, 0.2);
}
void paint() {
float t = iTime;
// clear screen with a subtle gradient
set_source_linear_gradient(
vec3(0.0,0.0,0.3),
vec3(0.0,0.0,0.6),
vec2(0.0,-1.0),
vec2(0.0,1.0));
clear();
grid(vec2(1.0/10.0));
set_line_width_px(1.0);
set_source_rgba(vec4(vec3(1.0),0.3));
stroke();
// draw 1D graph
graph1D(myf);
// graphs only look good at pixel size
set_line_width_px(1.0);
set_source_rgba(vec4(vec3(1.0),0.3));
stroke();
// draw 2D graph
graph2D(myf2);
// graphs only look good at pixel size
set_line_width_px(1.0);
set_source_rgba(vec4(vec3(0.0),0.8));
fill_preserve();
set_source_rgba(vec4(vec3(1.0),0.3));
stroke();
// fill ellipse
ellipse(0.0, 0.0, 0.3, 0.5);
bool in_circle = in_fill();
set_source_radial_gradient(
hsl(0.1, 1.0, in_circle?0.7:0.5),
hsl(0.0, 1.0, 0.5),
vec2(0.0), 0.3);
fill_preserve(); // don't reset shape
// add a circle
circle(0.3 + 0.3*(sin(t)*0.5+0.5), 0.0, 0.2);
set_line_width(0.04);
bool in_circle_rim = in_circle || in_stroke();
// stroke circle and ellipse, twice
set_source_rgb(hsl(0.0, 1.0, 0.3));
set_line_width(0.04);
stroke_preserve();
set_source_rgb(hsl(0.1, 1.0, in_circle_rim?0.8:0.5));
set_line_width(0.02);
stroke();
// shadowed stop sign stroke
move_to(-0.2,0.0);
line_to(0.2,0.0);
set_source_rgba(0.5,0.0,0.0,0.5);
set_line_width(0.02);
set_blur(0.05);
stroke_preserve();
set_blur(0.0);
set_source_rgb(vec3(1.0));
set_line_width(0.02);
stroke();
// transformed glowing triangle
// to preserve stroke width, first save context...
{ save(ctx);
translate(-1.0, 0.4);
scale(0.01 + 0.5 * (sin(t)*0.5+0.5));
rotate(radians(t*30.0));
move_to(0.5, 0.0);
for (int i = 1; i < 6; ++i) {
float a0 = radians((float(i)-0.5) * 360.0 / 5.0);
float a1 = radians(float(i) * 360.0 / 5.0);
curve_to(
cos(a0)*0.5, sin(a0)*0.5,
cos(a1)*0.5, sin(a1)*0.5);
}
//close_path();
// ...then restore to previous transformation
restore(ctx); }
set_line_width_px(0.1);
bool tri_active = in_stroke();
set_source_rgba(hsl(tri_active?0.1:0.5, 1.0, 0.5, 0.5));
fill_preserve();
// add glow
set_source_rgba(vec4(hsl(tri_active?0.1:0.52, 1.0, 0.5)*0.2,0.0));
set_line_width(0.02);
set_blur(0.1);
premultiply_alpha(true);
stroke_preserve();
premultiply_alpha(false);
set_blur(0.0);
// and stroke
set_line_width_px(1.2);
set_source_rgb(hsl(tri_active?0.1:0.5, 1.0, 0.5));
stroke();
// pink alphablended rectangle
{ save(ctx);
translate(0.9, 0.1);
rotate(-radians(t*30.0));
rounded_rectangle(-0.3,-0.4,0.6,0.8, mix(0.0,0.3,sin(iTime*1.114)*0.5+0.5));
if (in_fill()) {
// animate the texture a little
save(ctx);
translate(mod(iTime*0.2,1.0), 0.0);
set_source(iChannel0);
restore(ctx);
set_source_blend_mode(Multiply);
set_source_linear_gradient(
hsl(0.7, 1.0, 0.5, 1.0),
hsl(0.9, 1.0, 0.5, 1.0),
vec2(-0.3,-0.4),
vec2(0.3, 0.4));
set_source_blend_mode(Replace);
} else
set_source_linear_gradient(
hsl(0.7, 1.0, 0.5, 0.2),
hsl(0.9, 1.0, 0.5, 0.9),
vec2(-0.3,-0.4),
vec2(0.3, 0.4));
fill_preserve();
set_line_width(0.02);
set_source_linear_gradient(
hsl(0.9, 1.0, 0.5, 1.0),
hsl(0.7, 1.0, 0.5, 1.0),
vec2(0.3,0.4),
vec2(-0.3, -0.4));
stroke();
restore(ctx); }
// quadratic bezier spline
save(ctx);
translate(-0.8, -0.7);
shield_shape();
set_source_linear_gradient(
hsl(0.9, 1.0, 0.6),
hsl(0.9, 1.0, 0.4),
vec2(0.0,-0.2),
vec2(0.0, 0.2));
fill();
restore(ctx);
translate(0.8, -0.7);
shield_shape();
set_line_width(0.04);
bool bezier_active = in_stroke();
set_source_rgb(hsl(0.9, 1.0, bezier_active?1.0:0.5));
stroke_preserve();
set_line_width(0.02);
set_source_rgb(hsl(0.9, 1.0, 0.1));
stroke();
restore(ctx);
{
save(ctx);
translate(-1.2, 0.7);
scale(vec2(0.2));
for (int i = 0; i < 26; ++i) {
translate(0.4, 0.0);
letter(iChannel1, 48 + i, 0);
}
set_line_width(0.05);
set_source_rgb(vec3(0.0));
stroke_preserve();
set_source_rgb(vec3(1.0));
fill();
restore(ctx);
}
}
//////////////////////////////////////////////////////////
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
init(fragCoord, iMouse.xy, iResolution.xy);
paint();
blit(fragColor);
}
#ifdef GLSLSANDBOX
void main() {
mainImage(gl_FragColor, gl_FragCoord.xy);
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment