-
Notifications
You must be signed in to change notification settings - Fork 30
/
mathx.lua
232 lines (192 loc) · 5.37 KB
/
mathx.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
--[[
extra mathematical functions
]]
local mathx = setmetatable({}, {
__index = math,
})
--wrap v around range [lo, hi)
function mathx.wrap(v, lo, hi)
return (v - lo) % (hi - lo) + lo
end
--wrap i around the indices of t
function mathx.wrap_index(i, t)
return math.floor(mathx.wrap(i, 1, #t + 1))
end
--clamp v to range [lo, hi]
function mathx.clamp(v, lo, hi)
return math.max(lo, math.min(v, hi))
end
--clamp v to range [0, 1]
function mathx.clamp01(v)
return mathx.clamp(v, 0, 1)
end
--round v to nearest whole, away from zero
function mathx.round(v)
if v < 0 then
return math.ceil(v - 0.5)
end
return math.floor(v + 0.5)
end
--round v to one-in x
-- (eg x = 2, v rounded to increments of 0.5)
function mathx.to_one_in(v, x)
return mathx.round(v * x) / x
end
--round v to a given decimal precision
function mathx.to_precision(v, decimal_points)
return mathx.to_one_in(v, math.pow(10, decimal_points))
end
--0, 1, -1 sign of a scalar
--todo: investigate if a branchless or `/abs` approach is faster in general case
function mathx.sign(v)
if v < 0 then return -1 end
if v > 0 then return 1 end
return 0
end
--linear interpolation between a and b
function mathx.lerp(a, b, t)
return a * (1.0 - t) + b * t
end
--linear interpolation with a minimum "final step" distance
--useful for making sure dynamic lerps do actually reach their final destination
function mathx.lerp_eps(a, b, t, eps)
local v = mathx.lerp(a, b, t)
if math.abs(v - b) < eps then
v = b
end
return v
end
--bilinear interpolation between 4 samples
function mathx.bilerp(a, b, c, d, u, v)
return mathx.lerp(
mathx.lerp(a, b, u),
mathx.lerp(c, d, u),
v
)
end
--get the lerp factor on a range, inverse_lerp(6, 0, 10) == 0.6
function mathx.inverse_lerp(v, min, max)
return (v - min) / (max - min)
end
--remap a value from one range to another
function mathx.remap_range(v, in_min, in_max, out_min, out_max)
return mathx.lerp(out_min, out_max, mathx.inverse_lerp(v, in_min, in_max))
end
--remap a value from one range to another, staying within that range
function mathx.remap_range_clamped(v, in_min, in_max, out_min, out_max)
return mathx.lerp(out_min, out_max, mathx.clamp01(mathx.inverse_lerp(v, in_min, in_max)))
end
--easing curves
--(generally only "safe" for 0-1 range, see mathx.clamp01)
--no curve - can be used as a default to avoid needing a branch
function mathx.identity(f)
return f
end
--classic smoothstep
function mathx.smoothstep(f)
return f * f * (3 - 2 * f)
end
--classic smootherstep; zero 2nd order derivatives at 0 and 1
function mathx.smootherstep(f)
return f * f * f * (f * (f * 6 - 15) + 10)
end
--pingpong from 0 to 1 and back again
function mathx.pingpong(f)
return 1 - math.abs(1 - (f * 2) % 2)
end
--quadratic ease in
function mathx.ease_in(f)
return f * f
end
--quadratic ease out
function mathx.ease_out(f)
local oneminus = (1 - f)
return 1 - oneminus * oneminus
end
--quadratic ease in and out
--(a lot like smoothstep)
function mathx.ease_inout(f)
if f < 0.5 then
return f * f * 2
end
local oneminus = (1 - f)
return 1 - 2 * oneminus * oneminus
end
--branchless but imperfect quartic in/out
--either smooth or smootherstep are usually a better alternative
function mathx.ease_inout_branchless(f)
local halfsquared = f * f / 2
return halfsquared * (1 - halfsquared) * 4
end
--todo: more easings - back, bounce, elastic
--(internal; use a provided random generator object, or not)
local function _random(rng, ...)
if rng then return rng:random(...) end
if love then return love.math.random(...) end
return math.random(...)
end
--return a random sign
function mathx.random_sign(rng)
return _random(rng) < 0.5 and -1 or 1
end
--return a random value between two numbers (continuous)
function mathx.random_lerp(min, max, rng)
return mathx.lerp(min, max, _random(rng))
end
--nan checking
function mathx.isnan(v)
return v ~= v
end
--angle handling stuff
--superior constant handy for expressing things in turns
mathx.tau = math.pi * 2
--normalise angle onto the interval [-math.pi, math.pi)
--so each angle only has a single value representing it
function mathx.normalise_angle(a)
return mathx.wrap(a, -math.pi, math.pi)
end
--alias for americans
mathx.normalize_angle = mathx.normalise_angle
--get the normalised difference between two angles
function mathx.angle_difference(a, b)
a = mathx.normalise_angle(a)
b = mathx.normalise_angle(b)
return mathx.normalise_angle(b - a)
end
--mathx.lerp equivalent for angles
function mathx.lerp_angle(a, b, t)
local dif = mathx.angle_difference(a, b)
return mathx.normalise_angle(a + dif * t)
end
--mathx.lerp_eps equivalent for angles
function mathx.lerp_angle_eps(a, b, t, eps)
--short circuit to avoid having to wrap so many angles
if a == b then
return a
end
--same logic as lerp_eps
local v = mathx.lerp_angle(a, b, t)
if math.abs(mathx.angle_difference(v, b)) < eps then
v = b
end
return v
end
--geometric functions standalone/"unpacked" components and multi-return
--consider using vec2 if you need anything complex!
--rotate a point around the origin by an angle
function mathx.rotate(x, y, r)
local s = math.sin(r)
local c = math.cos(r)
return c * x - s * y, s * x + c * y
end
--get the length of a vector from the origin
function mathx.length(x, y)
return math.sqrt(x * x + y * y)
end
--get the distance between two points
function mathx.distance(x1, y1, x2, y2)
local dx = x1 - x2
local dy = y1 - y2
return mathx.length(dx, dy)
end
return mathx