BIG big commit, tweak physics parameters. Fix beat detection bug.

Scale down particle sprite. Fix sitelen pona rendering, fix text rendering.
Invert white border feedback, more intuitive semantics.
&c. &c.
This commit is contained in:
yaw-man 2023-01-15 16:46:32 -04:00
parent c5407266eb
commit acffdea826
15 changed files with 338 additions and 250 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
build/ build/
screenshots/
cover.png cover.png

View File

@ -4,20 +4,26 @@ local failChimes = {}
local drones = {} local drones = {}
local N = 8 local N = 8
love.audio.setDistanceModel( "exponentclamped" )
local function StartDrones() local function StartDrones()
love.audio.setEffect( "droneReverb", { love.audio.setEffect( "droneReverb", {
type = "reverb", type = "reverb",
volume = 1.0, } volume = 1.0,
decaytime = 20}
) )
drones.bass = love.audio.newSource("sounds/drone.flac", "stream") drones.bass = love.audio.newSource("sounds/drone.flac", "stream")
drones.alto = love.audio.newSource("sounds/drone.flac", "stream") drones.alto = love.audio.newSource("sounds/drone.flac", "stream")
drones.bass:setVolume( 0 ) drones.bass:setVolume( 0 )
drones.bass:setPitch( 0.25 ) drones.bass:setPitch( 0.125 )
drones.alto:setVolume( 0 ) drones.alto:setVolume( 0 )
drones.bass:setPitch( 0.5 ) drones.bass:setPitch( 0.25 )
drones.bass:setEffect( "droneReverb", true ) 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.bass )
love.audio.play( drones.alto ) love.audio.play( drones.alto )
@ -72,6 +78,13 @@ local function OnImpact( impact, score, pass, level )
if not pass then if not pass then
--drones.bass:seek( level ) --drones.bass:seek( level )
-- drones.alto:seek( level ) -- drones.alto:seek( level )
else
love.audio.setEffect( "passChimeReverb", {
type = "reverb",
volume = 1.0,
gain = 0.1 + level / 200} )
end end
local th = impact.th local th = impact.th
@ -81,6 +94,7 @@ local function OnImpact( impact, score, pass, level )
local chime = pass and GetNextPassChime() or GetNextFailChime() local chime = pass and GetNextPassChime() or GetNextFailChime()
chime:setPitch( GetPitch( th ) ) chime:setPitch( GetPitch( th ) )
chime:setPosition( r * math.cos( th ), r * math.sin( 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)) local highgain = math.max( 0, math.min( score, 1.0 ) - (pass and 0.0 or 0.5))
chime:setFilter{ chime:setFilter{
type = "lowpass", type = "lowpass",
@ -92,11 +106,22 @@ local function OnImpact( impact, score, pass, level )
end 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 --score is the hypothetical "beat score" that would be attained
--if an impact took place at this instant. --if an impact took place at this instant.
local function Update( score ) local function Update( score, level )
drones.bass:setVolume( 0.5 * math.sqrt( score ) )
--drones.alto:setVolume( 0.3 * score ) if level > 2 then
drones.bass:setVolume( 0.5 * math.sqrt( score ) )
drones.alto:setVolume( 0.3 * score )
end
end end
local function Reset( ) local function Reset( )
@ -108,4 +133,4 @@ local function Reset( )
end end
Reset() Reset()
return { OnImpact = OnImpact, Update = Update, Reset = Reset} return { OnImpact = OnImpact, Update = Update, Reset = Reset, OnVictory = OnVictory}

146
main.lua
View File

@ -10,7 +10,10 @@ local recorder
local DetectCollision local DetectCollision
local OnImpact local OnImpact
local OnVictory local OnVictory
local BeatScore
local Draw
local ExtrapolateBeatScore
local _ExtrapolateBeatScore
local state local state
state = { state = {
@ -22,9 +25,9 @@ state = {
state.timeToSimulate = 0.0 state.timeToSimulate = 0.0
end, end,
finishTime = 0.0,
timeToSimulate = 0.0, timeToSimulate = 0.0,
isGameStarted = false, isGameStarted = false,
beatScoreThreshold = 1.0,
lastBeatScore = 0.0, lastBeatScore = 0.0,
currentBeat = 1, currentBeat = 1,
startTime = 0.0, startTime = 0.0,
@ -47,12 +50,51 @@ local function UpdateWindowTransform( w, h )
transform = tf transform = tf
end 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() function love.load()
UpdateWindowTransform( love.graphics.getDimensions() ) 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 do--particle system setup
particles = love.graphics.newParticleSystem( particles = love.graphics.newParticleSystem(
love.graphics.newImage( "prideflag.png" ), love.graphics.newImage( "prideflag.png" ),
@ -84,22 +126,23 @@ function love.load()
DetectCollision = assert ( require "collision" ) DetectCollision = assert ( require "collision" )
audio = assert( require "audio" ) audio = assert( require "audio" )
recorder = assert( require "recorder" ) recorder = assert( require "recorder" )
return state.Reset() return NewGame()
end end
local function ExtrapolateBeatScore( ) ExtrapolateBeatScore = function( )
local t = love.timer.getTime() local t = love.timer.getTime()
local beat = state.beat local beat = state.beat
if not beat.t then return 2.0 end if not beat.t then return 2.0 end
if not beat.mu then return 2.0 end if not beat.mu then return 2.0 end
if beat.mu < 0.001 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 return t * t * t * t
end end
_ExtrapolateBeatScore = ExtrapolateBeatScore
local function BeatScore( t ) BeatScore = function( t )
local beat = state.beat local beat = state.beat
--Base case 1: first tap. --Base case 1: first tap.
@ -117,73 +160,51 @@ local function BeatScore( t )
return 2.0 return 2.0
end 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 score = score * score * score * score
--General case: update average beat length. --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 local mu = ( 1.0 - WEIGHT ) * beat.mu + WEIGHT * dt
beat.mu = mu beat.mu = mu
else
score = 0
end end
return score return score
end 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() OnVictory = function()
local totalTime = love.timer.getTime() - state.startTime
OnImpact = function() end 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 end
function love.draw() Draw = function()
--[[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 )]]
local score = ExtrapolateBeatScore() local score = ExtrapolateBeatScore()
love.graphics.push( "transform" ) love.graphics.push( "transform" )
love.graphics.applyTransform( transform ) love.graphics.applyTransform( transform )
@ -219,10 +240,9 @@ function love.draw()
end]] end]]
love.graphics.pop() love.graphics.pop()
sitelenpona.Draw( text.words[state.currentBeat] )
sitelenpona.Draw( text.tok[state.currentBeat] )
marble.Draw() marble.Draw()
text.Draw( state.currentBeat )
end end
@ -231,7 +251,7 @@ end
function love.update( dt ) function love.update( dt )
audio.Update( ExtrapolateBeatScore() ) audio.Update( ExtrapolateBeatScore(), state.currentBeat )
particles:update( dt ) particles:update( dt )
dt = dt + state.timeToSimulate dt = dt + state.timeToSimulate
@ -269,4 +289,8 @@ end
function love.resize( w, h ) function love.resize( w, h )
UpdateWindowTransform( w, h ) UpdateWindowTransform( w, h )
if marble then marble.Resize() end if marble then marble.Resize() end
end
function love.mousepressed( x, y, button, istouch, presses )
return NewGame()
end end

View File

@ -1,5 +1,6 @@
--Character controller. Renders point particle. --Character controller. Renders point particle.
local love = love local love = love
local marble = {}
local oldBuffer = love.graphics.newCanvas() local oldBuffer = love.graphics.newCanvas()
local newBuffer = love.graphics.newCanvas() local newBuffer = love.graphics.newCanvas()
local oldState, curState, newState local oldState, curState, newState
@ -12,14 +13,14 @@ local function State( )
return { t = 0, x = 0, y = 0, dx = 0, dy = 0 } return { t = 0, x = 0, y = 0, dx = 0, dy = 0 }
end end
local function GetAcceleration( )
return ddx, ddy
end
local function Current() return curState end function marble.Current() return curState end
local function Next() return newState 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.t = love.timer.getTime()
newState.dx = (1.0 - FRICTION) * curState.dx + FRICTION * ddx newState.dx = (1.0 - FRICTION) * curState.dx + FRICTION * ddx
newState.dy = (1.0 - FRICTION) * curState.dy + FRICTION * ddy 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 newState.y = curState.y + newState.dy * step * MAXSPEED
end end
local function OnImpact( impact ) function marble.OnImpact( impact )
--Adjust current trajectory according to collision. --Adjust current trajectory according to collision.
if not impact.dt then return end if not impact.dt then return end
@ -66,14 +67,14 @@ local function OnImpact( impact )
curState.x, curState.y = x, y curState.x, curState.y = x, y
curState.dx, curState.dy = vxout, vyout 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, 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 end
local function Update() function marble.Update()
--Roll the log. --Roll the log.
for k, v in pairs( oldState ) do for k, v in pairs( oldState ) do
oldState[k] = curState[k] 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) 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) 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 ddx, ddy = ddx / n, ddy / n
end 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. --Extrapolate forward for slightly smoother rendering.
local dt = love.timer.getTime() - curState.t 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 xp, yp = transform:transformPoint( oldState.x, oldState.y )
local xc, yc = transform:transformPoint( curState.x, curState.y ) local xc, yc = transform:transformPoint( curState.x, curState.y )
@ -130,12 +155,14 @@ local function Draw()
end end
local function Impact( impact ) marble._Draw = marble.Draw
function marble.Impact( impact )
end end
--Window resize. --Window resize.
local function Resize() function marble.Resize()
newBuffer = love.graphics.newCanvas() newBuffer = love.graphics.newCanvas()
--TODO: render oldBuffer to new newBuffer, but scaled down. --TODO: render oldBuffer to new newBuffer, but scaled down.
oldBuffer = love.graphics.newCanvas() oldBuffer = love.graphics.newCanvas()
@ -143,25 +170,18 @@ local function Resize()
end end
local function Reset() function marble.Reset()
oldState, curState, newState = State(), State(), State() oldState, curState, newState = State(), State(), State()
Resize() marble.Resize()
marble.Draw = marble._Draw
end
function marble.Canvas()
return newBuffer
end end
Reset() marble.Reset()
return { return marble
Integrate = Integrate,
OnImpact = OnImpact,
Update = Update,
OnKey = OnKey,
Draw = Draw,
Impact = Impact,
Reset = Reset,
Resize = Resize,
Current = Current,
Next = Next,
GetAcceleration = GetAcceleration,
}

