Skip to main content

luau_vm/libs/
vector.rs

1use crate::native::{NativeCallContext, NativeCallResult, NativeFunction};
2use crate::thread::Thread;
3
4static VECTOR_LIB: [NativeFunction; 14] = [
5    NativeFunction {
6        name: "create",
7        function: vector_create,
8    },
9    NativeFunction {
10        name: "magnitude",
11        function: vector_magnitude,
12    },
13    NativeFunction {
14        name: "normalize",
15        function: vector_normalize,
16    },
17    NativeFunction {
18        name: "cross",
19        function: vector_cross,
20    },
21    NativeFunction {
22        name: "dot",
23        function: vector_dot,
24    },
25    NativeFunction {
26        name: "angle",
27        function: vector_angle,
28    },
29    NativeFunction {
30        name: "floor",
31        function: vector_floor,
32    },
33    NativeFunction {
34        name: "ceil",
35        function: vector_ceil,
36    },
37    NativeFunction {
38        name: "abs",
39        function: vector_abs,
40    },
41    NativeFunction {
42        name: "sign",
43        function: vector_sign,
44    },
45    NativeFunction {
46        name: "clamp",
47        function: vector_clamp,
48    },
49    NativeFunction {
50        name: "max",
51        function: vector_max,
52    },
53    NativeFunction {
54        name: "min",
55        function: vector_min,
56    },
57    NativeFunction {
58        name: "lerp",
59        function: vector_lerp,
60    },
61];
62
63/// `vector_create`
64fn vector_create(ctx: NativeCallContext) -> NativeCallResult {
65    let count = ctx.arg_count();
66    let x = ctx.arg(1).number()? as f32;
67    let y = ctx.arg(2).number()? as f32;
68    let z = if count >= 3 {
69        ctx.arg(3).number()? as f32
70    } else {
71        0.0
72    };
73
74    #[cfg(feature = "vector4")]
75    {
76        let w = if count >= 4 {
77            ctx.arg(4).number()? as f32
78        } else {
79            0.0
80        };
81        ctx.push_vector([x, y, z, w])?;
82        Ok(1)
83    }
84
85    #[cfg(not(feature = "vector4"))]
86    {
87        ctx.push_vector([x, y, z])?;
88        Ok(1)
89    }
90}
91
92/// `vector_magnitude`
93fn vector_magnitude(ctx: NativeCallContext) -> NativeCallResult {
94    let vector = ctx.arg(1).vector()?;
95    ctx.push_number(
96        vector
97            .iter()
98            .map(|component| component * component)
99            .sum::<f32>()
100            .sqrt() as f64,
101    )?;
102    Ok(1)
103}
104
105/// `vector_normalize`
106fn vector_normalize(ctx: NativeCallContext) -> NativeCallResult {
107    let mut vector = ctx.arg(1).vector()?;
108    let inv_sqrt = 1.0f32
109        / vector
110            .iter()
111            .map(|component| component * component)
112            .sum::<f32>()
113            .sqrt();
114    for component in &mut vector {
115        *component *= inv_sqrt;
116    }
117    ctx.push_vector(vector)?;
118    Ok(1)
119}
120
121/// `vector_cross`
122fn vector_cross(ctx: NativeCallContext) -> NativeCallResult {
123    let left = ctx.arg(1).vector()?;
124    let right = ctx.arg(2).vector()?;
125
126    #[cfg(feature = "vector4")]
127    {
128        ctx.push_vector([
129            left[1] * right[2] - left[2] * right[1],
130            left[2] * right[0] - left[0] * right[2],
131            left[0] * right[1] - left[1] * right[0],
132            0.0,
133        ])?;
134        Ok(1)
135    }
136
137    #[cfg(not(feature = "vector4"))]
138    {
139        ctx.push_vector([
140            left[1] * right[2] - left[2] * right[1],
141            left[2] * right[0] - left[0] * right[2],
142            left[0] * right[1] - left[1] * right[0],
143        ])?;
144        Ok(1)
145    }
146}
147
148/// `vector_dot`
149fn vector_dot(ctx: NativeCallContext) -> NativeCallResult {
150    let left = ctx.arg(1).vector()?;
151    let right = ctx.arg(2).vector()?;
152    ctx.push_number(
153        left.iter()
154            .zip(right.iter())
155            .map(|(left, right)| left * right)
156            .sum::<f32>() as f64,
157    )?;
158    Ok(1)
159}
160
161/// `vector_angle`
162fn vector_angle(ctx: NativeCallContext) -> NativeCallResult {
163    let thread = ctx.raw_thread();
164    let angle = unsafe {
165        let left = ctx.arg(1).vector()?;
166        let right = ctx.arg(2).vector()?;
167        let axis = thread.opt_vector(3)?;
168
169        let cross = [
170            left[1] * right[2] - left[2] * right[1],
171            left[2] * right[0] - left[0] * right[2],
172            left[0] * right[1] - left[1] * right[0],
173        ];
174        let sin = (cross[0] * cross[0] + cross[1] * cross[1] + cross[2] * cross[2]).sqrt();
175        let cos = left[0] * right[0] + left[1] * right[1] + left[2] * right[2];
176        let mut angle = (sin as f64).atan2(cos as f64);
177
178        if let Some(axis) = axis
179            && cross[0] * axis[0] + cross[1] * axis[1] + cross[2] * axis[2] < 0.0
180        {
181            angle = -angle;
182        }
183
184        angle
185    };
186    ctx.push_number(angle)?;
187    Ok(1)
188}
189
190/// `vector_floor`
191fn vector_floor(ctx: NativeCallContext) -> NativeCallResult {
192    let mut vector = ctx.arg(1).vector()?;
193    for component in &mut vector {
194        *component = component.floor();
195    }
196    ctx.push_vector(vector)?;
197    Ok(1)
198}
199
200/// `vector_ceil`
201fn vector_ceil(ctx: NativeCallContext) -> NativeCallResult {
202    let mut vector = ctx.arg(1).vector()?;
203    for component in &mut vector {
204        *component = component.ceil();
205    }
206    ctx.push_vector(vector)?;
207    Ok(1)
208}
209
210/// `vector_abs`
211fn vector_abs(ctx: NativeCallContext) -> NativeCallResult {
212    let mut vector = ctx.arg(1).vector()?;
213    for component in &mut vector {
214        *component = component.abs();
215    }
216    ctx.push_vector(vector)?;
217    Ok(1)
218}
219
220/// `vector_sign`
221fn vector_sign(ctx: NativeCallContext) -> NativeCallResult {
222    let mut vector = ctx.arg(1).vector()?;
223    for component in &mut vector {
224        *component = crate::number::sign_f(*component);
225    }
226    ctx.push_vector(vector)?;
227    Ok(1)
228}
229
230/// `vector_clamp`
231fn vector_clamp(ctx: NativeCallContext) -> NativeCallResult {
232    let vector = ctx.arg(1).vector()?;
233    let min = ctx.arg(2).vector()?;
234    let max = ctx.arg(3).vector()?;
235
236    if min[0] > max[0] {
237        return ctx
238            .arg(3)
239            .error("max.x must be greater than or equal to min.x")
240            .map_err(Into::into);
241    }
242    if min[1] > max[1] {
243        return ctx
244            .arg(3)
245            .error("max.y must be greater than or equal to min.y")
246            .map_err(Into::into);
247    }
248    if min[2] > max[2] {
249        return ctx
250            .arg(3)
251            .error("max.z must be greater than or equal to min.z")
252            .map_err(Into::into);
253    }
254
255    let mut out = vector;
256    for index in 0..crate::types::LUA_VECTOR_SIZE {
257        out[index] = crate::number::clamp_f(vector[index], min[index], max[index]);
258    }
259    ctx.push_vector(out)?;
260    Ok(1)
261}
262
263/// `vector_min`
264fn vector_min(ctx: NativeCallContext) -> NativeCallResult {
265    let mut result = ctx.arg(1).vector()?;
266    for argument in ctx.args().skip(1) {
267        let vector = argument.vector()?;
268        for component in 0..crate::types::LUA_VECTOR_SIZE {
269            if vector[component] < result[component] {
270                result[component] = vector[component];
271            }
272        }
273    }
274    ctx.push_vector(result)?;
275    Ok(1)
276}
277
278/// `vector_max`
279fn vector_max(ctx: NativeCallContext) -> NativeCallResult {
280    let mut result = ctx.arg(1).vector()?;
281    for argument in ctx.args().skip(1) {
282        let vector = argument.vector()?;
283        for component in 0..crate::types::LUA_VECTOR_SIZE {
284            if vector[component] > result[component] {
285                result[component] = vector[component];
286            }
287        }
288    }
289    ctx.push_vector(result)?;
290    Ok(1)
291}
292
293/// `vector_index`
294fn vector_index(ctx: NativeCallContext) -> NativeCallResult {
295    let thread = ctx.raw_thread();
296    unsafe {
297        let vector = ctx.arg(1).vector()?;
298        let name = ctx.arg(2).string()?;
299
300        if name.len() == 1 {
301            let component_index = ((name[0] | b' ') as i32) - ('x' as i32);
302
303            #[cfg(feature = "vector4")]
304            let component_index = if component_index == -1 {
305                3
306            } else {
307                component_index
308            };
309
310            if (0..crate::types::LUA_VECTOR_SIZE as i32).contains(&component_index) {
311                ctx.push_number(vector[component_index as usize] as f64)?;
312                return Ok(1);
313            }
314        }
315
316        crate::error!(thread, "attempt to index vector with '%s'", name).map_err(Into::into)
317    }
318}
319
320/// `vector_lerp`
321fn vector_lerp(ctx: NativeCallContext) -> NativeCallResult {
322    let left = ctx.arg(1).vector()?;
323    let right = ctx.arg(2).vector()?;
324    let t = ctx.arg(3).number()? as f32;
325
326    let mut out = left;
327    for index in 0..crate::types::LUA_VECTOR_SIZE {
328        out[index] = crate::number::lerp_f(left[index], right[index], t);
329    }
330    ctx.push_vector(out)?;
331    Ok(1)
332}
333
334/// `createmetatable`
335fn create_metatable(thread: &Thread) -> NativeCallResult {
336    unsafe { thread.create_table(0, 1)? };
337
338    #[cfg(feature = "vector4")]
339    unsafe {
340        thread.push_vector([0.0, 0.0, 0.0, 0.0])?;
341    }
342    #[cfg(not(feature = "vector4"))]
343    unsafe {
344        thread.push_vector([0.0, 0.0, 0.0])?;
345    }
346
347    unsafe {
348        thread.push_value(-2)?;
349        thread.set_metatable(-2)?;
350        thread.pop(1);
351        thread.push_native_closure_k(vector_index, None, 0, None)?;
352        thread.raw_set_field(-2, "__index")?;
353        thread.set_readonly(-1, 1);
354        thread.pop(1);
355    }
356    Ok(0)
357}
358
359impl Thread {
360    /// `luaopen_vector`
361    pub unsafe fn open_vector(&self) -> NativeCallResult {
362        unsafe {
363            self.register(Some(super::LUA_VECLIB_NAME), &VECTOR_LIB[..])?;
364
365            #[cfg(feature = "vector4")]
366            {
367                self.push_vector([0.0, 0.0, 0.0, 0.0])?;
368                self.raw_set_field(-2, "zero")?;
369                self.push_vector([1.0, 1.0, 1.0, 1.0])?;
370                self.raw_set_field(-2, "one")?;
371            }
372            #[cfg(not(feature = "vector4"))]
373            {
374                self.push_vector([0.0, 0.0, 0.0])?;
375                self.raw_set_field(-2, "zero")?;
376                self.push_vector([1.0, 1.0, 1.0])?;
377                self.raw_set_field(-2, "one")?;
378            }
379
380            create_metatable(self)?;
381            Ok(1)
382        }
383    }
384}