Skip to content

Algorithm + manifold and accelerator passthrough in clipping functions #273

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

Merged
merged 33 commits into from
Apr 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
926b915
Move utils to `utils/utils.jl` and add edge list functionality
asinghvi17 Apr 16, 2025
838932a
Add LoopStateMachine and tests
asinghvi17 Apr 17, 2025
3419121
test all actions
asinghvi17 Apr 17, 2025
d6c1ffc
add SpatialTreeInterface implementation
asinghvi17 Apr 17, 2025
9210033
Define `isspatialtree` for FlatNoTree
asinghvi17 Apr 17, 2025
7b1b99b
Add basic tests for FlatNoTree
asinghvi17 Apr 17, 2025
7dd4014
ignore call notes since we don't want to commit them
asinghvi17 Apr 17, 2025
aa8ea43
test all of LoopStateMachine
asinghvi17 Apr 17, 2025
e9fb515
generic tests for STI with FlatNoTree and STR
asinghvi17 Apr 17, 2025
9efde48
actually include tests
asinghvi17 Apr 17, 2025
e5ad556
implement `isspatialtree` for STR types
asinghvi17 Apr 17, 2025
4ccf67b
fix tests
asinghvi17 Apr 17, 2025
0900db2
Apply suggestions from code review
asinghvi17 Apr 17, 2025
06c76a9
Implement and test a custom exception type for unknown actions in LSM
asinghvi17 Apr 17, 2025
728befc
Factor out SpatialTreeInterface into separate files
asinghvi17 Apr 17, 2025
bb76207
Fix tests
asinghvi17 Apr 17, 2025
8f4de31
no really
asinghvi17 Apr 17, 2025
d5a49de
add a test for multilevel dual tree query
asinghvi17 Apr 20, 2025
06727f1
Fix up the tests
asinghvi17 Apr 20, 2025
91ff5c2
Describe a FosterHormannClipping algorithm
asinghvi17 Mar 4, 2025
c81aef2
Create a TracingError type that stores all context
asinghvi17 Mar 4, 2025
7bcceb2
Pass algorithm all the way through `cut`
asinghvi17 Mar 4, 2025
ab52b17
Pass algorithm through all functions in clipping_processor
asinghvi17 Mar 4, 2025
dd514b1
Pass manifold through point_filled_curve_orientation in planar
asinghvi17 Mar 4, 2025
caf8800
Pass algorithm through difference + union
asinghvi17 Mar 4, 2025
7591f0b
use TracingError instead of printing out errors
asinghvi17 Mar 4, 2025
ddee859
Implement algorithm in intersection
asinghvi17 Mar 4, 2025
89ec64c
Fix typo that prevented multipolygon intersection from taking place
asinghvi17 Mar 14, 2025
fdb554c
Apply suggestions from code review
asinghvi17 Apr 20, 2025
e29407b
add a test for TracingError shows
asinghvi17 Apr 20, 2025
c3f0881
Merge branch 'main' into as/clipping
asinghvi17 Apr 20, 2025
353b775
add explanatory comments
asinghvi17 Apr 20, 2025
eef41bd
Update clipping_processor.jl
asinghvi17 Apr 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 128 additions & 31 deletions src/methods/clipping/clipping_processor.jl

Large diffs are not rendered by default.

