From 0cc5c0823e68b1560d76bb34fccddfbec5aebdb4 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Fri, 10 Mar 2023 11:33:49 +0100 Subject: [PATCH] Implement Vector2 datatypes --- packages/lib-roblox/src/datatypes/mod.rs | 28 +++- packages/lib-roblox/src/datatypes/vector2.rs | 152 +++++++++++++++++++ packages/lib-roblox/src/datatypes/vector3.rs | 21 +-- 3 files changed, 184 insertions(+), 17 deletions(-) create mode 100644 packages/lib-roblox/src/datatypes/vector2.rs diff --git a/packages/lib-roblox/src/datatypes/mod.rs b/packages/lib-roblox/src/datatypes/mod.rs index ef162ba..c447a9c 100644 --- a/packages/lib-roblox/src/datatypes/mod.rs +++ b/packages/lib-roblox/src/datatypes/mod.rs @@ -4,9 +4,11 @@ pub(crate) use rbx_dom_weak::types::{Variant as RbxVariant, VariantType as RbxVa // NOTE: We create a new inner module scope here to make imports of datatypes more ergonomic +mod vector2; mod vector3; pub mod types { + pub use super::vector2::Vector2; pub use super::vector3::Vector3; } @@ -25,7 +27,7 @@ pub(crate) enum RbxConversionError { detail: Option, }, DesiredTypeMismatch { - actual: &'static str, + can_convert_to: Option<&'static str>, detail: Option, }, } @@ -47,6 +49,30 @@ pub(crate) trait DatatypeTable { fn make_dt_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()>; } +// Shared impls for datatype metamethods + +fn datatype_impl_to_string(_: &Lua, datatype: &D, _: ()) -> LuaResult +where + D: LuaUserData + ToString + 'static, +{ + Ok(datatype.to_string()) +} + +fn datatype_impl_eq(_: &Lua, datatype: &D, value: LuaValue) -> LuaResult +where + D: LuaUserData + PartialEq + 'static, +{ + if let LuaValue::UserData(ud) = value { + if let Ok(vec) = ud.borrow::() { + Ok(*datatype == *vec) + } else { + Ok(false) + } + } else { + Ok(false) + } +} + // NOTE: This implementation is .. not great, but it's the best we can // do since we can't implement a trait like Display on a foreign type, // and we are really only using it to make better error messages anyway diff --git a/packages/lib-roblox/src/datatypes/vector2.rs b/packages/lib-roblox/src/datatypes/vector2.rs new file mode 100644 index 0000000..943fb7b --- /dev/null +++ b/packages/lib-roblox/src/datatypes/vector2.rs @@ -0,0 +1,152 @@ +use core::fmt; + +use glam::{Vec2, Vec3A}; +use mlua::prelude::*; +use rbx_dom_weak::types::Vector2 as RbxVector2; + +use super::*; + +/** + An implementation of the [Vector2](https://create.roblox.com/docs/reference/engine/datatypes/Vector2) + Roblox datatype, backed by [`glam::Vec2`]. + + This implements all documented properties, methods & + constructors of the Vector2 class as of March 2023. +*/ +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Vector2(pub Vec2); + +impl fmt::Display for Vector2 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}, {}", self.0.x, self.0.y) + } +} + +impl LuaUserData for Vector2 { + fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("Magnitude", |_, this| Ok(this.0.length())); + fields.add_field_method_get("Unit", |_, this| Ok(Vector2(this.0.normalize()))); + fields.add_field_method_get("X", |_, this| Ok(this.0.x)); + fields.add_field_method_get("Y", |_, this| Ok(this.0.y)); + } + + fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + // Methods + methods.add_method("Cross", |_, this, rhs: Vector2| { + let this_v3 = Vec3A::new(this.0.x, this.0.y, 0f32); + let rhs_v3 = Vec3A::new(rhs.0.x, rhs.0.y, 0f32); + Ok(this_v3.cross(rhs_v3).z) + }); + methods.add_method("Dot", |_, this, rhs: Vector2| Ok(this.0.dot(rhs.0))); + methods.add_method("Lerp", |_, this, (rhs, alpha): (Vector2, f32)| { + Ok(Vector2(this.0.lerp(rhs.0, alpha))) + }); + methods.add_method("Max", |_, this, rhs: Vector2| { + Ok(Vector2(this.0.max(rhs.0))) + }); + methods.add_method("Min", |_, this, rhs: Vector2| { + Ok(Vector2(this.0.min(rhs.0))) + }); + // Metamethods + methods.add_meta_method(LuaMetaMethod::Eq, datatype_impl_eq); + methods.add_meta_method(LuaMetaMethod::ToString, datatype_impl_to_string); + methods.add_meta_method(LuaMetaMethod::Unm, |_, this, ()| Ok(Vector2(-this.0))); + methods.add_meta_method(LuaMetaMethod::Add, |_, this, rhs: Vector2| { + Ok(Vector2(this.0 + rhs.0)) + }); + methods.add_meta_method(LuaMetaMethod::Sub, |_, this, rhs: Vector2| { + Ok(Vector2(this.0 - rhs.0)) + }); + methods.add_meta_method(LuaMetaMethod::Mul, |_, this, rhs: LuaValue| { + match &rhs { + LuaValue::Number(n) => return Ok(Vector2(this.0 * Vec2::splat(*n as f32))), + LuaValue::UserData(ud) => { + if let Ok(vec) = ud.borrow::() { + return Ok(Vector2(this.0 * vec.0)); + } + } + _ => {} + }; + Err(LuaError::FromLuaConversionError { + from: rhs.type_name(), + to: "Vector2", + message: Some(format!( + "Expected Vector2 or number, got {}", + rhs.type_name() + )), + }) + }); + methods.add_meta_method(LuaMetaMethod::Div, |_, this, rhs: LuaValue| { + match &rhs { + LuaValue::Number(n) => return Ok(Vector2(this.0 / Vec2::splat(*n as f32))), + LuaValue::UserData(ud) => { + if let Ok(vec) = ud.borrow::() { + return Ok(Vector2(this.0 / vec.0)); + } + } + _ => {} + }; + Err(LuaError::FromLuaConversionError { + from: rhs.type_name(), + to: "Vector2", + message: Some(format!( + "Expected Vector2 or number, got {}", + rhs.type_name() + )), + }) + }); + } +} + +impl DatatypeTable for Vector2 { + fn make_dt_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> { + // Constants + datatype_table.set("xAxis", Vector2(Vec2::X))?; + datatype_table.set("yAxis", Vector2(Vec2::Y))?; + datatype_table.set("zero", Vector2(Vec2::ZERO))?; + datatype_table.set("one", Vector2(Vec2::ONE))?; + // Constructors + datatype_table.set( + "new", + lua.create_function(|_, (x, y): (Option, Option)| { + Ok(Vector2(Vec2 { + x: x.unwrap_or_default(), + y: y.unwrap_or_default(), + })) + })?, + ) + } +} + +impl FromRbxVariant for Vector2 { + fn from_rbx_variant(variant: &RbxVariant) -> RbxConversionResult { + if let RbxVariant::Vector2(v) = variant { + Ok(Vector2(Vec2 { x: v.x, y: v.y })) + } else { + Err(RbxConversionError::FromRbxVariant { + from: variant.display_name(), + to: "Vector2", + detail: None, + }) + } + } +} + +impl ToRbxVariant for Vector2 { + fn to_rbx_variant( + &self, + desired_type: Option, + ) -> RbxConversionResult { + if matches!(desired_type, None | Some(RbxVariantType::Vector2)) { + Ok(RbxVariant::Vector2(RbxVector2 { + x: self.0.x, + y: self.0.y, + })) + } else { + Err(RbxConversionError::DesiredTypeMismatch { + can_convert_to: Some(RbxVariantType::Vector2.display_name()), + detail: None, + }) + } + } +} diff --git a/packages/lib-roblox/src/datatypes/vector3.rs b/packages/lib-roblox/src/datatypes/vector3.rs index 0917c8e..e9e728d 100644 --- a/packages/lib-roblox/src/datatypes/vector3.rs +++ b/packages/lib-roblox/src/datatypes/vector3.rs @@ -16,7 +16,7 @@ use super::*; Note that this does not use native Luau vectors to simplify implementation and instead allow us to implement all abovementioned APIs accurately. */ -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct Vector3(pub Vec3A); impl fmt::Display for Vector3 { @@ -58,20 +58,9 @@ impl LuaUserData for Vector3 { methods.add_method("Min", |_, this, rhs: Vector3| { Ok(Vector3(this.0.min(rhs.0))) }); - // Metamethods - normal - methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(this.to_string())); - methods.add_meta_method(LuaMetaMethod::Eq, |_, this, rhs: LuaValue| { - if let LuaValue::UserData(ud) = rhs { - if let Ok(vec) = ud.borrow::() { - Ok(this.0 == vec.0) - } else { - Ok(false) - } - } else { - Ok(false) - } - }); - // Metamethods - math + // Metamethods + methods.add_meta_method(LuaMetaMethod::Eq, datatype_impl_eq); + methods.add_meta_method(LuaMetaMethod::ToString, datatype_impl_to_string); methods.add_meta_method(LuaMetaMethod::Unm, |_, this, ()| Ok(Vector3(-this.0))); methods.add_meta_method(LuaMetaMethod::Add, |_, this, rhs: Vector3| { Ok(Vector3(this.0 + rhs.0)) @@ -174,7 +163,7 @@ impl ToRbxVariant for Vector3 { })) } else { Err(RbxConversionError::DesiredTypeMismatch { - actual: RbxVariantType::Vector3.display_name(), + can_convert_to: Some(RbxVariantType::Vector3.display_name()), detail: None, }) }