using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Navigation;
namespace XFEEDToolBox.Core.Galaxy;
public readonly record struct GalaxyCatalogueCell(ulong Address,int MassCode,GalaxyQueryBounds Bounds);
/// <summary>
/// Exact original mass-cell predicate intersected with the current geometric
/// frustum. Queue limits are tool scheduling bounds, never coverage evidence.
/// </summary>
public sealed class GalaxyCellQueryPlan
{
public const int DefaultMaximumCells=131072;
public IReadOnlyList<GalaxyCatalogueCell> Cells {get;}
public bool Truncated {get;}
public long OmittedCandidates {get;}
public GalaxyCatalogueLodDescriptor Descriptor {get;}
public Coordinates AbsoluteOrigin {get;}
public GalaxyCellQueryPlan(GalaxyCamera camera,GalaxyCatalogueLodDescriptor descriptor,Coordinates absoluteOrigin,
int maximumCells=DefaultMaximumCells,int maximumCandidates=2000000)
{
ArgumentNullException.ThrowIfNull(camera);ArgumentNullException.ThrowIfNull(descriptor);
if(!absoluteOrigin.IsValid||maximumCells<1||maximumCandidates<maximumCells)
throw new ArgumentException("原星区查询计划的坐标或调度上限无效。");
Descriptor=descriptor;AbsoluteOrigin=absoluteOrigin;
var cells=new List<GalaxyCatalogueCell>();long candidates=0,omitted=0;var truncated=false;
for(var mass=0;mass<8;mass++)
{
var radius=descriptor.RadiusForMassCode(mass);if(radius<=0)continue;
var size=10d*(1<<mass);var centre=descriptor.Centre;
var maxX=(1<<(14-mass))-1;var maxY=(1<<(13-mass))-1;
static long Index(double value,double size,long low,long high)=>(long)Math.Clamp(Math.Floor(value/size),low,high);
var minX=Index(centre.X+absoluteOrigin.X-radius,size,0,maxX+1);
var minY=Index(centre.Y+absoluteOrigin.Y-radius,size,0,maxY+1);
var minZ=Index(centre.Z+absoluteOrigin.Z-radius,size,0,maxX+1);
var endX=Index(centre.X+absoluteOrigin.X+radius,size,-1,maxX);
var endY=Index(centre.Y+absoluteOrigin.Y+radius,size,-1,maxY);
var endZ=Index(centre.Z+absoluteOrigin.Z+radius,size,-1,maxX);
if(minX>endX||minY>endY||minZ>endZ)continue;
var count=checked((endX-minX+1)*(endY-minY+1)*(endZ-minZ+1));
if(count>maximumCandidates-candidates)
{
// Do not enumerate billions of cells after an unsupported
// runtime override, or report the omitted region as covered.
omitted+=count;truncated=true;continue;
}
candidates+=count;
for(var x=minX;x<=endX;x++)for(var y=minY;y<=endY;y++)for(var z=minZ;z<=endZ;z++)
{
var address=Encode(mass,(int)x,(int)y,(int)z);
var bounds=GalaxyOriginalCatalogueLod.CellBounds(address,absoluteOrigin);
if(!descriptor.IntersectsCell(bounds,mass)||!IntersectsFrustum(camera,bounds))continue;
if(cells.Count>=maximumCells){truncated=true;omitted++;continue;}
cells.Add(new(address,mass,bounds));
}
}
// This near-first ordering is a tool scheduling policy. The exact
// requested cell set does not depend on the order or camera QueryFar.
double Priority(GalaxyCatalogueCell cell)
{
var p=cell.Bounds.Centre;
var delta=new Vector3d(p.X-camera.Eye.X,p.Y-camera.Eye.Y,p.Z-camera.Eye.Z);
return Math.Max(0,Vector3d.Dot(delta,camera.Forward));
}
Cells=Array.AsReadOnly(cells.OrderBy(Priority).ThenBy(c=>c.Address).ToArray());
Truncated=truncated;OmittedCandidates=omitted;
}
public static ulong Encode(int massCode,int x,int y,int z)
{
if(massCode is <0 or >7||x<0||y<0||z<0||x>=(1<<(14-massCode))||y>=(1<<(13-massCode))||z>=(1<<(14-massCode)))
throw new ArgumentOutOfRangeException(nameof(massCode),"原星区编号越界。");
return ((ulong)x<<(30-2*massCode))|((ulong)y<<(17-massCode))|((ulong)z<<3)|(uint)massCode;
}
/// <summary>Strip only the system ordinal, preserving all mass-dependent cell bits.</summary>
public static ulong CellAddress(ulong systemAddress)
{
if(systemAddress>=1UL<<55)throw new ArgumentOutOfRangeException(nameof(systemAddress));
var mass=(int)(systemAddress&7);
return systemAddress&((1UL<<(44-3*mass))-1);
}
public static bool IntersectsFrustum(GalaxyCamera camera,GalaxyQueryBounds bounds,double margin=1)
{
if(!double.IsFinite(margin)||margin<1)throw new ArgumentOutOfRangeException(nameof(margin));
var centre=bounds.Centre;
var extent=new Vector3d((bounds.Maximum.X-bounds.Minimum.X)/2,(bounds.Maximum.Y-bounds.Minimum.Y)/2,(bounds.Maximum.Z-bounds.Minimum.Z)/2);
var delta=new Vector3d(centre.X-camera.Eye.X,centre.Y-camera.Eye.Y,centre.Z-camera.Eye.Z);
double Support(Vector3d n)=>Math.Abs(n.X)*extent.X+Math.Abs(n.Y)*extent.Y+Math.Abs(n.Z)*extent.Z;
if(Vector3d.Dot(delta,camera.Forward)+Support(camera.Forward)<camera.Near)return false;
var tx=camera.Width/(2*camera.FocalLength)*margin;var ty=camera.Height/(2*camera.FocalLength)*margin;
foreach(var normal in new[]{camera.Forward*tx+camera.Right,camera.Forward*tx-camera.Right,
camera.Forward*ty+camera.Up,camera.Forward*ty-camera.Up})
if(Vector3d.Dot(delta,normal)+Support(normal)<0)return false;
return true;
}
}
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Navigation;
namespace XFEEDToolBox.Core.Galaxy;
public readonly record struct GalaxyCatalogueCell(ulong Address,int MassCode,GalaxyQueryBounds Bounds);
/// <summary>
/// Exact original mass-cell predicate intersected with the current geometric
/// frustum. Queue limits are tool scheduling bounds, never coverage evidence.
/// </summary>
public sealed class GalaxyCellQueryPlan
{
public const int DefaultMaximumCells=131072;
public IReadOnlyList<GalaxyCatalogueCell> Cells {get;}
public bool Truncated {get;}
public long OmittedCandidates {get;}
public GalaxyCatalogueLodDescriptor Descriptor {get;}
public Coordinates AbsoluteOrigin {get;}
public GalaxyCellQueryPlan(GalaxyCamera camera,GalaxyCatalogueLodDescriptor descriptor,Coordinates absoluteOrigin,
int maximumCells=DefaultMaximumCells,int maximumCandidates=2000000)
{
ArgumentNullException.ThrowIfNull(camera);ArgumentNullException.ThrowIfNull(descriptor);
if(!absoluteOrigin.IsValid||maximumCells<1||maximumCandidates<maximumCells)
throw new ArgumentException("原星区查询计划的坐标或调度上限无效。");
Descriptor=descriptor;AbsoluteOrigin=absoluteOrigin;
var cells=new List<GalaxyCatalogueCell>();long candidates=0,omitted=0;var truncated=false;
for(var mass=0;mass<8;mass++)
{
var radius=descriptor.RadiusForMassCode(mass);if(radius<=0)continue;
var size=10d*(1<<mass);var centre=descriptor.Centre;
var maxX=(1<<(14-mass))-1;var maxY=(1<<(13-mass))-1;
static long Index(double value,double size,long low,long high)=>(long)Math.Clamp(Math.Floor(value/size),low,high);
var minX=Index(centre.X+absoluteOrigin.X-radius,size,0,maxX+1);
var minY=Index(centre.Y+absoluteOrigin.Y-radius,size,0,maxY+1);
var minZ=Index(centre.Z+absoluteOrigin.Z-radius,size,0,maxX+1);
var endX=Index(centre.X+absoluteOrigin.X+radius,size,-1,maxX);
var endY=Index(centre.Y+absoluteOrigin.Y+radius,size,-1,maxY);
var endZ=Index(centre.Z+absoluteOrigin.Z+radius,size,-1,maxX);
if(minX>endX||minY>endY||minZ>endZ)continue;
var count=checked((endX-minX+1)*(endY-minY+1)*(endZ-minZ+1));
if(count>maximumCandidates-candidates)
{
// Do not enumerate billions of cells after an unsupported
// runtime override, or report the omitted region as covered.
omitted+=count;truncated=true;continue;
}
candidates+=count;
for(var x=minX;x<=endX;x++)for(var y=minY;y<=endY;y++)for(var z=minZ;z<=endZ;z++)
{
var address=Encode(mass,(int)x,(int)y,(int)z);
var bounds=GalaxyOriginalCatalogueLod.CellBounds(address,absoluteOrigin);
if(!descriptor.IntersectsCell(bounds,mass)||!IntersectsFrustum(camera,bounds))continue;
if(cells.Count>=maximumCells){truncated=true;omitted++;continue;}
cells.Add(new(address,mass,bounds));
}
}
// This near-first ordering is a tool scheduling policy. The exact
// requested cell set does not depend on the order or camera QueryFar.
double Priority(GalaxyCatalogueCell cell)
{
var p=cell.Bounds.Centre;
var delta=new Vector3d(p.X-camera.Eye.X,p.Y-camera.Eye.Y,p.Z-camera.Eye.Z);
return Math.Max(0,Vector3d.Dot(delta,camera.Forward));
}
Cells=Array.AsReadOnly(cells.OrderBy(Priority).ThenBy(c=>c.Address).ToArray());
Truncated=truncated;OmittedCandidates=omitted;
}
public static ulong Encode(int massCode,int x,int y,int z)
{
if(massCode is <0 or >7||x<0||y<0||z<0||x>=(1<<(14-massCode))||y>=(1<<(13-massCode))||z>=(1<<(14-massCode)))
throw new ArgumentOutOfRangeException(nameof(massCode),"原星区编号越界。");
return ((ulong)x<<(30-2*massCode))|((ulong)y<<(17-massCode))|((ulong)z<<3)|(uint)massCode;
}
/// <summary>Strip only the system ordinal, preserving all mass-dependent cell bits.</summary>
public static ulong CellAddress(ulong systemAddress)
{
if(systemAddress>=1UL<<55)throw new ArgumentOutOfRangeException(nameof(systemAddress));
var mass=(int)(systemAddress&7);
return systemAddress&((1UL<<(44-3*mass))-1);
}
public static bool IntersectsFrustum(GalaxyCamera camera,GalaxyQueryBounds bounds,double margin=1)
{
if(!double.IsFinite(margin)||margin<1)throw new ArgumentOutOfRangeException(nameof(margin));
var centre=bounds.Centre;
var extent=new Vector3d((bounds.Maximum.X-bounds.Minimum.X)/2,(bounds.Maximum.Y-bounds.Minimum.Y)/2,(bounds.Maximum.Z-bounds.Minimum.Z)/2);
var delta=new Vector3d(centre.X-camera.Eye.X,centre.Y-camera.Eye.Y,centre.Z-camera.Eye.Z);
double Support(Vector3d n)=>Math.Abs(n.X)*extent.X+Math.Abs(n.Y)*extent.Y+Math.Abs(n.Z)*extent.Z;
if(Vector3d.Dot(delta,camera.Forward)+Support(camera.Forward)<camera.Near)return false;
var tx=camera.Width/(2*camera.FocalLength)*margin;var ty=camera.Height/(2*camera.FocalLength)*margin;
foreach(var normal in new[]{camera.Forward*tx+camera.Right,camera.Forward*tx-camera.Right,
camera.Forward*ty+camera.Up,camera.Forward*ty-camera.Up})
if(Vector3d.Dot(delta,normal)+Support(normal)<0)return false;
return true;
}
}