using Windows.Foundation; using XFEEDToolBox.Core.Galaxy; using XFEEDToolBox.Core.Models; namespace XFEEDToolBox.Controls; public sealed partial class GalaxyMapView { private readonly Microsoft.UI.Dispatching.DispatcherQueueTimer cameraMotionTimer; private GalaxyCameraPose? cameraTarget; private long cameraMotionGeneration; private bool applyingCameraPose; // Explicit application stop tolerances: the original two BSS epsilon // values cannot be recovered from the offline image. Factors are original. private static readonly GalaxyCameraStopThresholds MotionStop = new(.0001, .000001, .0001); internal bool CameraMotionPendingForProbe => cameraTarget is not null; internal GalaxyCameraPose? TargetCameraPoseForProbe => cameraTarget; internal GalaxyCameraPose ActualCameraPoseForProbe => ActualCameraPose; private double CameraFocalLength => GalaxyOriginalProjection.FocalLength(Math.Max(1,ActualWidth),Math.Max(1,ActualHeight)); private GalaxyCameraPose ActualCameraPose { get { var camera=Camera;return new(camera.Focus,yaw,pitch,camera.Distance); } } private GalaxyCameraPose EnsureCameraTarget() { cameraTimer.Stop();cameraAnimation=null; if(cameraTarget is {} pending)return pending; var actual=ActualCameraPose; // Normalize the old screen-pan representation before beginning a // world-focus animation, preserving every star's current projection. if(panX!=0||panY!=0) { applyingCameraPose=true; try{panX=panY=0;Centre=actual.Focus;} finally{applyingCameraPose=false;} } cameraTarget=actual; return actual; } private GalaxyCamera TargetCamera(GalaxyCameraPose target) => GalaxyCamera.Create(target.Focus, Math.Max(1,ActualWidth),Math.Max(1,ActualHeight),target.Yaw,target.Pitch,CameraFocalLength/target.Distance); private void SetCameraTarget(GalaxyCameraPose target,bool cameraCraning=false,double craneInput=0) { cameraTarget=GalaxyOriginalCameraControls.PrepareTarget(target,cameraCraning,craneInput); cameraMotionGeneration++; if(loaded&&pageActive&&!cameraMotionTimer.IsRunning)cameraMotionTimer.Start(); } private void FinishCameraInput() { if(cameraTarget is {} target) SetCameraTarget(target,CameraCraneActive,CameraCraneActive?1:0); } private bool CameraCraneActive => cameraKeys.Contains(Windows.System.VirtualKey.R) ||cameraKeys.Contains(Windows.System.VirtualKey.F) ||dragging&&mouseGesture&&GalaxyMouseControls.Gesture(mouseButtons)==GalaxyMouseGesture.Crane; private void CancelCameraMotion() { cameraMotionTimer.Stop();cameraTarget=null;cameraMotionGeneration++; } private void CancelCameraInput() { CancelCameraMotion();cameraTimer.Stop();cameraAnimation=null; cameraKeys.Clear();keyboardTimer.Stop(); dragging=false;mouseGesture=false;mouseButtons=GalaxyMouseButtons.None; CancelHold();ReleasePointerCaptures(); } private void AdvanceCameraMotion() { if(!loaded||!pageActive||!IsInteractive){CancelCameraMotion();return;} if(cameraTarget is not {} target){cameraMotionTimer.Stop();return;} var generation=cameraMotionGeneration; var actual=ActualCameraPose; var next=GalaxyOriginalCameraControls.Smooth(actual,target,MotionStop); yaw=next.Yaw;pitch=next.Pitch; scale=CameraFocalLength/next.Distance; MoveCameraFocus(next.Focus); // Floor snapping publishes selection synchronously. A subscriber can // recenter or start another motion from that callback; this old tick // must never cancel the replacement target or publish its old state. if(generation!=cameraMotionGeneration)return; Invalidate();ScheduleViewport(); if(next==target) { CancelCameraMotion();FlushViewport(); } } /// Deterministic ordinary camera ticks, without changing input time. internal void AdvanceCameraMotionForProbe(int ticks=1) { if(ticks is <0 or >4096)throw new ArgumentOutOfRangeException(nameof(ticks)); for(var i=0;iFinishCameraInput(); internal void SetCameraPoseForProbe(GalaxyCameraPose pose) { var args=Environment.GetCommandLineArgs(); if(!args.Contains("--offline-preview")||!args.Contains("--probe-galaxy-rework"))throw new InvalidOperationException("仅允许独立离线星图验收设置相机夹具。"); if(!pose.Focus.IsValid||!double.IsFinite(pose.Yaw)||!double.IsFinite(pose.Pitch)||!double.IsFinite(pose.Distance)||pose.Distance