mkeeter / fidget

blazing fast implicit surface evaluation
Mozilla Public License 2.0
126 stars 11 forks source link

Weird Rhai error #2

Open virtualritz opened 1 year ago

virtualritz commented 1 year ago

So I just started playing around with the viewer.

This Rhai script

fn length(x, y) {
    sqrt(x*x + y*y)
}

draw(|x, y| length(x, y) - 1))

works as expected.

However

fn length(x, y) {
    sqrt(x*x + y*y)
}

fn circle(x, y, r) {
    length(x, y) - r
}

draw(|x, y| circle(x, y, 1))

Yields:

render thread got error "Rhai error: Function not found: __draw (Fn) (line 2, position 5)\nin call to function 'draw' (line 9, position 1)"; forwarding
mkeeter commented 1 year ago

This is somewhat sneaky: there is already a function named circle, and it's getting called instead of your function. In addition, it's already set up to return a lamda function, so you'd invoke it like

draw(circle(0, 0, 1)) // center x, center y, r -> (Fn(x, y) -> f32)

It works if you rename your function to not collide with the existing function:

fn length(x, y) {
    sqrt(x*x + y*y)
}
fn my_circle(x, y, r) {
    length(x, y) - r
}
draw(|x, y| my_circle(x, y, 1));

However, this violates the search order in the Rhai docs (and is confusing!), so let's leave this issue open.

virtualritz commented 1 year ago

However, this violates the search order in the Rhai docs (and is confusing!), so let's leave this issue open.

Shall I report this upstream to the rhai peeps?

virtualritz commented 6 months ago

It works if you rename your function to not collide with the existing function:

fn length(x, y) {
    sqrt(x*x + y*y)
}
fn my_circle(x, y, r) {
    length(x, y) - r
}
draw(|x, y| my_circle(x, y, 1));

When I try this with viewer in the current main branch, I get:

❯ cargo run -- test.rhai
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.09s
     Running `/home/moritz/code/crates/fidget/target/debug/viewer test.rhai`
[2024-04-09T11:58:18Z ERROR viewer] render thread got error "Rhai error: Function not found: draw (Fn) (line 9, position 1)"; forwarding
[...]
virtualritz commented 6 months ago

Ah, I see, it doesn't take a closure any more. Sorry about the noise, kindly ignore!

virtualritz commented 6 months ago

Docs say there is a draw_rgb function in Rhai but I get an error when trying to call this. Also: what's the signature of this function?

mkeeter commented 6 months ago

Yes, #67 switched to using the new Tree type in scripts (instead of closures).

The new signature is draw_rgb(Tree, f32, f32, f32), e.g. this works for me:

draw_rgb(circle(0, 1, 2), 1.0, 0.0, 0.0)
virtualritz commented 6 months ago

Cheers, it was a typo.

Is there a list of built-ins somewhere? It seems e.g. Rhai is missing e.g. abs? Docs say one needs to include ArithmeticPackage but the docs also say Engine::new() would include that. What am I missing?

virtualritz commented 6 months ago

I can not get a simple box in the viewer. Whatever function I try, except for the circle, I only get the positive part of the axes, i.e.

fn abs(x) {
    if x < 0 {
        -x
    } else {
        x
    }
}

fn box(x, y, radius_x, radius_y)
{
    let p_x = abs(x) - radius_x;
    let p_y = abs(y) - radius_y;

    max(p_x, p_y)
}

draw(box(x, y, 1, 1))

I get: image

Zooming out: image

I also tried move(box(...),...) but no luck. What am I missing?

mkeeter commented 6 months ago

Here's the issue:

fn abs(x) {
    if x < 0 {
        -x
    } else {
        x
    }
}

During script evaluation, x is a Tree handle. Normally, trees capture operations through operator overloading, e.g. x + 1 will return a new Tree. For some reason, Rhai comparisons of different types always return false, so x < 0 is evaluated during script evaluation and returns a new Tree that is always -x.

70 (merging soon) adds overloaded operators to ban these comparisons, so that it will fail at script evaluation instead of returning a misleading result. This PR also adds missing opcodes, so you can write your script as

fn box(radius_x, radius_y)
{
    let ax = axes();
    let p_x = abs(ax.x) - radius_x;
    let p_y = abs(ax.y) - radius_y;

    max(p_x, p_y)
}

draw(box(1, 1))

(I'm still figuring out best practices, but using axes() within a function seems cleaner than passing x, y as arguments)

virtualritz commented 6 months ago

(I'm still figuring out best practices, but using axes() within a function seems cleaner than passing x, y as arguments)

Speaking of which: writing the functions in Rhai, doing each operation for each coordinate, is very verbose because of the lack of tuple types. Most functions from e.g. here almost double in code length. For 3D cases it will be even more verbose.

What is the performance hit if one used arrays of two (or three) elements to represent vectors?

Of course then one would need to write the entire shebang of basics like mul, add, div etc. that work on those arrays. And the code of the kind of expressions used in SDFs would still look more like Lisp than math. :grin:.

I.e. best would probably be some language extension that supports vectors (and possible matrices) inside Rhai? Not sure this possible.

EDIT: I just saw this, so it seems this is possible and likely axes() does something like this already?

virtualritz commented 6 months ago

https://github.com/mkeeter/fidget/pull/70 (merging soon) adds overloaded operators to ban these comparisons, so that it will fail at script evaluation instead of returning a misleading result. This PR also adds missing opcodes, [...]

Cheers! :smiley:

So I have something like:

fn rounded_box(
    height_x, height_y,
    radius_top_right,
    radius_bottom_right,
    radius_top_left,
    radius_bottom_left)
{
    let ax = axes();
    let x = ax.x;
    let y = ax.y;

    let r_x = if 0.0 < x { radius_top_right } else { radius_top_left };
    let r_y = if 0.0 < x { radius_bottom_right } else { radius_bottom_left };
    let r_x = if 0.0 < y { r_x } else { r_y };
    let q_x = abs(x)- height_x + r_x;
    let q_y = abs(y)- height_y + r_x;

    min(max(q_x, q_y), 0) + length(max(q_x, 0.0), max(q_y, 0)) - r_x
}

And that gives me an error for the comparisons in the if a < bs:

cannot compare Tree types during function tracing

I tried rewriting the resp. expressions with compare(a, b) but this:

    let r_x = if compare(0, x) == -1 { radius_top_right } else { radius_top_left };
    let r_y = if compare(0, x) == -1 { radius_bottom_right } else { radius_bottom_left };
    let r_x = if compare(0, y) == -1 { r_x } else { r_y };

results in the same error.

What is the workaround here?

EDIT: thinking back of my days as a VFX shader writer (where conditionals were a no-no): step()/filteredstep() would be the alternative(s). And re. the latter: will there be access to filter size(s)/derivatives?

virtualritz commented 6 months ago

And another one. A simple rounding op, i.e.

fn round(shape, radius) {
    shape - radius
}

Only grows my shape by radius but there is no rounding. This is obviously somehow possible as visible in the viewer for the gradient 2D SDF view mode.

What am I missing/what is the workaround?