rusty_spine/draw/
simple.rs

1use crate::{
2    c::{c_void, spMeshAttachment_updateRegion},
3    BlendMode, Color, Skeleton, SkeletonClipping,
4};
5
6use super::{ColorSpace, CullDirection};
7
8#[allow(unused_imports)]
9use crate::extension;
10
11/// Renderables generated from [`SimpleDrawer::draw`].
12#[derive(Clone)]
13pub struct SimpleRenderable {
14    /// The index of the slot in [`Skeleton`] that this renderable represents.
15    pub slot_index: usize,
16    /// A list of vertex attributes for a mesh.
17    pub vertices: Vec<[f32; 2]>,
18    /// A list of UV attributes for a mesh.
19    pub uvs: Vec<[f32; 2]>,
20    /// A list of indices for a mesh.
21    pub indices: Vec<u16>,
22    /// The color tint of the mesh.
23    pub color: Color,
24    /// The dark color tint of the mesh.
25    /// See the [Spine User Guide](http://en.esotericsoftware.com/spine-slots#Tint-black).
26    pub dark_color: Color,
27    /// The blend mode to use when drawing this mesh.
28    pub blend_mode: BlendMode,
29    /// The attachment's renderer object as a raw pointer. Usually represents the texture created
30    /// from [`extension::set_create_texture_cb`].
31    pub attachment_renderer_object: Option<*const c_void>,
32}
33
34/// A simple drawer with no optimizations.
35///
36/// Assumes use of the default atlas attachment loader.
37///
38/// See [`SimpleDrawer::draw`]
39pub struct SimpleDrawer {
40    /// The cull direction to use for the vertices.
41    pub cull_direction: CullDirection,
42    /// Set to `true` if the textures are expected to have premultiplied alpha.
43    pub premultiplied_alpha: bool,
44    /// The color space to use for the colors returned in [`SimpleRenderable`].
45    pub color_space: ColorSpace,
46}
47
48impl SimpleDrawer {
49    /// This function returns a list of [`SimpleRenderable`] structs containing all the necessary
50    /// data to create and render meshes. One renderable is created for each visible attachment on
51    /// the skeleton. If a [`SkeletonClipping`] is provided, meshes will be properly clipped. The
52    /// renderables are expected to be rendered in the order provided with the first renderable
53    /// being drawn behind all the others.
54    ///
55    /// # Panics
56    ///
57    /// Panics if not using the default attachment loader with valid atlas regions.
58    pub fn draw(
59        &self,
60        skeleton: &mut Skeleton,
61        mut clipper: Option<&mut SkeletonClipping>,
62    ) -> Vec<SimpleRenderable> {
63        let mut renderables = vec![];
64        let mut world_vertices = vec![];
65        world_vertices.resize(1000, 0.);
66        for slot_index in 0..skeleton.slots_count() {
67            let Some(slot) = skeleton.draw_order_at_index(slot_index) else {
68                continue;
69            };
70            if !slot.bone().active() {
71                if let Some(clipper) = clipper.as_deref_mut() {
72                    clipper.clip_end(&slot);
73                }
74                continue;
75            }
76
77            let mut vertices = vec![];
78            let mut indices = vec![];
79            let mut uvs = vec![];
80            let mut color;
81
82            if let Some(mesh_attachment) = slot.attachment().and_then(|a| a.as_mesh()) {
83                unsafe {
84                    spMeshAttachment_updateRegion(mesh_attachment.c_ptr());
85                };
86                color = mesh_attachment.color();
87
88                unsafe {
89                    mesh_attachment.compute_world_vertices(
90                        &slot,
91                        0,
92                        mesh_attachment.world_vertices_length(),
93                        &mut world_vertices,
94                        0,
95                        2,
96                    );
97                }
98
99                vertices.reserve(mesh_attachment.world_vertices_length() as usize);
100                uvs.reserve(mesh_attachment.world_vertices_length() as usize);
101                uvs.resize(mesh_attachment.world_vertices_length() as usize, [0., 0.]);
102                for i in 0..mesh_attachment.world_vertices_length() {
103                    vertices.push([
104                        world_vertices[i as usize * 2],
105                        world_vertices[i as usize * 2 + 1],
106                    ]);
107                }
108
109                // UVs need to be copied from the indices. I'm not entirely sure why, but it can lead to crashes otherwise.
110                macro_rules! copy_uvs {
111                    ($i:ident) => {
112                        let index = *mesh_attachment.triangles().offset($i);
113                        uvs[index as usize] = [
114                            *mesh_attachment.c_ptr_mut().uvs.offset(index as isize * 2),
115                            *mesh_attachment
116                                .c_ptr_mut()
117                                .uvs
118                                .offset(index as isize * 2 + 1),
119                        ];
120                        let index = *mesh_attachment.triangles().offset($i + 1);
121                        uvs[index as usize] = [
122                            *mesh_attachment.c_ptr_mut().uvs.offset(index as isize * 2),
123                            *mesh_attachment
124                                .c_ptr_mut()
125                                .uvs
126                                .offset(index as isize * 2 + 1),
127                        ];
128                        let index = *mesh_attachment.triangles().offset($i + 2);
129                        uvs[index as usize] = [
130                            *mesh_attachment.c_ptr_mut().uvs.offset(index as isize * 2),
131                            *mesh_attachment
132                                .c_ptr_mut()
133                                .uvs
134                                .offset(index as isize * 2 + 1),
135                        ];
136                    };
137                }
138
139                indices.reserve(mesh_attachment.triangles_count() as usize);
140                if matches!(self.cull_direction, CullDirection::CounterClockwise) {
141                    for i in (0..mesh_attachment.triangles_count() as isize).step_by(3) {
142                        unsafe {
143                            indices.push(*mesh_attachment.triangles().offset(i + 2));
144                            indices.push(*mesh_attachment.triangles().offset(i + 1));
145                            indices.push(*mesh_attachment.triangles().offset(i));
146                            copy_uvs!(i);
147                        }
148                    }
149                } else {
150                    for i in (0..mesh_attachment.triangles_count() as isize).step_by(3) {
151                        unsafe {
152                            indices.push(*mesh_attachment.triangles().offset(i));
153                            indices.push(*mesh_attachment.triangles().offset(i + 1));
154                            indices.push(*mesh_attachment.triangles().offset(i + 2));
155                            copy_uvs!(i);
156                        }
157                    }
158                }
159            } else if let Some(region_attachment) = slot.attachment().and_then(|a| a.as_region()) {
160                color = region_attachment.color();
161
162                let mut world_vertices = vec![];
163                world_vertices.resize(1000, 0.);
164                unsafe {
165                    region_attachment.compute_world_vertices(&slot, &mut world_vertices, 0, 2);
166                }
167
168                vertices.reserve(4);
169                uvs.reserve(4);
170                for i in 0..4 {
171                    vertices.push([
172                        world_vertices[i as usize * 2],
173                        world_vertices[i as usize * 2 + 1],
174                    ]);
175
176                    uvs.push([
177                        region_attachment.uvs()[i as usize * 2],
178                        region_attachment.uvs()[i as usize * 2 + 1],
179                    ]);
180                }
181
182                indices.reserve(6);
183                if matches!(self.cull_direction, CullDirection::CounterClockwise) {
184                    indices.push(2);
185                    indices.push(1);
186                    indices.push(0);
187                    indices.push(0);
188                    indices.push(3);
189                    indices.push(2);
190                } else {
191                    indices.push(0);
192                    indices.push(1);
193                    indices.push(2);
194                    indices.push(2);
195                    indices.push(3);
196                    indices.push(0);
197                }
198            } else if let Some(clipping_attachment) =
199                slot.attachment().and_then(|a| a.as_clipping())
200            {
201                if let Some(clipper) = clipper.as_deref_mut() {
202                    clipper.clip_start(&slot, &clipping_attachment);
203                }
204                continue;
205            } else {
206                if let Some(clipper) = clipper.as_deref_mut() {
207                    clipper.clip_end(&slot);
208                }
209                continue;
210            }
211
212            if let Some(clipper) = clipper.as_deref_mut() {
213                if clipper.is_clipping() {
214                    unsafe {
215                        clipper.clip_triangles(
216                            vertices.as_mut_slice(),
217                            indices.as_mut_slice(),
218                            uvs.as_mut_slice(),
219                            2,
220                        );
221                        let clipped_vertices_size =
222                            (*clipper.c_ptr_ref().clippedVertices).size as usize;
223                        vertices.resize(clipped_vertices_size / 2, [0., 0.]);
224                        std::ptr::copy_nonoverlapping(
225                            (*clipper.c_ptr_ref().clippedVertices).items,
226                            vertices.as_mut_ptr().cast::<f32>(),
227                            clipped_vertices_size,
228                        );
229                        let clipped_triangles_size =
230                            (*clipper.c_ptr_ref().clippedTriangles).size as usize;
231                        indices.resize(clipped_triangles_size, 0);
232                        std::ptr::copy_nonoverlapping(
233                            (*clipper.c_ptr_ref().clippedTriangles).items,
234                            indices.as_mut_ptr(),
235                            clipped_triangles_size,
236                        );
237                        let clipped_uvs_size = (*clipper.c_ptr_ref().clippedUVs).size as usize;
238                        uvs.resize(clipped_uvs_size / 2, [0., 0.]);
239                        std::ptr::copy_nonoverlapping(
240                            (*clipper.c_ptr_ref().clippedUVs).items,
241                            uvs.as_mut_ptr().cast::<f32>(),
242                            clipped_uvs_size,
243                        );
244                    }
245                }
246            }
247
248            let attachment_renderer_object =
249                slot.attachment().and_then(|a| a.as_mesh()).map_or_else(
250                    || {
251                        slot.attachment().and_then(|a| a.as_region()).and_then(
252                            |region_attachment| unsafe {
253                                let attachment_renderer_object = region_attachment
254                                    .renderer_object()
255                                    .get_atlas_region()
256                                    .unwrap()
257                                    .page()
258                                    .c_ptr_ref()
259                                    .rendererObject
260                                    .cast_const();
261                                if attachment_renderer_object.is_null() {
262                                    None
263                                } else {
264                                    Some(attachment_renderer_object)
265                                }
266                            },
267                        )
268                    },
269                    |mesh_attachment| unsafe {
270                        let attachment_renderer_object = mesh_attachment
271                            .renderer_object()
272                            .get_atlas_region()
273                            .unwrap()
274                            .page()
275                            .c_ptr_ref()
276                            .rendererObject
277                            .cast_const();
278                        if attachment_renderer_object.is_null() {
279                            None
280                        } else {
281                            Some(attachment_renderer_object)
282                        }
283                    },
284                );
285
286            color *= slot.color() * skeleton.color();
287            let mut dark_color = slot.dark_color().unwrap_or_default();
288            if self.premultiplied_alpha {
289                color.premultiply_alpha();
290                dark_color *= color.a;
291                dark_color.a = 1.0;
292            } else {
293                dark_color.a = 0.;
294            }
295            color = match self.color_space {
296                ColorSpace::SRGB => color,
297                ColorSpace::Linear => color.nonlinear_to_linear(),
298            };
299
300            dark_color = match self.color_space {
301                ColorSpace::SRGB => dark_color,
302                ColorSpace::Linear => dark_color.nonlinear_to_linear(),
303            };
304
305            renderables.push(SimpleRenderable {
306                slot_index,
307                vertices,
308                uvs,
309                indices,
310                color,
311                dark_color,
312                blend_mode: slot.data().blend_mode(),
313                attachment_renderer_object,
314            });
315            if let Some(clipper) = clipper.as_deref_mut() {
316                clipper.clip_end(&slot);
317            }
318        }
319
320        if let Some(clipper) = clipper {
321            clipper.clip_end2();
322        }
323        renderables
324    }
325}
326
327#[cfg(test)]
328mod test {
329    use crate::test::TestAsset;
330
331    use super::*;
332
333    /// Ensure all the example assets draw without error.
334    #[test]
335    fn simple_drawer() {
336        for json in [true, false] {
337            for example_asset in TestAsset::all() {
338                let (mut skeleton, _) = example_asset.instance(json);
339                let drawer = SimpleDrawer {
340                    cull_direction: CullDirection::Clockwise,
341                    premultiplied_alpha: false,
342                    color_space: ColorSpace::Linear,
343                };
344                let mut clipper = SkeletonClipping::new();
345                let renderables = drawer.draw(&mut skeleton, Some(&mut clipper));
346                assert!(!renderables.is_empty());
347            }
348        }
349    }
350}