Scripting files for Arbgeom, August 13, 2024


Since I haven't put out an update in a long time, I wanted to show off what the scripting files will look like for Arbgeom.  I've attached the current assets file and pasted some of the .  I'm still developing right now so some of these files might be a little jank/incomplete/broken. Also it looks like itch.io mangled some of the generic arguments on the geometry description files, so it should look like this: <shape: Proj+Sdf>.  

Geometry Descriptions: Structured Scaffolding Language

Language Requirements:

  • Needs to be interpreted by rust (for physics) and compiled into wgsl (for rendering) -- CHECK
  • Allow for nesting of different objects -- CHECK
  • Lightweight -- CHECK
  • Fast & Optimized -- CHECK-ish, WIP (much faster than using rhai for this though)
    • Hard to do since the optimizations need to be done at the AST level for rust to interpret and to be turned into WGSL which is then compiled.

Basic shapes:

class Plane4D {
    normal: vec4,
    Proj::proj(vector: vec4) -> vec4 {
        vector - normal * dot(vector, normal)
    },
    Sdf::sdf(vector: vec4) -> f32 {
        dot(vector, normal)
    },
    Aabb::aabb() -> (vec4, vec4) {
        (-INFINITY * ONES, INFINITY * ONES)
    }
}
class Box4D {
    size: vec4,
    Proj::proj(vector: vec4) -> vec4 {
        let q: vec4 = abs(vector) - size;
        let add_to_proj: vec4 = select(
            select(
                select(vec4(0, 0, 0, -q.w), vec4(0, 0, -q.z, 0),
                    q.z > q.w
                ), vec4(0, -q.y, 0, 0),
                (q.y > q.z) && (q.y > q.w)
            ), vec4(-q.x, 0, 0, 0),
            q.x > q.y && q.x > q.z && q.x > q.w
        );
        select(
            {
                let proj: vec4 = abs(vector);
                proj = proj + add_to_proj;
                proj * sign(vector)
            },
            min(abs(vector), size) * sign(vector),
            length(max(q, vec4(0, 0, 0, 0))) > 0.0000001
        )
    },
    Sdf::sdf(vector: vec4) -> f32 {
        let q: vec4 = abs(vector) - size;
        let max_q: f32 = max(
            max(q.x, q.y),
            max(q.z, q.w)
        );
        length(max(q, vec4(0, 0, 0, 0))) + min(max_q, 0.0)
    },
    Aabb::aabb() -> (vec4, vec4) {
        (-size, size)
    }
}
class Sphere4D {
    radius: f32,
    Proj::proj(vector: vec4) -> vec4 {
        radius * normalize(vector + vec4(0.000001, 0.0, 0.0, 0.0))
    },
    Sdf::sdf(vector: vec4) -> f32 {
        length(vector) - radius
    },
    Aabb::aabb() -> (vec4, vec4) {
        (-radius * ONES, radius * ONES)
    }
}
class Ditorus {
    r01: f32,
    r012: f32,
    r0123: f32,
    Proj::proj(vector: vec4) -> vec4 {
        let proj1: vec4 = r01 * normalize(vec4(vector.x, vector.y, 0.0, 0.0));
        let proj2: vec4 = r012 * normalize(vec4(vector.x, vector.y, vector.z, 0.0) - proj1);
        let proj3: vec4 = r0123 * normalize(vector - proj2 - proj1);
        proj3 + proj2 + proj1
    },
    Sdf::sdf(vector: vec4) -> f32 {
        let proj1: vec4 = r01 * normalize(vec4(vector.x, vector.y, 0.0, 0.0));
        let proj2: vec4 = r012 * normalize(vec4(vector.x, vector.y, vector.z, 0.0) - proj1);
        length(vector - proj1 - proj2) - r0123
    },
    Aabb::aabb() -> (vec4, vec4) {
        (-r01 - r012 - r0123, r01 + r012 + r0123)
    }
}
class Torisphere {
    r012: f32,
    r0123: f32,
    Proj::proj(vector: vec4) -> vec4 {
        let proj1: vec4 = r012 * normalize(vec4(vector.x, vector.y, vector.z, 0.0));
        proj1 + r0123 * normalize(vector - proj1)
    },
    Sdf::sdf(vector: vec4) -> f32 {
        let proj1: vec4 = r012 * normalize(vec4(vector.x, vector.y, vector.z, 0.0));
        length(vector - proj1) - r0123
    },
    Aabb::aabb() -> (vec4, vec4) {
        ((-r012 - r0123) * ONES, (r012 + r0123) * ONES)
    }
}
class Spheritorus {
    r01: f32,
    r0123: f32,
    Proj::proj(vector: vec4) -> vec4 {
        let proj1: vec4 = r01 * normalize(vec4(vector.x, vector.y, 0.0, 0.0));
        proj1 + r0123 * normalize(vector - proj1)
    },
    Sdf::sdf(vector: vec4) -> f32 {
        let proj1: vec4 = r01 * normalize(vec4(vector.x, vector.y, 0.0, 0.0));
        length(vector - proj1) - r0123
    },
    Aabb::aabb() -> (vec4, vec4) {
        ((-r01 - r0123) * ONES, (r01 + r0123) * ONES)
    }
}
class Nothing {
    Proj::proj(vector: vec4) -> vec4 {
        vec4(1000000, 10000000, 1000000, 1000000)
    },
    Sdf::sdf(vector: vec4) -> f32 {
        1000000000.0
    },
    Aabb::aabb() -> (vec4, vec4) {
        (INFINITY * ONES, -INFINITY * ONES)
    }
}
class Marker {
    Proj::proj(vector: vec4) -> vec4 {
        vec4(0, 0, 0, 0)
    },
    Sdf::sdf(vector: vec4) -> f32 {
        length(vector)
    },
    Aabb::aabb() -> (vec4, vec4) {
        (vec4(0, 0, 0, 0), vec4(0, 0, 0, 0))
    }
}

