mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 13:00:37 +00:00
Fix CFrame math
This commit is contained in:
parent
93b83a5874
commit
a82624023f
3 changed files with 139 additions and 32 deletions
|
@ -8,6 +8,12 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issues with CFrame math operations
|
||||
|
||||
## `0.6.4` - March 26th, 2023
|
||||
|
||||
### Fixed
|
||||
|
|
|
@ -42,38 +42,36 @@ impl CFrame {
|
|||
// 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,
|
||||
)))
|
||||
},
|
||||
)?,
|
||||
lua.create_function(|_, (from, to, up): (Vector3, Vector3, Option<Vector3>)| {
|
||||
Ok(CFrame(look_at(
|
||||
from.0,
|
||||
to.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)))
|
||||
Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, 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)))
|
||||
Ok(CFrame(Mat4::from_euler(EulerRot::YXZ, ry, rx, rz)))
|
||||
})?,
|
||||
)?;
|
||||
datatype_table.set(
|
||||
"Angles",
|
||||
lua.create_function(|_, (rx, ry, rz): (f32, f32, f32)| {
|
||||
Ok(CFrame(Mat4::from_euler(EulerRot::ZYX, rx, ry, rz)))
|
||||
Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, 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)))
|
||||
Ok(CFrame(Mat4::from_euler(EulerRot::YXZ, ry, rx, rz)))
|
||||
})?,
|
||||
)?;
|
||||
datatype_table.set(
|
||||
|
@ -99,7 +97,7 @@ impl CFrame {
|
|||
)?;
|
||||
// Dynamic args constructor
|
||||
type ArgsPos = Vector3;
|
||||
type ArgsLook = (Vector3, Vector3);
|
||||
type ArgsLook = (Vector3, Vector3, Option<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);
|
||||
|
@ -110,8 +108,12 @@ impl CFrame {
|
|||
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((from, to, up)) = ArgsLook::from_lua_multi(args.clone(), lua) {
|
||||
Ok(CFrame(look_at(
|
||||
from.0,
|
||||
to.0,
|
||||
up.unwrap_or(Vector3(Vec3::Y)).0,
|
||||
)))
|
||||
} 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)) =
|
||||
|
@ -208,20 +210,22 @@ impl LuaUserData for CFrame {
|
|||
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,
|
||||
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))
|
||||
Ok(Quat::from_mat4(&this.0).to_euler(EulerRot::XYZ))
|
||||
});
|
||||
methods.add_method("ToEulerAnglesYXZ", |_, this, ()| {
|
||||
Ok(Quat::from_mat4(&this.0).to_euler(EulerRot::ZXY))
|
||||
let (ry, rx, rz) = Quat::from_mat4(&this.0).to_euler(EulerRot::YXZ);
|
||||
Ok((rx, ry, rz))
|
||||
});
|
||||
methods.add_method("ToOrientation", |_, this, ()| {
|
||||
Ok(Quat::from_mat4(&this.0).to_euler(EulerRot::ZXY))
|
||||
let (ry, rx, rz) = Quat::from_mat4(&this.0).to_euler(EulerRot::YXZ);
|
||||
Ok((rx, ry, rz))
|
||||
});
|
||||
methods.add_method("ToAxisAngle", |_, this, ()| {
|
||||
let (axis, angle) = Quat::from_mat4(&this.0).to_axis_angle();
|
||||
|
@ -329,3 +333,22 @@ impl From<CFrame> for DomCFrame {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a matrix at the position `from`, looking towards `to`.
|
||||
|
||||
[`glam`] does provide functions such as [`look_at_lh`], [`look_at_rh`] and more but
|
||||
they all create view matrices for camera transforms which is not what we want here.
|
||||
*/
|
||||
fn look_at(from: Vec3, to: Vec3, up: Vec3) -> Mat4 {
|
||||
let dir = (to - from).normalize();
|
||||
let xaxis = up.cross(dir).normalize();
|
||||
let yaxis = dir.cross(xaxis).normalize();
|
||||
|
||||
Mat4::from_cols(
|
||||
Vec3::new(xaxis.x, yaxis.x, dir.x).extend(0.0),
|
||||
Vec3::new(xaxis.y, yaxis.y, dir.y).extend(0.0),
|
||||
Vec3::new(xaxis.z, yaxis.z, dir.z).extend(0.0),
|
||||
from.extend(1.0),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,42 @@ local roblox = require("@lune/roblox") :: any
|
|||
local CFrame = roblox.CFrame
|
||||
local Vector3 = roblox.Vector3
|
||||
|
||||
local COMPONENT_NAMES =
|
||||
{ "X", "Y", "Z", "R00", "R01", "R02", "R10", "R11", "R12", "R20", "R21", "R22" }
|
||||
local function assertEq(actual, expected)
|
||||
local actComps: { number } = { actual:GetComponents() }
|
||||
local expComps: { number } = { expected:GetComponents() }
|
||||
for index, actComp in actComps do
|
||||
local expComp = expComps[index]
|
||||
if math.abs(expComp - actComp) >= (1 / 512) then
|
||||
local r0 = Vector3.new(actual:ToOrientation())
|
||||
local r1 = Vector3.new(expected:ToOrientation())
|
||||
error(
|
||||
string.format(
|
||||
"Expected component '%s' to be %.2f, got %.2f"
|
||||
.. "\nActual: %.2f, %.2f, %.2f | %.2f, %.2f, %.2f"
|
||||
.. "\nExpected: %.2f, %.2f, %.2f | %.2f, %.2f, %.2f",
|
||||
COMPONENT_NAMES[index],
|
||||
expComp,
|
||||
actComp,
|
||||
actual.Position.X,
|
||||
actual.Position.Y,
|
||||
actual.Position.Z,
|
||||
math.deg(r0.X),
|
||||
math.deg(r0.Y),
|
||||
math.deg(r0.Z),
|
||||
expected.Position.X,
|
||||
expected.Position.Y,
|
||||
expected.Position.Z,
|
||||
math.deg(r1.X),
|
||||
math.deg(r1.Y),
|
||||
math.deg(r1.Z)
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Constructors & properties
|
||||
|
||||
CFrame.new()
|
||||
|
@ -23,20 +59,62 @@ assert(CFrame.new(1, 2, 3).X == 1)
|
|||
assert(CFrame.new(1, 2, 3).Y == 2)
|
||||
assert(CFrame.new(1, 2, 3).Z == 3)
|
||||
|
||||
assertEq(
|
||||
CFrame.fromMatrix(
|
||||
Vector3.new(1, 2, 3),
|
||||
Vector3.new(1, 0, 0),
|
||||
Vector3.new(0, 1, 0),
|
||||
Vector3.new(0, 0, 1)
|
||||
),
|
||||
CFrame.new(1, 2, 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))
|
||||
assertEq(CFrame.identity, CFrame.new())
|
||||
assertEq(CFrame.identity, CFrame.new(0, 0, 0))
|
||||
assertEq(CFrame.identity, CFrame.Angles(0, 0, 0))
|
||||
assertEq(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))
|
||||
assertEq(CFrame.new(2, 4, 8) + Vector3.new(1, 1, 2), CFrame.new(3, 5, 10))
|
||||
assertEq(CFrame.new(2, 4, 8) - Vector3.new(1, 1, 2), CFrame.new(1, 3, 6))
|
||||
assertEq(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
|
||||
-- Mult ops with rotated CFrames
|
||||
|
||||
-- TODO: Methods
|
||||
assertEq(
|
||||
CFrame.fromOrientation(0, math.rad(90), 0) * CFrame.fromOrientation(math.rad(5), 0, 0),
|
||||
CFrame.fromOrientation(math.rad(5), math.rad(90), 0)
|
||||
)
|
||||
assertEq(
|
||||
CFrame.fromOrientation(0, math.rad(90), 0) * CFrame.new(0, 0, -5),
|
||||
CFrame.new(-5, 0, 0) * CFrame.fromOrientation(0, math.rad(90), 0)
|
||||
)
|
||||
|
||||
-- World & object space conversions
|
||||
|
||||
local offset = CFrame.new(0, 0, -5)
|
||||
assert(offset:ToWorldSpace(offset).Z == offset.Z * 2)
|
||||
assert(offset:ToObjectSpace(offset).Z == 0)
|
||||
|
||||
local world = CFrame.fromOrientation(0, math.rad(90), 0) * CFrame.new(0, 0, -5)
|
||||
local world2 = CFrame.fromOrientation(0, -math.rad(90), 0) * CFrame.new(0, 0, -5)
|
||||
assertEq(CFrame.identity:ToObjectSpace(world), world)
|
||||
assertEq(
|
||||
world:ToObjectSpace(world2),
|
||||
CFrame.fromOrientation(0, math.rad(180), 0) * CFrame.new(0, 0, -10)
|
||||
)
|
||||
|
||||
-- Look
|
||||
|
||||
assertEq(CFrame.fromOrientation(0, math.rad(90), 0), CFrame.lookAt(Vector3.zero, -Vector3.xAxis))
|
||||
assertEq(CFrame.fromOrientation(0, -math.rad(90), 0), CFrame.lookAt(Vector3.zero, Vector3.xAxis))
|
||||
assertEq(
|
||||
CFrame.new(0, 0, -5) * CFrame.fromOrientation(0, math.rad(90), 0),
|
||||
CFrame.lookAt(Vector3.new(0, 0, -5), Vector3.new(0, 0, -5) - Vector3.xAxis)
|
||||
)
|
||||
|
||||
-- TODO: More methods
|
||||
|
|
Loading…
Reference in a new issue