diff --git a/.gitignore b/.gitignore index a521fe4..2f18c4d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build/ +screenshots/ cover.png \ No newline at end of file diff --git a/audio.lua b/audio.lua index 9866abf..7b62ea3 100644 --- a/audio.lua +++ b/audio.lua @@ -4,20 +4,26 @@ local failChimes = {} local drones = {} local N = 8 +love.audio.setDistanceModel( "exponentclamped" ) + local function StartDrones() love.audio.setEffect( "droneReverb", { type = "reverb", - volume = 1.0, } + volume = 1.0, + decaytime = 20} -) + ) drones.bass = love.audio.newSource("sounds/drone.flac", "stream") drones.alto = love.audio.newSource("sounds/drone.flac", "stream") drones.bass:setVolume( 0 ) - drones.bass:setPitch( 0.25 ) + drones.bass:setPitch( 0.125 ) drones.alto:setVolume( 0 ) - drones.bass:setPitch( 0.5 ) + drones.bass:setPitch( 0.25 ) drones.bass:setEffect( "droneReverb", true ) + drones.alto:setEffect( "droneReverb", true ) + drones.bass:stop() + drones.alto:stop() love.audio.play( drones.bass ) love.audio.play( drones.alto ) @@ -72,6 +78,13 @@ local function OnImpact( impact, score, pass, level ) if not pass then --drones.bass:seek( level ) -- drones.alto:seek( level ) + else + + love.audio.setEffect( "passChimeReverb", { + type = "reverb", + volume = 1.0, + gain = 0.1 + level / 200} ) + end local th = impact.th @@ -81,6 +94,7 @@ local function OnImpact( impact, score, pass, level ) local chime = pass and GetNextPassChime() or GetNextFailChime() chime:setPitch( GetPitch( th ) ) chime:setPosition( r * math.cos( th ), r * math.sin( th ) ) + chime:setVolume( 0.1 + level / 200 ) local highgain = math.max( 0, math.min( score, 1.0 ) - (pass and 0.0 or 0.5)) chime:setFilter{ type = "lowpass", @@ -92,11 +106,22 @@ local function OnImpact( impact, score, pass, level ) end +local function OnVictory( ) + love.audio.setEffect( "passChimeReverb", { + type = "reverb", + volume = 1.0, + decaytime = 20,} ) +end + --score is the hypothetical "beat score" that would be attained --if an impact took place at this instant. -local function Update( score ) - drones.bass:setVolume( 0.5 * math.sqrt( score ) ) - --drones.alto:setVolume( 0.3 * score ) +local function Update( score, level ) + + if level > 2 then + drones.bass:setVolume( 0.5 * math.sqrt( score ) ) + drones.alto:setVolume( 0.3 * score ) + end + end local function Reset( ) @@ -108,4 +133,4 @@ local function Reset( ) end Reset() -return { OnImpact = OnImpact, Update = Update, Reset = Reset} \ No newline at end of file +return { OnImpact = OnImpact, Update = Update, Reset = Reset, OnVictory = OnVictory} \ No newline at end of file diff --git a/main.lua b/main.lua index 5956b11..faf0997 100644 --- a/main.lua +++ b/main.lua @@ -10,7 +10,10 @@ local recorder local DetectCollision local OnImpact local OnVictory - +local BeatScore +local Draw +local ExtrapolateBeatScore +local _ExtrapolateBeatScore local state state = { @@ -22,9 +25,9 @@ state = { state.timeToSimulate = 0.0 end, + finishTime = 0.0, timeToSimulate = 0.0, isGameStarted = false, - beatScoreThreshold = 1.0, lastBeatScore = 0.0, currentBeat = 1, startTime = 0.0, @@ -47,12 +50,51 @@ local function UpdateWindowTransform( w, h ) transform = tf end +OnImpact = function( impact ) + if not impact then return end + local score = BeatScore( impact.t ) + --DEBUG + state.lastBeatScore = score + local pass = false + + if score > 1.0 then + + pass = true + state.currentBeat = state.currentBeat + 1 + if state.currentBeat >= 120 then + return OnVictory() + end + + end + + + local x, y = math.cos(impact.th), math.sin(impact.th) + particles:setPosition( impact.r * x, impact.r * y ) + particles:setEmissionArea( "normal", 0.1, 0.2, impact.th, true ) + particles:emit( 50 * score * score ) + + audio.OnImpact( impact, score, pass, state.currentBeat ) + marble.OnImpact( impact ) + wave.OnImpact( impact, state.currentBeat ) +end +local _OnImpact = OnImpact + +local function NewGame() + love.graphics.setBackgroundColor( 245 / 255, 169 / 255, 184 / 255 ) --Trans pink. + OnImpact = _OnImpact + ExtrapolateBeatScore = _ExtrapolateBeatScore + love.draw = Draw + particles:reset() + state.Reset() + marble.Reset() + wave.Reset() + text.Reset() +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. - + do--particle system setup particles = love.graphics.newParticleSystem( love.graphics.newImage( "prideflag.png" ), @@ -84,22 +126,23 @@ function love.load() DetectCollision = assert ( require "collision" ) audio = assert( require "audio" ) recorder = assert( require "recorder" ) - return state.Reset() + return NewGame() end -local function ExtrapolateBeatScore( ) +ExtrapolateBeatScore = function( ) local t = love.timer.getTime() local beat = state.beat if not beat.t then return 2.0 end if not beat.mu then return 2.0 end if beat.mu < 0.001 then return 2.0 end - t = 1.1 * math.sin( math.pi * ( t - beat.t ) / beat.mu ) + t = 1.1 * math.sin( 0.5 * math.pi * ( t - beat.t ) / beat.mu ) return t * t * t * t end +_ExtrapolateBeatScore = ExtrapolateBeatScore -local function BeatScore( t ) +BeatScore = function( t ) local beat = state.beat --Base case 1: first tap. @@ -117,73 +160,51 @@ local function BeatScore( t ) return 2.0 end - local score = 1.1 * math.sin( math.pi * dt / beat.mu ) + local score = 1.1 * math.sin( 0.5 * math.pi * dt / beat.mu ) score = score * score * score * score --General case: update average beat length. - if dt > 0.2 then + + --Debounce: max BPM 200 + if dt > 60.0 / 200.0 then - local WEIGHT = 0.85 --High number makes the last beat more significant. + local WEIGHT = 0.75 --High number makes the last beat more significant. local mu = ( 1.0 - WEIGHT ) * beat.mu + WEIGHT * dt beat.mu = mu + else + score = 0 end return score end -OnImpact = function( impact ) - if not impact then return end - local score = BeatScore( impact.t ) - --DEBUG - state.lastBeatScore = score - local pass = false - - if score > 1.0 then - - pass = true - state.currentBeat = state.currentBeat + 1 - if state.currentBeat >= 120 then - return OnVictory() - end - end - - - local x, y = math.cos(impact.th), math.sin(impact.th) - particles:setPosition( impact.r * x, impact.r * y ) - particles:setEmissionArea( "normal", 0.1, 0.2, impact.th, true ) - particles:emit( 50 * score * score ) - - audio.OnImpact( impact, score, pass, state.currentBeat ) - marble.OnImpact( impact ) - wave.OnImpact( impact ) -end -local _OnImpact = OnImpact - - -local function NewGame() - OnImpact = _OnImpact - state.Reset() - marble.Reset() - wave.Reset() -end - - OnVictory = function() + + local totalTime = love.timer.getTime() - state.startTime OnImpact = function() end + love.draw = function() + love.graphics.setColor( 1, 1, 1, 1 ) + + text.Draw( 120 ) + love.graphics.printf( + "your.time:\n"..totalTime, + 0, 0.5 * love.graphics.getHeight(), + love.graphics.getWidth(), + "center" + ) + + marble.Draw() + end + marble.OnVictory() + love.graphics.setBackgroundColor( 91 / 255, 206 / 255, 250 / 255 ) --Trans blue. end -function love.draw() - - - --[[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 )]] +Draw = function() local score = ExtrapolateBeatScore() + love.graphics.push( "transform" ) love.graphics.applyTransform( transform ) @@ -219,10 +240,9 @@ function love.draw() end]] love.graphics.pop() - - sitelenpona.Draw( text.tok[state.currentBeat] ) - + sitelenpona.Draw( text.words[state.currentBeat] ) marble.Draw() + text.Draw( state.currentBeat ) end @@ -231,7 +251,7 @@ end function love.update( dt ) - audio.Update( ExtrapolateBeatScore() ) + audio.Update( ExtrapolateBeatScore(), state.currentBeat ) particles:update( dt ) dt = dt + state.timeToSimulate @@ -269,4 +289,8 @@ end function love.resize( w, h ) UpdateWindowTransform( w, h ) if marble then marble.Resize() end +end + +function love.mousepressed( x, y, button, istouch, presses ) + return NewGame() end \ No newline at end of file diff --git a/marble.lua b/marble.lua index 595b4bf..8265834 100644 --- a/marble.lua +++ b/marble.lua @@ -1,5 +1,6 @@ --Character controller. Renders point particle. local love = love +local marble = {} local oldBuffer = love.graphics.newCanvas() local newBuffer = love.graphics.newCanvas() local oldState, curState, newState @@ -12,14 +13,14 @@ local function State( ) return { t = 0, x = 0, y = 0, dx = 0, dy = 0 } end -local function GetAcceleration( ) - return ddx, ddy -end -local function Current() return curState end -local function Next() return newState end +function marble.Current() return curState end -local function Integrate( step ) +function marble.Next() return newState end + +function marble.GetAcceleration( ) return ddx, ddy end + +function marble.Integrate( step ) newState.t = love.timer.getTime() newState.dx = (1.0 - FRICTION) * curState.dx + FRICTION * ddx newState.dy = (1.0 - FRICTION) * curState.dy + FRICTION * ddy @@ -27,7 +28,7 @@ local function Integrate( step ) newState.y = curState.y + newState.dy * step * MAXSPEED end -local function OnImpact( impact ) +function marble.OnImpact( impact ) --Adjust current trajectory according to collision. if not impact.dt then return end @@ -66,14 +67,14 @@ local function OnImpact( impact ) curState.x, curState.y = x, y curState.dx, curState.dy = vxout, vyout - debugRenderImpact = { xi = x, yi = y, xf = x - vx, yf = y - vy, + --[[debugRenderImpact = { xi = x, yi = y, xf = x - vx, yf = y - vy, xn = 0.2 * unx + x, yn = 0.2 * uny + y, - vxout = x + vxout, vyout = y + vyout} + vxout = x + vxout, vyout = y + vyout}]] - return Integrate( math.max( impact.dt , 1 / 120 ) ) --Hmm! Maybe this should be a fixed step instead for stability's sake. + return marble.Integrate( math.max( impact.dt , 1 / 120 ) ) --Hmm! Maybe this should be a fixed step instead for stability's sake. end -local function Update() +function marble.Update() --Roll the log. for k, v in pairs( oldState ) do oldState[k] = curState[k] @@ -83,7 +84,7 @@ end -local function OnKey() +function marble.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( "w" ) and 1.0 or 0.0) - (love.keyboard.isScancodeDown( "s" ) and 1.0 or 0.0) @@ -92,11 +93,35 @@ local function OnKey() ddx, ddy = ddx / n, ddy / n end -local function Draw() +function marble.OnVictory() + + marble.Draw = function() + + --Extrapolate forward for slightly smoother rendering. + local dt = love.timer.getTime() - curState.t + marble.Integrate( dt + 1 / 60.0 ) + + local xp, yp = transform:transformPoint( oldState.x, oldState.y ) + local xc, yc = transform:transformPoint( curState.x, curState.y ) + local xn, yn = transform:transformPoint( newState.x, newState.y ) + + love.graphics.setColor( 91 / 255, 206 / 255, 250 / 255, 1.0 ) --Trans blue. + love.graphics.setLineJoin( "bevel" ) + love.graphics.setLineStyle( "smooth" ) + love.graphics.setLineWidth( 8.0 ) + love.graphics.line( xp, yp, xc, yc, xn, yn) + love.graphics.circle( "fill", xn, yn, 4.0 ) + love.graphics.setCanvas() + + + end +end + +function marble.Draw() --Extrapolate forward for slightly smoother rendering. local dt = love.timer.getTime() - curState.t - Integrate( dt + 1 / 60.0 ) + marble.Integrate( dt + 1 / 60.0 ) local xp, yp = transform:transformPoint( oldState.x, oldState.y ) local xc, yc = transform:transformPoint( curState.x, curState.y ) @@ -130,12 +155,14 @@ local function Draw() end -local function Impact( impact ) +marble._Draw = marble.Draw + +function marble.Impact( impact ) end --Window resize. -local function Resize() +function marble.Resize() newBuffer = love.graphics.newCanvas() --TODO: render oldBuffer to new newBuffer, but scaled down. oldBuffer = love.graphics.newCanvas() @@ -143,25 +170,18 @@ local function Resize() end -local function Reset() +function marble.Reset() oldState, curState, newState = State(), State(), State() - Resize() + marble.Resize() + marble.Draw = marble._Draw +end + +function marble.Canvas() + return newBuffer end -Reset() +marble.Reset() -return { - Integrate = Integrate, - OnImpact = OnImpact, - Update = Update, - OnKey = OnKey, - Draw = Draw, - Impact = Impact, - Reset = Reset, - Resize = Resize, - Current = Current, - Next = Next, - GetAcceleration = GetAcceleration, -} \ No newline at end of file +return marble \ No newline at end of file diff --git a/screenshots/a.PNG b/screenshots/a.PNG new file mode 100644 index 0000000..9cd53d7 Binary files /dev/null and b/screenshots/a.PNG differ diff --git a/screenshots/acrop.png b/screenshots/acrop.png new file mode 100644 index 0000000..251d2a9 Binary files /dev/null and b/screenshots/acrop.png differ diff --git a/screenshots/b.PNG b/screenshots/b.PNG new file mode 100644 index 0000000..a381d7e Binary files /dev/null and b/screenshots/b.PNG differ diff --git a/screenshots/favicon.png b/screenshots/favicon.png new file mode 100644 index 0000000..fb4ed2e Binary files /dev/null and b/screenshots/favicon.png differ diff --git a/screenshots/headboard.png b/screenshots/headboard.png new file mode 100644 index 0000000..8cf3ffc Binary files /dev/null and b/screenshots/headboard.png differ diff --git a/sitelenpona.lua b/sitelenpona.lua index 54b4bdd..aeec7ec 100644 --- a/sitelenpona.lua +++ b/sitelenpona.lua @@ -6,12 +6,32 @@ local info = love.filesystem.getInfo( "sitelenpona" ) for _, filename in ipairs(love.filesystem.getDirectoryItems( "sitelenpona" )) do sp[filename:gsub( ".png", "" )] = love.graphics.newImage( "sitelenpona/"..filename ) end ---Render one glyph in the center. -sp.Draw = function( str ) - local w, h = love.graphics.getDimensions() - local x, y = 0.5 * w - 128, 0.5 * h - 128 +--Render one or two glyphs in the center. +sp.Draw = function( t ) + local cx, cy = love.graphics.getDimensions() + cx, cy = 0.5 * cx, 0.5 * cy love.graphics.setColor( 1.0, 1.0, 1.0, 0.5 ) - love.graphics.draw( sp[str] or sp.q, x, y ) + + if #t == 1 then + love.graphics.draw( sp[t[1]] or sp.pilin, cx - 128, cy - 128 ) + elseif #t == 2 then + love.graphics.draw( sp[t[1]] or sp.pilin, cx - 128, cy - 64, 0, 0.5, 0.5 ) + love.graphics.draw( sp[t[2]] or sp.pilin, cx, cy - 64, 0, 0.5, 0.5 ) + elseif #t == 3 then + love.graphics.draw( sp[t[1]] or sp.pilin, cx - 128, cy - 64, 0, 0.5, 0.5 ) + love.graphics.draw( sp[t[1]] or sp.pilin, cx - 128, cy - 64, 0, 0.5, 0.5 ) + love.graphics.draw( sp[t[2]] or sp.pilin, cx, cy - 64, 0, 0.5, 0.5 ) + end + --[[ w in str:gmatch( "%a+") do + t[i] = w + i = i + 1 + end + for _, str in ipairs( t ) do + local w, h = love.graphics.getDimensions() + local x, y = 0.5 * w - 128, 0.5 * h - 128 + + love.graphics.draw( sp[str] or sp.pilin, x, y ) + end]] end diff --git a/sitelenpona/q.png b/sitelenpona/q.png index 0733aca..2dcfe51 100644 Binary files a/sitelenpona/q.png and b/sitelenpona/q.png differ diff --git a/text.lua b/text.lua index 653f39c..557e6b9 100644 --- a/text.lua +++ b/text.lua @@ -1,17 +1,72 @@ --Load poems. local love = love -local txt = {} +local feet = {} +local feetNL = {} +local words = {} --Sequence of tables. +local poem = {[0] = ""} +local poemLines = {[0] = 0} -local info = love.filesystem.getInfo( "text" ) -for _, filename in ipairs(love.filesystem.getDirectoryItems( "text" )) do - local s = love.filesystem.read( "text/"..filename ) - local t = {} - local i = 1 - for w in s:gmatch( "%a+") do - t[i] = w - i = i + 1 - end - txt[filename:gsub( ".txt", "" )] = t +local mt = { __index = function() return "linja" end } + +local s = love.filesystem.read( "text/tok.txt" ) +local smallFont = love.graphics.setNewFont( "text/linja-sike.ttf", 12 ) +local largeFont = love.graphics.setNewFont( "text/linja-sike.ttf", 32 ) + +local i = 1 +--Split string into feet. +for foot in s:gmatch( ".-%-") do + + feet[i] = foot:gsub( "-", "" ):gsub( " $", "" ):gsub( "%c", "") --Remove hyphens, trailing spaces, newlines. + feetNL[i] = foot:gsub( "-", "" ):gsub(" ", "")--:gsub( "%.", " " ) --Remove hyphens, spaces, transform periods to spaces + + + poem[i] = poem[ i - 1 ]..feetNL[i] + poemLines[i] = poemLines[i - 1] + (feetNL[i]:match( "\n") and 1 or 0) + + + i = i + 1 end -return txt \ No newline at end of file +--Split string into words. +i = 1 +local isTe = true +for foot in s:gmatch( ".-%-" ) do + --Split foot into words. + local word = {} + local j = 1 + + for w in foot:gmatch( [["]] ) do + word[j] = isTe and "te" or "to" + isTe = not( isTe ) + j = j + 1 + end + + for w in foot:gmatch( "%a+" ) do + word[j] = w + j = j + 1 + end + words[i] = word + i = i + 1 +end + +local function Draw( beat ) + + local foot = feet[ beat ] + + + love.graphics.setColor(1.0, 1.0, 1.0, 1.0) + love.graphics.printf( + foot, + 0, (9/10) * love.graphics.getHeight(), + love.graphics.getWidth(), + "center") + + love.graphics.setColor(1.0, 1.0, 1.0, 0.5) + love.graphics.print( poem[beat], 0, (3 - poemLines[beat]) * 32 ) + + +end + +local Reset = function() end + +return { feet = feet, words = words, feetNL = feetNL, Reset = Reset, Draw = Draw } \ No newline at end of file diff --git a/text/linja-sike.ttf b/text/linja-sike.ttf new file mode 100644 index 0000000..439ae43 Binary files /dev/null and b/text/linja-sike.ttf differ diff --git a/text/tok.txt b/text/tok.txt index 768b0cf..1c25a6f 100644 --- a/text/tok.txt +++ b/text/tok.txt @@ -1,120 +1,53 @@ -a -akesi -ala -alasa -ale -anpa -ante -anu -awen -e -en -esun -ijo -ike -ilo -insa -jaki -jan -jelo -jo -kala -kalama -kama -kasi -ken -kepeken -kili -kiwen -ko -kon -kule -kulupu -kute -la -lape -laso -lawa -len -lete -li -lili -linja -lipu -loje -lon -luka -lukin -lupa -ma -mama -mani -meli -mi -mije -moku -moli -monsi -mu -mun -musi -mute -nanpa -nasa -nasin -nena -ni -nimi -noka -o -olin -ona -open -pakala -pali -palisa -pan -pana -pi -pilin -pimeja -pini -pipi -poka -poki -pona -pu -sama -seli -selo -seme -sewi -sijelo -sike -sin -sina -sinpin -sitelen -sona -soweli -suli -suno -supa -suwi -tan -taso -tawa -telo -tenpo -toki -tomo -tu -unpa -uta -utala -walo -wan -waso -wawa -weka -wile \ No newline at end of file +open -la mi -jan pi -toki- +ala -li mu -taso -li ken- +ala -pana -toki -pilin- +toki -mu li -wawa -wawa +nasa -la jan -ante -li ken- +awen -pona -tawa -mi li- +awen -ala-sa e -pona.- +moku -mi li -tan jan -pona- +mama -mi li -pona -mute- +mama -tu li -lon. tu -ona- +o mi -taso. -wile -mute- +li lon -sije-lo mi -ike.- +mute -la mi -pilin -nasa- +a mi -wile -musi -mute- +wile -mi li -ni: ko -jelo- +ni o -kama -tomo -wawa- +tomo -ni li -jo e -poki- +poki l-a jan -mi li -lawa- +lawa -la jan -ni li -toki- +"tomo -mi li -pona -mute- +paka-la o -kama -ala- +weka -o ken -ala -kama"- +wile -ala -mi la -ni li- +kama: -jan li -paka-la e- +tomo -suli -pona -mi a- +ni li -ike -tawa -pilin- +jan li -ni tan -seme -ike- +mi ken -ala -sona -pona- +wile -toki -li lon -ala- +la mi -tawa -mama -kiwen- + +tenpo ni -mute li -kama li -weka la- +sijelo -ike li -ante a -mute +pilin mi -ala li -awen kin -sama- +wile mi su-li en wi-le mi li-li li- +nasa lon -tenpo ni. -mi kama -suli- +toki pi jan mute li kama ike. jan +mute li wile e pakala taso +lili li wile e sona e nasin +mute li wile e ni ala. nasa +mi toki ala lon poka pi jan ni +seme la mi wile alasa toki +jan ni la pilin mi li nasa mute +jan ni la musi mi li musi ala +seme la mi o lon poka jan ike +jan ike ni li ken pona e ala +mute la ona li wile e tawa +ala la wile ona li lon pali +mute la mi awen lon insa lawa +lawa la mi toki mute e pilin +lon lawa ala la mi lukin mute +lukin li wile e musi e sona +sitelen la m ken moku ni mute \ No newline at end of file diff --git a/wave.lua b/wave.lua index 8e32c11..8a019f7 100644 --- a/wave.lua +++ b/wave.lua @@ -1,9 +1,9 @@ --Render and simulate 1D wave equation. local love = love local N = 33 -local SOUNDSPEED = 0.5 -local IMPULSESIZE = 5 -local DAMPING = 0.01 +local SOUNDSPEED +local IMPULSESIZE +local DAMPING local old, cur, new --States add beginning of last tick, current tick, and next tick respectively. @@ -47,8 +47,9 @@ local shader = love.graphics.newShader([[ p.y = -p.y; float r = r( atan(p.y, p.x) ) - length( p ); + float q = float( r < 0.01 ) * clamp( 1.0 - score, 0.0, 1.0 ) ; - return vec4( (1.0 + float(score > 1.0) * r * 0.1) * color.xyz, float(r > -0.01)) ; + return vec4( q + (1.0 + clamp( score, 0.0, 1.0 ) * r * r * 0.2) * color.xyz, float(r > 0.0)) ; } ]]) @@ -146,9 +147,15 @@ local function AliasedSinc( theta, x ) return n / d end ---Apply bandlimited impulse to wave. -local function OnImpact( impact ) +--Apply bandlimited impulse to wave, adjust free parameters according to game state. +local function OnImpact( impact, level ) + + IMPULSESIZE = 10.0 * math.sqrt( level / 120.0 ) + SOUNDSPEED = 50.0 - 35 * level / 120 + DAMPING = 0.03 * ( 1.0 - 0.4 * level / 120 ) + + --Apply bandlimited impulse. local r = cur.radii local theta = impact.th local magnitude = IMPULSESIZE * impact.speed @@ -272,11 +279,14 @@ end Integrate = function( step ) for i = 1, N do + local rxx = cur:SecondDerivative( math.pi * 2.0 * ( i - 1 ) / N ) + local r = ( 1.0 - DAMPING ) * ( 2.0 * cur.radii[i] - old.radii[i] + step * step * SOUNDSPEED * rxx ) --Verlet + DAMPING --Damping: oscillate toward 1. - if r > 1.5 then r = 1.5 end - if r < 0.5 then r = 0.5 end + + --Avoid explosions. + r = math.max( 0.5, math.min( r, 4.0 ) ) new.radii[i] = r end @@ -290,10 +300,10 @@ end local function Reset() - SOUNDSPEED = 0.5 - IMPULSESIZE = 5 - DAMPING = 0.02 - + IMPULSESIZE = 1 / 10.0 + SOUNDSPEED = 5.0 + DAMPING = 0.1 / 1 + old = Wave() cur = Wave() new = Wave()