Skip to main content

luau_vm/libs/
math.rs

1use luau_common::flags;
2use web_time::{SystemTime, UNIX_EPOCH};
3
4use crate::handle::RawHandle;
5use crate::native::{NativeCallContext, NativeCallResult, NativeFunction};
6use crate::thread::{LUA_TNONE, Thread};
7use crate::types::LUA_TNIL;
8
9const LUAU_PI: f64 = core::f64::consts::PI;
10const RADIANS_PER_DEGREE: f64 = LUAU_PI / 180.0;
11const LUAU_E: f64 = core::f64::consts::E;
12#[allow(clippy::excessive_precision)] // Matches the source constant before f64 rounding.
13const LUAU_PHI: f64 = 1.61803398874989484820;
14const LUAU_SQRT2: f64 = core::f64::consts::SQRT_2;
15const LUAU_TAU: f64 = core::f64::consts::TAU;
16const PCG32_INC: u64 = 105;
17
18static MATH_LIB: [NativeFunction; 37] = [
19    NativeFunction {
20        name: "abs",
21        function: math_abs,
22    },
23    NativeFunction {
24        name: "acos",
25        function: math_acos,
26    },
27    NativeFunction {
28        name: "asin",
29        function: math_asin,
30    },
31    NativeFunction {
32        name: "atan2",
33        function: math_atan2,
34    },
35    NativeFunction {
36        name: "atan",
37        function: math_atan,
38    },
39    NativeFunction {
40        name: "ceil",
41        function: math_ceil,
42    },
43    NativeFunction {
44        name: "cosh",
45        function: math_cosh,
46    },
47    NativeFunction {
48        name: "cos",
49        function: math_cos,
50    },
51    NativeFunction {
52        name: "deg",
53        function: math_deg,
54    },
55    NativeFunction {
56        name: "exp",
57        function: math_exp,
58    },
59    NativeFunction {
60        name: "floor",
61        function: math_floor,
62    },
63    NativeFunction {
64        name: "fmod",
65        function: math_fmod,
66    },
67    NativeFunction {
68        name: "frexp",
69        function: math_frexp,
70    },
71    NativeFunction {
72        name: "ldexp",
73        function: math_ldexp,
74    },
75    NativeFunction {
76        name: "log10",
77        function: math_log10,
78    },
79    NativeFunction {
80        name: "log",
81        function: math_log,
82    },
83    NativeFunction {
84        name: "max",
85        function: math_max,
86    },
87    NativeFunction {
88        name: "min",
89        function: math_min,
90    },
91    NativeFunction {
92        name: "modf",
93        function: math_modf,
94    },
95    NativeFunction {
96        name: "pow",
97        function: math_pow,
98    },
99    NativeFunction {
100        name: "rad",
101        function: math_rad,
102    },
103    NativeFunction {
104        name: "random",
105        function: math_random,
106    },
107    NativeFunction {
108        name: "randomseed",
109        function: math_randomseed,
110    },
111    NativeFunction {
112        name: "sinh",
113        function: math_sinh,
114    },
115    NativeFunction {
116        name: "sin",
117        function: math_sin,
118    },
119    NativeFunction {
120        name: "sqrt",
121        function: math_sqrt,
122    },
123    NativeFunction {
124        name: "tanh",
125        function: math_tanh,
126    },
127    NativeFunction {
128        name: "tan",
129        function: math_tan,
130    },
131    NativeFunction {
132        name: "noise",
133        function: math_noise,
134    },
135    NativeFunction {
136        name: "clamp",
137        function: math_clamp,
138    },
139    NativeFunction {
140        name: "sign",
141        function: math_sign,
142    },
143    NativeFunction {
144        name: "round",
145        function: math_round,
146    },
147    NativeFunction {
148        name: "map",
149        function: math_map,
150    },
151    NativeFunction {
152        name: "lerp",
153        function: math_lerp,
154    },
155    NativeFunction {
156        name: "isnan",
157        function: math_isnan,
158    },
159    NativeFunction {
160        name: "isinf",
161        function: math_isinf,
162    },
163    NativeFunction {
164        name: "isfinite",
165        function: math_isfinite,
166    },
167];
168
169const PERLIN_HASH: [u8; 257] = [
170    151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69,
171    142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219,
172    203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175,
173    74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230,
174    220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76,
175    132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173,
176    186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206,
177    59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163,
178    70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232,
179    178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162,
180    241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204,
181    176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141,
182    128, 195, 78, 66, 215, 61, 156, 180, 151,
183];
184
185const PERLIN_GRAD: [[f32; 3]; 16] = [
186    [1.0, 1.0, 0.0],
187    [-1.0, 1.0, 0.0],
188    [1.0, -1.0, 0.0],
189    [-1.0, -1.0, 0.0],
190    [1.0, 0.0, 1.0],
191    [-1.0, 0.0, 1.0],
192    [1.0, 0.0, -1.0],
193    [-1.0, 0.0, -1.0],
194    [0.0, 1.0, 1.0],
195    [0.0, -1.0, 1.0],
196    [0.0, 1.0, -1.0],
197    [0.0, -1.0, -1.0],
198    [1.0, 1.0, 0.0],
199    [0.0, -1.0, 1.0],
200    [-1.0, 1.0, 0.0],
201    [0.0, -1.0, -1.0],
202];
203
204/// `pcg32_random`
205fn pcg32_random(state: &mut u64) -> u32 {
206    let old_state = *state;
207    *state = old_state
208        .wrapping_mul(6364136223846793005)
209        .wrapping_add(PCG32_INC | 1);
210    let xorshifted = (((old_state >> 18) ^ old_state) >> 27) as u32;
211    let rot = (old_state >> 59) as u32;
212    xorshifted.rotate_right(rot)
213}
214
215/// `pcg32_seed`
216fn pcg32_seed(state: &mut u64, seed: u64) {
217    *state = 0;
218    let _ = pcg32_random(state);
219    *state = state.wrapping_add(seed);
220    let _ = pcg32_random(state);
221}
222
223/// `perlin_fade`
224fn perlin_fade(t: f32) -> f32 {
225    t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
226}
227
228/// `perlin_lerp`
229fn perlin_lerp(t: f32, a: f32, b: f32) -> f32 {
230    a + t * (b - a)
231}
232
233/// `perlin_grad`
234fn perlin_grad(hash: i32, x: f32, y: f32, z: f32) -> f32 {
235    let grad = PERLIN_GRAD[(hash & 15) as usize];
236    grad[0] * x + grad[1] * y + grad[2] * z
237}
238
239/// `perlin`
240fn perlin(x: f32, y: f32, z: f32) -> f32 {
241    let x_floor = x.floor();
242    let y_floor = y.floor();
243    let z_floor = z.floor();
244
245    let xi = (x_floor as i32) & 255;
246    let yi = (y_floor as i32) & 255;
247    let zi = (z_floor as i32) & 255;
248
249    let xf = x - x_floor;
250    let yf = y - y_floor;
251    let zf = z - z_floor;
252
253    let u = perlin_fade(xf);
254    let v = perlin_fade(yf);
255    let w = perlin_fade(zf);
256
257    let p = &PERLIN_HASH;
258
259    let a = (p[xi as usize] as i32 + yi) & 255;
260    let aa = (p[a as usize] as i32 + zi) & 255;
261    let ab = (p[(a + 1) as usize] as i32 + zi) & 255;
262
263    let b = (p[(xi + 1) as usize] as i32 + yi) & 255;
264    let ba = (p[b as usize] as i32 + zi) & 255;
265    let bb = (p[(b + 1) as usize] as i32 + zi) & 255;
266
267    let la = perlin_lerp(
268        u,
269        perlin_grad(p[aa as usize] as i32, xf, yf, zf),
270        perlin_grad(p[ba as usize] as i32, xf - 1.0, yf, zf),
271    );
272    let lb = perlin_lerp(
273        u,
274        perlin_grad(p[ab as usize] as i32, xf, yf - 1.0, zf),
275        perlin_grad(p[bb as usize] as i32, xf - 1.0, yf - 1.0, zf),
276    );
277    let la1 = perlin_lerp(
278        u,
279        perlin_grad(p[(aa + 1) as usize] as i32, xf, yf, zf - 1.0),
280        perlin_grad(p[(ba + 1) as usize] as i32, xf - 1.0, yf, zf - 1.0),
281    );
282    let lb1 = perlin_lerp(
283        u,
284        perlin_grad(p[(ab + 1) as usize] as i32, xf, yf - 1.0, zf - 1.0),
285        perlin_grad(p[(bb + 1) as usize] as i32, xf - 1.0, yf - 1.0, zf - 1.0),
286    );
287
288    perlin_lerp(w, perlin_lerp(v, la, lb), perlin_lerp(v, la1, lb1))
289}
290
291fn modf_parts(value: f64) -> (f64, f64) {
292    let integer = value.trunc();
293    let fractional = value - integer;
294    (integer, fractional)
295}
296
297fn frexp_parts(value: f64) -> (f64, i32) {
298    if value == 0.0 || !value.is_finite() {
299        return (value, 0);
300    }
301
302    let bits = value.to_bits();
303    let sign = bits & (1u64 << 63);
304    let exponent = ((bits >> 52) & 0x7ff) as i32;
305    let mantissa = bits & ((1u64 << 52) - 1);
306
307    if exponent == 0 {
308        let (mantissa, exponent) = frexp_parts(value * ((1u64 << 53) as f64));
309        return (mantissa, exponent - 53);
310    }
311
312    let normalized = f64::from_bits(sign | (1022u64 << 52) | mantissa);
313    (normalized, exponent - 1022)
314}
315
316fn ldexp_value(value: f64, exponent: i32) -> f64 {
317    value * (2.0f64).powi(exponent)
318}
319
320/// `math_abs`
321fn math_abs(ctx: NativeCallContext) -> NativeCallResult {
322    ctx.push_number(ctx.arg(1).number()?.abs())?;
323    Ok(1)
324}
325
326/// `math_sin`
327fn math_sin(ctx: NativeCallContext) -> NativeCallResult {
328    ctx.push_number(ctx.arg(1).number()?.sin())?;
329    Ok(1)
330}
331
332/// `math_sinh`
333fn math_sinh(ctx: NativeCallContext) -> NativeCallResult {
334    ctx.push_number(ctx.arg(1).number()?.sinh())?;
335    Ok(1)
336}
337
338/// `math_cos`
339fn math_cos(ctx: NativeCallContext) -> NativeCallResult {
340    ctx.push_number(ctx.arg(1).number()?.cos())?;
341    Ok(1)
342}
343
344/// `math_cosh`
345fn math_cosh(ctx: NativeCallContext) -> NativeCallResult {
346    ctx.push_number(ctx.arg(1).number()?.cosh())?;
347    Ok(1)
348}
349
350/// `math_tan`
351fn math_tan(ctx: NativeCallContext) -> NativeCallResult {
352    ctx.push_number(ctx.arg(1).number()?.tan())?;
353    Ok(1)
354}
355
356/// `math_tanh`
357fn math_tanh(ctx: NativeCallContext) -> NativeCallResult {
358    ctx.push_number(ctx.arg(1).number()?.tanh())?;
359    Ok(1)
360}
361
362/// `math_asin`
363fn math_asin(ctx: NativeCallContext) -> NativeCallResult {
364    ctx.push_number(ctx.arg(1).number()?.asin())?;
365    Ok(1)
366}
367
368/// `math_acos`
369fn math_acos(ctx: NativeCallContext) -> NativeCallResult {
370    ctx.push_number(ctx.arg(1).number()?.acos())?;
371    Ok(1)
372}
373
374/// `math_atan`
375fn math_atan(ctx: NativeCallContext) -> NativeCallResult {
376    ctx.push_number(ctx.arg(1).number()?.atan())?;
377    Ok(1)
378}
379
380/// `math_atan2`
381fn math_atan2(ctx: NativeCallContext) -> NativeCallResult {
382    ctx.push_number(ctx.arg(1).number()?.atan2(ctx.arg(2).number()?))?;
383    Ok(1)
384}
385
386/// `math_ceil`
387fn math_ceil(ctx: NativeCallContext) -> NativeCallResult {
388    ctx.push_number(ctx.arg(1).number()?.ceil())?;
389    Ok(1)
390}
391
392/// `math_floor`
393fn math_floor(ctx: NativeCallContext) -> NativeCallResult {
394    ctx.push_number(ctx.arg(1).number()?.floor())?;
395    Ok(1)
396}
397
398/// `math_fmod`
399fn math_fmod(ctx: NativeCallContext) -> NativeCallResult {
400    ctx.push_number(ctx.arg(1).number()? % ctx.arg(2).number()?)?;
401    Ok(1)
402}
403
404/// `math_modf`
405fn math_modf(ctx: NativeCallContext) -> NativeCallResult {
406    let thread = ctx.raw_thread();
407    let (integer, fractional) = modf_parts(unsafe { thread.check_number(1)? });
408    unsafe {
409        thread.push_number(integer)?;
410        thread.push_number(fractional)?;
411    }
412    Ok(2)
413}
414
415/// `math_sqrt`
416fn math_sqrt(ctx: NativeCallContext) -> NativeCallResult {
417    ctx.push_number(ctx.arg(1).number()?.sqrt())?;
418    Ok(1)
419}
420
421/// `math_pow`
422fn math_pow(ctx: NativeCallContext) -> NativeCallResult {
423    ctx.push_number(ctx.arg(1).number()?.powf(ctx.arg(2).number()?))?;
424    Ok(1)
425}
426
427/// `math_log`
428fn math_log(ctx: NativeCallContext) -> NativeCallResult {
429    let thread = ctx.raw_thread();
430    let result = unsafe {
431        let x = thread.check_number(1)?;
432        if matches!(thread.type_of(2), LUA_TNONE | LUA_TNIL) {
433            x.ln()
434        } else {
435            let base = thread.check_number(2)?;
436            if base == 2.0 {
437                x.log2()
438            } else if base == 10.0 {
439                x.log10()
440            } else {
441                x.ln() / base.ln()
442            }
443        }
444    };
445
446    ctx.push_number(result)?;
447    Ok(1)
448}
449
450/// `math_log10`
451fn math_log10(ctx: NativeCallContext) -> NativeCallResult {
452    ctx.push_number(ctx.arg(1).number()?.log10())?;
453    Ok(1)
454}
455
456/// `math_exp`
457fn math_exp(ctx: NativeCallContext) -> NativeCallResult {
458    ctx.push_number(ctx.arg(1).number()?.exp())?;
459    Ok(1)
460}
461
462/// `math_deg`
463fn math_deg(ctx: NativeCallContext) -> NativeCallResult {
464    ctx.push_number(ctx.arg(1).number()? / RADIANS_PER_DEGREE)?;
465    Ok(1)
466}
467
468/// `math_rad`
469fn math_rad(ctx: NativeCallContext) -> NativeCallResult {
470    ctx.push_number(ctx.arg(1).number()? * RADIANS_PER_DEGREE)?;
471    Ok(1)
472}
473
474/// `math_frexp`
475fn math_frexp(ctx: NativeCallContext) -> NativeCallResult {
476    let thread = ctx.raw_thread();
477    let (mantissa, exponent) = frexp_parts(unsafe { thread.check_number(1)? });
478    unsafe {
479        thread.push_number(mantissa)?;
480        thread.push_integer(exponent)?;
481    }
482    Ok(2)
483}
484
485/// `math_ldexp`
486fn math_ldexp(ctx: NativeCallContext) -> NativeCallResult {
487    ctx.push_number(ldexp_value(ctx.arg(1).number()?, ctx.arg(2).integer()?))?;
488    Ok(1)
489}
490
491/// `math_min`
492fn math_min(ctx: NativeCallContext) -> NativeCallResult {
493    let mut result = ctx.arg(1).number()?;
494    for argument in ctx.args().skip(1) {
495        let value = argument.number()?;
496        if value < result {
497            result = value;
498        }
499    }
500    ctx.push_number(result)?;
501    Ok(1)
502}
503
504/// `math_max`
505fn math_max(ctx: NativeCallContext) -> NativeCallResult {
506    let mut result = ctx.arg(1).number()?;
507    for argument in ctx.args().skip(1) {
508        let value = argument.number()?;
509        if value > result {
510            result = value;
511        }
512    }
513    ctx.push_number(result)?;
514    Ok(1)
515}
516
517/// `math_random`
518fn math_random(ctx: NativeCallContext) -> NativeCallResult {
519    let thread = ctx.raw_thread();
520    let global = unsafe { thread.global() };
521    let state = unsafe { &mut global.as_ptr().as_mut().unwrap_unchecked().rng_state };
522    unsafe {
523        match thread.get_top() {
524            0 => {
525                let low = pcg32_random(state);
526                let high = pcg32_random(state);
527                let result = ldexp_value((low as u64 | ((high as u64) << 32)) as f64, -64);
528                thread.push_number(result)?;
529            }
530            1 => {
531                let upper = thread.check_integer(1)?;
532                if upper < 1 {
533                    return thread
534                        .lua_arg_error(1, "interval is empty")
535                        .map_err(Into::into);
536                }
537
538                let x = upper as u64 * pcg32_random(state) as u64;
539                let result = 1 + (x >> 32) as i32;
540                thread.push_integer(result)?;
541            }
542            2 => {
543                let lower = thread.check_integer(1)?;
544                let upper = thread.check_integer(2)?;
545                if lower > upper {
546                    return thread
547                        .lua_arg_error(2, "interval is empty")
548                        .map_err(Into::into);
549                }
550
551                let interval = (upper as u32).wrapping_sub(lower as u32);
552                if interval == u32::MAX {
553                    return thread
554                        .lua_arg_error(2, "interval is too large")
555                        .map_err(Into::into);
556                }
557
558                let x = (interval as u64 + 1) * pcg32_random(state) as u64;
559                let result = lower.wrapping_add((x >> 32) as i32);
560                thread.push_integer(result)?;
561            }
562            _ => {
563                return crate::error!(thread, "wrong number of arguments").map_err(Into::into);
564            }
565        }
566    }
567    Ok(1)
568}
569
570/// `math_randomseed`
571fn math_randomseed(ctx: NativeCallContext) -> NativeCallResult {
572    let seed = ctx.arg(1).integer()? as u64;
573    let global = unsafe { ctx.raw_thread().global() };
574    pcg32_seed(
575        unsafe { &mut global.as_ptr().as_mut().unwrap_unchecked().rng_state },
576        seed,
577    );
578    Ok(0)
579}
580
581/// `math_noise`
582fn math_noise(ctx: NativeCallContext) -> NativeCallResult {
583    let mut x = ctx.arg(1).number()?;
584    let mut y = ctx.arg(2).number_or(0.0)?;
585    let mut z = ctx.arg(3).number_or(0.0)?;
586
587    if flags::FixMathNoisePrecision.get() {
588        x %= 256.0;
589        y %= 256.0;
590        z %= 256.0;
591    }
592
593    ctx.push_number(perlin(x as f32, y as f32, z as f32) as f64)?;
594    Ok(1)
595}
596
597/// `math_clamp`
598fn math_clamp(ctx: NativeCallContext) -> NativeCallResult {
599    let value = ctx.arg(1).number()?;
600    let min = ctx.arg(2).number()?;
601    let max = ctx.arg(3).number()?;
602    if min > max {
603        return ctx
604            .arg(3)
605            .error("max must be greater than or equal to min")
606            .map_err(Into::into);
607    }
608
609    ctx.push_number(value.clamp(min, max))?;
610    Ok(1)
611}
612
613/// `math_sign`
614fn math_sign(ctx: NativeCallContext) -> NativeCallResult {
615    let value = ctx.arg(1).number()?;
616    ctx.push_number(if value > 0.0 {
617        1.0
618    } else if value < 0.0 {
619        -1.0
620    } else {
621        0.0
622    })?;
623    Ok(1)
624}
625
626/// `math_round`
627fn math_round(ctx: NativeCallContext) -> NativeCallResult {
628    ctx.push_number(ctx.arg(1).number()?.round())?;
629    Ok(1)
630}
631
632/// `math_map`
633fn math_map(ctx: NativeCallContext) -> NativeCallResult {
634    let x = ctx.arg(1).number()?;
635    let in_min = ctx.arg(2).number()?;
636    let in_max = ctx.arg(3).number()?;
637    let out_min = ctx.arg(4).number()?;
638    let out_max = ctx.arg(5).number()?;
639
640    ctx.push_number(out_min + (x - in_min) * (out_max - out_min) / (in_max - in_min))?;
641    Ok(1)
642}
643
644/// `math_lerp`
645fn math_lerp(ctx: NativeCallContext) -> NativeCallResult {
646    let a = ctx.arg(1).number()?;
647    let b = ctx.arg(2).number()?;
648    let t = ctx.arg(3).number()?;
649    ctx.push_number(if t == 1.0 { b } else { a + (b - a) * t })?;
650    Ok(1)
651}
652
653/// `math_isnan`
654fn math_isnan(ctx: NativeCallContext) -> NativeCallResult {
655    ctx.push_boolean(ctx.arg(1).number()?.is_nan())?;
656    Ok(1)
657}
658
659/// `math_isinf`
660fn math_isinf(ctx: NativeCallContext) -> NativeCallResult {
661    ctx.push_boolean(ctx.arg(1).number()?.is_infinite())?;
662    Ok(1)
663}
664
665/// `math_isfinite`
666fn math_isfinite(ctx: NativeCallContext) -> NativeCallResult {
667    ctx.push_boolean(ctx.arg(1).number()?.is_finite())?;
668    Ok(1)
669}
670
671impl Thread {
672    /// `luaopen_math`
673    pub unsafe fn open_math(&self) -> NativeCallResult {
674        unsafe {
675            let mut seed = self.encode_pointer(self.as_ptr() as usize) as u64;
676            seed ^= SystemTime::now()
677                .duration_since(UNIX_EPOCH)
678                .map_or(0, |duration| duration.as_secs());
679            seed ^= crate::clock().to_bits();
680
681            pcg32_seed(
682                &mut self.global().as_ptr().as_mut().unwrap_unchecked().rng_state,
683                seed,
684            );
685
686            self.register(Some(super::LUA_MATHLIB_NAME), &MATH_LIB[..])?;
687            self.push_number(LUAU_PI)?;
688            self.raw_set_field(-2, "pi")?;
689            self.push_number(f64::INFINITY)?;
690            self.raw_set_field(-2, "huge")?;
691            self.push_number(f64::NAN)?;
692            self.raw_set_field(-2, "nan")?;
693            self.push_number(LUAU_E)?;
694            self.raw_set_field(-2, "e")?;
695            self.push_number(LUAU_PHI)?;
696            self.raw_set_field(-2, "phi")?;
697            self.push_number(LUAU_SQRT2)?;
698            self.raw_set_field(-2, "sqrt2")?;
699            self.push_number(LUAU_TAU)?;
700            self.raw_set_field(-2, "tau")?;
701            Ok(1)
702        }
703    }
704}