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}