css-doodle/src/function.js

483 lines
12 KiB
JavaScript

import { create_svg_url, normalize_svg, generate_svg } from './svg';
import list from './utils/list';
import random_func from './utils/random';
import { cell_id, is_letter, alias_for } from './utils/index';
import { lazy, clamp, sequence, get_value } from './utils/index';
import by_unit from './utils/by-unit';
import by_charcode from './utils/by-charcode';
import calc from './calc';
import expand from './utils/expand';
import Stack from './utils/stack';
import memo from './utils/memo';
import { shapes, create_shape_points } from './shapes';
import parse_value_group from './parser/parse-value-group';
import parse_shape_commands from './parser/parse-shape-commands';
import parse_svg from './parser/parse-svg';
import parse_svg_path from './parser/parse-svg-path';
import { uniform_time } from './uniform';
function get_exposed(random) {
const { shuffle } = list(random);
const { pick, rand, nrand, unique_id } = random_func(random);
const Expose = {
index({ count }) {
return _ => count;
},
row({ y }) {
return _ => y;
},
col({ x }) {
return _ => x;
},
depth({ z }) {
return _ => z;
},
size({ grid }) {
return _ => grid.count;
},
['size-row']({ grid }) {
return _ => grid.y;
},
['size-col']({ grid }) {
return _ => grid.x;
},
['size-depth']({ grid }) {
return _ => grid.z;
},
id({ x, y, z }) {
return _ => cell_id(x, y, z);
},
n({ extra }) {
return n => extra ? (extra[0] + (Number(n) || 0)) : '@n';
},
nx({ extra }) {
return n => extra ? (extra[1] + (Number(n) || 0)) : '@nx';
},
ny({ extra }) {
return n => extra ? (extra[2] + (Number(n) || 0)) : '@ny';
},
N({ extra }) {
return n => extra ? (extra[3] + (Number(n) || 0)) : '@N';
},
repeat: (
make_sequence('')
),
multiple: (
make_sequence(',')
),
['multiple-with-space']: (
make_sequence(' ')
),
pick({ context }) {
return expand((...args) => {
return push_stack(context, 'last_pick', pick(args));
});
},
['pick-n']({ context, extra, position }) {
let counter = 'pn-counter' + position;
return expand((...args) => {
if (!context[counter]) context[counter] = 0;
context[counter] += 1;
let max = args.length;
let [idx = context[counter]] = extra || [];
let pos = (idx - 1) % max;
let value = args[pos];
return push_stack(context, 'last_pick', value);
});
},
['pick-d']({ context, extra, position }) {
let counter = 'pd-counter' + position;
let values = 'pd-values' + position;
return expand((...args) => {
if (!context[counter]) context[counter] = 0;
context[counter] += 1;
if (!context[values]) {
context[values] = shuffle(args);
}
let max = args.length;
let [idx = context[counter]] = extra || [];
let pos = (idx - 1) % max;
let value = context[values][pos];
return push_stack(context, 'last_pick', value);
});
},
['last-pick']({ context }) {
return (n = 1) => {
let stack = context.last_pick;
return stack ? stack.last(n) : '';
};
},
rand({ context }) {
return (...args) => {
let transform_type = args.every(is_letter)
? by_charcode
: by_unit;
let value = transform_type(rand).apply(null, args);
return push_stack(context, 'last_rand', value);
};
},
nrand({ context }) {
return (...args) => {
let transform_type = args.every(is_letter)
? by_charcode
: by_unit;
let value = transform_type(nrand).apply(null, args);
return push_stack(context, 'last_rand', value);
};
},
['rand-int']({ context }) {
return (...args) => {
let transform_type = args.every(is_letter)
? by_charcode
: by_unit;
let rand_int = (...args) => Math.round(rand(...args))
let value = transform_type(rand_int).apply(null, args)
return push_stack(context, 'last_rand', value);
}
},
['nrand-int']({ context }) {
return (...args) => {
let transform_type = args.every(is_letter)
? by_charcode
: by_unit;
let nrand_int = (...args) => Math.round(nrand(...args))
let value = transform_type(nrand_int).apply(null, args)
return push_stack(context, 'last_rand', value);
}
},
['last-rand']({ context }) {
return (n = 1) => {
let stack = context.last_rand;
return stack ? stack.last(n) : '';
};
},
stripe() {
return (...input) => {
let colors = input.map(get_value);
let max = colors.length;
let default_count = 0;
let custom_sizes = [];
let prev;
if (!max) {
return '';
}
colors.forEach(step => {
let [_, size] = parse_value_group(step);
if (size !== undefined) custom_sizes.push(size);
else default_count += 1;
});
let default_size = custom_sizes.length
? `(100% - ${custom_sizes.join(' - ')}) / ${default_count}`
: `100% / ${max}`
return colors.map((step, i) => {
if (custom_sizes.length) {
let [color, size] = parse_value_group(step);
let prefix = prev ? (prev + ' + ') : '';
prev = prefix + (size !== undefined ? size : default_size);
return `${color} 0 calc(${ prev })`
}
return `${step} 0 ${100 / max * (i + 1)}%`
})
.join(',');
}
},
reflect() {
return (...input) => {
return [
...input,
...input.slice(0, -1).reverse()
].join(',');
}
},
calc() {
return value => calc(get_value(value));
},
hex() {
return value => parseInt(get_value(value)).toString(16);
},
svg: lazy((...args) => {
let value = args.map(input => get_value(input()).trim()).join(',');
if (!value.startsWith('<')) {
let parsed = parse_svg(value);
value = generate_svg(parsed);
}
let svg = normalize_svg(value);
return create_svg_url(svg);
}),
['svg-filter']: lazy((...args) => {
let value = args.map(input => get_value(input()).trim()).join(',');
let id = unique_id('filter-');
if (!value.startsWith('<')) {
let parsed = parse_svg(value, {
type: 'block',
name: 'filter'
});
value = generate_svg(parsed);
}
let svg = normalize_svg(value).replace(
/<filter([\s>])/,
`<filter id="${ id }"$1`
);
return create_svg_url(svg, id);
}),
var() {
return value => `var(${ get_value(value) })`;
},
t() {
return value => `var(--${ uniform_time.name })`;
},
plot({ count, context, extra, position, grid }) {
let key = 'offset-points' + position;
return commands => {
let [idx = count, _, __, max = grid.count] = extra || [];
if (!context[key]) {
let config = parse_shape_commands(commands);
config.points = max;
context[key] = create_shape_points(config, {min: 1, max: 65536});
}
return context[key][idx - 1];
};
},
Plot({ count, context, extra, position, grid }) {
let key = 'offset-points' + position;
return commands => {
let [idx = count, _, __, max = grid.count] = extra || [];
if (!context[key]) {
let config = parse_shape_commands(commands);
config.points = max;
config.absolute = true;
context[key] = create_shape_points(config, {min: 1, max: 65536});
}
return context[key][idx - 1];
};
},
shape() {
return memo('shape-function', (type = '', ...args) => {
type = String(type).trim();
let points = [];
if (type.length) {
if (typeof shapes[type] === 'function') {
points = shapes[type](args);
} else {
let commands = type;
let rest = args.join(',');
if (rest.length) {
commands = type + ',' + rest;
}
let config = parse_shape_commands(commands);
points = create_shape_points(config, {min: 3, max: 3600});
}
}
return `polygon(${points.join(',')})`;
});
},
doodle() {
return value => value;
},
shaders() {
return value => value;
},
canvas() {
return value => value;
},
path() {
return value => value;
},
invert() {
return commands => {
let parsed = parse_svg_path(commands);
if (!parsed.valid) return commands;
return parsed.commands.map(({ name, value }) => {
switch (name) {
case 'v': return 'h' + value.join(' ');
case 'V': return 'H' + value.join(' ');
case 'h': return 'v' + value.join(' ');
case 'H': return 'V' + value.join(' ');
default: return name + value.join(' ');
}
}).join(' ');
};
},
flipH() {
return commands => {
let parsed = parse_svg_path(commands);
if (!parsed.valid) return commands;
return parsed.commands.map(({ name, value }) => {
switch (name) {
case 'h':
case 'H': return name + value.map(flip_value).join(' ');
default: return name + value.join(' ');
}
}).join(' ');
};
},
flipV() {
return commands => {
let parsed = parse_svg_path(commands);
if (!parsed.valid) return commands;
return parsed.commands.map(({ name, value }) => {
switch (name) {
case 'v':
case 'V': return name + value.map(flip_value).join(' ');
default: return name + value.join(' ');
}
}).join(' ');
};
},
flip(...args) {
let flipH = Expose.flipH(...args);
let flipV = Expose.flipV(...args);
return commands => {
return flipV(flipH(commands));
}
},
reverse(...args) {
return commands => {
let parsed = parse_svg_path(commands);
if (!parsed.valid) return commands;
return parsed.commands.reverse().map(({ name, value }) => {
return name + value.join(' ');
}).join(' ');
}
},
};
function make_sequence(c) {
return lazy((n, ...actions) => {
if (!actions || !n) return '';
let count = get_value(n());
let evaluated = calc(count);
if (evaluated === 0) {
evaluated = count;
}
return sequence(
evaluated,
(...args) => {
return actions.map(action => {
return get_value(action(...args))
}).join(',');
}
).join(c);
});
}
function push_stack(context, name, value) {
if (!context[name]) context[name] = new Stack();
context[name].push(value);
return value;
}
function flip_value(num) {
return -1 * num;
}
return alias_for(Expose, {
'm': 'multiple',
'M': 'multiple-with-space',
'r': 'rand',
'rn': 'nrand',
'ri': 'rand-int',
'rni': 'nrand-int',
'lr': 'last-rand',
'p': 'pick',
'pn': 'pick-n',
'pd': 'pick-d',
'lp': 'last-pick',
'rep': 'repeat',
'i': 'index',
'x': 'col',
'y': 'row',
'z': 'depth',
'I': 'size',
'X': 'size-col',
'Y': 'size-row',
'Z': 'size-depth',
'flipv': 'flipV',
'fliph': 'flipH',
// legacy names, keep them before 1.0
'nr': 'rn',
'nri': 'nri',
'ms': 'multiple-with-space',
's': 'size',
'sx': 'size-col',
'sy': 'size-row',
'sz': 'size-depth',
'size-x': 'size-col',
'size-y': 'size-row',
'size-z': 'size-depth',
'multi': 'multiple',
'pick-by-turn': 'pick-n',
'max-row': 'size-row',
'max-col': 'size-col',
'offset': 'plot',
'Offset': 'Plot',
'point': 'plot',
'Point': 'Plot',
// error prone
'stripes': 'stripe',
'strip': 'stripe',
});
}
export default get_exposed;