Created
May 20, 2022 21:15
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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)); | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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