Some new Manifolds: Duocylinder & Interpolation


This post contains some of the relevant code from this video:

Play this release right now (unpolished)

This release is available here: https://drive.google.com/drive/folders/14UXYHcIoFhr9kYHzdlgCnf-XlHYUd3h5?usp=sha... (sorry for using google drive, but I couldn't figure out how to make itch.io only show the downloads on this post).  Note, this will still have some bugs, but it has everything shown in the video and a little more.  Some controls that are new:

Often the controls I use to change the shape of the manifolds will be q&e, t&g, u&j, i&k, o&l, you can find what these control for each level in the .ron files in assets/worlds.  Also to play will the map, hit lshift+` to toggle it and hit ` to only toggle the cursor.  Rotate the map normally by clicking and dragging, hold alt to rotate the slice, hold shift to pan, and scroll to zoom in and out.  Also you can align the 3D-slice to be normal to the x,y,z, or w axis by hitting v,b,n, or m respectively.  Lastly the F key will toggle follow mode for the map where it will always face the marble (although your view might be hidden behind some other part of the geometry or you might have to hit f multiple times to get it to work properly).

I haven't verified that this version works on all platforms, but it seems to work on mac.

Interpolation Projection function

The purpose of this operation is to take two shapes, shape1 and shape2, and interpolate between.  The resulting manifold from this operation would be a combination of the two shapes weighted by the parameter t in the range 0-1 as shown in this screenshot of the interpolation of a box and spheritorus:

Mix of box and spheritorus

At first, this might seem easy, just project onto shape1 and shape2, then interpolate the result.  That's what I tried initially, but it unfortunately doesn't work.  The reason being that the projection function needs to not (significantly) move projected points over time.  For example, if you were to interpolate between two lines at 30° angle with t=0.5, instead of projecting directly onto the line between them, it would move the point forward a little.  Then if it were projected over and over again the projected point would slowly travel towards the intersection of the two lines as illustrated below:

Bad interpolation projection function

Instead you need to project onto the curve defined by the interpolation which is sdf1(p)/sdf2(p) = t/(1-t) .  First the problem can be simplified by assuming that the input point is already pretty close to the manifold.  This isn't true generally so the interpolation projection function would likely become fairly glitchy in those cases, but when the manifold itself is Interpolate( ... ) and not something like Rounding( shape: Interpolate( ... ), ... ) this isn't much of an issue (although if you look at the video you might be able to spot some instability at times during an interpolation level).  While assuming this, the two shapes being interpolated can be approximated as just planes tangent to and normal at proj1(p) and proj2(p).  From here I define arm1 = p - proj1(p) and arm2 = p - proj2(p) , and solve the rest of the problem in the plane spanned by these two vectors using the arm1 and arm1_perp basis.  In this basis the intersection of the two lines approximating each shape has to be found then after which the intersection point can be used as the origin with which the final projected point is computed relative to which is shown in the desmos below:

Finally once the point is computed in the arm1 and arm1_perp basis it is expanded back out into 4D.

SSL Code

