Skip to main content

oxihuman_morph/
rbf_deformer.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! RBF (radial basis function) deformer stub.
6
7/// RBF kernel type.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum RbfKernel {
10    Gaussian,
11    Multiquadric,
12    InverseQuadratic,
13}
14
15/// An RBF control point.
16#[derive(Debug, Clone)]
17pub struct RbfControlPoint {
18    pub center: Vec<f32>,
19    pub coefficient: f32,
20}
21
22/// RBF deformer.
23#[derive(Debug, Clone)]
24pub struct RbfDeformer {
25    pub kernel: RbfKernel,
26    pub epsilon: f32,
27    pub control_points: Vec<RbfControlPoint>,
28}
29
30impl RbfDeformer {
31    pub fn new(kernel: RbfKernel) -> Self {
32        RbfDeformer {
33            kernel,
34            epsilon: 1.0,
35            control_points: Vec::new(),
36        }
37    }
38}
39
40/// Create a new RBF deformer with given kernel.
41pub fn new_rbf_deformer(kernel: RbfKernel) -> RbfDeformer {
42    RbfDeformer::new(kernel)
43}
44
45/// Compute the RBF kernel value for a given distance.
46pub fn rbf_kernel_value(deformer: &RbfDeformer, distance: f32) -> f32 {
47    let r = distance * deformer.epsilon;
48    match deformer.kernel {
49        RbfKernel::Gaussian => (-r * r).exp(),
50        RbfKernel::Multiquadric => (1.0 + r * r).sqrt(),
51        RbfKernel::InverseQuadratic => 1.0 / (1.0 + r * r),
52    }
53}
54
55/// Add a control point.
56pub fn rbf_add_control_point(deformer: &mut RbfDeformer, center: Vec<f32>, coefficient: f32) {
57    deformer.control_points.push(RbfControlPoint {
58        center,
59        coefficient,
60    });
61}
62
63/// Return control point count.
64pub fn rbf_point_count(deformer: &RbfDeformer) -> usize {
65    deformer.control_points.len()
66}
67
68/// Evaluate the RBF at a given query point.
69pub fn rbf_evaluate(deformer: &RbfDeformer, query: &[f32]) -> f32 {
70    deformer
71        .control_points
72        .iter()
73        .map(|cp| {
74            let n = cp.center.len().min(query.len());
75            let dist: f32 = (0..n)
76                .map(|i| (cp.center[i] - query[i]).powi(2))
77                .sum::<f32>()
78                .sqrt();
79            cp.coefficient * rbf_kernel_value(deformer, dist)
80        })
81        .sum()
82}
83
84/// Return a JSON-like string.
85pub fn rbf_to_json(deformer: &RbfDeformer) -> String {
86    format!(
87        r#"{{"kernel":"{}","epsilon":{:.4},"points":{}}}"#,
88        match deformer.kernel {
89            RbfKernel::Gaussian => "gaussian",
90            RbfKernel::Multiquadric => "multiquadric",
91            RbfKernel::InverseQuadratic => "inverse_quadratic",
92        },
93        deformer.epsilon,
94        deformer.control_points.len()
95    )
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_new_rbf_deformer_no_points() {
104        let d = new_rbf_deformer(RbfKernel::Gaussian);
105        assert_eq!(
106            rbf_point_count(&d),
107            0, /* new deformer has no control points */
108        );
109    }
110
111    #[test]
112    fn test_add_control_point_increases_count() {
113        let mut d = new_rbf_deformer(RbfKernel::Gaussian);
114        rbf_add_control_point(&mut d, vec![0.0, 0.0], 1.0);
115        assert_eq!(rbf_point_count(&d), 1 /* count should increase */,);
116    }
117
118    #[test]
119    fn test_gaussian_at_zero_distance_is_one() {
120        let d = new_rbf_deformer(RbfKernel::Gaussian);
121        let v = rbf_kernel_value(&d, 0.0);
122        assert!((v - 1.0).abs() < 1e-5, /* Gaussian at zero distance should be 1 */);
123    }
124
125    #[test]
126    fn test_inverse_quadratic_at_zero_distance_is_one() {
127        let d = new_rbf_deformer(RbfKernel::InverseQuadratic);
128        let v = rbf_kernel_value(&d, 0.0);
129        assert!((v - 1.0).abs() < 1e-5, /* InverseQuadratic at zero distance should be 1 */);
130    }
131
132    #[test]
133    fn test_multiquadric_at_zero_distance_is_one() {
134        let d = new_rbf_deformer(RbfKernel::Multiquadric);
135        let v = rbf_kernel_value(&d, 0.0);
136        assert!((v - 1.0).abs() < 1e-5, /* Multiquadric at zero distance should be 1 */);
137    }
138
139    #[test]
140    fn test_evaluate_empty_is_zero() {
141        let d = new_rbf_deformer(RbfKernel::Gaussian);
142        let v = rbf_evaluate(&d, &[0.0, 0.0]);
143        assert!((v).abs() < 1e-6 /* empty deformer evaluates to 0 */,);
144    }
145
146    #[test]
147    fn test_evaluate_at_control_point() {
148        let mut d = new_rbf_deformer(RbfKernel::Gaussian);
149        rbf_add_control_point(&mut d, vec![0.0, 0.0], 1.0);
150        let v = rbf_evaluate(&d, &[0.0, 0.0]);
151        assert!(
152            (v - 1.0).abs() < 1e-5, /* evaluating exactly at control point with coeff=1 should give 1 */
153        );
154    }
155
156    #[test]
157    fn test_to_json_contains_kernel() {
158        let d = new_rbf_deformer(RbfKernel::Gaussian);
159        let j = rbf_to_json(&d);
160        assert!(j.contains("gaussian"), /* JSON must contain kernel type */);
161    }
162
163    #[test]
164    fn test_epsilon_default_one() {
165        let d = new_rbf_deformer(RbfKernel::Gaussian);
166        assert!((d.epsilon - 1.0).abs() < 1e-5, /* default epsilon is 1.0 */);
167    }
168
169    #[test]
170    fn test_gaussian_decays_with_distance() {
171        let d = new_rbf_deformer(RbfKernel::Gaussian);
172        let near = rbf_kernel_value(&d, 0.1);
173        let far = rbf_kernel_value(&d, 2.0);
174        assert!(near > far /* Gaussian should decay with distance */,);
175    }
176}