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 {}