your-own-drum/wave.lua

190 lines
4.0 KiB
Lua

--Render and simulate 1D wave equation.
local love = love
local step = assert( step )
local N = 25
--Calculate discrete fourier transform of radius function.
local DFT
do
local twiddlec = {}
local twiddles = {}
for i = 1, N do
twiddlec[i], twiddles[i] = math.cos( - 2.0 * ( i - 1 ) * math.pi / N ), math.sin( - 2.0 * (i - 1) * math.pi / N )
end
local function Twiddle( j )
j = 1 + j % N
return twiddlec[j], twiddles[j]
end
--Slow Discrete Fourier Transform of a real sequence.
DFT = function( wave )
local seq = wave.radii
local dre, dim = wave.dftre, wave.dftim
for k = 0, N - 1 do
local x, y = 0, 0 --Fourier coefficients in bin k.
for n = 0, N - 1 do
local cos, sin = Twiddle( n * k )
x = x + seq[n + 1] * cos
y = y + seq[n + 1] * sin
end
dre[k + 1], dim[k + 1] = x / N , y / N
end
end
end
--Minimal-oscillation interpolant of a real function from its discrete Fourier coefficients.
local Interpolate = function( wave, x )
local re, im = wave.dftre, wave.dftim
local y = re[1]
for k = 1, N / 2 do
local c, s = math.cos( x * k ), math.sin( x * k )
y = y
+ c * re[k + 1]
- s * im[k + 1]
+ c * re[N - k + 1]
+ s * im[N - k + 1]
end
return y
end
--Derivative of the interpolation.
local Derivative = function( wave, x )
local re, im = wave.dftre, wave.dftim
for k = 1, N / 2 do
local c, s = k * math.cos( x * k ), k * math.sin( x * k )
y = y
- c * im[k + 1]
- s * re[k + 1]
+ c * im[N - k + 1]
- s * re[N - k + 1]
end
return y
end
--Second derivative of the interpolation.
local SecondDerivative = function( wave, x )
local re, im = wave.dftre, wave.dftim
for k = 1, N / 2 do
local c, s = k * k * math.cos( x * k ), k * k * math.sin( x * k )
y = y
- c * re[k + 1]
+ s * im[k + 1]
- c * re[N - k + 1]
- s * im[N - k + 1]
end
return y
end
local mt = { __index = {
DFT = DFT,
Interpolate = Interpolate,
Derivative = Derivative,
SecondDerivative = SecondDerivative } }
local function Wave( )
local t = {
--radii[k] = radius of point on curve at angle (k - 1) / 13
radii = {},
--TIME derivative of radius
vrad = {},
--SPACE DFT of radius function (which is periodic)
dftre = {},
dftim = {},
}
for i = 1, N do
t.radii[i] = 1.0
t.vrad[i] = 0.0
end
DFT( t )
return setmetatable(t, mt)
end
local old = Wave() --State at beginning of tick.
local cur = Wave() --State at end of tick.
local function Draw()
-- Blue circle.
love.graphics.setColor( 91 / 255, 206 / 255, 250 / 255 )
love.graphics.circle("fill", 0, 0, 1)
local t = love.timer.getTime()
-- Debug dots.
for i = 1, N do
local th = ( i - 1 ) * 2.0 * math.pi / N
local cx, cy = cur.radii[i] * math.cos( th ), cur.radii[i] * math.sin( th )
love.graphics.setCanvas()
love.graphics.setColor( 0, 0, 0, 0.5 )
love.graphics.circle( "fill", cx, cy, 0.02 )
love.graphics.setColor( 1.0, 0, 0, 0.5 )
for k = 0.1, 1.0, 0.1 do
th = ( i - 1 + k ) * 2.0 * math.pi / N
r = cur:Interpolate( th )
x, y = r * math.cos( th ), r * math.sin( th )
love.graphics.circle( "fill", x, y, 0.01 )
end
end
love.graphics.setColor( 1, 1, 1, 0.5 )
local r = cur:Interpolate( t )
local x, y = r * math.cos( t ), r * math.sin( t )
love.graphics.circle( "fill", x, y, 0.02 )
end
local function Update()
--Deep copy of current state to old state.
for name, t in pairs( cur ) do
for i = 1, 13 do
old[name][i] = t[i]
end
end
local t = love.timer.getTime()
for i = 1, N do
cur.radii[i] = cur.radii[i] + old.vrad[i] * step
cur.vrad[i] = cur.vrad[i] - cur:SecondDerivative( ( i - 1 ) / N )
end
cur:DFT( )
end
local function DetectCollision( px, py, vpx, vpy )
local xi, xf = px + vpx * step, py + vpy * step
end
local function Reset()
old = Wave()
cur = Wave()
end
Reset()
return {
Reset = Reset,
Update = Update,
DetectCollision = DetectCollision,
Draw = Draw,
}