Skip to main content

modelio/
transform.rs

1use std::panic::AssertUnwindSafe;
2use std::ptr;
3
4use crate::animated_value_types::{
5    AnimatedMatrix4x4, AnimatedQuaternion, AnimatedScalar, AnimatedValue, AnimatedVector3,
6};
7use crate::error::Result;
8use crate::ffi;
9use crate::handle::ObjectHandle;
10use crate::object::Object;
11use crate::protocols::Component;
12use crate::types::TransformOpRotationOrder;
13use crate::util::{c_string, required_handle, take_string};
14
15fn copy_matrix(
16    handle: *mut core::ffi::c_void,
17    getter: unsafe extern "C" fn(*mut core::ffi::c_void, *mut f32),
18) -> [f32; 16] {
19    let mut matrix = [0.0_f32; 16];
20    // SAFETY: The unsafe operation is valid in this context.
21    unsafe { getter(handle, matrix.as_mut_ptr()) };
22    matrix
23}
24
25fn array_objects<T, F>(
26    array_ptr: *mut core::ffi::c_void,
27    context: &'static str,
28    mut map: F,
29) -> Result<Vec<T>>
30where
31    F: FnMut(ObjectHandle) -> T,
32{
33    let array = required_handle(array_ptr, context)?;
34    // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
35    let count = unsafe { ffi::mdl_array_count(array.as_ptr()) as usize };
36    let mut values = Vec::with_capacity(count);
37    for index in 0..count {
38        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
39        let ptr = unsafe { ffi::mdl_array_object_at(array.as_ptr(), index as u64) };
40        // SAFETY: The unsafe operation is valid in this context.
41        if let Some(handle) = unsafe { ObjectHandle::from_retained_ptr(ptr) } {
42            values.push(map(handle));
43        }
44    }
45    Ok(values)
46}
47
48const IDENTITY_MATRIX_F32: [f32; 16] = [
49    1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
50];
51const IDENTITY_MATRIX_F64: [f64; 16] = [
52    1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
53];
54
55type TransformComponentCallbackFn =
56    dyn Fn(TransformComponentEvent) -> TransformComponentResponse + Send + Sync + 'static;
57type TransformOpCallbackFn = dyn Fn(TransformOpEvent) -> TransformOpResponse + Send + Sync + 'static;
58
59struct TransformComponentCallback {
60    callback: Box<TransformComponentCallbackFn>,
61}
62
63struct TransformOpCallback {
64    callback: Box<TransformOpCallbackFn>,
65}
66
67#[derive(Debug, Clone)]
68/// Describes one `MDLTransformComponent` protocol request routed into Rust.
69pub enum TransformComponentEvent {
70    /// Requests the current matrix.
71    Matrix,
72    /// Updates the current matrix.
73    SetMatrix([f32; 16]),
74    /// Requests whether the transform resets parent-space transforms.
75    ResetsTransform,
76    /// Updates whether the transform resets parent-space transforms.
77    SetResetsTransform(bool),
78    /// Requests the minimum sample time.
79    MinimumTime,
80    /// Requests the maximum sample time.
81    MaximumTime,
82    /// Requests the stored key times.
83    KeyTimes,
84    /// Sets a non-animated local transform.
85    SetLocalTransform([f32; 16]),
86    /// Sets a sampled local transform at the requested time.
87    SetLocalTransformForTime { transform: [f32; 16], time: f64 },
88    /// Requests the local transform at the requested time.
89    LocalTransformAtTime(f64),
90}
91
92#[derive(Debug, Clone)]
93/// Returns the result of one `MDLTransformComponent` protocol request.
94pub enum TransformComponentResponse {
95    /// Returns a matrix result.
96    Matrix([f32; 16]),
97    /// Returns a boolean result.
98    Bool(bool),
99    /// Returns a time result.
100    Time(f64),
101    /// Returns a key-time array result.
102    KeyTimes(Vec<f64>),
103    /// Indicates that the callback did not provide a value.
104    None,
105}
106
107#[derive(Debug, Clone)]
108/// Describes one `MDLTransformOp` protocol request routed into Rust.
109pub enum TransformOpEvent {
110    /// Requests the operation name.
111    Name,
112    /// Requests the single-precision matrix at the requested time.
113    Float4x4AtTime(f64),
114    /// Requests the double-precision matrix at the requested time.
115    Double4x4AtTime(f64),
116    /// Requests whether the operation is inverse.
117    IsInverseOp,
118}
119
120#[derive(Debug, Clone)]
121/// Returns the result of one `MDLTransformOp` protocol request.
122pub enum TransformOpResponse {
123    /// Returns an optional name.
124    Name(Option<String>),
125    /// Returns a single-precision matrix.
126    Float4x4([f32; 16]),
127    /// Returns a double-precision matrix.
128    Double4x4([f64; 16]),
129    /// Returns a boolean result.
130    Bool(bool),
131    /// Indicates that the callback did not provide a value.
132    None,
133}
134
135fn duplicate_c_string(value: &str) -> *mut core::ffi::c_char {
136    let Ok(value) = std::ffi::CString::new(value) else {
137        return ptr::null_mut();
138    };
139    // SAFETY: The unsafe operation is valid in this context.
140    unsafe { libc::strdup(value.as_ptr()) }
141}
142
143fn matrix_f32_from_ptr(values: *const f32) -> [f32; 16] {
144    if values.is_null() {
145        return IDENTITY_MATRIX_F32;
146    }
147    let mut matrix = [0.0_f32; 16];
148    // SAFETY: The unsafe operation is valid in this context.
149    unsafe { matrix.as_mut_ptr().copy_from_nonoverlapping(values, matrix.len()) };
150    matrix
151}
152
153fn write_matrix_f32(out_values: *mut f32, values: [f32; 16]) {
154    if out_values.is_null() {
155        return;
156    }
157    // SAFETY: The unsafe operation is valid in this context.
158    unsafe { out_values.copy_from_nonoverlapping(values.as_ptr(), values.len()) };
159}
160
161fn write_matrix_f64(out_values: *mut f64, values: [f64; 16]) {
162    if out_values.is_null() {
163        return;
164    }
165    // SAFETY: The unsafe operation is valid in this context.
166    unsafe { out_values.copy_from_nonoverlapping(values.as_ptr(), values.len()) };
167}
168
169fn transform_component_response(
170    context: *mut core::ffi::c_void,
171    event: TransformComponentEvent,
172) -> Option<TransformComponentResponse> {
173    let context = (!context.is_null()).then_some(context.cast::<TransformComponentCallback>())?;
174    std::panic::catch_unwind(AssertUnwindSafe(|| {
175        // SAFETY: The unsafe operation is valid in this context.
176        (unsafe { &*context }.callback)(event)
177    }))
178    .ok()
179}
180
181fn transform_component_matrix_response(context: *mut core::ffi::c_void) -> [f32; 16] {
182    match transform_component_response(context, TransformComponentEvent::Matrix) {
183        Some(TransformComponentResponse::Matrix(matrix)) => matrix,
184        _ => IDENTITY_MATRIX_F32,
185    }
186}
187
188fn transform_component_key_times_response(context: *mut core::ffi::c_void) -> Vec<f64> {
189    match transform_component_response(context, TransformComponentEvent::KeyTimes) {
190        Some(TransformComponentResponse::KeyTimes(key_times)) => key_times,
191        _ => vec![0.0],
192    }
193}
194
195fn transform_op_response(
196    context: *mut core::ffi::c_void,
197    event: TransformOpEvent,
198) -> Option<TransformOpResponse> {
199    let context = (!context.is_null()).then_some(context.cast::<TransformOpCallback>())?;
200    std::panic::catch_unwind(AssertUnwindSafe(|| {
201        // SAFETY: The unsafe operation is valid in this context.
202        (unsafe { &*context }.callback)(event)
203    }))
204    .ok()
205}
206
207fn transform_op_matrix_f32(context: *mut core::ffi::c_void, time: f64) -> [f32; 16] {
208    match transform_op_response(context, TransformOpEvent::Float4x4AtTime(time)) {
209        Some(TransformOpResponse::Float4x4(matrix)) => matrix,
210        Some(TransformOpResponse::Double4x4(matrix)) => matrix.map(|value| value as f32),
211        _ => IDENTITY_MATRIX_F32,
212    }
213}
214
215fn transform_op_matrix_f64(context: *mut core::ffi::c_void, time: f64) -> [f64; 16] {
216    match transform_op_response(context, TransformOpEvent::Double4x4AtTime(time)) {
217        Some(TransformOpResponse::Double4x4(matrix)) => matrix,
218        Some(TransformOpResponse::Float4x4(matrix)) => matrix.map(f64::from),
219        _ => IDENTITY_MATRIX_F64,
220    }
221}
222
223#[no_mangle]
224pub extern "C" fn mdlx_transform_component_copy_matrix(
225    context: *mut core::ffi::c_void,
226    out_values: *mut f32,
227) {
228    write_matrix_f32(out_values, transform_component_matrix_response(context));
229}
230
231#[no_mangle]
232pub extern "C" fn mdlx_transform_component_set_matrix(
233    context: *mut core::ffi::c_void,
234    values: *const f32,
235) {
236    let _ = transform_component_response(
237        context,
238        TransformComponentEvent::SetMatrix(matrix_f32_from_ptr(values)),
239    );
240}
241
242#[no_mangle]
243pub extern "C" fn mdlx_transform_component_resets_transform(
244    context: *mut core::ffi::c_void,
245) -> i32 {
246    match transform_component_response(context, TransformComponentEvent::ResetsTransform) {
247        Some(TransformComponentResponse::Bool(resets_transform)) => i32::from(resets_transform),
248        _ => 0,
249    }
250}
251
252#[no_mangle]
253pub extern "C" fn mdlx_transform_component_set_resets_transform(
254    context: *mut core::ffi::c_void,
255    resets_transform: i32,
256) {
257    let _ = transform_component_response(
258        context,
259        TransformComponentEvent::SetResetsTransform(resets_transform != 0),
260    );
261}
262
263#[no_mangle]
264pub extern "C" fn mdlx_transform_component_minimum_time(
265    context: *mut core::ffi::c_void,
266) -> f64 {
267    match transform_component_response(context, TransformComponentEvent::MinimumTime) {
268        Some(TransformComponentResponse::Time(time)) => time,
269        _ => 0.0,
270    }
271}
272
273#[no_mangle]
274pub extern "C" fn mdlx_transform_component_maximum_time(
275    context: *mut core::ffi::c_void,
276) -> f64 {
277    match transform_component_response(context, TransformComponentEvent::MaximumTime) {
278        Some(TransformComponentResponse::Time(time)) => time,
279        _ => 0.0,
280    }
281}
282
283#[no_mangle]
284pub extern "C" fn mdlx_transform_component_key_time_count(
285    context: *mut core::ffi::c_void,
286) -> u64 {
287    transform_component_key_times_response(context).len() as u64
288}
289
290#[no_mangle]
291pub extern "C" fn mdlx_transform_component_copy_key_times(
292    context: *mut core::ffi::c_void,
293    out_values: *mut f64,
294    capacity: u64,
295) -> u64 {
296    let values = transform_component_key_times_response(context);
297    let total = values.len();
298    if out_values.is_null() || capacity == 0 {
299        return total as u64;
300    }
301    let write_count = total.min(capacity as usize);
302    // SAFETY: The unsafe operation is valid in this context.
303    unsafe { out_values.copy_from_nonoverlapping(values.as_ptr(), write_count) };
304    total as u64
305}
306
307#[no_mangle]
308pub extern "C" fn mdlx_transform_component_set_local_transform(
309    context: *mut core::ffi::c_void,
310    values: *const f32,
311) {
312    let _ = transform_component_response(
313        context,
314        TransformComponentEvent::SetLocalTransform(matrix_f32_from_ptr(values)),
315    );
316}
317
318#[no_mangle]
319pub extern "C" fn mdlx_transform_component_set_local_transform_for_time(
320    context: *mut core::ffi::c_void,
321    values: *const f32,
322    time: f64,
323) {
324    let _ = transform_component_response(
325        context,
326        TransformComponentEvent::SetLocalTransformForTime {
327            transform: matrix_f32_from_ptr(values),
328            time,
329        },
330    );
331}
332
333#[no_mangle]
334pub extern "C" fn mdlx_transform_component_copy_local_transform_at_time(
335    context: *mut core::ffi::c_void,
336    time: f64,
337    out_values: *mut f32,
338) {
339    let matrix = match transform_component_response(context, TransformComponentEvent::LocalTransformAtTime(time)) {
340        Some(TransformComponentResponse::Matrix(matrix)) => matrix,
341        _ => transform_component_matrix_response(context),
342    };
343    write_matrix_f32(out_values, matrix);
344}
345
346#[no_mangle]
347pub extern "C" fn mdlx_transform_component_release(context: *mut core::ffi::c_void) {
348    if context.is_null() {
349        return;
350    }
351    // SAFETY: The unsafe operation is valid in this context.
352    unsafe { drop(Box::from_raw(context.cast::<TransformComponentCallback>())) };
353}
354
355#[no_mangle]
356pub extern "C" fn mdlx_transform_op_name(
357    context: *mut core::ffi::c_void,
358) -> *mut core::ffi::c_char {
359    match transform_op_response(context, TransformOpEvent::Name) {
360        Some(TransformOpResponse::Name(Some(name))) => duplicate_c_string(&name),
361        _ => ptr::null_mut(),
362    }
363}
364
365#[no_mangle]
366pub extern "C" fn mdlx_transform_op_is_inverse(context: *mut core::ffi::c_void) -> i32 {
367    match transform_op_response(context, TransformOpEvent::IsInverseOp) {
368        Some(TransformOpResponse::Bool(is_inverse)) => i32::from(is_inverse),
369        _ => 0,
370    }
371}
372
373#[no_mangle]
374pub extern "C" fn mdlx_transform_op_copy_float4x4_at_time(
375    context: *mut core::ffi::c_void,
376    time: f64,
377    out_values: *mut f32,
378) {
379    write_matrix_f32(out_values, transform_op_matrix_f32(context, time));
380}
381
382#[no_mangle]
383pub extern "C" fn mdlx_transform_op_copy_double4x4_at_time(
384    context: *mut core::ffi::c_void,
385    time: f64,
386    out_values: *mut f64,
387) {
388    write_matrix_f64(out_values, transform_op_matrix_f64(context, time));
389}
390
391#[no_mangle]
392pub extern "C" fn mdlx_transform_op_release(context: *mut core::ffi::c_void) {
393    if context.is_null() {
394        return;
395    }
396    // SAFETY: The unsafe operation is valid in this context.
397    unsafe { drop(Box::from_raw(context.cast::<TransformOpCallback>())) };
398}
399
400fn release_transform_component_callback_context(context: *mut core::ffi::c_void) {
401    mdlx_transform_component_release(context);
402}
403
404fn release_transform_op_callback_context(context: *mut core::ffi::c_void) {
405    mdlx_transform_op_release(context);
406}
407
408#[derive(Debug, Clone)]
409/// Wraps the corresponding Model I/O transform component counterpart.
410pub struct TransformComponent {
411    handle: ObjectHandle,
412}
413
414impl Component for TransformComponent {}
415
416impl TransformComponent {
417    /// Wraps a Rust callback as the corresponding Model I/O transform component protocol counterpart.
418    pub fn new<F>(callback: F) -> Result<Self>
419    where
420        F: Fn(TransformComponentEvent) -> TransformComponentResponse + Send + Sync + 'static,
421    {
422        let callback = Box::new(TransformComponentCallback {
423            callback: Box::new(callback),
424        });
425        let callback_ptr = Box::into_raw(callback).cast::<core::ffi::c_void>();
426        let mut out_component = ptr::null_mut();
427        let mut out_error = ptr::null_mut();
428        // SAFETY: The unsafe operation is valid in this context.
429        let status = unsafe {
430            ffi::mdl_transform_component_new_with_callback(
431                callback_ptr,
432                &mut out_component,
433                &mut out_error,
434            )
435        };
436        if let Err(error) = crate::util::status_result(status, out_error) {
437            release_transform_component_callback_context(callback_ptr);
438            return Err(error);
439        }
440        match required_handle(out_component, "MDLTransformComponent") {
441            Ok(handle) => Ok(Self::from_handle(handle)),
442            Err(error) => {
443                release_transform_component_callback_context(callback_ptr);
444                Err(error)
445            }
446        }
447    }
448
449    /// Builds this wrapper from the retained handle of the wrapped Model I/O transform component counterpart.
450    pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
451        Self { handle }
452    }
453
454    /// Returns the opaque pointer used to call the wrapped Model I/O transform component counterpart.
455    pub(crate) fn as_ptr(&self) -> *mut core::ffi::c_void {
456        self.handle.as_ptr()
457    }
458
459    #[must_use]
460    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform component counterpart.
461    pub fn matrix(&self) -> [f32; 16] {
462        copy_matrix(self.handle.as_ptr(), ffi::mdl_transform_component_matrix)
463    }
464
465    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform component counterpart.
466    pub fn set_matrix(&self, matrix: [f32; 16]) {
467        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
468        unsafe { ffi::mdl_transform_component_set_matrix(self.handle.as_ptr(), matrix.as_ptr()) };
469    }
470
471    #[must_use]
472    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform component counterpart.
473    pub fn resets_transform(&self) -> bool {
474        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
475        unsafe { ffi::mdl_transform_component_resets_transform(self.handle.as_ptr()) != 0 }
476    }
477
478    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform component counterpart.
479    pub fn set_resets_transform(&self, resets_transform: bool) {
480        // SAFETY: The unsafe operation is valid in this context.
481        unsafe {
482            ffi::mdl_transform_component_set_resets_transform(
483                self.handle.as_ptr(),
484                i32::from(resets_transform),
485            );
486        }
487    }
488
489    #[must_use]
490    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform component counterpart.
491    pub fn minimum_time(&self) -> f64 {
492        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
493        unsafe { ffi::mdl_transform_component_minimum_time(self.handle.as_ptr()) }
494    }
495
496    #[must_use]
497    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform component counterpart.
498    pub fn maximum_time(&self) -> f64 {
499        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
500        unsafe { ffi::mdl_transform_component_maximum_time(self.handle.as_ptr()) }
501    }
502
503    #[must_use]
504    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform component counterpart.
505    pub fn key_times(&self) -> Vec<f64> {
506        let count =
507            // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
508            unsafe { ffi::mdl_transform_component_key_time_count(self.handle.as_ptr()) as usize };
509        let mut values = vec![0.0_f64; count];
510        if values.is_empty() {
511            return values;
512        }
513        // SAFETY: The unsafe operation is valid in this context.
514        let written = unsafe {
515            ffi::mdl_transform_component_copy_key_times(
516                self.handle.as_ptr(),
517                values.as_mut_ptr(),
518                values.len() as u64,
519            )
520        } as usize;
521        values.truncate(written);
522        values
523    }
524
525    #[must_use]
526    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform component counterpart.
527    pub fn local_transform_at_time(&self, time: f64) -> [f32; 16] {
528        let mut matrix = [0.0_f32; 16];
529        // SAFETY: The unsafe operation is valid in this context.
530        unsafe {
531            ffi::mdl_transform_component_local_transform_at_time(
532                self.handle.as_ptr(),
533                time,
534                matrix.as_mut_ptr(),
535            );
536        }
537        matrix
538    }
539
540    #[must_use]
541    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform component counterpart.
542    pub fn as_transform(&self) -> Option<Transform> {
543        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
544        (unsafe { ffi::mdl_transform_component_is_transform(self.handle.as_ptr()) != 0 })
545            .then(|| Transform::from_handle(self.handle.clone()))
546    }
547
548    #[must_use]
549    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform component counterpart.
550    pub fn as_transform_stack(&self) -> Option<TransformStack> {
551        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
552        (unsafe { ffi::mdl_transform_component_is_transform_stack(self.handle.as_ptr()) != 0 })
553            .then(|| TransformStack::from_handle(self.handle.clone()))
554    }
555
556    #[must_use]
557    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform component counterpart.
558    pub fn global_transform_with_object(object: &Object, time: f64) -> [f32; 16] {
559        let mut matrix = [0.0_f32; 16];
560        // SAFETY: The unsafe operation is valid in this context.
561        unsafe {
562            ffi::mdl_transform_component_global_transform_with_object(
563                object.as_ptr(),
564                time,
565                matrix.as_mut_ptr(),
566            );
567        }
568        matrix
569    }
570}
571
572#[derive(Debug, Clone)]
573/// Wraps the corresponding Model I/O transform counterpart.
574pub struct Transform {
575    handle: ObjectHandle,
576}
577
578impl Component for Transform {}
579
580impl Transform {
581    /// Builds this wrapper from the retained handle of the wrapped Model I/O transform counterpart.
582    pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
583        Self { handle }
584    }
585
586    /// Returns the opaque pointer used to call the wrapped Model I/O transform counterpart.
587    pub(crate) fn as_ptr(&self) -> *mut core::ffi::c_void {
588        self.handle.as_ptr()
589    }
590
591    /// Wraps the corresponding Model I/O initializer for the wrapped Model I/O transform counterpart.
592    pub fn new() -> Result<Self> {
593        let mut out_transform = ptr::null_mut();
594        let mut out_error = ptr::null_mut();
595        // SAFETY: Output pointers are initialized and managed; FFI function is called safely.
596        let status = unsafe { ffi::mdl_transform_new(&mut out_transform, &mut out_error) };
597        crate::util::status_result(status, out_error)?;
598        Ok(Self::from_handle(required_handle(
599            out_transform,
600            "MDLTransform",
601        )?))
602    }
603
604    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
605    pub fn from_component(component: &TransformComponent) -> Result<Self> {
606        let mut out_transform = ptr::null_mut();
607        let mut out_error = ptr::null_mut();
608        // SAFETY: The unsafe operation is valid in this context.
609        let status = unsafe {
610            ffi::mdl_transform_new_with_component(
611                component.as_ptr(),
612                &mut out_transform,
613                &mut out_error,
614            )
615        };
616        crate::util::status_result(status, out_error)?;
617        Ok(Self::from_handle(required_handle(
618            out_transform,
619            "MDLTransform",
620        )?))
621    }
622
623    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
624    pub fn from_component_with_resets_transform(
625        component: &TransformComponent,
626        resets_transform: bool,
627    ) -> Result<Self> {
628        let mut out_transform = ptr::null_mut();
629        let mut out_error = ptr::null_mut();
630        // SAFETY: The unsafe operation is valid in this context.
631        let status = unsafe {
632            ffi::mdl_transform_new_with_component_resets_transform(
633                component.as_ptr(),
634                i32::from(resets_transform),
635                &mut out_transform,
636                &mut out_error,
637            )
638        };
639        crate::util::status_result(status, out_error)?;
640        Ok(Self::from_handle(required_handle(
641            out_transform,
642            "MDLTransform",
643        )?))
644    }
645
646    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
647    pub fn from_matrix(matrix: [f32; 16]) -> Result<Self> {
648        let mut out_transform = ptr::null_mut();
649        let mut out_error = ptr::null_mut();
650        // SAFETY: The unsafe operation is valid in this context.
651        let status = unsafe {
652            ffi::mdl_transform_new_with_matrix(matrix.as_ptr(), &mut out_transform, &mut out_error)
653        };
654        crate::util::status_result(status, out_error)?;
655        Ok(Self::from_handle(required_handle(
656            out_transform,
657            "MDLTransform",
658        )?))
659    }
660
661    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
662    pub fn from_matrix_with_resets_transform(
663        matrix: [f32; 16],
664        resets_transform: bool,
665    ) -> Result<Self> {
666        let mut out_transform = ptr::null_mut();
667        let mut out_error = ptr::null_mut();
668        // SAFETY: The unsafe operation is valid in this context.
669        let status = unsafe {
670            ffi::mdl_transform_new_with_matrix_resets_transform(
671                matrix.as_ptr(),
672                i32::from(resets_transform),
673                &mut out_transform,
674                &mut out_error,
675            )
676        };
677        crate::util::status_result(status, out_error)?;
678        Ok(Self::from_handle(required_handle(
679            out_transform,
680            "MDLTransform",
681        )?))
682    }
683
684    #[must_use]
685    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
686    pub fn matrix(&self) -> [f32; 16] {
687        self.as_transform_component().matrix()
688    }
689
690    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
691    pub fn set_matrix(&self, matrix: [f32; 16]) {
692        self.as_transform_component().set_matrix(matrix);
693    }
694
695    #[must_use]
696    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
697    pub fn resets_transform(&self) -> bool {
698        self.as_transform_component().resets_transform()
699    }
700
701    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
702    pub fn set_resets_transform(&self, resets_transform: bool) {
703        self.as_transform_component()
704            .set_resets_transform(resets_transform);
705    }
706
707    #[must_use]
708    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
709    pub fn minimum_time(&self) -> f64 {
710        self.as_transform_component().minimum_time()
711    }
712
713    #[must_use]
714    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
715    pub fn maximum_time(&self) -> f64 {
716        self.as_transform_component().maximum_time()
717    }
718
719    #[must_use]
720    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
721    pub fn key_times(&self) -> Vec<f64> {
722        self.as_transform_component().key_times()
723    }
724
725    #[must_use]
726    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
727    pub fn local_transform_at_time(&self, time: f64) -> [f32; 16] {
728        self.as_transform_component().local_transform_at_time(time)
729    }
730
731    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
732    pub fn set_identity(&self) {
733        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
734        unsafe { ffi::mdl_transform_set_identity(self.handle.as_ptr()) };
735    }
736
737    #[must_use]
738    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
739    pub fn translation_at_time(&self, time: f64) -> [f32; 3] {
740        let mut value = [0.0_f32; 3];
741        // SAFETY: The unsafe operation is valid in this context.
742        unsafe {
743            ffi::mdl_transform_translation_at_time(self.handle.as_ptr(), time, value.as_mut_ptr());
744        }
745        value
746    }
747
748    #[must_use]
749    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
750    pub fn rotation_at_time(&self, time: f64) -> [f32; 3] {
751        let mut value = [0.0_f32; 3];
752        // SAFETY: The unsafe operation is valid in this context.
753        unsafe {
754            ffi::mdl_transform_rotation_at_time(self.handle.as_ptr(), time, value.as_mut_ptr());
755        }
756        value
757    }
758
759    #[must_use]
760    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
761    pub fn shear_at_time(&self, time: f64) -> [f32; 3] {
762        let mut value = [0.0_f32; 3];
763        // SAFETY: The unsafe operation is valid in this context.
764        unsafe {
765            ffi::mdl_transform_shear_at_time(self.handle.as_ptr(), time, value.as_mut_ptr());
766        }
767        value
768    }
769
770    #[must_use]
771    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
772    pub fn scale_at_time(&self, time: f64) -> [f32; 3] {
773        let mut value = [0.0_f32; 3];
774        // SAFETY: The unsafe operation is valid in this context.
775        unsafe {
776            ffi::mdl_transform_scale_at_time(self.handle.as_ptr(), time, value.as_mut_ptr());
777        }
778        value
779    }
780
781    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
782    pub fn set_matrix_for_time(&self, matrix: [f32; 16], time: f64) {
783        // SAFETY: The unsafe operation is valid in this context.
784        unsafe {
785            ffi::mdl_transform_set_matrix_for_time(self.handle.as_ptr(), matrix.as_ptr(), time);
786        };
787    }
788
789    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
790    pub fn set_translation_for_time(&self, translation: [f32; 3], time: f64) {
791        // SAFETY: The unsafe operation is valid in this context.
792        unsafe {
793            ffi::mdl_transform_set_translation_for_time(
794                self.handle.as_ptr(),
795                translation[0],
796                translation[1],
797                translation[2],
798                time,
799            );
800        }
801    }
802
803    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
804    pub fn set_rotation_for_time(&self, rotation: [f32; 3], time: f64) {
805        // SAFETY: The unsafe operation is valid in this context.
806        unsafe {
807            ffi::mdl_transform_set_rotation_for_time(
808                self.handle.as_ptr(),
809                rotation[0],
810                rotation[1],
811                rotation[2],
812                time,
813            );
814        }
815    }
816
817    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
818    pub fn set_shear_for_time(&self, shear: [f32; 3], time: f64) {
819        // SAFETY: The unsafe operation is valid in this context.
820        unsafe {
821            ffi::mdl_transform_set_shear_for_time(
822                self.handle.as_ptr(),
823                shear[0],
824                shear[1],
825                shear[2],
826                time,
827            );
828        }
829    }
830
831    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
832    pub fn set_scale_for_time(&self, scale: [f32; 3], time: f64) {
833        // SAFETY: The unsafe operation is valid in this context.
834        unsafe {
835            ffi::mdl_transform_set_scale_for_time(
836                self.handle.as_ptr(),
837                scale[0],
838                scale[1],
839                scale[2],
840                time,
841            );
842        }
843    }
844
845    #[must_use]
846    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
847    pub fn rotation_matrix_at_time(&self, time: f64) -> [f32; 16] {
848        let mut matrix = [0.0_f32; 16];
849        // SAFETY: The unsafe operation is valid in this context.
850        unsafe {
851            ffi::mdl_transform_rotation_matrix_at_time(
852                self.handle.as_ptr(),
853                time,
854                matrix.as_mut_ptr(),
855            );
856        }
857        matrix
858    }
859
860    #[must_use]
861    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
862    pub fn translation(&self) -> [f32; 3] {
863        let mut value = [0.0_f32; 3];
864        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
865        unsafe { ffi::mdl_transform_translation(self.handle.as_ptr(), value.as_mut_ptr()) };
866        value
867    }
868
869    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
870    pub fn set_translation(&self, translation: [f32; 3]) {
871        // SAFETY: The unsafe operation is valid in this context.
872        unsafe {
873            ffi::mdl_transform_set_translation(
874                self.handle.as_ptr(),
875                translation[0],
876                translation[1],
877                translation[2],
878            );
879        }
880    }
881
882    #[must_use]
883    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
884    pub fn rotation(&self) -> [f32; 3] {
885        let mut value = [0.0_f32; 3];
886        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
887        unsafe { ffi::mdl_transform_rotation(self.handle.as_ptr(), value.as_mut_ptr()) };
888        value
889    }
890
891    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
892    pub fn set_rotation(&self, rotation: [f32; 3]) {
893        // SAFETY: The unsafe operation is valid in this context.
894        unsafe {
895            ffi::mdl_transform_set_rotation(
896                self.handle.as_ptr(),
897                rotation[0],
898                rotation[1],
899                rotation[2],
900            );
901        }
902    }
903
904    #[must_use]
905    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
906    pub fn shear(&self) -> [f32; 3] {
907        let mut value = [0.0_f32; 3];
908        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
909        unsafe { ffi::mdl_transform_shear(self.handle.as_ptr(), value.as_mut_ptr()) };
910        value
911    }
912
913    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
914    pub fn set_shear(&self, shear: [f32; 3]) {
915        // SAFETY: The unsafe operation is valid in this context.
916        unsafe {
917            ffi::mdl_transform_set_shear(self.handle.as_ptr(), shear[0], shear[1], shear[2]);
918        }
919    }
920
921    #[must_use]
922    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
923    pub fn scale(&self) -> [f32; 3] {
924        let mut value = [0.0_f32; 3];
925        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
926        unsafe { ffi::mdl_transform_scale(self.handle.as_ptr(), value.as_mut_ptr()) };
927        value
928    }
929
930    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
931    pub fn set_scale(&self, scale: [f32; 3]) {
932        // SAFETY: The unsafe operation is valid in this context.
933        unsafe {
934            ffi::mdl_transform_set_scale(self.handle.as_ptr(), scale[0], scale[1], scale[2]);
935        }
936    }
937
938    #[must_use]
939    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform counterpart.
940    pub fn as_transform_component(&self) -> TransformComponent {
941        TransformComponent::from_handle(self.handle.clone())
942    }
943}
944
945#[derive(Debug, Clone)]
946/// Wraps the corresponding Model I/O transform op counterpart.
947pub struct TransformOp {
948    handle: ObjectHandle,
949}
950
951impl TransformOp {
952    /// Wraps a Rust callback as the corresponding Model I/O transform op protocol counterpart.
953    pub fn new<F>(callback: F) -> Result<Self>
954    where
955        F: Fn(TransformOpEvent) -> TransformOpResponse + Send + Sync + 'static,
956    {
957        let callback = Box::new(TransformOpCallback {
958            callback: Box::new(callback),
959        });
960        let callback_ptr = Box::into_raw(callback).cast::<core::ffi::c_void>();
961        let mut out_transform_op = ptr::null_mut();
962        let mut out_error = ptr::null_mut();
963        // SAFETY: The unsafe operation is valid in this context.
964        let status = unsafe {
965            ffi::mdl_transform_op_new_with_callback(
966                callback_ptr,
967                &mut out_transform_op,
968                &mut out_error,
969            )
970        };
971        if let Err(error) = crate::util::status_result(status, out_error) {
972            release_transform_op_callback_context(callback_ptr);
973            return Err(error);
974        }
975        match required_handle(out_transform_op, "MDLTransformOp") {
976            Ok(handle) => Ok(Self::from_handle(handle)),
977            Err(error) => {
978                release_transform_op_callback_context(callback_ptr);
979                Err(error)
980            }
981        }
982    }
983
984    /// Builds this wrapper from the retained handle of the wrapped Model I/O transform op counterpart.
985    pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
986        Self { handle }
987    }
988
989    #[must_use]
990    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform op counterpart.
991    pub fn name(&self) -> Option<String> {
992        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
993        take_string(unsafe { ffi::mdl_transform_op_name_string(self.handle.as_ptr()) })
994    }
995
996    #[must_use]
997    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform op counterpart.
998    pub fn is_inverse(&self) -> bool {
999        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
1000        unsafe { ffi::mdl_transform_op_is_inverse(self.handle.as_ptr()) != 0 }
1001    }
1002
1003    #[must_use]
1004    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform op counterpart.
1005    pub fn float4x4_at_time(&self, time: f64) -> [f32; 16] {
1006        let mut matrix = [0.0_f32; 16];
1007        // SAFETY: The unsafe operation is valid in this context.
1008        unsafe {
1009            ffi::mdl_transform_op_copy_float4x4_at_time(
1010                self.handle.as_ptr(),
1011                time,
1012                matrix.as_mut_ptr(),
1013            );
1014        };
1015        matrix
1016    }
1017
1018    #[must_use]
1019    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform op counterpart.
1020    pub fn double4x4_at_time(&self, time: f64) -> [f64; 16] {
1021        let mut matrix = [0.0_f64; 16];
1022        // SAFETY: The unsafe operation is valid in this context.
1023        unsafe {
1024            ffi::mdl_transform_op_copy_double4x4_at_time(
1025                self.handle.as_ptr(),
1026                time,
1027                matrix.as_mut_ptr(),
1028            );
1029        };
1030        matrix
1031    }
1032}
1033
1034macro_rules! define_transform_op {
1035    ($name:ident, $ffi_name:ident, $animated:ty) => {
1036        #[derive(Debug, Clone)]
1037        /// Wraps the corresponding Model I/O counterpart.
1038        pub struct $name {
1039            handle: ObjectHandle,
1040        }
1041
1042        impl $name {
1043            /// Builds this wrapper from the retained handle of the wrapped Model I/O name counterpart.
1044            pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
1045                Self { handle }
1046            }
1047
1048            #[must_use]
1049            /// Calls the corresponding Model I/O method on the wrapped Model I/O name counterpart.
1050            pub fn name(&self) -> Option<String> {
1051                TransformOp::from_handle(self.handle.clone()).name()
1052            }
1053
1054            #[must_use]
1055            /// Calls the corresponding Model I/O method on the wrapped Model I/O name counterpart.
1056            pub fn is_inverse(&self) -> bool {
1057                TransformOp::from_handle(self.handle.clone()).is_inverse()
1058            }
1059
1060            #[must_use]
1061            /// Calls the corresponding Model I/O method on the wrapped Model I/O name counterpart.
1062            pub fn float4x4_at_time(&self, time: f64) -> [f32; 16] {
1063                TransformOp::from_handle(self.handle.clone()).float4x4_at_time(time)
1064            }
1065
1066            #[must_use]
1067            /// Calls the corresponding Model I/O method on the wrapped Model I/O name counterpart.
1068            pub fn animated_value(&self) -> Option<$animated> {
1069                // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
1070                let ptr = unsafe { ffi::$ffi_name(self.handle.as_ptr()) };
1071                // SAFETY: The unsafe operation is valid in this context.
1072                unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(<$animated>::from_handle)
1073            }
1074
1075            #[must_use]
1076            /// Calls the corresponding Model I/O method on the wrapped Model I/O name counterpart.
1077            pub fn as_transform_op(&self) -> TransformOp {
1078                TransformOp::from_handle(self.handle.clone())
1079            }
1080        }
1081    };
1082}
1083
1084define_transform_op!(
1085    TransformRotateXOp,
1086    mdl_transform_rotate_x_op_animated_value,
1087    AnimatedScalar
1088);
1089define_transform_op!(
1090    TransformRotateYOp,
1091    mdl_transform_rotate_y_op_animated_value,
1092    AnimatedScalar
1093);
1094define_transform_op!(
1095    TransformRotateZOp,
1096    mdl_transform_rotate_z_op_animated_value,
1097    AnimatedScalar
1098);
1099define_transform_op!(
1100    TransformRotateOp,
1101    mdl_transform_rotate_op_animated_value,
1102    AnimatedVector3
1103);
1104define_transform_op!(
1105    TransformTranslateOp,
1106    mdl_transform_translate_op_animated_value,
1107    AnimatedVector3
1108);
1109define_transform_op!(
1110    TransformScaleOp,
1111    mdl_transform_scale_op_animated_value,
1112    AnimatedVector3
1113);
1114define_transform_op!(
1115    TransformMatrixOp,
1116    mdl_transform_matrix_op_animated_value,
1117    AnimatedMatrix4x4
1118);
1119define_transform_op!(
1120    TransformOrientOp,
1121    mdl_transform_orient_op_animated_value,
1122    AnimatedQuaternion
1123);
1124
1125#[derive(Debug, Clone)]
1126/// Wraps the corresponding Model I/O transform stack counterpart.
1127pub struct TransformStack {
1128    handle: ObjectHandle,
1129}
1130
1131impl TransformStack {
1132    /// Builds this wrapper from the retained handle of the wrapped Model I/O transform stack counterpart.
1133    pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
1134        Self { handle }
1135    }
1136
1137    /// Wraps the corresponding Model I/O initializer for the wrapped Model I/O transform stack counterpart.
1138    pub fn new() -> Result<Self> {
1139        let mut out_stack = ptr::null_mut();
1140        let mut out_error = ptr::null_mut();
1141        // SAFETY: Output pointers are initialized and managed; FFI function is called safely.
1142        let status = unsafe { ffi::mdl_transform_stack_new(&mut out_stack, &mut out_error) };
1143        crate::util::status_result(status, out_error)?;
1144        Ok(Self::from_handle(required_handle(
1145            out_stack,
1146            "MDLTransformStack",
1147        )?))
1148    }
1149
1150    #[must_use]
1151    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform stack counterpart.
1152    pub fn matrix(&self) -> [f32; 16] {
1153        self.as_transform_component().matrix()
1154    }
1155
1156    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform stack counterpart.
1157    pub fn set_matrix(&self, matrix: [f32; 16]) {
1158        self.as_transform_component().set_matrix(matrix);
1159    }
1160
1161    #[must_use]
1162    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform stack counterpart.
1163    pub fn resets_transform(&self) -> bool {
1164        self.as_transform_component().resets_transform()
1165    }
1166
1167    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform stack counterpart.
1168    pub fn set_resets_transform(&self, resets_transform: bool) {
1169        self.as_transform_component()
1170            .set_resets_transform(resets_transform);
1171    }
1172
1173    #[must_use]
1174    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform stack counterpart.
1175    pub fn minimum_time(&self) -> f64 {
1176        self.as_transform_component().minimum_time()
1177    }
1178
1179    #[must_use]
1180    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform stack counterpart.
1181    pub fn maximum_time(&self) -> f64 {
1182        self.as_transform_component().maximum_time()
1183    }
1184
1185    #[must_use]
1186    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform stack counterpart.
1187    pub fn key_times(&self) -> Vec<f64> {
1188        self.as_transform_component().key_times()
1189    }
1190
1191    #[must_use]
1192    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform stack counterpart.
1193    pub fn local_transform_at_time(&self, time: f64) -> [f32; 16] {
1194        self.as_transform_component().local_transform_at_time(time)
1195    }
1196
1197    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform stack counterpart.
1198    pub fn add_translate_op(&self, name: &str, inverse: bool) -> Result<TransformTranslateOp> {
1199        let name = c_string(name)?;
1200        // SAFETY: The unsafe operation is valid in this context.
1201        let ptr = unsafe {
1202            ffi::mdl_transform_stack_add_translate_op(
1203                self.handle.as_ptr(),
1204                name.as_ptr(),
1205                i32::from(inverse),
1206            )
1207        };
1208        Ok(TransformTranslateOp::from_handle(required_handle(
1209            ptr,
1210            "MDLTransformTranslateOp",
1211        )?))
1212    }
1213
1214    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform stack counterpart.
1215    pub fn add_rotate_x_op(&self, name: &str, inverse: bool) -> Result<TransformRotateXOp> {
1216        let name = c_string(name)?;
1217        // SAFETY: The unsafe operation is valid in this context.
1218        let ptr = unsafe {
1219            ffi::mdl_transform_stack_add_rotate_x_op(
1220                self.handle.as_ptr(),
1221                name.as_ptr(),
1222                i32::from(inverse),
1223            )
1224        };
1225        Ok(TransformRotateXOp::from_handle(required_handle(
1226            ptr,
1227            "MDLTransformRotateXOp",
1228        )?))
1229    }
1230
1231    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform stack counterpart.
1232    pub fn add_rotate_y_op(&self, name: &str, inverse: bool) -> Result<TransformRotateYOp> {
1233        let name = c_string(name)?;
1234        // SAFETY: The unsafe operation is valid in this context.
1235        let ptr = unsafe {
1236            ffi::mdl_transform_stack_add_rotate_y_op(
1237                self.handle.as_ptr(),
1238                name.as_ptr(),
1239                i32::from(inverse),
1240            )
1241        };
1242        Ok(TransformRotateYOp::from_handle(required_handle(
1243            ptr,
1244            "MDLTransformRotateYOp",
1245        )?))
1246    }
1247
1248    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform stack counterpart.
1249    pub fn add_rotate_z_op(&self, name: &str, inverse: bool) -> Result<TransformRotateZOp> {
1250        let name = c_string(name)?;
1251        // SAFETY: The unsafe operation is valid in this context.
1252        let ptr = unsafe {
1253            ffi::mdl_transform_stack_add_rotate_z_op(
1254                self.handle.as_ptr(),
1255                name.as_ptr(),
1256                i32::from(inverse),
1257            )
1258        };
1259        Ok(TransformRotateZOp::from_handle(required_handle(
1260            ptr,
1261            "MDLTransformRotateZOp",
1262        )?))
1263    }
1264
1265    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform stack counterpart.
1266    pub fn add_rotate_op(
1267        &self,
1268        name: &str,
1269        rotation_order: TransformOpRotationOrder,
1270        inverse: bool,
1271    ) -> Result<TransformRotateOp> {
1272        let name = c_string(name)?;
1273        // SAFETY: The unsafe operation is valid in this context.
1274        let ptr = unsafe {
1275            ffi::mdl_transform_stack_add_rotate_op(
1276                self.handle.as_ptr(),
1277                name.as_ptr(),
1278                rotation_order.as_raw(),
1279                i32::from(inverse),
1280            )
1281        };
1282        Ok(TransformRotateOp::from_handle(required_handle(
1283            ptr,
1284            "MDLTransformRotateOp",
1285        )?))
1286    }
1287
1288    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform stack counterpart.
1289    pub fn add_scale_op(&self, name: &str, inverse: bool) -> Result<TransformScaleOp> {
1290        let name = c_string(name)?;
1291        // SAFETY: The unsafe operation is valid in this context.
1292        let ptr = unsafe {
1293            ffi::mdl_transform_stack_add_scale_op(
1294                self.handle.as_ptr(),
1295                name.as_ptr(),
1296                i32::from(inverse),
1297            )
1298        };
1299        Ok(TransformScaleOp::from_handle(required_handle(
1300            ptr,
1301            "MDLTransformScaleOp",
1302        )?))
1303    }
1304
1305    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform stack counterpart.
1306    pub fn add_matrix_op(&self, name: &str, inverse: bool) -> Result<TransformMatrixOp> {
1307        let name = c_string(name)?;
1308        // SAFETY: The unsafe operation is valid in this context.
1309        let ptr = unsafe {
1310            ffi::mdl_transform_stack_add_matrix_op(
1311                self.handle.as_ptr(),
1312                name.as_ptr(),
1313                i32::from(inverse),
1314            )
1315        };
1316        Ok(TransformMatrixOp::from_handle(required_handle(
1317            ptr,
1318            "MDLTransformMatrixOp",
1319        )?))
1320    }
1321
1322    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform stack counterpart.
1323    pub fn add_orient_op(&self, name: &str, inverse: bool) -> Result<TransformOrientOp> {
1324        let name = c_string(name)?;
1325        // SAFETY: The unsafe operation is valid in this context.
1326        let ptr = unsafe {
1327            ffi::mdl_transform_stack_add_orient_op(
1328                self.handle.as_ptr(),
1329                name.as_ptr(),
1330                i32::from(inverse),
1331            )
1332        };
1333        Ok(TransformOrientOp::from_handle(required_handle(
1334            ptr,
1335            "MDLTransformOrientOp",
1336        )?))
1337    }
1338
1339    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform stack counterpart.
1340    pub fn animated_value_named(&self, name: &str) -> Result<Option<AnimatedValue>> {
1341        let name = c_string(name)?;
1342        // SAFETY: The unsafe operation is valid in this context.
1343        let ptr = unsafe {
1344            ffi::mdl_transform_stack_animated_value_named(self.handle.as_ptr(), name.as_ptr())
1345        };
1346        // SAFETY: The unsafe operation is valid in this context.
1347        Ok(unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(AnimatedValue::from_handle))
1348    }
1349
1350    #[must_use]
1351    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform stack counterpart.
1352    pub fn float4x4_at_time(&self, time: f64) -> [f32; 16] {
1353        let mut matrix = [0.0_f32; 16];
1354        // SAFETY: The unsafe operation is valid in this context.
1355        unsafe {
1356            ffi::mdl_transform_stack_copy_float4x4_at_time(
1357                self.handle.as_ptr(),
1358                time,
1359                matrix.as_mut_ptr(),
1360            );
1361        };
1362        matrix
1363    }
1364
1365    #[must_use]
1366    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform stack counterpart.
1367    pub fn count(&self) -> usize {
1368        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
1369        unsafe { ffi::mdl_transform_stack_count(self.handle.as_ptr()) as usize }
1370    }
1371
1372    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform stack counterpart.
1373    pub fn transform_ops(&self) -> Result<Vec<TransformOp>> {
1374        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
1375        let ptr = unsafe { ffi::mdl_transform_stack_transform_ops(self.handle.as_ptr()) };
1376        if ptr.is_null() {
1377            return Ok(Vec::new());
1378        }
1379        array_objects(
1380            ptr,
1381            "MDLTransformStack transformOps",
1382            TransformOp::from_handle,
1383        )
1384    }
1385
1386    #[must_use]
1387    /// Calls the corresponding Model I/O method on the wrapped Model I/O transform stack counterpart.
1388    pub fn as_transform_component(&self) -> TransformComponent {
1389        TransformComponent::from_handle(self.handle.clone())
1390    }
1391}
1392
1393impl Object {
1394    #[must_use]
1395    /// Calls the corresponding Model I/O method on the wrapped Model I/O object counterpart.
1396    pub fn transform_component(&self) -> Option<TransformComponent> {
1397        // SAFETY: ObjectHandle wraps a valid opaque pointer from Swift; FFI function accepts it safely.
1398        let ptr = unsafe { ffi::mdl_object_transform_component(self.as_ptr()) };
1399        // SAFETY: The unsafe operation is valid in this context.
1400        unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(TransformComponent::from_handle)
1401    }
1402
1403    /// Calls the corresponding Model I/O method on the wrapped Model I/O object counterpart.
1404    pub fn set_transform_component(&self, component: Option<&TransformComponent>) {
1405        // SAFETY: The unsafe operation is valid in this context.
1406        unsafe {
1407            ffi::mdl_object_set_transform_component(
1408                self.as_ptr(),
1409                component.map_or(ptr::null_mut(), TransformComponent::as_ptr),
1410            );
1411        }
1412    }
1413}