oxihuman_core/
transform_pipe.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct TransformStage {
11 pub name: String,
12 pub kind: TransformKind,
13}
14
15#[allow(dead_code)]
17#[derive(Debug, Clone, PartialEq)]
18pub enum TransformKind {
19 Scale(f32),
20 Offset(f32),
21 Clamp(f32, f32),
22 Abs,
23 Negate,
24 Reciprocal,
25 Sin,
26 Cos,
27}
28
29impl TransformKind {
30 #[allow(dead_code)]
31 fn apply(&self, x: f32) -> f32 {
32 match self {
33 TransformKind::Scale(s) => x * s,
34 TransformKind::Offset(o) => x + o,
35 TransformKind::Clamp(lo, hi) => x.clamp(*lo, *hi),
36 TransformKind::Abs => x.abs(),
37 TransformKind::Negate => -x,
38 TransformKind::Reciprocal => {
39 if x.abs() > 1e-9 {
40 1.0 / x
41 } else {
42 0.0
43 }
44 }
45 TransformKind::Sin => x.sin(),
46 TransformKind::Cos => x.cos(),
47 }
48 }
49}
50
51#[allow(dead_code)]
53#[derive(Debug, Clone, Default)]
54pub struct TransformPipe {
55 stages: Vec<TransformStage>,
56}
57
58#[allow(dead_code)]
60pub fn new_transform_pipe() -> TransformPipe {
61 TransformPipe::default()
62}
63
64#[allow(dead_code)]
66pub fn tp_add(pipe: &mut TransformPipe, name: &str, kind: TransformKind) {
67 pipe.stages.push(TransformStage {
68 name: name.to_string(),
69 kind,
70 });
71}
72
73#[allow(dead_code)]
75pub fn tp_apply(pipe: &TransformPipe, mut val: f32) -> f32 {
76 for stage in &pipe.stages {
77 val = stage.kind.apply(val);
78 }
79 val
80}
81
82#[allow(dead_code)]
84pub fn tp_len(pipe: &TransformPipe) -> usize {
85 pipe.stages.len()
86}
87
88#[allow(dead_code)]
90pub fn tp_is_empty(pipe: &TransformPipe) -> bool {
91 pipe.stages.is_empty()
92}
93
94#[allow(dead_code)]
96pub fn tp_pop(pipe: &mut TransformPipe) -> Option<TransformStage> {
97 pipe.stages.pop()
98}
99
100#[allow(dead_code)]
102pub fn tp_clear(pipe: &mut TransformPipe) {
103 pipe.stages.clear();
104}
105
106#[allow(dead_code)]
108pub fn tp_get(pipe: &TransformPipe, idx: usize) -> Option<&TransformStage> {
109 pipe.stages.get(idx)
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use std::f32::consts::PI;
116
117 #[test]
118 fn test_empty_pipe_passthrough() {
119 let pipe = new_transform_pipe();
120 assert!((tp_apply(&pipe, 3.5) - 3.5).abs() < 1e-6);
121 }
122
123 #[test]
124 fn test_scale() {
125 let mut pipe = new_transform_pipe();
126 tp_add(&mut pipe, "x2", TransformKind::Scale(2.0));
127 assert!((tp_apply(&pipe, 3.0) - 6.0).abs() < 1e-6);
128 }
129
130 #[test]
131 fn test_offset() {
132 let mut pipe = new_transform_pipe();
133 tp_add(&mut pipe, "shift", TransformKind::Offset(1.0));
134 assert!((tp_apply(&pipe, 4.0) - 5.0).abs() < 1e-6);
135 }
136
137 #[test]
138 fn test_clamp() {
139 let mut pipe = new_transform_pipe();
140 tp_add(&mut pipe, "clamp", TransformKind::Clamp(0.0, 1.0));
141 assert!((tp_apply(&pipe, 2.5) - 1.0).abs() < 1e-6);
142 }
143
144 #[test]
145 fn test_abs() {
146 let mut pipe = new_transform_pipe();
147 tp_add(&mut pipe, "abs", TransformKind::Abs);
148 assert!((tp_apply(&pipe, -7.0) - 7.0).abs() < 1e-6);
149 }
150
151 #[test]
152 fn test_negate() {
153 let mut pipe = new_transform_pipe();
154 tp_add(&mut pipe, "neg", TransformKind::Negate);
155 assert!((tp_apply(&pipe, 5.0) - (-5.0)).abs() < 1e-6);
156 }
157
158 #[test]
159 fn test_chain() {
160 let mut pipe = new_transform_pipe();
161 tp_add(&mut pipe, "scale", TransformKind::Scale(2.0));
162 tp_add(&mut pipe, "offset", TransformKind::Offset(-1.0));
163 assert!((tp_apply(&pipe, 3.0) - 5.0).abs() < 1e-6);
164 }
165
166 #[test]
167 fn test_sin_at_pi() {
168 let mut pipe = new_transform_pipe();
169 tp_add(&mut pipe, "sin", TransformKind::Sin);
170 assert!(tp_apply(&pipe, PI).abs() < 1e-5);
171 }
172
173 #[test]
174 fn test_pop_removes_stage() {
175 let mut pipe = new_transform_pipe();
176 tp_add(&mut pipe, "a", TransformKind::Abs);
177 tp_pop(&mut pipe);
178 assert!(tp_is_empty(&pipe));
179 }
180
181 #[test]
182 fn test_clear() {
183 let mut pipe = new_transform_pipe();
184 tp_add(&mut pipe, "a", TransformKind::Abs);
185 tp_clear(&mut pipe);
186 assert_eq!(tp_len(&pipe), 0);
187 }
188}