Skip to main content

qualia_core_db/render/
body_fit.rs

1//! Apply a person-authored [`AnatomyBodyFit`] to decoded organ vertices.
2//!
3//! The numbers come from `wellfare_core::anatomy::BodyFit` (same serde shape). This module
4//! lives in the renderer so the portal WASM can fit a body without depending on wellfare-core.
5
6use serde::{Deserialize, Serialize};
7
8/// View transform applied in CCF space before the orbit-frame normalise.
9#[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}