Skip to content

Commit

Permalink
added circle/tangent functions
Browse files Browse the repository at this point in the history
  • Loading branch information
cormullion committed Sep 16, 2018
1 parent 35784f6 commit ddbb106
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 1 deletion.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
# Changelog


## upcoming release

### Added

- circle/tangent functions

### Changed

- Travis documentation stage

### Removed

### Deprecated

## [v0.12.0] - 2018-08-17

- new version number for better compatibility with v1.0
Expand Down
95 changes: 95 additions & 0 deletions docs/src/simplegraphics.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,101 @@ nothing # hide
circlepath
```

### Circles and tangents

Functions to find circles that are tangential to other circles include:

- `circletangent2circles()` finds circles of a particular radius tangential to two circles
- `circlepointtangent()` finds circles of a particular radius passing through a point and tangential to another circle

These functions can return 0, 1, or 2 points (since there are often two solutions to a specific geometric layout).

`circletangent2circles()` takes the required radius and two existing circles:

```@example
using Luxor # hide
Drawing(600, 250, "assets/figures/circle-tangents.png") # hide
origin() # hide
background("white") # hide
sethue("black") # hide
setline(1) # hide
circle1 = (Point(-100, 0), 90)
circle(circle1..., :stroke)
circle2 = (Point(100, 0), 90)
circle(circle2..., :stroke)
requiredradius = 25
ncandidates, p1, p2 = circletangent2circles(requiredradius, circle1..., circle2...)
if ncandidates==2
sethue("orange")
circle(p1, requiredradius, :fill)
sethue("green")
circle(p2, requiredradius, :fill)
sethue("purple")
circle(p1, requiredradius, :stroke)
circle(p2, requiredradius, :stroke)
end
# the circles are 10 apart, so there should be just one circle
# that fits there
requiredradius = 10
ncandidates, p1, p2 = circletangent2circles(requiredradius, circle1..., circle2...)
if ncandidates==1
sethue("blue")
circle(p1, requiredradius, :fill)
sethue("cyan")
circle(p1, requiredradius, :stroke)
end
finish() # hide
nothing # hide
```

![circle tangents](assets/figures/circle-tangents.png)

`circlepointtangent()` looks for circles of a specified radius that pass through a point and are tangential to a circle. There are usually two candidates.

```@example
using Luxor # hide
Drawing(600, 250, "assets/figures/circle-point-tangent.png") # hide
origin() # hide
background("white") # hide
sethue("black") # hide
setline(1) # hide
circle1 = (Point(-100, 0), 90)
circle(circle1..., :stroke)
requiredradius = 50
requiredpassthrough = O + (80, 0)
ncandidates, p1, p2 = circlepointtangent(requiredpassthrough, requiredradius, circle1...)
if ncandidates==2
sethue("orange")
circle(p1, requiredradius, :stroke)
sethue("green")
circle(p2, requiredradius, :stroke)
end
sethue("black")
circle(requiredpassthrough, 4, :fill)
finish() # hide
nothing # hide
```

![circle tangents 2](assets/figures/circle-point-tangent.png)


```@docs
circletangent2circles
circlepointtangent
```

## More curved shapes: sectors, spirals, and squircles

A sector (technically an "annular sector") has an inner and outer radius, as well as start and end angles.
Expand Down
2 changes: 1 addition & 1 deletion src/Luxor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export Drawing, currentdrawing,
circle, circlepath, ellipse, hypotrochoid, epitrochoid, squircle, center3pts, curve,
arc, carc, arc2r, carc2r, spiral, sector, intersection2circles,
intersection_line_circle, intersectionlinecircle, intersectioncirclecircle, ispointonline,
intersectlinepoly, polyintersections,
intersectlinepoly, polyintersections, circlepointtangent, circletangent2circles,

intersectboundingboxes, boundingboxesintersect,

Expand Down
76 changes: 76 additions & 0 deletions src/curves.jl
Original file line number Diff line number Diff line change
Expand Up @@ -701,4 +701,80 @@ function intersectioncirclecircle(cp1, r1, cp2, r2)
return (true, p3, p4)
end

"""
circlepointtangent(through::Point, radius, targetcenter::Point, targetradius)
Find the centers of up to two circles of radius `radius` that pass through point
`through` and are tangential to a circle that has radius `targetradius` and
center `targetcenter`.
This function returns a tuple:
* (0, O, O) - no circles exist
* (1, pt1, O) - 1 circle exists, centered at pt1
* (2, pt1, pt2) - 2 circles exist, with centers at pt1 and pt2
(The O are just dummy points so that three values are always returned.)
"""
function circlepointtangent(through::Point, radius, targetcenter::Point, targetradius)
distx = targetcenter.x - through.x
disty = targetcenter.y - through.y
dsq = distance(through, targetcenter)^2
if isless(dsq, 10e-6) # coincident
return (0, O, O)
else
sqinv=0.5/dsq
s = dsq - ((2radius + targetradius) * targetradius)
root = 4(radius^2) * dsq - s^2
s *= sqinv
if isless(dsq, 0.0) # no center possible
return (0, O, O)
else
if isless(root, 10e-6) # only one circle possible
x = through.x + distx * s
y = through.y + disty * s
if isless(abs(distance(through, Point(x, y)) - radius), 10e-6)
return (1, Point(x, y), O)
else
return (0, O, O)
end
else # two circles are possible
root = sqrt(root) * sqinv
xconst = through.x + distx * s
yconst = through.y + disty * s
xvar = disty * root
yvar = distx * root
return (2, Point(xconst - xvar, yconst + yvar), Point(xconst + xvar, yconst - yvar))
end
end
end
end

"""
circletangent2circles(radius, circle1center::Point, circle1radius, circle2center::Point, circle2radius)
Find the centers of up to two circles of radius `radius` that are tangent to the
two circles defined by `circle1...` and `circle2...`. These two circles can
overlap, but one can't be inside the other.
* (0, O, O) - no such circles exist
* (1, pt1, O) - 1 circle exists, centered at pt1
* (2, pt1, pt2) - 2 circles exist, with centers at pt1 and pt2
(The O are just dummy points so that three values are always returned.)
"""
function circletangent2circles(radius, circle1center::Point, circle1radius, circle2center::Point, circle2radius)
modradius1 = radius + circle1radius
modradius2 = circle2radius - circle1radius
return circlepointtangent(circle1center, modradius1, circle2center, modradius2)
end

function randpoint()
return Point(rand(-200:200), rand(-200:200))
end

# eof
54 changes: 54 additions & 0 deletions test/circletangent-test.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env julia

using Luxor, Test, Random

Random.seed!(7)

function findanddrawsometangentialcircles(c1center, c1radius, c2center, c2radius)
for requiredradius in 1:150
res = circletangent2circles(requiredradius, c1center, c1radius, c2center, c2radius)
setopacity(0.5)
if first(res) == 1
circle(res[2], requiredradius, :stroke)
elseif first(res) == 2
circle(res[2], requiredradius, :stroke)
circle(res[3], requiredradius, :stroke)
elseif first(res) == 0
sethue("red")
circle(c1center, 5, :fill)
circle(c2center, 5, :fill)
end
end
end

function test_circletangents(fname)
pagewidth, pageheight = 1200, 1400
Drawing(pagewidth, pageheight, fname)
origin() # move 0/0 to center
background("ivory")
setline(0.5)
tiles = Tiler(1200, 1400, 5, 5)
for (pos, n) in tiles
@layer begin
translate(pos)
circle1 = (O + Point(rand(-20:20), rand(-20:20)), rand(20:40))
circle2 = (O + Point(rand(-20:20), rand(-20:20)), rand(20:40))
sethue("black")
setopacity(0.5)
circle(circle1..., :fill)
circle(circle2..., :fill)
setopacity(1.0)
circle(circle1..., :stroke)
circle(circle2..., :stroke)
sethue("purple")
findanddrawsometangentialcircles(circle1[1], circle1[2], circle2[1], circle2[2])
end
end

@test finish() == true
println("...finished circle tangent test, saved in $(fname)")
end

fname = "circletangent-test.pdf"

test_circletangents(fname)
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ function run_all_tests()
include("bezierpath.jl")
include("bezierpathtopoly.jl")
include("bezierstroke-test.jl")
include("circletangent-test.jl")
end

@testset "color" begin
Expand Down

0 comments on commit ddbb106

Please sign in to comment.