UNPKG

css-doodle

Version:

A web component for visual art & creative coding

129 lines (118 loc) 3.87 kB
import parse_pattern from '../parser/parse-pattern.js'; import parse_grid from '../parser/parse-grid.js'; import transform from './glsl-math-transformer.js'; const CIRCLE_MASK = ` vec2 cellUV = fract(uv * v) - 0.5; float dist = length(cellUV); shapeMask = 1.0 - smoothstep(0.5 - fwidth(dist), 0.5, dist); `; const float = n => String(n).includes('.') ? n : n + '.0'; const STATEMENT_HANDLERS = { fill(token, extra) { let { r, g, b, a } = extra.get_rgba_color(token.value); return { type: 'statement', value: `\ncolor = vec4(${float(r / 255)}, ${float(g / 255)}, ${float(b / 255)}, ${float(a)});\n`, }; }, grid(token) { return { type: 'grid', value: token.value }; }, shape(token, _, insideBlock) { let shape = token.value.trim(); if (insideBlock && shape) { return { type: 'statement', value: shape === 'circle' ? CIRCLE_MASK : '\nshapeMask = 1.0;\n', }; } return { type: 'shape', value: shape }; }, }; function generate_statement(token, extra, insideBlock = false, vars = {}) { let handler = STATEMENT_HANDLERS[token.name]; if (handler) { return handler(token, extra, insideBlock); } let varName = token.name.startsWith('--') ? token.name.slice(2).trim() : token.name.trim(); if (varName) { return { type: 'variable', name: varName, value: token.value.trim() }; } return { type: 'statement', value: '' }; } function substitute_variables(expr, vars, depth = 0, excludeName = null) { if (depth > 10) return expr; let names = Object.keys(vars).sort((a, b) => b.length - a.length); for (let name of names) { if (name === excludeName) continue; let regex = new RegExp(`\\b${name}\\b`, 'g'); if (regex.test(expr)) { let resolved = substitute_variables(vars[name], vars, depth + 1, name); expr = expr.replace(regex, `(${resolved})`); } } return expr; } function generate_block(token, extra, vars = {}) { if (token.name !== 'match') { return ''; } let expr = substitute_variables(token.args[0], vars); let cond = transform(expr, { expect: 'bool' }); let body = token.value .map(t => generate_statement(t, extra, true, vars)) .filter(s => s.type === 'statement') .map(s => s.value) .join(''); return ` if (${cond}) { ${body} } `; } function generate_shader(input, { x, y }, shape) { let shapeInit = shape === 'circle' ? CIRCLE_MASK : ''; return ` vec3 mapping(vec2 uv, vec2 grid) { float x = floor(uv.x * grid.x) + 1.0; float y = floor((1.0 - uv.y) * grid.y) + 1.0; float i = x + (y - 1.0) * grid.x; return vec3(x, y, i); } vec4 getColor(float x, float y, float i, float I, float X, float Y, float t, vec2 uv, vec2 v) { vec4 color = vec4(0, 0, 0, 0); float shapeMask = 1.0; ${shapeInit} ${input} color.a *= shapeMask; return color; } void main() { vec2 uv = gl_FragCoord.xy/u_resolution.xy; vec2 v = vec2(${x}, ${y}); vec3 p = mapping(uv, v); FragColor = getColor(p.x, p.y, p.z, v.x * v.y, v.x, v.y, u_time, uv, v); } `; } export default function draw_pattern(code, extra) { let tokens = parse_pattern(code); let result = []; let grid = { x: 1, y: 1 }; let shape = null; let vars = {}; for (let token of tokens) { if (token.type === 'statement') { let stmt = generate_statement(token, extra); switch (stmt.type) { case 'statement': result.push(stmt.value); break; case 'grid': grid = parse_grid(stmt.value, Infinity); break; case 'shape': shape = stmt.value; break; case 'variable': vars[stmt.name] = stmt.value; break; } } else if (token.type === 'block') { result.push(generate_block(token, extra, vars)); } } return generate_shader(result.join(''), grid, shape); }