Operations:

class Shell {
    offset: f32,
    shape: Class,
    Proj::proj<shape: Proj>(vector: vec4) -> vec4 {
        let proj: vec4 = shape.proj(vector);
        proj + offset * normalize(vector - proj)
    },
    Sdf::sdf<shape: Sdf>(vector: vec4) -> f32 {
        abs(shape.sdf(vector)) - offset
    },
    Aabb::aabb<shape: Aabb>() -> (vec4, vec4) {
        let aabb: (vec4, vec4) = shape.aabb();
        (-offset * ONES + aabb.0, offset * ONES + aabb.1)
    }
}
class Extrude {
    direction: vec4,
    shape: Class,
    Proj::proj<shape: proj="">(vector: vec4) -> vec4 {
        let dot: f32 = dot(vector, direction);
        direction * dot + shape.proj(vector - direction * dot)
    },
    Sdf::sdf<shape: sdf="">(vector: vec4) -> f32 {
        let dot: f32 = dot(vector, direction);
        shape.sdf(vector - direction * dot)
    },
    Aabb::aabb() -> (vec4, vec4) {
        (-INFINITY * ONES, INFINITY * ONES)
    }
}
class Shift {
    shift: vec4,
    shape: Class,
    Proj::proj<shape: proj="">(vector: vec4) -> vec4 {
        shape.proj(vector - shift) + shift
    },
    Sdf::sdf<shape: sdf="">(vector: vec4) -> f32 {
        shape.sdf(vector - shift)
    },
    Aabb::aabb<shape: aabb="">() -> (vec4, vec4) {
        let aabb: (vec4, vec4) = shape.aabb();
        (aabb.0 + shift, aabb.1 + shift)
    }
}
// TODO: Need to add support for precomputed data like orientation & inverted_orientation
class Reorient {
    inv_orientation: mat4x4,
    shape: Class,
    Proj::proj<shape: proj="">(vector: vec4) -> vec4 {
        shape.proj(inv_orientation * vector)
    },
    Sdf::sdf<shape: sdf="">(vector: vec4) -> f32 {
        shape.sdf(inv_orientation * vector)
    },
    // TODO: Improve this later, by actually computing the new AABB
    Aabb::aabb<shape: aabb="">() -> (vec4, vec4) {
        (-INFINITY * ONES, INFINITY * ONES)
    }
}
class Invert {
    shape: Class,
    Proj::proj<shape: proj="">(vector: vec4) -> vec4 {
        shape.proj(vector)
    },
    Sdf::sdf<shape: sdf="">(vector: vec4) -> f32 {
        -shape.sdf(vector)
    },
    Aabb::aabb<shape: aabb="">() -> (vec4, vec4) {
        shape.aabb()
    }
}
// TODO: Falls apart in some cases, needs improvement
class Intersect {
    shape1: Class,
    shape2: Class,
    Proj::proj<shape1: proj="" +="" sdf,="" shape2:="" sdf="">(vector: vec4) -> vec4 {
        let proj1: vec4 = shape1.proj(vector);
        let proj2: vec4 = shape2.proj(vector);
        let proj3: vec4 = shape2.proj(proj1);
        let sdf1: f32 = shape1.sdf(vector);
        let sdf2: f32 = shape2.sdf(vector);
        select(
            select(proj2, proj1, sdf1 > sdf2),
            proj3,
            sign(sdf1) == sign(sdf2)
        )
    },
    Sdf::sdf<shape1: sdf="" +="" proj,="" shape2:="" proj="">(vector: vec4) -> f32 {
        let proj1: vec4 = shape1.proj(vector);
        let proj2: vec4 = shape2.proj(vector);
        let proj3: vec4 = shape2.proj(proj1);
        let sdf1: f32 = shape1.sdf(vector);
        let sdf2: f32 = shape2.sdf(vector);
        select(
            select(length(proj2) * sign(sdf2), length(proj1) * sign(sdf1), sdf1 > sdf2),
            length(proj3) * sign(sdf1),
            sign(sdf1) == sign(sdf2)
        )
    },
    Aabb::aabb<shape1: aabb,="" shape2:="" aabb="">() -> (vec4, vec4) {
        let aabb1: (vec4, vec4) = shape1.aabb();
        let aabb2: (vec4, vec4) = shape2.aabb();
        (max(aabb1.0, aabb2.0), min(aabb1.1, aabb2.1))
    }
}
// TODO: Doesn't work at all :(
class Interpolate {
    shape1: Class,
    shape2: Class,
    t: f32,
    Proj::proj<shape1: Proj + sdf, shape2= Sdf>(vector: vec4) -> vec4 {
        let d1: vec4 = vector - shape1.proj(vector);
        let l1: f32 = shape1.sdf(vector);
        let d2: vec4 = vector - shape2.proj(vector);
        let l2: f32 = shape2.sdf(vector);
        let cur_t: f32 = l1 / (l1 + l2);
        let dot: f32 = dot(d1, d2) / (l1 * l2);
        select(
            vector + d1 / l1 * (l2 * t - l1 * (1 - t)) / (1 - t - dot * t),
            vector + d2 / l2 * (l2 * t - l1 * (1 - t)) / (dot - dot * t - t),
            cur_t < t
        )
    },
}
/*
[IGNORE]
OLD operations.rhai FILE:
//! shell { offset: f32, shape: Shape }
fn shell_proj(shape_proj, offset, vector) {
    let proj = shape_proj.call(vector);
    proj + offset * (vector - proj).normalize()
}
fn shell_sdf(shape_sdf, offset, vector) {
    -offset + abs(shape_sdf.call(vector))
}
//! extrude { direction: Vec4, shape: Shape }
fn extrude_proj(shape_proj, direction, vector) {
    let dot = vector.dot(direction);
    direction * dot + shape_proj.call(vector - direction * dot)
}
fn extrude_sdf(shape_sdf, direction, vector) {
    let dot = vector.dot(direction);
    shape_sdf.call(vector - direction * dot)
}
//! shift { shift: Vec4, shape: Shape }
fn shift_proj(shape_proj, shift, vector) {
    shift + shape_proj.call(vector - shift)
}
fn shift_sdf(shape_sdf, shift, vector) {
    shape_sdf.call(vector - shift)
}
//! reorient { inv_orientation: Mat4, shape: Shape }
fn reorient_proj(shape_proj, inv_orientation, vector) {
    shape_proj.call(inv_orientation * vector)
}
fn reorient_sdf(shape_sdf, inv_orientation, vector) {
    shape_sdf.call(inv_orientation * vector)
}
//! invert { shape: Shape }
fn invert_proj(shape_proj, vector) {
    shape_proj.call(vector)
}
fn invert_sdf(shape_sdf, vector) {
    -shape_sdf.call(vector)
}
fn invert_aabb() {
    [vec4(-100000000, -100000000, -100000000, -100000000), vec4(100000000, 100000000, 100000000, 100000000)]
}
//! intersection { shape1: Shape, shape2: Shape }
fn intersection_proj(shape1_proj, shape2_proj, shape1_sdf, shape2_sdf, vector) {
    let proj1 = shape1_proj.call(vector);
    let proj2 = shape2_proj.call(vector);
    let proj3 = shape2_proj.call(proj1);
    let sdf1 = shape1_sdf.call(vector);
    let sdf2 = shape2_sdf.call(vector);
    select(
        select(proj2, proj1, sdf1 > sdf2),
        proj3,
        sign(sdf1) == sign(sdf2)
    )
}
fn intersection_sdf(shape1_proj, shape2_proj, shape1_sdf, shape2_sdf, vector) {
    let proj1 = shape1_proj.call(vector);
    let proj2 = shape2_proj.call(vector);
    let proj3 = shape2_proj.call(proj1);
    let sdf1 = shape1_sdf.call(vector);
    let sdf2 = shape2_sdf.call(vector);
    select(
        select(length(proj2) * sign(sdf2), length(proj1) * sign(sdf1), sdf1 > sdf2),
        length(proj3) * sign(sdf1),
        sign(sdf1) == sign(sdf2)
    )
}
fn intersection_aabb() {
    [vec4(-100000000, -100000000, -100000000, -100000000), vec4(100000000, 100000000, 100000000, 100000000)]
}
*/
</shape1:></shape1:></shape1:></shape1:></shape:></shape:></shape:></shape:></shape:></shape:></shape:></shape:></shape:></shape:></shape:></shape:></shape:></shape:>