BIN
screenshots/a.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
screenshots/acrop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
screenshots/b.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
screenshots/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 990 B

BIN
screenshots/headboard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -6,12 +6,32 @@ local info = love.filesystem.getInfo( "sitelenpona" )
for _, filename in ipairs(love.filesystem.getDirectoryItems( "sitelenpona" )) do for _, filename in ipairs(love.filesystem.getDirectoryItems( "sitelenpona" )) do
sp[filename:gsub( ".png", "" )] = love.graphics.newImage( "sitelenpona/"..filename ) sp[filename:gsub( ".png", "" )] = love.graphics.newImage( "sitelenpona/"..filename )
end end
--Render one glyph in the center. --Render one or two glyphs in the center.
sp.Draw = function( str ) sp.Draw = function( t )
local w, h = love.graphics.getDimensions() local cx, cy = love.graphics.getDimensions()
local x, y = 0.5 * w - 128, 0.5 * h - 128 cx, cy = 0.5 * cx, 0.5 * cy
love.graphics.setColor( 1.0, 1.0, 1.0, 0.5 ) 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 end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 903 B

View File

@ -1,17 +1,72 @@
--Load poems. --Load poems.
local love = love 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" ) local mt = { __index = function() return "linja" end }
for _, filename in ipairs(love.filesystem.getDirectoryItems( "text" )) do
local s = love.filesystem.read( "text/"..filename ) local s = love.filesystem.read( "text/tok.txt" )
local t = {} local smallFont = love.graphics.setNewFont( "text/linja-sike.ttf", 12 )
local i = 1 local largeFont = love.graphics.setNewFont( "text/linja-sike.ttf", 32 )
for w in s:gmatch( "%a+") do
t[i] = w local i = 1
i = i + 1 --Split string into feet.
end for foot in s:gmatch( ".-%-") do
txt[filename:gsub( ".txt", "" )] = t
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 end
return txt --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 }

