Skip to content
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

SVGLoader: Shape generation breaks with large stroke width. #25326

Open
Mugen87 opened this issue Jan 24, 2023 · 9 comments
Open

SVGLoader: Shape generation breaks with large stroke width. #25326

Mugen87 opened this issue Jan 24, 2023 · 9 comments
Assignees

Comments

@Mugen87
Copy link
Collaborator

Mugen87 commented Jan 24, 2023

Description

The original issue was reported at the forum: https://discourse.threejs.org/t/glitches-with-svg-loader/46803

SVGs that exceed a certain stroke-width are not rendered correctly. As visible in the below screenshot, the resulting shape is rendered with additional triangles on its back side. When decreasing the stroke width (in the below SVG try 200), the issue goes away.

I suspect there is a problem in SVGLoader.pointsToStrokeWithBuffers() which generates triangles based on the stroke definition from the SVG.

Reproduction steps

  1. Load the following SVG with the loader to reproduce the glitch.
<?xml version="1.0"?>
<svg viewBox="0 0 600 792" width="600" height="792">
  <path stroke="#64C36E" fill="none" id="link_2_21_ok" d="M30,463.61904761904765C150,463.61904761904765,150,571.0476190476189,270,571.0476190476189" stroke-width="290" stroke-opacity="0.6"/>
</svg>

Code

  • See fiddles.

Live example

Screenshots

image

Version

r148

Device

Desktop, Mobile, Headset

Browser

Chrome, Firefox, Safari, Edge

OS

Windows, MacOS, Linux, Android, iOS

@Mugen87 Mugen87 changed the title SVGLoader: Shapes break SVGLoader: Shape generation breaks with large stroke width. Jan 24, 2023
@yomboprime
Copy link
Collaborator

I had the hope that setting a high value for the number of interpolation points would correct the issue (note the extra 5000 parameter here):
const geometry = SVGLoader.pointsToStroke( subPath.getPoints( 5000 ), path.userData.style );

Unfortunately it does not solve it. I'm afraid it is not addressable in the algorithm as it is written right now. It would require self-intersection tests. On the other hand it is doable in a completely separated 2D algorithm, let me see if I can put up something in the next days.

@yomboprime
Copy link
Collaborator

I tried to make a triangularization algorithm which discards overlapping triangles. But it gives false positives and discards too many. It works in tests but not in the SVG from the example.

@Mugen87
Copy link
Collaborator Author

Mugen87 commented Jun 9, 2023

Something that came to my mind: Could you utilize the existing Earcut implementation for this use case?

https://threejs.org/docs/index.html#api/en/extras/Earcut

Meaning you first generate the contour (which is just a sequence of points) an then let Earcut do its job.

@yomboprime
Copy link
Collaborator

Thanks for noting it. I will take a look. I will also investigate if I can get the holes indices.

@yomboprime
Copy link
Collaborator

yomboprime commented Jun 11, 2023

@Mugen87 I've implemented your idea, by holding two contour points arrays (left and right ones) and combining them at the end, giving the full stroke contour.

It works mostly correctly in the use case. I hoped it worked in the general case but you can see that the Tiger.svg has some artifacts. It is caused by the contour I generate, which folds over itself on some corners. Earcut does not like that on complex paths. As before, solving this would require checking self-intersections, but now in the linear contour, not in a triangle soup. It is doable, but only would work if the original path doesn't self-intersect.

Detail of the contour folding over itself:

imagen

Tiger.svg:

imagen

Use case:

imagen

Use case detail:

imagen

The algorithm is smaller and cleaner (751 lines down to 495). Performance seems similar as before. The drawback is that the two contour arrays must be allocated. This can be optimized by providing reusable user arrays.

As the whole pointsToStrokeWithBuffers() is modified, I don't know very well how to integrate this change. For now I've just duplicated the function with the name pointsToStrokeContourWithBuffers().

The function pointsToStroke() accepts a boolean parameter useEarcut (default false) and selects which function to call.

It seems THREE doesn't export Earcut so I've copied Earcut.js in /jsm just for testing.

I've tried to make a live preview but failed. You can read the modified loader and example here: yomboprime@563aea9

Expecting your feedback to see if this can be useful.

@yomboprime
Copy link
Collaborator

Just an update: There was some bugs in that branch commit. I'm trying to debug some last issues.

@yomboprime
Copy link
Collaborator

yomboprime commented Jun 18, 2023

Unfortunately I've not been able to solve it for the general case. I've spent many hours on this.

The problem is when the path points do auto-intersect like this (the contour gets flipped over itself):
contour_autointersection

I think this problem needs more thought than an amateur mathematician (me) can give.

@Mugen87
Copy link
Collaborator Author

Mugen87 commented Jun 19, 2023

I appreciate your effort on this! Even without a concrete solution the problem is now better understood than before 👍 .

@yomboprime
Copy link
Collaborator

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants