1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub struct AnatomyBodyFit {
11 #[serde(default = "one")]
12 pub stature_scale: f32,
13 #[serde(default = "one")]
14 pub torso_scale_y: f32,
15 #[serde(default = "one")]
16 pub leg_scale_y: f32,
17 #[serde(default = "one")]
18 pub arm_span_scale_x: f32,
19 #[serde(default = "one")]
20 pub shoulder_scale_x: f32,
21 #[serde(default = "one")]
22 pub chest_radial: f32,
23 #[serde(default = "one")]
24 pub waist_radial: f32,
25 #[serde(default = "one")]
26 pub hip_radial: f32,
27 #[serde(default)]
28 pub pregnancy_abdomen: f32,
29 #[serde(default = "pelvis_y")]
30 pub pelvis_y_norm: f32,
31 #[serde(default = "waist_y")]
32 pub waist_y_norm: f32,
33 #[serde(default = "chest_y")]
34 pub chest_y_norm: f32,
35 #[serde(default = "shoulder_y")]
36 pub shoulder_y_norm: f32,
37 #[serde(default)]
38 pub hidden_keys: Vec<String>,
39 #[serde(default)]
40 pub identity: bool,
41}
42
43fn one() -> f32 {
44 1.0
45}
46fn pelvis_y() -> f32 {
47 0.42
48}
49fn waist_y() -> f32 {
50 0.52
51}
52fn chest_y() -> f32 {
53 0.68
54}
55fn shoulder_y() -> f32 {
56 0.78
57}
58
59impl Default for AnatomyBodyFit {
60 fn default() -> Self {
61 Self {
62 stature_scale: 1.0,
63 torso_scale_y: 1.0,
64 leg_scale_y: 1.0,
65 arm_span_scale_x: 1.0,
66 shoulder_scale_x: 1.0,
67 chest_radial: 1.0,
68 waist_radial: 1.0,
69 hip_radial: 1.0,
70 pregnancy_abdomen: 0.0,
71 pelvis_y_norm: 0.42,
72 waist_y_norm: 0.52,
73 chest_y_norm: 0.68,
74 shoulder_y_norm: 0.78,
75 hidden_keys: Vec::new(),
76 identity: true,
77 }
78 }
79}
80
81impl AnatomyBodyFit {
82 pub fn transform_point(&self, p: [f32; 3], gmin: [f32; 3], gmax: [f32; 3]) -> [f32; 3] {
83 let span_y = (gmax[1] - gmin[1]).max(1e-6);
84 let mid_x = (gmin[0] + gmax[0]) * 0.5;
85 let mid_z = (gmin[2] + gmax[2]) * 0.5;
86 let y_norm = ((p[1] - gmin[1]) / span_y).clamp(0.0, 1.0);
87
88 let mut x = p[0] - mid_x;
89 let mut y = p[1];
90 let mut z = p[2] - mid_z;
91
92 let y_seg = if y_norm < self.pelvis_y_norm {
93 self.leg_scale_y
94 } else {
95 self.torso_scale_y
96 };
97 y = gmin[1] + (y - gmin[1]) * y_seg;
98
99 let radial = if y_norm < self.pelvis_y_norm {
100 lerp(
101 1.0,
102 self.hip_radial,
103 smoothstep(0.20, self.pelvis_y_norm, y_norm),
104 )
105 } else if y_norm < self.waist_y_norm {
106 lerp(
107 self.hip_radial,
108 self.waist_radial,
109 smoothstep(self.pelvis_y_norm, self.waist_y_norm, y_norm),
110 )
111 } else if y_norm < self.chest_y_norm {
112 lerp(
113 self.waist_radial,
114 self.chest_radial,
115 smoothstep(self.waist_y_norm, self.chest_y_norm, y_norm),
116 )
117 } else {
118 lerp(
119 self.chest_radial,
120 self.shoulder_scale_x,
121 smoothstep(self.chest_y_norm, self.shoulder_y_norm, y_norm),
122 )
123 };
124 x *= radial * self.arm_span_scale_x;
125 z *= radial;
126
127 if self.pregnancy_abdomen > 0.0 {
128 let band = bump(y_norm, 0.48, 0.10);
129 z += self.pregnancy_abdomen * band * span_y * 0.18;
130 x *= 1.0 + self.pregnancy_abdomen * band * 0.12;
131 }
132
133 [
134 mid_x + x * self.stature_scale,
135 gmin[1] + (y - gmin[1]) * self.stature_scale,
136 mid_z + z * self.stature_scale,
137 ]
138 }
139
140 pub fn apply_in_place(&self, positions: &mut [[f32; 3]], gmin: [f32; 3], gmax: [f32; 3]) {
141 if self.identity {
142 return;
143 }
144 for p in positions.iter_mut() {
145 *p = self.transform_point(*p, gmin, gmax);
146 }
147 }
148}
149
150fn lerp(a: f32, b: f32, t: f32) -> f32 {
151 a + (b - a) * t.clamp(0.0, 1.0)
152}
153
154fn smoothstep(edge0: f32, edge1: f32, x: f32) -> f32 {
155 let t = ((x - edge0) / (edge1 - edge0).max(1e-6)).clamp(0.0, 1.0);
156 t * t * (3.0 - 2.0 * t)
157}
158
159fn bump(x: f32, center: f32, width: f32) -> f32 {
160 let t = ((x - center) / width).abs();
161 if t >= 1.0 {
162 0.0
163 } else {
164 1.0 - t * t
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn identity_leaves_vertices() {
174 let fit = AnatomyBodyFit::default();
175 let mut pts = [[0.1, 0.5, 0.0], [0.2, 1.0, 0.1]];
176 let orig = pts;
177 fit.apply_in_place(&mut pts, [0.0, 0.0, 0.0], [0.4, 1.8, 0.3]);
178 assert_eq!(pts, orig);
179 }
180
181 #[test]
182 fn stature_scale_raises_crown() {
183 let fit = AnatomyBodyFit {
184 stature_scale: 1.2,
185 identity: false,
186 ..AnatomyBodyFit::default()
187 };
188 let p = fit.transform_point([0.0, 1.8, 0.0], [0.0, 0.0, 0.0], [0.4, 1.8, 0.3]);
189 assert!((p[1] - 2.16).abs() < 1e-3);
190 }
191}