#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransformChannel {
LocationX,
LocationY,
LocationZ,
RotationX,
RotationY,
RotationZ,
ScaleX,
ScaleY,
ScaleZ,
}
#[derive(Debug, Clone)]
pub struct TransformConstraint {
pub source_channel: TransformChannel,
pub target_channel: TransformChannel,
pub from_min: f32,
pub from_max: f32,
pub to_min: f32,
pub to_max: f32,
pub influence: f32,
pub label: String,
}
pub fn new_transform_constraint(
source: TransformChannel,
target: TransformChannel,
label: &str,
) -> TransformConstraint {
TransformConstraint {
source_channel: source,
target_channel: target,
from_min: -1.0,
from_max: 1.0,
to_min: -1.0,
to_max: 1.0,
influence: 1.0,
label: label.to_owned(),
}
}
pub fn map_value(c: &TransformConstraint, value: f32) -> f32 {
let from_range = c.from_max - c.from_min;
if from_range.abs() < 1e-8 {
return c.to_min;
}
let t = (value - c.from_min) / from_range;
let t = t.clamp(0.0, 1.0);
let mapped = c.to_min + t * (c.to_max - c.to_min);
mapped * c.influence
}
pub fn validate_transform_constraint(c: &TransformConstraint) -> bool {
c.from_max > c.from_min && c.to_max > c.to_min
}
pub fn channel_name(ch: TransformChannel) -> &'static str {
match ch {
TransformChannel::LocationX => "loc_x",
TransformChannel::LocationY => "loc_y",
TransformChannel::LocationZ => "loc_z",
TransformChannel::RotationX => "rot_x",
TransformChannel::RotationY => "rot_y",
TransformChannel::RotationZ => "rot_z",
TransformChannel::ScaleX => "scale_x",
TransformChannel::ScaleY => "scale_y",
TransformChannel::ScaleZ => "scale_z",
}
}
pub fn transform_constraint_to_json(c: &TransformConstraint) -> String {
format!(
r#"{{"label":"{}", "source":"{}", "target":"{}", "from":[{:.4},{:.4}], "to":[{:.4},{:.4}], "influence":{:.4}}}"#,
c.label,
channel_name(c.source_channel),
channel_name(c.target_channel),
c.from_min,
c.from_max,
c.to_min,
c.to_max,
c.influence
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn map_value_at_from_min_returns_to_min() {
let mut c =
new_transform_constraint(TransformChannel::LocationX, TransformChannel::ScaleY, "t");
c.from_min = 0.0;
c.from_max = 1.0;
c.to_min = 0.0;
c.to_max = 10.0;
assert!((map_value(&c, 0.0) - 0.0).abs() < 1e-5);
}
#[test]
fn map_value_at_from_max_returns_to_max() {
let mut c =
new_transform_constraint(TransformChannel::LocationX, TransformChannel::ScaleY, "t");
c.from_min = 0.0;
c.from_max = 1.0;
c.to_min = 0.0;
c.to_max = 10.0;
assert!((map_value(&c, 1.0) - 10.0).abs() < 1e-4);
}
#[test]
fn map_value_zero_influence_yields_zero() {
let mut c = new_transform_constraint(
TransformChannel::RotationX,
TransformChannel::LocationZ,
"t",
);
c.from_min = 0.0;
c.from_max = 1.0;
c.to_min = 0.0;
c.to_max = 5.0;
c.influence = 0.0;
assert!((map_value(&c, 0.5) - 0.0).abs() < 1e-5);
}
#[test]
fn validate_default_constraint_is_valid() {
let c = new_transform_constraint(TransformChannel::ScaleX, TransformChannel::ScaleX, "t");
assert!(validate_transform_constraint(&c));
}
#[test]
fn validate_inverted_range_fails() {
let mut c =
new_transform_constraint(TransformChannel::ScaleX, TransformChannel::ScaleX, "t");
c.from_min = 1.0;
c.from_max = -1.0;
assert!(!validate_transform_constraint(&c));
}
#[test]
fn channel_name_location_x() {
assert_eq!(channel_name(TransformChannel::LocationX), "loc_x");
}
#[test]
fn channel_name_scale_z() {
assert_eq!(channel_name(TransformChannel::ScaleZ), "scale_z");
}
#[test]
fn json_contains_label() {
let c = new_transform_constraint(
TransformChannel::RotationY,
TransformChannel::ScaleX,
"myTfm",
);
assert!(transform_constraint_to_json(&c).contains("myTfm"));
}
#[test]
fn default_influence_is_one() {
let c = new_transform_constraint(
TransformChannel::LocationY,
TransformChannel::LocationY,
"t",
);
assert!((c.influence - 1.0).abs() < 1e-6);
}
}