-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathmath.lua
353 lines (308 loc) · 12.2 KB
/
math.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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
-- To make things simpler, Lua’s math module is extended here
local function __clamp(value, min, max)
return value < min and min or value > max and max or value
end
---Takes value with even 0…1 distribution and remaps it to recreate a distribution
---similar to Gaussian’s one (with k≈0.52, a default value). Lower to make bell more
---compact, use a value above 1 to get some sort of inverse distibution.
---@param x number @Value to adjust.
---@param k number @Bell curvature parameter.
---@return number
function math.gaussianAdjustment(x, k)
-- https://jsfiddle.net/9g03fkxm/
k = k or 0.52
local i = 1 - math.abs(x * 2 - 1)
if i <= 0 then return x end
return math.lerp((1 - i ^ k) * math.sign(x - 0.5), x * 2 - 1, math.log(i) * k * 0.5) * 0.5 + 0.5
end
---Builds a list of points arranged in a square with poisson distribution.
---@param size integer @Number of points.
---@param tileMode boolean? @If set to `true`, resulting points would be tilable without breaking poisson distribution.
---@return vec2[]
function math.poissonSamplerSquare(size, tileMode)
size = math.floor(tonumber(size) or 0)
if size < 1 then return {} end
local arr = ffi.C.lj_poissonsampler_square(size, tileMode == true)
local result = {}
for i = 1, size do
result[i] = arr[i - 1]:clone()
end
return result
end
---Builds a list of points arranged in a circle with poisson distribution.
---@param size integer @Number of points.
---@return vec2[]
function math.poissonSamplerCircle(size)
size = math.floor(tonumber(size) or 0)
if size < 1 then return {} end
local arr = ffi.C.lj_poissonsampler_circle(size)
local result = {}
for i = 1, size do
result[i] = arr[i - 1]:clone()
end
return result
end
---Generates a random number in [0, INT32_MAX) range. Can be a good argument for `math.randomseed()`.
---@return integer
function math.randomKey()
return ffi.C.lj_random_seed()
end
---Generates random number based on a seed.
---@param seed integer|boolean|string @Seed.
---@return number @Random number from 0 to 1.
function math.seededRandom(seed)
if type(seed) == 'string' then
seed = ffi.C.lj_checksumXXH(seed)
elseif type(seed) ~= 'number' then
seed = seed and 1 or 0
end
return ffi.C.lj_seed_random(seed)
end
---Rounds number, leaves certain number of decimals.
---@param number number
---@param decimals number? @Default value: 0 (rounding to a whole number).
---@return integer
function math.round(number, decimals)
local c = 2^52 + 2^51
if decimals then
local scale = 10^decimals
return ((number * scale + c) - c) / scale
else
return (number + c) - c
end
end
--[[ …N functions are meant to work with numbers only, slightly faster ]]
---Clamps a number value between `min` and `max`.
---@param value number
---@param min number
---@param max number
---@return number
function math.clampN(value, min, max)
return value < min and min or value > max and max or value
end
---Clamps a number value between 0 and 1.
---@param value number
---@return number
function math.saturateN(value) return value < 0 and 0 or value > 1 and 1 or value end
--[[ …V functions are meant to work with vectors only, slightly faster ]]
---Clamps a copy of a vector between `min` and `max`. To avoid making copies, use `vec:clamp(min, max)`.
---@generic T
---@param value T
---@param min any
---@param max any
---@return T
function math.clampV(value, min, max) return value:clone():clamp(min, max) end
---Clamps a copy of a vector between 0 and 1. To avoid making copies, use `vec:saturate()`.
---@generic T
---@param value T
---@return T
function math.saturateV(value) return value:clone():saturate() end
---Clamps value between `min` and `max`, returning `min` if `x` is below `min` or `max` if `x` is above `max`. Universal version, so might be slower.
---Also, if given a vector or a color, would make a copy of it.
---@generic T
---@param x T
---@param min any
---@param max any
---@return T
function math.clamp(x, min, max)
if type(x) == 'number' then
return __clamp(x, min, max)
end
local bn = type(min) == 'number'
local bt = type(max) == 'number'
if bn and bt then
if vec3.isvec3(x) then
return vec3(__clamp(x.x, min, max), __clamp(x.y, min, max), __clamp(x.z, min, max))
end
if vec2.isvec2(x) then
return vec2(__clamp(x.x, min, max), __clamp(x.y, min, max))
end
if vec4.isvec4(x) then
return vec4(__clamp(x.x, min, max), __clamp(x.y, min, max), __clamp(x.z, min, max), __clamp(x.w, min, max))
end
if rgb.isrgb(x) then
return rgb(__clamp(x.r, min, max), __clamp(x.g, min, max), __clamp(x.b, min, max))
end
if rgbm.isrgbm(x) then
return rgbm(__clamp(x.r, min, max), __clamp(x.g, min, max), __clamp(x.b, min, max), __clamp(x.mult, min, max))
end
end
local b = bn and min or x:type().new(min)
local t = bt and max or x:type().new(max)
if vec3.isvec3(x) then
return vec3(__clamp(x.x, b.x, t.x), __clamp(x.y, b.y, t.y), __clamp(x.z, b.z, t.z))
end
if vec2.isvec2(x) then
return vec2(__clamp(x.x, b.x, t.x), __clamp(x.y, b.y, t.y))
end
if vec4.isvec4(x) then
return vec4(__clamp(x.x, b.x, t.x), __clamp(x.y, b.y, t.y), __clamp(x.z, b.z, t.z), __clamp(x.w, b.w, t.w))
end
if rgb.isrgb(x) then
return rgb(__clamp(x.r, b.r, t.r), __clamp(x.g, b.g, t.g), __clamp(x.b, b.b, t.b))
end
if rgbm.isrgbm(x) then
return rgbm(__clamp(x.r, b.r, t.r), __clamp(x.g, b.g, t.g), __clamp(x.b, b.b, t.b), __clamp(x.mult, b.mult, t.mult))
end
return __clamp(x, min, max)
end
---Clamps value between 0 and 1, returning 0 if `x` is below 0 or 1 if `x` is above 1. Universal version, so might be slower.
---Also, if given a vector or a color, would make a copy of it.
---@generic T
---@param x T
---@return T
function math.saturate(x) return math.clamp(x, 0, 1) end
---Returns a sing of a value, or 0 if value is 0.
---@param x number
---@return integer
function math.sign(x) if x > 0 then return 1 elseif x < 0 then return -1 else return 0 end end
---Linear interpolation between `x` and `y` using `mix` (x * (1 - mix) + y * mix).
---@generic T
---@param x T
---@param y T
---@param mix number
---@return T
function math.lerp(x, y, mix) return x * (1 - mix) + y * mix end
---Returns 0 if value is less than v0, returns 1 if it’s more than v1, linear interpolation in-between.
---@param value number
---@param min number
---@param max number
---@return number
function math.lerpInvSat(value, min, max) return math.saturate((value - min) / (max - min)) end
---Returns `newA` if `value` equals to `oldA`, `newB` if `value` is `oldB`, applies linear interpolation for other input values. Doesn’t apply clamping.
---@param value number
---@param oldA number
---@param oldB number
---@param newA number
---@param newB number
---@return number
function math.remap(value, oldA, oldB, newA, newB) return (value - oldA) / (oldB - oldA) * (newB - newA) + newA end
---Smoothstep operation. More about it in [wiki](https://en.wikipedia.org/wiki/Smoothstep).
---@param x number
---@return number
function math.smoothstep(x) return x * x * (3 - 2 * x) end
---Like a smoothstep operation, but even smoother.
---@param x number
---@return number
function math.smootherstep(x) return x * x * x * (x * (x * 6 - 15) + 10) end
---Creates a copy of a vector and normalizes it. Consider using a method `vec:normalize()` instead when you can change the original vector to save on performanceMeter.
---@generic T
---@param x T
---@return T
function math.normalize(x) return x:clone():normalize() end
---Creates a copy of a vector and runs a cross product on it. Consider avoiding making a copy with `vec:cross(otherVec)`.
---@param x vec3
---@return vec3
function math.cross(x, y) return x:clone():cross(y) end
---Calculates dot product of two vectors.
---@param x vec2|vec3|vec4
---@return number
function math.dot(x, y) return x:dot(y) end
---Calculates angle between vectors in radians.
---@param x vec2|vec3|vec4
---@return number @Radians.
function math.angle(x, y) return x:angle(y) end
---Calculates distance between vectors.
---@param x vec2|vec3|vec4
---@return number
function math.distance(x, y) return x:distance(y) end
---Calculates squared distance between vectors (slightly faster without taking a square root).
---@param x vec2|vec3|vec4
---@return number
function math.distanceSquared(x, y) return x:distanceSquared(y) end
---Creates a copy of a vector and projects it onto a different vector. Consider avoiding making a copy with `vec:project(otherVec)`.
---@generic T
---@param x T
---@return T
function math.project(x, y) return x:clone():project(y) end
function math.radians(x) return x * math.pi / 180 end
function math.degress(x) return x * 180 / math.pi end
---Checks if value is not-a-number.
---@param x number
---@return boolean
function math.isnan(x) return x ~= x end
---Checks if value is positive or negative infinity.
---@param x number
---@return boolean
function math.isinf(x) return x == math.huge or x == -math.huge end
---Checks if value is finite (not infinite or nan).
---@param x number
---@return boolean
function math.isfinite(x) x = tonumber(x) return x ~= nil and not math.isnan(x) and not math.isinf(x) end
---@type number
math.nan = 0/0
---@type number
math.tau = math.pi * 2
-- For compatibility:
---@deprecated Use math.isnan instead.
function math.isNaN(x) return x ~= x end
---@deprecated Use math.nan instead.
math.NaN = 0/0
-- Value used by applyLag, if called a lot with the same lag, might be better to cache it.
---@param lag number
---@param dt number
---@return number
function math.lagMult(lag, dt)
return math.saturateN((1.0 - lag) * dt * 60)
end
---Perlin noise for given input. Returns value within -1…1 range, or outside of it if `octaves` is above 1. If you’re using octaves, make sure `input`
---won’t overflow when being multiplied by two multiple times.
---@param input number|vec2|vec3
---@param octaves integer? @Pass number greater than 1 to generate octave noise instead (sum `octaves` noise functions together increasing input and multiplying amplitude by `persistence` each step). Default value: 1.
---@param persistence number? @Persistance for octave noise. Used only if `octaves` is above 1. Default value: 0.5.
---@return number
function math.perlin(input, octaves, persistence)
octaves = tonumber(octaves) or 1
persistence = tonumber(persistence) or 0.5
local inputNum = tonumber(input)
if inputNum then
return ffi.C.lj_perlin_1(inputNum, octaves, persistence)
elseif vec2.isvec2(input) then
return ffi.C.lj_perlin_2(input, octaves, persistence)
elseif vec3.isvec3(input) then
return ffi.C.lj_perlin_3(input, octaves, persistence)
end
error('number, 2D or 3D vector is required', 1)
end
---Roughly convert HDR value to LDR using conversion hints provided by current WeatherFX style. Doesn’t apply nothing like tonemapping or exposure
---correction, simply adjusts for a case where WeatherFX style uses small brightness multiplier or linear color space.
---
---Note: shaders have the same function called `convertHDR()`.
---@generic T: number|rgb|rgbm
---@param input T @Value to convert.
---@param toLDR boolean? @Pass `true` to do the reverse and convert LDR to HDR. Default value: `false`.
---@return T
function math.convertHDR(input, toLDR)
toLDR = toLDR == true
local inputNum = tonumber(input)
if inputNum then
return ffi.C.lj_convertHDRToLDR_inner(inputNum, toLDR)
elseif rgb.isrgb(input) then
return rgb(ffi.C.lj_convertHDRToLDR_inner(input.r, toLDR),
ffi.C.lj_convertHDRToLDR_inner(input.g, toLDR),
ffi.C.lj_convertHDRToLDR_inner(input.b, toLDR))
elseif rgbm.isrgbm(input) then
return rgbm(ffi.C.lj_convertHDRToLDR_inner(input.r, toLDR),
ffi.C.lj_convertHDRToLDR_inner(input.g, toLDR),
ffi.C.lj_convertHDRToLDR_inner(input.b, toLDR),
input.mult)
end
error('number, 2D or 3D vector is required', 1)
end
-- Simple smooth movement towards target value.
---@generic T : number|vec2|vec3|vec4
---@param value T
---@param target T
---@param lag number
---@param dt number
---@return T
function math.applyLag(value, target, lag, dt)
if lag <= 0 then return target end
if type(value) == 'number' then
return value + (target - value) * math.lagMult(lag, dt)
elseif type(value.scale) == 'function' then
return (target - value):scale(math.lagMult(lag, dt)):add(value)
else
error('Wrong type', 2)
end
end