Skip to main content

ferroui_base/rendering/composition/expressions/
built_in_expression_ffi.rs

1//! Built-in functions available from composition animation expressions.
2
3use std::sync::OnceLock;
4
5use super::delegate_expression_ffi::DelegateExpressionFfi;
6use super::expression_evaluation_context::IExpressionForeignFunctionInterface;
7use super::expression_variant::ExpressionVariant;
8use crate::media::Color;
9use crate::numerics::{Matrix3x2, Matrix4x4, Quaternion, Vector2, Vector3, Vector4};
10use crate::rendering::composition::animations::ColorInterpolator;
11use crate::utilities::math_utilities::{max_f32, min_f32};
12use crate::utilities::MathUtilities;
13use crate::{RelativePoint, RelativeScalar, RelativeUnit, Vector, Vector3D};
14
15/// Built-in functions for Foreign Function Interface available from composition animation expressions
16pub struct BuiltInExpressionFfi {
17    registry: DelegateExpressionFfi,
18}
19
20fn lerp(a: f32, b: f32, p: f32) -> f32 {
21    (a * (1.0 - p)) + (b * p)
22}
23
24fn lerp_f64(a: f64, b: f64, p: f64) -> f64 {
25    (a * (1.0 - p)) + (b * p)
26}
27
28fn smooth_step(edge0: f32, edge1: f32, x: f32) -> f32 {
29    let t = MathUtilities::clamp_f32((x - edge0) / (edge1 - edge0), 0.0, 1.0);
30    t * t * (3.0 - 2.0 * t)
31}
32
33fn smooth_step_f64(edge0: f64, edge1: f64, x: f64) -> f64 {
34    let t = MathUtilities::clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
35    t * t * (3.0 - 2.0 * t)
36}
37
38fn smooth_step_vector2(edge0: Vector2, edge1: Vector2, x: Vector2) -> Vector2 {
39    Vector2::new(
40        smooth_step(edge0.x, edge1.x, x.x),
41        smooth_step(edge0.y, edge1.y, x.y),
42    )
43}
44
45fn smooth_step_vector(edge0: Vector, edge1: Vector, x: Vector) -> Vector {
46    Vector::new(
47        smooth_step_f64(edge0.x, edge1.x, x.x),
48        smooth_step_f64(edge0.y, edge1.y, x.y),
49    )
50}
51
52fn smooth_step_vector3(edge0: Vector3, edge1: Vector3, x: Vector3) -> Vector3 {
53    Vector3::new(
54        smooth_step(edge0.x, edge1.x, x.x),
55        smooth_step(edge0.y, edge1.y, x.y),
56        smooth_step(edge0.z, edge1.z, x.z),
57    )
58}
59
60fn smooth_step_vector3d(edge0: Vector3D, edge1: Vector3D, x: Vector3D) -> Vector3D {
61    Vector3D::new(
62        smooth_step_f64(edge0.x, edge1.x, x.x),
63        smooth_step_f64(edge0.y, edge1.y, x.y),
64        smooth_step_f64(edge0.z, edge1.z, x.z),
65    )
66}
67
68fn smooth_step_vector4(edge0: Vector4, edge1: Vector4, x: Vector4) -> Vector4 {
69    Vector4::new(
70        smooth_step(edge0.x, edge1.x, x.x),
71        smooth_step(edge0.y, edge1.y, x.y),
72        smooth_step(edge0.z, edge1.z, x.z),
73        smooth_step(edge0.w, edge1.w, x.w),
74    )
75}
76
77/// Applies a double-precision function to a single-precision value.
78#[inline]
79fn via_f64(value: f32, function: impl FnOnce(f64) -> f64) -> f32 {
80    function(value as f64) as f32
81}
82
83impl BuiltInExpressionFfi {
84    fn new() -> Self {
85        let mut r = DelegateExpressionFfi::new();
86
87        r.add1("Abs", |f: f32| f.abs());
88        r.add1("Abs", |v: Vector2| Vector2::abs(v));
89        r.add1("Abs", |v: Vector| v.abs());
90        r.add1("Abs", |v: Vector3| Vector3::abs(v));
91        r.add1("Abs", |v: Vector3D| v.abs());
92        r.add1("Abs", |v: Vector4| Vector4::abs(v));
93
94        r.add1("ACos", |f: f32| via_f64(f, f64::acos));
95        r.add1("ASin", |f: f32| via_f64(f, f64::asin));
96        r.add1("ATan", |f: f32| via_f64(f, f64::atan));
97        r.add1("Ceil", |f: f32| via_f64(f, f64::ceil));
98
99        r.add3("Clamp", |a1: f32, a2: f32, a3: f32| MathUtilities::clamp_f32(a1, a2, a3));
100        r.add3("Clamp", |a1: Vector2, a2: Vector2, a3: Vector2| Vector2::clamp(a1, a2, a3));
101        r.add3("Clamp", |a1: Vector, a2: Vector, a3: Vector| Vector::clamp(a1, a2, a3));
102        r.add3("Clamp", |a1: Vector3, a2: Vector3, a3: Vector3| Vector3::clamp(a1, a2, a3));
103        r.add3("Clamp", |a1: Vector3D, a2: Vector3D, a3: Vector3D| {
104            Vector3D::clamp(a1, a2, a3)
105        });
106        r.add3("Clamp", |a1: Vector4, a2: Vector4, a3: Vector4| Vector4::clamp(a1, a2, a3));
107
108        r.add2("Concatenate", |a1: Quaternion, a2: Quaternion| {
109            Quaternion::concatenate(a1, a2)
110        });
111        r.add1("Cos", |a: f32| via_f64(a, f64::cos));
112
113        /*
114        TODO:
115            ColorHsl(Float h, Float s, Float l)
116            ColorLerpHSL(Color colorTo, CompositionColorcolorFrom, Float progress)
117        */
118
119        r.add3("ColorLerp", |to: Color, from: Color, progress: f32| {
120            ColorInterpolator::lerp_rgb(to, from, progress)
121        });
122        r.add3("ColorLerpRGB", |to: Color, from: Color, progress: f32| {
123            ColorInterpolator::lerp_rgb(to, from, progress)
124        });
125        r.add4("ColorRGB", |a: f32, r: f32, g: f32, b: f32| {
126            Color::from_argb(
127                MathUtilities::clamp_f32(a, 0.0, 255.0) as u8,
128                MathUtilities::clamp_f32(r, 0.0, 255.0) as u8,
129                MathUtilities::clamp_f32(g, 0.0, 255.0) as u8,
130                MathUtilities::clamp_f32(b, 0.0, 255.0) as u8,
131            )
132        });
133
134        r.add2("Distance", |a1: Vector2, a2: Vector2| Vector2::distance(a1, a2));
135        r.add2("Distance", |a1: Vector, a2: Vector| Vector::distance(a1, a2));
136        r.add2("Distance", |a1: Vector3, a2: Vector3| Vector3::distance(a1, a2));
137        r.add2("Distance", |a1: Vector3D, a2: Vector3D| Vector3D::distance(a1, a2));
138        r.add2("Distance", |a1: Vector4, a2: Vector4| Vector4::distance(a1, a2));
139
140        r.add2("DistanceSquared", |a1: Vector2, a2: Vector2| {
141            Vector2::distance_squared(a1, a2)
142        });
143        r.add2("DistanceSquared", |a1: Vector, a2: Vector| {
144            Vector::distance_squared(a1, a2)
145        });
146        r.add2("DistanceSquared", |a1: Vector3, a2: Vector3| {
147            Vector3::distance_squared(a1, a2)
148        });
149        r.add2("DistanceSquared", |a1: Vector3D, a2: Vector3D| {
150            Vector3D::distance_squared(a1, a2)
151        });
152        r.add2("DistanceSquared", |a1: Vector4, a2: Vector4| {
153            Vector4::distance_squared(a1, a2)
154        });
155
156        r.add1("Floor", |v: f32| via_f64(v, f64::floor));
157
158        r.add1("Inverse", |v: Matrix3x2| Matrix3x2::invert_or_nan(v));
159        r.add1("Inverse", |v: Matrix4x4| Matrix4x4::invert_or_nan(v));
160
161        r.add1("Length", |a1: Vector2| a1.length());
162        r.add1("Length", |a1: Vector| a1.length());
163        r.add1("Length", |a1: Vector3| a1.length());
164        r.add1("Length", |a1: Vector3D| a1.length());
165        r.add1("Length", |a1: Vector4| a1.length());
166        r.add1("Length", |a1: Quaternion| a1.length());
167
168        r.add1("LengthSquared", |a1: Vector2| a1.length_squared());
169        r.add1("LengthSquared", |a1: Vector| a1 * a1);
170        r.add1("LengthSquared", |a1: Vector3| a1.length_squared());
171        r.add1("LengthSquared", |a1: Vector3D| Vector3D::dot(a1, a1));
172        r.add1("LengthSquared", |a1: Vector4| a1.length_squared());
173        r.add1("LengthSquared", |a1: Quaternion| a1.length_squared());
174
175        r.add3("Lerp", |a1: f32, a2: f32, a3: f32| lerp(a1, a2, a3));
176        r.add3("Lerp", |a1: Vector2, a2: Vector2, a3: f32| Vector2::lerp(a1, a2, a3));
177        r.add3("Lerp", |a1: Vector, a2: Vector, a3: f32| {
178            Vector::new(
179                lerp_f64(a1.x, a2.x, a3 as f64),
180                lerp_f64(a1.y, a2.y, a3 as f64),
181            )
182        });
183        r.add3("Lerp", |a1: Vector3, a2: Vector3, a3: f32| Vector3::lerp(a1, a2, a3));
184        r.add3("Lerp", |a1: Vector3D, a2: Vector3D, a3: f32| {
185            Vector3D::new(
186                lerp_f64(a1.x, a2.x, a3 as f64),
187                lerp_f64(a1.y, a2.y, a3 as f64),
188                lerp_f64(a1.z, a2.z, a3 as f64),
189            )
190        });
191        r.add3("Lerp", |a1: Vector4, a2: Vector4, a3: f32| Vector4::lerp(a1, a2, a3));
192
193        r.add1("Ln", |f: f32| via_f64(f, f64::ln));
194        r.add1("Log10", |f: f32| via_f64(f, f64::log10));
195
196        r.add1("Matrix3x2.CreateFromScale", |v: Vector2| Matrix3x2::create_scale(v));
197        r.add1("Matrix3x2.CreateFromTranslation", |v: Vector2| {
198            Matrix3x2::create_translation(v)
199        });
200        r.add1("Matrix3x2.CreateRotation", |v: f32| Matrix3x2::create_rotation(v));
201        r.add1("Matrix3x2.CreateScale", |v: Vector2| Matrix3x2::create_scale(v));
202        r.add3("Matrix3x2.CreateSkew", |a1: f32, a2: f32, a3: Vector2| {
203            Matrix3x2::create_skew_at(a1, a2, a3)
204        });
205        // As in the reference implementation, this creates a scale matrix.
206        r.add1("Matrix3x2.CreateTranslation", |v: Vector2| Matrix3x2::create_scale(v));
207        r.add6(
208            "Matrix3x2",
209            |m11: f32, m12: f32, m21: f32, m22: f32, m31: f32, m32: f32| Matrix3x2::new(m11, m12, m21, m22, m31, m32),
210        );
211        r.add2("Matrix4x4.CreateFromAxisAngle", |v: Vector3, angle: f32| {
212            Matrix4x4::create_from_axis_angle(v, angle)
213        });
214        r.add1("Matrix4x4.CreateFromScale", |v: Vector3| Matrix4x4::create_scale(v));
215        r.add1("Matrix4x4.CreateFromTranslation", |v: Vector3| {
216            Matrix4x4::create_translation(v)
217        });
218        r.add1("Matrix4x4.CreateScale", |v: Vector3| Matrix4x4::create_scale(v));
219        // As in the reference implementation, this creates a scale matrix.
220        r.add1("Matrix4x4.CreateTranslation", |v: Vector3| Matrix4x4::create_scale(v));
221        r.add1("Matrix4x4", |m: Matrix3x2| Matrix4x4::from_matrix3x2(m));
222        #[allow(clippy::too_many_arguments)]
223        r.add16(
224            "Matrix4x4",
225            |m11: f32,
226             m12: f32,
227             m13: f32,
228             m14: f32,
229             m21: f32,
230             m22: f32,
231             m23: f32,
232             m24: f32,
233             m31: f32,
234             m32: f32,
235             m33: f32,
236             m34: f32,
237             m41: f32,
238             m42: f32,
239             m43: f32,
240             m44: f32| {
241                Matrix4x4::new(
242                    m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44,
243                )
244            },
245        );
246
247        r.add2("Max", |a1: f32, a2: f32| max_f32(a1, a2));
248        r.add2("Max", |a1: Vector2, a2: Vector2| Vector2::max(a1, a2));
249        r.add2("Max", |a1: Vector, a2: Vector| Vector::max(a1, a2));
250        r.add2("Max", |a1: Vector3, a2: Vector3| Vector3::max(a1, a2));
251        r.add2("Max", |a1: Vector3D, a2: Vector3D| Vector3D::max(a1, a2));
252        r.add2("Max", |a1: Vector4, a2: Vector4| Vector4::max(a1, a2));
253
254        r.add2("Min", |a1: f32, a2: f32| min_f32(a1, a2));
255        r.add2("Min", |a1: Vector2, a2: Vector2| Vector2::min(a1, a2));
256        r.add2("Min", |a1: Vector, a2: Vector| Vector::min(a1, a2));
257        r.add2("Min", |a1: Vector3, a2: Vector3| Vector3::min(a1, a2));
258        r.add2("Min", |a1: Vector3D, a2: Vector3D| Vector3D::min(a1, a2));
259        r.add2("Min", |a1: Vector4, a2: Vector4| Vector4::min(a1, a2));
260
261        r.add2("Mod", |a: f32, b: f32| a % b);
262
263        r.add1("Normalize", |a: Quaternion| Quaternion::normalize(a));
264        r.add1("Normalize", |a: Vector2| Vector2::normalize(a));
265        r.add1("Normalize", |a: Vector| Vector::normalize_vector(a));
266        r.add1("Normalize", |a: Vector3| Vector3::normalize(a));
267        r.add1("Normalize", |a: Vector3D| Vector3D::normalize(a));
268        r.add1("Normalize", |a: Vector4| Vector4::normalize(a));
269
270        r.add2("Pow", |a: f32, b: f32| (a as f64).powf(b as f64) as f32);
271        r.add2("Quaternion.CreateFromAxisAngle", |a: Vector3, b: f32| {
272            Quaternion::create_from_axis_angle(a, b)
273        });
274        r.add2("Quaternion.CreateFromAxisAngle", |a: Vector3D, b: f32| {
275            Quaternion::create_from_axis_angle(a.into(), b)
276        });
277        r.add4("Quaternion", |a: f32, b: f32, c: f32, d: f32| Quaternion::new(a, b, c, d));
278
279        r.add1("Round", |a: f32| via_f64(a, f64::round_ties_even));
280
281        r.add2("Scale", |a: Matrix3x2, b: f32| a * b);
282        r.add2("Scale", |a: Matrix4x4, b: f32| a * b);
283        r.add2("Scale", |a: Vector2, b: f32| a * b);
284        r.add2("Scale", |a: Vector, b: f32| a * (b as f64));
285        r.add2("Scale", |a: Vector3, b: f32| a * b);
286        r.add2("Scale", |a: Vector3D, b: f32| Vector3D::multiply_scalar(a, b as f64));
287        r.add2("Scale", |a: Vector4, b: f32| a * b);
288
289        r.add1("Sin", |a: f32| via_f64(a, f64::sin));
290
291        r.add3("SmoothStep", |a1: f32, a2: f32, a3: f32| smooth_step(a1, a2, a3));
292        r.add3("SmoothStep", |a1: Vector2, a2: Vector2, a3: Vector2| {
293            smooth_step_vector2(a1, a2, a3)
294        });
295        r.add3("SmoothStep", |a1: Vector, a2: Vector, a3: Vector| {
296            smooth_step_vector(a1, a2, a3)
297        });
298        r.add3("SmoothStep", |a1: Vector3, a2: Vector3, a3: Vector3| {
299            smooth_step_vector3(a1, a2, a3)
300        });
301        r.add3("SmoothStep", |a1: Vector3D, a2: Vector3D, a3: Vector3D| {
302            smooth_step_vector3d(a1, a2, a3)
303        });
304        r.add3("SmoothStep", |a1: Vector4, a2: Vector4, a3: Vector4| {
305            smooth_step_vector4(a1, a2, a3)
306        });
307
308        // I have no idea how to do a spherical interpolation for a scalar value, so we are doing a linear one
309        r.add3("Slerp", |a1: f32, a2: f32, a3: f32| lerp(a1, a2, a3));
310        r.add3("Slerp", |a1: Quaternion, a2: Quaternion, a3: f32| {
311            Quaternion::slerp(a1, a2, a3)
312        });
313
314        r.add1("Sqrt", |a: f32| via_f64(a, f64::sqrt));
315        r.add1("Square", |a: f32| a * a);
316        r.add1("Tan", |a: f32| via_f64(a, f64::tan));
317
318        r.add1("ToRadians", |a: f32| (a as f64 * std::f64::consts::PI / 180.0) as f32);
319        r.add1("ToDegrees", |a: f32| (a as f64 * 180.0 / std::f64::consts::PI) as f32);
320
321        r.add2("Transform", |a: Vector2, b: Matrix3x2| Vector2::transform(a, b));
322        r.add2("Transform", |a: Vector3, b: Matrix4x4| Vector3::transform(a, b));
323
324        r.add0("RelativeUnit.Absolute", || RelativeUnit::Absolute);
325        r.add0("RelativeUnit.Relative", || RelativeUnit::Relative);
326        r.add3("RelativePoint", |x: f32, y: f32, unit: RelativeUnit| {
327            RelativePoint::new(x as f64, y as f64, unit)
328        });
329        r.add3("RelativePoint", |x: f64, y: f64, unit: RelativeUnit| {
330            RelativePoint::new(x, y, unit)
331        });
332        r.add2("RelativeScalar", |value: f32, unit: RelativeUnit| {
333            RelativeScalar::new(value as f64, unit)
334        });
335        r.add2("RelativeScalar", |value: f64, unit: RelativeUnit| {
336            RelativeScalar::new(value, unit)
337        });
338
339        r.add2("Vector2", |a: f32, b: f32| Vector::new(a as f64, b as f64));
340        r.add2("Vector2", |a: f64, b: f64| Vector::new(a, b));
341        r.add3("Vector3", |a: f32, b: f32, c: f32| {
342            Vector3D::new(a as f64, b as f64, c as f64)
343        });
344        r.add3("Vector3", |a: f64, b: f64, c: f64| Vector3D::new(a, b, c));
345        r.add2("Vector3", |v2: Vector2, z: f32| Vector3::from_vector2(v2, z));
346        r.add2("Vector3", |v2: Vector, z: f32| Vector3D::new(v2.x, v2.y, z as f64));
347        r.add2("Vector3", |v2: Vector, z: f64| Vector3D::new(v2.x, v2.y, z));
348        r.add4("Vector4", |a: f32, b: f32, c: f32, d: f32| Vector4::new(a, b, c, d));
349        r.add3("Vector4", |v2: Vector2, z: f32, w: f32| Vector4::from_vector2(v2, z, w));
350        r.add2("Vector4", |v3: Vector3, w: f32| Vector4::from_vector3(v3, w));
351
352        Self { registry: r }
353    }
354
355    /// The shared instance.
356    pub fn instance() -> &'static BuiltInExpressionFfi {
357        static INSTANCE: OnceLock<BuiltInExpressionFfi> = OnceLock::new();
358        INSTANCE.get_or_init(BuiltInExpressionFfi::new)
359    }
360}
361
362impl IExpressionForeignFunctionInterface for BuiltInExpressionFfi {
363    fn call(&self, name: &str, arguments: &[ExpressionVariant]) -> Option<ExpressionVariant> {
364        self.registry.call(name, arguments)
365    }
366}