use super::orient_to_rings;
use molgfx_math::{CurveSample, TransportFrame, Vec3};
const EPSILON: f32 = 1.0e-5;
fn sample(tangent: Vec3, segment: u32, parameter: f32) -> CurveSample {
CurveSample {
position: Vec3::ZERO,
tangent,
segment,
parameter,
}
}
fn transported(tangent: Vec3) -> TransportFrame {
let normal = tangent.any_orthonormal_vector();
TransportFrame {
tangent,
normal,
binormal: tangent.cross(normal),
}
}
fn assert_axis(actual: Vec3, expected: Vec3, what: &str) {
assert!(
(actual - expected).length() < EPSILON,
"{what}: expected {expected:?}, got {actual:?}"
);
}
#[test]
fn a_ring_plane_across_the_curve_becomes_the_ribbon_thickness_axis() {
let samples = [sample(Vec3::X, 0, 0.0)];
let mut frames = [transported(Vec3::X)];
orient_to_rings(&samples, &[Vec3::Z, Vec3::Z], &mut frames);
let [frame] = frames;
assert_axis(frame.binormal, Vec3::Z, "thickness axis");
assert_axis(frame.normal, Vec3::Y, "width axis");
assert_axis(
frame.tangent.cross(frame.normal),
frame.binormal,
"handedness",
);
}
#[test]
fn the_ribbon_turns_halfway_between_two_rings_a_quarter_turn_apart() {
let samples = [sample(Vec3::X, 0, 0.5)];
let mut frames = [transported(Vec3::X)];
orient_to_rings(&samples, &[Vec3::Z, Vec3::Y], &mut frames);
let [frame] = frames;
let halfway = (Vec3::Z + Vec3::Y).normalize();
assert_axis(frame.binormal, halfway, "thickness axis at the midpoint");
}
#[test]
fn a_ring_plane_running_along_the_curve_leaves_the_transport_frame_alone() {
let samples = [sample(Vec3::X, 0, 0.0)];
let original = transported(Vec3::X);
let mut frames = [original];
orient_to_rings(&samples, &[Vec3::X, Vec3::X], &mut frames);
let [frame] = frames;
assert_axis(frame.normal, original.normal, "width axis");
assert_axis(frame.binormal, original.binormal, "thickness axis");
}
#[test]
fn a_trace_without_ring_planes_keeps_every_transport_frame() {
let samples = [sample(Vec3::X, 0, 0.0), sample(Vec3::Y, 1, 0.0)];
let original = [transported(Vec3::X), transported(Vec3::Y)];
let mut frames = original;
orient_to_rings(&samples, &[], &mut frames);
assert_eq!(frames, original);
}
#[test]
fn a_sample_past_the_last_ring_plane_holds_the_final_orientation() {
let samples = [sample(Vec3::X, 1, 0.75)];
let mut frames = [transported(Vec3::X)];
orient_to_rings(&samples, &[Vec3::Y, Vec3::Z], &mut frames);
let [frame] = frames;
assert_axis(
frame.binormal,
Vec3::Z,
"thickness axis at the trailing end",
);
}
#[test]
fn a_plane_blend_that_would_dive_into_the_tangent_still_turns_evenly() {
let start = Vec3::new(0.6, 0.0, 0.8).normalize();
let end = Vec3::new(0.6, 0.1, -0.79).normalize();
let steps = 24u8;
let mut previous: Option<Vec3> = None;
for step in 0..=steps {
let parameter = f32::from(step) / f32::from(steps);
let samples = [sample(Vec3::X, 0, parameter)];
let mut frames = [transported(Vec3::X)];
orient_to_rings(&samples, &[start, end], &mut frames);
let [frame] = frames;
if let Some(previous) = previous {
let turn = previous
.dot(frame.binormal)
.clamp(-1.0, 1.0)
.acos()
.to_degrees();
assert!(turn < 20.0, "frame jumped {turn} degrees in one sample");
}
previous = Some(frame.binormal);
}
}