Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions docs/config/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion zap/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}
Expand Down
36 changes: 28 additions & 8 deletions zap/src/irgen/des.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down
1 change: 1 addition & 0 deletions zap/src/irgen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ pub trait Gen {
#[derive(Debug, Clone)]
pub enum Stmt {
Local(&'static str, Option<Expr>),
LocalTuple(Vec<&'static str>, Option<Expr>),
Assign(Var, Expr),
Error(String),
Assert(Expr, Option<String>),
Expand Down
17 changes: 14 additions & 3 deletions zap/src/irgen/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())),
Expand Down
9 changes: 9 additions & 0 deletions zap/src/output/luau/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}\")")),
Expand Down