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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

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

public readonly record struct StarColour(byte R,byte G,byte B);
public enum StellarMorphology { Star, BrownDwarf, WhiteDwarf, NeutronStar, BlackHole }
public readonly record struct StellarSprite(float Radius,float CoreRadius,float Opacity,StellarMorphology Morphology,float Corona);
public static class StellarAppearance
{
    /// <summary>
    /// Original Galaxy52B instance size is converted to IEEE half before the
    /// VS multiplies a unit camera-facing corner. Project without a pixel floor.
    /// </summary>
    public static float OriginalProjectedHalfExtent(float styleSize,double pixelsPerLightYear)
    {
        if(!float.IsFinite(styleSize)||styleSize<=0||!double.IsFinite(pixelsPerLightYear)||pixelsPerLightYear<=0)return 0;
        var projected=(double)(float)(Half)styleSize*pixelsPerLightYear;
        return double.IsFinite(projected)&&projected<=float.MaxValue?(float)projected:0;
    }
    /// <summary>
    /// Original physical BackgroundStars layer selection(3B32EE0), from the
    /// frozen shared-appearance maps50/60. Unmapped classes return layer13.
    /// Types41/42 also require the original separate black-hole renderer.
    /// </summary>
    public static int OriginalSprite(ushort classification)
    {
        var type=classification&63;
        if(type is 41 or 42)return 8;
        var luminosity=(classification>>10)&31;
        if(luminosity<=13)return 5;
        if(luminosity==24)return 6;
        return type switch
        {
            0 or 1=>0,2 or 3 or 4 or 5 or 6=>1,7 or 8 or 11=>2,9 or 10=>3,
            12 or 13 or 14 or 15 or 16=>4,17 or 18 or 19 or 20 or 21 or 22 or 23 or 24=>5,
            40=>7,41 or 42=>8,43=>9,46=>10,_=>13
        };
    }
    public static readonly IReadOnlyList<StarColour> Palette=[new(160,188,255),new(181,203,255),new(222,233,255),new(249,247,235),new(255,239,177),new(255,187,107),new(255,115,70),new(215,77,58),new(170,83,118),new(129,94,133),new(208,233,255),new(164,224,255),new(166,161,222),new(226,230,240)];
    public static int ColourIndex(string value)
    {
        var s=value.Trim().ToUpperInvariant();
        if(s.Length==0)return 13;
        if(s=="H"||s.Contains("BLACK HOLE",StringComparison.Ordinal)||s.Contains("BLACKHOLE",StringComparison.Ordinal))return 12;
        if(s=="N"||s.Contains("NEUTRON",StringComparison.Ordinal))return 11;
        if(s.StartsWith('D')||s.Contains("WHITE DWARF",StringComparison.Ordinal))return 10;
        if(s.StartsWith('W')||s.StartsWith("AEBE",StringComparison.Ordinal)||s.Contains("HERBIG",StringComparison.Ordinal))return 0;
        if(s is "TTS"||s.Contains("T TAURI",StringComparison.Ordinal))return 5;
        if(s[0]=='C'||s is "S" or "MS"||s.StartsWith("S ",StringComparison.Ordinal)||s.StartsWith("S-TYPE",StringComparison.Ordinal)||s.StartsWith("MS-TYPE",StringComparison.Ordinal))return 6;
        const string classes="OBAFGKMLTY";
        var index=classes.IndexOf(s[0]);
        return index>=0&&(s.Length==1||s[1] is ' ' or '_'||char.IsDigit(s[1]))?index:13;
    }
    public static float PhysicalRadius(string value)
    {
        var type=value.Replace("_"," ",StringComparison.Ordinal);
        if(type.Contains("Supergiant",StringComparison.OrdinalIgnoreCase)||type.Contains("Super Giant",StringComparison.OrdinalIgnoreCase))return 2.4f;
        if(type.Contains("Giant",StringComparison.OrdinalIgnoreCase))return 1.8f;
        return 1;
    }
    /// <summary>Original territory point-size mode 2; geometry mode 1 does not use it.</summary>
    public static float PowerTerritoryDistanceScale(double eyeDistance)=>
        double.IsFinite(eyeDistance)?(float)Math.Max(1,Math.Min(Math.Max(0,eyeDistance),50)/25):1;
    /// <summary>
    /// Screen-space stellar light. A star's spectral class controls its photosphere;
    /// route and selection importance controls only the marker allocation around it.
    /// Distance here is measured from the camera focus, not from Sol or the player.
    /// </summary>
    public static StellarSprite Sprite(int spectral,double distance,double pixelsPerLightYear,int importance=0,bool realistic=true,float originalSize=0)
    {
        spectral=spectral>=0&&spectral<Palette.Count?spectral:13;
        distance=double.IsFinite(distance)?Math.Max(0,distance):0;
        pixelsPerLightYear=double.IsFinite(pixelsPerLightYear)?Math.Max(.00001,pixelsPerLightYear):4;
        var morphology=spectral switch{7 or 8 or 9=>StellarMorphology.BrownDwarf,10=>StellarMorphology.WhiteDwarf,11=>StellarMorphology.NeutronStar,12=>StellarMorphology.BlackHole,_=>StellarMorphology.Star};
        var hasOriginalSize=realistic&&float.IsFinite(originalSize)&&originalSize>0&&originalSize<=8;
        var luminosity=hasOriginalSize?1:spectral switch{0=>1.28,1=>1.17,2=>1.1,3=>1.04,4=>1,5=>.9,6=>.78,7=>.64,8=>.56,9=>.48,10=>.74,11=>.69,12=>.85,_=>.9};
        // Keep distant stars sub-pixel instead of giving every catalogue entry the
        // same large opaque disc. Zooming nearby reveals a small limb-darkened core.
        var zoom=Math.Clamp(Math.Pow(pixelsPerLightYear/4,.24),.48,3.3);
        var attenuation=1/Math.Sqrt(1+distance*pixelsPerLightYear/420);
        var core=(float)Math.Clamp(1.38*luminosity*zoom*(.48+.52*attenuation),.42,6);
        // The original compact's logarithmic size replaces the estimated
        // spectral size. Keep its ratio to original uniform size .7, including
        // the original small-size range, instead of applying a pixel minimum.
        if(hasOriginalSize)core*=originalSize/GalaxyOriginalAppearance.DefaultSize;
        var opacity=(float)Math.Clamp((.3+.7*attenuation)*(spectral is 7 or 8 or 9?.68:1),.12,1);
        if(!realistic){core=Math.Clamp(core*1.4f,2.6f,4.8f);opacity=Math.Max(.78f,opacity);}
        var corona=spectral switch{7 or 8 or 9=>.065f,10=>.16f,11=>.18f,12=>0,_=>.2f};
        var radius=realistic?Math.Max(4.5f,core*6):Math.Max(3,core*1.85f);
        if(morphology==StellarMorphology.NeutronStar&&realistic)radius=Math.Max(radius,core*8);
        if(importance>=2)radius=Math.Max(radius,26);
        return new(radius,core,opacity,morphology,corona);
    }
}

