Skip to content

Commit

Permalink
small changes and polygon experiments
Browse files Browse the repository at this point in the history
  • Loading branch information
cormullion committed Feb 15, 2019
1 parent 2a35c1f commit 62d2358
Show file tree
Hide file tree
Showing 10 changed files with 407 additions and 24 deletions.
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
# Changelog

## [v1.1.5] - 2019-01-26
## [v1.2.0] - 2019-02-15

### Added

- more box functions take vertices=true/false
- added setdash(dashes)
- miscellaneous polygon functions tho they're largely untested and needing improvements

### Changed

- tried to fix odd bug in `rule(..., π/2)`

### Removed

-
- Luxor.juliacolorsceheme, it's no longer compatible with ColorSchemes.jl

### Deprecated

Expand Down Expand Up @@ -354,7 +355,7 @@ Misc updates and bug fixes

## [v0.9.2] - 2017-09-26

Miscellanous changes and documentation improvements
Miscellaneous changes and documentation improvements

### Added
-
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ A short tutorial can be found in the documentation. There are some Luxor-related

Luxor isn't interactive: for interactive graphics, look at [Gtk.jl](https://github.com/JuliaGraphics/Gtk.jl), and [GLVisualize](https://github.com/JuliaGL/GLVisualize.jl). [Makie](https://github.com/JuliaPlots/Makie.jl) is worth investigating.

## How can you contribute?

If you _know any geometry_ you probably know more than me, so there are plenty of improvements to algorithms waiting to be made. There are some _TODO_s sprinkled through the code, but plenty more opportunities for improvement.

