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
Refactor primitive meshing #12793
base: main
Are you sure you want to change the base?
Refactor primitive meshing #12793
Conversation
49ea8c8
to
6e356cd
Compare
Fixing test issues Co-authored-by: vero <email@atlasdostal.com>
Fixing test issues Co-authored-by: vero <email@atlasdostal.com>
Fixing test issues Co-authored-by: vero <email@atlasdostal.com>
f07b9b0
to
d08fae3
Compare
…tfeets/bevy into refactor-primitive-meshing
…tfeets/bevy into refactor-primitive-meshing
…tfeets/bevy into refactor-primitive-meshing
Preemptively apologizing for the awful git history, I am still in the process of learning git. |
I sort of recall this sort of refactor being discussed in discord, and I think the main optimization being the ability to pre-compute a lookup table of sin/cos values when the iterator is constructed. That would prevent some needless trig in loops in the torus/cylinder. Have you tried that? |
if self.wrap { | ||
self.count -= 1; | ||
Some(Vec2::new( | ||
(self.theta * self.count as f32).cos(), |
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.
Why not sin_cos
?
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 main goal of this step was to duplicate the existing logic and relocate it to a unified function, and then validate that it hasn't impacted performance or broken the existing meshing. I do agree that sin_cos would make sense, though.
|
||
impl CircleIterator { | ||
pub(crate) fn new(count: usize, wrap: bool) -> CircleIterator { | ||
if wrap { |
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 think this if
could potentially be removed by count: count + wrap as usize
.
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.
Correct. I received some similar feedback earlier and will revise accordingly.
} | ||
|
||
impl CircleIterator { | ||
pub(crate) fn new(count: usize, wrap: bool) -> CircleIterator { |
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.
A doc comment here explaining the inputs would be appreciated.
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.
Noted, will revise.
} | ||
} | ||
impl Iterator for CircleIterator { | ||
type Item = Vec2; |
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 opening a discussion here:
Thoughts on using Vec2
here? Having the resulting values accessed by .x
and .y
seems sort of confusing.
Maybe (f32, f32)
or perhaps SinCos { sin: f32, cos: f32 }
?
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.
Vec2
was chosen because the first alternative to sin/cos calls that I wanted to investigate was application of a Mat2
for rotation, which would get generated on the initialization of the iterator - which could then be subsequently cloned for creation of identical sets of unit circle points.
If this method does not yield favorable results, I agree that something else should be used (and if it does, it would still make sense to have it return something with labels that are more intuitive than .x
, .y
).
I have yet to try a pre-computed lookup, but it is definitely a method that is worth investigating. |
I was the one who suggested the reduction in trigonometry, the goal with this PR was to preserve existing logic and just refactor. It was not done all in one go because this way we can benchmark the refactor and the potential optimization separately |
As I recall, the optimization that @rodolphito brought up in the first place was computing a rotation matrix for the fixed step angle and then just using that to rotate around the circle by iteratively applying it; the point of that is that you only have to do trigonometry one time and the rest is just linear algebra, so it might be a little bit faster. On the other hand, I'm not sure it's really a priority; as we discussed, there is a concern with numerical stability when using that approach, and you run the risk, for instance, of having circular meshes with duplicated vertices not closing up properly if you aren't careful. |
…tfeets/bevy into refactor-primitive-meshing
So, I ended up making a few changes:
Furthermore, it turned out I had made a mistake with creating the initial benchmark. It now actually creates the meshes, and I also made it create meshes at several resolutions. I have made sure to reproduce benchmarks for the appropriate stages of my changes. Before moving original logic to After initial refactor: After changing iterator to use a fixed rotation matrix: |
…tfeets/bevy into refactor-primitive-meshing
This was fairly simple and was mostly a matter of me wanting to make it more clear what was occurring when I was manually creating these forms of iterator in the various geometry implementations.
I basically reimplemented it in a way that is based somewhat off of the sphere implementation, and the only preserved detail from the original is the basic optimization ideas and the math for calculating UVs. The UV math is still the same and the revision should not yield notably distinct results. There was also some confusing variable naming - More importantly, So, I changed the names to be reflective of the sphere implementation, and made it so |
…tfeets/bevy into refactor-primitive-meshing
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.
its lovely to see so much red in the capsule file
@@ -68,6 +68,12 @@ name = "torus" | |||
path = "benches/bevy_render/torus.rs" | |||
harness = false | |||
|
|||
[[bench]] | |||
name = "capsule" | |||
path = "benches/bevy_render/capsule.rs" |
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.
Torus and capsule could probably live together in primitives.rs
black_box( | ||
TorusMeshBuilder::new(black_box(0.5), black_box(1.0)) | ||
.minor_resolution(black_box(12)) | ||
.major_resolution(black_box(16)) |
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.
Its interesting that torus needs the builder-style api to set the resolution while Capsule (and Sphere?) have them set from new
but then also have builder methods. Would be nice to make them all consistent
longitudes: 32, | ||
latitudes: 16, | ||
sectors: 32, | ||
stacks: 8, |
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.
How would you feel about unifying mesh resolution setting across primitives to something along the lines of
/// The number of segments along the U texture coordinate.
u_segments: u32,
/// The number of segments along the V texture coordinate.
v_segments: u32,
I feel like stacks and sectors are not widespread terminology and clearly longitudes and latitudes are easy to mix up given that bevy main capsule has them flipped, plus they dont make sense for something like a cylinder. U and V is both generic over primitve and reuses existing terminology
let stacks_iter = CircleIterator::quarter_circle(stacks); | ||
let sector_circle: Vec<Vec2> = CircleIterator::wrapping(sectors).collect(); | ||
|
||
//insertion of top hemisphere and its associated UVs and normals |
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 think convention is to have a space precede the comment // like this
let z = xz * q.y; | ||
vertices.push([x, y + half_length, z]); | ||
normals.push([x * length_inv, y * length_inv, z * length_inv]); | ||
uvs.push([(j as f32) / sectors_f32, v]); |
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.
can probably store inv_sectors
Objective
Solution
Among a set of a few PRs to refactor and improve primitive meshing.
Summary of changes
CircleIterator
. Behavior has not been changed for all shapes, except forcapsule.rs
.torus.rs
, and worst-case, equivalent speed but (hopefully) better readability in cases such ascapsule.rs
.u32
tousize
to keep things more in line withsphere.rs
, and to allow for somewhat more convenient array indexing calculations forcapsule.rs
.usize
may not be ideal, and it may be more favorable to useu32
for these values instead.capsule.rs
, more significant breaking changes have been made:latitudes
variable - which after integer division by 2 specified the quantity of lines of longitude, has been renamed tostacks
, to be consistent with howsphere.rs
behaves.stacks
now defines the quantity of quad rows on each hemisphere. Accordingly, the setter has also been renamed.latitudes
number should be halved. If it was an odd number, this never affected capsule geometry anyway.longitudes
variable - which defined the quantity of lines of latitude, has been renamed tosectors
, to also be consistent with howsphere.rs
behaves. The setter for this variable has also been renamed.