#if FLEET || TESTS
using System;
using System.Collections.Generic;
using VRageMath;
namespace AutoMiningScript
{
public partial class Program
{
public partial class Dashboard
{
sealed class RegionView
{
public Vector3D Center, Up, Right;
public double Extent = 1, Area;
public bool Search;
public int Total, Complete, Scanned, Rays, Sampled, Empty, Unknown;
public readonly bool[] Cells = new bool[576];
public readonly List<Job> Dots = new List<Job>();
public readonly List<ScanRecord> Rocks = new List<ScanRecord>();
public readonly Dictionary<long,int> RockIds = new Dictionary<long,int>();
public Vector2 Project(Vector3D point)
{
var d = point-Center; return new Vector2((float)Data.Dot(d,Right),(float)Data.Dot(d,Up));
}
public Vector2 Size(ScanRecord rock)
=> new Vector2((float)(Math.Abs(Right.X)*rock.Size.X+Math.Abs(Right.Y)*rock.Size.Y+Math.Abs(Right.Z)*rock.Size.Z),
(float)(Math.Abs(Up.X)*rock.Size.X+Math.Abs(Up.Y)*rock.Size.Y+Math.Abs(Up.Z)*rock.Size.Z));
public void Rock(ScanRecord rock)
{
if (rock.Asteroid == 0) return;
int index;
if (RockIds.TryGetValue(rock.Asteroid,out index))
{
if (!rock.Sized) return;
var old = Rocks[index]; var size = Size(old); Area -= (double)size.X*size.Y;
if (!old.Sized) Unknown--; Rocks[index] = rock;
}
else { RockIds.Add(rock.Asteroid,Rocks.Count); Rocks.Add(rock); if (!rock.Sized) Unknown++; }
var next = Size(rock); Area += (double)next.X*next.Y;
}
// Raster cells mean "a ray crossed here", never full-cell clearance.
// Clip before rasterizing: off-map parallel rays must not paint an edge.
public void Ray(ScanRecord scan)
{
var a = Project(scan.Start); var d = Project(scan.End)-a; double lo=0,hi=1;
for (int axis=0;axis<2;axis++)
{
double x=axis==0?a.X:a.Y, v=axis==0?d.X:d.Y;
if (Math.Abs(v)<.00001) { if (Math.Abs(x)>Extent) return; continue; }
double t=(-Extent-x)/v,u=(Extent-x)/v;
lo=Math.Max(lo,Math.Min(t,u)); hi=Math.Min(hi,Math.Max(t,u));
}
if(lo>hi)return;
var start=a+d*(float)lo; var span=d*(float)(hi-lo);
int steps=Math.Max(1,(int)Math.Ceiling(Math.Max(Math.Abs(span.X),Math.Abs(span.Y))*12/Extent));
for(int n=0;n<=steps;n++)
{
var q=start+span*((float)n/steps);
int x=(int)Data.Clamp(Math.Floor((q.X/Extent+1)*12),0,23),y=(int)Data.Clamp(Math.Floor((q.Y/Extent+1)*12),0,23);
Cells[y*24+x]=true;
}
}
}
Dictionary<string,RegionView> mapViews=new Dictionary<string,RegionView>(),buildingViews;
List<string> mapRegions=new List<string>(),buildingRegions,buildingPages;
List<int> mapParts=new List<int>(),buildingParts;
List<Job> buildingSource;
Job buildingFirst;
int jobCursor,buildingDone,completedJobs,pageCursor;
double nextJobRefresh;
void RefreshJobs(RoleLogic role)
{
var jobs=role.GetJobs(); Job first=jobs.Count==0?null:jobs[0];
var replaced=jobs!=buildingSource || first!=buildingFirst;
if(buildingViews==null && p.Now<nextJobRefresh && !replaced)return;
if(buildingViews==null || replaced || jobCursor>jobs.Count)
{
buildingSource=jobs; buildingFirst=first; buildingViews=new Dictionary<string,RegionView>();
buildingRegions=new List<string>();buildingPages=new List<string>();buildingParts=new List<int>();jobCursor=buildingDone=pageCursor=0;
}
int work=0;
while(jobCursor<jobs.Count && work++<64 && p.HasBudget(.55))
{
Job j=jobs[jobCursor++];RegionView view;
var region=role.MapRegion(j.RegionId);
if(!buildingViews.TryGetValue(j.RegionId,out view))
{
var direction=region==null?j.Direction:region.Direction;
view=new RegionView {Center=region==null?j.Entry:region.Center,Search=region==null?j.Kind==JobKind.Survey:region.Search,
Up=Data.Perpendicular(direction,region==null?j.Up:region.Up)};
view.Right=Data.Cross(direction,view.Up);
if(view.Search)view.Extent=Math.Max(1,region==null?j.Depth+j.Radius:region.Radius);
buildingViews[j.RegionId]=view;buildingRegions.Add(j.RegionId);
}
var pos=view.Project(j.Entry);
if(!view.Search)view.Extent=Math.Max(view.Extent,Math.Max(Math.Abs(pos.X),Math.Abs(pos.Y))+j.Radius);
view.Total++;if(Finished(j)){view.Complete++;buildingDone++;}
if(j.Kind==JobKind.Survey)view.Rays++;
if(j.Kind==JobKind.Probe && j.Outcome=="Complete")view.Sampled++;
if(j.Outcome=="SurveyEmpty")view.Empty++;
if(view.Dots.Count<400)view.Dots.Add(j);
if(j.Scan!=null)
{
view.Scanned++;if(view.Search)view.Ray(j.Scan);view.Rock(j.Scan);
}
else if(j.Kind==JobKind.Probe && j.EntityId!=0 && region!=null && !region.Gravity)
view.Rock(new ScanRecord {Asteroid=j.EntityId,Center=j.Entry,End=j.Entry});
}
if(jobCursor<jobs.Count)return;
work=0;
while(pageCursor<buildingRegions.Count && work++<64 && p.HasBudget(.55))
{
var id=buildingRegions[pageCursor++];int pages=Math.Max(1,(buildingViews[id].Rocks.Count+7)/8);
for(int n=0;n<pages;n++){buildingPages.Add(id);buildingParts.Add(n);}
}
if(pageCursor<buildingRegions.Count)return;
mapViews=buildingViews;mapRegions=buildingPages;mapParts=buildingParts;completedJobs=buildingDone;
buildingViews=null;nextJobRefresh=p.Now+.5;
}
static string Area(double value) => value>=1e6?F(value/1e6,"0.00")+" km2":F(value,"0")+" m2";
void Map(Canvas c,List<Telemetry> fleet,int listPage)
{
if(mapRegions.Count==0){c.Text(L.DisplayNoArea,22,110,.55f,Amber);return;}
int page=listPage%mapRegions.Count;var id=mapRegions[page];var view=mapViews[id];
c.Text(view.Search?L.DisplayExplorationMap:L.DisplayMiningPlane,22,93,.6f,White);
c.TextFit(view.Search?L.F(L.DisplayScanSummary,view.Scanned,view.Rays,view.Rocks.Count,view.Unknown):L.F(L.DisplayMapLegend,view.Sampled,view.Empty),22,120,c.Width-44,.40f,Blue);
float left=22,top=147,width=c.Width-44,height=c.Height-270,bottom=top+height;
var origin=new Vector2(c.Width/2,top+height/2);var scale=(float)((height-12)/(2*view.Extent));
c.Box(left,top,width,height,Panel);
c.Line(new Vector2(left,origin.Y),new Vector2(left+width,origin.Y),Background);
c.Line(new Vector2(origin.X,top),new Vector2(origin.X,bottom),Background);
if(view.Search)
{
var cell=(float)(view.Extent/12)*scale;
for(int n=0;n<576;n++)if(view.Cells[n])c.Box(origin.X+(n%24-12)*cell,origin.Y-(n/24-11)*cell,cell-0.4f,cell-0.4f,new Color(34,84,113));
for(int n=0;n<32;n++)
{
double a=n*Math.PI/16,b=(n+1)*Math.PI/16;
c.Line(origin+new Vector2((float)Math.Cos(a),(float)Math.Sin(a))*(float)view.Extent*scale,origin+new Vector2((float)Math.Cos(b),(float)Math.Sin(b))*(float)view.Extent*scale,Muted);
}
}
var dots=new HashSet<int>();
foreach(var j in view.Dots)
{
var point=origin+view.Project(j.Entry)*new Vector2(scale,-scale);
int cell=(int)(point.X/3)+(int)(point.Y/3)*4096;
if(!dots.Add(cell))continue;
c.Dot(point.X,point.Y,2,j.Outcome=="Blocked"||j.Outcome=="InvalidSample"?Red:j.Owner.Length>0?Amber:Finished(j)?Green:Muted);
}
var slots=new bool[32];
int start=mapParts[page]*8,end=Math.Min(view.Rocks.Count,start+8);
for(int n=start;n<end;n++)
{
var rock=view.Rocks[n];var point=origin+view.Project(rock.Center)*new Vector2(scale,-scale);var size=view.Size(rock);
if(rock.Sized)
{
float x1=Math.Max(left,point.X-size.X*scale/2),x2=Math.Min(left+width,point.X+size.X*scale/2);
float y1=Math.Max(top,point.Y-size.Y*scale/2),y2=Math.Min(bottom,point.Y+size.Y*scale/2);
if(x2>x1&&y2>y1){c.Box(x1,y1,x2-x1,1,Amber);c.Box(x1,y2,x2-x1,1,Amber);c.Box(x1,y1,1,y2-y1,Amber);c.Box(x2,y1,1,y2-y1,Amber);}
}
point.X=(float)Data.Clamp(point.X,left+4,left+width-4);point.Y=(float)Data.Clamp(point.Y,top+3,bottom-3);
float y=top+(n-start)*height/16;slots[16+n-start]=true;
c.Line(point,new Vector2(left+width-174,y+7),Amber);
c.Text("A"+(n+1)+" "+(rock.Sized?"~"+Area((double)size.X*size.Y):"?"),left+width-170,y,.36f,Amber);
}
// Two banks of label slots keep even co-located miners' IDs distinct.
foreach(var t in fleet)
{
var point=origin+view.Project(t.Position)*new Vector2(scale,-scale);
var outside=point.X<left||point.X>left+width||point.Y<top||point.Y>bottom;
point.X=(float)Data.Clamp(point.X,left+3,left+width-3);point.Y=(float)Data.Clamp(point.Y,top+3,bottom-3);
int bank=point.X>origin.X?16:0,row=(int)Data.Clamp((point.Y-top)*16/height,0,15),slot=-1;
for(int n=0;n<32;n++){int candidate=(bank+row+n)%32;if(!slots[candidate]){slot=candidate;break;}}
if(slot<0)break;slots[slot]=true;
float x=slot<16?left+5:left+width-170,y=top+(slot%16)*height/16;
var color=!Fresh(t)||t.State==FlightState.Fault?Red:Blue;
c.Line(point,new Vector2(slot<16?x+164:x,y+7),color);
c.Text("+",point.X,point.Y,.5f,color);
c.TextFit((outside?"*":"")+t.Id+(Fresh(t)?"":" / "+L.DisplayOffline),x,y,165,.36f,color);
}
c.TextFit(id+" "+view.Complete+"/"+view.Total+" +/- "+F(view.Extent,"0")+"m",22,bottom+8,width,.40f,White);
c.TextFit(L.F(L.DisplayRockArea,Area(view.Area),view.Rocks.Count==0?0:start+1,end,view.Rocks.Count),22,bottom+28,width,.38f,Amber);
c.TextFit(L.DisplayScanLegend,22,bottom+49,width,.36f,Blue);
c.TextFit(L.DisplayScanCaution,22,bottom+69,width,.34f,Muted);
}
}
}
}
#endif
#if FLEET || TESTS
using System;
using System.Collections.Generic;
using VRageMath;
namespace AutoMiningScript
{
public partial class Program
{
public partial class Dashboard
{
sealed class RegionView
{
public Vector3D Center, Up, Right;
public double Extent = 1, Area;
public bool Search;
public int Total, Complete, Scanned, Rays, Sampled, Empty, Unknown;
public readonly bool[] Cells = new bool[576];
public readonly List<Job> Dots = new List<Job>();
public readonly List<ScanRecord> Rocks = new List<ScanRecord>();
public readonly Dictionary<long,int> RockIds = new Dictionary<long,int>();
public Vector2 Project(Vector3D point)
{
var d = point-Center; return new Vector2((float)Data.Dot(d,Right),(float)Data.Dot(d,Up));
}
public Vector2 Size(ScanRecord rock)
=> new Vector2((float)(Math.Abs(Right.X)*rock.Size.X+Math.Abs(Right.Y)*rock.Size.Y+Math.Abs(Right.Z)*rock.Size.Z),
(float)(Math.Abs(Up.X)*rock.Size.X+Math.Abs(Up.Y)*rock.Size.Y+Math.Abs(Up.Z)*rock.Size.Z));
public void Rock(ScanRecord rock)
{
if (rock.Asteroid == 0) return;
int index;
if (RockIds.TryGetValue(rock.Asteroid,out index))
{
if (!rock.Sized) return;
var old = Rocks[index]; var size = Size(old); Area -= (double)size.X*size.Y;
if (!old.Sized) Unknown--; Rocks[index] = rock;
}
else { RockIds.Add(rock.Asteroid,Rocks.Count); Rocks.Add(rock); if (!rock.Sized) Unknown++; }
var next = Size(rock); Area += (double)next.X*next.Y;
}
// Raster cells mean "a ray crossed here", never full-cell clearance.
// Clip before rasterizing: off-map parallel rays must not paint an edge.
public void Ray(ScanRecord scan)
{
var a = Project(scan.Start); var d = Project(scan.End)-a; double lo=0,hi=1;
for (int axis=0;axis<2;axis++)
{
double x=axis==0?a.X:a.Y, v=axis==0?d.X:d.Y;
if (Math.Abs(v)<.00001) { if (Math.Abs(x)>Extent) return; continue; }
double t=(-Extent-x)/v,u=(Extent-x)/v;
lo=Math.Max(lo,Math.Min(t,u)); hi=Math.Min(hi,Math.Max(t,u));
}
if(lo>hi)return;
var start=a+d*(float)lo; var span=d*(float)(hi-lo);
int steps=Math.Max(1,(int)Math.Ceiling(Math.Max(Math.Abs(span.X),Math.Abs(span.Y))*12/Extent));
for(int n=0;n<=steps;n++)
{
var q=start+span*((float)n/steps);
int x=(int)Data.Clamp(Math.Floor((q.X/Extent+1)*12),0,23),y=(int)Data.Clamp(Math.Floor((q.Y/Extent+1)*12),0,23);
Cells[y*24+x]=true;
}
}
}
Dictionary<string,RegionView> mapViews=new Dictionary<string,RegionView>(),buildingViews;
List<string> mapRegions=new List<string>(),buildingRegions,buildingPages;
List<int> mapParts=new List<int>(),buildingParts;
List<Job> buildingSource;
Job buildingFirst;
int jobCursor,buildingDone,completedJobs,pageCursor;
double nextJobRefresh;
void RefreshJobs(RoleLogic role)
{
var jobs=role.GetJobs(); Job first=jobs.Count==0?null:jobs[0];
var replaced=jobs!=buildingSource || first!=buildingFirst;
if(buildingViews==null && p.Now<nextJobRefresh && !replaced)return;
if(buildingViews==null || replaced || jobCursor>jobs.Count)
{
buildingSource=jobs; buildingFirst=first; buildingViews=new Dictionary<string,RegionView>();
buildingRegions=new List<string>();buildingPages=new List<string>();buildingParts=new List<int>();jobCursor=buildingDone=pageCursor=0;
}
int work=0;
while(jobCursor<jobs.Count && work++<64 && p.HasBudget(.55))
{
Job j=jobs[jobCursor++];RegionView view;
var region=role.MapRegion(j.RegionId);
if(!buildingViews.TryGetValue(j.RegionId,out view))
{
var direction=region==null?j.Direction:region.Direction;
view=new RegionView {Center=region==null?j.Entry:region.Center,Search=region==null?j.Kind==JobKind.Survey:region.Search,
Up=Data.Perpendicular(direction,region==null?j.Up:region.Up)};
view.Right=Data.Cross(direction,view.Up);
if(view.Search)view.Extent=Math.Max(1,region==null?j.Depth+j.Radius:region.Radius);
buildingViews[j.RegionId]=view;buildingRegions.Add(j.RegionId);
}
var pos=view.Project(j.Entry);
if(!view.Search)view.Extent=Math.Max(view.Extent,Math.Max(Math.Abs(pos.X),Math.Abs(pos.Y))+j.Radius);
view.Total++;if(Finished(j)){view.Complete++;buildingDone++;}
if(j.Kind==JobKind.Survey)view.Rays++;
if(j.Kind==JobKind.Probe && j.Outcome=="Complete")view.Sampled++;
if(j.Outcome=="SurveyEmpty")view.Empty++;
if(view.Dots.Count<400)view.Dots.Add(j);
if(j.Scan!=null)
{
view.Scanned++;if(view.Search)view.Ray(j.Scan);view.Rock(j.Scan);
}
else if(j.Kind==JobKind.Probe && j.EntityId!=0 && region!=null && !region.Gravity)
view.Rock(new ScanRecord {Asteroid=j.EntityId,Center=j.Entry,End=j.Entry});
}
if(jobCursor<jobs.Count)return;
work=0;
while(pageCursor<buildingRegions.Count && work++<64 && p.HasBudget(.55))
{
var id=buildingRegions[pageCursor++];int pages=Math.Max(1,(buildingViews[id].Rocks.Count+7)/8);
for(int n=0;n<pages;n++){buildingPages.Add(id);buildingParts.Add(n);}
}
if(pageCursor<buildingRegions.Count)return;
mapViews=buildingViews;mapRegions=buildingPages;mapParts=buildingParts;completedJobs=buildingDone;
buildingViews=null;nextJobRefresh=p.Now+.5;
}
static string Area(double value) => value>=1e6?F(value/1e6,"0.00")+" km2":F(value,"0")+" m2";
void Map(Canvas c,List<Telemetry> fleet,int listPage)
{
if(mapRegions.Count==0){c.Text(L.DisplayNoArea,22,110,.55f,Amber);return;}
int page=listPage%mapRegions.Count;var id=mapRegions[page];var view=mapViews[id];
c.Text(view.Search?L.DisplayExplorationMap:L.DisplayMiningPlane,22,93,.6f,White);
c.TextFit(view.Search?L.F(L.DisplayScanSummary,view.Scanned,view.Rays,view.Rocks.Count,view.Unknown):L.F(L.DisplayMapLegend,view.Sampled,view.Empty),22,120,c.Width-44,.40f,Blue);
float left=22,top=147,width=c.Width-44,height=c.Height-270,bottom=top+height;
var origin=new Vector2(c.Width/2,top+height/2);var scale=(float)((height-12)/(2*view.Extent));
c.Box(left,top,width,height,Panel);
c.Line(new Vector2(left,origin.Y),new Vector2(left+width,origin.Y),Background);
c.Line(new Vector2(origin.X,top),new Vector2(origin.X,bottom),Background);
if(view.Search)
{
var cell=(float)(view.Extent/12)*scale;
for(int n=0;n<576;n++)if(view.Cells[n])c.Box(origin.X+(n%24-12)*cell,origin.Y-(n/24-11)*cell,cell-0.4f,cell-0.4f,new Color(34,84,113));
for(int n=0;n<32;n++)
{
double a=n*Math.PI/16,b=(n+1)*Math.PI/16;
c.Line(origin+new Vector2((float)Math.Cos(a),(float)Math.Sin(a))*(float)view.Extent*scale,origin+new Vector2((float)Math.Cos(b),(float)Math.Sin(b))*(float)view.Extent*scale,Muted);
}
}
var dots=new HashSet<int>();
foreach(var j in view.Dots)
{
var point=origin+view.Project(j.Entry)*new Vector2(scale,-scale);
int cell=(int)(point.X/3)+(int)(point.Y/3)*4096;
if(!dots.Add(cell))continue;
c.Dot(point.X,point.Y,2,j.Outcome=="Blocked"||j.Outcome=="InvalidSample"?Red:j.Owner.Length>0?Amber:Finished(j)?Green:Muted);
}
var slots=new bool[32];
int start=mapParts[page]*8,end=Math.Min(view.Rocks.Count,start+8);
for(int n=start;n<end;n++)
{
var rock=view.Rocks[n];var point=origin+view.Project(rock.Center)*new Vector2(scale,-scale);var size=view.Size(rock);
if(rock.Sized)
{
float x1=Math.Max(left,point.X-size.X*scale/2),x2=Math.Min(left+width,point.X+size.X*scale/2);
float y1=Math.Max(top,point.Y-size.Y*scale/2),y2=Math.Min(bottom,point.Y+size.Y*scale/2);
if(x2>x1&&y2>y1){c.Box(x1,y1,x2-x1,1,Amber);c.Box(x1,y2,x2-x1,1,Amber);c.Box(x1,y1,1,y2-y1,Amber);c.Box(x2,y1,1,y2-y1,Amber);}
}
point.X=(float)Data.Clamp(point.X,left+4,left+width-4);point.Y=(float)Data.Clamp(point.Y,top+3,bottom-3);
float y=top+(n-start)*height/16;slots[16+n-start]=true;
c.Line(point,new Vector2(left+width-174,y+7),Amber);
c.Text("A"+(n+1)+" "+(rock.Sized?"~"+Area((double)size.X*size.Y):"?"),left+width-170,y,.36f,Amber);
}
// Two banks of label slots keep even co-located miners' IDs distinct.
foreach(var t in fleet)
{
var point=origin+view.Project(t.Position)*new Vector2(scale,-scale);
var outside=point.X<left||point.X>left+width||point.Y<top||point.Y>bottom;
point.X=(float)Data.Clamp(point.X,left+3,left+width-3);point.Y=(float)Data.Clamp(point.Y,top+3,bottom-3);
int bank=point.X>origin.X?16:0,row=(int)Data.Clamp((point.Y-top)*16/height,0,15),slot=-1;
for(int n=0;n<32;n++){int candidate=(bank+row+n)%32;if(!slots[candidate]){slot=candidate;break;}}
if(slot<0)break;slots[slot]=true;
float x=slot<16?left+5:left+width-170,y=top+(slot%16)*height/16;
var color=!Fresh(t)||t.State==FlightState.Fault?Red:Blue;
c.Line(point,new Vector2(slot<16?x+164:x,y+7),color);
c.Text("+",point.X,point.Y,.5f,color);
c.TextFit((outside?"*":"")+t.Id+(Fresh(t)?"":" / "+L.DisplayOffline),x,y,165,.36f,color);
}
c.TextFit(id+" "+view.Complete+"/"+view.Total+" +/- "+F(view.Extent,"0")+"m",22,bottom+8,width,.40f,White);
c.TextFit(L.F(L.DisplayRockArea,Area(view.Area),view.Rocks.Count==0?0:start+1,end,view.Rocks.Count),22,bottom+28,width,.38f,Amber);
c.TextFit(L.DisplayScanLegend,22,bottom+49,width,.36f,Blue);
c.TextFit(L.DisplayScanCaution,22,bottom+69,width,.34f,Muted);
}
}
}
}
#endif