bevy_mikktspace/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc(
3    html_logo_url = "https://bevy.org/assets/icon.png",
4    html_favicon_url = "https://bevy.org/assets/icon.png"
5)]
6
7//! Provides a byte-identical implementation of [`mikktspace`] in entirely safe
8//! and idiomatic Rust.
9//! This is used to generate [`TangentSpace`] values for 3D geometry.
10//! Like the original implementation, this crate has no dependencies.
11//!
12//! With _only_ the default features enabled, this should produce _identical_
13//! results to [`mikktspace`] on x86 architectures.
14//! Other architectures may produce differing results due to the original
15//! implementation's reliance on undefined behavior.
16//!
17//! # Usage
18//!
19//! As a preliminary step for `no_std` users, you must provide an implementation
20//! for [`Ops`].
21//! When the `std` feature is enabled, one is provided and automatically selected
22//! as the default.
23//!
24//! First, implement [`Geometry`] for your geometry.
25//!
26//! ```ignore
27//! impl Geometry for MyGeometry { /* ... */ }
28//! ```
29//!
30//! The interface is how this crate reads geometric information _and_ writes back
31//! the generated tangent space information.
32//!
33//! Finally, use [`generate_tangents`] to calculate and write back all
34//! [`TangentSpace`] values.
35//!
36//! # Description
37//!
38//! The code is designed to consistently generate the same tangent spaces, for a
39//! given mesh, in any tool in which it is used.
40//! This is done by performing an internal welding step and subsequently an
41//! order-independent evaluation of tangent space for meshes consisting of
42//! triangles and quads.
43//! This means faces can be received in any order and the same is true for the
44//! order of vertices of each face.
45//! The generated result will not be affected by such reordering.
46//! Additionally, whether degenerate (vertices or texture coordinates) primitives
47//! are present or not will not affect the generated results either.
48//!
49//! Once tangent space calculation is done the vertices of degenerate primitives
50//! will simply inherit tangent space from neighboring non degenerate primitives.
51//! The analysis behind this implementation can be found in Morten S. Mikkelsen's
52//! master's [thesis].
53//!
54//! Note that though the tangent spaces at the vertices are generated in an
55//! order-independent way, by this implementation, the interpolated tangent space
56//! is still affected by which diagonal is chosen to split each quad.
57//! A sensible solution is to have your tools pipeline always split quads by the
58//! shortest diagonal.
59//! This choice is order-independent and works with mirroring.
60//! If these have the same length then compare the diagonals defined by the
61//! texture coordinates.
62//! [XNormal], which is a tool for baking normal maps, allows you to write your
63//! own tangent space plugin and also quad triangulator plugin.
64//!
65//! # Features
66//!
67//! ## `std` (default)
68//!
69//! Provides access to the standard library, allowing a default implementation
70//! of [`Ops`] to be provided.
71//! If you disable this feature, you will need to provide a type implementing
72//! [`Ops`] as the `O` parameter in the [`Geometry`] trait.
73//!
74//! ```
75//! # use bevy_mikktspace::{Geometry, Ops};
76//! # struct MyOps;
77//! # struct MyGeometry;
78//! impl Ops for MyOps {
79//!     fn sqrt(x: f32) -> f32 {
80//!         unimplemented!()
81//!     }
82//!
83//!     fn acos(x: f32) -> f32 {
84//!         unimplemented!()
85//!     }
86//! }
87//!
88//! # #[cfg(any())]
89//! impl Geometry<MyOps> for MyGeometry { /* ... */ }
90//! ```
91//!
92//! A common backend for implementing [`Ops`] is [`libm`]:
93//!
94//! ```
95//! # use bevy_mikktspace::Ops;
96//! # struct LibmOps;
97//! impl Ops for LibmOps {
98//!     fn sqrt(x: f32) -> f32 {
99//!         libm::sqrtf(x)
100//!     }
101//!
102//!     fn acos(x: f32) -> f32 {
103//!         libm::acos(x as f64) as f32
104//!     }
105//! }
106//! ```
107//!
108//! Note that alternate backends _may_ break byte-compatibility with the original
109//! C implementation.
110//! It also should go without saying that improper implementations could give
111//! entirely incorrect results.
112//!
113//! ## `corrected-edge-sorting`
114//!
115//! Fixes a known bug in the original C implementation which can affect the
116//! generated values.
117//! The bug can cause edges to be improperly sorted, leading neighboring faces
118//! to be ungrouped.
119//! If you do not need byte compatibility with the C implementation, it is
120//! recommended to enable this feature for improved performance, compile times,
121//! and correctness.
122//!
123//! ## `corrected-vertex-welding`
124//!
125//! Fixes a known bug in the original C implementation which can affect the
126//! generated values.
127//! This bug causes vertices to be combined in such a way that the lowest vertex
128//! index isn't reliably selected.
129//! If you do not need byte compatibility with the C implementation, it is
130//! recommended to enable this feature for improved performance, compile times,
131//! and correctness.
132//!
133//! # Copyright
134//!
135//! This code is a Rust reimplementation of <https://github.com/mmikk/MikkTSpace>.
136//! The copyright notice below reflects that history, and should not be removed.
137//!
138//! > Copyright (C) 2011 by Morten S. Mikkelsen
139//! >
140//! > This software is provided 'as-is', without any express or implied
141//! > warranty.  In no event will the authors be held liable for any damages
142//! > arising from the use of this software.
143//! >
144//! > Permission is granted to anyone to use this software for any purpose,
145//! > including commercial applications, and to alter it and redistribute it
146//! > freely, subject to the following restrictions:
147//! >
148//! > 1. The origin of this software must not be misrepresented; you must not
149//! >    claim that you wrote the original software. If you use this software
150//! >    in a product, an acknowledgment in the product documentation would be
151//! >    appreciated but is not required.
152//! > 2. Altered source versions must be plainly marked as such, and must not be
153//! >    misrepresented as being the original software.
154//! > 3. This notice may not be removed or altered from any source distribution.
155//!
156//! The above notice will also be included in any derivative source files.
157//!
158//! # Advice
159//!
160//! To avoid visual errors (distortions/unwanted hard edges in lighting), when
161//! using sampled normal maps, the normal map sampler must use the exact inverse
162//! of the pixel shader transformation.
163//! The most efficient transformation we can possibly do in the pixel shader is
164//! achieved by using, directly, the "unnormalized" interpolated tangent, bitangent
165//! and vertex normal: `vT`, `vB` and `vN`.
166//!
167//! ```c, ignore
168//! // pixel shader (fast transform out)
169//! vNout = normalize(vNt.x * vT + vNt.y * vB + vNt.z * vN);
170//! ```
171//!
172//! where `vNt` is the tangent space normal.
173//!
174//! The normal map sampler must likewise use the interpolated and "unnormalized"
175//! tangent, bitangent and vertex normal to be compliant with the pixel shader.
176//!
177//! ```c, ignore
178//! // sampler does (exact inverse of pixel shader):
179//! float3 row0 = cross(vB, vN);
180//! float3 row1 = cross(vN, vT);
181//! float3 row2 = cross(vT, vB);
182//! float fSign = dot(vT, row0)<0 ? -1 : 1;
183//! vNt = normalize(fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)));
184//! ```
185//!
186//! where `vNout` is the sampled normal in some chosen 3D space.
187//!
188//! Should you choose to reconstruct the bitangent in the pixel shader instead of
189//! the vertex shader, as explained earlier, then be sure to do this in the normal
190//! map sampler also.
191//!
192//! Finally, beware of quad triangulations.
193//! If the normal map sampler doesn't use the same triangulation of quads as your
194//! renderer then problems will occur since the interpolated tangent spaces will
195//! differ even though the vertex level tangent spaces match.
196//! This can be solved either by triangulating before sampling/exporting or by
197//! using the order-independent choice of diagonal for splitting quads suggested earlier.
198//! However, this must be used both by the sampler and your tools/rendering pipeline.
199//!
200//! [`mikktspace`]: http://www.mikktspace.com/
201//! [thesis]: https://web.archive.org/web/20250321012901/https://image.diku.dk/projects/media/morten.mikkelsen.08.pdf
202//! [XNormal]: https://xnormal.net/
203//! [`libm`]: https://docs.rs/libm
204
205#![forbid(unsafe_code)]
206#![no_std]
207
208extern crate alloc;
209
210mod math;
211mod mikktspace;
212
213#[cfg(feature = "std")]
214mod std {
215    extern crate std;
216
217    /// Implements [`Ops`](crate::Ops) using the standard library.
218    /// This is the recommended default when the `std` feature is enabled, as it
219    /// it designed to identically match the results provided by the original
220    /// mikktspace C library.
221    pub struct StdOps;
222
223    impl crate::Ops for StdOps {
224        #[inline]
225        fn sqrt(x: f32) -> f32 {
226            x.sqrt()
227        }
228
229        #[inline]
230        fn acos(x: f32) -> f32 {
231            // Using f64::acos for added precision.
232            // This is required to match the C implementation.
233            (x as f64).acos() as f32
234        }
235    }
236}
237
238pub use math::Ops;
239
240#[cfg(feature = "std")]
241pub use std::StdOps;
242
243/// Generates [`TangentSpace`]s for the provided geometry with the grouping
244/// threshold disabled.
245pub fn generate_tangents<I, O>(interface: &mut I) -> Result<(), GenerateTangentSpaceError>
246where
247    I: Geometry<O>,
248    O: Ops,
249{
250    generate_tangents_with_threshold(interface, -1_f32)
251}
252
253/// Generates [`TangentSpace`]s for the provided geometry with a provided `threshold`
254/// for vertex grouping.
255///
256/// Note that unlike the original C implementation, which accepted an _angular_ threshold,
257/// this function accepts a _linear_ threshold.
258/// The angular threshold can be converted into a linear one trivially using cosine.
259///
260/// ```ignore
261/// let angular_threshold = 180_f32;
262/// let linear_threshold = angular_threshold.to_radians().cos();
263/// ```
264///
265/// Appropriate threshold values should be in the range `[-1..=1]`.
266pub fn generate_tangents_with_threshold<I, O>(
267    interface: &mut I,
268    threshold: f32,
269) -> Result<(), GenerateTangentSpaceError>
270where
271    I: Geometry<O>,
272    O: Ops,
273{
274    mikktspace::generate_tangent_space_and_write(interface, threshold)
275}
276
277/// Provides an interface for reading vertex information from geometry, and writing
278/// back out the calculated tangent space information.
279///
280/// Without the `std` feature, there is no default implementation for [`Ops`]
281/// provided.
282/// Instead, you must also provide a type implementing [`Ops`] using an alternative
283/// math backend, such as [`libm`].
284///
285/// [`libm`]: https://docs.rs/libm
286pub trait Geometry<
287    #[cfg(not(feature = "std"))] O: Ops,
288    #[cfg(feature = "std")] O: Ops = std::StdOps,
289>
290{
291    /// Returns the number of faces on the mesh to be processed.
292    /// This can include unsupported face types (e.g., not triangles or quads),
293    /// but they will be ignored.
294    fn num_faces(&self) -> usize;
295
296    /// Returns the number of vertices on face number `face`.
297    /// `face` is a number in the range `0..get_num_faces()`.
298    fn num_vertices_of_face(&self, face: usize) -> usize;
299
300    /// Returns the position of the referenced `face` of vertex number `vert`.
301    /// `face` is a number in the range `0..get_num_faces()`.
302    /// `vert` is in the range `0..=2` for triangles and `0..=3` for quads.
303    fn position(&self, face: usize, vert: usize) -> [f32; 3];
304
305    /// Returns the normal of the referenced `face` of vertex number `vert`.
306    /// `face` is a number in the range `0..get_num_faces()`.
307    /// `vert` is in the range `0..=2` for triangles and `0..=3` for quads.
308    fn normal(&self, face: usize, vert: usize) -> [f32; 3];
309
310    /// Returns the texture coordinate of the referenced `face` of vertex number `vert`.
311    /// `face` is a number in the range `0..get_num_faces()`.
312    /// `vert` is in the range `0..=2` for triangles and `0..=3` for quads.
313    fn tex_coord(&self, face: usize, vert: usize) -> [f32; 2];
314
315    /// This function is used to return tangent space results to the application.
316    ///
317    /// Note that unlike the original C implementation, tangent spaces are turned
318    /// as an [`Option<TangentSpace>`].
319    /// This serves two purposes:
320    ///
321    /// 1. [`TangentSpace`] encodes all calculated results for a particular vertex,
322    ///    and provides convenient getters for simplified results.
323    /// 2. An [`Option`] is provided as the internal algorithm _may not produce a
324    ///    value for this vertex_.
325    ///    This typically occurs in cases where a degenerate face has no neighbors
326    ///    to borrow a value from.
327    ///    Instead of silently returning a default value, this implementation
328    ///    explicitly provides [`None`] and leaves it up to the user to instead
329    ///    use the default value.
330    fn set_tangent(&mut self, tangent_space: Option<TangentSpace>, face: usize, vert: usize);
331}
332
333/// Wraps the relevant results generated when calculating the tangent space for
334/// a particular vertex on a particular face.
335///
336/// Typically, you will call [`tangent`](TangentSpace::tangent) to retrieve the
337/// tangent value.
338#[derive(Clone, Copy, PartialEq)]
339pub struct TangentSpace {
340    tangent: [f32; 3],
341    bi_tangent: [f32; 3],
342    mag_s: f32,
343    mag_t: f32,
344    is_orientation_preserving: bool,
345}
346
347impl Default for TangentSpace {
348    fn default() -> Self {
349        Self {
350            tangent: [1., 0., 0.],
351            bi_tangent: [0., 1., 0.],
352            mag_s: 1.0,
353            mag_t: 1.0,
354            is_orientation_preserving: false,
355        }
356    }
357}
358
359impl TangentSpace {
360    /// Returns the normalized tangent as an `[x, y, z]` array.
361    #[inline]
362    pub const fn tangent(&self) -> [f32; 3] {
363        self.tangent
364    }
365
366    /// Returns the normalized bi-tangent as an `[x, y, z]` array.
367    #[inline]
368    pub const fn bi_tangent(&self) -> [f32; 3] {
369        self.bi_tangent
370    }
371
372    /// Returns the magnitude of the tangent.
373    #[inline]
374    pub const fn tangent_magnitude(&self) -> f32 {
375        self.mag_s
376    }
377
378    /// Returns the magnitude of the bi-tangent.
379    #[inline]
380    pub const fn bi_tangent_magnitude(&self) -> f32 {
381        self.mag_t
382    }
383
384    /// Indicates if this generated tangent preserves the original orientation of
385    /// the face.
386    #[inline]
387    pub const fn is_orientation_preserving(&self) -> bool {
388        self.is_orientation_preserving
389    }
390
391    /// Returns an encoded summary of the tangent and bi-tangent as an `[x, y, z, w]`
392    /// array.
393    #[inline]
394    pub const fn tangent_encoded(&self) -> [f32; 4] {
395        let sign = if self.is_orientation_preserving {
396            1.0
397        } else {
398            -1.0
399        };
400        [self.tangent[0], self.tangent[1], self.tangent[2], sign]
401    }
402}
403
404/// Error returned when failing to generate tangent spaces for a geometry.
405#[derive(Clone, PartialEq, Debug)]
406// Reserving the right to introduce new error variants in the future.
407#[non_exhaustive]
408pub enum GenerateTangentSpaceError {}
409
410impl core::fmt::Display for GenerateTangentSpaceError {
411    fn fmt(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
412        unreachable!()
413    }
414}
415
416impl core::error::Error for GenerateTangentSpaceError {}