_Update the code_, most of which was written for Julia versions 0.2, v0.3 and 0.4 (remember when broadcasting wasn't a thing?) so there are probably many areas where the code could take more advantage of version 1.

There can always be _more tests_, as the present tests are mainly visual, showing that something works, but there should be much more testing of things that shouldn't work - inappropriate input, overlapping polygons, coincident or collinear points, anticlockwise polygons, etc.


[docs-latest-img]: https://img.shields.io/badge/docs-latest-blue.svg
[docs-latest-url]: http://juliagraphics.github.io/Luxor.jl/latest/

Expand Down
20 changes: 20 additions & 0 deletions docs/src/animation.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,26 @@ function frame(scene, framenumber)
end
```

### Passing information to the frame() function

If you want to pass information to the frame function, such as an array of values, try these:

``
function frame(scene, framenumber, datapoints)
...
end

somedata = Datapoints[...]

animate(demo, [
Scene(demo, (s, f) -> frame(s, f, somedata),
0:100,
optarg=somedata)
],
creategif=true,
pathname="...")
```
## Easing functions
Transitions for animations often use non-constant and non-linear motions, and these are usually provided by *easing* functions. Luxor defines some of the basic easing functions and they're listed in the (unexported) array `Luxor.easingfunctions`. Each scene can have one easing function.
Expand Down
2 changes: 2 additions & 0 deletions docs/src/basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ Drawing()
background(1, 1, 1, 0)
origin()
setline(30)
# current opacity is 0.0, so use setcolor() rather than sethue()
# or use setopacity()
setcolor("green")
box(BoundingBox() - 50, :stroke)
finish()
Expand Down
6 changes: 5 additions & 1 deletion src/Luxor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,11 @@ export Drawing,
easeinoutexpo, easeincirc, easeoutcirc, easeinoutcirc, easingflat, easeinoutinversequad,

# noise
noise, seednoise
noise, seednoise,

# experimental polygon functions
polyremovecollinearpoints, polytriangulate!, polyselfintersections,
ispointinsidetriangle, ispolyclockwise, polyorientation

# basic unit conversion to Cairo/PostScript points
const inch = 72.0
Expand Down
2 changes: 2 additions & 0 deletions src/arrows.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ function arrow(startpoint::Point, endpoint::Point;
gsave()
setlinejoin("butt")
setline(linewidth)

isapprox(startpoint, endpoint) && throw(error("can't draw arrow between two identical points"))
shaftlength = distance(startpoint, endpoint)
shaftangle = atan(startpoint.y - endpoint.y, startpoint.x - endpoint.x)
arrowheadtopsideangle = shaftangle + arrowheadangle
Expand Down
29 changes: 10 additions & 19 deletions src/juliagraphics.jl
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
# the current julia logo and graphics

const darker_blue = (0.251, 0.388, 0.847)
const lighter_blue = (0.4, 0.51, 0.878)
const darker_purple = (0.584, 0.345, 0.698)
const lighter_purple = (0.667, 0.475, 0.757)
const darker_green = (0.22, 0.596, 0.149)
const darker_blue = (0.251, 0.388, 0.847)
const lighter_blue = (0.4, 0.51, 0.878)
const darker_purple = (0.584, 0.345, 0.698)
const lighter_purple = (0.667, 0.475, 0.757)
const darker_green = (0.22, 0.596, 0.149)
const lighter_green = (0.376, 0.678, 0.318)
const darker_red = (0.796, 0.235, 0.2)
const lighter_red = (0.835, 0.388, 0.361)
const purples = (darker_purple, lighter_purple)
const greens = (darker_green, lighter_green)
const reds = (darker_red, lighter_red)

const juliacolorscheme = [
RGB(Luxor.darker_purple...),
RGB(Luxor.darker_red...),
RGB(Luxor.darker_red...),
RGB(Luxor.darker_green...),
RGB(Luxor.darker_green...),
RGB(Luxor.darker_purple...)
]
const darker_red = (0.796, 0.235, 0.2)
const lighter_red = (0.835, 0.388, 0.361)
const purples = (darker_purple, lighter_purple)
const greens = (darker_green, lighter_green)
const reds = (darker_red, lighter_red)

"""
julialogo(;action=:fill, color=true)
Expand Down
168 changes: 167 additions & 1 deletion src/polygons.jl
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,7 @@ end
polyarea(p::AbstractArray)
Find the area of a simple polygon. It works only for polygons that don't
self-intersect.
self-intersect. See also `polyorientation()`.
"""
function polyarea(plist::AbstractArray{Point, 1})
n = length(plist)
Expand Down Expand Up @@ -779,6 +779,7 @@ end
Return an array of the points in polygon S plus the points where polygon S crosses
polygon C. Calls `intersectlinepoly()`.
TODO This code is experimental...
"""
function polyintersections(S::AbstractArray{Point, 1}, C::AbstractArray{Point, 1})
Splusintersectionpoints = Point[]
Expand All @@ -791,3 +792,168 @@ function polyintersections(S::AbstractArray{Point, 1}, C::AbstractArray{Point, 1
end
return Splusintersectionpoints
end

# TODO these experimental functions don't work all the time
# use with caution... :)

"""
polyorientation(pgon)
Returns a number which is positive if the polygon is clockwise in Luxor...
TODO This code is still experimental...
"""
function polyorientation(pgon::AbstractArray{Point, 1})
# in Luxor polys are usually clockwise
# perhaps this is because the Y axis goes down...
sum = 0.0
for i in 1:length(pgon)
sum += crossproduct(pgon[i], pgon[mod1(i + 1, end)])
end
return sum
end

polyorientation(pt1, pt2, pt3) = polyorientation(Point[pt1, pt2, pt3])

"""
ispolyclockwise(pgon)
Returns true if polygon is clockwise. WHEN VIEWED IN A LUXOR DRAWING...?
TODO This code is still experimental...
"""
function ispolyclockwise(pgon::AbstractArray{Point, 1})
polyorientation(pgon) > 0.0
end

"""
ispointinsidetriangle(p, p1, p2, p3)
ispointinsidetriangle(p, triangle::Array{Point, 1})
Returns false if `p` is not inside triangle p1 p2 p3.
"""

function ispointinsidetriangle(p::Point, p1::Point, p2::Point, p3::Point)
if polyorientation([p1, p2, p3]) < 0.0
p1, p3 = p3, p1
end
# barycentric method?
s = p1.y * p3.x - p1.x * p3.y + (p3.y - p1.y) * p.x + (p1.x - p3.x) * p.y
t = p1.x * p2.y - p1.y * p2.x + (p1.y - p2.y) * p.x + (p2.x - p1.x) * p.y

if s < 0 != t < 0
return false
end
A = -p2.y * p3.x + p1.y * (p3.x - p2.x) + p1.x * (p2.y - p3.y) + p2.x * p3.y
return A < 0 ?
(s <= 0 && s + t >= A) :
(s >= 0 && s + t <= A)
end

ispointinsidetriangle(p::Point, triangle::Array{Point,1}) =
ispointinsidetriangle(p, triangle[1], triangle[2], triangle[3])

"""
polyselfintersections(pgon::AbstractArray{Point, 1};
findfirst=false)
return a set of points defining the intersecting lines.
Crossings are usually included twice... ?
If `findfirst` is true, only the first one is returned, which should be quicker.
TODO This code is still experimental... Needs some thought about closed
polygons where the first and last points are the same...?
"""
function polyselfintersections(S::AbstractArray{Point, 1};
findfirst=false)
selfcrossings = Array{Point, 1}[]
for i in 1:length(S)
for j in 1:length(S)
flag, p = intersection(
S[i], S[mod1(i+1, length(S))],
S[j], S[mod1(j+1, length(S))],
crossingonly = true,
commonendpoints = true)
if flag
push!(selfcrossings, Point[
S[i],
S[mod1(i+1, length(S))],
S[j],
S[mod1(j+1, length(S))]
])
end
if findfirst
length(selfcrossings) > 0 && break
end
end
end
return selfcrossings
end

"""
polytriangulate!(pgon::AbstractArray{Point, 1})
Replace the polygon with an array of triangles which triangulate the polygon.
Caution: this destroys the polygon in place.
TODO This code is still experimental...
"""
function polytriangulate!(pgon::AbstractArray{Point, 1})
if !ispolyclockwise(pgon)
pgon = reverse(pgon)
end
triangles = Array{Point, 1}[]
sz = length(pgon)
while sz >= 3
istriangleremoved = false
for i in 1:sz-1
sz = length(pgon)
p1 = pgon[mod1(i, sz)]
p2 = pgon[mod1(i + 1, sz)]
p3 = pgon[mod1(i + 2, sz)]
iscw = (polyorientation(p1, p2, p3) > 0.0)
!iscw && continue
overlappingpoints = 0
for v in 1:sz-1
v == i || v == i + 1 || v == i + 2 && continue
if ispointinsidetriangle(pgon[mod1(v, sz)], p1, p2, p3)
overlappingpoints += 1
end
overlappingpoints > 0 && continue
end
push!(triangles, Point[p1, p2, p3])
deleteat!(pgon, mod1(i + 1, sz))
sz = length(pgon)
istriangleremoved = true
end
!istriangleremoved && break
end
return triangles
end

"""
polyremovecollinearpoints(pgon::AbstractArray{Point, 1})
Return copy of polygon with no collinear points.
Caution: may return an empty polygon... !
TODO This code is still experimental...
"""
function polyremovecollinearpoints(pgon::AbstractArray{Point, 1})
markfordeletion = []
for n in 1:length(pgon)
p1 = pgon[n]

pfirst = pgon[mod1(n - 1, length(pgon))]
plast = pgon[mod1(n + 1, length(pgon))]

if ispointonline(p1, pfirst, plast, extended=true, atol=0.1)
push!(markfordeletion, n)
end
end
return pgon[setdiff(1:length(pgon), markfordeletion)]
end
Loading

0 comments on commit 62d2358

Please sign in to comment.