Reorganize; DFT; Use Transformation Matrices

This commit is contained in:
yaw-man 2023-01-13 23:46:39 -04:00
parent 79e9090c7e
commit da0ca76748
8 changed files with 277 additions and 22 deletions

9
collision.lua Normal file
View File

@ -0,0 +1,9 @@
local function DetectCollision(
xi, yi, vx, vy, xf, yf,
GetRadius,
GetRadialDerivative)
end
return DetectCollision

14
conf.lua Normal file
View File

@ -0,0 +1,14 @@
function love.conf( t )
t.modules.joystick = false
t.modules.physics = false
t.modules.touch = false
t.modules.video = false
t.window.title = "By Your Own Beat"
t.window.width = 600
t.window.height = 600
t.window.resizable = true
t.window.minwidth = 200
t.window.minheight = 200
end

View File

@ -4,13 +4,14 @@ local step = step
local sitelenpona local sitelenpona
local text local text
local marble local marble
local wave
local sounds = { local sounds = {
} }
local state local state
state = { state = {
@ -27,7 +28,7 @@ state = {
lastBeatScore = 0.0, lastBeatScore = 0.0,
currentBeat = 1, currentBeat = 1,
startTime = 0.0, startTime = 0.0,
wave = { wave = {
x = { 1.0, 0.0, -0.5, 0.2, 0.4, 0.8, 0.3, 0.9, -0.4, 0.8, 0.5, 0.1, -0.9 }, x = { 1.0, 0.0, -0.5, 0.2, 0.4, 0.8, 0.3, 0.9, -0.4, 0.8, 0.5, 0.1, -0.9 },
dx = { 1.0, 0.0, -0.5, 0.2, 0.4, 0.8, 0.3, 0.9, -0.4, 0.8, 0.5, 0.1, -0.9 }, dx = { 1.0, 0.0, -0.5, 0.2, 0.4, 0.8, 0.3, 0.9, -0.4, 0.8, 0.5, 0.1, -0.9 },
@ -52,14 +53,29 @@ state = {
}, },
} }
local function UpdateWindowTransform( w, h )
local d = math.min( w, h )
local r = love.graphics.get
local tf = love.math.newTransform()
local size = 3
tf:translate( w / 2, h / 2)
tf:scale( d / size, -d / size )
transform = tf
end
function love.load() function love.load()
UpdateWindowTransform( love.graphics.getDimensions() )
love.graphics.setBackgroundColor( 245 / 255, 169 / 255, 184 / 255 ) --Trans pink. love.graphics.setBackgroundColor( 245 / 255, 169 / 255, 184 / 255 ) --Trans pink.
--love.graphics.setBackgroundColor( 91 / 255, 206 / 255, 250 / 255 ) --Trans blue. --love.graphics.setBackgroundColor( 91 / 255, 206 / 255, 250 / 255 ) --Trans blue.
sounds.goodPing = love.audio.newSource("soundTest.ogg", "static") sounds.goodPing = love.audio.newSource("sounds/soundTest.ogg", "static")
sounds.badPing = love.audio.newSource("chime8.ogg", "static") sounds.badPing = love.audio.newSource("sounds/chime8.ogg", "static")
sitelenpona = assert( require "sitelenpona" ) sitelenpona = assert( require "sitelenpona" )
text = assert( require "text" ) text = assert( require "text" )
marble = assert( require "marble" ) marble = assert( require "marble" )
wave = assert( require "wave" )
return state.Reset() return state.Reset()
end end
@ -134,21 +150,26 @@ end
function love.draw() function love.draw()
local w, h = love.graphics.getDimensions()
love.graphics.setColor(1.0, 1.0, 1.0) love.graphics.setColor(1.0, 1.0, 1.0)
love.graphics.print( state.beat.mu or 0, 0) love.graphics.print( state.beat.mu or 0, 0)
love.graphics.print( state.beat.t or 0, 0, 10) love.graphics.print( state.beat.t or 0, 0, 10)
love.graphics.print( state.beatScoreThreshold, 0, 20) love.graphics.print( state.beatScoreThreshold, 0, 20)
love.graphics.print( state.lastBeatScore, 0, 30 ) love.graphics.print( state.lastBeatScore, 0, 30 )
love.graphics.setColor( 91 / 255, 206 / 255, 250 / 255 )
love.graphics.circle("fill", w / 2, h / 2, 200)
sitelenpona.Draw( text.tok[state.currentBeat] )
love.graphics.push( "transform" )
love.graphics.applyTransform( transform )
wave.Draw()
love.graphics.pop()
sitelenpona.Draw( text.tok[state.currentBeat] )
marble.Draw() marble.Draw()
end end
@ -157,8 +178,10 @@ function love.update( dt )
dt = dt + state.timeToSimulate dt = dt + state.timeToSimulate
while dt > step do while dt > step do
marble.Update() marble.Update()
wave.Update()
dt = dt - step dt = dt - step
end end
state.timeToSimulate = dt
end end
function love.keypressed( key, code, isRepeat ) function love.keypressed( key, code, isRepeat )
@ -169,4 +192,9 @@ end
function love.keyreleased( key, code ) function love.keyreleased( key, code )
return marble.OnKey() return marble.OnKey()
end
function love.resize( w, h )
UpdateWindowTransform( w, h )
if marble then marble.Resize() end
end end

View File

@ -1,11 +1,12 @@
--Character controller. Renders point particle. --Character controller. Renders point particle.
local love = love local love = love
--local wave = assert( require "wave" )
local oldBuffer = love.graphics.newCanvas() local oldBuffer = love.graphics.newCanvas()
local newBuffer = love.graphics.newCanvas() local newBuffer = love.graphics.newCanvas()
local step = assert( step ) local step = assert( step )
local state local state
local FRICTION = 0.008 local FRICTION = 0.01
local MAXSPEED = 1.0 local MAXSPEED = 5.0
local t, x, y, dx, dy, ddx, ddy local t, x, y, dx, dy, ddx, ddy
@ -16,11 +17,14 @@ local function Update()
dy = (1.0 - FRICTION) * dy + FRICTION * ddy dy = (1.0 - FRICTION) * dy + FRICTION * ddy
local xf = x + dx * step * MAXSPEED local xf = x + dx * step * MAXSPEED
local yf = y + dy * step * MAXSPEED local yf = y + dy * step * MAXSPEED
x = xf
y = yf
end end
local function OnKey() local function OnKey()
ddx = (love.keyboard.isScancodeDown( "d" ) and 1.0 or 0.0) - (love.keyboard.isScancodeDown( "a" ) and 1.0 or 0.0) ddx = (love.keyboard.isScancodeDown( "d" ) and 1.0 or 0.0) - (love.keyboard.isScancodeDown( "a" ) and 1.0 or 0.0)
ddy = (love.keyboard.isScancodeDown( "s" ) and 1.0 or 0.0) - (love.keyboard.isScancodeDown( "w" ) and 1.0 or 0.0) ddy = (love.keyboard.isScancodeDown( "w" ) and 1.0 or 0.0) - (love.keyboard.isScancodeDown( "s" ) and 1.0 or 0.0)
local n = math.sqrt( ddx * ddx + ddy * ddy ) local n = math.sqrt( ddx * ddx + ddy * ddy )
if n < 0.001 then return end if n < 0.001 then return end
@ -32,18 +36,22 @@ local function Draw()
local xf, yf = x + dt * dx, y + dt * dy local xf, yf = x + dt * dx, y + dt * dy
local w, h = love.graphics.getDimensions() local w, h = love.graphics.getDimensions()
local xi, yi = 1000.0 * x + 0.5 * w, 1000.0 * y + 0.5 * h local xi, yi = transform:transformPoint( x, y )
xf, yf = 1000.0 * xf + 0.5 * w, 1000.0 * yf + 0.5 * h xf, yf = transform:transformPoint( xf, yf )
love.graphics.setColor( 0, 0, 0, 1 )
love.graphics.print( x, 0, 60 )
love.graphics.print( y, 0, 80 )
love.graphics.setCanvas( newBuffer ) love.graphics.setCanvas( newBuffer )
love.graphics.setColor( 1.0, 1.0, 1.0 , 0.9 ) --Trans blue. love.graphics.setColor( 1.0, 1.0, 1.0 , 0.99 ) -- White.
love.graphics.draw( oldBuffer ) --Time-dependent fade: overlay canvas over itself. love.graphics.draw( oldBuffer ) --Time-dependent fade: overlay canvas over itself.
--Render latest segment in trail. --Render latest segment in trail.
love.graphics.setColor( 1.0, 1.0, 1.0, 1.0 ) --White? love.graphics.setColor( 245 / 255, 169 / 255, 184 / 255, 1.0 ) --Trans pink.
love.graphics.circle( "fill", xf, yf, 4.0 ) --TODO: replace with segments. love.graphics.circle( "fill", xf, yf, 4.0 ) --TODO: replace with segments.
love.graphics.line( xi, yi, xf, yf ) --love.graphics.line( xi, yi, xf, yf )
love.graphics.setCanvas( oldBuffer ) love.graphics.setCanvas( oldBuffer )
love.graphics.clear( 1.0, 1.0, 1.0, 0.0 ) love.graphics.clear( 1.0, 1.0, 1.0, 0.0 )
@ -51,8 +59,9 @@ local function Draw()
--Render circle directly to screen. --Render circle directly to screen.
love.graphics.setCanvas() love.graphics.setCanvas()
love.graphics.draw( newBuffer ) love.graphics.draw( newBuffer )
love.graphics.setColor( 245 / 255, 169 / 255, 184 / 255 ) --Trans pink.
love.graphics.circle( "fill", xf, yf, 5.0 ) love.graphics.circle( "fill", xf, yf, 5.0 )
love.graphics.setColor( 1, 1, 1, 1.0 ) --White.
love.graphics.circle( "line", xf, yf, 5.0 )
oldBuffer, newBuffer = newBuffer, oldBuffer oldBuffer, newBuffer = newBuffer, oldBuffer
@ -64,7 +73,11 @@ end
--Window resize. --Window resize.
local function Resize() local function Resize()
newBuffer = love.graphics.newCanvas()
--TODO: render oldBuffer to new newBuffer, but scaled down.
oldBuffer = love.graphics.newCanvas()
end end
local function Reset() local function Reset()
@ -87,4 +100,5 @@ return {
Draw = Draw, Draw = Draw,
Impact = Impact, Impact = Impact,
Reset = Reset, Reset = Reset,
Resize = Resize,
} }

View File

@ -10,7 +10,7 @@ end
sp.Draw = function( str ) sp.Draw = function( str )
local w, h = love.graphics.getDimensions() local w, h = love.graphics.getDimensions()
local x, y = 0.5 * w - 128, 0.5 * h - 128 local x, y = 0.5 * w - 128, 0.5 * h - 128
love.graphics.setColor( 1.0, 1.0, 1.0, 0.5 ) love.graphics.setColor( 1.0, 1.0, 1.0, 1.0 )
love.graphics.draw( sp[str] or sp.q, x, y ) love.graphics.draw( sp[str] or sp.q, x, y )
end end

190
wave.lua Normal file
View File

@ -0,0 +1,190 @@
--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,
}