├── README.md └── Spring.lua /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fraktality/Spring/d71c4f558c2de47e7e3418af26b9bcbf194fefc6/README.md -------------------------------------------------------------------------------- /Spring.lua: -------------------------------------------------------------------------------- 1 | -- Spring solver 2 | -- Copyright 2023 Fractality 3 | -- github.com/Fraktality/Spring 4 | -- Distributed under the MIT license; see full notice at the end of this file. 5 | 6 | local Spring = {} do 7 | Spring.__index = Spring 8 | 9 | local pi = math.pi 10 | local exp = math.exp 11 | local sin = math.sin 12 | local cos = math.cos 13 | local sqrt = math.sqrt 14 | 15 | local EPS = 1e-4 16 | 17 | function Spring.new(dampingRatio: number, frequency: number, position) 18 | assert(type(dampingRatio) == "number", "Damping ratio must be a number") 19 | assert(type(frequency) == "number", "Frequency must be a number") 20 | assert(dampingRatio*frequency >= 0, "Spring does not converge") 21 | 22 | return setmetatable({ 23 | d = dampingRatio, 24 | f = frequency, 25 | g = position, 26 | p = position, 27 | v = position*0, -- Match the original vector type 28 | }, Spring) 29 | end 30 | 31 | function Spring:setGoal(newGoal) 32 | self.g = newGoal 33 | end 34 | 35 | function Spring:getPosition() 36 | return self.p 37 | end 38 | 39 | function Spring:getVelocity() 40 | return self.v 41 | end 42 | 43 | function Spring:step(dt: number) 44 | local d = self.d 45 | local f = self.f*2*pi 46 | local g = self.g 47 | local p0 = self.p 48 | local v0 = self.v 49 | 50 | local offset = p0 - g 51 | local decay = exp(-d*f*dt) 52 | 53 | local p1, v1 54 | 55 | if d == 1 then -- Critically damped 56 | p1 = (offset*(1 + f*dt) + v0*dt)*decay + g 57 | v1 = (v0*(1 - f*dt) - offset*(f*f*dt))*decay 58 | 59 | elseif d < 1 then -- Underdamped 60 | local c = sqrt(1 - d*d) 61 | 62 | local i = cos(f*c*dt) 63 | local j = sin(f*c*dt) 64 | 65 | -- Damping ratios approaching 1 can cause division by small numbers. 66 | -- To fix that, group terms around z=j/c and find an approximation for z. 67 | -- Start with the definition of z: 68 | -- z = sin(dt*f*c)/c 69 | -- Substitute a=dt*f: 70 | -- z = sin(a*c)/c 71 | -- Take the Maclaurin expansion of z with respect to c: 72 | -- z = a - (a^3*c^2)/6 + (a^5*c^4)/120 + O(c^6) 73 | -- z ≈ a - (a^3*c^2)/6 + (a^5*c^4)/120 74 | -- Rewrite in Horner form: 75 | -- z ≈ a + ((a*a)*(c*c)*(c*c)/20 - c*c)*(a*a*a)/6 76 | 77 | local z 78 | if c > EPS then 79 | z = j/c 80 | else 81 | local a = dt*f 82 | z = a + ((a*a)*(c*c)*(c*c)/20 - c*c)*(a*a*a)/6 83 | end 84 | 85 | -- Frequencies approaching 0 present a similar problem. 86 | -- We want an approximation for y as f approaches 0, where: 87 | -- y = sin(dt*f*c)/(f*c) 88 | -- Substitute b=dt*c: 89 | -- y = sin(b*c)/b 90 | -- Now reapply the process from z. 91 | 92 | local y 93 | if f*c > EPS then 94 | y = j/(f*c) 95 | else 96 | local b = f*c 97 | y = dt + ((dt*dt)*(b*b)*(b*b)/20 - b*b)*(dt*dt*dt)/6 98 | end 99 | 100 | p1 = (offset*(i + d*z) + v0*y)*decay + g 101 | v1 = (v0*(i - z*d) - offset*(z*f))*decay 102 | 103 | else -- Overdamped 104 | local c = sqrt(d*d - 1) 105 | 106 | local r1 = -f*(d + c) 107 | local r2 = -f*(d - c) 108 | 109 | local co2 = (v0 - offset*r1)/(2*f*c) 110 | local co1 = offset - co2 111 | 112 | local e1 = co1*exp(r1*dt) 113 | local e2 = co2*exp(r2*dt) 114 | 115 | p1 = e1 + e2 + g 116 | v1 = e1*r1 + e2*r2 117 | end 118 | 119 | self.p = p1 120 | self.v = v1 121 | 122 | return p1 123 | end 124 | end 125 | 126 | -- Copyright 2023 Fractality 127 | -- 128 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 129 | -- documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 130 | -- rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 131 | -- permit persons to whom the Software is furnished to do so, subject to the following conditions: 132 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 133 | -- Software. 134 | -- 135 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 136 | -- WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 137 | -- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 138 | -- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 139 | --------------------------------------------------------------------------------