diff options
| author | user <user@clank> | 2026-08-16 15:58:11 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-16 15:58:11 +0200 |
| commit | 72ffc4cc5cc5f9b95d21a115e761de6a632d68a5 (patch) | |
| tree | 860336fd32f5aa62676eff0fcdbd5e72f214908a /src/expr.c | |
| parent | sl0ppty: bits (diff) | |
| download | slosh-72ffc4cc5cc5f9b95d21a115e761de6a632d68a5.tar.gz slosh-72ffc4cc5cc5f9b95d21a115e761de6a632d68a5.tar.xz slosh-72ffc4cc5cc5f9b95d21a115e761de6a632d68a5.zip | |
sl0ppty: sin and cos
128 + sin(t / 4) / 2 a breathe that is actually a sine
abs(sin(x * 6 + t / 5)) a wave travelling along a frame
abs(sin(dist(x, y, curx, cury) * 20 - t / 4)) / 2 ripples from the cursor
Degrees in, -255..255 out. Two decisions, both for the same reason -- this is a
language people type into a config file, not one they read a manual for.
Degrees because that is what anybody guesses, and 255 because it is the scale
every strength here already uses: `128 + sin(t / 4) / 2` is a breathe centred
half way up with nothing to rescale, and `abs(sin(...))` is an amount as it
stands.
Every angle wraps, negative ones included. `t / 4` does not stop at 360 and an
expression that died at one turn would be no use for the one thing this is for.
A 91-entry table of round(255*sin) at one-degree steps, three quadrants by
symmetry, no interpolation -- the argument is an integer and there is nothing
between two degrees to interpolate. A table and not a polynomial because
`contrib/shadertoy.html` reimplements this language in JavaScript so the preview
can run in a browser, and `tests/test_shadertoy.py` compares the two on every
expression: identical data with identical reduction cannot drift the way two
approximations of one curve would. The JS carries the same 91 numbers.
Until now the only oscillator was `abs(t / 6 % 510 - 255)`, a triangle wave
written as arithmetic. It works and it looks like what it is -- a ramp with a
corner in it. A sine has no corner, which is the whole difference between a pane
that pulses and a pane that flickers.
Verified: 19 cases in `tests/expr_test.c` (the cardinal angles, quadrant
symmetry, wrapping past a turn and below zero, and the breathe idiom); the
cross-check now agrees with the compiler on 660 evaluations including generated
trig; the repl's completion drift test caught `sin`/`cos` missing from its list
before I did, which is what it is for. Then six chains in a real pane -- breathe,
travelling wave, clipped crests, plasma, rolling scanlines, cursor ripples --
each measured over eight distinct frames.
Diffstat (limited to 'src/expr.c')
| -rw-r--r-- | src/expr.c | 41 |
1 files changed, 40 insertions, 1 deletions
@@ -19,7 +19,7 @@ enum { * contrib/shadertoy.html reimplements this language in JS and a test compares * the two, so `x << 33` has to mean the same thing in both. */ OP_BAND, OP_BXOR, OP_BOR, OP_BNOT, OP_SHL, OP_SHR, - OP_MIN, OP_MAX, OP_ABS, OP_CLAMP, OP_DIST, + OP_MIN, OP_MAX, OP_ABS, OP_CLAMP, OP_DIST, OP_SIN, OP_COS, OP_SELECT, /* cond a b -> a or b, both already evaluated */ }; @@ -190,6 +190,7 @@ static void parse_primary(parser_t *ps, expr_prog_t *pr) { } FNS[] = { {"min", 2, OP_MIN}, {"max", 2, OP_MAX}, {"abs", 1, OP_ABS}, {"clamp", 3, OP_CLAMP}, {"dist", 4, OP_DIST}, + {"sin", 1, OP_SIN}, {"cos", 1, OP_COS}, }; for (size_t i = 0; i < sizeof FNS / sizeof *FNS; i++) { if (strcmp(FNS[i].name, name) != 0) continue; @@ -376,6 +377,40 @@ static void parse_expr(parser_t *ps, expr_prog_t *pr) { emit(ps, pr, OP_SELECT, 0); } + +/* ---- trigonometry, in the only arithmetic this language has --------------- + * + * `sin(d)` and `cos(d)` take **degrees** and answer -255..255. Degrees because + * they are what a person writing a config guesses, and 255 because that is the + * strength scale everything else works in: `128 + sin(t / 4) / 2` is a breathe + * centred half way up, with nothing to rescale. + * + * A quarter turn of round(255*sin) at one-degree steps, with the other three + * quadrants by symmetry. No interpolation: the argument is an integer and there + * is nothing between two degrees to interpolate. A table rather than a + * polynomial for one reason -- contrib/shadertoy.html has to answer identically, + * and identical data with identical reduction cannot drift the way two + * approximations of the same curve do. + */ +static const int16_t SIN_Q[91] = { + 0, 4, 9, 13, 18, 22, 27, 31, 35, 40, 44, 49, 53, + 57, 62, 66, 70, 75, 79, 83, 87, 91, 96, 100, 104, 108, + 112, 116, 120, 124, 127, 131, 135, 139, 143, 146, 150, 153, 157, + 160, 164, 167, 171, 174, 177, 180, 183, 186, 190, 192, 195, 198, + 201, 204, 206, 209, 211, 214, 216, 219, 221, 223, 225, 227, 229, + 231, 233, 235, 236, 238, 240, 241, 243, 244, 245, 246, 247, 248, + 249, 250, 251, 252, 253, 253, 254, 254, 254, 255, 255, 255, 255, +}; + +static int32_t isin(int32_t deg) { + int32_t d = deg % 360; + if (d < 0) d += 360; + if (d <= 90) return SIN_Q[d]; + if (d <= 180) return SIN_Q[180 - d]; + if (d <= 270) return -SIN_Q[d - 180]; + return -SIN_Q[360 - d]; +} + /* ---- VM ----------------------------------------------------------------- */ static int vm_run(const expr_prog_t *pr, const expr_env_t *env) { @@ -408,6 +443,8 @@ static int vm_run(const expr_prog_t *pr, const expr_env_t *env) { case OP_NOT: if (sp) st[sp - 1] = !st[sp - 1]; break; case OP_BNOT: if (sp) st[sp - 1] = (int32_t)~(uint32_t)st[sp - 1]; break; case OP_ABS: if (sp) st[sp - 1] = st[sp - 1] < 0 ? -st[sp - 1] : st[sp - 1]; break; + case OP_SIN: if (sp) st[sp - 1] = isin(st[sp - 1]); break; + case OP_COS: if (sp) st[sp - 1] = isin(st[sp - 1] + 90); break; default: { int need = in->op == OP_CLAMP ? 3 : in->op == OP_DIST ? 4 : in->op == OP_SELECT ? 3 : 2; @@ -505,6 +542,8 @@ static bool stack_fits(const expr_prog_t *pr) { case OP_NEG: case OP_NOT: case OP_BNOT: + case OP_SIN: + case OP_COS: case OP_ABS: break; case OP_CLAMP: case OP_SELECT: sp -= 2; break; |
