private void LateUpdate() { if (!adapter.PluginRunning() || FlightDriver.Pause) { return; } const double degree = Math.PI / 180; var reference_rotation = (UnityEngine.QuaternionD)adapter.Plugin().CameraReferenceRotation(); var camera_roll = UnityEngine.QuaternionD.AngleAxis(camera_roll_ / degree, Vector3d.forward); if (should_transfer_camera_coordinates) { UnityEngine.QuaternionD previous_referenced_pivot = previous_camera_reference_rotation_ * (UnityEngine.QuaternionD)PlanetariumCamera.fetch.GetPivot().rotation * camera_roll; // Note that we use a single-precision quaternion here because the // double-precision one that comes with KSP does not implement Euler // angles. UnityEngine.Quaternion new_dereferenced_pivot = UnityEngine.QuaternionD.Inverse(reference_rotation) * previous_referenced_pivot; double new_heading = (new_dereferenced_pivot.eulerAngles.y - Planetarium.InverseRotAngle) * degree; double new_pitch = new_dereferenced_pivot.eulerAngles.x * degree; // The camera cannot be given nonzero roll by camera controls, but we // smoothly bring its roll to 0 over the course of a few frames to make // the change in orientation continuous, and thus easier to follow: // instantly flipping can be confusing if it is a large change, e.g. Earth // equator to Uranus equator. camera_roll_ = ((new_dereferenced_pivot.eulerAngles.z + 180) % 360 - 180) * degree; // Unity has a very mad Euler angle convention that has pitch in // [0, π/2] ∪ [3π/2, 2π]. if (new_pitch > Math.PI) { new_pitch -= 2 * Math.PI; } PlanetariumCamera.fetch.camHdg = (float)new_heading; PlanetariumCamera.fetch.camPitch = (float)new_pitch; // Use the old reference rotation for this frame: since the change to // camera heading and pitch has yet to take effect, the new one would // result in a weird orientation for one frame. Similarly, we keep the // existing |camera_roll|. reference_rotation = previous_camera_reference_rotation_; should_transfer_camera_coordinates = false; } previous_camera_reference_rotation_ = reference_rotation; // Both the scaled space and galaxy cameras are used in the flight scene as // well as map view; they should not be reoriented there. if (MapView.MapIsEnabled) { PlanetariumCamera.fetch.GetPivot().rotation = reference_rotation * (UnityEngine.QuaternionD)PlanetariumCamera.fetch.GetPivot().rotation * camera_roll; ScaledCamera.Instance.galaxyCamera.transform.rotation = reference_rotation * (UnityEngine.QuaternionD)ScaledCamera.Instance.galaxyCamera.transform. rotation * camera_roll; } if (camera_roll_ != 0) { // TODO(egg): Should we be doing this in LateUpdate? const double roll_change_per_frame = 0.1 /*radians*/; if (Math.Abs(camera_roll_) < roll_change_per_frame) { camera_roll_ = 0; } else { camera_roll_ += camera_roll_ > 0 ? -roll_change_per_frame : roll_change_per_frame; } } }
private void LateUpdate() { if (!adapter.PluginRunning() || FlightDriver.Pause) { return; } var reference_rotation = (UnityEngine.QuaternionD)adapter.Plugin().CameraReferenceRotation(); if (should_transfer_camera_coordinates) { UnityEngine.QuaternionD previous_referenced_pivot = previous_camera_reference_rotation_ * last_fresh_planetarium_camera_rotation_ * camera_roll; // Note that we use a single-precision quaternion here because the // double-precision one that comes with KSP does not implement Euler // angles. UnityEngine.Quaternion new_dereferenced_pivot = UnityEngine.QuaternionD.Inverse(reference_rotation) * previous_referenced_pivot; double new_heading = (new_dereferenced_pivot.eulerAngles.y - Planetarium.InverseRotAngle) * degree; double new_pitch = new_dereferenced_pivot.eulerAngles.x * degree; // The camera cannot be given nonzero roll by camera controls, but we // smoothly bring its roll to 0 over the course of a few frames to make // the change in orientation continuous, and thus easier to follow: // instantly flipping can be confusing if it is a large change, e.g. Earth // equator to Uranus equator. camera_roll_ = ((new_dereferenced_pivot.eulerAngles.z + 180) % 360 - 180) * degree; // Unity has a very mad Euler angle convention that has pitch in // [0, π/2] ∪ [3π/2, 2π]. if (new_pitch > Math.PI) { new_pitch -= 2 * Math.PI; } PlanetariumCamera.fetch.camHdg = (float)new_heading; PlanetariumCamera.fetch.camPitch = (float)new_pitch; // The next LateUpdate would set the pivot rotation from the camera // heading and pitch fields, but it would still be wrong for one frame // when we adjust it below, and it would not happen when the camera // controls are locked—which they most likely are. Do it ourselves. PlanetariumCamera.fetch.GetPivot().rotation = UnityEngine.Quaternion.Euler(new_dereferenced_pivot.eulerAngles.x, new_dereferenced_pivot.eulerAngles.y, 0); last_fresh_planetarium_camera_rotation_ = PlanetariumCamera.fetch.GetPivot().rotation; should_transfer_camera_coordinates = false; } previous_camera_reference_rotation_ = reference_rotation; // Both the scaled space and galaxy cameras are used in the flight scene as // well as map view; they should not be reoriented there. if (MapView.MapIsEnabled) { // The pivot does not get updated when camera controls are locked, so we // would be taking our previously corrected orientation as an uncorrected // orientation in the next frame, leading to wild spin. Don’t do that. if (InputLockManager.IsUnlocked(ControlTypes.CAMERACONTROLS)) { last_fresh_planetarium_camera_rotation_ = PlanetariumCamera.fetch.GetPivot().rotation; } PlanetariumCamera.fetch.GetPivot().rotation = reference_rotation * last_fresh_planetarium_camera_rotation_ * camera_roll; ScaledCamera.Instance.galaxyCamera.transform.rotation = reference_rotation * (UnityEngine.QuaternionD)ScaledCamera.Instance.galaxyCamera.transform. rotation * camera_roll; } if (camera_roll_ != 0) { // TODO(egg): Should we be doing this in LateUpdate? const double roll_change_per_frame = 0.1 /*radians*/; if (Math.Abs(camera_roll_) < roll_change_per_frame) { camera_roll_ = 0; } else { camera_roll_ += camera_roll_ > 0 ? -roll_change_per_frame : roll_change_per_frame; } } }