Skip to content

Conversation

podusowski
Copy link
Contributor

I am working on rendering vector maps in Walkers (which looks promising!) and I noticed weird artifacts:

image

It appears that the issue is caused by edge normals, which kind of explode when the shape is sharp enough. This PR adds a limiter, so that the normal is clamped when it crosses certain length. It's not perfect and most likely can be fixed in more elegant way, but it does seem like an improvement:

image image

Copy link

github-actions bot commented Sep 21, 2025

Preview available at https://egui-pr-preview.github.io/pr/7554-feathering-fix
Note that it might take a couple seconds for the update to show up after the preview_build workflow has completed.

View snapshot changes at kitdiff

Copy link
Owner

@emilk emilk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks very promising! Thanks for working on this.

There is some related code here:

let normal = (n0 + n1) / 2.0;
let length_sq = normal.length_sq();
let right_angle_length_sq = 0.5;
let sharper_than_a_right_angle = length_sq < right_angle_length_sq;
if sharper_than_a_right_angle {
// cut off the sharp corner
let center_normal = normal.normalized();
let n0c = (n0 + center_normal) / 2.0;
let n1c = (n1 + center_normal) / 2.0;
self.add_point(points[i], n0c / n0c.length_sq());
self.add_point(points[i], n1c / n1c.length_sq());
} else {
// miter join
self.add_point(points[i], normal / length_sq);
}

and here:

// We can't just cut off corners for filled shapes like this,
// because the feather will both expand and contract the corner along the provided normals
// to make sure it doesn't grow, and the shrinking will make the inner points cross each other.
//
// A better approach is to shrink the vertices in by half the feather-width here
// and then only expand during feathering.
//
// See https://github.com/emilk/egui/issues/1226
const CUT_OFF_SHARP_CORNERS: bool = false;
let right_angle_length_sq = 0.5;
let sharper_than_a_right_angle = length_sq < right_angle_length_sq;
if CUT_OFF_SHARP_CORNERS && sharper_than_a_right_angle {
// cut off the sharp corner
let center_normal = normal.normalized();
let n0c = (n0 + center_normal) / 2.0;
let n1c = (n1 + center_normal) / 2.0;
self.add_point(points[i], n0c / n0c.length_sq());
self.add_point(points[i], n1c / n1c.length_sq());
} else {
// miter join
self.add_point(points[i], normal / length_sq);
}

I wonder if that is still needed 🤔

Also worth checking that we don't regress on #1226

thanks for adding a test btw 🙏

@podusowski podusowski marked this pull request as draft September 25, 2025 17:03
@podusowski
Copy link
Contributor Author

I did couple of changes to the test, namely to be more like #1226. CUT_OFF_SHARP_CORNERS is not affected by this change, I mean, it still needs to be turned off, otherwise jagged edges come back.

I've put the limit into TessellationOptions, so now it's easier to play with it.

Unfortunately, although IMO it's still better to have this, but it seems that there are no golden value here.

Baseline:
image

image image image

@podusowski podusowski marked this pull request as ready for review September 30, 2025 17:51
@podusowski podusowski requested a review from emilk September 30, 2025 17:52
Comment on lines 824 to 825
if normal.length_sq() > max_feathering_normal_length_sq {
normal * (max_feathering_normal_length_sq / normal.length_sq())
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is incorrect.

if normal = [10, 0] for instance, and max_feathering_normal_length = 2, so max_feathering_normal_length_sq = 4, then we would divide by 100/4 instead of 10/2.

Suggested change
if normal.length_sq() > max_feathering_normal_length_sq {
normal * (max_feathering_normal_length_sq / normal.length_sq())
if normal.length_sq() > sqr(max_feathering_normal_length) {
max_feathering_normal_length * normal.normalize()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had some issues when I did literal clamping, not sure why because now it looks fine (more or less).

Baseline:
image

Default (40.0):
image

@podusowski podusowski force-pushed the feathering-fix branch 2 times, most recently from e8350d6 to be732d9 Compare October 10, 2025 17:16
@podusowski podusowski requested a review from emilk October 13, 2025 18:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

epaint visuals Renderings / graphics releated

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants