Skip to content

Tree based acceleration for polygon clipping / boolean ops #274

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

Open
wants to merge 18 commits into
base: main
Choose a base branch
from

Conversation

asinghvi17
Copy link
Member

@asinghvi17 asinghvi17 commented Mar 4, 2025

Can use STRtrees (expensive to construct, efficient to query) or Natural trees (from tg, cheap to construct, ~same query speed for our usecases)

There are three methods here:

  • naive, n*m nested loop
  • loop over all edges of a, tree on b
  • dual tree query on trees from a and b

This algorithm should be easily portable to s2 when that becomes available - and the structure allows e.g. manifold passthrough as well.


This PR also adds a LoopStateMachine module that basically just defines an Action wrapper struct, and a macro that can take care of that action wrapper struct.

With this macro, a function running within a loop can return Break() or Continue() which indicates to the caller that it should break or continue.

E.g.:

f(i) = i == 3 ? Break() : i
count = 1
for i in 1:5
    count = @processloopaction f(i)
end
count # 2

TODOs:

  • add rejection tests for duplicate points
  • add lopsided single tree query
  • add dual tree query
  • figure out good heuristics for when to choose which
  • establish tree construction and query benchmarks on various cases
  • port the tg benchmarks here
  • add more tests and benchmarks with complex polygons

Copy link
Member Author

asinghvi17 commented Mar 4, 2025

@asinghvi17 asinghvi17 mentioned this pull request Mar 9, 2025
@asinghvi17 asinghvi17 force-pushed the as/trees branch 2 times, most recently from 476cf2f to be0dd9c Compare March 16, 2025 07:11
asinghvi17 added a commit that referenced this pull request Apr 3, 2025
This was factored out of the "dev branch" #259 and contains the subset of changes that apply to GeometryOpsCore, for easier review.

Child PRs: #271 (TGGeometry) -> #275 (AdaptivePredicates) -> #273 (clipping algorithm type) -> #274 (trees)

- Use [StableTasks.jl](https://github.com/JuliaFolds2/StableTasks.jl) in apply and applyreduce - its type-stable tasks save us some allocations!
- Remove `Base.@assume_effects` on the low level functions, which caused issues on Julia v1.11 and was probably incorrect anyway
- Add an algorithm interface with an abstract supertype `Algorithm{M <: Manifold}`, as discussed in #247.  Also adds an abstract Operator supertype and some discussion in code comments, but no implementation or interface surface there yet.
- Split out `types.jl` into a directory `types` with a bunch of files in it, for ease of readability / docs / use.
- (out of context change): refactor CI a bit for cleanliness.


TODOs for later (not this PR):
- [ ] Add a `format` method that takes in an incompletely specified algorithm and some geometry as input, and returns a completely specified algorithm.  What does this mean?  Imagine I call `GO.intersection(FosterHormannClipping(), geom1, geom2)`.  That `FosterHormannClipping()` should get expanded to `FosterHormannClipping(AutoAlgorithm(), AutoAccelerator())`.  Then, `format` will take `format(alg, args...)` and:
  - get the `crstrait` of the two geometries, scan for incompatibilities, assign the correct manifold to the algorithm (maybe warn or emit debug info)
  - if no geometries available, get the manifold via `best_manifold(::Algorithm)`.
  - maybe inflate the accelerator by checking `npoint` and later preparations to see what's most efficient, maybe not - depends on what we want!
@asinghvi17 asinghvi17 force-pushed the as/trees branch 2 times, most recently from 71b1953 to a801c2c Compare April 16, 2025 21:25
@asinghvi17 asinghvi17 changed the base branch from as/clipping to graphite-base/274 May 1, 2025 02:23
@asinghvi17 asinghvi17 force-pushed the graphite-base/274 branch from eef41bd to f6d526f Compare May 1, 2025 02:23
@asinghvi17 asinghvi17 changed the base branch from graphite-base/274 to main May 1, 2025 02:23
@asinghvi17
Copy link
Member Author

Looks like the SingleSTRtree implementation is ignoring some edges somehow...will have to give the a_list and b_list a look later on, maybe during the hackathon.

@asinghvi17
Copy link
Member Author

asinghvi17 commented May 9, 2025

Looks like the one largeish error here is in difference(p25, p26; target = GI.PolygonTrait()) specifically with SingleNaturalTree:

download

the colorful ones are the polygons generated from difference individually - the base poly is blue.

here's what it looks like with the base nested loop:
download-1

and here's what it looks like with geos:
download-2

This is also somehow spitting out invalid polygons according to libgeos, it doesn't tell me why though.

@asinghvi17
Copy link
Member Author

asinghvi17 commented May 9, 2025

It may be, that this is because the algorithm is returning degenerate (self intersecting) polygons.

LG.isValidReason on the first result polygon gives me a self intersection, you can see it here:

download-3

the reason is specifically "Ring Self-intersection[4.5 1]"

@asinghvi17
Copy link
Member Author

ah this is pretty bad actually, polys 1 and 4 both have self intersections...and the hole detection is not working. This is all the outputs, colored by index in the output array:

download-4

and this is what it should look like:

download-5

@asinghvi17
Copy link
Member Author

somehow single natural tree seems to be swapping the order of intersects....

@asinghvi17
Copy link
Member Author

asinghvi17 commented May 9, 2025

Argh, I forgot to add a hook for f_after_each_a in the loop.....need to switch this to SpatialTreeInterface proper asap!

Here's what I am thinking - intersection accelerators can get simpler.

struct NestedLoop end
struct TreeAccelerated{Tree1, Tree2}
    treetype1::Tree1
    treetype2::Tree2
end

the trees in there can be tree specs that then get instantiated, I'm thinking something like your ArgsAndKwargs @rafaqz

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

Successfully merging this pull request may close these issues.

2 participants