diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index d425bcb..3adb7bd 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -1896,8 +1896,7 @@ public sealed class GameWindow : IDisposable float localY = result.Position.Y - (lbY - _liveCenterY) * 192f; uint wireCellId = ((uint)lbX << 24) | ((uint)lbY << 16) | (result.CellId & 0xFFFFu); var wirePos = new System.Numerics.Vector3(localX, localY, result.Position.Z); - var wireRot = System.Numerics.Quaternion.CreateFromAxisAngle( - System.Numerics.Vector3.UnitZ, _playerController.Yaw); + var wireRot = YawToAcQuaternion(_playerController.Yaw); if (result.MotionStateChanged) { @@ -1956,6 +1955,34 @@ public sealed class GameWindow : IDisposable } } + /// + /// Convert our internal yaw (math convention: 0=+X East, PI/2=+Y North) + /// to AC's quaternion heading convention. + /// AC heading: 0=West, 90=North, 180=East, 270=South. + /// Formula from holtburger Quaternion::from_heading. + /// + private static System.Numerics.Quaternion YawToAcQuaternion(float yaw) + { + // Our yaw → AC heading in degrees: + // yaw=0 → East → AC 180°, yaw=PI/2 → North → AC 90° + // heading_deg = 180 - yaw_degrees + float yawDeg = yaw * (180f / MathF.PI); + float headingDeg = 180f - yawDeg; + if (headingDeg < 0f) headingDeg += 360f; + if (headingDeg >= 360f) headingDeg -= 360f; + + // holtburger from_heading: theta = (450 - heading_deg) in radians + float theta = (450f - headingDeg) * (MathF.PI / 180f); + float halfTheta = theta * 0.5f; + float w = MathF.Cos(halfTheta); + float z = MathF.Sin(halfTheta); + + // Canonicalize: w must be non-negative + if (w < 0f) { w = -w; z = -z; } + + return new System.Numerics.Quaternion(0f, 0f, z, w); + } + private void OnCameraModeChanged(bool isFlyMode) { if (_input is null) return;