diff --git a/CHANGELOG.md b/CHANGELOG.md index d23f1b3..6bdff05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased Changes +* Added support for CFrame ([#48](https://github.com/rojo-rbx/remodel/pull/48)) * Added support for Vector3, and improved Vector3int16 ([#46](https://github.com/rojo-rbx/remodel/pull/46)) * Added Color3.fromRGB(red, blue, green) ([#44](https://github.com/rojo-rbx/remodel/pull/44)) diff --git a/src/roblox_api/cframe.rs b/src/roblox_api/cframe.rs new file mode 100644 index 0000000..9ca622f --- /dev/null +++ b/src/roblox_api/cframe.rs @@ -0,0 +1,72 @@ +use rbx_dom_weak::types::{CFrame, Matrix3, Vector3}; +use rlua::{UserData, UserDataMethods, Value as LuaValue}; + +use crate::value::{CFrameValue, Vector3Value}; + +pub struct CFrameUserData; + +impl CFrameUserData { + fn from_position(x: f32, y: f32, z: f32) -> CFrameValue { + CFrameValue::new(CFrame::new( + Vector3::new(x as f32, y as f32, z as f32), + // TODO: replace with `Matrix3::identity()` once + // a version higher than 0.3.0 of rbx_types ships + Matrix3::new( + Vector3::new(1.0, 0.0, 0.0), + Vector3::new(0.0, 1.0, 0.0), + Vector3::new(0.0, 0.0, 1.0), + ), + )) + } +} + +fn try_into_f32(value: LuaValue<'_>) -> Option { + match value { + LuaValue::Number(num) => Some(num as f32), + LuaValue::Integer(int) => Some(int as f32), + _ => None, + } +} + +impl UserData for CFrameUserData { + fn add_methods<'lua, T: UserDataMethods<'lua, Self>>(methods: &mut T) { + methods.add_function( + "new", + |_context, + arguments: ( + Option>, + Option>, + Option>, + )| { + match arguments { + (None, None, None) => return Ok(Self::from_position(0.0, 0.0, 0.0)), + (Some(LuaValue::UserData(user_data)), None, None) => { + let position = &*user_data.borrow::()?; + return Ok(CFrameValue::new(CFrame::new( + position.inner(), + // TODO: replace with `rbx_dom_weak::types::Matrix3::identity()` once + // a version higher than 0.3.0 of rbx_types ships + Matrix3::new( + Vector3::new(1.0, 0.0, 0.0), + Vector3::new(0.0, 1.0, 0.0), + Vector3::new(0.0, 0.0, 1.0), + ), + ))); + } + _ => {} + }; + + let x = arguments.0.and_then(try_into_f32); + let y = arguments.1.and_then(try_into_f32); + let z = arguments.2.and_then(try_into_f32); + + match (x, y, z) { + (Some(x), Some(y), Some(z)) => Ok(Self::from_position(x, y, z)), + _ => Err(rlua::Error::external( + "invalid argument #1 to 'new' (Vector3 expected)", + )), + } + }, + ); + } +} diff --git a/src/roblox_api/mod.rs b/src/roblox_api/mod.rs index 40a9395..76e489a 100644 --- a/src/roblox_api/mod.rs +++ b/src/roblox_api/mod.rs @@ -1,3 +1,4 @@ +mod cframe; mod instance; use std::sync::Arc; @@ -10,6 +11,7 @@ use crate::{ value::{Color3Value, Vector3Value, Vector3int16Value}, }; +use cframe::CFrameUserData; pub use instance::LuaInstance; pub struct RobloxApi; @@ -20,6 +22,7 @@ impl RobloxApi { context.globals().set("Vector3", Vector3)?; context.globals().set("Vector3int16", Vector3int16)?; context.globals().set("Color3", Color3)?; + context.globals().set("CFrame", CFrameUserData)?; Ok(()) } diff --git a/src/value.rs b/src/value.rs index d98f780..dc68cc5 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,6 +1,8 @@ //! Defines how to turn Variant values into Lua values and back. -use rbx_dom_weak::types::{Color3, Color3uint8, Variant, VariantType, Vector3, Vector3int16}; +use rbx_dom_weak::types::{ + CFrame, Color3, Color3uint8, Variant, VariantType, Vector3, Vector3int16, +}; use rlua::{ Context, MetaMethod, Result as LuaResult, ToLua, UserData, UserDataMethods, Value as LuaValue, }; @@ -21,7 +23,7 @@ pub fn rbxvalue_to_lua<'lua>(context: Context<'lua>, value: &Variant) -> LuaResu } Variant::BrickColor(_) => unimplemented_type("BrickColor"), Variant::Bool(value) => value.to_lua(context), - Variant::CFrame(_) => unimplemented_type("CFrame"), + Variant::CFrame(cframe) => CFrameValue::new(*cframe).to_lua(context), Variant::Color3(value) => Color3Value::new(*value).to_lua(context), Variant::Color3uint8(value) => Color3uint8Value::new(*value).to_lua(context), Variant::ColorSequence(_) => unimplemented_type("ColorSequence"), @@ -234,6 +236,10 @@ impl Vector3Value { Self(value) } + pub fn inner(&self) -> Vector3 { + self.0 + } + fn meta_index<'lua>( &self, context: Context<'lua>, @@ -379,3 +385,92 @@ impl UserData for Vector3int16Value { }); } } + +#[derive(Debug, Clone, Copy)] +pub struct CFrameValue(CFrame); + +impl CFrameValue { + pub fn new(value: CFrame) -> Self { + Self(value) + } + + fn meta_index<'lua>( + &self, + context: Context<'lua>, + key: &str, + ) -> rlua::Result> { + match key { + "X" => self.0.position.x.to_lua(context), + "Y" => self.0.position.y.to_lua(context), + "Z" => self.0.position.z.to_lua(context), + "RightVector" => Vector3Value::new(Vector3::new( + self.0.orientation.x.x, + self.0.orientation.y.x, + self.0.orientation.z.x, + )) + .to_lua(context), + "UpVector" => Vector3Value::new(Vector3::new( + self.0.orientation.x.y, + self.0.orientation.y.y, + self.0.orientation.z.y, + )) + .to_lua(context), + "LookVector" => Vector3Value::new(Vector3::new( + -self.0.orientation.x.z, + -self.0.orientation.y.z, + -self.0.orientation.z.z, + )) + .to_lua(context), + "XVector" => Vector3Value::new(self.0.orientation.x).to_lua(context), + "YVector" => Vector3Value::new(self.0.orientation.y).to_lua(context), + "ZVector" => Vector3Value::new(self.0.orientation.z).to_lua(context), + _ => Err(rlua::Error::external(format!( + "'{}' is not a valid member of CFrame", + key + ))), + } + } +} + +impl From<&CFrameValue> for Variant { + fn from(cframe: &CFrameValue) -> Variant { + Variant::CFrame(cframe.0) + } +} + +impl fmt::Display for CFrameValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}", + self.0.position.x, + self.0.position.y, + self.0.position.z, + self.0.orientation.x.x, + self.0.orientation.y.x, + self.0.orientation.z.x, + self.0.orientation.x.y, + self.0.orientation.y.y, + self.0.orientation.z.y, + self.0.orientation.x.z, + self.0.orientation.y.z, + self.0.orientation.z.z, + ) + } +} + +impl UserData for CFrameValue { + fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_meta_method(MetaMethod::Eq, |context, this, rhs: Self| { + (this.0 == rhs.0).to_lua(context) + }); + + methods.add_meta_method(MetaMethod::Index, |context, this, key: String| { + this.meta_index(context, &key) + }); + + methods.add_meta_method(MetaMethod::ToString, |context, this, _arg: ()| { + this.to_string().to_lua(context) + }); + } +} diff --git a/test-scripts/type-cframe.lua b/test-scripts/type-cframe.lua new file mode 100644 index 0000000..6f22f61 --- /dev/null +++ b/test-scripts/type-cframe.lua @@ -0,0 +1,35 @@ +local function assertCFramePosition(vec, x, y, z) + assert(vec.X == x, ("%f ~= %f"):format(vec.X, x)) + assert(vec.Y == y, ("%f ~= %f"):format(vec.Y, y)) + assert(vec.Z == z, ("%f ~= %f"):format(vec.Z, z)) +end + +local function assertVector(vec, x, y, z) + assert(vec.X == x, ("x: %f ~= %f (%s)"):format(vec.X, x, tostring(vec))) + assert(vec.Y == y, ("y: %f ~= %f (%s)"):format(vec.Y, y, tostring(vec))) + assert(vec.Z == z, ("z: %f ~= %f (%s)"):format(vec.Z, z, tostring(vec))) +end + +-- new with combinations of integer and floats +assertCFramePosition(CFrame.new(), 0, 0, 0) +assertCFramePosition(CFrame.new(1, 2, 3), 1, 2, 3) +assertCFramePosition(CFrame.new(1.5, 2, 3), 1.5, 2, 3) +assertCFramePosition(CFrame.new(1, 2.5, 3), 1, 2.5, 3) +assertCFramePosition(CFrame.new(1, 2, 3.5), 1, 2, 3.5) +assertCFramePosition(CFrame.new(1.5, 2.5, 3), 1.5, 2.5, 3) +assertCFramePosition(CFrame.new(1, 2.5, 3.5), 1, 2.5, 3.5) +assertCFramePosition(CFrame.new(1.5, 2.5, 3.5), 1.5, 2.5, 3.5) + +-- new from Vector3 +assertCFramePosition(CFrame.new(Vector3.new(1, 2, 3)), 1, 2, 3) + +-- properties +assertVector(CFrame.new().XVector, 1, 0, 0) +assertVector(CFrame.new().YVector, 0, 1, 0) +assertVector(CFrame.new().ZVector, 0, 0, 1) + +assertVector(CFrame.new().RightVector, 1, 0, 0) +assertVector(CFrame.new().UpVector, 0, 1, 0) +assertVector(CFrame.new().LookVector, -0, -0, -1) + +assert(tostring(CFrame.new(7, 8, 9)) == "7, 8, 9, 1, 0, 0, 0, 1, 0, 0, 0, 1", "got " .. tostring(CFrame.new()))