18 changes: 10 additions & 8 deletions src/methods/clipping/cut.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,29 +58,31 @@
[[[5.0, 0.0], [10.0, 0.0], [10.0, 10.0], [5.0, 10.0], [5.0, 0.0]]]
```
"""
cut(geom, line, ::Type{T} = Float64) where {T <: AbstractFloat} =
_cut(T, GI.trait(geom), geom, GI.trait(line), line; exact = True())
cut(geom, line, ::Type{T} = Float64) where {T <: AbstractFloat} = cut(FosterHormannClipping(), geom, line, T)
cut(m::Manifold, geom, line, ::Type{T} = Float64) where {T <: AbstractFloat} = cut(FosterHormannClipping(m), geom, line, T)

Check warning on line 62 in src/methods/clipping/cut.jl

View check run for this annotation

Codecov / codecov/patch

src/methods/clipping/cut.jl#L62

Added line #L62 was not covered by tests
cut(alg::FosterHormannClipping{M, A}, geom, line, ::Type{T} = Float64) where {T <: AbstractFloat, M, A} =
_cut(alg, T, GI.trait(geom), geom, GI.trait(line), line; exact = True())

#= Cut a given polygon by given line. Add polygon holes back into resulting pieces if there
are any holes. =#
function _cut(::Type{T}, ::GI.PolygonTrait, poly, ::GI.LineTrait, line; exact) where T
function _cut(alg::FosterHormannClipping{M, A}, ::Type{T}, ::GI.PolygonTrait, poly, ::GI.LineTrait, line; exact) where {T, M, A}
ext_poly = GI.getexterior(poly)
poly_list, intr_list = _build_a_list(T, ext_poly, line; exact)
poly_list, intr_list = _build_a_list(alg, T, ext_poly, line; exact)
n_intr_pts = length(intr_list)
# If an impossible number of intersection points, return original polygon
if n_intr_pts < 2 || isodd(n_intr_pts)
return [tuples(poly)]
end
# Cut polygon by line
cut_coords = _cut(T, ext_poly, line, poly_list, intr_list, n_intr_pts; exact)
cut_coords = _cut(alg, T, ext_poly, line, poly_list, intr_list, n_intr_pts; exact)
# Close coords and create polygons
for c in cut_coords
push!(c, c[1])
end
cut_polys = [GI.Polygon([c]) for c in cut_coords]
# Add original polygon holes back in
remove_idx = falses(length(cut_polys))
_add_holes_to_polys!(T, cut_polys, GI.gethole(poly), remove_idx; exact)
_add_holes_to_polys!(alg, T, cut_polys, GI.gethole(poly), remove_idx; exact)
return cut_polys
end

Expand All @@ -97,10 +99,10 @@
of cut geometry in Vector{Vector{Tuple}} format.

Note: degenerate cases where intersection points are vertices do not work right now. =#
function _cut(::Type{T}, geom, line, geom_list, intr_list, n_intr_pts; exact) where T
function _cut(alg::FosterHormannClipping{M, A}, ::Type{T}, geom, line, geom_list, intr_list, n_intr_pts; exact) where {T, M, A}
# Sort and categorize the intersection points
sort!(intr_list, by = x -> geom_list[x].fracs[2])
_flag_ent_exit!(GI.LineTrait(), line, geom_list; exact)
_flag_ent_exit!(alg, GI.LineTrait(), line, geom_list; exact)
# Add first point to output list
return_coords = [[geom_list[1].point]]
cross_backs = [(T(Inf),T(Inf))]
Expand Down
55 changes: 36 additions & 19 deletions src/methods/clipping/difference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,23 @@
```
"""
function difference(
geom_a, geom_b, ::Type{T} = Float64; target=nothing, kwargs...,
alg::FosterHormannClipping, geom_a, geom_b, ::Type{T} = Float64; target=nothing, kwargs...,
) where {T<:AbstractFloat}
return _difference(
TraitTarget(target), T, GI.trait(geom_a), geom_a, GI.trait(geom_b), geom_b;
alg, TraitTarget(target), T, GI.trait(geom_a), geom_a, GI.trait(geom_b), geom_b;
exact = True(), kwargs...,
)
end
# fallback definitions
difference(geom_a, geom_b, ::Type{T} = Float64; target=nothing, kwargs...) where T = difference(FosterHormannClipping(Planar()), geom_a, geom_b, T; target, kwargs...)
# if manifold but no algorithm - assume FosterHormannClipping with provided manifold.
difference(m::Manifold, geom_a, geom_b, ::Type{T} = Float64; target=nothing, kwargs...) where T = difference(FosterHormannClipping(m), geom_a, geom_b, T; target, kwargs...)

Check warning on line 46 in src/methods/clipping/difference.jl

View check run for this annotation

Codecov / codecov/patch

src/methods/clipping/difference.jl#L46

Added line #L46 was not covered by tests

#= The 'difference' function returns the difference of two polygons as a list of polygons.
The algorithm to determine the difference was adapted from "Efficient clipping of efficient
polygons," by Greiner and Hormann (1998). DOI: https://doi.org/10.1145/274363.274364 =#
function _difference(
::TraitTarget{GI.PolygonTrait}, ::Type{T},
alg::FosterHormannClipping, target::TraitTarget{GI.PolygonTrait}, ::Type{T},
::GI.PolygonTrait, poly_a,
::GI.PolygonTrait, poly_b;
exact, kwargs...
Expand All @@ -54,11 +58,11 @@
ext_a = GI.getexterior(poly_a)
ext_b = GI.getexterior(poly_b)
# Find the difference of the exterior of the polygons
a_list, b_list, a_idx_list = _build_ab_list(T, ext_a, ext_b, _diff_delay_cross_f, _diff_delay_bounce_f; exact)
polys = _trace_polynodes(T, a_list, b_list, a_idx_list, _diff_step, poly_a, poly_b)
a_list, b_list, a_idx_list = _build_ab_list(alg, T, ext_a, ext_b, _diff_delay_cross_f, _diff_delay_bounce_f; exact)
polys = _trace_polynodes(alg, T, a_list, b_list, a_idx_list, _diff_step, poly_a, poly_b)
# if no crossing points, determine if either poly is inside of the other
if isempty(polys)
a_in_b, b_in_a = _find_non_cross_orientation(a_list, b_list, ext_a, ext_b; exact)
a_in_b, b_in_a = _find_non_cross_orientation(alg.manifold, a_list, b_list, ext_a, ext_b; exact)
# add case for if they polygons are the same (all intersection points!)
# add a find_first check to find first non-inter poly!
if b_in_a && !a_in_b # b in a and can't be the same polygon
Expand All @@ -72,19 +76,19 @@
remove_idx = falses(length(polys))
# If the original polygons had holes, take that into account.
if GI.nhole(poly_a) != 0
_add_holes_to_polys!(T, polys, GI.gethole(poly_a), remove_idx; exact)
_add_holes_to_polys!(alg, T, polys, GI.gethole(poly_a), remove_idx; exact)
end
if GI.nhole(poly_b) != 0
for hole in GI.gethole(poly_b)
hole_poly = GI.Polygon(StaticArrays.SVector(hole))
new_polys = intersection(hole_poly, poly_a, T; target = GI.PolygonTrait)
new_polys = intersection(alg, hole_poly, poly_a, T; target = GI.PolygonTrait)
if length(new_polys) > 0
append!(polys, new_polys)
end
end
end
# Remove unneeded collinear points on same edge
_remove_collinear_points!(polys, remove_idx, poly_a, poly_b)
_remove_collinear_points!(alg, polys, remove_idx, poly_a, poly_b)
return polys
end

Expand All @@ -110,15 +114,15 @@
#= Polygon with multipolygon difference - note that all intersection regions between
`poly_a` and any of the sub-polygons of `multipoly_b` are removed from `poly_a`. =#
function _difference(
target::TraitTarget{GI.PolygonTrait}, ::Type{T},
alg::FosterHormannClipping, target::TraitTarget{GI.PolygonTrait}, ::Type{T},
::GI.PolygonTrait, poly_a,
::GI.MultiPolygonTrait, multipoly_b;
kwargs...,
) where T
polys = [tuples(poly_a, T)]
for poly_b in GI.getpolygon(multipoly_b)
isempty(polys) && break
polys = mapreduce(p -> difference(p, poly_b; target), append!, polys)
polys = mapreduce(p -> difference(alg, p, poly_b; target), append!, polys)
end
return polys
end
Expand All @@ -128,7 +132,7 @@
sub-polygon. Unless specified with `fix_multipoly = nothing`, `multipolygon_a` will be
validated using the given (default is `UnionIntersectingPolygons()`) correction. =#
function _difference(
target::TraitTarget{GI.PolygonTrait}, ::Type{T},
alg::FosterHormannClipping, target::TraitTarget{GI.PolygonTrait}, ::Type{T},
::GI.MultiPolygonTrait, multipoly_a,
::GI.PolygonTrait, poly_b;
fix_multipoly = UnionIntersectingPolygons(), kwargs...,
Expand All @@ -139,7 +143,7 @@
polys = Vector{_get_poly_type(T)}()
sizehint!(polys, GI.npolygon(multipoly_a))
for poly_a in GI.getpolygon(multipoly_a)
append!(polys, difference(poly_a, poly_b; target))
append!(polys, difference(alg, poly_a, poly_b; target))
end
return polys
end
Expand All @@ -150,7 +154,7 @@
`multipolygon_a` will be validated using the given (default is `UnionIntersectingPolygons()`)
correction. =#
function _difference(
target::TraitTarget{GI.PolygonTrait}, ::Type{T},
alg::FosterHormannClipping, target::TraitTarget{GI.PolygonTrait}, ::Type{T},
::GI.MultiPolygonTrait, multipoly_a,
::GI.MultiPolygonTrait, multipoly_b;
fix_multipoly = UnionIntersectingPolygons(), kwargs...,
Expand All @@ -165,26 +169,39 @@
pieces of `multipolygon_a`` are removed, continue to take difference with new shape
`polys` =#
polys = if i == 1
difference(multipoly_a, poly_b; target, fix_multipoly)
difference(alg, multipoly_a, poly_b; target, fix_multipoly)
else
difference(GI.MultiPolygon(polys), poly_b; target, fix_multipoly)
difference(alg, GI.MultiPolygon(polys), poly_b; target, fix_multipoly)
end
#= One multipoly_a has been completely covered (and thus removed) there is no need to
continue taking the difference =#
isempty(polys) && break
end
return polys
end

function _difference(

Check warning on line 182 in src/methods/clipping/difference.jl

View check run for this annotation

Codecov / codecov/patch

src/methods/clipping/difference.jl#L182

Added line #L182 was not covered by tests
alg::FosterHormannClipping, ::TraitTarget{GI.MultiPolygonTrait}, ::Type{T},
trait_a::Union{GI.PolygonTrait, GI.MultiPolygonTrait}, polylike_a,
trait_b::Union{GI.PolygonTrait, GI.MultiPolygonTrait}, polylike_b;
fix_multipoly = UnionIntersectingPolygons(), kwargs...
) where T
polys = _difference(alg, TraitTarget(GI.PolygonTrait()), T, trait_a, polylike_a, trait_b, polylike_b; kwargs...)
if isnothing(fix_multipoly)
return GI.MultiPolygon(polys)

Check warning on line 190 in src/methods/clipping/difference.jl

View check run for this annotation

Codecov / codecov/patch

src/methods/clipping/difference.jl#L188-L190

Added lines #L188 - L190 were not covered by tests
else
return fix_multipoly(GI.MultiPolygon(polys))

Check warning on line 192 in src/methods/clipping/difference.jl

View check run for this annotation

Codecov / codecov/patch

src/methods/clipping/difference.jl#L192

Added line #L192 was not covered by tests
end
end
# Many type and target combos aren't implemented
function _difference(
::TraitTarget{Target}, ::Type{T},
alg::GeometryOpsCore.Algorithm, target::TraitTarget{Target}, ::Type{T},
trait_a::GI.AbstractTrait, geom_a,
trait_b::GI.AbstractTrait, geom_b,
kw...
) where {Target, T}
@assert(
false,
"Difference between $trait_a and $trait_b with target $Target isn't implemented yet.",
"Difference between $trait_a and $trait_b with target $Target and algorithm $alg isn't implemented yet.",
)
return nothing
end
Expand Down
Loading
Loading