Skip to main content

modelio/
transform.rs

1use std::ptr;
2
3use crate::animated_value_types::{
4    AnimatedMatrix4x4, AnimatedQuaternion, AnimatedScalar, AnimatedValue, AnimatedVector3,
5};
6use crate::error::Result;
7use crate::ffi;
8use crate::handle::ObjectHandle;
9use crate::object::Object;
10use crate::types::TransformOpRotationOrder;
11use crate::util::{c_string, required_handle, take_string};
12
13fn copy_matrix(
14    handle: *mut core::ffi::c_void,
15    getter: unsafe extern "C" fn(*mut core::ffi::c_void, *mut f32),
16) -> [f32; 16] {
17    let mut matrix = [0.0_f32; 16];
18    unsafe { getter(handle, matrix.as_mut_ptr()) };
19    matrix
20}
21
22fn array_objects<T, F>(array_ptr: *mut core::ffi::c_void, context: &'static str, mut map: F) -> Result<Vec<T>>
23where
24    F: FnMut(ObjectHandle) -> T,
25{
26    let array = required_handle(array_ptr, context)?;
27    let count = unsafe { ffi::mdl_array_count(array.as_ptr()) as usize };
28    let mut values = Vec::with_capacity(count);
29    for index in 0..count {
30        let ptr = unsafe { ffi::mdl_array_object_at(array.as_ptr(), index as u64) };
31        if let Some(handle) = unsafe { ObjectHandle::from_retained_ptr(ptr) } {
32            values.push(map(handle));
33        }
34    }
35    Ok(values)
36}
37
38#[derive(Debug, Clone)]
39pub struct TransformComponent {
40    handle: ObjectHandle,
41}
42
43impl TransformComponent {
44    pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
45        Self { handle }
46    }
47
48    pub(crate) fn as_ptr(&self) -> *mut core::ffi::c_void {
49        self.handle.as_ptr()
50    }
51
52    #[must_use]
53    pub fn matrix(&self) -> [f32; 16] {
54        copy_matrix(self.handle.as_ptr(), ffi::mdl_transform_component_matrix)
55    }
56
57    pub fn set_matrix(&self, matrix: [f32; 16]) {
58        unsafe { ffi::mdl_transform_component_set_matrix(self.handle.as_ptr(), matrix.as_ptr()) };
59    }
60
61    #[must_use]
62    pub fn resets_transform(&self) -> bool {
63        unsafe { ffi::mdl_transform_component_resets_transform(self.handle.as_ptr()) != 0 }
64    }
65
66    pub fn set_resets_transform(&self, resets_transform: bool) {
67        unsafe {
68            ffi::mdl_transform_component_set_resets_transform(
69                self.handle.as_ptr(),
70                i32::from(resets_transform),
71            );
72        }
73    }
74
75    #[must_use]
76    pub fn minimum_time(&self) -> f64 {
77        unsafe { ffi::mdl_transform_component_minimum_time(self.handle.as_ptr()) }
78    }
79
80    #[must_use]
81    pub fn maximum_time(&self) -> f64 {
82        unsafe { ffi::mdl_transform_component_maximum_time(self.handle.as_ptr()) }
83    }
84
85    #[must_use]
86    pub fn key_times(&self) -> Vec<f64> {
87        let count = unsafe { ffi::mdl_transform_component_key_time_count(self.handle.as_ptr()) as usize };
88        let mut values = vec![0.0_f64; count];
89        if values.is_empty() {
90            return values;
91        }
92        let written = unsafe {
93            ffi::mdl_transform_component_copy_key_times(
94                self.handle.as_ptr(),
95                values.as_mut_ptr(),
96                values.len() as u64,
97            )
98        } as usize;
99        values.truncate(written);
100        values
101    }
102
103    #[must_use]
104    pub fn local_transform_at_time(&self, time: f64) -> [f32; 16] {
105        let mut matrix = [0.0_f32; 16];
106        unsafe {
107            ffi::mdl_transform_component_local_transform_at_time(
108                self.handle.as_ptr(),
109                time,
110                matrix.as_mut_ptr(),
111            );
112        }
113        matrix
114    }
115
116    #[must_use]
117    pub fn as_transform(&self) -> Option<Transform> {
118        (unsafe { ffi::mdl_transform_component_is_transform(self.handle.as_ptr()) != 0 })
119            .then(|| Transform::from_handle(self.handle.clone()))
120    }
121
122    #[must_use]
123    pub fn as_transform_stack(&self) -> Option<TransformStack> {
124        (unsafe { ffi::mdl_transform_component_is_transform_stack(self.handle.as_ptr()) != 0 })
125            .then(|| TransformStack::from_handle(self.handle.clone()))
126    }
127
128    #[must_use]
129    pub fn global_transform_with_object(object: &Object, time: f64) -> [f32; 16] {
130        let mut matrix = [0.0_f32; 16];
131        unsafe {
132            ffi::mdl_transform_component_global_transform_with_object(
133                object.as_ptr(),
134                time,
135                matrix.as_mut_ptr(),
136            );
137        }
138        matrix
139    }
140}
141
142#[derive(Debug, Clone)]
143pub struct Transform {
144    handle: ObjectHandle,
145}
146
147impl Transform {
148    pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
149        Self { handle }
150    }
151
152    pub fn new() -> Result<Self> {
153        let mut out_transform = ptr::null_mut();
154        let mut out_error = ptr::null_mut();
155        let status = unsafe { ffi::mdl_transform_new(&mut out_transform, &mut out_error) };
156        crate::util::status_result(status, out_error)?;
157        Ok(Self::from_handle(required_handle(out_transform, "MDLTransform")?))
158    }
159
160    pub fn from_component(component: &TransformComponent) -> Result<Self> {
161        let mut out_transform = ptr::null_mut();
162        let mut out_error = ptr::null_mut();
163        let status = unsafe {
164            ffi::mdl_transform_new_with_component(
165                component.as_ptr(),
166                &mut out_transform,
167                &mut out_error,
168            )
169        };
170        crate::util::status_result(status, out_error)?;
171        Ok(Self::from_handle(required_handle(out_transform, "MDLTransform")?))
172    }
173
174    pub fn from_component_with_resets_transform(
175        component: &TransformComponent,
176        resets_transform: bool,
177    ) -> Result<Self> {
178        let mut out_transform = ptr::null_mut();
179        let mut out_error = ptr::null_mut();
180        let status = unsafe {
181            ffi::mdl_transform_new_with_component_resets_transform(
182                component.as_ptr(),
183                i32::from(resets_transform),
184                &mut out_transform,
185                &mut out_error,
186            )
187        };
188        crate::util::status_result(status, out_error)?;
189        Ok(Self::from_handle(required_handle(out_transform, "MDLTransform")?))
190    }
191
192    pub fn from_matrix(matrix: [f32; 16]) -> Result<Self> {
193        let mut out_transform = ptr::null_mut();
194        let mut out_error = ptr::null_mut();
195        let status = unsafe {
196            ffi::mdl_transform_new_with_matrix(
197                matrix.as_ptr(),
198                &mut out_transform,
199                &mut out_error,
200            )
201        };
202        crate::util::status_result(status, out_error)?;
203        Ok(Self::from_handle(required_handle(out_transform, "MDLTransform")?))
204    }
205
206    pub fn from_matrix_with_resets_transform(
207        matrix: [f32; 16],
208        resets_transform: bool,
209    ) -> Result<Self> {
210        let mut out_transform = ptr::null_mut();
211        let mut out_error = ptr::null_mut();
212        let status = unsafe {
213            ffi::mdl_transform_new_with_matrix_resets_transform(
214                matrix.as_ptr(),
215                i32::from(resets_transform),
216                &mut out_transform,
217                &mut out_error,
218            )
219        };
220        crate::util::status_result(status, out_error)?;
221        Ok(Self::from_handle(required_handle(out_transform, "MDLTransform")?))
222    }
223
224    #[must_use]
225    pub fn matrix(&self) -> [f32; 16] {
226        self.as_transform_component().matrix()
227    }
228
229    pub fn set_matrix(&self, matrix: [f32; 16]) {
230        self.as_transform_component().set_matrix(matrix);
231    }
232
233    #[must_use]
234    pub fn resets_transform(&self) -> bool {
235        self.as_transform_component().resets_transform()
236    }
237
238    pub fn set_resets_transform(&self, resets_transform: bool) {
239        self.as_transform_component().set_resets_transform(resets_transform);
240    }
241
242    #[must_use]
243    pub fn minimum_time(&self) -> f64 {
244        self.as_transform_component().minimum_time()
245    }
246
247    #[must_use]
248    pub fn maximum_time(&self) -> f64 {
249        self.as_transform_component().maximum_time()
250    }
251
252    #[must_use]
253    pub fn key_times(&self) -> Vec<f64> {
254        self.as_transform_component().key_times()
255    }
256
257    #[must_use]
258    pub fn local_transform_at_time(&self, time: f64) -> [f32; 16] {
259        self.as_transform_component().local_transform_at_time(time)
260    }
261
262    pub fn set_identity(&self) {
263        unsafe { ffi::mdl_transform_set_identity(self.handle.as_ptr()) };
264    }
265
266    #[must_use]
267    pub fn translation_at_time(&self, time: f64) -> [f32; 3] {
268        let mut value = [0.0_f32; 3];
269        unsafe {
270            ffi::mdl_transform_translation_at_time(self.handle.as_ptr(), time, value.as_mut_ptr());
271        }
272        value
273    }
274
275    #[must_use]
276    pub fn rotation_at_time(&self, time: f64) -> [f32; 3] {
277        let mut value = [0.0_f32; 3];
278        unsafe {
279            ffi::mdl_transform_rotation_at_time(self.handle.as_ptr(), time, value.as_mut_ptr());
280        }
281        value
282    }
283
284    #[must_use]
285    pub fn shear_at_time(&self, time: f64) -> [f32; 3] {
286        let mut value = [0.0_f32; 3];
287        unsafe {
288            ffi::mdl_transform_shear_at_time(self.handle.as_ptr(), time, value.as_mut_ptr());
289        }
290        value
291    }
292
293    #[must_use]
294    pub fn scale_at_time(&self, time: f64) -> [f32; 3] {
295        let mut value = [0.0_f32; 3];
296        unsafe {
297            ffi::mdl_transform_scale_at_time(self.handle.as_ptr(), time, value.as_mut_ptr());
298        }
299        value
300    }
301
302    pub fn set_matrix_for_time(&self, matrix: [f32; 16], time: f64) {
303        unsafe { ffi::mdl_transform_set_matrix_for_time(self.handle.as_ptr(), matrix.as_ptr(), time) };
304    }
305
306    pub fn set_translation_for_time(&self, translation: [f32; 3], time: f64) {
307        unsafe {
308            ffi::mdl_transform_set_translation_for_time(
309                self.handle.as_ptr(),
310                translation[0],
311                translation[1],
312                translation[2],
313                time,
314            );
315        }
316    }
317
318    pub fn set_rotation_for_time(&self, rotation: [f32; 3], time: f64) {
319        unsafe {
320            ffi::mdl_transform_set_rotation_for_time(
321                self.handle.as_ptr(),
322                rotation[0],
323                rotation[1],
324                rotation[2],
325                time,
326            );
327        }
328    }
329
330    pub fn set_shear_for_time(&self, shear: [f32; 3], time: f64) {
331        unsafe {
332            ffi::mdl_transform_set_shear_for_time(
333                self.handle.as_ptr(),
334                shear[0],
335                shear[1],
336                shear[2],
337                time,
338            );
339        }
340    }
341
342    pub fn set_scale_for_time(&self, scale: [f32; 3], time: f64) {
343        unsafe {
344            ffi::mdl_transform_set_scale_for_time(
345                self.handle.as_ptr(),
346                scale[0],
347                scale[1],
348                scale[2],
349                time,
350            );
351        }
352    }
353
354    #[must_use]
355    pub fn rotation_matrix_at_time(&self, time: f64) -> [f32; 16] {
356        let mut matrix = [0.0_f32; 16];
357        unsafe {
358            ffi::mdl_transform_rotation_matrix_at_time(
359                self.handle.as_ptr(),
360                time,
361                matrix.as_mut_ptr(),
362            );
363        }
364        matrix
365    }
366
367    #[must_use]
368    pub fn translation(&self) -> [f32; 3] {
369        let mut value = [0.0_f32; 3];
370        unsafe { ffi::mdl_transform_translation(self.handle.as_ptr(), value.as_mut_ptr()) };
371        value
372    }
373
374    pub fn set_translation(&self, translation: [f32; 3]) {
375        unsafe {
376            ffi::mdl_transform_set_translation(
377                self.handle.as_ptr(),
378                translation[0],
379                translation[1],
380                translation[2],
381            );
382        }
383    }
384
385    #[must_use]
386    pub fn rotation(&self) -> [f32; 3] {
387        let mut value = [0.0_f32; 3];
388        unsafe { ffi::mdl_transform_rotation(self.handle.as_ptr(), value.as_mut_ptr()) };
389        value
390    }
391
392    pub fn set_rotation(&self, rotation: [f32; 3]) {
393        unsafe {
394            ffi::mdl_transform_set_rotation(
395                self.handle.as_ptr(),
396                rotation[0],
397                rotation[1],
398                rotation[2],
399            );
400        }
401    }
402
403    #[must_use]
404    pub fn shear(&self) -> [f32; 3] {
405        let mut value = [0.0_f32; 3];
406        unsafe { ffi::mdl_transform_shear(self.handle.as_ptr(), value.as_mut_ptr()) };
407        value
408    }
409
410    pub fn set_shear(&self, shear: [f32; 3]) {
411        unsafe {
412            ffi::mdl_transform_set_shear(
413                self.handle.as_ptr(),
414                shear[0],
415                shear[1],
416                shear[2],
417            );
418        }
419    }
420
421    #[must_use]
422    pub fn scale(&self) -> [f32; 3] {
423        let mut value = [0.0_f32; 3];
424        unsafe { ffi::mdl_transform_scale(self.handle.as_ptr(), value.as_mut_ptr()) };
425        value
426    }
427
428    pub fn set_scale(&self, scale: [f32; 3]) {
429        unsafe {
430            ffi::mdl_transform_set_scale(
431                self.handle.as_ptr(),
432                scale[0],
433                scale[1],
434                scale[2],
435            );
436        }
437    }
438
439    #[must_use]
440    pub fn as_transform_component(&self) -> TransformComponent {
441        TransformComponent::from_handle(self.handle.clone())
442    }
443}
444
445#[derive(Debug, Clone)]
446pub struct TransformOp {
447    handle: ObjectHandle,
448}
449
450impl TransformOp {
451    pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
452        Self { handle }
453    }
454
455    #[must_use]
456    pub fn name(&self) -> Option<String> {
457        take_string(unsafe { ffi::mdl_transform_op_name_string(self.handle.as_ptr()) })
458    }
459
460    #[must_use]
461    pub fn is_inverse(&self) -> bool {
462        unsafe { ffi::mdl_transform_op_is_inverse(self.handle.as_ptr()) != 0 }
463    }
464
465    #[must_use]
466    pub fn float4x4_at_time(&self, time: f64) -> [f32; 16] {
467        let mut matrix = [0.0_f32; 16];
468        unsafe { ffi::mdl_transform_op_copy_float4x4_at_time(self.handle.as_ptr(), time, matrix.as_mut_ptr()) };
469        matrix
470    }
471}
472
473macro_rules! define_transform_op {
474    ($name:ident, $ffi_name:ident, $animated:ty) => {
475        #[derive(Debug, Clone)]
476        pub struct $name {
477            handle: ObjectHandle,
478        }
479
480        impl $name {
481            pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
482                Self { handle }
483            }
484
485            #[must_use]
486            pub fn name(&self) -> Option<String> {
487                TransformOp::from_handle(self.handle.clone()).name()
488            }
489
490            #[must_use]
491            pub fn is_inverse(&self) -> bool {
492                TransformOp::from_handle(self.handle.clone()).is_inverse()
493            }
494
495            #[must_use]
496            pub fn float4x4_at_time(&self, time: f64) -> [f32; 16] {
497                TransformOp::from_handle(self.handle.clone()).float4x4_at_time(time)
498            }
499
500            #[must_use]
501            pub fn animated_value(&self) -> Option<$animated> {
502                let ptr = unsafe { ffi::$ffi_name(self.handle.as_ptr()) };
503                unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(<$animated>::from_handle)
504            }
505
506            #[must_use]
507            pub fn as_transform_op(&self) -> TransformOp {
508                TransformOp::from_handle(self.handle.clone())
509            }
510        }
511    };
512}
513
514define_transform_op!(TransformRotateXOp, mdl_transform_rotate_x_op_animated_value, AnimatedScalar);
515define_transform_op!(TransformRotateYOp, mdl_transform_rotate_y_op_animated_value, AnimatedScalar);
516define_transform_op!(TransformRotateZOp, mdl_transform_rotate_z_op_animated_value, AnimatedScalar);
517define_transform_op!(TransformRotateOp, mdl_transform_rotate_op_animated_value, AnimatedVector3);
518define_transform_op!(TransformTranslateOp, mdl_transform_translate_op_animated_value, AnimatedVector3);
519define_transform_op!(TransformScaleOp, mdl_transform_scale_op_animated_value, AnimatedVector3);
520define_transform_op!(TransformMatrixOp, mdl_transform_matrix_op_animated_value, AnimatedMatrix4x4);
521define_transform_op!(TransformOrientOp, mdl_transform_orient_op_animated_value, AnimatedQuaternion);
522
523#[derive(Debug, Clone)]
524pub struct TransformStack {
525    handle: ObjectHandle,
526}
527
528impl TransformStack {
529    pub(crate) fn from_handle(handle: ObjectHandle) -> Self {
530        Self { handle }
531    }
532
533    pub fn new() -> Result<Self> {
534        let mut out_stack = ptr::null_mut();
535        let mut out_error = ptr::null_mut();
536        let status = unsafe { ffi::mdl_transform_stack_new(&mut out_stack, &mut out_error) };
537        crate::util::status_result(status, out_error)?;
538        Ok(Self::from_handle(required_handle(
539            out_stack,
540            "MDLTransformStack",
541        )?))
542    }
543
544    #[must_use]
545    pub fn matrix(&self) -> [f32; 16] {
546        self.as_transform_component().matrix()
547    }
548
549    pub fn set_matrix(&self, matrix: [f32; 16]) {
550        self.as_transform_component().set_matrix(matrix);
551    }
552
553    #[must_use]
554    pub fn resets_transform(&self) -> bool {
555        self.as_transform_component().resets_transform()
556    }
557
558    pub fn set_resets_transform(&self, resets_transform: bool) {
559        self.as_transform_component().set_resets_transform(resets_transform);
560    }
561
562    #[must_use]
563    pub fn minimum_time(&self) -> f64 {
564        self.as_transform_component().minimum_time()
565    }
566
567    #[must_use]
568    pub fn maximum_time(&self) -> f64 {
569        self.as_transform_component().maximum_time()
570    }
571
572    #[must_use]
573    pub fn key_times(&self) -> Vec<f64> {
574        self.as_transform_component().key_times()
575    }
576
577    #[must_use]
578    pub fn local_transform_at_time(&self, time: f64) -> [f32; 16] {
579        self.as_transform_component().local_transform_at_time(time)
580    }
581
582    pub fn add_translate_op(&self, name: &str, inverse: bool) -> Result<TransformTranslateOp> {
583        let name = c_string(name)?;
584        let ptr = unsafe {
585            ffi::mdl_transform_stack_add_translate_op(
586                self.handle.as_ptr(),
587                name.as_ptr(),
588                i32::from(inverse),
589            )
590        };
591        Ok(TransformTranslateOp::from_handle(required_handle(
592            ptr,
593            "MDLTransformTranslateOp",
594        )?))
595    }
596
597    pub fn add_rotate_x_op(&self, name: &str, inverse: bool) -> Result<TransformRotateXOp> {
598        let name = c_string(name)?;
599        let ptr = unsafe {
600            ffi::mdl_transform_stack_add_rotate_x_op(
601                self.handle.as_ptr(),
602                name.as_ptr(),
603                i32::from(inverse),
604            )
605        };
606        Ok(TransformRotateXOp::from_handle(required_handle(
607            ptr,
608            "MDLTransformRotateXOp",
609        )?))
610    }
611
612    pub fn add_rotate_y_op(&self, name: &str, inverse: bool) -> Result<TransformRotateYOp> {
613        let name = c_string(name)?;
614        let ptr = unsafe {
615            ffi::mdl_transform_stack_add_rotate_y_op(
616                self.handle.as_ptr(),
617                name.as_ptr(),
618                i32::from(inverse),
619            )
620        };
621        Ok(TransformRotateYOp::from_handle(required_handle(
622            ptr,
623            "MDLTransformRotateYOp",
624        )?))
625    }
626
627    pub fn add_rotate_z_op(&self, name: &str, inverse: bool) -> Result<TransformRotateZOp> {
628        let name = c_string(name)?;
629        let ptr = unsafe {
630            ffi::mdl_transform_stack_add_rotate_z_op(
631                self.handle.as_ptr(),
632                name.as_ptr(),
633                i32::from(inverse),
634            )
635        };
636        Ok(TransformRotateZOp::from_handle(required_handle(
637            ptr,
638            "MDLTransformRotateZOp",
639        )?))
640    }
641
642    pub fn add_rotate_op(
643        &self,
644        name: &str,
645        rotation_order: TransformOpRotationOrder,
646        inverse: bool,
647    ) -> Result<TransformRotateOp> {
648        let name = c_string(name)?;
649        let ptr = unsafe {
650            ffi::mdl_transform_stack_add_rotate_op(
651                self.handle.as_ptr(),
652                name.as_ptr(),
653                rotation_order.as_raw(),
654                i32::from(inverse),
655            )
656        };
657        Ok(TransformRotateOp::from_handle(required_handle(
658            ptr,
659            "MDLTransformRotateOp",
660        )?))
661    }
662
663    pub fn add_scale_op(&self, name: &str, inverse: bool) -> Result<TransformScaleOp> {
664        let name = c_string(name)?;
665        let ptr = unsafe {
666            ffi::mdl_transform_stack_add_scale_op(
667                self.handle.as_ptr(),
668                name.as_ptr(),
669                i32::from(inverse),
670            )
671        };
672        Ok(TransformScaleOp::from_handle(required_handle(
673            ptr,
674            "MDLTransformScaleOp",
675        )?))
676    }
677
678    pub fn add_matrix_op(&self, name: &str, inverse: bool) -> Result<TransformMatrixOp> {
679        let name = c_string(name)?;
680        let ptr = unsafe {
681            ffi::mdl_transform_stack_add_matrix_op(
682                self.handle.as_ptr(),
683                name.as_ptr(),
684                i32::from(inverse),
685            )
686        };
687        Ok(TransformMatrixOp::from_handle(required_handle(
688            ptr,
689            "MDLTransformMatrixOp",
690        )?))
691    }
692
693    pub fn add_orient_op(&self, name: &str, inverse: bool) -> Result<TransformOrientOp> {
694        let name = c_string(name)?;
695        let ptr = unsafe {
696            ffi::mdl_transform_stack_add_orient_op(
697                self.handle.as_ptr(),
698                name.as_ptr(),
699                i32::from(inverse),
700            )
701        };
702        Ok(TransformOrientOp::from_handle(required_handle(
703            ptr,
704            "MDLTransformOrientOp",
705        )?))
706    }
707
708    pub fn animated_value_named(&self, name: &str) -> Result<Option<AnimatedValue>> {
709        let name = c_string(name)?;
710        let ptr = unsafe { ffi::mdl_transform_stack_animated_value_named(self.handle.as_ptr(), name.as_ptr()) };
711        Ok(unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(AnimatedValue::from_handle))
712    }
713
714    #[must_use]
715    pub fn float4x4_at_time(&self, time: f64) -> [f32; 16] {
716        let mut matrix = [0.0_f32; 16];
717        unsafe { ffi::mdl_transform_stack_copy_float4x4_at_time(self.handle.as_ptr(), time, matrix.as_mut_ptr()) };
718        matrix
719    }
720
721    #[must_use]
722    pub fn count(&self) -> usize {
723        unsafe { ffi::mdl_transform_stack_count(self.handle.as_ptr()) as usize }
724    }
725
726    pub fn transform_ops(&self) -> Result<Vec<TransformOp>> {
727        let ptr = unsafe { ffi::mdl_transform_stack_transform_ops(self.handle.as_ptr()) };
728        if ptr.is_null() {
729            return Ok(Vec::new());
730        }
731        array_objects(ptr, "MDLTransformStack transformOps", TransformOp::from_handle)
732    }
733
734    #[must_use]
735    pub fn as_transform_component(&self) -> TransformComponent {
736        TransformComponent::from_handle(self.handle.clone())
737    }
738}
739
740impl Object {
741    #[must_use]
742    pub fn transform_component(&self) -> Option<TransformComponent> {
743        let ptr = unsafe { ffi::mdl_object_transform_component(self.as_ptr()) };
744        unsafe { ObjectHandle::from_retained_ptr(ptr) }.map(TransformComponent::from_handle)
745    }
746
747    pub fn set_transform_component(&self, component: Option<&TransformComponent>) {
748        unsafe {
749            ffi::mdl_object_set_transform_component(
750                self.as_ptr(),
751                component.map_or(ptr::null_mut(), TransformComponent::as_ptr),
752            );
753        }
754    }
755}