mirror of
https://github.com/CompeyDev/lune-packaging.git
synced 2025-01-09 12:19:09 +00:00
Implement CFrame roblox datatype
This commit is contained in:
parent
010f7325d1
commit
94e84d6054
5 changed files with 380 additions and 1 deletions
|
@ -121,7 +121,6 @@ impl<'lua> RbxVariantToLua<'lua> for LuaAnyUserData<'lua> {
|
|||
// check `EnumItem::from_instance_property` for specifics
|
||||
Ok(match variant.clone() {
|
||||
// Not yet implemented datatypes
|
||||
// Rbx::CFrame(_) => todo!(),
|
||||
// Rbx::NumberRange(_) => todo!(),
|
||||
// Rbx::NumberSequence(_) => todo!(),
|
||||
// Rbx::OptionalCFrame(_) => todo!(),
|
||||
|
@ -133,6 +132,8 @@ impl<'lua> RbxVariantToLua<'lua> for LuaAnyUserData<'lua> {
|
|||
Rbx::Axes(value) => lua.create_userdata(Axes::from(value))?,
|
||||
Rbx::Faces(value) => lua.create_userdata(Faces::from(value))?,
|
||||
|
||||
Rbx::CFrame(value) => lua.create_userdata(CFrame::from(value))?,
|
||||
|
||||
Rbx::BrickColor(value) => lua.create_userdata(BrickColor::from(value))?,
|
||||
Rbx::Color3(value) => lua.create_userdata(Color3::from(value))?,
|
||||
Rbx::Color3uint8(value) => lua.create_userdata(Color3::from(value))?,
|
||||
|
@ -172,6 +173,8 @@ impl<'lua> LuaToRbxVariant<'lua> for LuaAnyUserData<'lua> {
|
|||
RbxVariantType::Axes => convert::<Axes, rbx::Axes>,
|
||||
RbxVariantType::Faces => convert::<Faces, rbx::Faces>,
|
||||
|
||||
RbxVariantType::CFrame => convert::<CFrame, rbx::CFrame>,
|
||||
|
||||
RbxVariantType::BrickColor => convert::<BrickColor, rbx::BrickColor>,
|
||||
RbxVariantType::Color3 => convert::<Color3, rbx::Color3>,
|
||||
RbxVariantType::Color3uint8 => convert::<Color3, rbx::Color3uint8>,
|
||||
|
|
329
packages/lib-roblox/src/datatypes/types/cframe.rs
Normal file
329
packages/lib-roblox/src/datatypes/types/cframe.rs
Normal file
|
@ -0,0 +1,329 @@
|
|||
use core::fmt;
|
||||
use std::ops;
|
||||
|
||||
use glam::{EulerRot, Mat4, Quat, Vec3};
|
||||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::{CFrame as RbxCFrame, Matrix3 as RbxMatrix3, Vector3 as RbxVector3};
|
||||
|
||||
use super::{super::*, Vector3};
|
||||
|
||||
/**
|
||||
An implementation of the [CFrame](https://create.roblox.com/docs/reference/engine/datatypes/CFrame)
|
||||
Roblox datatype, backed by [`glam::Mat4`].
|
||||
|
||||
This implements all documented properties, methods &
|
||||
constructors of the CFrame class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct CFrame(Mat4);
|
||||
|
||||
impl CFrame {
|
||||
fn position(&self) -> Vec3 {
|
||||
self.0.w_axis.truncate()
|
||||
}
|
||||
|
||||
fn orientation(&self) -> (Vec3, Vec3, Vec3) {
|
||||
(
|
||||
self.0.x_axis.truncate(),
|
||||
self.0.y_axis.truncate(),
|
||||
self.0.z_axis.truncate(),
|
||||
)
|
||||
}
|
||||
|
||||
fn inverse(&self) -> Self {
|
||||
Self(self.0.inverse())
|
||||
}
|
||||
|
||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
||||
// Constants
|
||||
datatype_table.set("identity", CFrame(Mat4::IDENTITY))?;
|
||||
// Strict args constructors
|
||||
datatype_table.set(
|
||||
"lookAt",
|
||||
lua.create_function(
|
||||
|_, (at, look_at, up): (Vector3, Vector3, Option<Vector3>)| {
|
||||
Ok(CFrame(Mat4::look_at_rh(
|
||||
at.0,
|
||||
look_at.0,
|
||||
up.unwrap_or(Vector3(Vec3::Y)).0,
|
||||
)))
|
||||
},
|
||||
)?,
|
||||
)?;
|
||||
datatype_table.set(
|
||||
"fromEulerAnglesXYZ",
|
||||
lua.create_function(|_, (rx, ry, rz): (f32, f32, f32)| {
|
||||
Ok(CFrame(Mat4::from_euler(EulerRot::ZYX, rx, ry, rz)))
|
||||
})?,
|
||||
)?;
|
||||
datatype_table.set(
|
||||
"fromEulerAnglesYXZ",
|
||||
lua.create_function(|_, (rx, ry, rz): (f32, f32, f32)| {
|
||||
Ok(CFrame(Mat4::from_euler(EulerRot::ZXY, rx, ry, rz)))
|
||||
})?,
|
||||
)?;
|
||||
datatype_table.set(
|
||||
"Angles",
|
||||
lua.create_function(|_, (rx, ry, rz): (f32, f32, f32)| {
|
||||
Ok(CFrame(Mat4::from_euler(EulerRot::ZYX, rx, ry, rz)))
|
||||
})?,
|
||||
)?;
|
||||
datatype_table.set(
|
||||
"fromOrientation",
|
||||
lua.create_function(|_, (rx, ry, rz): (f32, f32, f32)| {
|
||||
Ok(CFrame(Mat4::from_euler(EulerRot::ZXY, rx, ry, rz)))
|
||||
})?,
|
||||
)?;
|
||||
datatype_table.set(
|
||||
"fromAxisAngle",
|
||||
lua.create_function(|_, (v, r): (Vector3, f32)| {
|
||||
Ok(CFrame(Mat4::from_axis_angle(v.0, r)))
|
||||
})?,
|
||||
)?;
|
||||
datatype_table.set(
|
||||
"fromMatrix",
|
||||
lua.create_function(
|
||||
|_, (pos, rx, ry, rz): (Vector3, Vector3, Vector3, Option<Vector3>)| {
|
||||
Ok(CFrame(Mat4::from_cols(
|
||||
rx.0.extend(0.0),
|
||||
ry.0.extend(0.0),
|
||||
rz.map(|r| r.0)
|
||||
.unwrap_or_else(|| rx.0.cross(ry.0).normalize())
|
||||
.extend(0.0),
|
||||
pos.0.extend(1.0),
|
||||
)))
|
||||
},
|
||||
)?,
|
||||
)?;
|
||||
// Dynamic args constructor
|
||||
type ArgsPos = Vector3;
|
||||
type ArgsLook = (Vector3, Vector3);
|
||||
type ArgsPosXYZ = (f32, f32, f32);
|
||||
type ArgsPosXYZQuat = (f32, f32, f32, f32, f32, f32, f32);
|
||||
type ArgsMatrix = (f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32);
|
||||
datatype_table.set(
|
||||
"new",
|
||||
lua.create_function(|lua, args: LuaMultiValue| {
|
||||
if args.clone().into_vec().is_empty() {
|
||||
Ok(CFrame(Mat4::IDENTITY))
|
||||
} else if let Ok(pos) = ArgsPos::from_lua_multi(args.clone(), lua) {
|
||||
Ok(CFrame(Mat4::from_translation(pos.0)))
|
||||
} else if let Ok((pos, look_at)) = ArgsLook::from_lua_multi(args.clone(), lua) {
|
||||
Ok(CFrame(Mat4::look_at_rh(pos.0, look_at.0, Vec3::Y)))
|
||||
} else if let Ok((x, y, z)) = ArgsPosXYZ::from_lua_multi(args.clone(), lua) {
|
||||
Ok(CFrame(Mat4::from_translation(Vec3::new(x, y, z))))
|
||||
} else if let Ok((x, y, z, qx, qy, qz, qw)) =
|
||||
ArgsPosXYZQuat::from_lua_multi(args.clone(), lua)
|
||||
{
|
||||
Ok(CFrame(Mat4::from_rotation_translation(
|
||||
Quat::from_array([qx, qy, qz, qw]),
|
||||
Vec3::new(x, y, z),
|
||||
)))
|
||||
} else if let Ok((x, y, z, r00, r01, r02, r10, r11, r12, r20, r21, r22)) =
|
||||
ArgsMatrix::from_lua_multi(args, lua)
|
||||
{
|
||||
Ok(CFrame(Mat4::from_cols_array_2d(&[
|
||||
[r00, r01, r02, 0.0],
|
||||
[r10, r11, r12, 0.0],
|
||||
[r20, r21, r22, 0.0],
|
||||
[x, y, z, 1.0],
|
||||
])))
|
||||
} else {
|
||||
// FUTURE: Better error message here using given arg types
|
||||
Err(LuaError::RuntimeError(
|
||||
"Invalid arguments to constructor".to_string(),
|
||||
))
|
||||
}
|
||||
})?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for CFrame {
|
||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("Position", |_, this| Ok(Vector3(this.position())));
|
||||
fields.add_field_method_get("Rotation", |_, this| {
|
||||
Ok(CFrame(Mat4::from_cols(
|
||||
this.0.x_axis,
|
||||
this.0.y_axis,
|
||||
this.0.z_axis,
|
||||
Vec3::ZERO.extend(1.0),
|
||||
)))
|
||||
});
|
||||
fields.add_field_method_get("X", |_, this| Ok(this.position().x));
|
||||
fields.add_field_method_get("Y", |_, this| Ok(this.position().y));
|
||||
fields.add_field_method_get("Z", |_, this| Ok(this.position().z));
|
||||
fields.add_field_method_get("XVector", |_, this| Ok(Vector3(this.orientation().0)));
|
||||
fields.add_field_method_get("YVector", |_, this| Ok(Vector3(this.orientation().1)));
|
||||
fields.add_field_method_get("ZVector", |_, this| Ok(Vector3(this.orientation().2)));
|
||||
fields.add_field_method_get("RightVector", |_, this| Ok(Vector3(this.orientation().0)));
|
||||
fields.add_field_method_get("UpVector", |_, this| Ok(Vector3(this.orientation().1)));
|
||||
fields.add_field_method_get("LookVector", |_, this| Ok(Vector3(-this.orientation().2)));
|
||||
}
|
||||
|
||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
// Methods
|
||||
methods.add_method("Inverse", |_, this, ()| Ok(this.inverse()));
|
||||
methods.add_method("Lerp", |_, this, (goal, alpha): (CFrame, f32)| {
|
||||
let quat_this = Quat::from_mat4(&this.0);
|
||||
let quat_goal = Quat::from_mat4(&goal.0);
|
||||
let translation = this
|
||||
.0
|
||||
.w_axis
|
||||
.truncate()
|
||||
.lerp(goal.0.w_axis.truncate(), alpha);
|
||||
let rotation = quat_this.slerp(quat_goal, alpha);
|
||||
Ok(CFrame(Mat4::from_rotation_translation(
|
||||
rotation,
|
||||
translation,
|
||||
)))
|
||||
});
|
||||
methods.add_method("Orthonormalize", |_, this, ()| {
|
||||
let rotation = Quat::from_mat4(&this.0);
|
||||
let translation = this.0.w_axis.truncate();
|
||||
Ok(CFrame(Mat4::from_rotation_translation(
|
||||
rotation.normalize(),
|
||||
translation,
|
||||
)))
|
||||
});
|
||||
methods.add_method("ToWorldSpace", |_, this, rhs: CFrame| Ok(*this * rhs));
|
||||
methods.add_method("ToObjectSpace", |_, this, rhs: CFrame| {
|
||||
Ok(this.inverse() * rhs)
|
||||
});
|
||||
methods.add_method("PointToWorldSpace", |_, this, rhs: Vector3| Ok(*this * rhs));
|
||||
methods.add_method("PointToObjectSpace", |_, this, rhs: Vector3| {
|
||||
Ok(this.inverse() * rhs)
|
||||
});
|
||||
methods.add_method("VectorToWorldSpace", |_, this, rhs: Vector3| {
|
||||
Ok((*this - Vector3(this.position())) * rhs)
|
||||
});
|
||||
methods.add_method("VectorToObjectSpace", |_, this, rhs: Vector3| {
|
||||
let inv = this.inverse();
|
||||
Ok((inv - Vector3(inv.position())) * rhs)
|
||||
});
|
||||
#[rustfmt::skip]
|
||||
methods.add_method("GetComponents", |_, this, ()| {
|
||||
let pos = this.position();
|
||||
let (rx, ry, rz) = this.orientation();
|
||||
Ok((
|
||||
pos.x, pos.y, pos.z,
|
||||
rx.x, rx.y, rx.z,
|
||||
ry.x, ry.y, ry.z,
|
||||
rz.x, rz.y, rz.z,
|
||||
))
|
||||
});
|
||||
methods.add_method("ToEulerAnglesXYZ", |_, this, ()| {
|
||||
Ok(Quat::from_mat4(&this.0).to_euler(EulerRot::ZYX))
|
||||
});
|
||||
methods.add_method("ToEulerAnglesYXZ", |_, this, ()| {
|
||||
Ok(Quat::from_mat4(&this.0).to_euler(EulerRot::ZXY))
|
||||
});
|
||||
methods.add_method("ToOrientation", |_, this, ()| {
|
||||
Ok(Quat::from_mat4(&this.0).to_euler(EulerRot::ZXY))
|
||||
});
|
||||
methods.add_method("ToAxisAngle", |_, this, ()| {
|
||||
let (axis, angle) = Quat::from_mat4(&this.0).to_axis_angle();
|
||||
Ok((Vector3(axis), angle))
|
||||
});
|
||||
// Metamethods
|
||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||
methods.add_meta_method(LuaMetaMethod::Mul, |lua, this, rhs: LuaValue| {
|
||||
if let LuaValue::UserData(ud) = &rhs {
|
||||
if let Ok(cf) = ud.borrow::<CFrame>() {
|
||||
return lua.create_userdata(*this * *cf);
|
||||
} else if let Ok(vec) = ud.borrow::<Vector3>() {
|
||||
return lua.create_userdata(*this * *vec);
|
||||
}
|
||||
};
|
||||
Err(LuaError::FromLuaConversionError {
|
||||
from: rhs.type_name(),
|
||||
to: "userdata",
|
||||
message: Some(format!(
|
||||
"Expected CFrame or Vector3, got {}",
|
||||
rhs.type_name()
|
||||
)),
|
||||
})
|
||||
});
|
||||
methods.add_meta_method(LuaMetaMethod::Add, |_, this, vec: Vector3| Ok(*this + vec));
|
||||
methods.add_meta_method(LuaMetaMethod::Sub, |_, this, vec: Vector3| Ok(*this - vec));
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CFrame {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let pos = self.position();
|
||||
let (rx, ry, rz) = self.orientation();
|
||||
write!(
|
||||
f,
|
||||
"{}, {}, {}, {}",
|
||||
Vector3(pos),
|
||||
Vector3(rx),
|
||||
Vector3(ry),
|
||||
Vector3(rz)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Mul for CFrame {
|
||||
type Output = Self;
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
CFrame(self.0 * rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Mul<Vector3> for CFrame {
|
||||
type Output = Vector3;
|
||||
fn mul(self, rhs: Vector3) -> Self::Output {
|
||||
Vector3(self.0.project_point3(rhs.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Add<Vector3> for CFrame {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: Vector3) -> Self::Output {
|
||||
CFrame(Mat4::from_cols(
|
||||
self.0.x_axis,
|
||||
self.0.y_axis,
|
||||
self.0.z_axis,
|
||||
self.0.w_axis + rhs.0.extend(0.0),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Sub<Vector3> for CFrame {
|
||||
type Output = Self;
|
||||
fn sub(self, rhs: Vector3) -> Self::Output {
|
||||
CFrame(Mat4::from_cols(
|
||||
self.0.x_axis,
|
||||
self.0.y_axis,
|
||||
self.0.z_axis,
|
||||
self.0.w_axis - rhs.0.extend(0.0),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RbxCFrame> for CFrame {
|
||||
fn from(v: RbxCFrame) -> Self {
|
||||
CFrame(Mat4::from_cols(
|
||||
Vector3::from(v.orientation.x).0.extend(0.0),
|
||||
Vector3::from(v.orientation.y).0.extend(0.0),
|
||||
Vector3::from(v.orientation.z).0.extend(0.0),
|
||||
Vector3::from(v.position).0.extend(1.0),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CFrame> for RbxCFrame {
|
||||
fn from(v: CFrame) -> Self {
|
||||
let (rx, ry, rz) = v.orientation();
|
||||
RbxCFrame {
|
||||
position: RbxVector3::from(Vector3(v.position())),
|
||||
orientation: RbxMatrix3::new(
|
||||
RbxVector3::from(Vector3(rx)),
|
||||
RbxVector3::from(Vector3(ry)),
|
||||
RbxVector3::from(Vector3(rz)),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
mod axes;
|
||||
mod brick_color;
|
||||
mod cframe;
|
||||
mod color3;
|
||||
mod color_sequence;
|
||||
mod color_sequence_keypoint;
|
||||
|
@ -17,6 +18,7 @@ mod vector3int16;
|
|||
|
||||
pub use axes::Axes;
|
||||
pub use brick_color::BrickColor;
|
||||
pub use cframe::CFrame;
|
||||
pub use color3::Color3;
|
||||
pub use color_sequence::ColorSequence;
|
||||
pub use color_sequence_keypoint::ColorSequenceKeypoint;
|
||||
|
@ -76,6 +78,7 @@ mod tests {
|
|||
create_tests! {
|
||||
axes: "datatypes/Axes",
|
||||
brick_color: "datatypes/BrickColor",
|
||||
cframe: "datatypes/CFrame",
|
||||
color3: "datatypes/Color3",
|
||||
color_sequence: "datatypes/ColorSequence",
|
||||
color_sequence_keypoint: "datatypes/ColorSequenceKeypoint",
|
||||
|
|
|
@ -21,6 +21,7 @@ fn make_all_datatypes(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaValue)>> {
|
|||
// Classes
|
||||
("Axes", make_dt(lua, Axes::make_table)?),
|
||||
("BrickColor", make_dt(lua, BrickColor::make_table)?),
|
||||
("CFrame", make_dt(lua, CFrame::make_table)?),
|
||||
("Color3", make_dt(lua, Color3::make_table)?),
|
||||
("ColorSequence", make_dt(lua, ColorSequence::make_table)?),
|
||||
("ColorSequenceKeypoint", make_dt(lua, ColorSequenceKeypoint::make_table)?),
|
||||
|
|
43
tests/roblox/datatypes/CFrame.luau
Normal file
43
tests/roblox/datatypes/CFrame.luau
Normal file
|
@ -0,0 +1,43 @@
|
|||
-- HACK: Make luau happy, with the mlua rust
|
||||
-- crate all globals are also present in _G
|
||||
local CFrame = _G.CFrame
|
||||
local Vector3 = _G.Vector3
|
||||
|
||||
-- Constructors & properties
|
||||
|
||||
CFrame.new()
|
||||
CFrame.new(0, 0, 0)
|
||||
CFrame.new(0 / 0, 0 / 0, 0 / 0)
|
||||
CFrame.new(0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1)
|
||||
|
||||
assert(not pcall(function()
|
||||
return CFrame.new(false)
|
||||
end))
|
||||
assert(not pcall(function()
|
||||
return CFrame.new("", "")
|
||||
end))
|
||||
assert(not pcall(function()
|
||||
return CFrame.new(newproxy(true))
|
||||
end))
|
||||
|
||||
assert(CFrame.new(1, 2, 3).X == 1)
|
||||
assert(CFrame.new(1, 2, 3).Y == 2)
|
||||
assert(CFrame.new(1, 2, 3).Z == 3)
|
||||
|
||||
-- Constants
|
||||
|
||||
assert(CFrame.identity == CFrame.new())
|
||||
assert(CFrame.identity == CFrame.new(0, 0, 0))
|
||||
assert(CFrame.identity == CFrame.Angles(0, 0, 0))
|
||||
assert(CFrame.identity == CFrame.fromOrientation(0, 0, 0))
|
||||
|
||||
-- Ops
|
||||
|
||||
assert(CFrame.new(2, 4, 8) + Vector3.new(1, 1, 2) == CFrame.new(3, 5, 10))
|
||||
assert(CFrame.new(2, 4, 8) - Vector3.new(1, 1, 2) == CFrame.new(1, 3, 6))
|
||||
assert(CFrame.new(2, 4, 8) * CFrame.new(1, 1, 2) == CFrame.new(3, 5, 10))
|
||||
assert(CFrame.new(2, 4, 8) * Vector3.new(1, 1, 2) == Vector3.new(3, 5, 10))
|
||||
|
||||
-- TODO: Check mult ops with rotated CFrames
|
||||
|
||||
-- TODO: Methods
|
Loading…
Reference in a new issue