Implement Vector2 datatypes

This commit is contained in:
Filip Tibell 2023-03-10 11:33:49 +01:00
parent 501933e3f9
commit 0cc5c0823e
No known key found for this signature in database
3 changed files with 184 additions and 17 deletions

View file

@ -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 // NOTE: We create a new inner module scope here to make imports of datatypes more ergonomic
mod vector2;
mod vector3; mod vector3;
pub mod types { pub mod types {
pub use super::vector2::Vector2;
pub use super::vector3::Vector3; pub use super::vector3::Vector3;
} }
@ -25,7 +27,7 @@ pub(crate) enum RbxConversionError {
detail: Option<String>, detail: Option<String>,
}, },
DesiredTypeMismatch { DesiredTypeMismatch {
actual: &'static str, can_convert_to: Option<&'static str>,
detail: Option<String>, detail: Option<String>,
}, },
} }
@ -47,6 +49,30 @@ pub(crate) trait DatatypeTable {
fn make_dt_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()>; fn make_dt_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()>;
} }
// Shared impls for datatype metamethods
fn datatype_impl_to_string<D>(_: &Lua, datatype: &D, _: ()) -> LuaResult<String>
where
D: LuaUserData + ToString + 'static,
{
Ok(datatype.to_string())
}
fn datatype_impl_eq<D>(_: &Lua, datatype: &D, value: LuaValue) -> LuaResult<bool>
where
D: LuaUserData + PartialEq + 'static,
{
if let LuaValue::UserData(ud) = value {
if let Ok(vec) = ud.borrow::<D>() {
Ok(*datatype == *vec)
} else {
Ok(false)
}
} else {
Ok(false)
}
}
// NOTE: This implementation is .. not great, but it's the best we can // 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, // 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 // and we are really only using it to make better error messages anyway

View file

@ -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::<Vector2>() {
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::<Vector2>() {
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<f32>, Option<f32>)| {
Ok(Vector2(Vec2 {
x: x.unwrap_or_default(),
y: y.unwrap_or_default(),
}))
})?,
)
}
}
impl FromRbxVariant for Vector2 {
fn from_rbx_variant(variant: &RbxVariant) -> RbxConversionResult<Self> {
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<RbxVariantType>,
) -> RbxConversionResult<RbxVariant> {
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,
})
}
}
}

View file

@ -16,7 +16,7 @@ use super::*;
Note that this does not use native Luau vectors to simplify implementation Note that this does not use native Luau vectors to simplify implementation
and instead allow us to implement all abovementioned APIs accurately. and instead allow us to implement all abovementioned APIs accurately.
*/ */
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct Vector3(pub Vec3A); pub struct Vector3(pub Vec3A);
impl fmt::Display for Vector3 { impl fmt::Display for Vector3 {
@ -58,20 +58,9 @@ impl LuaUserData for Vector3 {
methods.add_method("Min", |_, this, rhs: Vector3| { methods.add_method("Min", |_, this, rhs: Vector3| {
Ok(Vector3(this.0.min(rhs.0))) Ok(Vector3(this.0.min(rhs.0)))
}); });
// Metamethods - normal // Metamethods
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(this.to_string())); methods.add_meta_method(LuaMetaMethod::Eq, datatype_impl_eq);
methods.add_meta_method(LuaMetaMethod::Eq, |_, this, rhs: LuaValue| { methods.add_meta_method(LuaMetaMethod::ToString, datatype_impl_to_string);
if let LuaValue::UserData(ud) = rhs {
if let Ok(vec) = ud.borrow::<Vector3>() {
Ok(this.0 == vec.0)
} else {
Ok(false)
}
} else {
Ok(false)
}
});
// Metamethods - math
methods.add_meta_method(LuaMetaMethod::Unm, |_, this, ()| Ok(Vector3(-this.0))); methods.add_meta_method(LuaMetaMethod::Unm, |_, this, ()| Ok(Vector3(-this.0)));
methods.add_meta_method(LuaMetaMethod::Add, |_, this, rhs: Vector3| { methods.add_meta_method(LuaMetaMethod::Add, |_, this, rhs: Vector3| {
Ok(Vector3(this.0 + rhs.0)) Ok(Vector3(this.0 + rhs.0))
@ -174,7 +163,7 @@ impl ToRbxVariant for Vector3 {
})) }))
} else { } else {
Err(RbxConversionError::DesiredTypeMismatch { Err(RbxConversionError::DesiredTypeMismatch {
actual: RbxVariantType::Vector3.display_name(), can_convert_to: Some(RbxVariantType::Vector3.display_name()),
detail: None, detail: None,
}) })
} }