From c8fe7b7d36d6e44d60e5da52d0162ec02264bbab Mon Sep 17 00:00:00 2001 From: yaw-man Date: Sat, 14 Jan 2023 14:47:46 -0400 Subject: [PATCH] Collision impulses --- .gitignore | 3 ++- collision.lua | 4 ++++ main.lua | 30 ++++++++++++++++++++++++-- marble.lua | 42 +++++++++++++++++++++++++++++++++++- wave.lua | 59 ++++++++++++++++++++++++++++++++------------------- 5 files changed, 112 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index d163863..a521fe4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -build/ \ No newline at end of file +build/ +cover.png \ No newline at end of file diff --git a/collision.lua b/collision.lua index e701a8e..9c2b311 100644 --- a/collision.lua +++ b/collision.lua @@ -36,10 +36,13 @@ local function DetectCollision( curMarble, newMarble, curWave, newWave ) local fRadius, fTheta = tPolar( newMarble ) local iWaveRadius = curWave:Interpolate( iTheta ) local fWaveRadius = curWave:Interpolate( fTheta ) + + local speed = newMarble.dx * newMarble.dx + newMarble.dy * newMarble.dy --Case 1: marble is already outside the curve. if iRadius > iWaveRadius then return { + speed = speed, dt = newMarble.t - curMarble.t, t = curMarble.t, r = iWaveRadius, @@ -51,6 +54,7 @@ local function DetectCollision( curMarble, newMarble, curWave, newWave ) if fRadius > fWaveRadius then --This isn't right, fix later. return { + speed = speed, dt = 0, t = newMarble.t, r = fWaveRadius, diff --git a/main.lua b/main.lua index e08262f..9965a30 100644 --- a/main.lua +++ b/main.lua @@ -1,5 +1,5 @@ local love = love -local step = 1.0 / 120.0 +local step = 1.0 / 120 local sitelenpona local text local marble @@ -107,7 +107,8 @@ local function BeatScore( t ) --Safety: avoid a dbz. if mu < 0.001 or dt < 0.001 then - error( "DBZ! Beat length too small." ) + return 0.0 + --error( "DBZ! Beat length too small." ) end --Calculate beat score. @@ -171,6 +172,31 @@ function love.draw() love.graphics.push( "transform" ) love.graphics.applyTransform( transform ) wave.Draw() + + if debugRenderImpact then + + love.graphics.setLineWidth( 0.01 ) + love.graphics.setColor( 1, 0, 0, 0.5 ) --Red: Incoming + love.graphics.line( + debugRenderImpact.xi, + debugRenderImpact.yi, + debugRenderImpact.xf, + debugRenderImpact.yf) + love.graphics.setColor( 0, 1, 0, 0.5 ) --Green: Normal + love.graphics.line( + debugRenderImpact.xi, + debugRenderImpact.yi, + debugRenderImpact.xn, + debugRenderImpact.yn) + love.graphics.setColor( 0, 0, 1, 0.5 ) -- Blue: Outgoing + love.graphics.line( + debugRenderImpact.xi, + debugRenderImpact.yi, + debugRenderImpact.vxout, + debugRenderImpact.vyout) + + end + love.graphics.pop() sitelenpona.Draw( text.tok[state.currentBeat] ) diff --git a/marble.lua b/marble.lua index f1cda9e..06db986 100644 --- a/marble.lua +++ b/marble.lua @@ -26,7 +26,47 @@ end local function OnImpact( impact ) --Adjust current trajectory according to collision. if not impact.dt then return end - return Integrate( impact.dt ) --Hmm! Maybe this should be a fixed step instead for stability's sake. + + local x, y = impact.r * math.cos( impact.th ), impact.r * math.sin( impact.th ) + local vx, vy = newState.dx, newState.dy --Velocity of particle going into collision. + local unx, uny = math.cos( impact.normal ), math.sin( impact.normal ) --Outward-facing normal of wave. + local uvx, uvy --Unit vector velocity of particle. + local speed = math.sqrt( vx * vx + vy * vy ) + if speed < 0.01 then + --If the marble is motionless, there is no angular velocity wrt 0, + --so the wave is headed directly inward. + --We handle the collision as if the marble is headed directly outward. + uvx, uvy = unx, uny + vx, vy = uvx, uvy + else + uvx, uvy = vx / speed , vy / speed + end + + + + --Get signed angle between normal and incoming velocity (both unit vectors) + local dot = unx * uvy - uny * uvx + + --Fudge factor: apply an impulse inward so that you don't stick or slide on the wave. + local inward = ( dot > 0 ) and dot or -dot + inward = inward * inward * inward + + --Calculate the rotation matrix: + --counterclockwise rotation by 2 * pi - 2 * arccos( n dot v ) + local c, s = 1 - 2 * dot * dot, - 2 * dot * math.sqrt( 1 - dot * dot ) + --Apply: + local vxout, vyout = + inward * (- math.cos(impact.th) ) - (1.0 - inward) * ( vx * c - vy * s ), + inward * (- math.sin(impact.th) ) - (1.0 - inward) * ( x * s + vy * c ) + + curState.x, curState.y = x, y + curState.dx, curState.dy = 0.5 * vxout, 0.5 * vyout + + 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} + + return Integrate( math.max( impact.dt , 1 / 60 ) ) --Hmm! Maybe this should be a fixed step instead for stability's sake. end local function Update() diff --git a/wave.lua b/wave.lua index a499256..51349e6 100644 --- a/wave.lua +++ b/wave.lua @@ -1,15 +1,23 @@ --Render and simulate 1D wave equation. local love = love -local old, cur, new --States add beginning of last tick, current tick, and next tick respectively. +local N = 15 +local SOUNDSPEED = 0.5 +local IMPULSESIZE = 20 +local DAMPING = 0.01 -local N = 17 -local SOUNDSPEED = 2.0 + +local old, cur, new --States add beginning of last tick, current tick, and next tick respectively. local function Current() return cur end local function Next() return new end +local Integrate +local Interpolate +local Derivative +local SecondDerivative +local DFT + --Calculate discrete fourier transform of radius function. -local DFT do local twiddlec = {} local twiddles = {} @@ -43,7 +51,7 @@ do end --Minimal-oscillation interpolant of a real function from its discrete Fourier coefficients. -local Interpolate = function( wave, x ) +Interpolate = function( wave, x ) local re, im = wave.dftre, wave.dftim local y = re[1] @@ -61,7 +69,7 @@ local Interpolate = function( wave, x ) end --Derivative of the interpolation. -local Derivative = function( wave, x ) +Derivative = function( wave, x ) local re, im = wave.dftre, wave.dftim local y = 0 @@ -79,7 +87,7 @@ local Derivative = function( wave, x ) end --Second derivative of the interpolation. -local SecondDerivative = function( wave, x ) +SecondDerivative = function( wave, x ) local re, im = wave.dftre, wave.dftim local y = 0 @@ -103,13 +111,20 @@ local function AliasedSinc( theta, x ) end --Apply bandlimited impulse to wave. -local Impulse = function( wave, location, magnitude ) - local speed = wave.vrad +local function OnImpact( impact ) + local r = cur.radii + local theta = impact.th + local magnitude = IMPULSESIZE * impact.speed + local dt = math.max( impact.dt, 1 / 120.0 ) for i = 0, N - 1 do - speed[ i + 1 ] = speed[ i + 1 ] + magnitude * AliasedSinc( 2.0 * math.pi * i, location ) - end + r[ i + 1 ] = r[ i + 1 ] + dt * magnitude * AliasedSinc( theta, 2.0 * math.pi * i / N ) + end + --We've updated the positions, now we need to take a DFT + --in order to get the bandlimited second spatial derivative. + cur:DFT() + return Integrate( dt ) end local mt = { __index = { @@ -134,7 +149,7 @@ local function Wave( ) } for i = 1, N do - t.radii[i] = 1.0 + 0.05 * math.sin( i * 2.0 * math.pi / N ) + t.radii[i] = 1.0 + 0.05 * ( i/N + math.sin( i * 2.0 * math.pi / N )) t.vrad[i] = 0.0 end DFT( t ) @@ -166,11 +181,11 @@ local function Draw() th = ( i - 1 + k ) * 2.0 * math.pi / N local r = cur:Interpolate( th ) local x, y = r * math.cos( th ), r * math.sin( th ) - --love.graphics.circle( "fill", x, y, 0.01 ) - love.graphics.circle( "fill", th / math.pi - 1.0 , r, 0.01) + love.graphics.circle( "fill", x, y, 0.01 ) + --love.graphics.circle( "fill", th / math.pi - 1.0 , r, 0.01) --First derivative. - love.graphics.setColor( 0, 1.0, 0, 0.7 ) + --[[love.graphics.setColor( 0, 1.0, 0, 0.7 ) r = cur:Derivative( th ) x, y = r * math.cos( th ), r * math.sin( th ) --love.graphics.circle( "fill", x, y, 0.01 ) @@ -184,7 +199,7 @@ local function Draw() love.graphics.circle( "fill", th / math.pi - 1.0, r, 0.01) love.graphics.setColor( 1.0, 1.0, 1.0, 0.2 ) - love.graphics.circle( "fill", 2.0 * ( i + k ) / N - 1.2, 0, 0.02 ) + love.graphics.circle( "fill", 2.0 * ( i + k ) / N - 1.2, 0, 0.02 )]] end end @@ -195,9 +210,6 @@ local function Draw() love.graphics.circle( "fill", x, y, 0.02 ) end -local function OnImpact( impact ) - -end local function Update() @@ -216,12 +228,15 @@ local function Update() end end -local function Integrate( step ) +Integrate = function( step ) for i = 1, N do - --new.vrad[i] = local rxx = cur:SecondDerivative( math.pi * 2.0 * ( i - 1 ) / N ) - new.radii[i] = 2.0 * cur.radii[i] - old.radii[i] + step * step * SOUNDSPEED * rxx + 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 + new.radii[i] = r end new:DFT( )