Combined:

class Wormhole {
    shape: Class,
    offset: f32,
} => Shell {
     offset,
     shape: Intersect {
         shape1: Shell {
             shape: Plane4D {
                 normal: vec4(0, 0, 0, 1)
             },
             offset: 0
         },
         shape2: Invert {
             shape: Extrude {
                 direction: vec4(0, 0, 0, 1),
                 shape
             }
         }
     }
 }
class WormholeWrapped {
    shape: Class,
    offset: f32,
    dist: f32
} => Shell {
    offset,
    shape: Intersect {
        shape1: Intersect {
            shape1: Shell {
                shape: Plane4D { normal: vec4(0, 0, 0, 1) },
                offset: 0
            },
            shape2: Shift {
                shift: vec4(0, 0, dist, 0),
                shape: Plane4D { normal: vec4(0, 0, 1, 0) }
            }
        },
        shape2: Invert {
            shape: Extrude {
                direction: vec4(0, 0, 0, 1),
                shape
            }
        }
    }
}

Complex: 

class Portal {
    width: f32,
    height: f32,
    bridge_radius: f32,
    cutout_size: f32,
    Proj::proj(vector: vec4) -> vec4 {
        let bend_proj: vec4 = bridge_radius * normalize(vec4(0.0, 0.0, vector.z, vector.w)) + vec4(vector.x, vector.y, 0.0, 0.0);
        let plane_proj: vec4 = vec4(vector.x, vector.y, vector.z, bridge_radius * sign(vector.w));
        let is_in_cutout: bool = abs(vector.x) < width &&
            abs(vector.y) < height &&
            0 < vector.z && vector.z < cutout_size;
        select(
            plane_proj, bend_proj,
            abs(vector.w) < bridge_radius - 0.1 || is_in_cutout
        )
    },
    // TODO: Sdf shouldn't be required just because the parent impls it! (Same for other fns)
    Sdf::sdf(vector: vec4) -> f32 {
        0
    },
    Aabb::aabb() -> (vec4, vec4) {
        (ZERO, ZERO)
    }
}

