diff --git a/CHANGELOG.md b/CHANGELOG.md index c0a2f15..7fb4a1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changes +## Unreleased + +### Added +- `arc!` primitive to construct circular boundary geometries +- Example showing how to use `arc!` + +### Fixed +- `circle!` primitive now constructs the circle based on polar coordinates, i.e. starting at `[r,0]`. + ## v2.7.0 October 10, 2026 - Allow for TetGen.jl version 3. Consult the [TetGen.jl README](https://github.com/JuliaGeometry/TetGen.jl/blob/master/README.md) for information on the different versions the upstream C++ TetGen code and its Julia binary package TetGen_jll.jl. diff --git a/examples/examples2d.jl b/examples/examples2d.jl index 6e6f39e..90e2c7f 100644 --- a/examples/examples2d.jl +++ b/examples/examples2d.jl @@ -252,6 +252,34 @@ end # ![](swiss_cheese_2d.png) # +# ## Domain with an arc boundary +# We can generate domains where a part of the boundary is described through an arc. +# This function constructs a quarter the well know "plate with a hole" geometry. +# +function holeplate_2d(H = 1.0, r = 0.33) + builder = SimplexGridBuilder(; Generator = Triangulate) + p1 = point!(builder, 0, r) + p2 = point!(builder, 0, H) + p3 = point!(builder, H, H) + p4 = point!(builder, H, 0) + p5 = point!(builder, r, 0) + + facetregion!(builder, 1) + facet!(builder, p1, p2) + facetregion!(builder, 2) + facet!(builder, p2, p3) + facetregion!(builder, 3) + facet!(builder, p3, p4) + facetregion!(builder, 4) + facet!(builder, p4, p5) + facetregion!(builder, 5) + SimplexGridFactory.arc!(builder, p5, [0.0, 0.0], p1; n = 5) + + return simplexgrid(builder, maxvolume = 0.0125) +end +# +# ![](holeplate_2d.png) +# # ## Remeshing another grid # @@ -351,5 +379,8 @@ function generateplots(picdir; Plotter = nothing) p = gridplot(glue_2d(); Plotter, size) Plotter.save(joinpath(picdir, "glue_2d.png"), p) + + p = gridplot(holeplate_2d(); Plotter, size) + Plotter.save(joinpath(picdir, "holeplate_2d.png"), p) end end diff --git a/src/SimplexGridFactory.jl b/src/SimplexGridFactory.jl index 2eaef75..a19ac87 100644 --- a/src/SimplexGridFactory.jl +++ b/src/SimplexGridFactory.jl @@ -34,7 +34,7 @@ include("triangle.jl") include("tetgen.jl") include("primitives.jl") -export circle!, rect2d!, rect3d!, sphere!, bregions!, moveto!, lineto! +export circle!, arc!, rect2d!, rect3d!, sphere!, bregions!, moveto!, lineto! export model3d!, mesh3d! include("plot.jl") diff --git a/src/primitives.jl b/src/primitives.jl index 76abd9a..4e28497 100644 --- a/src/primitives.jl +++ b/src/primitives.jl @@ -6,7 +6,7 @@ circle!(builder, center, radius; n=20) Add points and facets approximating a circle. """ function circle!(builder::SimplexGridBuilder, center, radius; n = 20) - points = [point!(builder, center[1] + radius * sin(t), center[2] + radius * cos(t)) for t in range(0, 2π; length = n)] + points = [point!(builder, center[1] + radius * cos(t), center[2] + radius * sin(t)) for t in range(0, 2π; length = n)] for i in 1:(n - 1) facet!(builder, points[i], points[i + 1]) end @@ -14,6 +14,65 @@ function circle!(builder::SimplexGridBuilder, center, radius; n = 20) return builder end +""" + arc!(builder::SimplexGridBuilder, startpoint, center, endpoint; n = 5) + +Add points and facets approximating a counterclockwise arc on a circle. + +Note that you probably still need to connect the start- and endpoint to other points with facets. +""" +function arc!(builder, startpoint, center, endpoint; n = 5) + radius, θ = _arcdata(startpoint, center, endpoint, n) + points = [point!(builder, center[1] + radius * cos(t), center[2] + radius * sin(t)) for t in θ] + for i in 1:(length(points) - 1) + facet!(builder, points[i], points[i + 1]) + end + return builder +end + +""" + arc!(builder, startpoint::Integer, center, endpoint::Integer; n = 5) + +Add points and facets approximating a counterclockwise arc on a circle. +Here, start- and endpoint are specified as point ids of points already known to the `builder`. +""" +function arc!(builder, startpoint::Integer, center, endpoint::Integer; n = 5) + radius, θ = _arcdata(builder.pointlist.points[:, startpoint], center, builder.pointlist.points[:, endpoint], n) + points = [point!(builder, center[1] + radius * cos(t), center[2] + radius * sin(t)) for t in θ[2:end]] + facet!(builder, startpoint, points[1]) + for i in 1:(length(points) - 1) + facet!(builder, points[i], points[i + 1]) + end + facet!(builder, points[end], endpoint) + return builder +end + +function _arcdata(startpoint, center, endpoint, n) + a = startpoint - center + b = endpoint - center + radius = sqrt(a[1]^2 + a[2]^2) + if !isapprox(radius, sqrt(b[1]^2 + b[2]^2)) + error("Can't construct arc: start and endpoint are not on the same circle.") + end + a /= radius + b /= radius + α = atan(a[2], a[1]) + β = atan(b[2], b[1]) + # If a point lies in the lower half of the circle, adjust angle + if a[2] < 0 + α += 2π + end + if b[2] < 0 + β += 2π + end + # Ensure the arc is clockwise + if α >= β + β += 2π + end + return radius, range(α, β; length = n) +end + + """ ``` bregions!(builder::SimplexGridBuilder,grid, pairs...) diff --git a/test/runtests.jl b/test/runtests.jl index 68fc8a6..40d0157 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -426,3 +426,41 @@ end @info builderplot(t2d(); Plotter = CairoMakie) |> typeof @test isa(builderplot(t2d(); Plotter = CairoMakie), CairoMakie.Scene) end + +@testset "2d arc primitive" begin + piquart = sqrt(2.0) / 2.0 #x,y value for π/4 + function arc_vs_circle2d() + circbuilder = SimplexGridBuilder(; Generator = Triangulate) + arcbuilder = SimplexGridBuilder(; Generator = Triangulate) + center = rand(2) + radius = rand() + a = [center[1] + radius, center[2]] + n = Int64(ceil(rand() * 20.0)) + 3 + circle!(circbuilder, center, radius; n = n) + SimplexGridFactory.arc!(arcbuilder, a, center, a; n = n) + for i in 1:(n - 1) + @test isapprox(circbuilder.pointlist.points[:, i], arcbuilder.pointlist.points[:, i]) + end + end + function arcs_quadrant1_quadrant3() + b = SimplexGridBuilder(; Generator = Triangulate) + quad1_center = point!(b, piquart, piquart) + quad3_center = point!(b, -piquart, -piquart) + SimplexGridFactory.arc!(b, quad1_center, [0.0, 0.0], quad3_center; n = 3) + SimplexGridFactory.arc!(b, quad3_center, [0.0, 0.0], quad1_center; n = 3) + @test isapprox(b.pointlist.points[:, 3][1], -piquart) + @test isapprox(b.pointlist.points[:, 3][2], piquart) + @test isapprox(b.pointlist.points[:, 4][1], piquart) + @test isapprox(b.pointlist.points[:, 4][2], -piquart) + end + function arc_radius_mismatch() + b = SimplexGridBuilder(; Generator = Triangulate) + p1 = point!(b, piquart, piquart) + p2 = point!(b, -2 * piquart, 2 * piquart) + @test_throws "Can't construct arc: start and endpoint are not on the same circle." SimplexGridFactory.arc!(b, p1, [0.0, 0.0], p2) + end + + arc_vs_circle2d() + arcs_quadrant1_quadrant3() + arc_radius_mismatch() +end