From da0ca767483d99cbb691814ddc7fb5d6ea045198 Mon Sep 17 00:00:00 2001 From: yaw-man Date: Fri, 13 Jan 2023 23:46:39 -0400 Subject: [PATCH] Reorganize; DFT; Use Transformation Matrices --- collision.lua | 9 ++ conf.lua | 14 ++ main.lua | 50 +++++-- marble.lua | 34 +++-- sitelenpona.lua | 2 +- chime8.ogg => sounds/chime8.ogg | Bin soundTest.ogg => sounds/soundTest.ogg | Bin wave.lua | 190 ++++++++++++++++++++++++++ 8 files changed, 277 insertions(+), 22 deletions(-) create mode 100644 collision.lua create mode 100644 conf.lua rename chime8.ogg => sounds/chime8.ogg (100%) rename soundTest.ogg => sounds/soundTest.ogg (100%) create mode 100644 wave.lua diff --git a/collision.lua b/collision.lua new file mode 100644 index 0000000..5fdcbab --- /dev/null +++ b/collision.lua @@ -0,0 +1,9 @@ +local function DetectCollision( + xi, yi, vx, vy, xf, yf, + GetRadius, + GetRadialDerivative) + + +end + +return DetectCollision \ No newline at end of file diff --git a/conf.lua b/conf.lua new file mode 100644 index 0000000..5428891 --- /dev/null +++ b/conf.lua @@ -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 \ No newline at end of file diff --git a/main.lua b/main.lua index 0db9fb3..b201eed 100644 --- a/main.lua +++ b/main.lua @@ -4,13 +4,14 @@ local step = step local sitelenpona local text local marble +local wave local sounds = { - } + local state state = { @@ -27,7 +28,7 @@ state = { lastBeatScore = 0.0, currentBeat = 1, startTime = 0.0, - + 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 }, 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() + UpdateWindowTransform( love.graphics.getDimensions() ) + love.graphics.setBackgroundColor( 245 / 255, 169 / 255, 184 / 255 ) --Trans pink. --love.graphics.setBackgroundColor( 91 / 255, 206 / 255, 250 / 255 ) --Trans blue. - sounds.goodPing = love.audio.newSource("soundTest.ogg", "static") - sounds.badPing = love.audio.newSource("chime8.ogg", "static") + sounds.goodPing = love.audio.newSource("sounds/soundTest.ogg", "static") + sounds.badPing = love.audio.newSource("sounds/chime8.ogg", "static") sitelenpona = assert( require "sitelenpona" ) text = assert( require "text" ) marble = assert( require "marble" ) + wave = assert( require "wave" ) return state.Reset() end @@ -134,21 +150,26 @@ end function love.draw() - local w, h = love.graphics.getDimensions() - - + + love.graphics.setColor(1.0, 1.0, 1.0) love.graphics.print( state.beat.mu or 0, 0) love.graphics.print( state.beat.t or 0, 0, 10) love.graphics.print( state.beatScoreThreshold, 0, 20) 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() + end @@ -157,8 +178,10 @@ function love.update( dt ) dt = dt + state.timeToSimulate while dt > step do marble.Update() + wave.Update() dt = dt - step end + state.timeToSimulate = dt end function love.keypressed( key, code, isRepeat ) @@ -169,4 +192,9 @@ end function love.keyreleased( key, code ) return marble.OnKey() +end + +function love.resize( w, h ) + UpdateWindowTransform( w, h ) + if marble then marble.Resize() end end \ No newline at end of file diff --git a/marble.lua b/marble.lua index 03cdd70..36ceb72 100644 --- a/marble.lua +++ b/marble.lua @@ -1,11 +1,12 @@ --Character controller. Renders point particle. local love = love +--local wave = assert( require "wave" ) local oldBuffer = love.graphics.newCanvas() local newBuffer = love.graphics.newCanvas() local step = assert( step ) local state -local FRICTION = 0.008 -local MAXSPEED = 1.0 +local FRICTION = 0.01 +local MAXSPEED = 5.0 local t, x, y, dx, dy, ddx, ddy @@ -16,11 +17,14 @@ local function Update() dy = (1.0 - FRICTION) * dy + FRICTION * ddy local xf = x + dx * step * MAXSPEED local yf = y + dy * step * MAXSPEED + + x = xf + y = yf end local function OnKey() 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 ) if n < 0.001 then return end @@ -32,18 +36,22 @@ local function Draw() local xf, yf = x + dt * dx, y + dt * dy local w, h = love.graphics.getDimensions() - local xi, yi = 1000.0 * x + 0.5 * w, 1000.0 * y + 0.5 * h - xf, yf = 1000.0 * xf + 0.5 * w, 1000.0 * yf + 0.5 * h + local xi, yi = transform:transformPoint( x, y ) + 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.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. --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.line( xi, yi, xf, yf ) + --love.graphics.line( xi, yi, xf, yf ) love.graphics.setCanvas( oldBuffer ) love.graphics.clear( 1.0, 1.0, 1.0, 0.0 ) @@ -51,8 +59,9 @@ local function Draw() --Render circle directly to screen. love.graphics.setCanvas() 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.setColor( 1, 1, 1, 1.0 ) --White. + love.graphics.circle( "line", xf, yf, 5.0 ) oldBuffer, newBuffer = newBuffer, oldBuffer @@ -64,7 +73,11 @@ end --Window resize. local function Resize() + newBuffer = love.graphics.newCanvas() + --TODO: render oldBuffer to new newBuffer, but scaled down. + oldBuffer = love.graphics.newCanvas() + end local function Reset() @@ -87,4 +100,5 @@ return { Draw = Draw, Impact = Impact, Reset = Reset, + Resize = Resize, } \ No newline at end of file diff --git a/sitelenpona.lua b/sitelenpona.lua index 54b4bdd..859dca9 100644 --- a/sitelenpona.lua +++ b/sitelenpona.lua @@ -10,7 +10,7 @@ end sp.Draw = function( str ) local w, h = love.graphics.getDimensions() 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 ) end diff --git a/chime8.ogg b/sounds/chime8.ogg similarity index 100% rename from chime8.ogg rename to sounds/chime8.ogg diff --git a/soundTest.ogg b/sounds/soundTest.ogg similarity index 100% rename from soundTest.ogg rename to sounds/soundTest.ogg diff --git a/wave.lua b/wave.lua new file mode 100644 index 0000000..d8383ed --- /dev/null +++ b/wave.lua @@ -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, +} \ No newline at end of file