Sdf_only:

class Fence {
    spacing: f32,
    thickness: f32,
    size: vec4,
    Sdf::sdf(vector: vec4) -> f32 {
        let v: vec4 = vector - spacing * round(clamp(vector, -size, size) / spacing);
        v = vec4(v.x, v.y, vector.z, v.w);
        let v1: vec4 = v - vec4(v.x, 0, 0, 0);
        let v2: vec4 = v - vec4(0, v.y, 0, 0);
        min(length(v1) - thickness, length(v2) - thickness)
    },
    // TODO: In the future you should only try to load what you can, but I'll just give a wrong implementation for now
    Proj::proj(vector: vec4) -> vec4 {
        vector
    },
}
class ReorientSdf {
    inv_orientation: mat4x4,
    shape: Class,
    Sdf::sdf<shape: sdf="">(vector: vec4) -> f32 {
        shape.sdf(inv_orientation * vector)
    },
}

Level Descriptions: Rusty Object Notation (RON) (with some pre-processing)

Language Requirements:

  • Customizable and able to ergonomically hold shape data -- CHECK
  • Easy to extend with preprocessor -- CHECK

Test world:

World(
    name: "Dev world",
    description: "A bunch of levels I created to help test/debug the game or just to explore what the system can do...",
    levels: [
        Level(
            name: "Plane",
            description: "more desc >:D",
            scripts: ["worlds/test_world/script1.rhai"],
            manifold: Plane4D(normal: [0, 0, 0, 1]),
            spawn_pose: Pose(position: [30, 30, 0, 0]),
            geometries: [
                Geometry(
                    pose: Pose(position: [0, -3, 0, 0]),
                    shape: Box4D(size: [100, 3, 100, 100]),
                    material: "basic",
                    color: "gray"
                ),
            ]
        ),
        Level(
            name: "Many much wormholen",
            description: "more desc >:D",
            scripts: ["worlds/test_world/script1.rhai"],
            // TODO: Because wormhole is an instance class it breaks when other classes use it
            manifold: Wormhole(
                shape: Sphere4D(radius: 20),
                offset: 10
            ),
            spawn_pose: Pose(position: [0, 10, 30, 10]),
            geometries: [
                /*Geometry(
                    pose: Pose(position: [0, 10, -30, 10]),
                    shape: Sphere4D(radius: 5),
                    material: "basic",
                    color: "pink",
                    is_dynamic: true
                ),*/
                Geometry(
                    pose: Pose(position: [0, 40, 0, 0]),
                    shape: Invert(shape: Box4D(size: [60, 40, 60, 60])),
                    material: "basic",
                ),
            ]
        ),
        Level(
            name: "Cup",
            description: "more desc >:D",
            scripts: ["worlds/test_world/script1.rhai"],
            manifold: Shell(
                offset: 5,
                shape: Intersect(
                    shape1: Plane4D( normal: [0, 0, 0, 1] ),
                    shape2: Invert(shape: Shell(
                        offset: 10,
                        shape: Box4D(size: [10, 5, 10, 40])
                    ))
                )
            ),
            spawn_pose: Pose(position: [80, 0, 0, 10]),
            geometries: [
                Geometry(
                    pose: Pose(position: [0, 0, 0, 0]),
                    shape: Invert(shape: Box4D(size: [90, 40, 60, 60])),
                    material: "basic"
                ),
            ]
        ),
        Level(
            name: "Bare Portal",
            description: "more desc >:D",
            scripts: ["worlds/test_world/script1.rhai"],
            manifold: Portal( width: Expr("qe+10"), height: Expr("tg+20"), bridge_radius: Expr("ol+20"), cutout_size: Expr("uj+6") ),
            spawn_pose: Pose(position: [40, 10, 0, 30]),
            geometries: [
                Geometry(
                    pose: Pose(position: [0, 40, 0, 0]),
                    shape: Invert(shape: Box4D(size: [90, 40, 60, 60])),
                    material: "basic"
                ),
                Geometry(
                    pose: Pose(position: [-40, 40, -40, 20]),
                    shape: ReorientSdf(
                        inv_orientation: [[.707107, .707107, 0, 0], [-.707107, .707107, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]],
                        shape: Fence(spacing: 2, thickness: 0.04, size: [100, 100, 100, 3])
                    ),
                    collider: Box4D(size: [100, 100, 0.1, 3]),
                    material: "basic",
                    color: "gray"
                ),
                /*Geometry(
                    pose: Pose(position: [-30, 10, 0, -30]),
                    shape: Sphere4D(radius: 5),
                    material: "basic",
                    color: "pink",
                    is_dynamic: true
                ),*/
            ]
        ),
        Level(
            name: "Portal??",
            description: "more desc >:D",
            scripts: ["worlds/test_world/script1.rhai"],
            manifold: Portal( width: 10.0, height: 20.0, bridge_radius: 20.0, cutout_size: 6.0 ),
            spawn_pose: Pose(position: [40, 10, 0, 30]),
            geometries: [
                Geometry(
                    pose: Pose(position: [10.0, 0.0, 3.4, 0]),
                    shape: Box4D(size: [0.4, 20.0, 3.4, 60]),
                    material: "rough"
                ),
                Geometry(
                    pose: Pose(position: [-10.0, 0.0, 3.4, 0]),
                    shape: Box4D(size: [0.4, 20.0, 3.4, 60]),
                    material: "rough"
                ),
                Geometry(
                    pose: Pose(position: [0.0, 20.0, 3.4, 0]),
                    shape: Box4D(size: [10.0, 0.4, 3.4, 60]),
                    material: "rough"
                ),
                Geometry(
                    pose: Pose(position: [-10.0, 0.0, 10.0, 0]),
                    shape: Box4D(size: [0.4, 20.0, 10.0, 19.0]),
                    material: "rough"
                ),
                Geometry(
                    pose: Pose(position: [10.0, 0.0, 10.0, 0]),
                    shape: Box4D(size: [0.4, 20.0, 10.0, 19.0]),
                    material: "rough"
                ),
                Geometry(
                    pose: Pose(position: [0.0, 20.0, 10.0, 0]),
                    shape: Box4D(size: [10.0, 0.4, 10.0, 19.0]),
                    material: "rough"
                ),
                Geometry(
                    pose: Pose(position: [0.0, 0.0, 6.0, 20]),
                    shape: Box4D(size: [10.4, 20.4, 0.7, 0.1]),
                    material: "rough"
                ),
                Geometry(
                    pose: Pose(position: [0.0, 0.0, 6.0, -20]),
                    shape: Box4D(size: [10.0, 20.0, 0.4, 0.1]),
                    material: "rough"
                ),
                Geometry(
                    pose: Pose(position: [0, 40, 0, 0]),
                    shape: Invert(shape: Box4D(size: [90, 40, 60, 60])),
                    material: "basic"
                ),
                Geometry(
                    pose: Pose(position: [-40, 40, -40, 20]),
                    shape: ReorientSdf(
                        inv_orientation: [[.707107, .707107, 0, 0], [-.707107, .707107, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]],
                        shape: Fence(spacing: 2, thickness: 0.04, size: [100, 100, 100, 3])
                    ),
                    collider: Box4D(size: [100, 100, 0.1, 3]),
                    material: "basic",
                    color: "gray"
                ),
                Geometry(
                    pose: Pose(position: [-30, 10, 0, -30]),
                    shape: Sphere4D(radius: 1),
                    material: "basic",
                    color: "pink",
                    is_dynamic: true
                ),
            ]
        ),
        Level(
            name: "Plane",
            description: "more desc >:D",
            scripts: ["worlds/test_world/script1.rhai"],
            manifold: Plane4D(normal: [0, 0, 0, 1]),
            spawn_pose: Pose(position: [30, 30, 0, 0]),
            geometries: [
                Geometry(
                    pose: Pose(position: [-30, 10, 0, 0]),
                    shape: Sphere4D(radius: Expr("0.5*sin(t)+3")),
                    material: "basic",
                    color: "pink",
                    is_dynamic: true
                ),
                Geometry(
                    pose: Pose(position: [0, -3, 0, 0]),
                    shape: Box4D(size: [100, 3, 100, 100]),
                    material: "basic",
                    color: "gray"
                ),
            ]
        ),
        Level(
            name: "Wormhole gun",
            description: "more desc >:D",
            scripts: ["worlds/test_world/script1.rhai"],
            // TODO: Because wormhole is an instance class it breaks when other classes use it
            manifold: Wormhole(
                shape: Shift(shift: Expr("(vec4(20, 0, select(-20, 20, sin(t) > 0), 0))"), shape: Sphere4D(radius: Expr("abs(20*sin(t))"))),
                offset: Expr("tg + 10")
            ),
            spawn_pose: Pose(position: [0, 10, 30, 10]),
            geometries: [
                Geometry(
                    pose: Pose(position: [0, 10, -30, 10]),
                    shape: Sphere4D(radius: 5),
                    material: "basic",
                    color: "pink",
                    is_dynamic: true
                ),
                Geometry(
                    pose: Pose(position: [0, 40, 0, 0]),
                    shape: Invert(shape: Box4D(size: [60, 40, 60, 60])),
                    material: "basic",
                ),
                Geometry(
                    pose: Pose(position: [0, 40, 0, 10]),
                    shape: ReorientSdf(
                        inv_orientation: [[.707107, .707107, 0, 0], [-.707107, .707107, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]],
                        shape: Fence(spacing: 2, thickness: 0.04, size: [100, 100, 100, 3])
                    ),
                    collider: Box4D(size: [100, 100, 0.1, 3]),
                    material: "basic",
                    color: "gray"
                ),
            ]
        ),
        Level(
            name: "Wormhole gun cube-ey",
            description: "more desc >:D",
            scripts: ["worlds/test_world/script1.rhai"],
            // TODO: Because wormhole is an instance class it breaks when other classes use it
            manifold: Wormhole(
                shape: Shift(
                    shift: Expr("(vec4(20, -10, select(-30, 30, sin(t/3) > 0), 0))"),
                    shape: Shell(
                        offset: 11,
                        shape: Box4D(size: Expr("vec4(0.1, 40*abs(sin(t/3)), 10, 100)"))
                    )
                ),
                offset: 10
            ),
            spawn_pose: Pose(position: [0, 10, 30, 10]),
            geometries: [
                Geometry(
                    pose: Pose(position: [0, 10, -30, 10]),
                    shape: Sphere4D(radius: 5),
                    material: "basic",
                    color: "pink",
                    is_dynamic: true
                ),
                Geometry(
                    pose: Pose(position: [0, 40, 0, 0]),
                    shape: Invert(shape: Box4D(size: [60, 40, 60, 60])),
                    material: "basic",
                ),
                Geometry(
                    pose: Pose(position: [0, 40, 0, 10]),
                    shape: ReorientSdf(
                        inv_orientation: [
                            [.707107, .707107, 0, 0],
                            [-.707107, .707107, 0, 0],
                            [0, 0, 1, 0],
                            [0, 0, 0, 1]
                        ],
                        shape: Fence(spacing: 2, thickness: 0.04, size: [100, 100, 100, 3])
                    ),
                    collider: Box4D(size: [100, 100, 0.1, 3]),
                    material: "basic",
                    color: "gray"
                ),
            ]
        ),
        Level(
            name: "Wormhole gun portally",
            description: "more desc >:D",
            scripts: ["worlds/test_world/script1.rhai"],
            manifold: Shift(
                shift: Expr("vec4(select(-30, 30, sin(t/3) > 0), 0, 20, 0)"),
                shape: Portal( width: 10.0, height: Expr("30*abs(sin(t/3))"), bridge_radius: 10.0, cutout_size: 6.0 )
            ),
            spawn_pose: Pose(position: [30, 10, 0, 10]),
            geometries: [
                Geometry(
                    pose: Pose(position: [-30, 10, 0, 10]),
                    shape: Sphere4D(radius: 5),
                    material: "basic",
                    color: "pink",
                    is_dynamic: true
                ),
                Geometry(
                    pose: Pose(position: [0, 40, 0, 0]),
                    shape: Invert(shape: Box4D(size: [60, 40, 60, 60])),
                    material: "basic",
                ),
                Geometry(
                    pose: Pose(position: [0, 40, 0, 10]),
                    shape: ReorientSdf(
                        inv_orientation: [
                            [0, 0, 1, 0],
                            [-.707107, .707107, 0, 0],
                            [.707107, .707107, 0, 0],
                            [0, 0, 0, 1]
                        ],
                        shape: Fence(spacing: 2, thickness: 0.04, size: [100, 100, 100, 3])
                    ),
                    collider: Box4D(size: [0.1, 100, 100.0, 3]),
                    material: "basic",
                    color: "gray"
                ),
            ]
        ),
        Level(
            name: "Interp pt.2",
            description: "more desc >:D muahahaha",
            scripts: ["worlds/test_world/script1.rhai"],
            manifold: Shell(
                shape: Box4D(size: [25, 25, 25, 25]),
                offset: Expr("qe + 5")
            ),
            spawn_pose: Pose(position: [30, 30, 0, 0]),
            geometries: [
                Geometry(
                    pose: Pose(position: [-100, -3, 0, 0]),
                    shape: Box4D(size: [100, 3, 100, 100]),
                    material: "basic",
                    color: "pink"
                ),
                Geometry(
                    pose: Pose(position: [100, -3, 0, 0]),
                    shape: Box4D(size: [100, 3, 100, 100]),
                    material: "basic",
                    color: "gray",
                    id: "floor"
                ),
            ]
        ),
        Level(
            name: "Wormulous Holicules",
            description: "more desc >:D",
            scripts: ["worlds/test_world/script1.rhai"],
            manifold: WormholeWrapped(
                shape: Sphere4D(radius: Expr("qe + 10")),
                offset: Expr("tg + 5"),
                dist: Expr("tg + 10")
            ),
            spawn_pose: Pose(position: [30, 30, 0, 5]),
            geometries: [
                Geometry(
                    pose: Pose(position: [-30, 10, 0, -5]),
                    shape: Sphere4D(radius: Expr("0.5*sin(t)+3")),
                    material: "basic",
                    color: "pink",
                    is_dynamic: true
                ),
                Geometry(
                    pose: Pose(position: [0, -3, 0, 0]),
                    shape: Box4D(size: [100, 3, 100, 100]),
                    material: "basic",
                    color: "gray",
                    id: "floor"
                ),
            ]
        ),
        Level(
            name: "Wormulous Holicules -- Cube",
            description: "more desc >:D",
            scripts: ["worlds/test_world/script1.rhai"],
            manifold: WormholeWrapped(
                shape: Shell(offset: Expr("tg + 6 + qe"), shape: Box4D(size: Expr("vec4(ol + 5, 20 * yh + 5, ik + 5, 5)"))),
                offset: Expr("tg + 5"),
                dist: Expr("uj + 30")
            ),
            spawn_pose: Pose(position: [30, 30, 0, 5]),
            geometries: [
                Geometry(
                    pose: Pose(position: [-30, 10, 0, -5]),
                    shape: Sphere4D(radius: Expr("0.5*sin(t)+3")),
                    material: "basic",
                    color: "pink",
                    is_dynamic: true
                ),
                Geometry(
                    pose: Pose(position: [0, 40, 0, 0]),
                    shape: Invert(shape: Box4D(size: [50, 40, 50, 50])),
                    material: "basic",
                    id: "floor"
                ),
            ]
        ),
        Level(
            name: "Wormulous Holicules -- Cube no rounding",
            description: "more desc >:D",
            scripts: ["worlds/test_world/script1.rhai"],
            manifold: WormholeWrapped(
                shape: Box4D(size: Expr("vec4(10*qe + 20, 20 * tg + 20, 10*yh + 20, 10)")),
                offset: Expr("uj + 5"),
                dist: Expr("ik + 40")
            ),
            spawn_pose: Pose(position: [30, 30, 0, 5]),
            geometries: [
                Geometry(
                    pose: Pose(position: [-30, 10, 0, -5]),
                    shape: Sphere4D(radius: 3),
                    material: "basic",
                    color: "pink",
                    is_dynamic: true
                ),
                Geometry(
                    pose: Pose(position: [0, 40, 0, 0]),
                    shape: Invert(shape: Box4D(size: [60, 40, 60, 60])),
                    material: "basic",
                    id: "floor"
                ),
            ]
        ),
        Level(
            name: "Wormulous Holicules -- Torus",
            description: "more desc >:D",
            scripts: ["worlds/test_world/script1.rhai"],
            manifold: WormholeWrapped(
                shape: Shift(shift: Expr("vec4(5 * uj, 5 * qe, 5 * tg, 0)"), shape: Spheritorus(r01: 15, r012: 10, r0123: 10)),
                offset: Expr("ik + 5"),
                dist: 30
            ),
            spawn_pose: Pose(position: [30, 30, 0, 5]),
            geometries: [
                Geometry(
                    pose: Pose(position: [100, -3, 0, 0]),
                    shape: Box4D(size: [100, 3, 100, 100]),
                    material: "basic",
                    color: "gray"
                ),
                Geometry(
                    pose: Pose(position: [-100, -3, 0, -100]),
                    shape: Box4D(size: [100, 3, 100, 100]),
                    material: "basic",
                    color: "purple"
                ),
                Geometry(
                    pose: Pose(position: [-100, -3, 100, 100]),
                    shape: Box4D(size: [100, 3, 100, 100]),
                    material: "basic",
                    color: "orange"
                ),
            ]
        ),
        Level(
            name: "Weird cut in box",
            description: "more desc >:D muahahaha",
            scripts: ["worlds/test_world/script1.rhai"],
            manifold: Shell( offset: 10, shape: Intersect( shape1: Intersect(
                shape1: Box4D(size: [25, 25, 25, 25]),
                shape2: Invert( shape: Shell(
                    shape: Shift( shift: [25, 0, 25, 25], shape: Box4D(size: [30, 30, 30, 30])),
                    offset: 20
                ))
            ), shape2: Invert(shape: Shift(shift: [0, 0, 0, -25], shape: Sphere4D(radius: 15))))),
            spawn_pose: Pose(position: [-30, 30, 0, 0]),
            geometries: [
                Geometry(
                    pose: Pose(position: [-100, -3, 0, 0]),
                    shape: Box4D(size: [100, 3, 100, 100]),
                    material: "basic",
                    color: "pink"
                ),
                Geometry(
                    pose: Pose(position: [100, -3, 0, 0]),
                    shape: Box4D(size: [100, 3, 100, 100]),
                    material: "basic",
                    color: "gray"
                ),
                Geometry(
                    pose: Pose(position: [-30, 10, 0, 0]),
                    shape: Sphere4D(radius: 5),
                    material: "basic",
                    color: "yellow",
                    is_dynamic: true
                ),
            ]
        ),
        Level(
            name: "Spherical test",
            description: "ABCD",
            scripts: ["worlds/test_world/script1.rhai"],
            manifold: Sphere4D(radius: 30),
            spawn_pose: Pose(position: [30, 30, 0, 0]),
            geometries: [
                Geometry(
                    pose: Pose(position: [-30, 0, 0, 0]),
                    shape: Sphere4D(radius: Expr("0.5*sin(t)+3")),
                    material: "basic",
                    color: "pink",
                    is_dynamic: true
                ),
                Geometry(
                    pose: Pose(position: [0, -1000, 0, 0]),
                    shape: Sphere4D(radius: 1000),
                    material: "basic",
                    color: "gray"
                ),
                // Geometry(
                //     pose: Pose(position: [0, -3, 0, 0]),
                //     shape: Box4D(size: [100, 3, 100, 100]),
                //     material: "basic",
                //     color: "gray"
                // ),
            ]
        ),
        Level(
            name: "RUNNN!!",
            description: "more desc >:D",
            scripts: ["worlds/test_world/script1.rhai"],
            manifold: Wormhole(
                shape: Sphere4D(radius: Expr("max(0, 200 * cos(2 * 3.141592 * t / 20))")),
                offset: 20
            ),
            spawn_pose: Pose(position: [0, 5, -150, 20]),
            geometries: [
                Geometry(
                    pose: Pose(position: [0, 5, -200, -20]),
                    shape: Sphere4D(radius: 3),
                    material: "basic",
                    color: "pink",
                    is_dynamic: true
                ),
                Geometry(
                    pose: Pose(position: [0, -0.5, 0, 0]),
                    shape: Box4D(size: [5, 0.5, 1000, 100]),
                    material: "basic",
                    color: "gray"
                ),
                Geometry(
                    pose: Pose(position: [0, -0.9999, -200, -200]),
                    shape: Box4D(size: [10, 1, 10, 200]),
                    material: "basic",
                    color: "red"
                ),
                Geometry(
                    pose: Pose(position: [0, 0, -110, 0]),
                    shape: Invert(shape: Box4D(size: [80, 80, 110, 80])),
                    material: "basic"
                ),
            ]
        ),
        Level(
            name: "Failed Tube -- intersection is hard :(",
            description: "more desc >:D",
            scripts: ["worlds/test_world/script1.rhai"],
            manifold: Plane4D(normal: [0, 0, 0, 1]),
            spawn_pose: Pose(position: [0, 10, 0, 0]),
            geometries: [
                Geometry(
                    pose: Pose(position: [0, 40, 0, 0]),
                    shape: Invert(shape: Box4D(size: [50, 40, 50, 80])),
                    material: "basic"
                ),
                Geometry(
                    pose: Pose(position: [30, 0, 0, 0]),
                    shape: Intersect(
                        shape1: Plane4D(normal: [0, 0, 1, 0]),
                        shape2: Shell(
                            offset: 1,
                            shape: Sphere4D(radius: 10)
                        )
                    ),
                    material: "basic"
                ),
            ]
        ),
    ]
)

