stabilkon/
lib.rs

1#![crate_type = "lib"]
2
3mod common_types;
4mod draw_params;
5
6pub use common_types::*;
7pub use draw_params::*;
8pub use mint;
9use snafu::{ensure, Backtrace, Snafu};
10
11type Result<T, E = Error> = std::result::Result<T, E>;
12
13#[derive(Snafu, Debug)]
14#[non_exhaustive]
15pub enum Error {
16    #[snafu(display("Quad count is too large"))]
17    QuadCountIsTooLarge { backtrace: Backtrace },
18
19    #[snafu(display("Texture size is invalid: {}x{}", size.x, size.y))]
20    InvalidTextureSize { size: Vec2, backtrace: Backtrace },
21
22    #[snafu(display(
23        "Vertex buffer with length '{}' is too large. \
24        Generally, to render large meshes you want to subdivide the data into smaller, separate \
25        meshes and render each of those individually",
26        length
27    ))]
28    VertexBufferIsTooLarge { length: usize, backtrace: Backtrace },
29}
30
31/// This is a wrapper for a vertex and index buffers used to build a static mesh quad by quad.
32///
33/// It is expected to be used with a custom vertex type with implemented `From<PosUvColor>`,
34/// with support for ggez and Tetra vertex types provided via crate features.
35///
36/// Just make sure that given vertex type only contains values and does not contain references,
37/// or constructor will fail spectacularly: internally, vertex buffer is inited with zeroed memory
38/// by `MaybeUninit::zeroed()`, due to ggez not having `Default` trait on its vertex type.
39///
40/// # Example
41///
42/// A simple tile map with internal vertex type called `PosUvColor`:
43///
44/// ```
45/// use stabilkon::*;
46/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
47/// // Load texture atlas with tile images:
48/// let tiles_texture_atlas_size = [288.0, 128.0];
49/// // We won't be using custom shaders and such, - let the mesh builder fix UVs for us:
50/// let use_half_pixel_offset = true;
51///
52/// // Single tile is 32×32:
53/// let tile_size = 32.0_f32;
54/// // Let's make test map 256×256;
55/// let map_size = [256, 256];
56/// // Calculate required quad limit for the map:
57/// let quad_count = map_size[0] * map_size[1];
58/// // Standard white color, means tile images will be drawn as-is.
59/// let white_color = [1.0_f32, 1.0, 1.0, 1.0];
60///
61/// // Pick grass tile from atlas, which is lockated at the very top-left of texture atlas.
62/// let grass_tile_source = [0.0, 0.0, 32.0, 32.0];
63/// // Let's draw imaginary grass tile which is located at the very top-left of texture atlas.
64/// let grass_tile_source = [0.0, 0.0, 32.0, 32.0];
65/// // When adding a quad to a mesh builder, you can control UV flipping with `UvFlip` parameter.
66/// // By default the usual left-to-right, bottom-to-top system is used.
67/// // But we decided to use left-to-right, top-to-bottom coordinate system in Rectangle creation above, so when
68/// // adding quads using `grass_tile_source` a value of `UvFlip::Vertical` should be supplied.  
69///
70/// // Create a mesh builder for an indexed mesh capable of holding entire map...
71/// let mut quad_index = 0_u32;
72/// let mut mesh_builder: MeshFromQuads<PosUvColor> =
73///     MeshFromQuads::new(tiles_texture_atlas_size, use_half_pixel_offset, quad_count)?;
74/// // ... and fill it with grass tile:
75/// for y in 0..map_size[1] {
76///     for x in 0..map_size[0] {
77///         let position = [x as f32 * tile_size, y as f32 * tile_size];
78///         mesh_builder.set_pos_color_source(quad_index, position, white_color, grass_tile_source, UvFlip::Vertical);
79///         quad_index += 1;
80///     }
81/// }
82/// // Finally, create a mesh consisting of quads covered with grass tile texture region:
83/// let (vertices, indices) = mesh_builder.into_vertices_and_indices();
84/// // All done, now you can draw these vertices using any API you want.
85/// // Both vertices and indices are in clockwise order.
86/// # Ok(())
87/// # }
88/// ```
89#[derive(Clone, Debug)]
90pub struct MeshFromQuads<TVertex>
91where
92    TVertex: From<PosUvColor>,
93{
94    texture_size: Vec2,
95    use_half_pixel_offset: bool,
96    indices: Option<Vec<u32>>,
97    vertices: Vec<TVertex>,
98    quad_limit: u32,
99    use_indices: bool,
100    vertices_per_quad: u32,
101    max_vertices: u32,
102}
103
104#[cfg(feature = "ggez")]
105impl MeshFromQuads<ggez::graphics::Vertex> {
106    /// Creates a ggez mesh from all the added quads.
107    ///
108    /// # Errors
109    ///
110    /// Will return `Err` if builder has no indices.
111    pub fn create_mesh(
112        &self,
113        ctx: &mut ggez::Context,
114        texture: ggez::graphics::Image,
115    ) -> ggez::GameResult<ggez::graphics::Mesh> {
116        use ggez::graphics::Mesh;
117        match self.indices.as_ref() {
118            Some(indices) => Mesh::from_raw(ctx, &self.vertices, indices, Some(texture)),
119            None => Err(ggez::GameError::CustomError(
120                "Unindexed meshes are not supported".to_owned(),
121            )),
122        }
123    }
124
125    /// Changes the specified ggez mesh to use vertex and index buffers of this builder.
126    /// Don't forget to set mesh's texture if needed.
127    ///
128    /// # Errors
129    ///
130    /// Will return `Err` if builder has no indices.
131    pub fn update_mesh(
132        &self,
133        ctx: &mut ggez::Context,
134        mesh: &mut ggez::graphics::Mesh,
135    ) -> ggez::GameResult<()> {
136        match self.indices.as_ref() {
137            Some(indices) => {
138                mesh.set_vertices(ctx, &self.vertices, indices);
139                Ok(())
140            }
141            None => Err(ggez::GameError::CustomError(
142                "Unindexed meshes are not supported".to_owned(),
143            )),
144        }
145    }
146}
147
148#[cfg(feature = "tetra")]
149impl MeshFromQuads<tetra::graphics::mesh::Vertex> {
150    /// Creates a Tetra mesh from all the added quads.
151    ///
152    /// Returns both the mesh and its new vertex buffer. You can use its `set_data` if an update is needed later.
153    ///
154    /// # Errors
155    ///
156    /// Will return `Err` if the underlying graphics API encounters an error when allocating vertex or index buffer.
157    pub fn create_mesh(
158        &self,
159        ctx: &mut tetra::Context,
160        texture: tetra::graphics::Texture,
161    ) -> tetra::Result<(
162        tetra::graphics::mesh::Mesh,
163        tetra::graphics::mesh::VertexBuffer,
164    )> {
165        use tetra::graphics::mesh::{IndexBuffer, Mesh, VertexBuffer};
166        let vertex_buffer = VertexBuffer::new(ctx, &self.vertices)?;
167        let mut mesh = if let Some(index_buffer) = &self.indices {
168            Mesh::indexed(vertex_buffer.clone(), IndexBuffer::new(ctx, index_buffer)?)
169        } else {
170            Mesh::new(vertex_buffer.clone())
171        };
172        mesh.set_texture(texture);
173        Ok((mesh, vertex_buffer))
174    }
175
176    /// Changes the specified Tetra mesh to use texture, vertex and index buffers of this builder.
177    /// Don't forget to set mesh's texture if needed.
178    ///
179    /// Returns mesh's new vertex buffer. You can use its `set_data` if an update is needed later.
180    ///
181    /// # Errors
182    ///
183    /// Will return `Err` if the underlying graphics API encounters an error when allocating vertex or index buffer.
184    pub fn update_mesh(
185        &self,
186        ctx: &mut tetra::Context,
187        mesh: &mut tetra::graphics::mesh::Mesh,
188    ) -> tetra::Result<tetra::graphics::mesh::VertexBuffer> {
189        use tetra::graphics::mesh::{IndexBuffer, VertexBuffer};
190        let vertex_buffer = VertexBuffer::new(ctx, &self.vertices)?;
191        if let Some(index_buffer) = &self.indices {
192            mesh.set_index_buffer(IndexBuffer::new(ctx, index_buffer)?);
193        } else {
194            mesh.reset_index_buffer();
195        }
196        mesh.set_vertex_buffer(vertex_buffer.clone());
197        Ok(vertex_buffer)
198    }
199}
200
201impl<TVertex> MeshFromQuads<TVertex>
202where
203    TVertex: Clone + From<PosUvColor>,
204{
205    /// Creates a mesh builder for an indexed mesh capable of holding exactly `quad_limit` quads.
206    ///
207    /// Note that indices and vertices are allocated immediately for the entire `quad_limit`
208    /// regardless of actual `push` call count.
209    ///
210    /// * `texture_size` - Size of the texture atlas which will be used by the resulting mesh.
211    /// * `use_half_pixel_offset` - If set to true, applies [half pixel correction]
212    /// (https://docs.microsoft.com/en-us/windows/win32/direct3d9/directly-mapping-texels-to-pixels) directly to UVs.
213    /// It is better to use padded texture atlas with this fix,
214    /// otherwise only half of border pixels will be displayed. This is often imperceptible, unlike bleeding,
215    /// but keep it in mind.
216    /// If set to false, expects end users to deal with texture bleeding themselves,
217    /// e.g. with correct texture sampling or shifting viewport by half a pixel.
218    /// * `quad_limit` - Amount of quads in the built static mesh. For safest allocations,
219    /// try not to go over 32 MB of needed VRAM for a single mesh.
220    ///
221    /// # Errors
222    ///
223    /// Will return `Err` if `texture_size` is < 1 or `quad_limit` is too high.
224    #[inline]
225    pub fn new<T: Into<Vec2>>(
226        texture_size: T,
227        use_half_pixel_offset: bool,
228        quad_limit: u32,
229    ) -> Result<Self> {
230        Self::create(texture_size, use_half_pixel_offset, quad_limit, true)
231    }
232
233    /// Creates a mesh builder for a mesh without indices capable of holding exactly `quad_limit` quads.
234    ///
235    /// Note that vertices are allocated immediately for the entire `quad_limit`
236    /// regardless of actual `push` call count.
237    ///
238    /// * `texture_size` - Size of the texture atlas which will be used by the resulting mesh.
239    /// * `use_half_pixel_offset` - If set to true, applies [half pixel correction]
240    /// (https://docs.microsoft.com/en-us/windows/win32/direct3d9/directly-mapping-texels-to-pixels) directly to UVs.
241    /// It is better to use padded texture atlas with this fix,
242    /// otherwise only half of border pixels will be displayed. This is often imperceptible, unlike bleeding,
243    /// but keep it in mind.
244    /// If set to false, expects end users to deal with texture bleeding themselves,
245    /// e.g. with correct texture sampling or shifting viewport by half a pixel.
246    /// * `quad_limit` - Amount of quads in the built static mesh. For safest allocations,
247    /// try not to go over 32 MB of needed VRAM for a single mesh.
248    ///
249    /// # Errors
250    ///
251    /// Will return `Err` if `texture_size` is < 1 or `quad_limit` is too high.
252    #[inline]
253    pub fn new_without_indices<T: Into<Vec2>>(
254        texture_size: T,
255        use_half_pixel_offset: bool,
256        quad_limit: u32,
257    ) -> Result<Self> {
258        Self::create(texture_size, use_half_pixel_offset, quad_limit, false)
259    }
260
261    /// Creates a mesh builder from the existing vertices and indices.
262    ///
263    /// * `texture_size` - Size of the texture atlas which will be used by the resulting mesh.
264    /// * `use_half_pixel_offset` - If set to true, applies [half pixel correction]
265    /// (https://docs.microsoft.com/en-us/windows/win32/direct3d9/directly-mapping-texels-to-pixels) directly to UVs.
266    /// It is better to use padded texture atlas with this fix,
267    /// otherwise only half of border pixels will be displayed. This is often imperceptible, unlike bleeding,
268    /// but keep it in mind.
269    /// If set to false, expects end users to deal with texture bleeding themselves,
270    /// e.g. with correct texture sampling or shifting viewport by half a pixel.
271    /// * `vertices` - Existing vertices to modify.
272    /// * `indices` - Indices for the given existing vertices.
273    ///
274    /// # Errors
275    ///
276    /// Will return `Err` if `texture_size` is < 1.
277    pub fn from_texture_vertices_indices<T: Into<Vec2>>(
278        texture_size: T,
279        use_half_pixel_offset: bool,
280        vertices: Vec<TVertex>,
281        indices: Option<Vec<u32>>,
282    ) -> Result<Self> {
283        let texture_size_vec: Vec2 = texture_size.into();
284        ensure!(
285            texture_size_vec.x >= 1.0 && texture_size_vec.y >= 1.0,
286            InvalidTextureSize {
287                size: texture_size_vec
288            }
289        );
290        ensure!(
291            u32::try_from(vertices.len()).is_ok(),
292            VertexBufferIsTooLarge {
293                length: vertices.len()
294            }
295        );
296
297        let use_indices = indices.is_some();
298        let vertices_per_quad = vertices_per_quad(use_indices);
299        let max_vertices = vertices.len() as u32;
300        let quad_limit = max_vertices / vertices_per_quad;
301        Ok(Self {
302            texture_size: texture_size_vec,
303            use_half_pixel_offset,
304            indices,
305            vertices,
306            quad_limit,
307            use_indices,
308            vertices_per_quad,
309            max_vertices,
310        })
311    }
312
313    pub(crate) fn create<T: Into<Vec2>>(
314        texture_size: T,
315        use_half_pixel_offset: bool,
316        quad_limit: u32,
317        use_indices: bool,
318    ) -> Result<Self> {
319        let texture_size_vec: Vec2 = texture_size.into();
320        ensure!(
321            texture_size_vec.x >= 1.0 && texture_size_vec.y >= 1.0,
322            InvalidTextureSize {
323                size: texture_size_vec
324            }
325        );
326
327        let indices = if use_indices {
328            Some(generate_quad_indices(quad_limit)?)
329        } else {
330            None
331        };
332        let vertices_per_quad = vertices_per_quad(use_indices);
333        let max_vertices = total_vertices_in_quads(quad_limit, use_indices)?;
334        let zeroed_vertex = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
335        let vertices: Vec<TVertex> = vec![zeroed_vertex; max_vertices as usize];
336        Ok(Self {
337            texture_size: texture_size_vec,
338            use_half_pixel_offset,
339            indices,
340            vertices,
341            quad_limit,
342            use_indices,
343            vertices_per_quad,
344            max_vertices,
345        })
346    }
347
348    /// Gets the reference to the indices which will be stored in an index buffer after a `create_mesh` call.
349    ///
350    /// Indices draw the vertices in clockwise order.
351    /// Index vec is pre-allocated and will contain valid indices for the entire `quad_limit` of quads.
352    #[inline]
353    #[must_use]
354    pub fn indices(&self) -> Option<&Vec<u32>> {
355        self.indices.as_ref()
356    }
357
358    /// Gets the total amount of quads in the vertex buffer.
359    #[inline]
360    #[must_use]
361    pub fn quad_limit(&self) -> u32 {
362        self.quad_limit
363    }
364
365    /// Gets the reference to the vertices which will be stored in a vertex buffer after a `create_mesh` call.
366    ///
367    /// Vertices are in clockwise order.
368    /// Vertex vec is pre-allocated for the entire `quad_limit` of quads,
369    /// with currently unused vertices set to `Vertex::default`.
370    #[inline]
371    #[must_use]
372    pub fn vertices(&self) -> &Vec<TVertex> {
373        &self.vertices
374    }
375
376    /// Gets the total amount of vertices in the vertex buffer.
377    #[inline]
378    #[must_use]
379    pub fn vertices_limit(&self) -> u32 {
380        self.max_vertices
381    }
382
383    /// Gets the amount of vertices used per single quad: 4 if this builder uses indices, 6 otherwise.
384    #[inline]
385    #[must_use]
386    pub fn vertices_per_quad(&self) -> u32 {
387        self.vertices_per_quad
388    }
389
390    #[inline]
391    /// Sets all added quad vertices to a default vertex data.
392    pub fn clear(&mut self) {
393        for item in &mut self.vertices {
394            *item = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
395        }
396    }
397
398    /// Consumes this builder and returns its vertices and indices.
399    ///
400    /// Both vertices and indices are in clockwise order.
401    #[inline]
402    #[must_use]
403    pub fn into_vertices_and_indices(self) -> (Vec<TVertex>, Option<Vec<u32>>) {
404        (self.vertices, self.indices)
405    }
406
407    /// Changes quad at the given index to use the specified draw params.
408    /// Returns true if the given quad index was in vertices range and vertices were set correctly; false otherwise.
409    pub fn set<T: QuadDrawParams>(&mut self, quad_index: u32, draw_params: &T) -> bool {
410        let vertices_per_quad = self.vertices_per_quad();
411        let target_offset = quad_index * vertices_per_quad;
412        if target_offset + vertices_per_quad <= self.max_vertices {
413            draw_params.set_vertices(
414                self.texture_size,
415                self.use_half_pixel_offset,
416                self.use_indices,
417                target_offset as usize,
418                &mut self.vertices,
419            );
420            true
421        } else {
422            false
423        }
424    }
425
426    /// Changes quad at the given index to use the specified position, color and texture source rectangle.
427    /// Returns true if the given quad index was in vertices range and vertices were set correctly; false otherwise.
428    ///
429    /// * `quad_index` - Infex of the quad to set. Quads start at 0 and end at `limit` - 1.
430    /// * `position` - Quad position, top-left corner.
431    /// * `color` - Quad vertices color.
432    /// * `source` - Texture source rectangle. Along with `flip`, determines which part of the texture will drawn.
433    /// * `flip` - UV flip mode.
434    #[inline]
435    pub fn set_pos_color_source<TColor, TRect, TVec2>(
436        &mut self,
437        quad_index: u32,
438        position: TVec2,
439        color: TColor,
440        source: TRect,
441        flip: UvFlip,
442    ) -> bool
443    where
444        TColor: Into<Color>,
445        TRect: Into<Rectangle>,
446        TVec2: Into<Vec2>,
447    {
448        let draw_info = PosColorSource::new(position, color, source, flip);
449        self.set(quad_index, &draw_info)
450    }
451
452    /// Changes quad at the given index to use the specified position, color, size and texture source rectangle.
453    /// Returns true if the given quad index was in vertices range and vertices were set correctly; false otherwise.
454    ///
455    /// * `quad_index` - Infex of the quad to set. Quads start at 0 and end at `limit` - 1.
456    /// * `position` - Quad position, top-left corner.
457    /// * `color` - Quad vertices color.
458    /// * `size` - Destination size, used for absolute scaling.
459    /// * `source` - Texture source rectangle. Along with `flip`, determines which part of the texture will drawn.
460    /// * `flip` - UV flip mode.
461    #[inline]
462    pub fn set_pos_color_size_source<TColor, TRect, TVec2>(
463        &mut self,
464        quad_index: u32,
465        position: TVec2,
466        color: TColor,
467        size: TVec2,
468        source: TRect,
469        flip: UvFlip,
470    ) -> bool
471    where
472        TColor: Into<Color>,
473        TRect: Into<Rectangle>,
474        TVec2: Into<Vec2>,
475    {
476        let draw_info = PosColorSizeSource::new(position, color, size, source, flip);
477        self.set(quad_index, &draw_info)
478    }
479}
480
481/// Generates indices for the given amount of quads.
482///
483/// # Errors
484///
485/// Will return `Err` if `quad_count` multiplied by 6 overflows u32.
486pub fn generate_quad_indices(quad_count: u32) -> Result<Vec<u32>> {
487    let length = match quad_count.checked_mul(6) {
488        Some(total_indices) => Ok(total_indices),
489        None => QuadCountIsTooLarge {}.fail(),
490    }?;
491    let mut indices = vec![0_u32; length as usize];
492    let mut offset: usize = 0;
493    let mut index_value: u32 = 0;
494    while offset < length as usize {
495        indices[offset] = index_value;
496        indices[offset + 1] = index_value + 1;
497        indices[offset + 2] = index_value + 2;
498        indices[offset + 3] = index_value + 2;
499        indices[offset + 4] = index_value + 3;
500        indices[offset + 5] = index_value;
501        index_value += 4;
502        offset += 6;
503    }
504    Ok(indices)
505}
506
507/// Gets the amount of vertices used per single quad: 4 when using indices, 6 otherwise.
508#[inline]
509#[must_use]
510pub const fn vertices_per_quad(use_indices: bool) -> u32 {
511    if use_indices {
512        4
513    } else {
514        6
515    }
516}
517
518/// Gets the amount of vertices needed to draw given quad count.
519///
520/// # Errors
521///
522/// Will return `Err` if `quad_count` multiplied by vertices per quad overflows u32.
523#[inline]
524pub(crate) fn total_vertices_in_quads(quad_count: u32, use_indices: bool) -> Result<u32> {
525    match quad_count.checked_mul(vertices_per_quad(use_indices)) {
526        Some(total_vertices) => Ok(total_vertices),
527        None => QuadCountIsTooLarge {}.fail(),
528    }
529}