Skip to content

Commit

Permalink
rule two points
Browse files Browse the repository at this point in the history
  • Loading branch information
cormullion committed Jul 1, 2024
1 parent 4c0ee03 commit 37707e4
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 17 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
### Added

- triangular grids
- `squirclepath()`
- `rule(pt1, pt2)`

### Changed

Expand Down
4 changes: 3 additions & 1 deletion docs/src/howto/simplegraphics.md
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,8 @@ Use [`line`](@ref) and [`rline`](@ref) to draw straight lines. `line(pt1, pt2, a

You can use [`rule`](@ref) to draw a horizontal line through a point. Supply an angle for lines at an angle to the current x-axis.

Another method of `rule()` draws a line through two points.

```@example
using Luxor # hide
Drawing(800, 200, "../assets/figures/rule.png") # hide
Expand All @@ -625,7 +627,7 @@ nothing # hide

![arc](../assets/figures/rule.png)

Use the `boundingbox` keyword argument to crop the ruled lines with a BoundingBox.
Use the `boundingbox` keyword argument to crop the ruled lines where they cross the boundaries of a BoundingBox.

```@example
using Luxor # hide
Expand Down
2 changes: 1 addition & 1 deletion docs/src/howto/tables-grids.md
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ using Luxor, Colors
vertices, trianglenumber = e
sethue(HSB(rescale(trianglenumber, 0, nrows * ncols, 0, 360),
0.7, 0.8))
poly(vertices, :fillpreserve)
poly(vertices, :fillpreserve, close=true)
sethue("black")
strokepath()
text(string(trianglenumber), halign=:center, polycentroid(vertices))
Expand Down
2 changes: 1 addition & 1 deletion src/Luxor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export Drawing,
boxmiddlecenter, boxmiddleright, boxbottomleft,
boxbottomcenter, boxbottomright, intersectboundingboxes, boundingboxesintersect,
pointcrossesboundingbox, BoxmapTile, boxmap, circle, circlepath, circlering, ellipse, hypotrochoid, epitrochoid,
squircle, polysuper, center3pts, curve, arc, carc, arc2r, carc2r,
squircle, squirclepath, polysuper, center3pts, curve, arc, carc, arc2r, carc2r,
isarcclockwise, arc2sagitta, carc2sagitta, spiral,
sector, intersection2circles, intersection_line_circle,
intersectionlinecircle, intersectioncirclecircle,
Expand Down
42 changes: 37 additions & 5 deletions src/basics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -491,9 +491,11 @@ The function:
rule(O, pi/2, boundingbox=BoundingBox()/2)
```
draws a line that spans a bounding box half the width and height of the drawing,
and returns a Set of end points. If you just want the vertices and don't want to
draw anything, use `vertices=true`.
draws a line that spans a bounding box half the width and height of the drawing.
Returns the two end points in a Vector.
Use `vertices=true` to just return the end points, without drawing a line.
"""
function rule(pos, theta = 0.0;
boundingbox = BoundingBox(),
Expand All @@ -507,8 +509,8 @@ function rule(pos, theta = 0.0;
bottomside = vcat(bbox[4], bbox[1])
leftside = bbox[1:2]

# ruled line could be as long as the diagonal so add a bit extra
r = boxdiagonal(boundingbox) + 10
# ruled line could be as long as the diagonal so make it longer
r = boxdiagonal(boundingbox) * 2
rcosa = r * cos(theta)
rsina = r * sin(theta)
ruledline = (pos - (rcosa, rsina), pos + (rcosa, rsina))
Expand Down Expand Up @@ -550,13 +552,43 @@ function rule(pos, theta = 0.0;
# eliminate duplicates due to rounding errors
ruledline = unique(interpoints)

if length(ruledline) != 2
@debug "bounding box doesn't contain this line"
end
# finally draw the line if we have two points
if vertices == false && length(ruledline) == 2
line(ruledline..., :stroke)
end
return ruledline
end

"""
rule1(point1::Point, point2::Point;
boundingbox=BoundingBox(),
vertices=false)
Draw a straight line passing through points `point1` and `point2`.
```julia
rule(Point(10, 10), Point(30, -30))
```
Supply a BoundingBox to restrict the ruled lines to a rectangular area.
Returns the two end points in a Vector.
Use `vertices=true` to just return the end points, without drawing a line.
"""
function rule(point1::Point, point2::Point;
boundingbox=BoundingBox(),
vertices=false)
theta = slope(point1, point2)
rule(point1, theta;
boundingbox=boundingbox,
vertices=vertices)
end

# we need a thread safe way to store the color palette as a stack:
# access to the stack is only possible using the global function:
# - predefine all needed Dict entries in a thread safe way
Expand Down
54 changes: 51 additions & 3 deletions src/curves.jl
Original file line number Diff line number Diff line change
Expand Up @@ -212,16 +212,18 @@ ellipse(c::Point, w::Real, h::Real, action::Symbol) = ellipse(c, w, h, action =
squircle(center::Point, hradius, vradius, action;
rt = 0.5, stepby = pi/40, vertices=false)
Make a squircle or superellipse (basically a rectangle with rounded corners),
and add it to the current path. Specify the center position, horizontal radius
Make a squircle or superellipse (basically a rectangle with rounded corners).
Specify the center position, horizontal radius
(distance from center to a side), and vertical radius (distance from center to
top or bottom):
The root (`rt`) option defaults to 0.5, and gives an intermediate shape. Values
less than 0.5 make the shape more rectangular. Values above make the shape more
round. The horizontal and vertical radii can be different.
See also: `polysuper()`.
This function generates a series of points and makes a polygon or returns an array of Points.
See also: `polysuper()` and `squirclepath()`.
"""
function squircle(center::Point, hradius::Real, vradius::Real;
action = :none,
Expand Down Expand Up @@ -254,6 +256,52 @@ squircle(center::Point, hradius::Real, vradius::Real, action::Symbol;
stepby = pi / 40,
reversepath = false)

"""
squirclepath(cpos, w, h;
action = :none,
kappa = 0.75,
reversepath = false)
Make a squircle or superellipse (basically a rectangle with rounded corners).
Specify the center position, width, and height.
`kappa` is a value (usually between 0 and 1) that determines the circularity of
the shape. If `kappa` is `4.0 * (sqrt(2.0) - 1.0) / 3.0` (ie `0.5522847498307936`),
the shape is a resonable approximation to a circle.
Use the `reversepath` option to construct the path in the opposite direction.
The difference betweeen `squircle()` and `squirclepath()` is that the former
builds polygons from points, the latter uses Bezier curves to build paths.
See also `circlepath()`.
"""
function squirclepath(cpos, w, h;
action = :none,
kappa = 0.75,
reversepath = false)
@layer begin
translate(cpos)
if reversepath
bottom_left, top_left, top_right, bottom_right = reverse(box(O, 2w, 2h))
else
bottom_left, top_left, top_right, bottom_right = box(O, 2w, 2h)
end
top_center = midpoint(top_left, top_right)
right_center = midpoint(top_right, bottom_right)
bottom_center = midpoint(bottom_left, bottom_right)
left_center = midpoint(top_left, bottom_left)
move(left_center)
curve(between(left_center, top_left, kappa), between(top_center, top_left, kappa), top_center)
curve(between(top_center, top_right, kappa), between(right_center, top_right, kappa), right_center)
curve(between(right_center, bottom_right, kappa), between(bottom_center, bottom_right, kappa), bottom_center)
curve(between(bottom_center, bottom_left, kappa), between(left_center, bottom_left, kappa), left_center)
closepath()
result = storepath()
do_action(action)
end
return result
end
function arc(xc, yc, radius, angle1, angle2;
action = :none)
Cairo.arc(_get_current_cr(), xc, yc, radius, angle1, angle2)
Expand Down
25 changes: 19 additions & 6 deletions src/tiles-grids.jl
Original file line number Diff line number Diff line change
Expand Up @@ -566,16 +566,25 @@ An EquilateralTriangleGrid is an iterator that makes a grid of equilateral
triangles with side length `side` positioned on a rectangular grid with `nrows`
and `ncols`.
```julia
EquilateralTriangleGrid(startpoint::Point, side, nrows, ncols; up = true)
```
The first triangle is centered at `startpoint` and points up if `up` is true.
Example
###Example
```julia
nrows = 5
ncols = 8
side = 150
eqtg = EquilateralTriangleGrid(O, side, nrows, ncols)
@show first(eqtg)
# (Point[Point(-75.0, 64.9519052838329),
Point(0.0, -64.9519052838329),
Point(75.0, 64.9519052838329)], 1)
# now you can use the iterator to generate (and draw) triangles:
for tri in eqtg
vertices, trianglenumber = tri
Expand Down Expand Up @@ -629,13 +638,15 @@ function Base.iterate(eqt::EquilateralTriangleGrid)
eqt.currentrow, eqt.currentcol = 1, 1
cellnumber = (eqt.currentrow - 1) * eqt.nrows + eqt.currentcol
h = (sqrt(3) * eqt.side) / 2
r = (eqt.side * sqrt(3)) / 3 # circumradius
pointup = (isodd(eqt.currentrow) && isodd(eqt.currentcol)) || (iseven(eqt.currentrow) && iseven(eqt.currentcol)) ? eqt.up : !eqt.up
# the first triangle
cpt_c = Point(eqt.startpoint.x - eqt.side/2 + (eqt.side / 2 * eqt.currentcol),
eqt.startpoint.y -h + (h * eqt.currentrow))
cpt_c = Point(eqt.startpoint.x + ((eqt.side / 2) * eqt.currentcol) - eqt.side / 2,
eqt.startpoint.y + (h * eqt.currentrow) - h)
pts_c = _equilateral_triangle(cpt_c, eqt.side, pointup ? :up : :down)
# next one
cpt_n = Point(eqt.startpoint.x + (eqt.side / 2 * eqt.currentcol), eqt.startpoint.y + (h * eqt.currentrow))
cpt_n = Point(eqt.startpoint.x + (eqt.side / 2 * eqt.currentcol),
eqt.startpoint.y + (h * eqt.currentrow))
pts_n = _equilateral_triangle(cpt_n, eqt.side, pointup ? :down : :up)
return ((pts_c, cellnumber), (pts_n, cellnumber + 1))
end
Expand All @@ -651,9 +662,11 @@ function Base.iterate(eqt::EquilateralTriangleGrid, state)
eqt.currentrow += 1
end
h = (sqrt(3) * eqt.side) / 2
r = (eqt.side * sqrt(3)) / 3
pointup = (isodd(eqt.currentrow) && isodd(eqt.currentcol)) || (iseven(eqt.currentrow) && iseven(eqt.currentcol)) ? eqt.up : !eqt.up
cpt_c = Point(eqt.startpoint.x - eqt.side / 2 + (eqt.side / 2 * eqt.currentcol),
eqt.startpoint.y - h + (h * eqt.currentrow))

cpt_c = Point(eqt.startpoint.x + ((eqt.side / 2) * eqt.currentcol) - eqt.side / 2,
eqt.startpoint.y + (h * eqt.currentrow) - h)
pts_c = _equilateral_triangle(cpt_c, eqt.side, pointup ? :up : :down)
cpt_n = Point(eqt.startpoint.x + (eqt.side / 2 * eqt.currentcol), eqt.startpoint.y + (h * eqt.currentrow))
pts_n = _equilateral_triangle(cpt_n, eqt.side, pointup ? :down : :up)
Expand Down
33 changes: 33 additions & 0 deletions test/rules2.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Luxor
using Test

function test_rule2(fname)
Drawing(500, 500, fname)
background("white")
origin()
setline(0.7)

L = 110

toppts = between.(Point(-250, -250), Point(250, -250), range(0, 1, length = L))
bottompts = between.(Point(-250, 250), Point(250, 250), range(0, 1, length = L))

circle.(toppts, 3, :fill)
circle.(bottompts, 3, :fill)

bbox = BoundingBox(box(O, 495, 495))
rule.(toppts, bottompts, boundingbox = bbox)
rule.(toppts, reverse(bottompts), boundingbox = bbox)

leftpts = between.(Point(-250, -250), Point(-250, 250), range(0, 1, length = L))
rightpts = between.(Point(250, -250), Point(250, 250), range(0, 1, length = L))

rule.(leftpts, rightpts)
rule.(leftpts, reverse(rightpts))

@test finish() == true
println("...finished test: output in $(fname)")
end

test_rule2("rule2.png")
println("...finished rule2 test")
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ function run_all_tests()
@testset "misc" begin
include("luxor-test1.jl")
include("rules.jl")
include("rules2.jl")
include("barstest.jl")
include("unit-conversions.jl")
include("grid-test.jl")
Expand Down

0 comments on commit 37707e4

Please sign in to comment.