public readonly record struct MapSegment(double X1,double Y1,double X2,double Y2);
public static class InfiniteGalaxyGrid
{
    public const int MaximumLines=GalaxyOriginalGrid.MaximumLines;
    // Invert the actual camera projection at the viewport corners. Only the
    // visible portion of the unbounded XZ plane is emitted, with bounded density.
    public static int Write(Span<MapSegment> output,double width,double height,double yaw,double pitch,double scale,double panX,double panY,out double step)
    {
        step=0;
        if(output.Length<8||!double.IsFinite(width+height+yaw+pitch+scale+panX+panY)||width<=0||height<=0||scale<=0||Math.Abs(Math.Sin(pitch))<.015)return 0;
        var cy=Math.Cos(yaw);var sy=Math.Sin(yaw);var sp=Math.Sin(pitch);
        var minX=double.PositiveInfinity;var maxX=double.NegativeInfinity;var minZ=minX;var maxZ=maxX;
        for(var i=0;i<4;i++)
        {
            var x=((i%2)*width-width/2-panX)/scale;var depth=((i/2)*height-height/2-panY)/(scale*sp);
            var wx=x*cy-depth*sy;var wz=x*sy+depth*cy;
            minX=Math.Min(minX,wx);maxX=Math.Max(maxX,wx);minZ=Math.Min(minZ,wz);maxZ=Math.Max(maxZ,wz);
        }
        var budget=Math.Min(MaximumLines,output.Length);var wanted=Math.Max(Math.Max(maxX-minX,maxZ-minZ)/(budget/2-4),24/(scale*Math.Abs(sp)));
        var decade=Math.Pow(10,Math.Floor(Math.Log10(wanted)));var fraction=wanted/decade;
        step=decade*(fraction<=1?1:fraction<=2?2:fraction<=5?5:10);
        if(!double.IsFinite(step)||step<=0)return 0;
        var count=0;
        for(var axis=0;axis<2;axis++)
        {
            var first=Math.Ceiling((axis==0?minX:minZ)/step);var n=(int)Math.Min(budget,Math.Floor((axis==0?maxX:maxZ)/step)-first+1);
            for(var i=0;i<n&&count<budget;i++)
            {
                var at=(first+i)*step;
                var x1=axis==0?at:minX;var x2=axis==0?at:maxX;var z1=axis==0?minZ:at;var z2=axis==0?maxZ:at;
                var ax=width/2+panX+(x1*cy+z1*sy)*scale;var ay=height/2+panY+(-x1*sy+z1*cy)*sp*scale;
                var bx=width/2+panX+(x2*cy+z2*sy)*scale;var by=height/2+panY+(-x2*sy+z2*cy)*sp*scale;
                if(Clip(ax,ay,bx,by,width,height,out var line))output[count++]=line;
            }
        }
        return count;
    }
    private static bool Clip(double x1,double y1,double x2,double y2,double width,double height,out MapSegment line)
    {
        line=default;var dx=x2-x1;var dy=y2-y1;var low=0d;var high=1d;
        if(!Bound(-dx,x1,ref low,ref high)||!Bound(dx,width-x1,ref low,ref high)||!Bound(-dy,y1,ref low,ref high)||!Bound(dy,height-y1,ref low,ref high))return false;
        line=new(x1+dx*low,y1+dy*low,x1+dx*high,y1+dy*high);return true;
    }
    private static bool Bound(double p,double q,ref double low,ref double high)
    {
        if(Math.Abs(p)<1e-12)return q>=-1e-7;
        var t=q/p;if(p<0){if(t>high)return false;low=Math.Max(low,t);}else{if(t<low)return false;high=Math.Min(high,t);}return true;
    }
}