-
-
Notifications
You must be signed in to change notification settings - Fork 52
/
fourier.jl
197 lines (168 loc) · 5.53 KB
/
fourier.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
#=
Drawing Julia using a Fourier series.
A high definition animation can be seen here: https://youtu.be/rrmx2Q3sO1Y
This code is based on code kindly provided by ric-cioffi (https://github.com/ric-cioffi)
But was rewritten for v0.3 by Ole Kröger.
=#
using Javis, FFTW, FFTViews
using TravelingSalesmanHeuristics
function ground(args...)
background("black")
sethue("white")
end
function circ(; r = 10, vec = O, action = :stroke, color = "white")
sethue(color)
circle(O, r, action)
my_arrow(O, vec)
return vec
end
function my_arrow(start_pos, end_pos)
arrow(
start_pos,
end_pos;
linewidth = distance(start_pos, end_pos) / 100,
arrowheadlength = 7,
)
return end_pos
end
function draw_line(
p1 = O,
p2 = O;
color = "white",
action = :stroke,
edge = "solid",
linewidth = 3,
)
sethue(color)
setdash(edge)
setline(linewidth)
line(p1, p2, action)
end
function draw_path!(path, pos, color)
sethue(color)
push!(path, pos)
return draw_line.(path[2:end], path[1:(end - 1)]; color = color)
end
function get_points(npoints, options)
Drawing() # julialogo needs a drawing
julialogo(; action = :path, centered = true)
shapes = pathtopoly()
new_shapes = shapes[1:6]
last_i = 1
# the circles in the JuliaLogo are part of a single shape
# this loop creates new shapes for each circle
for shape in shapes[7:7]
max_dist = 0.0
for i in 2:length(shape)
d = distance(shape[i - 1], shape[i])
if d > 3
push!(new_shapes, shape[last_i:(i - 1)])
last_i = i
end
end
end
push!(new_shapes, shapes[7][last_i:end])
shapes = new_shapes
for i in 1:length(shapes)
shapes[i] .*= options.shape_scale
end
total_distance = 0.0
for shape in shapes
total_distance += polyperimeter(shape)
end
parts = []
points = Point[]
start_i = 1
for shape in shapes
len = polyperimeter(shape)
portion = len / total_distance
nlocalpoints = floor(Int, portion * npoints)
new_points = [
Javis.get_polypoint_at(shape, i / (nlocalpoints - 1)) for
i in 0:(nlocalpoints - 1)
]
append!(points, new_points)
new_i = start_i + length(new_points) - 1
push!(parts, start_i:new_i)
start_i = new_i
end
return points, parts
end
c2p(c::Complex) = Point(real(c), imag(c))
remap_idx(i::Int) = (-1)^i * floor(Int, i / 2)
remap_inv(n::Int) = 2n * sign(n) - 1 * (n > 0)
function animate_fourier(options)
npoints = options.npoints
nplay_frames = options.nplay_frames
nruns = options.nruns
nframes = nplay_frames + options.nend_frames
# obtain points from julialogo
points, parts = get_points(npoints, options)
npoints = length(points)
println("#points: $npoints")
# solve tsp to reduce length of extra edges
distmat = [distance(points[i], points[j]) for i in 1:npoints, j in 1:npoints]
path, cost = solve_tsp(distmat; quality_factor = options.tsp_quality_factor)
println("TSP cost: $cost")
points = points[path] # tsp saves the last point again
# optain the fft result and scale
x = [p.x for p in points]
y = [p.y for p in points]
fs = FFTView(fft(complex.(x, y)))
# normalize the points as fs isn't normalized
fs ./= npoints
npoints = length(fs)
video = Video(options.width, options.height)
Background(1:nframes, ground)
circles = Object[]
for i in 1:npoints
ridx = remap_idx(i)
push!(circles, Object((args...) -> circ(; r = abs(fs[ridx]), vec = c2p(fs[ridx]))))
if i > 1
# translate to the tip of the vector of the previous circle
act!(circles[i], Action(1:1, anim_translate(O, circles[i - 1])))
end
ridx = remap_idx(i)
act!(circles[i], Action(1:nplay_frames, anim_rotate(0.0, ridx * 2π * nruns)))
end
trace_points = Point[]
Object(1:nframes, (args...) -> draw_path!(trace_points, pos(circles[end]), "red"))
return render(video; pathname = options.filename)
end
function main()
hd_options = (
npoints = 3001, # rough number of points for the shape => number of circles
nplay_frames = 1200, # number of frames for the animation of fourier
nruns = 2, # how often it's drawn
nend_frames = 200, # number of frames in the end
width = 1920,
height = 1080,
shape_scale = 2.5, # scale factor for the logo
tsp_quality_factor = 50,
filename = "julia_hd.mp4",
)
fast_options = (
npoints = 1001, # rough number of points for the shape => number of circles
nplay_frames = 600, # number of frames for the animation of fourier
nruns = 1, # how often it's drawn
nend_frames = 200, # number of frames in the end
width = 1000,
height = 768,
shape_scale = 1.5, # scale factor for the logo
tsp_quality_factor = 40,
filename = "julia_fast.mp4",
)
gif_options = (
npoints = 651, # rough number of points for the shape => number of circles
nplay_frames = 600, # number of frames for the animation of fourier
nruns = 2, # how often it's drawn
nend_frames = 0, # number of frames in the end
width = 350,
height = 219,
shape_scale = 0.8, # scale factor for the logo
tsp_quality_factor = 80,
filename = "julia_logo_dft.gif",
)
return animate_fourier(gif_options)
end
main()