rbx_types/
basic_types.rs

1use thiserror::Error;
2
3use crate::Error;
4
5/// Represents any Roblox EnumItem.
6///
7/// Roblox enums are not strongly typed, so the meaning of a value depends on
8/// where they're assigned.
9///
10/// A list of all enums and their values are available [on the Roblox Developer
11/// Hub](https://create.roblox.com/docs/reference/engine/enums).
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[cfg_attr(
14    feature = "serde",
15    derive(serde::Serialize, serde::Deserialize),
16    serde(transparent)
17)]
18pub struct Enum {
19    value: u32,
20}
21
22impl Enum {
23    pub fn from_u32(value: u32) -> Self {
24        Self { value }
25    }
26
27    pub fn to_u32(self) -> u32 {
28        self.value
29    }
30}
31
32/// Represents a specific Roblox EnumItem.
33///
34/// A list of all enums and their values are available [on the Roblox Developer
35/// Hub](https://create.roblox.com/docs/reference/engine/enums).
36#[derive(Debug, Clone, PartialEq, Eq)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize,))]
38pub struct EnumItem {
39    #[cfg_attr(feature = "serde", serde(rename = "type"))]
40    pub ty: String,
41    pub value: u32,
42}
43
44impl From<EnumItem> for Enum {
45    fn from(enum_item: EnumItem) -> Self {
46        Self {
47            value: enum_item.value,
48        }
49    }
50}
51
52/// The standard 2D vector type used in Roblox.
53///
54/// ## See Also
55/// * [`Vector2int16`][struct.Vector2int16.html]
56/// * [Vector2 on Roblox Developer Hub](https://developer.roblox.com/en-us/api-reference/datatype/Vector2)
57#[derive(Debug, Clone, Copy, PartialEq)]
58pub struct Vector2 {
59    pub x: f32,
60    pub y: f32,
61}
62
63impl Vector2 {
64    pub fn new(x: f32, y: f32) -> Self {
65        Self { x, y }
66    }
67}
68
69/// A version of [`Vector2`][Vector2] whose coordinates are signed 16-bit
70/// integers.
71///
72/// ## See Also
73/// * [`Vector2`][Vector2], which is used for most values.
74/// * [Vector2int16 on Roblox Developer Hub](https://developer.roblox.com/en-us/api-reference/datatype/Vector2int16)
75///
76/// [Vector2]: struct.Vector2.html
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub struct Vector2int16 {
79    pub x: i16,
80    pub y: i16,
81}
82
83impl Vector2int16 {
84    pub fn new(x: i16, y: i16) -> Self {
85        Self { x, y }
86    }
87}
88
89/// The standard 3D vector type used in Roblox.
90///
91/// ## See Also
92/// * [`Vector3int16`][struct.Vector3int16.html]
93/// * [Vector3 on Roblox Developer Hub](https://developer.roblox.com/en-us/api-reference/datatype/Vector3)
94#[derive(Debug, Clone, Copy, PartialEq)]
95pub struct Vector3 {
96    pub x: f32,
97    pub y: f32,
98    pub z: f32,
99}
100
101fn approx_unit_or_zero(value: f32) -> Option<i32> {
102    if value.abs() <= f32::EPSILON {
103        Some(0)
104    } else if value.abs() - 1.0 <= f32::EPSILON {
105        Some(1.0f32.copysign(value) as i32)
106    } else {
107        None
108    }
109}
110
111impl Vector3 {
112    pub fn new(x: f32, y: f32, z: f32) -> Self {
113        Self { x, y, z }
114    }
115
116    /// If the vector is a positive or negative basis vector, returns
117    /// its corresponding ID. Otherwise, returns None.
118    /// The mapping goes like this:
119    /// (1.0, 0.0, 0.0) -> 0
120    /// (0.0, 1.0, 0.0) -> 1
121    /// (0.0, 0.0, 1.0) -> 2
122    /// (-1.0, 0.0, 0.0) -> 3
123    /// (0.0, -1.0, 0.0) -> 4
124    /// (0.0, 0.0, -1.0) -> 5
125    // We accidentally did not follow this convention, but that's okay, it's not
126    // a huge deal and not something we can change now.
127    #[allow(clippy::wrong_self_convention)]
128    pub fn to_normal_id(&self) -> Option<u8> {
129        fn get_normal_id(position: u8, value: i32) -> Option<u8> {
130            match value {
131                1 => Some(position),
132                -1 => Some(position + 3),
133                _ => None,
134            }
135        }
136
137        let x = approx_unit_or_zero(self.x);
138        let y = approx_unit_or_zero(self.y);
139        let z = approx_unit_or_zero(self.z);
140
141        match (x, y, z) {
142            (Some(x), Some(0), Some(0)) => get_normal_id(0, x),
143            (Some(0), Some(y), Some(0)) => get_normal_id(1, y),
144            (Some(0), Some(0), Some(z)) => get_normal_id(2, z),
145            _ => None,
146        }
147    }
148}
149
150/// A version of [`Vector3`][Vector3] whose coordinates are signed 16-bit
151/// integers. `Vector3int16` is often used when working with Terrain.
152///
153/// ## See Also
154/// * [`Vector3`][Vector3], which is used for most values.
155/// * [Vector3int16 on Roblox Developer Hub](https://developer.roblox.com/en-us/api-reference/datatype/Vector3int16)
156///
157/// [Vector3]: struct.Vector3.html
158#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159pub struct Vector3int16 {
160    pub x: i16,
161    pub y: i16,
162    pub z: i16,
163}
164
165impl Vector3int16 {
166    pub fn new(x: i16, y: i16, z: i16) -> Self {
167        Self { x, y, z }
168    }
169}
170
171/// Represents a position and orientation in 3D space.
172///
173/// ## See Also
174/// * [CFrame on Roblox Developer Hub](https://developer.roblox.com/en-us/api-reference/datatype/CFrame)
175#[derive(Debug, Clone, Copy, PartialEq)]
176#[cfg_attr(
177    feature = "serde",
178    derive(serde::Serialize, serde::Deserialize),
179    serde(rename_all = "camelCase")
180)]
181pub struct CFrame {
182    pub position: Vector3,
183    pub orientation: Matrix3,
184}
185
186impl CFrame {
187    pub fn new(position: Vector3, orientation: Matrix3) -> Self {
188        Self {
189            position,
190            orientation,
191        }
192    }
193}
194
195/// Used to represent the `orientation` field of `CFrame` and not a standalone
196/// type in Roblox.
197#[derive(Debug, Clone, Copy, PartialEq)]
198pub struct Matrix3 {
199    pub x: Vector3,
200    pub y: Vector3,
201    pub z: Vector3,
202}
203
204#[derive(Debug, Error)]
205pub(crate) enum Matrix3Error {
206    #[error("invalid rotation ID: {id}")]
207    BadRotationId { id: u8 },
208}
209
210impl Matrix3 {
211    pub fn new(x: Vector3, y: Vector3, z: Vector3) -> Self {
212        Self { x, y, z }
213    }
214
215    pub fn identity() -> Self {
216        Self {
217            x: Vector3::new(1.0, 0.0, 0.0),
218            y: Vector3::new(0.0, 1.0, 0.0),
219            z: Vector3::new(0.0, 0.0, 1.0),
220        }
221    }
222
223    pub fn transpose(&self) -> Self {
224        Self {
225            x: Vector3::new(self.x.x, self.y.x, self.z.x),
226            y: Vector3::new(self.x.y, self.y.y, self.z.y),
227            z: Vector3::new(self.x.z, self.y.z, self.z.z),
228        }
229    }
230
231    pub fn to_basic_rotation_id(&self) -> Option<u8> {
232        let transpose = self.transpose();
233        let x_id = transpose.x.to_normal_id()?;
234        let y_id = transpose.y.to_normal_id()?;
235        let z_id = transpose.z.to_normal_id()?;
236        let basic_rotation_id = (6 * x_id) + y_id + 1;
237
238        // Because we don't enforce orthonormality, it's still possible at
239        // this point for the z row to differ from the basic rotation's z
240        // row. We check for this case to avoid altering the value.
241        if Matrix3::from_basic_rotation_id(basic_rotation_id)
242            .ok()?
243            .transpose()
244            .z
245            .to_normal_id()?
246            == z_id
247        {
248            Some(basic_rotation_id)
249        } else {
250            None
251        }
252    }
253
254    pub fn from_basic_rotation_id(id: u8) -> Result<Matrix3, Error> {
255        match id {
256            0x02 => Ok(Matrix3::identity()),
257            0x03 => Ok(Matrix3::new(
258                Vector3::new(1.0, 0.0, 0.0),
259                Vector3::new(0.0, 0.0, -1.0),
260                Vector3::new(0.0, 1.0, 0.0),
261            )),
262            0x05 => Ok(Matrix3::new(
263                Vector3::new(1.0, 0.0, 0.0),
264                Vector3::new(0.0, -1.0, 0.0),
265                Vector3::new(0.0, 0.0, -1.0),
266            )),
267            0x06 => Ok(Matrix3::new(
268                Vector3::new(1.0, 0.0, 0.0),
269                Vector3::new(0.0, 0.0, 1.0),
270                Vector3::new(0.0, -1.0, 0.0),
271            )),
272            0x07 => Ok(Matrix3::new(
273                Vector3::new(0.0, 1.0, 0.0),
274                Vector3::new(1.0, 0.0, 0.0),
275                Vector3::new(0.0, 0.0, -1.0),
276            )),
277            0x09 => Ok(Matrix3::new(
278                Vector3::new(0.0, 0.0, 1.0),
279                Vector3::new(1.0, 0.0, 0.0),
280                Vector3::new(0.0, 1.0, 0.0),
281            )),
282            0x0a => Ok(Matrix3::new(
283                Vector3::new(0.0, -1.0, 0.0),
284                Vector3::new(1.0, 0.0, 0.0),
285                Vector3::new(0.0, 0.0, 1.0),
286            )),
287            0x0c => Ok(Matrix3::new(
288                Vector3::new(0.0, 0.0, -1.0),
289                Vector3::new(1.0, 0.0, 0.0),
290                Vector3::new(0.0, -1.0, 0.0),
291            )),
292            0x0d => Ok(Matrix3::new(
293                Vector3::new(0.0, 1.0, 0.0),
294                Vector3::new(0.0, 0.0, 1.0),
295                Vector3::new(1.0, 0.0, 0.0),
296            )),
297            0x0e => Ok(Matrix3::new(
298                Vector3::new(0.0, 0.0, -1.0),
299                Vector3::new(0.0, 1.0, 0.0),
300                Vector3::new(1.0, 0.0, 0.0),
301            )),
302            0x10 => Ok(Matrix3::new(
303                Vector3::new(0.0, -1.0, 0.0),
304                Vector3::new(0.0, 0.0, -1.0),
305                Vector3::new(1.0, 0.0, 0.0),
306            )),
307            0x11 => Ok(Matrix3::new(
308                Vector3::new(0.0, 0.0, 1.0),
309                Vector3::new(0.0, -1.0, 0.0),
310                Vector3::new(1.0, 0.0, 0.0),
311            )),
312            0x14 => Ok(Matrix3::new(
313                Vector3::new(-1.0, 0.0, 0.0),
314                Vector3::new(0.0, 1.0, 0.0),
315                Vector3::new(0.0, 0.0, -1.0),
316            )),
317            0x15 => Ok(Matrix3::new(
318                Vector3::new(-1.0, 0.0, 0.0),
319                Vector3::new(0.0, 0.0, 1.0),
320                Vector3::new(0.0, 1.0, 0.0),
321            )),
322            0x17 => Ok(Matrix3::new(
323                Vector3::new(-1.0, 0.0, 0.0),
324                Vector3::new(0.0, -1.0, 0.0),
325                Vector3::new(0.0, 0.0, 1.0),
326            )),
327            0x18 => Ok(Matrix3::new(
328                Vector3::new(-1.0, 0.0, 0.0),
329                Vector3::new(0.0, 0.0, -1.0),
330                Vector3::new(0.0, -1.0, 0.0),
331            )),
332            0x19 => Ok(Matrix3::new(
333                Vector3::new(0.0, 1.0, 0.0),
334                Vector3::new(-1.0, 0.0, 0.0),
335                Vector3::new(0.0, 0.0, 1.0),
336            )),
337            0x1b => Ok(Matrix3::new(
338                Vector3::new(0.0, 0.0, -1.0),
339                Vector3::new(-1.0, 0.0, 0.0),
340                Vector3::new(0.0, 1.0, 0.0),
341            )),
342            0x1c => Ok(Matrix3::new(
343                Vector3::new(0.0, -1.0, 0.0),
344                Vector3::new(-1.0, 0.0, 0.0),
345                Vector3::new(0.0, 0.0, -1.0),
346            )),
347            0x1e => Ok(Matrix3::new(
348                Vector3::new(0.0, 0.0, 1.0),
349                Vector3::new(-1.0, 0.0, 0.0),
350                Vector3::new(0.0, -1.0, 0.0),
351            )),
352            0x1f => Ok(Matrix3::new(
353                Vector3::new(0.0, 1.0, 0.0),
354                Vector3::new(0.0, 0.0, -1.0),
355                Vector3::new(-1.0, 0.0, 0.0),
356            )),
357            0x20 => Ok(Matrix3::new(
358                Vector3::new(0.0, 0.0, 1.0),
359                Vector3::new(0.0, 1.0, 0.0),
360                Vector3::new(-1.0, 0.0, 0.0),
361            )),
362            0x22 => Ok(Matrix3::new(
363                Vector3::new(0.0, -1.0, 0.0),
364                Vector3::new(0.0, 0.0, 1.0),
365                Vector3::new(-1.0, 0.0, 0.0),
366            )),
367            0x23 => Ok(Matrix3::new(
368                Vector3::new(0.0, 0.0, -1.0),
369                Vector3::new(0.0, -1.0, 0.0),
370                Vector3::new(-1.0, 0.0, 0.0),
371            )),
372            _ => Err(Error::from(Matrix3Error::BadRotationId { id })),
373        }
374    }
375}
376
377/// Represents any color, including HDR colors.
378///
379/// ## See Also
380/// * [`Color3uint8`](struct.Color3uint8.html), which is used instead of
381///   `Color3` on some types and does not represent HDR colors.
382/// * [Color3 on Roblox Developer Hub](https://developer.roblox.com/en-us/api-reference/datatype/Color3)
383#[derive(Debug, Clone, Copy, PartialEq)]
384pub struct Color3 {
385    pub r: f32,
386    pub g: f32,
387    pub b: f32,
388}
389
390impl Color3 {
391    pub fn new(r: f32, g: f32, b: f32) -> Self {
392        Self { r, g, b }
393    }
394}
395
396impl From<Color3uint8> for Color3 {
397    fn from(value: Color3uint8) -> Self {
398        Self {
399            r: value.r as f32 / 255.0,
400            g: value.g as f32 / 255.0,
401            b: value.b as f32 / 255.0,
402        }
403    }
404}
405
406/// Represents non-HDR colors, i.e. those whose individual color channels do not
407/// exceed 1. This type is used for serializing properties like
408/// [`BasePart.Color`][BasePart.Color], but is not exposed as a distinct type to
409/// Lua code.
410///
411/// ## See Also
412/// * [`Color3`](struct.Color3.html), which is more common and can represent HDR
413///   colors.
414///
415/// [BasePart.Color]: https://developer.roblox.com/en-us/api-reference/property/BasePart/Color
416#[derive(Debug, Clone, Copy, PartialEq, Eq)]
417pub struct Color3uint8 {
418    pub r: u8,
419    pub g: u8,
420    pub b: u8,
421}
422
423impl Color3uint8 {
424    pub fn new(r: u8, g: u8, b: u8) -> Self {
425        Self { r, g, b }
426    }
427}
428
429impl From<Color3> for Color3uint8 {
430    fn from(value: Color3) -> Self {
431        Self {
432            r: (value.r.clamp(0.0, 1.0) * 255.0).round() as u8,
433            g: (value.g.clamp(0.0, 1.0) * 255.0).round() as u8,
434            b: (value.b.clamp(0.0, 1.0) * 255.0).round() as u8,
435        }
436    }
437}
438
439/// Represents a ray in 3D space. Direction does not have to be a unit vector,
440/// and is used by APIs like [`Workspace:FindPartOnRay`][FindPartOnRay] to set a
441/// max distance.
442///
443/// ## See Also
444/// * [Ray on Roblox Developer Hub](https://developer.roblox.com/en-us/api-reference/datatype/Ray)
445///
446/// [FindPartOnRay]: https://developer.roblox.com/en-us/api-reference/function/WorldRoot/FindPartOnRay
447#[derive(Debug, Clone, Copy, PartialEq)]
448#[cfg_attr(
449    feature = "serde",
450    derive(serde::Serialize, serde::Deserialize),
451    serde(rename_all = "camelCase")
452)]
453pub struct Ray {
454    pub origin: Vector3,
455    pub direction: Vector3,
456}
457
458impl Ray {
459    pub fn new(origin: Vector3, direction: Vector3) -> Self {
460        Self { origin, direction }
461    }
462}
463
464/// Represents a bounding box in 3D space.
465///
466/// ## See Also
467/// * [`Region3int16`](struct.Region3int16.html)
468/// * [Region3 on Roblox Developer Hub](https://developer.roblox.com/en-us/api-reference/datatype/Region3)
469#[derive(Debug, Clone, Copy, PartialEq)]
470pub struct Region3 {
471    pub min: Vector3,
472    pub max: Vector3,
473}
474
475impl Region3 {
476    pub fn new(min: Vector3, max: Vector3) -> Self {
477        Self { min, max }
478    }
479}
480
481/// A version of [`Region3`][Region3] that uses signed 16-bit integers instead
482/// of floats. `Region3int16` is generally used in Terrain APIs.
483///
484/// ## See Also
485/// * [`Region`][Region3]
486/// * [Region3int16 on Roblox Developer Hub](https://developer.roblox.com/en-us/api-reference/datatype/Region3int16)
487///
488/// [Region3]: struct.Region3.html
489#[derive(Debug, Clone, Copy, PartialEq, Eq)]
490pub struct Region3int16 {
491    pub min: Vector3int16,
492    pub max: Vector3int16,
493}
494
495impl Region3int16 {
496    pub fn new(min: Vector3int16, max: Vector3int16) -> Self {
497        Self { min, max }
498    }
499}
500
501/// Represents a bounding rectangle in 2D space.
502///
503/// ## See Also
504/// * [Rect on Roblox Developer Hub](https://developer.roblox.com/en-us/api-reference/datatype/Rect)
505#[derive(Debug, Clone, Copy, PartialEq)]
506pub struct Rect {
507    pub min: Vector2,
508    pub max: Vector2,
509}
510
511impl Rect {
512    pub fn new(min: Vector2, max: Vector2) -> Self {
513        Self { min, max }
514    }
515}
516
517/// Standard unit for measuring UI given as `scale`, a fraction of the
518/// container's size and `offset`, display-indepdendent pixels.
519///
520/// ## See Also
521/// * [UDim on Roblox Developer Hub](https://developer.roblox.com/en-us/api-reference/datatype/UDim)
522#[derive(Debug, Clone, Copy, PartialEq)]
523pub struct UDim {
524    pub scale: f32,
525    pub offset: i32,
526}
527
528impl UDim {
529    pub fn new(scale: f32, offset: i32) -> Self {
530        Self { scale, offset }
531    }
532}
533
534/// Standard 2D unit for measuring UI given as `scale`, a fraction of the
535/// container's size and `offset`, display-indepdendent pixels.
536///
537/// ## See Also
538/// * [UDim2 on Roblox Developer Hub](https://developer.roblox.com/en-us/api-reference/datatype/UDim2)
539#[derive(Debug, Clone, Copy, PartialEq)]
540pub struct UDim2 {
541    pub x: UDim,
542    pub y: UDim,
543}
544
545impl UDim2 {
546    pub fn new(x: UDim, y: UDim) -> Self {
547        Self { x, y }
548    }
549}
550
551/// A range between two numbers.
552///
553/// ## See Also
554/// * [NumberRange on Roblox Developer Hub](https://developer.roblox.com/en-us/api-reference/datatype/NumberRange)
555#[derive(Debug, Clone, Copy, PartialEq)]
556pub struct NumberRange {
557    pub min: f32,
558    pub max: f32,
559}
560
561impl NumberRange {
562    pub fn new(min: f32, max: f32) -> Self {
563        Self { min, max }
564    }
565}
566
567/// A series of colors that can be tweened through.
568///
569/// ## See Also
570/// * [ColorSequence on Roblox Developer Hub](https://developer.roblox.com/en-us/api-reference/datatype/ColorSequence)
571#[derive(Debug, Clone, PartialEq)]
572#[cfg_attr(
573    feature = "serde",
574    derive(serde::Serialize, serde::Deserialize),
575    serde(rename_all = "camelCase")
576)]
577pub struct ColorSequence {
578    pub keypoints: Vec<ColorSequenceKeypoint>,
579}
580
581/// A single color and point in time of a [`ColorSequence`][ColorSequence]
582///
583/// ## See Also
584/// * [ColorSequenceKeypoint on Roblox Developer Hub](https://developer.roblox.com/en-us/api-reference/datatype/ColorSequenceKeypoint)
585///
586/// [ColorSequence]: struct.ColorSequence.html
587#[derive(Debug, Clone, Copy, PartialEq)]
588#[cfg_attr(
589    feature = "serde",
590    derive(serde::Serialize, serde::Deserialize),
591    serde(rename_all = "camelCase")
592)]
593pub struct ColorSequenceKeypoint {
594    pub time: f32,
595    pub color: Color3,
596}
597
598impl ColorSequenceKeypoint {
599    pub fn new(time: f32, color: Color3) -> Self {
600        Self { time, color }
601    }
602}
603
604/// A sequence of numbers on a timeline. Each point contains a timestamp, a
605/// value, and a range that allows for randomized values.
606///
607/// ## See Also
608/// * [NumberSequence on Roblox Developer Hub](https://developer.roblox.com/en-us/api-reference/datatype/NumberSequence)
609#[derive(Debug, Clone, PartialEq)]
610#[cfg_attr(
611    feature = "serde",
612    derive(serde::Serialize, serde::Deserialize),
613    serde(rename_all = "camelCase")
614)]
615pub struct NumberSequence {
616    pub keypoints: Vec<NumberSequenceKeypoint>,
617}
618
619/// A single value, envelope, and point in time of a [`NumberSequence`][NumberSequence]
620///
621/// ## See Also
622/// * [`NumberSequence`][NumberSequence]
623/// * [NumberSequenceKeypoint on Roblox Developer Hub](https://developer.roblox.com/en-us/api-reference/datatype/NumberSequenceKeypoint)
624///
625/// [NumberSequence]: struct.NumberSequence.html
626#[derive(Debug, Clone, Copy, PartialEq)]
627#[cfg_attr(
628    feature = "serde",
629    derive(serde::Serialize, serde::Deserialize),
630    serde(rename_all = "camelCase")
631)]
632pub struct NumberSequenceKeypoint {
633    pub time: f32,
634    pub value: f32,
635    pub envelope: f32,
636}
637
638impl NumberSequenceKeypoint {
639    pub fn new(time: f32, value: f32, envelope: f32) -> Self {
640        Self {
641            time,
642            value,
643            envelope,
644        }
645    }
646}
647
648#[cfg(feature = "serde")]
649serde_tuple! {
650    Vector2(x: f32, y: f32),
651    Vector2int16(x: i16, y: i16),
652    Vector3(x: f32, y: f32, z: f32),
653    Vector3int16(x: i16, y: i16, z: i16),
654
655    Color3(r: f32, g: f32, b: f32),
656    Color3uint8(r: u8, g: u8, b: u8),
657
658    UDim(scale: f32, offset: i32),
659    UDim2(x: UDim, y: UDim),
660
661    NumberRange(min: f32, max: f32),
662
663    Rect(min: Vector2, max: Vector2),
664    Region3(min: Vector3, max: Vector3),
665    Region3int16(min: Vector3int16, max: Vector3int16),
666
667    Matrix3(x: Vector3, y: Vector3, z: Vector3),
668}
669
670#[cfg(all(test, feature = "serde"))]
671mod serde_test {
672    use super::*;
673
674    use std::fmt::Debug;
675
676    use serde::{de::DeserializeOwned, Serialize};
677
678    fn test_ser<T: Debug + PartialEq + Serialize + DeserializeOwned>(value: T, output: &str) {
679        let serialized = serde_json::to_string(&value).unwrap();
680        assert_eq!(serialized, output);
681
682        let deserialized: T = serde_json::from_str(output).unwrap();
683        assert_eq!(deserialized, value);
684    }
685
686    #[test]
687    fn vec2_json() {
688        test_ser(Vector2 { x: 2.0, y: 3.5 }, "[2.0,3.5]");
689    }
690
691    #[test]
692    fn udim_json() {
693        test_ser(
694            UDim {
695                scale: 1.0,
696                offset: 175,
697            },
698            "[1.0,175]",
699        );
700    }
701
702    #[test]
703    fn udim2_json() {
704        test_ser(
705            UDim2 {
706                x: UDim {
707                    scale: 0.0,
708                    offset: 30,
709                },
710                y: UDim {
711                    scale: 1.0,
712                    offset: 60,
713                },
714            },
715            "[[0.0,30],[1.0,60]]",
716        );
717    }
718
719    #[test]
720    fn region3_json() {
721        test_ser(
722            Region3 {
723                min: Vector3::new(-1.0, -2.0, -3.0),
724                max: Vector3::new(4.0, 5.0, 6.0),
725            },
726            "[[-1.0,-2.0,-3.0],[4.0,5.0,6.0]]",
727        );
728    }
729
730    #[test]
731    fn matrix3_json() {
732        test_ser(
733            Matrix3 {
734                x: Vector3::new(1.0, 2.0, 3.0),
735                y: Vector3::new(4.0, 5.0, 6.0),
736                z: Vector3::new(7.0, 8.0, 9.0),
737            },
738            "[[1.0,2.0,3.0],[4.0,5.0,6.0],[7.0,8.0,9.0]]",
739        );
740    }
741
742    #[test]
743    fn tagged_enum_json() {
744        test_ser(
745            EnumItem {
746                ty: "PlayTag".to_string(),
747                value: 3,
748            },
749            r#"{"type":"PlayTag","value":3}"#,
750        );
751    }
752}