class DuoCylinder {
    r01: f32,
    r23: f32,
    Proj::proj(vector: vec4) -> vec4 {
        let xy: vec4 = vec4(vector.x, vector.y, 0, 0);
        let xy_len: f32 = length(xy);
        let zw: vec4 = vec4(0, 0, vector.z, vector.w);
        let zw_len: f32 = length(zw);
        let new_xy: vec4 = select(xy, r01 * xy / xy_len, xy_len > r01);
        let new_zw: vec4 = select(zw, r23 * zw / zw_len, zw_len > r23);
        select(
            new_xy + new_zw,
            select(xy + (r23 * zw / zw_len), (r01 * xy / xy_len) + zw, r01 - xy_len > r23 - zw_len),
            (xy_len < r01) && (zw_len < r23)
        )
    }
}
class Interpolate2 {
    shape1: Class,
    shape2: Class,
    t: f32,
    Proj::proj<shape1: Proj + Sdf, shape2: Proj + Sdf>(vector: vec4) -> vec4 {
        let proj1: vec4 = shape1.proj(vector);
        let proj2: vec4 = shape2.proj(vector);
        let arm1: vec4 = normalize(vector - proj1);
        let sdf1: f32 = shape1.sdf(vector);
        let arm2: vec4 = normalize(vector - proj2);
        let sdf2: f32 = shape2.sdf(vector);
        let arm1_perp: vec4 = select(normalize(arm2 - dot(arm2, arm1) * arm1), vec4(0,0,0,0), abs(dot(arm2, arm1)) == 1);
        // _b suffix means it is in the arm1, arm1_perp basis
        let arm2_b: vec4 = vec4(dot(arm2, arm1), dot(arm2, arm1_perp), 0, 0);
        let arm2_perp_b: vec4 = vec4(-arm2_b.y, arm2_b.x, 0, 0);
        // In basis b with vector as the origin
        let intersection_point_b: vec4 = vec4(
            abs(sdf1),
            select(-arm2_b.x / arm2_b.y * (abs(sdf1) - abs(sdf2) * arm2_b.x) + arm2_b.y * abs(sdf2), 0, arm2_b.y == 0),
            0, 0
        );
        let sdf_sign: f32 = sign(sdf1) * sign(sdf2);
        let midline_dir_b: vec4 = normalize(t * arm2_perp_b + sdf_sign * (1-t) * vec4(0, 1, 0, 0));
        // In basis b with intersection_point as the origin
        let vector_b: vec4 = -intersection_point_b;
        let offset_proj_b: vec4 = -dot(midline_dir_b, vector_b) * midline_dir_b;
        let proj_b: vec4 = offset_proj_b + vector_b;
        select(proj_b.x * arm1 + proj_b.y * arm1_perp + vector, vector, (length(vector - proj1) == 0) || (length(vector - proj2) == 0))
    },
}

RON Level Definitions

