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:
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:
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
Arbgeom beta
Curved space marble game
Status | In development |
Author | Tomwol |
Tags | bevy, marble-game, non-eucledian, non-euclidean |
More posts
- Scripting files for Arbgeom, August 13, 2024Aug 14, 2024
- Implementing 4D PGA into XPBDJan 20, 2024
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!