BIN
text/linja-sike.ttf Normal file

Binary file not shown.

View File

@ -1,120 +1,53 @@
a open -la mi -jan pi -toki-
akesi ala -li mu -taso -li ken-
ala ala -pana -toki -pilin-
alasa toki -mu li -wawa -wawa
ale nasa -la jan -ante -li ken-
anpa awen -pona -tawa -mi li-
ante awen -ala-sa e -pona.-
anu moku -mi li -tan jan -pona-
awen mama -mi li -pona -mute-
e mama -tu li -lon. tu -ona-
en o mi -taso. -wile -mute-
esun li lon -sije-lo mi -ike.-
ijo mute -la mi -pilin -nasa-
ike a mi -wile -musi -mute-
ilo wile -mi li -ni: ko -jelo-
insa ni o -kama -tomo -wawa-
jaki tomo -ni li -jo e -poki-
jan poki l-a jan -mi li -lawa-
jelo lawa -la jan -ni li -toki-
jo "tomo -mi li -pona -mute-
kala paka-la o -kama -ala-
kalama weka -o ken -ala -kama"-
kama wile -ala -mi la -ni li-
kasi kama: -jan li -paka-la e-
ken tomo -suli -pona -mi a-
kepeken ni li -ike -tawa -pilin-
kili jan li -ni tan -seme -ike-
kiwen mi ken -ala -sona -pona-
ko wile -toki -li lon -ala-
kon la mi -tawa -mama -kiwen-
kule
kulupu tenpo ni -mute li -kama li -weka la-
kute sijelo -ike li -ante a -mute
la pilin mi -ala li -awen kin -sama-
lape wile mi su-li en wi-le mi li-li li-
laso nasa lon -tenpo ni. -mi kama -suli-
lawa toki pi jan mute li kama ike. jan
len mute li wile e pakala taso
lete lili li wile e sona e nasin
li mute li wile e ni ala. nasa
lili mi toki ala lon poka pi jan ni
linja seme la mi wile alasa toki
lipu jan ni la pilin mi li nasa mute
loje jan ni la musi mi li musi ala
lon seme la mi o lon poka jan ike
luka jan ike ni li ken pona e ala
lukin mute la ona li wile e tawa
lupa ala la wile ona li lon pali
ma mute la mi awen lon insa lawa
mama lawa la mi toki mute e pilin
mani lon lawa ala la mi lukin mute
meli lukin li wile e musi e sona
mi sitelen la m ken moku ni mute
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

