From 56fa8b8a06a317704d3199e3ed18ede01ed254c4 Mon Sep 17 00:00:00 2001 From: Josh Ott Date: Mon, 16 May 2022 23:22:36 -0400 Subject: Added main scripts and example graphic --- animation.jl | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++ bezier.jl | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ fourier.jl | 70 +++++++++++++++++++++++++++++++++++++++++++++++++ graphics/jerma.svg | 46 +++++++++++++++++++++++++++++++++ 4 files changed, 267 insertions(+) create mode 100644 animation.jl create mode 100644 bezier.jl create mode 100644 fourier.jl create mode 100644 graphics/jerma.svg diff --git a/animation.jl b/animation.jl new file mode 100644 index 0000000..86cb505 --- /dev/null +++ b/animation.jl @@ -0,0 +1,75 @@ +using Javis + +function ground(args...) + background("black") + sethue("white") +end + +function armObject(video, object, frame, frames, tp, C) + arm = tp(comptopoint.(fourierArm(frame / frames, C))) + for i in 1:length(arm) - 1 + setline(2) + line(arm[i], arm[i + 1], :stroke) + end +end + +function drawing(video, object, frame, points, frames) + for i in 1:trunc(Int, frame / frames * length(points)) - 1 + setline(2) + line(points[i], points[i + 1], :stroke) + end +end + +comptopoint(z) = Point(real(z), imag(z)) + +""" +Render an animation of a Fourier series drawing and save it to `savePath`. + +By default, resolution is 1080p. If only one dimension is specified, the other will be calculated to be in proportion with the drawing. + +`n_drawing` defines the number of points used in graphing the drawing. By default, it is equal to the number of frames (1 point for each frame). +""" +function createVideo(coefficients, savePath, frames; width = nothing, height = nothing, n_drawing = frames, fps = 60) + f = t -> fourierSeries(t, coefficients) + t_array = range(0, 1, length=n_drawing) + + test_points = f.(t_array) + x_max = maximum(real(test_points)) + x_min = minimum(real(test_points)) + y_max = maximum(imag(test_points)) + y_min = minimum(imag(test_points)) + x_dif = x_max - x_min + y_dif = y_max - y_min + + if width === nothing && height === nothing + + if 16/x_dif >= 9/y_dif + scale = 1080/y_dif + height = 1080 + width = ((x_dif * scale) ÷ 2) * 2 + else + scale = 1920 / x_dif + width = 1920 + height = ((y_dif * scale) ÷ 2) * 2 + end + elseif width === nothing + scale = height / y_dif + width = ((x_dif * scale) ÷ 2) * 2 + elseif height === nothing + scale = width / x_dif + height = ((y_dif * scale) ÷ 2) * 2 + else + scale = width / x_dif >= height / y_dif ? height / y_dif : width / x_dif + end + + transformPoints(points) = (((points .- Point(x_min, y_min)) .* Point(scale, -scale)) .+ Point(-x_dif * scale / 2, y_dif * scale / 2)) .* 0.9 + drawing_points = transformPoints(map(i -> comptopoint(f(i)), t_array)) + + myvideo = Video(round(Int, width), round(Int, height)) + Background(1:frames, ground) + + Object(1:frames, (args...) -> armObject(args..., frames, transformPoints, coefficients)) + Object(1:frames, (args...) -> drawing(args..., drawing_points, frames)) + + render(myvideo; pathname = savePath, framerate=fps) +end \ No newline at end of file diff --git a/bezier.jl b/bezier.jl new file mode 100644 index 0000000..7d42746 --- /dev/null +++ b/bezier.jl @@ -0,0 +1,76 @@ +using EzXML + +""" +Get a complex parametric function for a series of cubic bezier curves as described in an .svg file. See README for details on file requirements. +""" +function createCurve(fileName) + doc = readxml(pwd() * "/" * fileName) + + svg = root(doc) + + layers = [] + paths = [] + + for element in eachelement(svg) + occursin("layer", element["id"]) && push!(layers, element) + end + + for element in eachelement(layers[1]) + occursin("path", element["id"]) && push!(paths, element) + end + + pointPattern = r"(?<=c ).*" + curveString = match(pointPattern, paths[1]["d"]).match + + pattern = r"((?:-)?\d*(?:\.\d*)?),((?:-)?\d*(?:\.\d*)?)" + m = collect(eachmatch(pattern, curveString)) + + b_input = [[0.0,0.0]] + + for i in m + k = length(b_input) + Δx = parse(Float64, i[1]) + Δy = -parse(Float64, i[2]) + new_point = [b_input[k-(k-1)%3][1] + Δx, b_input[k-(k-1)%3][2] + Δy] + + push!(b_input, new_point) + end + + return t -> parametricBezier(t, b_input) +end + +""" +Parametric function for a curve made up of cubic bezier curves described by a vector of complex coordinates `input`. + +Format for points taken from svg format. +""" +function parametricBezier(t, input) + t = t%1 + n_input = length(input)-1 + n_cubics = n_input÷3 + + cubicIndex = convert(Int, t÷(1//n_cubics)+1) + cubicTime = (t%(1/n_cubics))*n_cubics + currentCubic = input[3(cubicIndex-1)+1:3cubicIndex+1] + b_t = b(cubicTime, currentCubic) + + return b_t[1] + b_t[2]*im +end + +""" +Get position along bezier curve described by points `p` at time `t` from 0 to 1. +""" +function b(t, p) + s = [0,0] + + for i in 1:length(p) + s += B(length(p)-1,i-1,t) * p[i] + end + + return s +end + +"Bernstein polynomial" +function B(n_p,i,t) + binomial(n_p,i)*t^i*(1-t)^(n_p-i) +end \ No newline at end of file diff --git a/fourier.jl b/fourier.jl new file mode 100644 index 0000000..911c984 --- /dev/null +++ b/fourier.jl @@ -0,0 +1,70 @@ +""" +Simpson's rule integration on function `f` with bounds `a` to `b` with `n` intervals. +""" +function integrate(f, a, b, n) + h = (b-a)/2n + x(k) = a + k*h + y(k) = f(x(k)) + + s = y(0) + y(2n) + for i in 1:2n-1 + i % 2 == 1 ? s += 4y(i) : s += 2y(i) + end + + I = h/3*s + return I +end + +""" +Calculate Fourier coefficient for term with frequency `n` with function `f`. +""" +function c(f, n) + g(t)=f(t)*exp(-2π*im*n*t) + integrate(g,0,1,500) +end + +""" +Calculate `n` coefficients for the terms in the fourier series approximation of a given function `f(t)` over domain [0,1]. + +Corresponding frequencies for the coefficients are 0, 1, -1, 2, -2, and so on. +""" +function calculateCoefficients(f, n) + coefficients = zeros(Complex{Float64}, n) + coefficients[1] = c(f, 0) + + Threads.@threads for i in 2:n + coefficients[i] = c(f, (i÷2)*(-1)^i) + end + + return coefficients +end + +""" +Calculate Fourier series at time `t` with `coefficients` as given by `calculateCoefficients`. +""" +function fourierSeries(t, coefficients) + z = coefficients[1] + n = length(coefficients) + + for i in 2:n + z += coefficients[i]*exp(2π*im*(i÷2)*(-1)^i*t) + end + + return z +end + +""" +Similar to `fourierSeries` but returns a vector of all the intermediate sums. For use in animations. +""" +function fourierArm(t, coefficients) + n = length(coefficients) + arm = zeros(Complex{Float64}, n) + z = 0 + + for i in 1:n + z += coefficients[i] * exp(2π*im*(i÷2)*(-1)^i*t) + arm[i] = z + end + + return arm +end \ No newline at end of file diff --git a/graphics/jerma.svg b/graphics/jerma.svg new file mode 100644 index 0000000..74c0f3c --- /dev/null +++ b/graphics/jerma.svg @@ -0,0 +1,46 @@ + + + + + + + + + + -- cgit v1.3