lumen_geometry/colon/
config.rs

1//! Configuration for colon geometry generation.
2
3use super::ColonSegment;
4
5/// Configuration parameters for generating a colon curve.
6///
7/// All parameters have sensible defaults based on adult human anatomy.
8/// Use `seed` to generate procedural variations while maintaining
9/// anatomical plausibility.
10///
11/// # Example
12///
13/// ```rust
14/// use lumen_geometry::colon::ColonConfig;
15///
16/// // Default adult anatomy
17/// let config = ColonConfig::default();
18///
19/// // Procedurally varied anatomy
20/// let varied = ColonConfig {
21///     seed: Some(12345),
22///     ..Default::default()
23/// };
24///
25/// // Custom configuration
26/// let custom = ColonConfig {
27///     total_length: 120.0,  // Shorter colon
28///     base_radius: 2.5,     // Narrower lumen
29///     ..Default::default()
30/// };
31/// ```
32#[derive(Debug, Clone)]
33pub struct ColonConfig {
34    /// Seed for procedural variation. `None` uses default geometry.
35    ///
36    /// When set, the seed affects:
37    /// - Segment length ratios (within anatomical bounds)
38    /// - Flexure angles (within anatomical bounds)
39    /// - Radius variations along the length
40    /// - Secondary curve wobble
41    pub seed: Option<u64>,
42
43    /// Total arc length of the colon in world units.
44    ///
45    /// Adult human colon is approximately 150cm. Default is scaled
46    /// for typical game/simulation use.
47    pub total_length: f32,
48
49    /// Base lumen radius in world units.
50    ///
51    /// This is the default radius; actual radius varies by segment.
52    pub base_radius: f32,
53
54    /// Per-segment configuration overrides.
55    ///
56    /// If `None`, uses default parameters for each segment.
57    pub segment_params: Option<[SegmentParams; 8]>,
58
59    /// How much the seed can vary segment lengths.
60    ///
61    /// Value of 0.0 = no variation, 1.0 = maximum variation.
62    /// Default is 0.3 (30% variation from base lengths).
63    pub length_variation: f32,
64
65    /// How much the seed can vary flexure angles.
66    ///
67    /// Value of 0.0 = no variation, 1.0 = maximum variation.
68    /// Default is 0.2 (20% variation from base angles).
69    pub angle_variation: f32,
70
71    /// Secondary wobble amplitude for organic feel.
72    ///
73    /// Adds small-scale variation to the centerline path.
74    /// Default is 0.1 (10% of radius).
75    pub wobble_amplitude: f32,
76
77    /// Frequency of secondary wobble.
78    ///
79    /// Higher values = more frequent small bends.
80    /// Default is 8.0 (approximately 8 wobbles per segment).
81    pub wobble_frequency: f32,
82}
83
84/// Parameters for a single colon segment.
85#[derive(Debug, Clone, Copy)]
86pub struct SegmentParams {
87    /// Fraction of total length this segment occupies.
88    ///
89    /// All segment fractions should sum to 1.0.
90    pub length_fraction: f32,
91
92    /// Radius multiplier relative to base_radius.
93    ///
94    /// 1.0 = base radius, 1.2 = 20% wider, etc.
95    pub radius_scale: f32,
96
97    /// Curvature intensity for this segment.
98    ///
99    /// 0.0 = straight, 1.0 = normal curve, 2.0 = sharp bend.
100    /// Flexures typically have higher values.
101    pub curvature: f32,
102
103    /// Primary bend direction (radians).
104    ///
105    /// Determines which way this segment curves.
106    pub bend_direction: f32,
107}
108
109impl Default for ColonConfig {
110    fn default() -> Self {
111        Self {
112            seed: None,
113            total_length: 150.0,
114            base_radius: 3.0,
115            segment_params: None,
116            length_variation: 0.3,
117            angle_variation: 0.2,
118            wobble_amplitude: 0.1,
119            wobble_frequency: 8.0,
120        }
121    }
122}
123
124impl ColonConfig {
125    /// Create a new config with a specific seed.
126    pub fn with_seed(seed: u64) -> Self {
127        Self {
128            seed: Some(seed),
129            ..Default::default()
130        }
131    }
132
133    /// Get parameters for a specific segment.
134    ///
135    /// Returns custom params if set, otherwise default for that segment.
136    pub fn params_for(&self, segment: ColonSegment) -> SegmentParams {
137        if let Some(ref params) = self.segment_params {
138            params[segment.index()]
139        } else {
140            SegmentParams::default_for(segment)
141        }
142    }
143}
144
145impl SegmentParams {
146    /// Default parameters for each colon segment.
147    ///
148    /// Based on anatomical characteristics:
149    /// - Flexures are short but sharply curved
150    /// - Transverse is longest
151    /// - Cecum is widest
152    pub fn default_for(segment: ColonSegment) -> Self {
153        use std::f32::consts::PI;
154
155        match segment {
156            ColonSegment::Rectum => Self {
157                length_fraction: 0.08,
158                radius_scale: 1.0,
159                curvature: 0.2,
160                bend_direction: 0.0,
161            },
162            ColonSegment::Sigmoid => Self {
163                length_fraction: 0.12,
164                radius_scale: 0.9,
165                curvature: 1.5,
166                bend_direction: PI * 0.25, // S-curve
167            },
168            ColonSegment::Descending => Self {
169                length_fraction: 0.15,
170                radius_scale: 1.0,
171                curvature: 0.3,
172                bend_direction: PI * 0.5, // Leftward/upward
173            },
174            ColonSegment::SplenicFlexure => Self {
175                length_fraction: 0.05,
176                radius_scale: 0.95,
177                curvature: 2.5, // Sharp bend
178                bend_direction: PI,
179            },
180            ColonSegment::Transverse => Self {
181                length_fraction: 0.25,
182                radius_scale: 1.1,
183                curvature: 0.4,
184                bend_direction: PI * 1.5, // Rightward
185            },
186            ColonSegment::HepaticFlexure => Self {
187                length_fraction: 0.05,
188                radius_scale: 0.95,
189                curvature: 2.5, // Sharp bend
190                bend_direction: 0.0,
191            },
192            ColonSegment::Ascending => Self {
193                length_fraction: 0.20,
194                radius_scale: 1.05,
195                curvature: 0.3,
196                bend_direction: PI * 0.5, // Downward
197            },
198            ColonSegment::Cecum => Self {
199                length_fraction: 0.10,
200                radius_scale: 1.3, // Widest section
201                curvature: 0.5,
202                bend_direction: PI * 0.75,
203            },
204        }
205    }
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211
212    #[test]
213    fn default_length_fractions_sum_to_one() {
214        let sum: f32 = ColonSegment::ALL
215            .iter()
216            .map(|s| SegmentParams::default_for(*s).length_fraction)
217            .sum();
218        assert!(
219            (sum - 1.0).abs() < 0.001,
220            "Length fractions sum to {}, expected 1.0",
221            sum
222        );
223    }
224
225    #[test]
226    fn flexures_have_high_curvature() {
227        let splenic = SegmentParams::default_for(ColonSegment::SplenicFlexure);
228        let hepatic = SegmentParams::default_for(ColonSegment::HepaticFlexure);
229        let transverse = SegmentParams::default_for(ColonSegment::Transverse);
230
231        assert!(splenic.curvature > transverse.curvature);
232        assert!(hepatic.curvature > transverse.curvature);
233    }
234
235    #[test]
236    fn cecum_is_widest() {
237        let cecum = SegmentParams::default_for(ColonSegment::Cecum);
238        for segment in ColonSegment::ALL.iter() {
239            let params = SegmentParams::default_for(*segment);
240            assert!(
241                cecum.radius_scale >= params.radius_scale,
242                "{:?} is wider than Cecum",
243                segment
244            );
245        }
246    }
247}