-
Notifications
You must be signed in to change notification settings - Fork 3
The Big Refactor #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some comments to assist reviewers.
|
||
[dependencies] | ||
bitflags = "2.3" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because of alignment, there was actually no space savings in the Rust implementation using bitflags
over the far simpler "just use 4 bools".
# The below features will cause the resulting values to differ from the original | ||
# C implementation. | ||
|
||
# Corrects a sorting bug in the original C implementation. | ||
# See https://github.com/mmikk/MikkTSpace/issues/5 for details | ||
corrected-edge-sorting = [] | ||
|
||
# Uses a BTreeMap to weld vertices which is guaranteed to use the smallest vertex | ||
# indices. | ||
# This handles `NaN` values differently to the C implementation, so will produce | ||
# different results for poor geometry. | ||
# For typical geometry, this should produce identical results. | ||
corrected-vertex-welding = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These features might be worth prefixing with "unstable" so we can hit 1.0.
@@ -23,31 +21,12 @@ struct Mesh { | |||
vertices: Vec<Vertex>, | |||
} | |||
|
|||
struct GlamSpace; | |||
|
|||
impl bevy_mikktspace::VectorSpace for GlamSpace { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this was something I introduced only a few weeks ago, but having spent all that time neck deep in the unsafe C version, I think I prefer the simplicity of just giving the 2 missing float methods.
Plus, an std
feature on by default improves the typical user experience.
fn set_tangent_encoded(&mut self, tangent: [f32; 4], face: usize, vert: usize) { | ||
fn set_tangent( | ||
&mut self, | ||
tangent: Option<bevy_mikktspace::TangentSpace>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change allows both set_tangent
methods to be combined, avoiding the somewhat ugly "implement one of these" pattern that was a holdover from the original C implementation.
Also note I've explicitly replaced this with an Option
. The original C implementation will silently return a default value under certain circumstances, which I think is something worth highlighting to the user, as they may have a more sensible default. TangentSpace
implements Default
, so users can get the exact behaviour anyway.
@@ -280,7 +264,7 @@ fn make_cube() -> Mesh { | |||
|
|||
fn main() { | |||
let mut cube = make_cube(); | |||
bevy_mikktspace::generate_tangents(&mut cube); | |||
let _ = bevy_mikktspace::generate_tangents(&mut cube); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't return bool
in Rust to indicate success! We have Result
!
generate_tests! { | ||
cube: "../data/cube.obj", | ||
crangeract: "../data/crangeract.obj", | ||
doomcone_smooth: "../data/doomcone_smooth.obj", | ||
doomcone: "../data/doomcone.obj", | ||
obliterated: "../data/obliterated.obj", | ||
rancid_geometry: "../data/rancid_geometry.obj", | ||
tangeract: "../data/tangeract.obj", | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For all of these tests we directly compare to the C implementation to ensure identical results.
mod face_vertex; | ||
#[cfg_attr( | ||
not(feature = "corrected-edge-sorting"), | ||
path = "mikktspace/quick_sort_legacy.rs" | ||
)] | ||
mod quick_sort; | ||
mod raw_tangent_space; | ||
#[cfg_attr( | ||
not(feature = "corrected-vertex-welding"), | ||
path = "mikktspace/weld_vertices_legacy.rs" | ||
)] | ||
mod weld_vertices; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've split out some of this file into a couple of supporting modules, just to help with clarity and feature gating.
/// | ||
/// This is separated from [`generate_tangent_space_and_write`] to highlight this | ||
/// step does not require mutable access to the provided [context](MikkTSpaceInterface). | ||
fn generate_tangent_space<I: Geometry<O>, O: Ops>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Compared to the original C implementation, I've reordered some operations to simplify. For example, the vertex list isn't actually used until much later in the algorithm, but it's generated early and must be amended as the triangle list is modified to keep them in sync. Instead, I generate the list towards the end and skip all the intermediate busy work.
) -> Result<Vec<Option<TangentSpace<O>>>, GenerateTangentSpaceError> { | ||
// This set of operations can be done on-line. | ||
// That is, they do not require an intermediatory buffer. | ||
let mut triangle_info_list = generate_triangle_info_list(context, faces_total); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've moved most of the manipulation of triangle information into this method, as the original implementation iterates over the list many times, when it could instead perform most of the tasks in one go. With Rust iterators that's very trivial.
struct TriangleInfo<O: Ops> { | ||
/// Stores the index of each neighboring triangle to this one, if any. | ||
/// Indices correspond to _edges_ of this triangle, rather than _vertices_. | ||
face_neighbors: [Option<usize>; 3], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
usize
increases the size of this type a lot, but it's idiomatic Rust so I'd prefer it over u32
indexing...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NonMaxUsize? will this always be 0..3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah so this is a bit of bad documentation on my part. The Option<usize>
value is an index into the big list of all triangles. The edge value is bounded to 0..3
, but is the index into the face_neighbors
array itself, not the value stored in each slot. I've updated the documentation to better explain that.
As for NonMaxUsize
, I agree, but I'd rather not. The goal is to get this to 1.0 and never worry about it again, whereas the nonmax
crate is still pre-1.0. Technically it shouldn't matter, as it wouldn't be a part of the public API, but it'd still potentially cause duplicated dependencies in the future, etc.
@@ -0,0 +1,33 @@ | |||
/// New-type index for a particular vertex on a particular face. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documenting the winding order here would be helpful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way mikktspace works should be independent of the winding order:
This is done by performing an internal welding step and subsequently an
order-independent evaluation of tangent space for meshes consisting of
triangles and quads.
This means faces can be received in any order and the same is true for the
order of vertices of each face.
The generated result will not be affected by such reordering.
But I'll leave this comment unresolved just in case someone with better rendering experience would like to weigh in!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-malicious, completely safe, generally very clear. I am very much leaning on tests for correctness.
I have a few small nits, and I would like to clarify the copyright notices before we merge this. I know the standard style is "just dump it at the top of the file", but I always find that very unhelpful and unclear about why the notice exists at all.
Co-Authored-By: Alice Cecile <[email protected]>
Added a benchmark which compares |
Objective
It has been discovered through fuzzing that the current
bevy_mikktspace
is no longer byte-compatible with the original C implementation. Usinggit bisect
, I determined that the discrepancy was introduced before the transition to safe Rust. This makes diagnosing and resolving the issue almost impossible.So I started again from scratch.
Using
c2rust
,cargo-fuzz
, test data produced by @atlv24, and the original safe translation by @LaylBongers, I recreated a safe port ofmikktspace
in safe Rust here.This PR moves the changes there to
bevy_mikktspace
.Solution
cargo-fuzz
with built-in comparison tomikktspace-sys
(the original C implementation with Rust bindings).Geometry
API to avoid mutually exclusive trait methods for setting tangent values.no_std
support to directly receive the two missing float methods (sqrt
andacos
) rather than a higher levelVec3
implementation.std
feature to provide a byte-compatible default. This is motivated by a desire to make this a viable replacement formikktspace-sys
for non-Bevy use-cases.Notes
This is a complete replacement of the original code, so it may be easier to just check out and read rather than reviewing online.