Scripting: Rhai

Language Requirements:

  • Easy to integrate into the game loop -- CHECK
  • Nice APIs -- CHECK
  • Able to interact nicely with rust structs and data -- CHECK

Test World Script (script1.rhai):

let qe = 0;
let tg = 0;
let yh = 0;
let uj = 0;
let ik = 0;
let ol = 0;
fn init() {
    print("init called from rhai");
}
fn update() {
    if "floor" in geometries {
        geometries["floor"].pose.position.y = ol;
    }
}
fn on_key_press_q() {
    print("q pressed");
    qe += 0.1;
}
fn on_key_press_e() {
    print("e pressed");
    qe -= 0.1;
}
fn on_key_press_t() {
    print("t pressed");
    tg += 0.1;
}
fn on_key_press_g() {
    print("g pressed");
    tg -= 0.1;
}
fn on_key_press_y() {
    print("y pressed");
    yh += 0.1;
}
fn on_key_press_h() {
    print("h pressed");
    yh -= 0.1;
}
fn on_key_press_u() {
    print("u pressed");
    uj += 0.1;
}
fn on_key_press_j() {
    print("j pressed");
    uj -= 0.1;
}
fn on_key_press_i() {
    print("i pressed");
    ik += 0.1;
}
fn on_key_press_k() {
    print("k pressed");
    ik -= 0.1;
}
fn on_key_press_o() {
    print("o pressed");
    ol += 0.1;
}
fn on_key_press_l() {
    print("l pressed");
    ol -= 0.1;
}

