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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
namespace XFEEDToolBox.Core.Galaxy;

/// <summary>Fills the render budget across occupied screen cells, keeping sparse edges and mandatory markers.</summary>
public sealed class GalaxyMapLod
{
    /// <summary>Priority retains an eligible published point, never overrides a query or visibility rejection.</summary>
    public static bool IsPriority(GalacticSystem system,GalaxyCatalogueSelection? selection,bool eligible,bool retainedMarker=false)
        =>eligible&&(retainedMarker||selection?.IsForced(system)==true);

    public const int Columns=32,Rows=18,Cells=Columns*Rows;
    private readonly int[] counts=new int[Cells],quotas=new int[Cells],seen=new int[Cells];
    private double width=1,height=1;
    private int capacity,priorityCount,priorityAccepted,priorityQuota;
    public int Candidates {get;private set;}
    public int Budget {get;private set;}
    public int OccupiedCells {get;private set;}
    public bool Active=>Candidates>capacity;
    public void Begin(double viewportWidth,double viewportHeight,int maximum)
    {
        width=Math.Max(1,viewportWidth);height=Math.Max(1,viewportHeight);capacity=Math.Max(0,maximum);
        Candidates=Budget=OccupiedCells=priorityCount=priorityAccepted=priorityQuota=0;
        Array.Clear(counts);Array.Clear(quotas);Array.Clear(seen);
    }
    private int Cell(double x,double y)=>Math.Clamp((int)(x/width*Columns),0,Columns-1)+Math.Clamp((int)(y/height*Rows),0,Rows-1)*Columns;
    public void Count(double x,double y,bool priority=false)
    {
        Candidates++;if(priority){priorityCount++;return;}counts[Cell(x,y)]++;
    }
    public void Seal()
    {
        priorityQuota=Math.Min(capacity,priorityCount);Budget=Math.Min(capacity,Candidates);
        var available=Budget-priorityQuota;var largest=0;
        foreach(var count in counts){if(count>0)OccupiedCells++;largest=Math.Max(largest,count);}
        var low=0;var high=largest;
        while(low<high)
        {
            var level=low+(high-low+1)/2;long needed=0;
            foreach(var count in counts)needed+=Math.Min(count,level);
            if(needed<=available)low=level;else high=level-1;
        }
        var used=0;for(var i=0;i<Cells;i++){quotas[i]=Math.Min(counts[i],low);used+=quotas[i];}
        for(var i=0;i<Cells&&used<available;i++)if(quotas[i]<counts[i]){quotas[i]++;used++;}
    }
    public bool Accept(double x,double y,bool priority=false)
    {
        if(priority)return priorityAccepted++<priorityQuota;
        var cell=Cell(x,y);var count=counts[cell];if(count==0)return false;
        var at=seen[cell]++;var quota=quotas[cell];
        // Sample the entire source order within each overflowing tile; taking its
        // first N entries would still favour the nearest central depth slice.
        return (long)(at+1)*quota/count>(long)at*quota/count;
    }
}