diff --git a/docs/config/types.md b/docs/config/types.md index 2bf562c7..63676280 100644 --- a/docs/config/types.md +++ b/docs/config/types.md @@ -180,6 +180,41 @@ You can also specify what kind of instance you want to accept, for example: Classes that inherit your specified class will be accepted, for example `Part`. +## CFrames + +Zap supports sending CFrames. There are two types of CFrame you may send - a regular `CFrame`, and an `AlignedCFrame`. + +CFrame rotation matrices are compressed using the axis-angle representation. + +::: danger +CFrames are orthonormalized when sent. If you need to send a CFrame that is not orthogonal, i.e. one that does not have a valid rotation matrix, it is recommended to send the components and reconstruct it on the other side. Note that use cases for this are exceedingly rare and you most likely will not have to worry about this, as the common CFrame constructors only return orthogonal CFrames. +::: + +### Aligned CFrames +When you know that a CFrame is going to be axis-aligned, it is preferrable to use the `AlignedCFrame` type. + +It uses much less bandwidth, as the rotation can just be represented as a single byte Enum of the possible axis aligned rotations. + +You can think of an axis-aligned CFrame as one whose LookVector, UpVector, and RightVector all align with the world axes in some way. Even if the RightVector is facing upwards, for example, it would still be axis-aligned. + +Position does not matter at all, only the rotation. + +::: danger +If the CFrame is not axis aligned then Zap will throw an error, so make sure to use this type carefully! Don't let this dissuade you from using it though, as the bandwidth savings can be significant. +::: + +Here are some examples of axis-aligned CFrames. +```lua +local CFrameSpecialCases = { + CFrame.Angles(0, 0, 0), + CFrame.Angles(0, math.rad(180), math.rad(90)), + CFrame.Angles(0, math.rad(-90), 0), + CFrame.Angles(math.rad(90), math.rad(-90), 0), + CFrame.Angles(0, math.rad(90), math.rad(180)), + -- and so on. there are 24 of these in total. +} +``` + ## Other Roblox Classes The following Roblox Classes are also available as types in Zap: diff --git a/zap/src/config.rs b/zap/src/config.rs index b2816b16..b15e3da1 100644 --- a/zap/src/config.rs +++ b/zap/src/config.rs @@ -181,7 +181,7 @@ impl<'src> Ty<'src> { Self::Color3 => (12, Some(12)), Self::Vector3 => (12, Some(12)), Self::AlignedCFrame => (13, Some(13)), - Self::CFrame => (48, Some(48)), + Self::CFrame => (24, Some(24)), Self::Unknown => (0, None), } } diff --git a/zap/src/irgen/des.rs b/zap/src/irgen/des.rs index 0f273415..71332479 100644 --- a/zap/src/irgen/des.rs +++ b/zap/src/irgen/des.rs @@ -281,18 +281,38 @@ impl Des { } Ty::CFrame => { self.push_local("pos", Some(self.readvector3())); - self.push_local("vX", Some(self.readvector3())); - self.push_local("vY", Some(self.readvector3())); - self.push_local("vZ", Some(self.readvector3())); + self.push_local("axisangle", Some(self.readvector3())); + self.push_local("angle", Some(Var::from("axisangle").nindex("Magnitude").into())); + // We don't need to convert the axis back to a unit vector as the constructor does that for us + // The angle is the magnitude of the axis vector + // If the magnitude is 0, there is no rotation, so just make a cframe at the right position. + // Trying to use fromAxisAngle in this situation gives NAN which is not ideal, so the branch is required. + + // if angle ~= 0 then + // value = CFrame.fromAxisAngle(axisangle, angle) + pos + // else + // value = CFrame.new(pos) + // end + + self.push_stmt(Stmt::If(Expr::Neq(Box::new("angle".into()), Box::new("0".into())))); self.push_assign( - into, - Expr::Call( - Box::new(Var::from("CFrame").nindex("fromMatrix")), - None, - vec!["pos".into(), "vX".into(), "vY".into(), "vZ".into()], + into.clone(), + Expr::Add( + Box::new(Expr::Call( + Box::new(Var::from("CFrame").nindex("fromAxisAngle")), + None, + vec!["axisangle".into(), "angle".into()], + )), + Box::new("pos".into()), ), ); + self.push_stmt(Stmt::Else); + self.push_assign( + into, + Expr::Call(Box::new(Var::from("CFrame").nindex("new")), None, vec!["pos".into()]), + ); + self.push_stmt(Stmt::End); } } } diff --git a/zap/src/irgen/mod.rs b/zap/src/irgen/mod.rs index 7ad0078f..df412fe6 100644 --- a/zap/src/irgen/mod.rs +++ b/zap/src/irgen/mod.rs @@ -249,6 +249,7 @@ pub trait Gen { #[derive(Debug, Clone)] pub enum Stmt { Local(&'static str, Option), + LocalTuple(Vec<&'static str>, Option), Assign(Var, Expr), Error(String), Assert(Expr, Option), diff --git a/zap/src/irgen/ser.rs b/zap/src/irgen/ser.rs index c312023a..a1c8dce7 100644 --- a/zap/src/irgen/ser.rs +++ b/zap/src/irgen/ser.rs @@ -266,10 +266,21 @@ impl Ser { } Ty::CFrame => { + // local axis, angle = Value:ToAxisAngle() + self.push_stmt(Stmt::LocalTuple( + vec!["axis", "angle"], + Some(Expr::Call(from.clone().into(), Some("ToAxisAngle".into()), vec![])), + )); + + // axis = axis * angle + // store the angle into the axis, as it is a unit vector, so the magnitude can be used to encode a number + self.push_stmt(Stmt::Assign( + Var::Name("axis".into()), + Expr::Mul(Box::new("axis".into()), Box::new("angle".into())), + )); + self.push_ty(&Ty::Vector3, from.clone().nindex("Position")); - self.push_ty(&Ty::Vector3, from.clone().nindex("XVector")); - self.push_ty(&Ty::Vector3, from.clone().nindex("YVector")); - self.push_ty(&Ty::Vector3, from.clone().nindex("ZVector")); + self.push_ty(&Ty::Vector3, "axis".into()); } Ty::Boolean => self.push_writeu8(from_expr.and(1.0.into()).or(0.0.into())), diff --git a/zap/src/output/luau/mod.rs b/zap/src/output/luau/mod.rs index c0b20b27..eb4baff1 100644 --- a/zap/src/output/luau/mod.rs +++ b/zap/src/output/luau/mod.rs @@ -31,6 +31,15 @@ pub trait Output { self.push_line(&format!("local {name}")); } } + Stmt::LocalTuple(var, expr) => { + let items = var.join(", "); + + if let Some(expr) = expr { + self.push_line(&format!("local {items} = {expr}")); + } else { + self.push_line(&format!("local {items}")); + } + } Stmt::Assign(var, expr) => self.push_line(&format!("{var} = {expr}")), Stmt::Error(msg) => self.push_line(&format!("error(\"{msg}\")")),