use alice_sdf::SdfNode;
use glam::Vec3;
use std::sync::Arc;
pub const PEG_BLADE_W: f32 = 5.0;
pub const PEG_BLADE_T: f32 = 4.5;
pub const BOARD_T: f32 = 5.0;
pub const SHOULDER_DEPTH: f32 = 2.0;
pub const SHOULDER_H: f32 = 8.0;
#[must_use]
pub fn skadis_peg_and_shoulder(hook_width: f32) -> SdfNode {
let blade = SdfNode::Box3d {
half_extents: Vec3::new(BOARD_T * 0.5, PEG_BLADE_T * 0.5, hook_width * 0.5),
};
let blade_placed = SdfNode::Translate {
child: Arc::new(blade),
offset: Vec3::new(-BOARD_T * 0.5, 0.0, 0.0),
};
let shoulder = SdfNode::Box3d {
half_extents: Vec3::new(SHOULDER_DEPTH * 0.5, SHOULDER_H * 0.5, hook_width * 0.5),
};
let shoulder_placed = SdfNode::Translate {
child: Arc::new(shoulder),
offset: Vec3::new(-BOARD_T - SHOULDER_DEPTH * 0.5, 0.0, 0.0),
};
SdfNode::Union {
a: Arc::new(blade_placed),
b: Arc::new(shoulder_placed),
}
}
#[must_use]
pub fn capsule_polyline_sdf(pts: &[glam::Vec2], tube_radius: f32, hook_width: f32) -> SdfNode {
if pts.len() < 2 {
return SdfNode::Sphere { radius: 0.0 };
}
let capsules: Vec<SdfNode> = pts
.windows(2)
.map(|pair| SdfNode::Capsule {
point_a: Vec3::new(pair[0].x, pair[0].y, 0.0),
point_b: Vec3::new(pair[1].x, pair[1].y, 0.0),
radius: tube_radius,
})
.collect();
let curve = super::balanced_union_fold(capsules).unwrap_or(SdfNode::Sphere { radius: 0.0 });
let z_clip = SdfNode::Box3d {
half_extents: Vec3::new(1000.0, 1000.0, hook_width * 0.5),
};
SdfNode::Intersection {
a: Arc::new(curve),
b: Arc::new(z_clip),
}
}
#[must_use]
pub fn skadis_hook_l_sdf() -> SdfNode {
let reach: f32 = 75.0;
let root_t: f32 = 7.0;
let hook_width: f32 = 8.0; let radius = root_t * 0.5;
let mut pts: Vec<glam::Vec2> =
vec![glam::Vec2::new(0.0, 0.0), glam::Vec2::new(reach - 8.0, 0.0)];
let n = 12;
for i in 1..=n {
#[allow(clippy::cast_precision_loss)]
let a = std::f32::consts::FRAC_PI_2 * i as f32 / n as f32;
pts.push(glam::Vec2::new(
reach - 8.0 + 8.0 * a.sin(),
8.0 * (1.0 - a.cos()),
));
}
let peg_shoulder = skadis_peg_and_shoulder(hook_width);
let body = capsule_polyline_sdf(&pts, radius, hook_width);
SdfNode::Union {
a: Arc::new(peg_shoulder),
b: Arc::new(body),
}
}
#[must_use]
pub fn skadis_hook_j_sdf() -> SdfNode {
let reach: f32 = 25.0;
let drop: f32 = 70.0;
let root_t: f32 = 7.5;
let hook_width: f32 = 8.0;
let radius = root_t * 0.5;
let mut pts: Vec<glam::Vec2> = vec![glam::Vec2::new(0.0, 0.0)];
let n = 32;
for i in 0..=n {
#[allow(clippy::cast_precision_loss)]
let a = std::f32::consts::FRAC_PI_2 * i as f32 / n as f32;
pts.push(glam::Vec2::new(reach * a.sin(), -reach * (1.0 - a.cos())));
}
let curve_end_y = pts.last().map_or(0.0, |v| v.y);
let tip_y = curve_end_y - (drop - reach);
pts.push(glam::Vec2::new(reach, tip_y));
let tip_r: f32 = 8.0;
for i in 0..=n {
#[allow(clippy::cast_precision_loss)]
let a = -std::f32::consts::FRAC_PI_2 + std::f32::consts::PI * 0.75 * i as f32 / n as f32;
pts.push(glam::Vec2::new(
reach - tip_r + tip_r * a.cos(),
tip_y + tip_r * a.sin(),
));
}
let peg_shoulder = skadis_peg_and_shoulder(hook_width);
let body = capsule_polyline_sdf(&pts, radius, hook_width);
SdfNode::Union {
a: Arc::new(peg_shoulder),
b: Arc::new(body),
}
}
#[must_use]
pub fn skadis_hook_s_sdf() -> SdfNode {
let reach: f32 = 22.0;
let drop: f32 = 45.0;
let root_t: f32 = 5.5;
let hook_width: f32 = 5.0; let radius = root_t * 0.5;
let mut pts: Vec<glam::Vec2> = vec![glam::Vec2::new(0.0, 0.0)];
let n = 16;
for i in 0..=n {
#[allow(clippy::cast_precision_loss)]
let a = std::f32::consts::FRAC_PI_2 * i as f32 / n as f32;
pts.push(glam::Vec2::new(reach * a.sin(), -reach * (1.0 - a.cos())));
}
let curve_end_y = pts.last().map_or(0.0, |v| v.y);
let tip_base_y = curve_end_y - (drop - reach);
pts.push(glam::Vec2::new(reach, tip_base_y));
let tip_r: f32 = 6.0;
for i in 0..=n {
#[allow(clippy::cast_precision_loss)]
let a = -std::f32::consts::FRAC_PI_2 + std::f32::consts::PI * 0.7 * i as f32 / n as f32;
pts.push(glam::Vec2::new(
reach - tip_r + tip_r * a.cos(),
tip_base_y + tip_r * a.sin(),
));
}
let peg_shoulder = skadis_peg_and_shoulder(hook_width);
let body = capsule_polyline_sdf(&pts, radius, hook_width);
SdfNode::Union {
a: Arc::new(peg_shoulder),
b: Arc::new(body),
}
}
pub const SKADIS_PEG_W: f32 = 5.0;
pub const SKADIS_PEG_H: f32 = 15.0;
pub const SKADIS_GRID_PITCH: f32 = 40.0;
pub const SKADIS_GRID_OFFSET: f32 = 20.0;
pub const SKADIS_EDGE_MARGIN: f32 = 20.0;
pub const SKADIS_PANEL_THICKNESS: f32 = 5.0;
pub const HOLE_THROUGH_MARGIN: f32 = 0.5;
pub const SKADIS_CONN_SCREW_D: f32 = 2.7;
pub const SKADIS_CONN_INSET: f32 = 6.0;
pub const SKADIS_MOUNT_HOLE_R: f32 = 2.5;
pub const SKADIS_OUTER_FRAME: f32 = 12.0;
#[must_use]
pub fn skadis_panel_sdf(size: f32, thickness: f32, corner_radius: f32) -> SdfNode {
let panel_infl = SdfNode::RoundedBox {
half_extents: Vec3::new(size * 0.5, thickness * 0.5, size * 0.5),
round_radius: corner_radius,
};
let y_cutter = SdfNode::Box3d {
half_extents: Vec3::new(
size * 0.5 + corner_radius + 1.0, thickness * 0.5, size * 0.5 + corner_radius + 1.0,
),
};
let panel = SdfNode::Intersection {
a: Arc::new(panel_infl),
b: Arc::new(y_cutter),
};
let t_pass = thickness + 2.0 * HOLE_THROUGH_MARGIN;
let stadium_middle_z = (SKADIS_PEG_H - SKADIS_PEG_W) * 0.5; let peg_middle = SdfNode::Box3d {
half_extents: Vec3::new(SKADIS_PEG_W * 0.5, t_pass * 0.5, stadium_middle_z),
};
let peg_end_cyl = SdfNode::Cylinder {
radius: SKADIS_PEG_W * 0.5, half_height: t_pass * 0.5,
};
let peg_end_top = SdfNode::Translate {
child: Arc::new(peg_end_cyl.clone()),
offset: Vec3::new(0.0, 0.0, stadium_middle_z),
};
let peg_end_bot = SdfNode::Translate {
child: Arc::new(peg_end_cyl),
offset: Vec3::new(0.0, 0.0, -stadium_middle_z),
};
let peg_hole = SdfNode::Union {
a: Arc::new(SdfNode::Union {
a: Arc::new(peg_middle),
b: Arc::new(peg_end_top),
}),
b: Arc::new(peg_end_bot),
};
let usable = size - 2.0 * SKADIS_EDGE_MARGIN;
#[allow(clippy::cast_possible_truncation)]
let count = ((usable * 0.5) / SKADIS_GRID_PITCH).floor() as i32;
let mut hole_list: Vec<SdfNode> = Vec::new();
for (grid_x, grid_z) in [(0.0, 0.0), (SKADIS_GRID_OFFSET, SKADIS_GRID_OFFSET)] {
for ix in -count..=count {
for iz in -count..=count {
#[allow(clippy::cast_precision_loss)]
let cx = ix as f32 * SKADIS_GRID_PITCH + grid_x;
#[allow(clippy::cast_precision_loss)]
let cz = iz as f32 * SKADIS_GRID_PITCH + grid_z;
hole_list.push(SdfNode::Translate {
child: Arc::new(peg_hole.clone()),
offset: Vec3::new(cx, 0.0, cz),
});
}
}
}
let conn_cyl = SdfNode::Cylinder {
radius: SKADIS_CONN_SCREW_D * 0.5, half_height: t_pass * 0.5,
};
let half_size = size * 0.5;
let conn_inset_from_center = half_size - SKADIS_CONN_INSET;
let mut i = 1i32;
loop {
#[allow(clippy::cast_precision_loss)]
let pos = i as f32 * SKADIS_GRID_PITCH;
if pos >= size {
break;
}
let center_shift = pos - half_size;
hole_list.push(SdfNode::Translate {
child: Arc::new(conn_cyl.clone()),
offset: Vec3::new(center_shift, 0.0, -conn_inset_from_center),
});
hole_list.push(SdfNode::Translate {
child: Arc::new(conn_cyl.clone()),
offset: Vec3::new(center_shift, 0.0, conn_inset_from_center),
});
hole_list.push(SdfNode::Translate {
child: Arc::new(conn_cyl.clone()),
offset: Vec3::new(-conn_inset_from_center, 0.0, center_shift),
});
hole_list.push(SdfNode::Translate {
child: Arc::new(conn_cyl.clone()),
offset: Vec3::new(conn_inset_from_center, 0.0, center_shift),
});
i += 1;
}
for &(sx, sz) in &[
(-conn_inset_from_center, -conn_inset_from_center),
(conn_inset_from_center, -conn_inset_from_center),
(-conn_inset_from_center, conn_inset_from_center),
(conn_inset_from_center, conn_inset_from_center),
] {
hole_list.push(SdfNode::Translate {
child: Arc::new(conn_cyl.clone()),
offset: Vec3::new(sx, 0.0, sz),
});
}
let mount_cyl = SdfNode::Cylinder {
radius: SKADIS_MOUNT_HOLE_R, half_height: t_pass * 0.5,
};
let mount_x_positions = [60.0, 150.0, 220.0];
for &x in &mount_x_positions {
let cx = x - half_size;
hole_list.push(SdfNode::Translate {
child: Arc::new(mount_cyl.clone()),
offset: Vec3::new(cx, 0.0, -conn_inset_from_center),
});
hole_list.push(SdfNode::Translate {
child: Arc::new(mount_cyl.clone()),
offset: Vec3::new(cx, 0.0, conn_inset_from_center),
});
}
let all_holes = super::balanced_union_fold(hole_list);
match all_holes {
Some(holes) => SdfNode::Subtraction {
a: Arc::new(panel),
b: Arc::new(holes),
},
None => panel,
}
}
#[cfg(test)]
mod tests {
use super::*;
use alice_sdf::eval;
#[test]
fn skadis_panel_sdf_returns_subtraction() {
let panel = skadis_panel_sdf(300.0, 5.0, 5.0);
assert!(matches!(panel, SdfNode::Subtraction { .. }));
}
#[test]
fn skadis_panel_material_region_is_inside() {
let panel = skadis_panel_sdf(300.0, 5.0, 5.0);
assert!(eval(&panel, Vec3::new(10.0, 0.0, 10.0)) < 0.0);
}
#[test]
fn skadis_panel_peg_center_is_hole() {
let panel = skadis_panel_sdf(300.0, 5.0, 5.0);
assert!(eval(&panel, Vec3::ZERO) > 0.0);
}
#[test]
fn skadis_panel_stagger_peg_position() {
let panel = skadis_panel_sdf(300.0, 5.0, 5.0);
assert!(eval(&panel, Vec3::new(20.0, 0.0, 20.0)) > 0.0);
}
#[test]
fn skadis_panel_outside_boundary() {
let panel = skadis_panel_sdf(300.0, 5.0, 5.0);
assert!(eval(&panel, Vec3::new(200.0, 0.0, 0.0)) > 0.0);
}
#[test]
fn skadis_panel_y_thickness_bounded_at_thickness_half() {
let panel = skadis_panel_sdf(300.0, 5.0, 6.0);
assert!(
eval(&panel, Vec3::new(10.0, 3.0, 10.0)) > 0.0,
"Y=3mm は thickness/2=2.5mm を超えるので空間になるはず"
);
assert!(
eval(&panel, Vec3::new(10.0, 2.0, 10.0)) < 0.0,
"Y=2mm + XZ=(10,10) は material 内部になるはず"
);
}
#[test]
fn skadis_panel_xz_corner_fillet_preserved() {
let panel = skadis_panel_sdf(300.0, 5.0, 6.0);
assert!(
eval(&panel, Vec3::new(156.0, 0.0, 156.0)) > 0.0,
"コーナー (156, 0, 156) は fillet で切り取られるので空間になるはず"
);
assert!(
eval(&panel, Vec3::new(110.0, 0.0, 110.0)) < 0.0,
"(110, 0, 110) は grid 外の material 内部になるはず"
);
}
#[test]
fn skadis_panel_connector_holes_are_present() {
let panel = skadis_panel_sdf(300.0, 5.0, 6.0);
for &(sx, sz) in &[
(-144.0f32, -144.0),
(144.0, -144.0),
(-144.0, 144.0),
(144.0, 144.0),
] {
assert!(
eval(&panel, Vec3::new(sx, 0.0, sz)) > 0.0,
"コーナー connector 穴 ({sx}, 0, {sz}) は空間になるはず"
);
}
for &cx in &[-110.0f32, 10.0, 130.0] {
assert!(
eval(&panel, Vec3::new(cx, 0.0, -144.0)) > 0.0,
"上辺 connector 穴 ({cx}, 0, -144) は空間になるはず"
);
}
}
#[test]
fn skadis_panel_mount_holes_are_present() {
let panel = skadis_panel_sdf(300.0, 5.0, 6.0);
for &cx in &[-90.0f32, 0.0, 70.0] {
assert!(
eval(&panel, Vec3::new(cx, 0.0, -144.0)) > 0.0,
"上辺 mount 穴 ({cx}, 0, -144) は空間になるはず"
);
assert!(
eval(&panel, Vec3::new(cx, 0.0, 144.0)) > 0.0,
"下辺 mount 穴 ({cx}, 0, 144) は空間になるはず"
);
}
}
#[test]
fn skadis_panel_frame_zone_material_between_holes() {
let panel = skadis_panel_sdf(300.0, 5.0, 6.0);
assert!(
eval(&panel, Vec3::new(-140.0, 0.0, -140.0)) < 0.0,
"frame 内 穴なし位置 (-140, 0, -140) は material 内部になるはず"
);
}
#[test]
fn skadis_panel_peg_hole_stadium_shape() {
let panel = skadis_panel_sdf(300.0, 5.0, 6.0);
assert!(
eval(&panel, Vec3::new(0.0, 0.0, 6.0)) > 0.0,
"(0, 0, 6.0) は stadium 半円 end 内なので穴になるはず"
);
assert!(
eval(&panel, Vec3::new(0.0, 0.0, 8.0)) < 0.0,
"(0, 0, 8.0) は stadium 外側で material 内部になるはず"
);
assert!(
eval(&panel, Vec3::new(3.0, 0.0, 0.0)) < 0.0,
"(3, 0, 0) は stadium X 幅 (±2.5) の外なので material 内部になるはず"
);
}
#[test]
fn skadis_peg_and_shoulder_returns_union() {
let ps = skadis_peg_and_shoulder(8.0);
assert!(matches!(ps, SdfNode::Union { .. }));
}
#[test]
fn capsule_polyline_short_input_returns_sphere() {
let empty: Vec<glam::Vec2> = vec![glam::Vec2::ZERO];
let s = capsule_polyline_sdf(&empty, 1.0, 5.0);
assert!(matches!(s, SdfNode::Sphere { .. }));
}
#[test]
fn skadis_hook_l_body_returns_union() {
let h = skadis_hook_l_sdf();
assert!(matches!(h, SdfNode::Union { .. }));
}
#[test]
fn skadis_hook_j_and_s_return_union() {
let j = skadis_hook_j_sdf();
let s = skadis_hook_s_sdf();
assert!(matches!(j, SdfNode::Union { .. }));
assert!(matches!(s, SdfNode::Union { .. }));
}
#[test]
fn hook_peg_area_is_inside_material() {
for hook in [
skadis_hook_l_sdf(),
skadis_hook_j_sdf(),
skadis_hook_s_sdf(),
] {
assert!(eval(&hook, Vec3::new(-2.5, 0.0, 0.0)) < 0.0);
}
}
#[test]
fn container_returns_union() {
let c = skadis_container_sdf();
assert!(matches!(c, SdfNode::Union { .. }));
}
#[test]
fn clip_returns_union() {
let c = skadis_clip_sdf();
assert!(matches!(c, SdfNode::Union { .. }));
}
#[test]
fn shelf_returns_union() {
let s = skadis_shelf_sdf();
assert!(matches!(s, SdfNode::Union { .. }));
}
#[test]
fn elastic_cord_returns_union() {
let e = skadis_elastic_cord_sdf();
assert!(matches!(e, SdfNode::Union { .. }));
}
#[test]
fn all_phase_5_2_accessories_produce_finite_sdf() {
let nodes = [
skadis_container_sdf(),
skadis_clip_sdf(),
skadis_shelf_sdf(),
skadis_elastic_cord_sdf(),
];
for (i, n) in nodes.iter().enumerate() {
let d = eval(n, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "accessory {i}: non-finite SDF");
}
}
}
pub const CONTAINER_W: f32 = 65.0;
pub const CONTAINER_D: f32 = 75.0;
pub const CONTAINER_H: f32 = 70.0;
pub const CONTAINER_WALL_T: f32 = 1.6;
pub const CONTAINER_BOTTOM_T: f32 = 1.6;
#[must_use]
pub fn skadis_container_sdf() -> SdfNode {
let outer_w = CONTAINER_W + 2.0 * CONTAINER_WALL_T;
let outer_d = CONTAINER_D + 2.0 * CONTAINER_WALL_T;
let total_h = CONTAINER_H + CONTAINER_BOTTOM_T;
let outer = SdfNode::Box3d {
half_extents: Vec3::new(outer_w * 0.5, total_h * 0.5, outer_d * 0.5),
};
let inner = SdfNode::Box3d {
half_extents: Vec3::new(
CONTAINER_W * 0.5,
(CONTAINER_H + 1.0) * 0.5, CONTAINER_D * 0.5,
),
};
let inner_placed = SdfNode::Translate {
child: Arc::new(inner),
offset: Vec3::new(
0.0,
CONTAINER_BOTTOM_T + CONTAINER_H * 0.5 - total_h * 0.5,
0.0,
),
};
let hollow = SdfNode::Subtraction {
a: Arc::new(outer),
b: Arc::new(inner_placed),
};
let peg = skadis_peg_and_shoulder(PEG_BLADE_W);
let peg_placed = SdfNode::Translate {
child: Arc::new(peg),
offset: Vec3::new(0.0, total_h * 0.35, -outer_d * 0.5),
};
SdfNode::Union {
a: Arc::new(hollow),
b: Arc::new(peg_placed),
}
}
pub const CLIP_WIDTH: f32 = 15.0;
pub const CLIP_LENGTH: f32 = 55.0;
pub const CLIP_BODY_T: f32 = 3.0;
pub const CLIP_SLOT_W: f32 = 1.2;
#[must_use]
pub fn skadis_clip_sdf() -> SdfNode {
let gap = CLIP_SLOT_W * 0.5;
let root = SdfNode::Box3d {
half_extents: Vec3::new((gap + CLIP_BODY_T) * 0.5, 4.0, CLIP_WIDTH * 0.5),
};
let root_placed = SdfNode::Translate {
child: Arc::new(root),
offset: Vec3::new(gap * 0.5, PEG_BLADE_T * 0.25, 0.0),
};
let front = SdfNode::Box3d {
half_extents: Vec3::new(CLIP_BODY_T * 0.5, CLIP_LENGTH * 0.5, CLIP_WIDTH * 0.5),
};
let front_placed = SdfNode::Translate {
child: Arc::new(front),
offset: Vec3::new(gap + CLIP_BODY_T * 0.5, -CLIP_LENGTH * 0.5, 0.0),
};
let back = SdfNode::Box3d {
half_extents: Vec3::new(CLIP_BODY_T * 0.5, CLIP_LENGTH * 0.5, CLIP_WIDTH * 0.5),
};
let back_placed = SdfNode::Translate {
child: Arc::new(back),
offset: Vec3::new(-gap - CLIP_BODY_T * 0.5, -CLIP_LENGTH * 0.5, 0.0),
};
let tip = SdfNode::Box3d {
half_extents: Vec3::new(0.4, 2.5, CLIP_WIDTH * 0.5),
};
let tip_placed = SdfNode::Translate {
child: Arc::new(tip),
offset: Vec3::new(gap + 0.1, -CLIP_LENGTH + 2.5, 0.0),
};
let peg = skadis_peg_and_shoulder(CLIP_WIDTH);
let body_upper = SdfNode::Union {
a: Arc::new(root_placed),
b: Arc::new(peg),
};
let body_front = SdfNode::Union {
a: Arc::new(body_upper),
b: Arc::new(front_placed),
};
let body_back = SdfNode::Union {
a: Arc::new(body_front),
b: Arc::new(back_placed),
};
SdfNode::Union {
a: Arc::new(body_back),
b: Arc::new(tip_placed),
}
}
pub const SHELF_W: f32 = 260.0;
pub const SHELF_D: f32 = 80.0;
pub const SHELF_LIP_H: f32 = 20.0;
pub const SHELF_BACK_H: f32 = 25.0;
pub const SHELF_BOTTOM_T: f32 = 2.0;
pub const SHELF_PEG_SPACING: f32 = 240.0;
#[must_use]
pub fn skadis_shelf_sdf() -> SdfNode {
let bottom = SdfNode::Box3d {
half_extents: Vec3::new(SHELF_W * 0.5, SHELF_BOTTOM_T * 0.5, SHELF_D * 0.5),
};
let back = SdfNode::Box3d {
half_extents: Vec3::new(SHELF_W * 0.5, SHELF_BACK_H * 0.5, 1.6 * 0.5),
};
let back_placed = SdfNode::Translate {
child: Arc::new(back),
offset: Vec3::new(0.0, SHELF_BACK_H * 0.5, -SHELF_D * 0.5 + 0.8),
};
let lip = SdfNode::Box3d {
half_extents: Vec3::new(SHELF_W * 0.5, SHELF_LIP_H * 0.5, 1.6 * 0.5),
};
let lip_placed = SdfNode::Translate {
child: Arc::new(lip),
offset: Vec3::new(0.0, SHELF_LIP_H * 0.5, SHELF_D * 0.5 - 0.8),
};
let peg = skadis_peg_and_shoulder(PEG_BLADE_W);
let peg_l = SdfNode::Translate {
child: Arc::new(peg.clone()),
offset: Vec3::new(-SHELF_PEG_SPACING * 0.5, SHELF_BACK_H * 0.7, -SHELF_D * 0.5),
};
let peg_r = SdfNode::Translate {
child: Arc::new(peg),
offset: Vec3::new(SHELF_PEG_SPACING * 0.5, SHELF_BACK_H * 0.7, -SHELF_D * 0.5),
};
let step1 = SdfNode::Union {
a: Arc::new(bottom),
b: Arc::new(back_placed),
};
let step2 = SdfNode::Union {
a: Arc::new(step1),
b: Arc::new(lip_placed),
};
let step3 = SdfNode::Union {
a: Arc::new(step2),
b: Arc::new(peg_l),
};
SdfNode::Union {
a: Arc::new(step3),
b: Arc::new(peg_r),
}
}
pub const ELASTIC_CORD_BODY_T: f32 = 3.0;
pub const ELASTIC_CORD_PEG_PITCH: f32 = 40.0;
pub const ELASTIC_CORD_HOOK_REACH: f32 = 12.0;
pub const ELASTIC_CORD_HOOK_R: f32 = 3.0;
#[must_use]
pub fn skadis_elastic_cord_sdf() -> SdfNode {
let ht = ELASTIC_CORD_BODY_T * 0.5;
let hook_width = 8.0;
let peg_top = skadis_peg_and_shoulder(hook_width);
let peg_bot = SdfNode::Translate {
child: Arc::new(skadis_peg_and_shoulder(hook_width)),
offset: Vec3::new(0.0, -ELASTIC_CORD_PEG_PITCH, 0.0),
};
let spine = SdfNode::Box3d {
half_extents: Vec3::new(ht * 0.5, (ELASTIC_CORD_PEG_PITCH + 8.0) * 0.5, 4.0),
};
let spine_placed = SdfNode::Translate {
child: Arc::new(spine),
offset: Vec3::new(ht * 0.5, -ELASTIC_CORD_PEG_PITCH * 0.5, 0.0),
};
let mut top_pts: Vec<glam::Vec2> = vec![glam::Vec2::new(ht, -8.0)];
let n = 12;
for i in 1..=n {
#[allow(clippy::cast_precision_loss)]
let a = std::f32::consts::FRAC_PI_2 * i as f32 / n as f32;
top_pts.push(glam::Vec2::new(
ht + (ELASTIC_CORD_HOOK_REACH - ELASTIC_CORD_HOOK_R) * a.sin(),
-8.0 - ELASTIC_CORD_HOOK_R * (1.0 - a.cos()),
));
}
let top_hook = capsule_polyline_sdf(&top_pts, ht, hook_width);
let mut bot_pts: Vec<glam::Vec2> = vec![glam::Vec2::new(ht, -ELASTIC_CORD_PEG_PITCH + 8.0)];
for i in 1..=n {
#[allow(clippy::cast_precision_loss)]
let a = std::f32::consts::FRAC_PI_2 * i as f32 / n as f32;
bot_pts.push(glam::Vec2::new(
ht + (ELASTIC_CORD_HOOK_REACH - ELASTIC_CORD_HOOK_R) * a.sin(),
-ELASTIC_CORD_PEG_PITCH + 8.0 + ELASTIC_CORD_HOOK_R * (1.0 - a.cos()),
));
}
let bot_hook = capsule_polyline_sdf(&bot_pts, ht, hook_width);
let step1 = SdfNode::Union {
a: Arc::new(peg_top),
b: Arc::new(peg_bot),
};
let step2 = SdfNode::Union {
a: Arc::new(step1),
b: Arc::new(spine_placed),
};
let step3 = SdfNode::Union {
a: Arc::new(step2),
b: Arc::new(top_hook),
};
SdfNode::Union {
a: Arc::new(step3),
b: Arc::new(bot_hook),
}
}