View File

@ -1,9 +1,9 @@
--Render and simulate 1D wave equation. --Render and simulate 1D wave equation.
local love = love local love = love
local N = 33 local N = 33
local SOUNDSPEED = 0.5 local SOUNDSPEED
local IMPULSESIZE = 5 local IMPULSESIZE
local DAMPING = 0.01 local DAMPING
local old, cur, new --States add beginning of last tick, current tick, and next tick respectively. 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; p.y = -p.y;
float r = r( atan(p.y, p.x) ) - length( p ); 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 return n / d
end end
--Apply bandlimited impulse to wave. --Apply bandlimited impulse to wave, adjust free parameters according to game state.
local function OnImpact( impact ) 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 r = cur.radii
local theta = impact.th local theta = impact.th
local magnitude = IMPULSESIZE * impact.speed local magnitude = IMPULSESIZE * impact.speed
@ -272,11 +279,14 @@ end
Integrate = function( step ) Integrate = function( step )
for i = 1, N do for i = 1, N do
local rxx = cur:SecondDerivative( math.pi * 2.0 * ( i - 1 ) / N ) 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 local r = ( 1.0 - DAMPING ) * ( 2.0 * cur.radii[i] - old.radii[i] + step * step * SOUNDSPEED * rxx ) --Verlet
+ DAMPING --Damping: oscillate toward 1. + 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 new.radii[i] = r
end end
@ -290,10 +300,10 @@ end
local function Reset() local function Reset()
SOUNDSPEED = 0.5 IMPULSESIZE = 1 / 10.0
IMPULSESIZE = 5 SOUNDSPEED = 5.0
DAMPING = 0.02 DAMPING = 0.1 / 1
old = Wave() old = Wave()
cur = Wave() cur = Wave()
new = Wave() new = Wave()