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, HslColor};
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
16///
17/// The functions are those of the WinUI composition expression reference
18/// (`ExpressionAnimation`, "Expression Functions per Type"), with the overloads
19/// the reference does not list but the WinUI expression engine accepts (the
20/// scalar `Length`, `Distance` and `Scale`, the vector `Mod`), a few
21/// `System.Numerics` overloads of the matrix factories, and the functions of
22/// the upstream framework (`SmoothStep`, the relative point and scalar
23/// constructors). Function names are case-insensitive, as in WinUI.
24pub struct BuiltInExpressionFfi {
25    registry: DelegateExpressionFfi,
26}
27
28fn lerp(a: f32, b: f32, p: f32) -> f32 {
29    (a * (1.0 - p)) + (b * p)
30}
31
32fn lerp_f64(a: f64, b: f64, p: f64) -> f64 {
33    (a * (1.0 - p)) + (b * p)
34}
35
36/// `ColorHsl(h, s, l)`: an opaque colour from a hue in radians (0 to 2 pi, other
37/// values wrap around) and a saturation and a lightness in 0..1 (clamped).
38fn color_hsl(hue_radians: f32, saturation: f32, lightness: f32) -> Color {
39    HslColor::hsl_to_rgb((hue_radians as f64).to_degrees(), saturation as f64, lightness as f64, 1.0)
40}
41
42/// `ColorLerpHSL(color1, color2, progress)`: interpolates in the HSL colour
43/// space, giving `color1` at progress 0 and `color2` at progress 1 (the
44/// argument order of `ColorLerp`). Saturation, lightness and alpha are
45/// interpolated linearly; the hue takes the shorter way around the colour
46/// wheel, and the hue of an achromatic colour (no saturation: black, white,
47/// greys) is that of the other colour, so a grey turns into a colour without
48/// passing through other hues. The interpolation is
49/// [`ColorInterpolator::lerp_hsl`], which the `Hsl` colour space of a colour
50/// animation uses too.
51fn color_lerp_hsl(color1: Color, color2: Color, progress: f32) -> Color {
52    ColorInterpolator::lerp_hsl(color1, color2, progress)
53}
54
55fn smooth_step(edge0: f32, edge1: f32, x: f32) -> f32 {
56    let t = MathUtilities::clamp_f32((x - edge0) / (edge1 - edge0), 0.0, 1.0);
57    t * t * (3.0 - 2.0 * t)
58}
59
60fn smooth_step_f64(edge0: f64, edge1: f64, x: f64) -> f64 {
61    let t = MathUtilities::clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
62    t * t * (3.0 - 2.0 * t)
63}
64
65fn smooth_step_vector2(edge0: Vector2, edge1: Vector2, x: Vector2) -> Vector2 {
66    Vector2::new(
67        smooth_step(edge0.x, edge1.x, x.x),
68        smooth_step(edge0.y, edge1.y, x.y),
69    )
70}
71
72fn smooth_step_vector(edge0: Vector, edge1: Vector, x: Vector) -> Vector {
73    Vector::new(
74        smooth_step_f64(edge0.x, edge1.x, x.x),
75        smooth_step_f64(edge0.y, edge1.y, x.y),
76    )
77}
78
79fn smooth_step_vector3(edge0: Vector3, edge1: Vector3, x: Vector3) -> Vector3 {
80    Vector3::new(
81        smooth_step(edge0.x, edge1.x, x.x),
82        smooth_step(edge0.y, edge1.y, x.y),
83        smooth_step(edge0.z, edge1.z, x.z),
84    )
85}
86
87fn smooth_step_vector3d(edge0: Vector3D, edge1: Vector3D, x: Vector3D) -> Vector3D {
88    Vector3D::new(
89        smooth_step_f64(edge0.x, edge1.x, x.x),
90        smooth_step_f64(edge0.y, edge1.y, x.y),
91        smooth_step_f64(edge0.z, edge1.z, x.z),
92    )
93}
94
95fn smooth_step_vector4(edge0: Vector4, edge1: Vector4, x: Vector4) -> Vector4 {
96    Vector4::new(
97        smooth_step(edge0.x, edge1.x, x.x),
98        smooth_step(edge0.y, edge1.y, x.y),
99        smooth_step(edge0.z, edge1.z, x.z),
100        smooth_step(edge0.w, edge1.w, x.w),
101    )
102}
103
104/// Applies a double-precision function to a single-precision value.
105#[inline]
106fn via_f64(value: f32, function: impl FnOnce(f64) -> f64) -> f32 {
107    function(value as f64) as f32
108}
109
110impl BuiltInExpressionFfi {
111    fn new() -> Self {
112        let mut r = DelegateExpressionFfi::new_ignoring_case();
113
114        r.add1("Abs", |f: f32| f.abs());
115        r.add1("Abs", |v: Vector2| Vector2::abs(v));
116        r.add1("Abs", |v: Vector| v.abs());
117        r.add1("Abs", |v: Vector3| Vector3::abs(v));
118        r.add1("Abs", |v: Vector3D| v.abs());
119        r.add1("Abs", |v: Vector4| Vector4::abs(v));
120
121        r.add1("ACos", |f: f32| via_f64(f, f64::acos));
122        r.add1("ASin", |f: f32| via_f64(f, f64::asin));
123        r.add1("ATan", |f: f32| via_f64(f, f64::atan));
124        r.add1("Ceil", |f: f32| via_f64(f, f64::ceil));
125
126        r.add3("Clamp", |a1: f32, a2: f32, a3: f32| MathUtilities::clamp_f32(a1, a2, a3));
127        r.add3("Clamp", |a1: Vector2, a2: Vector2, a3: Vector2| Vector2::clamp(a1, a2, a3));
128        r.add3("Clamp", |a1: Vector, a2: Vector, a3: Vector| Vector::clamp(a1, a2, a3));
129        r.add3("Clamp", |a1: Vector3, a2: Vector3, a3: Vector3| Vector3::clamp(a1, a2, a3));
130        r.add3("Clamp", |a1: Vector3D, a2: Vector3D, a3: Vector3D| {
131            Vector3D::clamp(a1, a2, a3)
132        });
133        r.add3("Clamp", |a1: Vector4, a2: Vector4, a3: Vector4| Vector4::clamp(a1, a2, a3));
134
135        r.add2("Concatenate", |a1: Quaternion, a2: Quaternion| {
136            Quaternion::concatenate(a1, a2)
137        });
138        r.add1("Cos", |a: f32| via_f64(a, f64::cos));
139
140        r.add3("ColorHsl", color_hsl);
141        r.add3("ColorLerp", |to: Color, from: Color, progress: f32| {
142            ColorInterpolator::lerp_rgb(to, from, progress)
143        });
144        r.add3("ColorLerpHSL", color_lerp_hsl);
145        // The name the WinUI expression builder writes for `ColorLerpHSL`.
146        r.add3("ColorHslLerp", color_lerp_hsl);
147        r.add3("ColorLerpRGB", |to: Color, from: Color, progress: f32| {
148            ColorInterpolator::lerp_rgb(to, from, progress)
149        });
150        // The name the WinUI expression builder writes for `ColorLerpRGB`.
151        r.add3("ColorRgbLerp", |to: Color, from: Color, progress: f32| {
152            ColorInterpolator::lerp_rgb(to, from, progress)
153        });
154        r.add4("ColorRGB", |a: f32, r: f32, g: f32, b: f32| {
155            Color::from_argb(
156                MathUtilities::clamp_f32(a, 0.0, 255.0) as u8,
157                MathUtilities::clamp_f32(r, 0.0, 255.0) as u8,
158                MathUtilities::clamp_f32(g, 0.0, 255.0) as u8,
159                MathUtilities::clamp_f32(b, 0.0, 255.0) as u8,
160            )
161        });
162
163        r.add2("Distance", |a1: f32, a2: f32| (a1 - a2).abs());
164        r.add2("Distance", |a1: Vector2, a2: Vector2| Vector2::distance(a1, a2));
165        r.add2("Distance", |a1: Vector, a2: Vector| Vector::distance(a1, a2));
166        r.add2("Distance", |a1: Vector3, a2: Vector3| Vector3::distance(a1, a2));
167        r.add2("Distance", |a1: Vector3D, a2: Vector3D| Vector3D::distance(a1, a2));
168        r.add2("Distance", |a1: Vector4, a2: Vector4| Vector4::distance(a1, a2));
169
170        r.add2("DistanceSquared", |a1: f32, a2: f32| (a1 - a2) * (a1 - a2));
171        r.add2("DistanceSquared", |a1: Vector2, a2: Vector2| {
172            Vector2::distance_squared(a1, a2)
173        });
174        r.add2("DistanceSquared", |a1: Vector, a2: Vector| {
175            Vector::distance_squared(a1, a2)
176        });
177        r.add2("DistanceSquared", |a1: Vector3, a2: Vector3| {
178            Vector3::distance_squared(a1, a2)
179        });
180        r.add2("DistanceSquared", |a1: Vector3D, a2: Vector3D| {
181            Vector3D::distance_squared(a1, a2)
182        });
183        r.add2("DistanceSquared", |a1: Vector4, a2: Vector4| {
184            Vector4::distance_squared(a1, a2)
185        });
186
187        r.add1("Floor", |v: f32| via_f64(v, f64::floor));
188
189        r.add1("Inverse", |v: Matrix3x2| Matrix3x2::invert_or_nan(v));
190        r.add1("Inverse", |v: Matrix4x4| Matrix4x4::invert_or_nan(v));
191
192        r.add1("Length", |a1: f32| a1.abs());
193        r.add1("Length", |a1: Vector2| a1.length());
194        r.add1("Length", |a1: Vector| a1.length());
195        r.add1("Length", |a1: Vector3| a1.length());
196        r.add1("Length", |a1: Vector3D| a1.length());
197        r.add1("Length", |a1: Vector4| a1.length());
198        r.add1("Length", |a1: Quaternion| a1.length());
199
200        r.add1("LengthSquared", |a1: f32| a1 * a1);
201        r.add1("LengthSquared", |a1: Vector2| a1.length_squared());
202        r.add1("LengthSquared", |a1: Vector| a1 * a1);
203        r.add1("LengthSquared", |a1: Vector3| a1.length_squared());
204        r.add1("LengthSquared", |a1: Vector3D| Vector3D::dot(a1, a1));
205        r.add1("LengthSquared", |a1: Vector4| a1.length_squared());
206        r.add1("LengthSquared", |a1: Quaternion| a1.length_squared());
207
208        r.add3("Lerp", |a1: f32, a2: f32, a3: f32| lerp(a1, a2, a3));
209        r.add3("Lerp", |a1: Vector2, a2: Vector2, a3: f32| Vector2::lerp(a1, a2, a3));
210        r.add3("Lerp", |a1: Vector, a2: Vector, a3: f32| {
211            Vector::new(
212                lerp_f64(a1.x, a2.x, a3 as f64),
213                lerp_f64(a1.y, a2.y, a3 as f64),
214            )
215        });
216        r.add3("Lerp", |a1: Vector3, a2: Vector3, a3: f32| Vector3::lerp(a1, a2, a3));
217        r.add3("Lerp", |a1: Vector3D, a2: Vector3D, a3: f32| {
218            Vector3D::new(
219                lerp_f64(a1.x, a2.x, a3 as f64),
220                lerp_f64(a1.y, a2.y, a3 as f64),
221                lerp_f64(a1.z, a2.z, a3 as f64),
222            )
223        });
224        r.add3("Lerp", |a1: Vector4, a2: Vector4, a3: f32| Vector4::lerp(a1, a2, a3));
225        r.add3("Lerp", |a1: Matrix3x2, a2: Matrix3x2, a3: f32| Matrix3x2::lerp(a1, a2, a3));
226        r.add3("Lerp", |a1: Matrix4x4, a2: Matrix4x4, a3: f32| Matrix4x4::lerp(a1, a2, a3));
227
228        r.add1("Ln", |f: f32| via_f64(f, f64::ln));
229        r.add1("Log10", |f: f32| via_f64(f, f64::log10));
230
231        // The static factories of the reference take the overloads listed
232        // there; the other overloads are those of `System.Numerics`.
233        r.add1("Matrix3x2.CreateFromScale", |v: Vector2| Matrix3x2::create_scale(v));
234        r.add1("Matrix3x2.CreateFromTranslation", |v: Vector2| {
235            Matrix3x2::create_translation(v)
236        });
237        r.add1("Matrix3x2.CreateRotation", |v: f32| Matrix3x2::create_rotation(v));
238        r.add2("Matrix3x2.CreateRotation", |v: f32, center: Vector2| {
239            Matrix3x2::create_rotation_at(v, center)
240        });
241        r.add1("Matrix3x2.CreateScale", |v: Vector2| Matrix3x2::create_scale(v));
242        r.add1("Matrix3x2.CreateScale", |v: f32| Matrix3x2::create_scale_uniform(v));
243        r.add2("Matrix3x2.CreateScale", |x: f32, y: f32| Matrix3x2::create_scale_xy(x, y));
244        r.add2("Matrix3x2.CreateScale", |v: Vector2, center: Vector2| {
245            Matrix3x2::create_scale_at(v, center)
246        });
247        r.add2("Matrix3x2.CreateSkew", |a1: f32, a2: f32| Matrix3x2::create_skew(a1, a2));
248        r.add3("Matrix3x2.CreateSkew", |a1: f32, a2: f32, a3: Vector2| {
249            Matrix3x2::create_skew_at(a1, a2, a3)
250        });
251        // The reference implementation creates a scale matrix here; WinUI
252        // documents it as the same function as `CreateFromTranslation`.
253        r.add1("Matrix3x2.CreateTranslation", |v: Vector2| Matrix3x2::create_translation(v));
254        r.add2("Matrix3x2.CreateTranslation", |x: f32, y: f32| {
255            Matrix3x2::create_translation_xy(x, y)
256        });
257        r.add6(
258            "Matrix3x2",
259            |m11: f32, m12: f32, m21: f32, m22: f32, m31: f32, m32: f32| Matrix3x2::new(m11, m12, m21, m22, m31, m32),
260        );
261        r.add2("Matrix4x4.CreateFromAxisAngle", |v: Vector3, angle: f32| {
262            Matrix4x4::create_from_axis_angle(v, angle)
263        });
264        r.add1("Matrix4x4.CreateFromScale", |v: Vector3| Matrix4x4::create_scale(v));
265        r.add1("Matrix4x4.CreateFromTranslation", |v: Vector3| {
266            Matrix4x4::create_translation(v)
267        });
268        r.add1("Matrix4x4.CreateRotationX", |angle: f32| Matrix4x4::create_rotation_x(angle));
269        r.add2("Matrix4x4.CreateRotationX", |angle: f32, center: Vector3| {
270            Matrix4x4::create_rotation_x_at(angle, center)
271        });
272        r.add1("Matrix4x4.CreateRotationY", |angle: f32| Matrix4x4::create_rotation_y(angle));
273        r.add2("Matrix4x4.CreateRotationY", |angle: f32, center: Vector3| {
274            Matrix4x4::create_rotation_y_at(angle, center)
275        });
276        r.add1("Matrix4x4.CreateRotationZ", |angle: f32| Matrix4x4::create_rotation_z(angle));
277        r.add2("Matrix4x4.CreateRotationZ", |angle: f32, center: Vector3| {
278            Matrix4x4::create_rotation_z_at(angle, center)
279        });
280        r.add1("Matrix4x4.CreateScale", |v: Vector3| Matrix4x4::create_scale(v));
281        r.add1("Matrix4x4.CreateScale", |v: f32| Matrix4x4::create_scale_uniform(v));
282        r.add3("Matrix4x4.CreateScale", |x: f32, y: f32, z: f32| {
283            Matrix4x4::create_scale_xyz(x, y, z)
284        });
285        r.add2("Matrix4x4.CreateScale", |v: Vector3, center: Vector3| {
286            Matrix4x4::create_scale_at(v, center)
287        });
288        // The reference implementation creates a scale matrix here; WinUI
289        // documents it as the same function as `CreateFromTranslation`.
290        r.add1("Matrix4x4.CreateTranslation", |v: Vector3| Matrix4x4::create_translation(v));
291        r.add3("Matrix4x4.CreateTranslation", |x: f32, y: f32, z: f32| {
292            Matrix4x4::create_translation_xyz(x, y, z)
293        });
294        r.add1("Matrix4x4", |m: Matrix3x2| Matrix4x4::from_matrix3x2(m));
295        #[allow(clippy::too_many_arguments)]
296        r.add16(
297            "Matrix4x4",
298            |m11: f32,
299             m12: f32,
300             m13: f32,
301             m14: f32,
302             m21: f32,
303             m22: f32,
304             m23: f32,
305             m24: f32,
306             m31: f32,
307             m32: f32,
308             m33: f32,
309             m34: f32,
310             m41: f32,
311             m42: f32,
312             m43: f32,
313             m44: f32| {
314                Matrix4x4::new(
315                    m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44,
316                )
317            },
318        );
319
320        r.add2("Max", |a1: f32, a2: f32| max_f32(a1, a2));
321        r.add2("Max", |a1: Vector2, a2: Vector2| Vector2::max(a1, a2));
322        r.add2("Max", |a1: Vector, a2: Vector| Vector::max(a1, a2));
323        r.add2("Max", |a1: Vector3, a2: Vector3| Vector3::max(a1, a2));
324        r.add2("Max", |a1: Vector3D, a2: Vector3D| Vector3D::max(a1, a2));
325        r.add2("Max", |a1: Vector4, a2: Vector4| Vector4::max(a1, a2));
326
327        r.add2("Min", |a1: f32, a2: f32| min_f32(a1, a2));
328        r.add2("Min", |a1: Vector2, a2: Vector2| Vector2::min(a1, a2));
329        r.add2("Min", |a1: Vector, a2: Vector| Vector::min(a1, a2));
330        r.add2("Min", |a1: Vector3, a2: Vector3| Vector3::min(a1, a2));
331        r.add2("Min", |a1: Vector3D, a2: Vector3D| Vector3D::min(a1, a2));
332        r.add2("Min", |a1: Vector4, a2: Vector4| Vector4::min(a1, a2));
333
334        r.add2("Mod", |a: f32, b: f32| a % b);
335        r.add2("Mod", |a: Vector2, b: Vector2| Vector2::new(a.x % b.x, a.y % b.y));
336        r.add2("Mod", |a: Vector, b: Vector| Vector::new(a.x % b.x, a.y % b.y));
337        r.add2("Mod", |a: Vector3, b: Vector3| Vector3::new(a.x % b.x, a.y % b.y, a.z % b.z));
338        r.add2("Mod", |a: Vector3D, b: Vector3D| {
339            Vector3D::new(a.x % b.x, a.y % b.y, a.z % b.z)
340        });
341        r.add2("Mod", |a: Vector4, b: Vector4| {
342            Vector4::new(a.x % b.x, a.y % b.y, a.z % b.z, a.w % b.w)
343        });
344
345        r.add1("Normalize", |a: Quaternion| Quaternion::normalize(a));
346        r.add1("Normalize", |a: Vector2| Vector2::normalize(a));
347        r.add1("Normalize", |a: Vector| Vector::normalize_vector(a));
348        r.add1("Normalize", |a: Vector3| Vector3::normalize(a));
349        r.add1("Normalize", |a: Vector3D| Vector3D::normalize(a));
350        r.add1("Normalize", |a: Vector4| Vector4::normalize(a));
351
352        r.add2("Pow", |a: f32, b: f32| (a as f64).powf(b as f64) as f32);
353        r.add2("Quaternion.CreateFromAxisAngle", |a: Vector3, b: f32| {
354            Quaternion::create_from_axis_angle(a, b)
355        });
356        r.add2("Quaternion.CreateFromAxisAngle", |a: Vector3D, b: f32| {
357            Quaternion::create_from_axis_angle(a.into(), b)
358        });
359        r.add4("Quaternion", |a: f32, b: f32, c: f32, d: f32| Quaternion::new(a, b, c, d));
360
361        r.add1("Round", |a: f32| via_f64(a, f64::round_ties_even));
362
363        r.add2("Scale", |a: f32, b: f32| a * b);
364        r.add2("Scale", |a: Matrix3x2, b: f32| a * b);
365        r.add2("Scale", |a: Matrix4x4, b: f32| a * b);
366        r.add2("Scale", |a: Vector2, b: f32| a * b);
367        r.add2("Scale", |a: Vector, b: f32| a * (b as f64));
368        r.add2("Scale", |a: Vector3, b: f32| a * b);
369        r.add2("Scale", |a: Vector3D, b: f32| Vector3D::multiply_scalar(a, b as f64));
370        r.add2("Scale", |a: Vector4, b: f32| a * b);
371
372        r.add1("Sin", |a: f32| via_f64(a, f64::sin));
373
374        r.add3("SmoothStep", |a1: f32, a2: f32, a3: f32| smooth_step(a1, a2, a3));
375        r.add3("SmoothStep", |a1: Vector2, a2: Vector2, a3: Vector2| {
376            smooth_step_vector2(a1, a2, a3)
377        });
378        r.add3("SmoothStep", |a1: Vector, a2: Vector, a3: Vector| {
379            smooth_step_vector(a1, a2, a3)
380        });
381        r.add3("SmoothStep", |a1: Vector3, a2: Vector3, a3: Vector3| {
382            smooth_step_vector3(a1, a2, a3)
383        });
384        r.add3("SmoothStep", |a1: Vector3D, a2: Vector3D, a3: Vector3D| {
385            smooth_step_vector3d(a1, a2, a3)
386        });
387        r.add3("SmoothStep", |a1: Vector4, a2: Vector4, a3: Vector4| {
388            smooth_step_vector4(a1, a2, a3)
389        });
390
391        // I have no idea how to do a spherical interpolation for a scalar value, so we are doing a linear one
392        r.add3("Slerp", |a1: f32, a2: f32, a3: f32| lerp(a1, a2, a3));
393        r.add3("Slerp", |a1: Quaternion, a2: Quaternion, a3: f32| {
394            Quaternion::slerp(a1, a2, a3)
395        });
396
397        r.add1("Sqrt", |a: f32| via_f64(a, f64::sqrt));
398        r.add1("Square", |a: f32| a * a);
399        r.add1("Tan", |a: f32| via_f64(a, f64::tan));
400
401        r.add1("ToRadians", |a: f32| (a as f64 * std::f64::consts::PI / 180.0) as f32);
402        r.add1("ToDegrees", |a: f32| (a as f64 * 180.0 / std::f64::consts::PI) as f32);
403
404        r.add2("Transform", |a: Vector2, b: Matrix3x2| Vector2::transform(a, b));
405        r.add2("Transform", |a: Vector3, b: Matrix4x4| Vector3::transform(a, b));
406        r.add2("Transform", |a: Vector4, b: Matrix4x4| Vector4::transform(a, b));
407
408        r.add0("RelativeUnit.Absolute", || RelativeUnit::Absolute);
409        r.add0("RelativeUnit.Relative", || RelativeUnit::Relative);
410        r.add3("RelativePoint", |x: f32, y: f32, unit: RelativeUnit| {
411            RelativePoint::new(x as f64, y as f64, unit)
412        });
413        r.add3("RelativePoint", |x: f64, y: f64, unit: RelativeUnit| {
414            RelativePoint::new(x, y, unit)
415        });
416        r.add2("RelativeScalar", |value: f32, unit: RelativeUnit| {
417            RelativeScalar::new(value as f64, unit)
418        });
419        r.add2("RelativeScalar", |value: f64, unit: RelativeUnit| {
420            RelativeScalar::new(value, unit)
421        });
422
423        r.add2("Vector2", |a: f32, b: f32| Vector::new(a as f64, b as f64));
424        r.add2("Vector2", |a: f64, b: f64| Vector::new(a, b));
425        r.add3("Vector3", |a: f32, b: f32, c: f32| {
426            Vector3D::new(a as f64, b as f64, c as f64)
427        });
428        r.add3("Vector3", |a: f64, b: f64, c: f64| Vector3D::new(a, b, c));
429        r.add2("Vector3", |v2: Vector2, z: f32| Vector3::from_vector2(v2, z));
430        r.add2("Vector3", |v2: Vector, z: f32| Vector3D::new(v2.x, v2.y, z as f64));
431        r.add2("Vector3", |v2: Vector, z: f64| Vector3D::new(v2.x, v2.y, z));
432        r.add4("Vector4", |a: f32, b: f32, c: f32, d: f32| Vector4::new(a, b, c, d));
433        r.add3("Vector4", |v2: Vector2, z: f32, w: f32| Vector4::from_vector2(v2, z, w));
434        r.add2("Vector4", |v3: Vector3, w: f32| Vector4::from_vector3(v3, w));
435
436        Self { registry: r }
437    }
438
439    /// The shared instance.
440    pub fn instance() -> &'static BuiltInExpressionFfi {
441        static INSTANCE: OnceLock<BuiltInExpressionFfi> = OnceLock::new();
442        INSTANCE.get_or_init(BuiltInExpressionFfi::new)
443    }
444}
445
446impl IExpressionForeignFunctionInterface for BuiltInExpressionFfi {
447    fn call(&self, name: &str, arguments: &[ExpressionVariant]) -> Option<ExpressionVariant> {
448        self.registry.call(name, arguments)
449    }
450}