Assets Dowload

If you want to download the assets folder to play around with it is available here

How to add your own shapes

(New as of May 22, 2025)
This advice is for the April 2025 prerelease version of Arbgeom available here: https://drive.google.com/drive/u/1/folders/14UXYHcIoFhr9kYHzdlgCnf-XlHYUd3h5 . This version isn’t polished, but is available if you want to tinker with the new features before the next version comes out.
  1. First find where the game's asset folder is located.  This should be in the same folder as the game app itself.
    • MacOS: Open the app and right click on the icon in the dock, then click "Show in Finder", then right click the app again and select “Show Package Contents”.
    • Windows: Search for the app, right click the app, and select "Open file location".
  2. Then navigate to the assets folder. The ssl files are in /geometry_elements and each world has its own folder in /worlds with a .ron and .rhai file. The manifold functions are held in the .ssl files, so create a new one in /geometry_elements and name it whatever you want.
  3. You can now add shape definitions to this file and use them in a .ron file! However, right now, there is no good error catching. If you are on windows a terminal with some debug info might pop up, but more likely you won't see any good info come up. I will be adding better error handling in the future but, until then, here are some indicators of errors (although I might've missed some):
    • Crash when the level loads: This implies that the one of the ssl functions that was used in this level was parsed, but contained a mistake like a typo in a variable or type name.
    • Clicking play does nothing: This implies that there was a parse error in an ssl file. Make sure all "let" declarations have types, parentheses are balanced, and that everything is good.
    • World doesn't appear: This could be caused by an error in .ron or in .ssl, make sure all manifold names are correctly spelt and all parentheses/brackets are balanced in .ron, and that all shapes in .ssl have proj, sdf, and aabb functions (even if you only really implement one of them, if you're making manifolds you just need proj and sometimes sdf, so just put whatever in aabb as long as it parses).
    • Dark grey screen: This indicates an error in WGSL which means that you probably used a constant like IDENTITY or ONES in a ssl function. These aren't in WGSL yet (I know they do exist currently in aabb functions, but those only run on the cpu which is why they get to use those consts). This could also mean that you used the wrong type in a "let" declaration so double check those.
  4. Now you can start adding stuff to .ron files and they will show up in your levels. Right now you can only have three worlds so just add to an existing one. One bit of advice: since the error catching is really bad right now, make sure to go slowly when adding shapes. Add something small, then try it out, then add to it, then try it out again, and so on rather than adding a bunch of stuff all at once.

Get Arbgeom beta

Comments

Log in with itch.io to leave a comment.

(+1)

How exactly do I make a custom function, such that I can use it in the ssl files?

I added some steps to the bottom of this article.  Basically, you can navigate to the assets/geometry_elements folder to add some shape definitions and then create levels in an existing .ron file.

i mean how do i make functions that i can use INSIDE the basic_shapes file?

like i mean a function that takes in some inputs and returns a numerical value

Unfortunately that is still tbd, so right now you would have to just copy+paste.  I’ll be adding a mechanism for that soon since I’ll need to do the same for my better intersection functions anyway.

alright, thanks for letting me know, that will be huge when finished though

How do I enter the test/dev worlds exactly?

Wait i forgot it was hardcoded! never mind, take your time though