XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
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();
        }
    }

    /// <summary>Deterministic ordinary camera ticks, without changing input time.</summary>
    internal void AdvanceCameraMotionForProbe(int ticks=1)
    {
        if(ticks is <0 or >4096)throw new ArgumentOutOfRangeException(nameof(ticks));
        for(var i=0;i<ticks&&cameraTarget is not null;i++)AdvanceCameraMotion();
    }

    internal void FinishCameraInputForProbe()=>FinishCameraInput();

    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<GalaxyOriginalCameraControls.MinimumDistance)
            throw new ArgumentException("离线相机夹具无效。");
        CancelCameraMotion();cameraTimer.Stop();cameraAnimation=null;
        applyingCameraPose=true;
        try{panX=panY=0;Centre=pose.Focus;yaw=pose.Yaw;pitch=pose.Pitch;scale=CameraFocalLength/pose.Distance;}
        finally{applyingCameraPose=false;}
        Invalidate();
    }

    private void ZoomCameraTarget(double factor,Point anchor,bool? craning=null)
    {
        var target=EnsureCameraTarget();
        var camera=TargetCamera(target);
        var before=camera.UnprojectOnFocusPlane(anchor.X,anchor.Y);
        var distance=Math.Clamp(target.Distance/factor,GalaxyOriginalCameraControls.MinimumDistance,GalaxyOriginalCameraControls.MaximumDistance);
        var scaled=TargetCamera(target with{Distance=distance});
        var after=scaled.UnprojectOnFocusPlane(anchor.X,anchor.Y);
        // Pointer anchoring is retained application behavior. Original zoom
        // axis gain/bindings are not claimed to be a per-pixel calibration.
        target=target with{Distance=distance,Focus=new(target.Focus.X+before.X-after.X,target.Focus.Y+before.Y-after.Y,target.Focus.Z+before.Z-after.Z)};
        var active=craning??CameraCraneActive;
        SetCameraTarget(target,active,active?1:0);
    }
}