Skip to main content

spine_sys/
skeleton.rs

1#![allow(clippy::needless_pass_by_value)]
2
3use crate::{
4    animation::Animation, atlas::Atlas, enums::AttachmentType, enums::BlendMode, ffi, SpineError,
5    SpineMutPtr,
6};
7use std::{convert::TryInto, ffi::*, marker::PhantomData, path::Path};
8
9pub struct BoneIndex(i32);
10pub struct SlotIndex(i32);
11
12pub struct RegionAttachment<'a> {
13    pub(crate) inner: *mut ffi::spRegionAttachment,
14    _lifetime: PhantomData<&'a ()>,
15}
16impl<'a> RegionAttachment<'a> {
17    /// Loads the 8 vertex positions for this attachments rendering into the provided `vertices`  buffer.
18    ///
19    /// # spine-c
20    /// Maps to calling `spRegionAttachment_computeWorldVertices`
21    pub fn load_vertices(&self, bone: &Bone<'_>, vertices: &mut [f32; 8]) {
22        unsafe {
23            ffi::spRegionAttachment_computeWorldVertices(
24                self.inner,
25                bone.inner,
26                vertices.as_mut_ptr(),
27                0,
28                2,
29            );
30        }
31    }
32
33    /// Returns the 8 vertex positions for this attachments rendering in a new array.
34    ///
35    /// # spine-c
36    /// Maps to calling `spRegionAttachment_computeWorldVertices`
37    pub fn get_vertices(&self, bone: &Bone<'_>) -> [f32; 8] {
38        let mut vertices = [0.0; 8];
39
40        self.load_vertices(bone, &mut vertices);
41
42        vertices
43    }
44
45    /// Returns the `u32` texture id which was provided for this image during `Atlas` loading.
46    ///
47    /// # spine-c
48    /// Maps to dereferencing `((*spAtlasRegion)spRegionAttachment->rendererObject)->rendererObject`
49    pub fn texture_id(&self) -> u32 {
50        // TODO: CLEAN THIS UP OMG
51        unsafe {
52            let atlas_region = self.as_ref().rendererObject as *mut ffi::spAtlasRegion;
53            let page = (*atlas_region).page;
54            (*page).rendererObject as u32
55        }
56    }
57
58    pub fn rotation(&self) -> f32 {
59        self.as_ref().rotation
60    }
61
62    pub fn color(&self) -> [f32; 4] {
63        let color = &self.as_ref().color;
64        [color.r, color.b, color.g, color.a]
65    }
66
67    pub fn position(&self) -> [f32; 2] {
68        let r = self.as_ref();
69        [r.x, r.y]
70    }
71
72    pub fn scale(&self) -> [f32; 2] {
73        let r = self.as_ref();
74        [r.scaleX, r.scaleY]
75    }
76
77    pub fn dimensions(&self) -> [f32; 2] {
78        let r = self.as_ref();
79        [r.width, r.height]
80    }
81
82    pub fn uv(&self) -> [f32; 8] {
83        let r = self.as_ref();
84        r.uvs
85    }
86
87    pub fn offset(&self) -> [f32; 8] {
88        let r = self.as_ref();
89        r.offset
90    }
91
92    pub(crate) fn as_ref(&self) -> &ffi::spRegionAttachment {
93        unsafe { self.inner.as_ref().unwrap() }
94    }
95    pub(crate) fn as_mut(&mut self) -> &mut ffi::spRegionAttachment {
96        unsafe { self.inner.as_mut().unwrap() }
97    }
98}
99
100pub struct Attachment<'a> {
101    pub(crate) inner: *mut ffi::spAttachment,
102    _lifetime: PhantomData<&'a ()>,
103}
104impl<'a> Attachment<'a> {
105    pub fn as_region_attachment(&mut self) -> RegionAttachment<'a> {
106        RegionAttachment {
107            inner: self.inner as *mut ffi::spRegionAttachment,
108            _lifetime: PhantomData::<&'a ()>::default(),
109        }
110    }
111
112    pub fn name(&self) -> &str {
113        unsafe { CStr::from_ptr(self.as_ref().name) }
114            .to_str()
115            .unwrap()
116    }
117
118    pub fn kind(&self) -> AttachmentType {
119        self.as_ref().type_.into()
120    }
121
122    pub(crate) fn as_ref(&self) -> &ffi::spAttachment {
123        unsafe { self.inner.as_ref().unwrap() }
124    }
125    pub(crate) fn as_mut(&mut self) -> &mut ffi::spAttachment {
126        unsafe { self.inner.as_mut().unwrap() }
127    }
128}
129
130pub struct Slot<'a> {
131    pub(crate) inner: *mut ffi::spSlot,
132    _lifetime: PhantomData<&'a ()>,
133}
134impl<'a> Slot<'a> {
135    pub fn bone(&self) -> Option<Bone<'_>> {
136        let r = self.as_ref();
137
138        if r.bone.is_null() {
139            None
140        } else {
141            Some(Bone {
142                inner: r.bone,
143                _lifetime: PhantomData::<&'a ()>::default(),
144            })
145        }
146    }
147
148    pub fn color(&self) -> [f32; 4] {
149        let color = &self.as_ref().color;
150        [color.r, color.b, color.g, color.a]
151    }
152
153    pub fn blend_mode(&self) -> BlendMode {
154        unsafe { self.as_ref().data.as_ref() }
155            .unwrap()
156            .blendMode
157            .into()
158    }
159
160    pub fn active_attachment(&self) -> Option<Attachment<'_>> {
161        let r = self.as_ref();
162
163        if r.attachment.is_null() {
164            None
165        } else {
166            Some(Attachment {
167                inner: r.attachment,
168                _lifetime: PhantomData::<&'a ()>::default(),
169            })
170        }
171    }
172
173    pub fn index(&self) -> SlotIndex {
174        unsafe { SlotIndex((*self.as_ref().data).index) }
175    }
176
177    pub(crate) fn as_ref(&self) -> &ffi::spSlot {
178        unsafe { self.inner.as_ref().unwrap() }
179    }
180    pub(crate) fn as_mut(&mut self) -> &mut ffi::spSlot {
181        unsafe { self.inner.as_mut().unwrap() }
182    }
183}
184
185pub struct Bone<'a> {
186    pub(crate) inner: *mut ffi::spBone,
187    _lifetime: PhantomData<&'a ()>,
188}
189impl<'a> Bone<'a> {
190    pub fn index(&self) -> BoneIndex {
191        unsafe { BoneIndex((*self.as_ref().data).index) }
192    }
193
194    pub(crate) fn as_ref(&self) -> &ffi::spBone {
195        unsafe { self.inner.as_ref().unwrap() }
196    }
197    pub(crate) fn as_mut(&mut self) -> &mut ffi::spBone {
198        unsafe { self.inner.as_mut().unwrap() }
199    }
200}
201
202pub struct BoneData<'a> {
203    pub(crate) inner: &'a ffi::spBoneData,
204}
205impl<'a> BoneData<'a> {
206    pub fn name(&self) -> &str {
207        unsafe { CStr::from_ptr(self.inner.name) }.to_str().unwrap()
208    }
209}
210impl<'a> std::fmt::Debug for BoneData<'a> {
211    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
212        write!(f, "Bone {{ name: {} }}", self.name())
213    }
214}
215
216pub struct Skeleton {
217    pub(crate) inner: SpineMutPtr<ffi::spSkeleton>,
218}
219
220impl Skeleton {
221    pub fn new(data: &SkeletonData) -> Self {
222        Self {
223            inner: SpineMutPtr::new(
224                unsafe { ffi::spSkeleton_create(data.inner.as_mut_ptr()) },
225                Some(ffi::spSkeleton_dispose),
226            ),
227        }
228    }
229
230    pub fn slots(&self) -> Vec<Slot<'_>> {
231        let inner_ref = self.inner.as_ref();
232        let mut converted = Vec::with_capacity(inner_ref.slotsCount.try_into().unwrap());
233
234        for n in 0..inner_ref.slotsCount.try_into().unwrap() {
235            unsafe {
236                let inner = *(inner_ref.slots.add(n));
237                converted.push(Slot {
238                    inner,
239                    _lifetime: PhantomData::<&'_ ()>::default(),
240                });
241            }
242        }
243
244        converted
245    }
246
247    pub fn draw_slots(&self) -> Vec<Slot<'_>> {
248        let inner_ref = self.inner.as_ref();
249        let mut converted = Vec::with_capacity(inner_ref.slotsCount.try_into().unwrap());
250
251        for n in 0..inner_ref.slotsCount.try_into().unwrap() {
252            unsafe {
253                let inner = *(inner_ref.drawOrder.add(n));
254                converted.push(Slot {
255                    inner,
256                    _lifetime: PhantomData::<&'_ ()>::default(),
257                });
258            }
259        }
260
261        converted
262    }
263
264    pub fn bone(&self) -> Vec<Bone<'_>> {
265        let inner_ref = self.inner.as_ref();
266        let mut converted = Vec::with_capacity(inner_ref.bonesCount.try_into().unwrap());
267
268        for n in 0..inner_ref.bonesCount.try_into().unwrap() {
269            unsafe {
270                let inner = *(inner_ref.bones.add(n));
271                converted.push(Bone {
272                    inner,
273                    _lifetime: PhantomData::<&'_ ()>::default(),
274                });
275            }
276        }
277
278        converted
279    }
280
281    pub fn bone_index(&self, name: &str) -> BoneIndex {
282        let name = CString::new(name).unwrap();
283        BoneIndex(unsafe { ffi::spSkeleton_findBoneIndex(self.inner.as_mut_ptr(), name.as_ptr()) })
284    }
285
286    pub fn slot(&self, name: &str) -> Option<Slot<'_>> {
287        let name = CString::new(name).unwrap();
288        let inner = unsafe { ffi::spSkeleton_findSlot(self.inner.as_mut_ptr(), name.as_ptr()) };
289
290        if inner.is_null() {
291            None
292        } else {
293            Some(Slot {
294                inner,
295                _lifetime: PhantomData::<&'_ ()>::default(),
296            })
297        }
298    }
299
300    pub fn color(&self) -> [f32; 4] {
301        let color = &self.inner.as_ref().color;
302        [color.r, color.b, color.g, color.a]
303    }
304
305    pub fn position(&self) -> [f32; 2] {
306        let r = self.inner.as_ref();
307        [r.x, r.y]
308    }
309
310    pub fn scale(&self) -> [f32; 2] {
311        let r = self.inner.as_ref();
312        [r.scaleX, r.scaleY]
313    }
314
315    fn time(&self) -> f32 {
316        self.inner.as_ref().time
317    }
318
319    pub fn slot_index(&self, name: &str) -> SlotIndex {
320        let name = CString::new(name).unwrap();
321        SlotIndex(unsafe { ffi::spSkeleton_findSlotIndex(self.inner.as_mut_ptr(), name.as_ptr()) })
322    }
323
324    pub fn reset(&mut self) {
325        unsafe { ffi::spSkeleton_setToSetupPose(self.inner.as_mut_ptr()) }
326    }
327
328    pub fn reset_bones(&mut self) {
329        unsafe { ffi::spSkeleton_setBonesToSetupPose(self.inner.as_mut_ptr()) }
330    }
331
332    pub fn reset_slots(&mut self) {
333        unsafe { ffi::spSkeleton_setSlotsToSetupPose(self.inner.as_mut_ptr()) }
334    }
335
336    pub fn update_cache(&mut self) {
337        unsafe { ffi::spSkeleton_updateCache(self.inner.as_mut_ptr()) }
338    }
339
340    pub fn update(&mut self, delta: f32) {
341        unsafe { ffi::spSkeleton_update(self.inner.as_mut_ptr(), delta) }
342    }
343
344    pub fn update_world_transforms(&mut self) {
345        unsafe { ffi::spSkeleton_updateWorldTransform(self.inner.as_mut_ptr()) }
346    }
347}
348
349pub struct SkeletonData {
350    pub(crate) inner: SpineMutPtr<ffi::spSkeletonData>,
351    pub(crate) atlas: SpineMutPtr<ffi::spAtlas>,
352}
353
354impl SkeletonData {
355    pub(crate) fn as_mut(&mut self) -> &mut ffi::spSkeletonData {
356        self.inner.as_mut()
357    }
358    pub(crate) fn as_ref(&self) -> &ffi::spSkeletonData {
359        self.inner.as_ref()
360    }
361
362    pub fn animations<'a>(&'a self) -> Vec<Animation<'a>> {
363        let mut converted = Vec::with_capacity(self.as_ref().animationsCount.try_into().unwrap());
364
365        for n in 0..self.as_ref().animationsCount.try_into().unwrap() {
366            unsafe {
367                let inner = *(self.as_ref().animations.add(n));
368                converted.push(Animation {
369                    inner,
370                    _lifetime: PhantomData::<&'a ()>::default(),
371                });
372            }
373        }
374
375        converted
376    }
377
378    pub fn bones<'a>(&'a self) -> Vec<BoneData<'a>> {
379        let mut converted = Vec::with_capacity(self.as_ref().bonesCount.try_into().unwrap());
380
381        for n in 0..self.as_ref().bonesCount.try_into().unwrap() {
382            unsafe {
383                let inner = *(self.as_ref().bones.add(n));
384                converted.push(BoneData {
385                    inner: inner.as_mut().unwrap(),
386                });
387            }
388        }
389
390        converted
391    }
392
393    pub fn strings(&self) -> Vec<&str> {
394        let mut converted = Vec::new();
395
396        for n in 0..self.as_ref().stringsCount.try_into().unwrap() {
397            unsafe {
398                converted.push(
399                    CStr::from_ptr(*self.as_ref().strings.add(n))
400                        .to_str()
401                        .unwrap(),
402                );
403            }
404        }
405
406        converted
407    }
408
409    pub fn position(&self) -> (f32, f32) {
410        let r = self.inner.as_ref();
411        (r.x, r.y)
412    }
413
414    pub fn dimensions(&self) -> (f32, f32) {
415        let r = self.inner.as_ref();
416        (r.width, r.height)
417    }
418
419    pub fn set_position(&mut self, position: (f32, f32)) {
420        let r = self.inner.as_mut();
421        r.x = position.0;
422        r.y = position.1;
423    }
424
425    pub fn set_dimensions(&mut self, dimensions: (f32, f32)) {
426        let r = self.inner.as_mut();
427        r.width = dimensions.0;
428        r.height = dimensions.1;
429    }
430
431    /// Loads a `SkeletonData` instance from the provided binary file path.
432    ///
433    /// # Errors
434    /// Returns a `SpineError::FailLoadSkeleton` instance, with a text message detailing why loading failed.
435    pub fn from_binary_file<P>(path: P, atlas: Atlas) -> Result<Self, SpineError>
436    where
437        P: AsRef<Path>,
438    {
439        let path_str_c = CString::new(path.as_ref().to_str().ok_or_else(|| {
440            SpineError::FailLoadSkeleton("Failed to convert path to string".to_owned())
441        })?)
442        .map_err(|e| {
443            SpineError::FailLoadSkeleton(format!("Failed to convert path to string: {:?}", e))
444        })?;
445
446        unsafe {
447            let binary_data = ffi::spSkeletonBinary_create(atlas.inner.as_mut_ptr());
448            if binary_data.is_null() {
449                return Err(SpineError::FailLoadSkeleton(
450                    "failed to begin binary data load".to_owned(),
451                ));
452            }
453            let data = ffi::spSkeletonBinary_readSkeletonDataFile(binary_data, path_str_c.as_ptr());
454            if data.is_null() {
455                ffi::spSkeletonBinary_dispose(binary_data);
456                return Err(SpineError::FailLoadSkeleton(
457                    CStr::from_ptr((*binary_data).error)
458                        .to_str()
459                        .map_err(|e| {
460                            SpineError::FailLoadSkeleton(format!(
461                                "Failed to retrieve skeleton loading error: {:?}",
462                                e
463                            ))
464                        })?
465                        .to_owned(),
466                ));
467            }
468
469            ffi::spSkeletonBinary_dispose(binary_data);
470
471            Ok(Self {
472                inner: SpineMutPtr::new(data, Some(ffi::spSkeletonData_dispose)),
473                atlas: atlas.inner,
474            })
475        }
476    }
477}
478
479#[cfg(test)]
480mod tests {
481    use super::*;
482    use crate::tests::TEST_CASES;
483
484    #[test]
485    fn load_skeleton() {
486        let test_case = &TEST_CASES[0];
487
488        let mut load = 0;
489
490        let atlas = Atlas::from_file(test_case.atlas(), |_, _| {
491            load += 1;
492            0
493        })
494        .unwrap();
495
496        assert_eq!(2, load);
497
498        let skeleton_data = SkeletonData::from_binary_file(test_case.binary(), atlas).unwrap();
499        let _skeleton = Skeleton::new(&skeleton_data);
500        println!("bones = {:?}", skeleton_data.bones());
501    }
502
503    #[test]
504    fn load_skeleton_drop_order() {
505        let test_case = &TEST_CASES[0];
506
507        let mut load = 0;
508
509        let skeleton_data = {
510            let atlas = Atlas::from_file(test_case.atlas(), |_, _| {
511                load += 1;
512                0
513            })
514            .unwrap();
515
516            assert_eq!(2, load);
517
518            SkeletonData::from_binary_file(test_case.binary(), atlas).unwrap()
519        };
520        let _skeleton = Skeleton::new(&skeleton_data);
521        println!("bones = {:?}", skeleton_data.bones());
522    }
523}