diff --git a/packages/lib-roblox/src/datatypes/conversion.rs b/packages/lib-roblox/src/datatypes/conversion.rs index e09f264..8285529 100644 --- a/packages/lib-roblox/src/datatypes/conversion.rs +++ b/packages/lib-roblox/src/datatypes/conversion.rs @@ -121,7 +121,6 @@ impl<'lua> RbxVariantToLua<'lua> for LuaAnyUserData<'lua> { // Not yet implemented datatypes // Rbx::Font(_) => todo!(), // Rbx::PhysicalProperties(_) => todo!(), - // Rbx::Ray(_) => todo!(), Rbx::Axes(value) => lua.create_userdata(Axes::from(value))?, Rbx::Faces(value) => lua.create_userdata(Faces::from(value))?, @@ -140,6 +139,8 @@ impl<'lua> RbxVariantToLua<'lua> for LuaAnyUserData<'lua> { Rbx::NumberRange(value) => lua.create_userdata(NumberRange::from(value))?, Rbx::NumberSequence(value) => lua.create_userdata(NumberSequence::from(value))?, + Rbx::Ray(value) => lua.create_userdata(Ray::from(value))?, + Rbx::Rect(value) => lua.create_userdata(Rect::from(value))?, Rbx::UDim(value) => lua.create_userdata(UDim::from(value))?, Rbx::UDim2(value) => lua.create_userdata(UDim2::from(value))?, @@ -196,6 +197,8 @@ impl<'lua> LuaToRbxVariant<'lua> for LuaAnyUserData<'lua> { RbxVariantType::UDim => convert::, RbxVariantType::UDim2 => convert::, + RbxVariantType::Ray => convert::, + RbxVariantType::Region3 => convert::, RbxVariantType::Region3int16 => convert::, RbxVariantType::Vector2 => convert::, diff --git a/packages/lib-roblox/src/datatypes/types/mod.rs b/packages/lib-roblox/src/datatypes/types/mod.rs index ca9a307..09fe054 100644 --- a/packages/lib-roblox/src/datatypes/types/mod.rs +++ b/packages/lib-roblox/src/datatypes/types/mod.rs @@ -11,6 +11,7 @@ mod faces; mod number_range; mod number_sequence; mod number_sequence_keypoint; +mod ray; mod rect; mod region3; mod region3int16; @@ -34,6 +35,7 @@ pub use number_sequence_keypoint::NumberSequenceKeypoint; pub use r#enum::Enum; pub use r#enum_item::EnumItem; pub use r#enums::Enums; +pub use ray::Ray; pub use rect::Rect; pub use region3::Region3; pub use region3int16::Region3int16; @@ -97,6 +99,7 @@ mod tests { number_range: "datatypes/NumberRange", number_sequence: "datatypes/NumberSequence", number_sequence_keypoint: "datatypes/NumberSequenceKeypoint", + ray: "datatypes/Ray", rect: "datatypes/Rect", udim: "datatypes/UDim", udim2: "datatypes/UDim2", diff --git a/packages/lib-roblox/src/datatypes/types/ray.rs b/packages/lib-roblox/src/datatypes/types/ray.rs new file mode 100644 index 0000000..3b1b045 --- /dev/null +++ b/packages/lib-roblox/src/datatypes/types/ray.rs @@ -0,0 +1,92 @@ +use core::fmt; + +use glam::Vec3; +use mlua::prelude::*; +use rbx_dom_weak::types::Ray as RbxRay; + +use super::{super::*, Vector3}; + +/** + An implementation of the [Ray](https://create.roblox.com/docs/reference/engine/datatypes/Ray) + Roblox datatype, backed by [`glam::Vec3`]. + + This implements all documented properties, methods & constructors of the Ray class as of March 2023. +*/ +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Ray { + pub(crate) origin: Vec3, + pub(crate) direction: Vec3, +} + +impl Ray { + fn closest_point(&self, point: Vec3) -> Vec3 { + let norm = self.direction.normalize(); + let lhs = point - self.origin; + + let dot_product = lhs.dot(norm).max(0.0); + self.origin + norm * dot_product + } + + pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> { + datatype_table.set( + "new", + lua.create_function(|_, (origin, direction): (Vector3, Vector3)| { + Ok(Ray { + origin: origin.0, + direction: direction.0, + }) + })?, + ) + } +} + +impl LuaUserData for Ray { + fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("Origin", |_, this| Ok(Vector3(this.origin))); + fields.add_field_method_get("Direction", |_, this| Ok(Vector3(this.direction))); + fields.add_field_method_get("Unit", |_, this| { + Ok(Ray { + origin: this.origin, + direction: this.direction.normalize(), + }) + }); + } + + fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + // Methods + methods.add_method("ClosestPoint", |_, this, to: Vector3| { + Ok(Vector3(this.closest_point(to.0))) + }); + methods.add_method("Distance", |_, this, to: Vector3| { + let closest = this.closest_point(to.0); + Ok((closest - to.0).length()) + }); + // Metamethods + methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq); + methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string); + } +} + +impl fmt::Display for Ray { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}, {}", Vector3(self.origin), Vector3(self.direction)) + } +} + +impl From for Ray { + fn from(v: RbxRay) -> Self { + Ray { + origin: Vector3::from(v.origin).0, + direction: Vector3::from(v.direction).0, + } + } +} + +impl From for RbxRay { + fn from(v: Ray) -> Self { + RbxRay { + origin: Vector3(v.origin).into(), + direction: Vector3(v.direction).into(), + } + } +} diff --git a/packages/lib-roblox/src/lib.rs b/packages/lib-roblox/src/lib.rs index 44a594a..2d66e1e 100644 --- a/packages/lib-roblox/src/lib.rs +++ b/packages/lib-roblox/src/lib.rs @@ -29,6 +29,7 @@ fn make_all_datatypes(lua: &Lua) -> LuaResult> { ("NumberRange", make_dt(lua, NumberRange::make_table)?), ("NumberSequence", make_dt(lua, NumberSequence::make_table)?), ("NumberSequenceKeypoint", make_dt(lua, NumberSequenceKeypoint::make_table)?), + ("Ray", make_dt(lua, Ray::make_table)?), ("Rect", make_dt(lua, Rect::make_table)?), ("UDim", make_dt(lua, UDim::make_table)?), ("UDim2", make_dt(lua, UDim2::make_table)?), diff --git a/tests/roblox/datatypes/Ray.luau b/tests/roblox/datatypes/Ray.luau new file mode 100644 index 0000000..67edae1 --- /dev/null +++ b/tests/roblox/datatypes/Ray.luau @@ -0,0 +1,50 @@ +-- HACK: Make luau happy, with the mlua rust +-- crate all globals are also present in _G +local Ray = _G.Ray +local Vector3 = _G.Vector3 + +local origin = Vector3.zero +local direction = Vector3.zAxis * 10 + +-- Constructors & properties + +Ray.new(origin, direction) + +assert(not pcall(function() + return Ray.new(false) +end)) +assert(not pcall(function() + return Ray.new("", "") +end)) +assert(not pcall(function() + return Ray.new(newproxy(true)) +end)) + +assert(Ray.new(origin, direction).Origin == origin) +assert(Ray.new(origin, direction).Direction == direction) + +assert(Ray.new(origin, direction).Unit.Origin == origin) +assert(Ray.new(origin, direction).Unit.Direction == direction.Unit) + +-- Ops + +assert(not pcall(function() + return Ray.new(origin, direction) + Ray.new(origin, direction) +end)) +assert(not pcall(function() + return Ray.new(origin, direction) / Ray.new(origin, direction) +end)) + +-- Methods + +assert(Ray.new(origin, direction):ClosestPoint(origin) == origin) +assert(Ray.new(origin, direction):Distance(origin) == 0) + +for z = 0, 10, 1 do + local x = if z % 2 == 0 then 2.5 else 7.5 + assert( + Ray.new(origin, direction):ClosestPoint(Vector3.new(x, 0, z)) + == Vector3.zero + Vector3.zAxis * z + ) + assert(Ray.new(origin, direction):Distance(Vector3.new(x, 0, z)) == x) +end