using Microsoft.UI.Xaml.Media;
using Windows.Foundation;
using XFEEDToolBox.Core.Galaxy;
using XFEEDToolBox.Core.Rendering;
namespace XFEEDToolBox.Controls;
public sealed partial class GalaxyMapView
{
private readonly List<(TextBlock Text,TranslateTransform Position)> regionNameVisuals=[];
private readonly List<(TextBlock Text,TranslateTransform Position)> galacticZoneNameVisuals=[];
private readonly List<Rect> annotationBounds=[];
private readonly Dictionary<(string Text,double FontSize),Size> annotationSizes=[];
private TextBlock? annotationMeasure;
internal int VisibleRegionNameLabels {get;private set;}
internal int VisibleGalacticZoneLabels {get;private set;}
internal bool GalacticZonesPlaneReadyForProbe=>graphics?.GalacticZonesPlaneReady==true;
internal bool GalacticZonesPlaneDrawnForProbe=>graphics?.GalacticZonesPlaneDrawn==true;
internal object AnnotationMetricsForProbe=>new{width=ActualWidth,height=ActualHeight,scale=XamlRoot?.RasterizationScale,
sizes=annotationSizes.Select(s=>new{text=s.Key.Text,fontSize=s.Key.FontSize,width=s.Value.Width,height=s.Value.Height}).ToArray(),
visible=regionNameVisuals.Where(s=>s.Text.Opacity>0).Select(s=>new{text=s.Text.Text,x=s.Position.X,y=s.Position.Y,width=s.Text.ActualWidth,height=s.Text.ActualHeight,desiredWidth=s.Text.DesiredSize.Width}).ToArray()};
private void DrawOriginalRegionNames(GalaxyCamera camera)
{
annotationBounds.Clear();DrawGalacticZoneNames(camera);var count=0;
if(Filter?.ShowRegionNames==true)foreach(var region in GalaxyOriginalRegions.Reference.Regions.OrderBy(r=>r.Spheres.Min(s=>s.Position.DistanceTo(camera.Focus))))
{
if(count>=32)break;
foreach(var sphere in region.Spheres.OrderBy(s=>s.Position.DistanceTo(camera.Focus)))
{
var p=camera.Project(sphere.Position);if(!p.IsVisible)continue;
var size=MeasureAnnotation(region.Name,14);var box=new Rect(p.X-size.Width/2,p.Y-size.Height/2,size.Width,size.Height);
if(box.Left<4||box.Right>camera.Width-4||box.Top<4||box.Bottom>camera.Height-4||AnnotationBlocked(box))continue;
if(regionNameVisuals.Count==count)
{
var position=new TranslateTransform();var text=new TextBlock{FontSize=14,Foreground=Brush(160,177,188),RenderTransform=position,IsHitTestVisible=false};
labels.Children.Add(text);regionNameVisuals.Add((text,position));
}
var label=regionNameVisuals[count++];if(label.Text.Text!=region.Name)label.Text.Text=region.Name;
label.Position.X=box.X;label.Position.Y=box.Y;label.Text.Opacity=1;annotationBounds.Add(box);break;
}
}
for(var i=count;i<regionNameVisuals.Count;i++)regionNameVisuals[i].Text.Opacity=0;
VisibleRegionNameLabels=count;
}
private void DrawGalacticZoneNames(GalaxyCamera camera)
{
var count=0;
// Text anchors are area centroids calculated from the original GZM;
// the resource contains names and boundaries, no authored 3D labels.
if(Filter?.ShowGalacticZoneNames==true&&camera.Scale<=.06)
foreach(var zone in GalaxyOriginalZones.Reference.Zones.OrderBy(z=>z.Position.DistanceTo(camera.Focus)))
{
var p=camera.Project(zone.Position);if(!p.IsVisible)continue;
var size=MeasureAnnotation(zone.Name,18);var box=new Rect(p.X-size.Width/2,p.Y-size.Height/2,size.Width,size.Height);
if(box.Left<4||box.Right>camera.Width-4||box.Top<4||box.Bottom>camera.Height-4||AnnotationBlocked(box))continue;
if(galacticZoneNameVisuals.Count==count)
{
var position=new TranslateTransform();var text=new TextBlock{FontSize=18,Foreground=Brush(142,180,194),RenderTransform=position,IsHitTestVisible=false};
labels.Children.Add(text);galacticZoneNameVisuals.Add((text,position));
}
var label=galacticZoneNameVisuals[count++];if(label.Text.Text!=zone.Name)label.Text.Text=zone.Name;
label.Position.X=box.X;label.Position.Y=box.Y;label.Text.Opacity=1;annotationBounds.Add(box);
}
for(var i=count;i<galacticZoneNameVisuals.Count;i++)galacticZoneNameVisuals[i].Text.Opacity=0;
VisibleGalacticZoneLabels=count;
}
private Size MeasureAnnotation(string text,double fontSize)
{
var key=(text,fontSize);if(annotationSizes.TryGetValue(key,out var size))return size;
// A detached WinUI TextBlock can report an empty DesiredSize. Attach
// the invisible measurer so it receives the same root/font metrics.
if(annotationMeasure is null)
{
annotationMeasure=new TextBlock{IsHitTestVisible=false,Opacity=0};labels.Children.Add(annotationMeasure);
}
annotationMeasure.Text=text;annotationMeasure.FontSize=fontSize;annotationMeasure.InvalidateMeasure();
annotationMeasure.Measure(new(100000,100000));
var measured=annotationMeasure.DesiredSize;
if(measured.Width<=0||measured.Height<=0)
return new(text.Length*fontSize+8,fontSize*2+4);
size=new(Math.Ceiling(measured.Width)+6,Math.Ceiling(measured.Height)+4);annotationSizes.Add(key,size);return size;
}
private bool AnnotationBlocked(Rect box)
{
static bool Intersects(Rect a,Rect b)=>a.Left<b.Right&&a.Right>b.Left&&a.Top<b.Bottom&&a.Bottom>b.Top;
if(annotationBounds.Any(b=>Intersects(b,box))||worldMarkerHits.Any(m=>Intersects(m.Bounds,box))||currentLocationHit is {} current&&Intersects(current.Bounds,box))return true;
foreach(var label in names)if(label.Visible&&Intersects(new Rect(label.Transform.X,label.Transform.Y,label.BoxWidth,label.BoxHeight),box))return true;
return false;
}
}
using Microsoft.UI.Xaml.Media;
using Windows.Foundation;
using XFEEDToolBox.Core.Galaxy;
using XFEEDToolBox.Core.Rendering;
namespace XFEEDToolBox.Controls;
public sealed partial class GalaxyMapView
{
private readonly List<(TextBlock Text,TranslateTransform Position)> regionNameVisuals=[];
private readonly List<(TextBlock Text,TranslateTransform Position)> galacticZoneNameVisuals=[];
private readonly List<Rect> annotationBounds=[];
private readonly Dictionary<(string Text,double FontSize),Size> annotationSizes=[];
private TextBlock? annotationMeasure;
internal int VisibleRegionNameLabels {get;private set;}
internal int VisibleGalacticZoneLabels {get;private set;}
internal bool GalacticZonesPlaneReadyForProbe=>graphics?.GalacticZonesPlaneReady==true;
internal bool GalacticZonesPlaneDrawnForProbe=>graphics?.GalacticZonesPlaneDrawn==true;
internal object AnnotationMetricsForProbe=>new{width=ActualWidth,height=ActualHeight,scale=XamlRoot?.RasterizationScale,
sizes=annotationSizes.Select(s=>new{text=s.Key.Text,fontSize=s.Key.FontSize,width=s.Value.Width,height=s.Value.Height}).ToArray(),
visible=regionNameVisuals.Where(s=>s.Text.Opacity>0).Select(s=>new{text=s.Text.Text,x=s.Position.X,y=s.Position.Y,width=s.Text.ActualWidth,height=s.Text.ActualHeight,desiredWidth=s.Text.DesiredSize.Width}).ToArray()};
private void DrawOriginalRegionNames(GalaxyCamera camera)
{
annotationBounds.Clear();DrawGalacticZoneNames(camera);var count=0;
if(Filter?.ShowRegionNames==true)foreach(var region in GalaxyOriginalRegions.Reference.Regions.OrderBy(r=>r.Spheres.Min(s=>s.Position.DistanceTo(camera.Focus))))
{
if(count>=32)break;
foreach(var sphere in region.Spheres.OrderBy(s=>s.Position.DistanceTo(camera.Focus)))
{
var p=camera.Project(sphere.Position);if(!p.IsVisible)continue;
var size=MeasureAnnotation(region.Name,14);var box=new Rect(p.X-size.Width/2,p.Y-size.Height/2,size.Width,size.Height);
if(box.Left<4||box.Right>camera.Width-4||box.Top<4||box.Bottom>camera.Height-4||AnnotationBlocked(box))continue;
if(regionNameVisuals.Count==count)
{
var position=new TranslateTransform();var text=new TextBlock{FontSize=14,Foreground=Brush(160,177,188),RenderTransform=position,IsHitTestVisible=false};
labels.Children.Add(text);regionNameVisuals.Add((text,position));
}
var label=regionNameVisuals[count++];if(label.Text.Text!=region.Name)label.Text.Text=region.Name;
label.Position.X=box.X;label.Position.Y=box.Y;label.Text.Opacity=1;annotationBounds.Add(box);break;
}
}
for(var i=count;i<regionNameVisuals.Count;i++)regionNameVisuals[i].Text.Opacity=0;
VisibleRegionNameLabels=count;
}
private void DrawGalacticZoneNames(GalaxyCamera camera)
{
var count=0;
// Text anchors are area centroids calculated from the original GZM;
// the resource contains names and boundaries, no authored 3D labels.
if(Filter?.ShowGalacticZoneNames==true&&camera.Scale<=.06)
foreach(var zone in GalaxyOriginalZones.Reference.Zones.OrderBy(z=>z.Position.DistanceTo(camera.Focus)))
{
var p=camera.Project(zone.Position);if(!p.IsVisible)continue;
var size=MeasureAnnotation(zone.Name,18);var box=new Rect(p.X-size.Width/2,p.Y-size.Height/2,size.Width,size.Height);
if(box.Left<4||box.Right>camera.Width-4||box.Top<4||box.Bottom>camera.Height-4||AnnotationBlocked(box))continue;
if(galacticZoneNameVisuals.Count==count)
{
var position=new TranslateTransform();var text=new TextBlock{FontSize=18,Foreground=Brush(142,180,194),RenderTransform=position,IsHitTestVisible=false};
labels.Children.Add(text);galacticZoneNameVisuals.Add((text,position));
}
var label=galacticZoneNameVisuals[count++];if(label.Text.Text!=zone.Name)label.Text.Text=zone.Name;
label.Position.X=box.X;label.Position.Y=box.Y;label.Text.Opacity=1;annotationBounds.Add(box);
}
for(var i=count;i<galacticZoneNameVisuals.Count;i++)galacticZoneNameVisuals[i].Text.Opacity=0;
VisibleGalacticZoneLabels=count;
}
private Size MeasureAnnotation(string text,double fontSize)
{
var key=(text,fontSize);if(annotationSizes.TryGetValue(key,out var size))return size;
// A detached WinUI TextBlock can report an empty DesiredSize. Attach
// the invisible measurer so it receives the same root/font metrics.
if(annotationMeasure is null)
{
annotationMeasure=new TextBlock{IsHitTestVisible=false,Opacity=0};labels.Children.Add(annotationMeasure);
}
annotationMeasure.Text=text;annotationMeasure.FontSize=fontSize;annotationMeasure.InvalidateMeasure();
annotationMeasure.Measure(new(100000,100000));
var measured=annotationMeasure.DesiredSize;
if(measured.Width<=0||measured.Height<=0)
return new(text.Length*fontSize+8,fontSize*2+4);
size=new(Math.Ceiling(measured.Width)+6,Math.Ceiling(measured.Height)+4);annotationSizes.Add(key,size);return size;
}
private bool AnnotationBlocked(Rect box)
{
static bool Intersects(Rect a,Rect b)=>a.Left<b.Right&&a.Right>b.Left&&a.Top<b.Bottom&&a.Bottom>b.Top;
if(annotationBounds.Any(b=>Intersects(b,box))||worldMarkerHits.Any(m=>Intersects(m.Bounds,box))||currentLocationHit is {} current&&Intersects(current.Bounds,box))return true;
foreach(var label in names)if(label.Visible&&Intersects(new Rect(label.Transform.X,label.Transform.Y,label.BoxWidth,label.BoxHeight),box))return true;
return false;
}
}