Created
May 12, 2022 12:32
-
-
Save jonocarroll/e22ea4982a27e6663ec75b82b55b3ec3 to your computer and use it in GitHub Desktop.
Lissajous curve matrix in Julia
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## Goal: reproduce e.g. https://www.reddit.com/r/oddlysatisfying/comments/uc054a/lissajous_polygons | |
using Plots | |
import GeometryBasics: Point | |
## https://jcarroll.xyz/2022/04/07/interpolation-animation-in.html | |
interpolate(a, b) = t -> ((1.0 - t) * a + t * b) | |
## define the vertices of an N-gon | |
""" | |
vertices(center, R, n[, closed]) | |
# Arguments | |
- `center::Point`: center of polygon | |
- `R::Real`: circumradius | |
- `n::Int`: number of sides | |
- `closed::Bool`: should the first point be repeated? | |
Polygon has a flat bottom and points progress counterclockwise | |
starting at the right end of the base | |
The final point is the starting point when closed = true | |
# Examples | |
```julia-repl | |
using Plots | |
a = vertices(Point(0,0), 1, 5, true); | |
plot(a[1,:], a[2,:], xlim = (-1.2, 1.2), ylim = (-1.2, 1.2), ratio = 1) | |
scatter!(a[1,:], a[2,:]) | |
``` | |
""" | |
function vertices(center::Point, R::Real, n::Int, closed::Bool=true) | |
X = center[1] .+ R * cos.(π/n .* (1 .+ 2 .* (0:n-1)) .- π/2) | |
Y = center[2] .+ R * sin.(π/n .* (1 .+ 2 .* (0:n-1)) .- π/2) | |
res = permutedims([X Y]) | |
## append the start point if closed | |
if closed | |
res = hcat(res, res[:,1]) | |
end | |
return res | |
end | |
a = vertices(Point(0,0), 1, 5, true); | |
plot(a[1,:], a[2,:], xlim = (-1.2, 1.2), ylim = (-1.2, 1.2), ratio = 1) | |
scatter!(a[1,:], a[2,:]) | |
png("vertices.png") | |
""" | |
_interPoints(pts, steps, slice) | |
# Arguments | |
- `pts::Array`: Array of `Point`s representing a polygon | |
- `steps::Int`: number of points to interpolate | |
- `slice::Int`: which polygon vertex to begin with; points will be interpolated to the next vertex | |
This is an internal function to interpolate points between | |
two vertices of a polygon. It is intended to be used | |
in a `map` across slices of a polygon. | |
# Examples | |
```julia-repl | |
using Plots | |
a = vertices(Point(0,0), 1, 5, true); | |
b = _interPoints(a, 10, 1); | |
plot(a[1,:], a[2,:], ratio = 1) | |
scatter!(b[1,:], b[2,:]) | |
``` | |
""" | |
function _interPoints(pts::Array, steps::Int, slice::Int) | |
int = interpolate(pts[:,slice], pts[:,slice+1]) | |
explode = [int(t) for t in range(0,1,length=steps)] | |
return hcat(explode...) | |
end | |
a = vertices(Point(0,0), 1, 5, true); | |
b = _interPoints(a, 10, 1); | |
plot(a[1,:], a[2,:], ratio = 1) | |
scatter!(b[1,:], b[2,:]) | |
png("_interPoints.png") | |
""" | |
interPoints(pts, steps) | |
# Arguments | |
- `pts::Array`: Array of `Point`s representing a polygon | |
- `steps::Int`: number of points to interpolate between each pair of vertices | |
This takes an `Array` of `Point`s representing polygon vertices and interpolates between the vertices | |
# Examples | |
```julia-repl | |
using Plots | |
a = vertices(Point(0,0), 1, 5, true); | |
b = interPoints(a, 10); | |
plot(b[1,:], b[2,:], xlim = (-1.2, 1.2), ylim = (-1.2, 1.2), ratio = 1) | |
scatter!(b[1,:], b[2,:]) | |
``` | |
""" | |
function interPoints(pts::Array, steps::Int) | |
res = map(s -> _interPoints(pts, steps, s), 1:(size(pts,2)-1)) | |
return hcat(res...) | |
end | |
a = vertices(Point(0,0), 1, 5, true); | |
b = interPoints(a, 10); | |
plot(b[1,:], b[2,:], xlim = (-1.2, 1.2), ylim = (-1.2, 1.2), ratio = 1) | |
scatter!(b[1,:], b[2,:]) | |
png("interPoints.png") | |
## animate it! | |
anim = @animate for t in 1:size(b,2) | |
plot(b[1,:], b[2,:], xlim = (-1.2, 1.2), ylim = (-1.2, 1.2), ratio=1) | |
scatter!([b[1,t]], [b[2,t]], markersize=8) | |
end | |
gif(anim, "n5_points.gif", fps = 12) | |
""" | |
Find the intersection of two Arrays (representing polygons) | |
# Arguments | |
- `a::Array`: first polygon (for x values) | |
- `b::Array`: second polygon (for y values) | |
Take the x values from a and the y values from b | |
# Examples | |
```julia-repl | |
using Plots | |
t1 = interPoints(vertices(Point(2,8), 0.5, 5), 10); | |
t2 = interPoints(vertices(Point(1,7), 0.5, 5), 10); | |
tx = intersection(t1, t2); | |
plot(t1[1,:], t1[2,:], xlim = (0,3.5), ylim = (6,9), ratio = 1) | |
plot!(t2[1,:], t2[2,:]) | |
plot!(tx[1,:], tx[2,:]) | |
``` | |
""" | |
function intersection(a::Array, b::Array) | |
permutedims(hcat([(a[1, :])...], [(b[2, :])...])) | |
end | |
t1 = interPoints(vertices(Point(2,8), 0.5, 5), 10); | |
t2 = interPoints(vertices(Point(1,7), 0.5, 5), 10); | |
tx = intersection(t1, t2); | |
plot(t1[1,:], t1[2,:], xlim = (0,3.5), ylim = (6,9), ratio = 1) | |
plot!(t2[1,:], t2[2,:]) | |
plot!(tx[1,:], tx[2,:]) | |
png("intersection.png") | |
## PUT IT ALL TOGETHER! | |
""" | |
speed_factor(poly, speed) | |
# Arguments | |
- `poly::Array`: Array of `Point`s representing a polygon | |
- `speed::Real`: mulitiplicative factor representing how the number of times a polygon should be traversed | |
# Examples | |
```julia-repl | |
r = 0.4; | |
d = 3; | |
# Both produce a 2x72 Array{Float64,2} | |
tx1 = interPoints(vertices(Point(2,6), r, d), 24) | |
tx2 = speed_factor(interPoints(vertices(Point(3,6), r, d), 16) , 1.5) | |
``` | |
""" | |
function speed_factor(poly::Array, speed::Real) | |
if (speed % 1 == 0) | |
res = repeat(poly, outer = (1,Int(speed))) | |
else | |
n = Int(floor(speed / 1)) | |
res = repeat(poly, outer=(1,n)) | |
n_rem = Int(speed*size(poly,2)-size(res,2)) | |
res = hcat(res, poly[:,1:n_rem]) | |
end | |
res | |
end | |
## n = 3 | |
r = 0.4; | |
d = 3; | |
tx1 = interPoints(vertices(Point(2,6), r, d), 24) | |
tx2 = speed_factor(interPoints(vertices(Point(3,6), r, d), 16), 1.5) | |
tx3 = speed_factor(interPoints(vertices(Point(4,6), r, d), 12), 2) | |
tx4 = speed_factor(interPoints(vertices(Point(5,6), r, d), 10), 2.4) | |
tx5 = speed_factor(interPoints(vertices(Point(6,6), r, d), 8), 3) | |
ty1 = interPoints(vertices(Point(1,5), r, d), 24) | |
ty2 = speed_factor(interPoints(vertices(Point(1,4), r, d), 16), 1.5) | |
ty3 = speed_factor(interPoints(vertices(Point(1,3), r, d), 12), 2) | |
ty4 = speed_factor(interPoints(vertices(Point(1,2), r, d), 10), 2.4) | |
ty5 = speed_factor(interPoints(vertices(Point(1,1), r, d), 8), 3) | |
allx = [tx1, tx2, tx3, tx4, tx5] | |
ally = [ty1, ty2, ty3, ty4, ty5] | |
allint = [intersection(x, y) for x in allx, y in ally] | |
bbox = Point(6.5,6.5); | |
anim3 = @animate for t in 1:size(tx1,2) | |
plot(xlim=(0,bbox[2]), ylim=(0,bbox[2]), | |
legend=false, ratio=1, axis=nothing, border=:none, | |
background_color="black", size=(1200,1200)) | |
for p in 1:size(allx,1) | |
plot!(allx[p][1,1:t], allx[p][2,1:t], color=p, linewidth=6) | |
plot!(ally[p][1,1:t], ally[p][2,1:t], color=p, linewidth=6) | |
plot!([allx[p][1,t], allx[p][1,t]], [0.5, allx[p][2,t]], color="grey", alpha=0.5, linewidth=5) | |
plot!([ally[p][1,t], bbox[2]], [ally[p][2,t], ally[p][2,t]], color="grey", alpha=0.5, linewidth=5) | |
end | |
for p in allint | |
plot!(p[1,1:t], p[2,1:t], color="blue", linewidth=5) | |
end | |
end | |
gif(anim3, "n3.gif", fps=12) | |
## n = 4 | |
r = 0.4; | |
d = 4; | |
tx1 = interPoints(vertices(Point(2,6), r, d), 24) | |
tx2 = speed_factor(interPoints(vertices(Point(3,6), r, d), 16), 1.5) | |
tx3 = speed_factor(interPoints(vertices(Point(4,6), r, d), 12), 2) | |
tx4 = speed_factor(interPoints(vertices(Point(5,6), r, d), 10), 2.4) | |
tx5 = speed_factor(interPoints(vertices(Point(6,6), r, d), 8), 3) | |
ty1 = interPoints(vertices(Point(1,5), r, d), 24) | |
ty2 = speed_factor(interPoints(vertices(Point(1,4), r, d), 16), 1.5) | |
ty3 = speed_factor(interPoints(vertices(Point(1,3), r, d), 12), 2) | |
ty4 = speed_factor(interPoints(vertices(Point(1,2), r, d), 10), 2.4) | |
ty5 = speed_factor(interPoints(vertices(Point(1,1), r, d), 8), 3) | |
allx = [tx1, tx2, tx3, tx4, tx5] | |
ally = [ty1, ty2, ty3, ty4, ty5] | |
allint = [intersection(x, y) for x in allx, y in ally] | |
bbox = Point(6.5,6.5); | |
anim4 = @animate for t in 1:size(tx1,2) | |
plot(xlim=(0,bbox[2]), ylim=(0,bbox[2]), | |
legend=false, ratio=1, axis=nothing, border=:none, | |
background_color="black", size=(1200,1200)) | |
for p in 1:size(allx,1) | |
plot!(allx[p][1,1:t], allx[p][2,1:t], color=p, linewidth=6) | |
plot!(ally[p][1,1:t], ally[p][2,1:t], color=p, linewidth=6) | |
plot!([allx[p][1,t], allx[p][1,t]], [0.5, allx[p][2,t]], color="grey", alpha=0.5, linewidth=5) | |
plot!([ally[p][1,t], bbox[2]], [ally[p][2,t], ally[p][2,t]], color="grey", alpha=0.5, linewidth=5) | |
end | |
for p in allint | |
plot!(p[1,1:t], p[2,1:t], color="blue", linewidth=5) | |
end | |
end | |
gif(anim4, "n4.gif", fps=12) | |
## n = 5 | |
r = 0.4; | |
d = 5; | |
tx1 = interPoints(vertices(Point(2,6), r, d), 24) | |
tx2 = speed_factor(interPoints(vertices(Point(3,6), r, d), 16), 1.5) | |
tx3 = speed_factor(interPoints(vertices(Point(4,6), r, d), 12), 2) | |
tx4 = speed_factor(interPoints(vertices(Point(5,6), r, d), 10), 2.4) | |
tx5 = speed_factor(interPoints(vertices(Point(6,6), r, d), 8), 3) | |
ty1 = interPoints(vertices(Point(1,5), r, d), 24) | |
ty2 = speed_factor(interPoints(vertices(Point(1,4), r, d), 16), 1.5) | |
ty3 = speed_factor(interPoints(vertices(Point(1,3), r, d), 12), 2) | |
ty4 = speed_factor(interPoints(vertices(Point(1,2), r, d), 10), 2.4) | |
ty5 = speed_factor(interPoints(vertices(Point(1,1), r, d), 8), 3) | |
allx = [tx1, tx2, tx3, tx4, tx5] | |
ally = [ty1, ty2, ty3, ty4, ty5] | |
allint = [intersection(x, y) for x in allx, y in ally] | |
bbox = Point(6.5,6.5); | |
anim5 = @animate for t in 1:size(tx1,2) | |
plot(xlim=(0,bbox[2]), ylim=(0,bbox[2]), | |
legend=false, ratio=1, axis=nothing, border=:none, | |
background_color="black", size=(1200,1200)) | |
for p in 1:size(allx,1) | |
plot!(allx[p][1,1:t], allx[p][2,1:t], color=p, linewidth=6) | |
plot!(ally[p][1,1:t], ally[p][2,1:t], color=p, linewidth=6) | |
plot!([allx[p][1,t], allx[p][1,t]], [0.5, allx[p][2,t]], color="grey", alpha=0.5, linewidth=5) | |
plot!([ally[p][1,t], bbox[2]], [ally[p][2,t], ally[p][2,t]], color="grey", alpha=0.5, linewidth=5) | |
end | |
for p in allint | |
plot!(p[1,1:t], p[2,1:t], color="blue", linewidth=5) | |
end | |
end | |
gif(anim5, "n5.gif", fps=12) | |
## n = 6 | |
r = 0.4; | |
d = 6; | |
tx1 = interPoints(vertices(Point(2,6), r, d), 24) | |
tx2 = speed_factor(interPoints(vertices(Point(3,6), r, d), 16), 1.5) | |
tx3 = speed_factor(interPoints(vertices(Point(4,6), r, d), 12), 2) | |
tx4 = speed_factor(interPoints(vertices(Point(5,6), r, d), 10), 2.4) | |
tx5 = speed_factor(interPoints(vertices(Point(6,6), r, d), 8), 3) | |
ty1 = interPoints(vertices(Point(1,5), r, d), 24) | |
ty2 = speed_factor(interPoints(vertices(Point(1,4), r, d), 16), 1.5) | |
ty3 = speed_factor(interPoints(vertices(Point(1,3), r, d), 12), 2) | |
ty4 = speed_factor(interPoints(vertices(Point(1,2), r, d), 10), 2.4) | |
ty5 = speed_factor(interPoints(vertices(Point(1,1), r, d), 8), 3) | |
allx = [tx1, tx2, tx3, tx4, tx5] | |
ally = [ty1, ty2, ty3, ty4, ty5] | |
allint = [intersection(x, y) for x in allx, y in ally] | |
bbox = Point(6.5,6.5); | |
anim6 = @animate for t in 1:size(tx1,2) | |
plot(xlim=(0,bbox[2]), ylim=(0,bbox[2]), | |
legend=false, ratio=1, axis=nothing, border=:none, | |
background_color="black", size=(1200,1200)) | |
for p in 1:size(allx,1) | |
plot!(allx[p][1,1:t], allx[p][2,1:t], color=p, linewidth=6) | |
plot!(ally[p][1,1:t], ally[p][2,1:t], color=p, linewidth=6) | |
plot!([allx[p][1,t], allx[p][1,t]], [0.5, allx[p][2,t]], color="grey", alpha=0.5, linewidth=5) | |
plot!([ally[p][1,t], bbox[2]], [ally[p][2,t], ally[p][2,t]], color="grey", alpha=0.5, linewidth=5) | |
end | |
for p in allint | |
plot!(p[1,1:t], p[2,1:t], color="blue", linewidth=5) | |
end | |
end | |
gif(anim6, "n6.gif", fps=12) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment