Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shapes library? #145

Open
waywardmonkeys opened this issue Jun 13, 2024 · 3 comments
Open

Shapes library? #145

waywardmonkeys opened this issue Jun 13, 2024 · 3 comments

Comments

@waywardmonkeys
Copy link
Contributor

Would there be interest in having a collection of the more common SDF shapes that create trees to give a slightly higher level API for using this?

@LukeP0WERS
Copy link

I created a somewhat shitty crate which makes shape construction easier with vector types: https://github.com/LukeP0WERS/fidget_math
My SDF modeler only has boxes and spheres at the moment, but I will eventually be adding most if not all of the common SDF shapes that can be represented with fidget. Since the TreeVec3 type was made specifically for shape construction I think it would make sense to add some shapes to that crate if you want me to. For example this is what a cube looks like normally:

fn cube(pos: [Tree; 3], size: [Tree; 3]) -> Tree {
    let q = [
        pos[0].abs() - size[0].clone(),
        pos[1].abs() - size[1].clone(),
        pos[2].abs() - size[2].clone(),
    ];
    let q_length = (
        q[0].max([0.0](f64::EPSILON)).square() +
        q[1].max([0.0](f64::EPSILON)).square() +
        q[2].max([0.0](f64::EPSILON)).square()
    ).sqrt();

    return q_length + q[0].clone().max(q[1].clone().max(q[2].clone())).min(0.0);
}

and this is what it looks like with my crate:

pub fn cube (pos: TreeVec3, size: TreeVec3) -> Tree {
    let q = pos.abs() - size;
    return q.clone().max(TreeVec3::splat(f64::EPSILON)).length() + q.max_element().min(0.0);
}

If this sounds useful to you let me know and I can add this in.

@mkeeter
Copy link
Owner

mkeeter commented Jun 17, 2024

This has been on my mind, but I have more questions than answers at the moment.

I started making a shapes library in core.rhai, but suspect that's not the right place for it; we probably want a Rust module that exports Rhai bindings.

I agree with @LukeP0WERS that we probably want TreeVec2/3 types for arguments, and they should support all of the operator overloading that you'd expect. Interestingly, this may Just Work using nalgebra:

    #[test]
    fn nalgebra_tree() {
        let x = Tree::x();
        let y = Tree::y();
        let z = Tree::z();
        let v = nalgebra::Vector3::new(x, y, z);
        let q = nalgebra::Vector3::new(
            Tree::constant(1.0),
            Tree::constant(2.0),
            Tree::constant(3.0),
        );
        let out = v + q;
        println!("{out:?}");
    }

The lack of default and named arguments in Rust functions makes things a bit awkward, e.g. lots of functions just take vec2, vec2.

A different option would be to declare a trait and build an explicit object tree:

trait ShapeLike {
    fn get(&self, ctx: &mut Context, axes: [Node; 3]) -> Node;
}

struct Circle {
    center: TVec3,
    radius: TFloat,
}

impl ShapeLike for Circle {
    fn get(&self, ctx: &mut Context, axes: [Node; 3]) -> Node {
        // ... etc
}

struct Union {
    lhs: Box<dyn ShapeLike>,
    rhs: Box<dyn ShapeLike>,
}

impl ShapeLike for Union {
    fn get(&self, ctx: &mut Context, axes: [Node; 3]) -> Node {
        let lhs = self.lhs.get(ctx, axes);
        let rhs = self.rhs.get(ctx, axes);
        ctx.min(lhs, rhs).unwrap()
    }
}

(yes, yes, this would be a third way to build shapes, along with Tree and Context)

As another data point, libfive used a terrible homebrew syntax to automatically generate Scheme and Python bindings from a C header, documented in this README.

@LukeP0WERS
Copy link

Right now I have a fully functional modeler with domain operations, shapes, and combination operations working with trees alone and just converting to a Context and then to a Function at the end. I have thought about using Context but I don't fully understand what it is supposed to be used for. Does it better optimize out unused operations or scale to having tons of operations? The flexibility and simplicity of trees have made them much more appealing to me so far, mostly due to a lack of understanding on my part, but they are also much less verbose to write and are very similar to shader code.

Also I feel like making a higher level API for things other than shapes, such as combinations or domain ops, could end up being a terrible idea since it would make things less flexible, or an amazing one if you managed to find a perfect solution to cover any conceivable use case (which obviously sounds stupid until you try it and it happens to work).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants