Skip to content

Instantly share code, notes, and snippets.

@jonocarroll
Created May 12, 2022 12:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonocarroll/e22ea4982a27e6663ec75b82b55b3ec3 to your computer and use it in GitHub Desktop.
Save jonocarroll/e22ea4982a27e6663ec75b82b55b3ec3 to your computer and use it in GitHub Desktop.
Lissajous curve matrix in Julia
## 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