using XFEEDToolBox.Core.Models;
namespace XFEEDToolBox.Core.Galaxy;
/// <summary>
/// The ordinary original catalogue descriptor (3A81510/3C14AF0). Radii select
/// whole mass-code cells, not individual stars inside a point-distance sphere.
/// </summary>
public sealed class GalaxyCatalogueLodDescriptor
{
private readonly IReadOnlyList<double> radii;
internal GalaxyCatalogueLodDescriptor(Coordinates centre,double density,uint qualityStars,double modifier,
double multiplier,double expansion,double baseRadius,double innerRadius,double outerRadius)
{
Centre=centre;SurfaceDensity=density;QualityStarCount=qualityStars;Dd4Modifier=modifier;
RuntimeRangeMultiplier=multiplier;SparseExpansionFactor=expansion;BaseRadius=baseRadius;
InnerRadius=innerRadius;OuterRadius=outerRadius;
radii=Array.AsReadOnly(new[]{innerRadius,innerRadius,innerRadius,innerRadius,innerRadius,innerRadius,innerRadius,outerRadius});
}
public Coordinates Centre {get;}
public double SurfaceDensity {get;}
public uint QualityStarCount {get;}
public uint EffectiveQualityStarCount=>Math.Max(500u,QualityStarCount);
public double Dd4Modifier {get;}
public double RuntimeRangeMultiplier {get;}
public double SparseExpansionFactor {get;}
public double BaseRadius {get;}
public double InnerRadius {get;}
public double OuterRadius {get;}
public IReadOnlyList<double> MassRadii=>radii;
public double RadiusForMassCode(int massCode)=>massCode is >=0 and <8?radii[massCode]:
throw new ArgumentOutOfRangeException(nameof(massCode));
/// <summary>3C353C0: strict sphere/cell-AABB intersection, including boundary cells in full.</summary>
public bool IntersectsCell(GalaxyQueryBounds cell,int massCode)
{
ArgumentNullException.ThrowIfNull(cell);
var radius=RadiusForMassCode(massCode);
if(cell.Minimum is not{IsValid:true}||cell.Maximum is not{IsValid:true}||
cell.Minimum.X>cell.Maximum.X||cell.Minimum.Y>cell.Maximum.Y||cell.Minimum.Z>cell.Maximum.Z)
throw new ArgumentException("星区边界无效。",nameof(cell));
static double Axis(double p,double minimum,double maximum)=>Math.Max(Math.Max(minimum-p,p-maximum),0);
var x=Axis(Centre.X,cell.Minimum.X,cell.Maximum.X);var y=Axis(Centre.Y,cell.Minimum.Y,cell.Maximum.Y);
var z=Axis(Centre.Z,cell.Minimum.Z,cell.Maximum.Z);
// The original divides distance by radius and tests 1-result>0. At
// radius zero this rejects even a cell containing the centre (0/0).
return radius>0&&Math.Sqrt(x*x+y*y+z*z)<radius;
}
public bool IntersectsCell(ulong cellAddress,Coordinates absoluteOrigin)=>
IntersectsCell(GalaxyOriginalCatalogueLod.CellBounds(cellAddress,absoluteOrigin),(int)(cellAddress&7));
}
/// <summary>
/// Pure double-precision port of the recovered ordinary range formula. No
/// current quality level, DD4 mode/fade, or runtime global is guessed here.
/// </summary>
public static class GalaxyOriginalCatalogueLod
{
// Preserve the actual single-precision source operands in double arithmetic.
public const double DensityThreshold=(double).3f;
public const double FourPi=(double)12.566370964050293f;
public const double DensityCoefficient=(double).2864789068698883f;
public const double CubeRootExponent=(double).3333333432674408f;
public const double MaximumBaseRadius=200;
public const double ConstructorSparseExpansionFactor=4;
public static double BaseRadius(double surfaceDensity,uint qualityStarCount)
{
Finite(surfaceDensity,nameof(surfaceDensity));
var density=Math.Clamp(surfaceDensity,0,1);
var stars=Math.Max(500d,qualityStarCount);
var numerator=3*stars;
var minimum=numerator/(Math.Pow(MaximumBaseRadius,3)*FourPi);
var densityTerm=Math.Pow(density,3.5)*DensityCoefficient;
return Math.Pow(numerator/(Math.Max(minimum,densityTerm)*FourPi),CubeRootExponent);
}
public static GalaxyCatalogueLodDescriptor Create(Coordinates centre,double surfaceDensity,uint qualityStarCount,
double dd4Modifier,double runtimeRangeMultiplier,double sparseExpansionFactor)
{
if(centre is not{IsValid:true})throw new ArgumentException("星图目录中心无效。",nameof(centre));
Finite(surfaceDensity,nameof(surfaceDensity));Nonnegative(dd4Modifier,nameof(dd4Modifier));
Nonnegative(runtimeRangeMultiplier,nameof(runtimeRangeMultiplier));Nonnegative(sparseExpansionFactor,nameof(sparseExpansionFactor));
var basic=BaseRadius(surfaceDensity,qualityStarCount);
var inner=basic*dd4Modifier*runtimeRangeMultiplier;
var fraction=Math.Clamp((surfaceDensity-DensityThreshold)/DensityThreshold,0,1);
var outer=inner+(1-fraction*fraction)*sparseExpansionFactor*10*128;
if(!double.IsFinite(inner)||!double.IsFinite(outer))throw new ArgumentOutOfRangeException(nameof(runtimeRangeMultiplier));
return new(centre,surfaceDensity,qualityStarCount,dd4Modifier,runtimeRangeMultiplier,sparseExpansionFactor,basic,inner,outer);
}
/// <summary>Unknown source values remain unknown; null is never silently replaced with 500 or image defaults.</summary>
public static bool TryCreate(Coordinates centre,double? surfaceDensity,uint? qualityStarCount,double? dd4Modifier,
double? runtimeRangeMultiplier,double? sparseExpansionFactor,out GalaxyCatalogueLodDescriptor? descriptor)
{
descriptor=null;
if(centre is not{IsValid:true}||surfaceDensity is not{} d||qualityStarCount is not{} n||
dd4Modifier is not{} modifier||runtimeRangeMultiplier is not{} multiplier||sparseExpansionFactor is not{} expansion||
!double.IsFinite(d)||!double.IsFinite(modifier)||modifier<0||!double.IsFinite(multiplier)||multiplier<0||
!double.IsFinite(expansion)||expansion<0)return false;
try{descriptor=Create(centre,d,n,modifier,multiplier,expansion);return true;}
catch(ArgumentException){return false;}
}
/// <summary>3B8E040: unavailable/default settings use Eye; a ready nonzero original view mode uses Focus.</summary>
public static Coordinates QueryCentre(GalaxyCamera camera,int? originalViewMode)=>
originalViewMode.GetValueOrDefault()!=0?camera.Focus:camera.Eye;
/// <summary>3B46FD0 clamps the absolute float input before publishing the ordinary descriptor centre.</summary>
public static Coordinates ClampCentre(Coordinates relativeInput, Coordinates absoluteOrigin)
{
if (!relativeInput.IsValid || !absoluteOrigin.IsValid) throw new ArgumentException("原星图坐标无效。");
var x = (float)(relativeInput.X + absoluteOrigin.X);
var y = (float)(relativeInput.Y + absoluteOrigin.Y);
var z = (float)(relativeInput.Z + absoluteOrigin.Z);
if (!float.IsFinite(x) || !float.IsFinite(y) || !float.IsFinite(z)) throw new ArgumentOutOfRangeException(nameof(relativeInput));
return new(Math.Clamp(x, 100f, 163738f) - absoluteOrigin.X,
Math.Clamp(y, 100f, 81818f) - absoluteOrigin.Y, Math.Clamp(z, 100f, 163738f) - absoluteOrigin.Z);
}
/// <summary>
/// The ordinary DD4 range fade uses the raw input, not the clamped centre.
/// The debug region replaces the descriptor callback and is deliberately
/// rejected here rather than being passed through the ordinary formula.
/// </summary>
public static bool TryOrdinaryModifier(Coordinates relativeInput, Coordinates absoluteOrigin, bool? smallMode,
Coordinates? referenceAbsolutePosition, bool? debugRangeEnabled, int? originalViewMode, out double modifier)
{
modifier = 0;
if (!relativeInput.IsValid || !absoluteOrigin.IsValid || smallMode is null) return false;
if (smallMode == true) { modifier = 1; return true; }
if (debugRangeEnabled is null || debugRangeEnabled == true && originalViewMode is null || referenceAbsolutePosition is not { IsValid: true } reference) return false;
var x = (float)(relativeInput.X + absoluteOrigin.X);
var y = (float)(relativeInput.Y + absoluteOrigin.Y);
var z = (float)(relativeInput.Z + absoluteOrigin.Z);
if (!float.IsFinite(x) || !float.IsFinite(y) || !float.IsFinite(z)) return false;
static float Square(float value) => value * value;
if (debugRangeEnabled == true && originalViewMode == 2 &&
Square(x - 49985f) + Square(y - 40985f) + Square(z - 24105f) < 4000000f) return false;
var distance = MathF.Sqrt(Square(x - (float)reference.X) + Square(y - (float)reference.Y) + Square(z - (float)reference.Z));
var fade = (distance - 1000f) / -500f;
if (!float.IsFinite(fade)) return false;
modifier = Math.Clamp(fade, 0, 1);
return true;
}
/// <summary>Cell coordinates are absolute galactic ly; absoluteOrigin converts them to the caller's coordinate frame.</summary>
public static GalaxyQueryBounds CellBounds(ulong cellAddress,Coordinates absoluteOrigin)
{
if(absoluteOrigin is not{IsValid:true})throw new ArgumentException("银河坐标原点无效。",nameof(absoluteOrigin));
if(cellAddress>=1UL<<55)throw new ArgumentOutOfRangeException(nameof(cellAddress));
var mass=(int)(cellAddress&7);var size=10d*(1<<mass);
var x=(((cellAddress>>(30-2*mass))<<mass)&0x3FFF)*10d-absoluteOrigin.X;
var y=(((cellAddress>>(17-mass))<<mass)&0x1FFF)*10d-absoluteOrigin.Y;
var z=(((cellAddress>>3)<<mass)&0x3FFF)*10d-absoluteOrigin.Z;
return new(new(x,y,z),new(x+size,y+size,z+size));
}
private static void Finite(double value,string name)
{if(!double.IsFinite(value))throw new ArgumentOutOfRangeException(name);}
private static void Nonnegative(double value,string name)
{Finite(value,name);if(value<0)throw new ArgumentOutOfRangeException(name);}
}
using XFEEDToolBox.Core.Models;
namespace XFEEDToolBox.Core.Galaxy;
/// <summary>
/// The ordinary original catalogue descriptor (3A81510/3C14AF0). Radii select
/// whole mass-code cells, not individual stars inside a point-distance sphere.
/// </summary>
public sealed class GalaxyCatalogueLodDescriptor
{
private readonly IReadOnlyList<double> radii;
internal GalaxyCatalogueLodDescriptor(Coordinates centre,double density,uint qualityStars,double modifier,
double multiplier,double expansion,double baseRadius,double innerRadius,double outerRadius)
{
Centre=centre;SurfaceDensity=density;QualityStarCount=qualityStars;Dd4Modifier=modifier;
RuntimeRangeMultiplier=multiplier;SparseExpansionFactor=expansion;BaseRadius=baseRadius;
InnerRadius=innerRadius;OuterRadius=outerRadius;
radii=Array.AsReadOnly(new[]{innerRadius,innerRadius,innerRadius,innerRadius,innerRadius,innerRadius,innerRadius,outerRadius});
}
public Coordinates Centre {get;}
public double SurfaceDensity {get;}
public uint QualityStarCount {get;}
public uint EffectiveQualityStarCount=>Math.Max(500u,QualityStarCount);
public double Dd4Modifier {get;}
public double RuntimeRangeMultiplier {get;}
public double SparseExpansionFactor {get;}
public double BaseRadius {get;}
public double InnerRadius {get;}
public double OuterRadius {get;}
public IReadOnlyList<double> MassRadii=>radii;
public double RadiusForMassCode(int massCode)=>massCode is >=0 and <8?radii[massCode]:
throw new ArgumentOutOfRangeException(nameof(massCode));
/// <summary>3C353C0: strict sphere/cell-AABB intersection, including boundary cells in full.</summary>
public bool IntersectsCell(GalaxyQueryBounds cell,int massCode)
{
ArgumentNullException.ThrowIfNull(cell);
var radius=RadiusForMassCode(massCode);
if(cell.Minimum is not{IsValid:true}||cell.Maximum is not{IsValid:true}||
cell.Minimum.X>cell.Maximum.X||cell.Minimum.Y>cell.Maximum.Y||cell.Minimum.Z>cell.Maximum.Z)
throw new ArgumentException("星区边界无效。",nameof(cell));
static double Axis(double p,double minimum,double maximum)=>Math.Max(Math.Max(minimum-p,p-maximum),0);
var x=Axis(Centre.X,cell.Minimum.X,cell.Maximum.X);var y=Axis(Centre.Y,cell.Minimum.Y,cell.Maximum.Y);
var z=Axis(Centre.Z,cell.Minimum.Z,cell.Maximum.Z);
// The original divides distance by radius and tests 1-result>0. At
// radius zero this rejects even a cell containing the centre (0/0).
return radius>0&&Math.Sqrt(x*x+y*y+z*z)<radius;
}
public bool IntersectsCell(ulong cellAddress,Coordinates absoluteOrigin)=>
IntersectsCell(GalaxyOriginalCatalogueLod.CellBounds(cellAddress,absoluteOrigin),(int)(cellAddress&7));
}
/// <summary>
/// Pure double-precision port of the recovered ordinary range formula. No
/// current quality level, DD4 mode/fade, or runtime global is guessed here.
/// </summary>
public static class GalaxyOriginalCatalogueLod
{
// Preserve the actual single-precision source operands in double arithmetic.
public const double DensityThreshold=(double).3f;
public const double FourPi=(double)12.566370964050293f;
public const double DensityCoefficient=(double).2864789068698883f;
public const double CubeRootExponent=(double).3333333432674408f;
public const double MaximumBaseRadius=200;
public const double ConstructorSparseExpansionFactor=4;
public static double BaseRadius(double surfaceDensity,uint qualityStarCount)
{
Finite(surfaceDensity,nameof(surfaceDensity));
var density=Math.Clamp(surfaceDensity,0,1);
var stars=Math.Max(500d,qualityStarCount);
var numerator=3*stars;
var minimum=numerator/(Math.Pow(MaximumBaseRadius,3)*FourPi);
var densityTerm=Math.Pow(density,3.5)*DensityCoefficient;
return Math.Pow(numerator/(Math.Max(minimum,densityTerm)*FourPi),CubeRootExponent);
}
public static GalaxyCatalogueLodDescriptor Create(Coordinates centre,double surfaceDensity,uint qualityStarCount,
double dd4Modifier,double runtimeRangeMultiplier,double sparseExpansionFactor)
{
if(centre is not{IsValid:true})throw new ArgumentException("星图目录中心无效。",nameof(centre));
Finite(surfaceDensity,nameof(surfaceDensity));Nonnegative(dd4Modifier,nameof(dd4Modifier));
Nonnegative(runtimeRangeMultiplier,nameof(runtimeRangeMultiplier));Nonnegative(sparseExpansionFactor,nameof(sparseExpansionFactor));
var basic=BaseRadius(surfaceDensity,qualityStarCount);
var inner=basic*dd4Modifier*runtimeRangeMultiplier;
var fraction=Math.Clamp((surfaceDensity-DensityThreshold)/DensityThreshold,0,1);
var outer=inner+(1-fraction*fraction)*sparseExpansionFactor*10*128;
if(!double.IsFinite(inner)||!double.IsFinite(outer))throw new ArgumentOutOfRangeException(nameof(runtimeRangeMultiplier));
return new(centre,surfaceDensity,qualityStarCount,dd4Modifier,runtimeRangeMultiplier,sparseExpansionFactor,basic,inner,outer);
}
/// <summary>Unknown source values remain unknown; null is never silently replaced with 500 or image defaults.</summary>
public static bool TryCreate(Coordinates centre,double? surfaceDensity,uint? qualityStarCount,double? dd4Modifier,
double? runtimeRangeMultiplier,double? sparseExpansionFactor,out GalaxyCatalogueLodDescriptor? descriptor)
{
descriptor=null;
if(centre is not{IsValid:true}||surfaceDensity is not{} d||qualityStarCount is not{} n||
dd4Modifier is not{} modifier||runtimeRangeMultiplier is not{} multiplier||sparseExpansionFactor is not{} expansion||
!double.IsFinite(d)||!double.IsFinite(modifier)||modifier<0||!double.IsFinite(multiplier)||multiplier<0||
!double.IsFinite(expansion)||expansion<0)return false;
try{descriptor=Create(centre,d,n,modifier,multiplier,expansion);return true;}
catch(ArgumentException){return false;}
}
/// <summary>3B8E040: unavailable/default settings use Eye; a ready nonzero original view mode uses Focus.</summary>
public static Coordinates QueryCentre(GalaxyCamera camera,int? originalViewMode)=>
originalViewMode.GetValueOrDefault()!=0?camera.Focus:camera.Eye;
/// <summary>3B46FD0 clamps the absolute float input before publishing the ordinary descriptor centre.</summary>
public static Coordinates ClampCentre(Coordinates relativeInput, Coordinates absoluteOrigin)
{
if (!relativeInput.IsValid || !absoluteOrigin.IsValid) throw new ArgumentException("原星图坐标无效。");
var x = (float)(relativeInput.X + absoluteOrigin.X);
var y = (float)(relativeInput.Y + absoluteOrigin.Y);
var z = (float)(relativeInput.Z + absoluteOrigin.Z);
if (!float.IsFinite(x) || !float.IsFinite(y) || !float.IsFinite(z)) throw new ArgumentOutOfRangeException(nameof(relativeInput));
return new(Math.Clamp(x, 100f, 163738f) - absoluteOrigin.X,
Math.Clamp(y, 100f, 81818f) - absoluteOrigin.Y, Math.Clamp(z, 100f, 163738f) - absoluteOrigin.Z);
}
/// <summary>
/// The ordinary DD4 range fade uses the raw input, not the clamped centre.
/// The debug region replaces the descriptor callback and is deliberately
/// rejected here rather than being passed through the ordinary formula.
/// </summary>
public static bool TryOrdinaryModifier(Coordinates relativeInput, Coordinates absoluteOrigin, bool? smallMode,
Coordinates? referenceAbsolutePosition, bool? debugRangeEnabled, int? originalViewMode, out double modifier)
{
modifier = 0;
if (!relativeInput.IsValid || !absoluteOrigin.IsValid || smallMode is null) return false;
if (smallMode == true) { modifier = 1; return true; }
if (debugRangeEnabled is null || debugRangeEnabled == true && originalViewMode is null || referenceAbsolutePosition is not { IsValid: true } reference) return false;
var x = (float)(relativeInput.X + absoluteOrigin.X);
var y = (float)(relativeInput.Y + absoluteOrigin.Y);
var z = (float)(relativeInput.Z + absoluteOrigin.Z);
if (!float.IsFinite(x) || !float.IsFinite(y) || !float.IsFinite(z)) return false;
static float Square(float value) => value * value;
if (debugRangeEnabled == true && originalViewMode == 2 &&
Square(x - 49985f) + Square(y - 40985f) + Square(z - 24105f) < 4000000f) return false;
var distance = MathF.Sqrt(Square(x - (float)reference.X) + Square(y - (float)reference.Y) + Square(z - (float)reference.Z));
var fade = (distance - 1000f) / -500f;
if (!float.IsFinite(fade)) return false;
modifier = Math.Clamp(fade, 0, 1);
return true;
}
/// <summary>Cell coordinates are absolute galactic ly; absoluteOrigin converts them to the caller's coordinate frame.</summary>
public static GalaxyQueryBounds CellBounds(ulong cellAddress,Coordinates absoluteOrigin)
{
if(absoluteOrigin is not{IsValid:true})throw new ArgumentException("银河坐标原点无效。",nameof(absoluteOrigin));
if(cellAddress>=1UL<<55)throw new ArgumentOutOfRangeException(nameof(cellAddress));
var mass=(int)(cellAddress&7);var size=10d*(1<<mass);
var x=(((cellAddress>>(30-2*mass))<<mass)&0x3FFF)*10d-absoluteOrigin.X;
var y=(((cellAddress>>(17-mass))<<mass)&0x1FFF)*10d-absoluteOrigin.Y;
var z=(((cellAddress>>3)<<mass)&0x3FFF)*10d-absoluteOrigin.Z;
return new(new(x,y,z),new(x+size,y+size,z+size));
}
private static void Finite(double value,string name)
{if(!double.IsFinite(value))throw new ArgumentOutOfRangeException(name);}
private static void Nonnegative(double value,string name)
{Finite(value,name);if(value<0)throw new ArgumentOutOfRangeException(name);}
}