use super::ribbon::{PROFILE_SIDES, SplineProfile};
use molgfx_core::{SecondaryStructure, TubeRadiusMapping};
use molgfx_math::Rgba8;
pub(super) const PROFILE: [[f32; 2]; PROFILE_SIDES] = [
[1.0, 0.0],
[0.923_879_5, 0.382_683_43],
[0.707_106_77, 0.707_106_77],
[0.382_683_43, 0.923_879_5],
[0.0, 1.0],
[-0.382_683_43, 0.923_879_5],
[-0.707_106_77, 0.707_106_77],
[-0.923_879_5, 0.382_683_43],
[-1.0, 0.0],
[-0.923_879_5, -0.382_683_43],
[-0.707_106_77, -0.707_106_77],
[-0.382_683_43, -0.923_879_5],
[0.0, -1.0],
[0.382_683_43, -0.923_879_5],
[0.707_106_77, -0.707_106_77],
[0.923_879_5, -0.382_683_43],
];
pub(super) const BOX_PROFILE: [[f32; 4]; PROFILE_SIDES] = [
[1.0, 1.0, 0.0, 1.0],
[-1.0, 1.0, 0.0, 1.0],
[-1.0, 1.0, -1.0, 0.0],
[-1.0, -1.0, -1.0, 0.0],
[-1.0, 1.0, -1.0, 0.0],
[-1.0, -1.0, -1.0, 0.0],
[-1.0, -1.0, 0.0, -1.0],
[1.0, -1.0, 0.0, -1.0],
[-1.0, -1.0, 0.0, -1.0],
[1.0, -1.0, 0.0, -1.0],
[1.0, -1.0, 1.0, 0.0],
[1.0, 1.0, 1.0, 0.0],
[1.0, -1.0, 1.0, 0.0],
[1.0, 1.0, 1.0, 0.0],
[1.0, 1.0, 0.0, 1.0],
[1.0, 1.0, 0.0, 1.0],
];
#[inline]
pub(super) fn cross_section(
flat: bool,
side: usize,
width: f32,
thickness: f32,
) -> (f32, f32, [f32; 2]) {
let side = side % PROFILE_SIDES;
if flat {
let [x, y, normal_x, normal_y] = BOX_PROFILE[side];
return (x, y, [normal_x, normal_y]);
}
let [x, y] = PROFILE[side];
(x, y, [x * thickness, y * width])
}
#[inline]
pub(super) fn profile_color(profile: SplineProfile, thickness_axis: f32, base: Rgba8) -> Rgba8 {
if profile != SplineProfile::Twister {
return base;
}
if thickness_axis > 0.0 {
Rgba8::new(226, 232, 244, base.a)
} else {
Rgba8::new(28, 74, 168, base.a)
}
}
pub(super) const BOX_FACE_SIDES: [bool; PROFILE_SIDES] = [
true, false, false, false, true, false, false, false, true, false, false, false, true, false,
false, false,
];
pub(super) const BOX_CORNERS: [[f32; 2]; 4] = [[1.0, 1.0], [-1.0, 1.0], [-1.0, -1.0], [1.0, -1.0]];
#[inline]
fn finite_property(properties: &[f32], control: u32) -> Option<f32> {
let Ok(index) = usize::try_from(control) else {
return None;
};
let &value = properties.get(index)?;
value.is_finite().then_some(value)
}
#[must_use]
#[inline]
pub fn variable_tube_radius(
mapping: TubeRadiusMapping,
properties: &[f32],
controls: [u32; 2],
amount: f32,
fallback: f32,
) -> f32 {
let left = finite_property(properties, controls[0]);
let right = finite_property(properties, controls[1]);
let value = match (left, right) {
(Some(left), Some(right)) => left + (right - left) * amount,
(Some(value), None) | (None, Some(value)) => value,
(None, None) => return fallback,
};
mapping.radius(value, fallback)
}
const REFERENCE_HALF_WIDTH: f32 = 0.6;
const REFERENCE_HALF_THICKNESS: f32 = 0.14;
const HELIX_HALF_EXTENT: [f32; 2] = [1.35, 0.25];
const STRAND_HALF_EXTENT: [f32; 2] = [1.4, 0.4];
const LOOP_RADIUS: f32 = 0.2;
const STRAND_WIDTH_SCALE: f32 = STRAND_HALF_EXTENT[0] / REFERENCE_HALF_WIDTH;
#[inline]
fn strand_width(parameter: f32, styles: &[SecondaryStructure], segment: usize) -> f32 {
let next_style = segment.checked_add(1).and_then(|next| styles.get(next));
if matches!(next_style, Some(SecondaryStructure::Strand)) {
return STRAND_WIDTH_SCALE;
}
let arrow = if parameter < 0.65 {
1.0 + parameter * (0.6 / 0.65)
} else {
(1.6 * (1.0 - parameter) / 0.35).max(0.08)
};
arrow * STRAND_WIDTH_SCALE
}
#[inline]
pub(super) fn rocket_scale(
style: SecondaryStructure,
parameter: f32,
styles: &[SecondaryStructure],
segment: usize,
) -> (f32, f32) {
match style {
SecondaryStructure::Helix => (1.35, 1.35),
SecondaryStructure::Strand => (strand_width(parameter, styles, segment), 0.34),
SecondaryStructure::Turn | SecondaryStructure::Coil | SecondaryStructure::Unknown => {
(0.3, 0.3)
}
}
}
#[inline]
pub(super) fn profile_scale(
style: SecondaryStructure,
parameter: f32,
styles: &[SecondaryStructure],
segment: usize,
) -> (f32, f32) {
match style {
SecondaryStructure::Helix => (
HELIX_HALF_EXTENT[0] / REFERENCE_HALF_WIDTH,
HELIX_HALF_EXTENT[1] / REFERENCE_HALF_THICKNESS,
),
SecondaryStructure::Strand => (
strand_width(parameter, styles, segment),
STRAND_HALF_EXTENT[1] / REFERENCE_HALF_THICKNESS,
),
SecondaryStructure::Turn | SecondaryStructure::Coil | SecondaryStructure::Unknown => (
LOOP_RADIUS / REFERENCE_HALF_WIDTH,
LOOP_RADIUS / REFERENCE_HALF_THICKNESS,
),
}
}