World(
    name: "Recommended stuff",
    description: "Stuff people wanted to see :)",
    levels: [
        Level(
            name: "Wormhole moving",
            description: "more desc >:D",
            scripts: ["worlds/test_world/script1.rhai"],
            manifold: Wormhole(
                shape: Shift(shift: Expr("vec4(20*sin((qe+2)*t/5), 0, 0, 0)"), shape: Sphere4D(radius: Expr("tg+20"))),
                offset: Expr("uj+10")
            ),
            spawn_pose: Pose(position: [0, 10, 25, 10]),
            geometries: [
                Geometry(
                    pose: Pose(position: [25, 10, 0, -10]),
                    shape: Sphere4D(radius: 3),
                    material: "basic",
                    color: "pink",
                    is_dynamic: true
                ),
                Geometry(
                    pose: Pose(position: [0, 37, 0, 0]),
                    shape: Invert(shape: Box4D(size: [50, 40, 50, 100])),
                    material: "basic",
                ),
            ]
        ),
        Level(
            name: "MobiusStrip to show off map",
            description: "more desc >:D",
            scripts: ["worlds/test_world/script1.rhai"],
            manifold: MobiusStrip(
                radius: Expr("qe+60"),
                width: Expr("tg+35"),
                turns: Expr("floor(uj+0.5)/2"),
                turn_length: Expr("ik+6.28")
            ),
            spawn_pose: Pose(position: [30, 5, 0, 0]),
            geometries: [
                Geometry(
                    pose: Pose(position: [0.0, -3.0, 0.0, 0.0]),
                    shape: Box4D(size: [100, 3, 100, 100]),
                    material: "basic",
                    color: "gray",
                    classes: ["move_xyzw"]
                ),
                Geometry(
                    pose: Pose(plane: "yz", angle_deg: 90),
                    shape: Invert(shape: Spheritorus(r01: Expr("qe+60"), r0123: Expr("(tg+35)/2-1.0"))),
                    material: "basic",
                    color: "blue"
                ),
                Geometry(
                    pose: Pose(position: [-30, 5, 0, 0]),
                    shape: Sphere4D(radius: 3),
                    material: "basic",
                    color: "pink",
                    is_dynamic: true
                ),
            ]
        ),
        Level(
            name: "Rounded DuoCylinder :)",
            description: "more desc >:D muahahaha",
            scripts: ["worlds/recommends_world/script1.rhai"],
            manifold: Shell(offset: 5, shape: DuoCylinder(r01: 25, r23: 25)),
            spawn_pose: Pose(position: [50, 5, 40, -30]),
            geometries: [
                Geometry(
                    pose: Pose(position: [0.0, -3.0, -100.0, 0.0]),
                    shape: Box4D(size: [100, 3, 100, 100]),
                    material: "basic",
                    color: "red",
                    classes: ["move_xyzw"]
                ),
                Geometry(
                    pose: Pose(position: [0.0, -3.0, 100.0, 0.0]),
                    shape: Box4D(size: [100, 3, 100, 100]),
                    material: "basic",
                    color: "blue",
                    classes: ["move_xyzw"]
                ),
                Geometry(
                    pose: Pose(position: [40, 2, -40, 0]),
                    shape: Sphere4D(radius: 2),
                    material: "basic",
                    color: "pink",
                    is_dynamic: true
                ),
            ]
        ),
        Level(
            name: "Rounded DuoSpindle for comparison :)",
            description: "more desc >:D muahahaha",
            scripts: ["worlds/recommends_world/script1.rhai"],
            manifold: Shell(offset: 5, shape: DuoSpindle(r: 25)),
            spawn_pose: Pose(position: [50, 5, 40, -30]),
            geometries: [
                Geometry(
                    pose: Pose(position: [0.0, -3.0, -100.0, 0.0]),
                    shape: Box4D(size: [100, 3, 100, 100]),
                    material: "basic",
                    color: "red",
                    classes: ["move_xyzw"]
                ),
                Geometry(
                    pose: Pose(position: [0.0, -3.0, 100.0, 0.0]),
                    shape: Box4D(size: [100, 3, 100, 100]),
                    material: "basic",
                    color: "blue",
                    classes: ["move_xyzw"]
                ),
                Geometry(
                    pose: Pose(position: [40, 2, -40, 0]),
                    shape: Sphere4D(radius: 2),
                    material: "basic",
                    color: "pink",
                    is_dynamic: true
                ),
            ]
        ),
        Level(
            name: "DuoCylinder with slanted floor",
            description: "more desc >:D muahahaha",
            scripts: ["worlds/recommends_world/script1.rhai"],
            manifold: Shell(offset: Expr("ik+10"), shape: DuoCylinder(r01: Expr("qe+40"), r23: Expr("tg+40"))),
            spawn_pose: Pose(position: [50, 5, 50, 0]),
            gravity: (-35.0, -35.0, -35.0, -35.0),
            geometries: [
                Geometry(
                    pose: Pose(v1: [0.0, 1.0, 0.0, 0.0], v2: [0.5, 0.5, 0.5, 0.5], position: [0.0, 0.0, 0.0, 0.0]),
                    shape: Box4D(size: [200, 3, 200, 200]),
                    material: "basic",
                    color: "blue",
                    classes: ["move_xyzw"]
                ),
                Geometry(
                    pose: Pose(position: [40, 2, -40, 0]),
                    shape: Sphere4D(radius: 2),
                    material: "basic",
                    color: "pink",
                    is_dynamic: true
                ),
                Geometry(
                    pose: Pose(position: [40, 6, 0, 40]),
                    shape: Sphere4D(radius: 2),
                    material: "basic",
                    color: "red",
                    is_dynamic: true
                ),
            ]
        ),
        Level(
            name: "Interpolation - Sphere4D <-> Rounded Box",
            description: "more desc >:D muahahaha",
            scripts: ["worlds/recommends_world/script1.rhai"],
            manifold: Interpolate2(
                shape1: Sphere4D(radius: 50),
                shape2: Rounding(radius: 20, shape: Box4D(size: [30, 30, 30, 30])),
                t: Expr("clamp(qe/5, 0, 1)")
            ),
            spawn_pose: Pose(position: [0, 5, 20, 34]),
            geometries: [
                Geometry(
                    pose: Pose(position: [0.0, -3.0, 0.0, 0.0]),
                    shape: Box4D(size: [100, 3, 100, 100]),
                    material: "basic",
                    color: "blue",
                    classes: ["move_xyzw"]
                ),
                Geometry(
                    pose: Pose(position: [-34, 2, -20, 0]),
                    shape: Sphere4D(radius: 2),
                    material: "basic",
                    color: "pink",
                    is_dynamic: true
                ),
            ]
        ),
        Level(
            name: "Interpolation - Torisphere <-> Rounded Box",
            description: "more desc >:D muahahaha",
            scripts: ["worlds/recommends_world/script1.rhai"],
            manifold: Interpolate2(
                shape1: Torisphere(r012: 50, r0123: 20),
                shape2: Rounding(radius: 20, shape: Box4D(size: [30, 30, 30, 10])),
                t: Expr("clamp(qe/5, 0, 1)")
            ),
            spawn_pose: Pose(position: [0, 5, 20, 34]),
            geometries: [
                Geometry(
                    pose: Pose(position: [0.0, -3.0, 0.0, 0.0]),
                    shape: Box4D(size: [100, 3, 100, 100]),
                    material: "basic",
                    color: "blue",
                    classes: ["move_xyzw"]
                ),
                Geometry(
                    pose: Pose(position: [-34, 2, -20, 0]),
                    shape: Sphere4D(radius: 2),
                    material: "basic",
                    color: "pink",
                    is_dynamic: true
                ),
            ]
        ),
        Level(
            name: "Interpolation - Spheritorus <-> Plane4D",
            description: "more desc >:D muahahaha",
            scripts: ["worlds/recommends_world/script1.rhai"],
            manifold: Interpolate2(
                shape1: Spheritorus(r01: 50, r0123: 20),
                shape2: Plane4D(normal: [0, 0, 0, 1]),
                t: Expr("clamp(qe/10, 0, 1)")
            ),
            gravity: (0.0, 0.0, -70.0, 0.0),
            spawn_pose: Pose(position: [0, 5, 20, 34]),
            geometries: [
                Geometry(
                    pose: Pose(position: [0.0, -100.0, -3.0, 0.0]),
                    shape: Box4D(size: [100, 100, 3, 100]),
                    material: "basic",
                    color: "blue",
                    classes: ["move_xyzw"]
                ),
                Geometry(
                    pose: Pose(position: [0.0, 100.0, -3.0, 0.0]),
                    shape: Box4D(size: [100, 100, 3, 100]),
                    material: "basic",
                    color: "red",
                    classes: ["move_xyzw"]
                ),
                Geometry(
                    pose: Pose(position: [-34, 2, 20, 0]),
                    shape: Sphere4D(radius: 2),
                    material: "basic",
                    color: "pink",
                    is_dynamic: true
                ),
            ]
        ),
    ],
)

Get Arbgeom beta

Comments

Log in with itch.io to leave a comment.

thats odd, the download button is gone!

ooooh! i REALLY want to play around with that! maybe release the most stable build? like the january one?

I uploaded the version I had in this video to the google drive here: https://drive.google.com/drive/folders/14UXYHcIoFhr9kYHzdlgCnf-XlHYUd3h5?usp=sha... .  I also added some instructions for it to the top of this post, enjoy!  : )

Works great! the map crashes and for some reason the font sizes are too big on linux but it runs very smoothly!