using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Windows.Foundation;
using XFEEDToolBox.Core.Galaxy;
using XFEEDToolBox.Core.Rendering;
namespace XFEEDToolBox.Controls;
public sealed partial class GalaxyMapView
{
private sealed record MarkerSample(GalacticSystem System,string Asset,double HorizontalOffset);
private sealed class MarkerVisual(Image icon,TranslateTransform position)
{
internal Image Icon=icon;
internal string Asset="";
internal TranslateTransform Position=position;
internal bool Visible;
}
private MarkerSample[] worldMarkerSamples=[];
private readonly List<MarkerVisual> worldMarkerVisuals=[];
private readonly List<(GalacticSystem System,Rect Bounds)> worldMarkerHits=[];
private readonly HashSet<string> worldMarkerRenderedNames=new(StringComparer.OrdinalIgnoreCase);
private readonly List<(TextBlock Text,TranslateTransform Position)> nebulaNameLabels=[];
private Image? currentLocationIcon;
private readonly TranslateTransform currentLocationPosition=new();
private (GalacticSystem System,Rect Bounds)? currentLocationHit;
internal bool VisibleCurrentLocationMarker=>currentLocationHit is not null;
internal int VisibleNebulaNameLabels {get;private set;}
internal int VisibleWorldMarkers=>worldMarkerHits.Count;
internal IReadOnlyList<(GalacticSystem System,Rect Bounds)> WorldMarkerBoundsForProbe=>worldMarkerHits;
// Current mapui Icons frames retain their original colours, borders and
// alpha. Legacy bookmark-editor IDs never select current world glyphs.
private static string? WorldMarkerIcon(string key)
{
var frame=key switch
{
"engineer"=>"GM_DisplayOptions_Engineers","mission"=>"GM_DisplayOptions_Missions",
"fleetcarrier"=>"GM_DisplayOptions_CarrierLocation","drydock"=>"GM_DisplayOptions_DryDock",
"conflictzone"=>"GM_DisplayOptions_CZ","starterzone"=>"GM_DisplayOptions_StarterBookmarks",
"navigation"=>"GM_DisplayOptions_Navigation","rescuemegaship"=>"GM_Marker_RescueMegaship",
"bookmark"=>"GM_DisplayOptions_Bookmarks","squadronbookmark"=>"GM_DisplayOptions_BookmarksSquadron",
"ship"=>"GM_DisplayOptions_StoredShips","communitygoal"=>"GM_DisplayOptions_CommunityGoals",
"friend"=>"GM_DisplayOptions_Friends","wing"=>"GM_DisplayOptions_Wings",
"damaged"=>"GM_DisplayOptions_StationDamaged","repairing"=>"GM_DisplayOptions_StationRepairing",
"underattack"=>"GM_Marker_StationUnderAttack","abandoned"=>"GM_Marker_StationAbandoned",
_=>null
};
return frame is not null&&GalaxyOriginalIcons.Map(frame) is {} icon?"ms-appx:///"+icon.AssetPath:null;
}
private void RebuildWorldMarkers()
{
if(Filter?.ShowMarkers==false){worldMarkerSamples=[];return;}
var values=Filter is {IsQueryActive:true,Category:GalaxyFilterCategory.Markers}?Filter.Values:null;
IEnumerable<MarkerSample> Samples(GalacticSystem system)
{
IEnumerable<string> Assets(string key)
{
if(key=="mission"&&system.Data.NativeAccountMarkers?.MissionMarkerTypes is {Count:>0} types)
{
foreach(var frame in types.Select(GalaxyMissionMarkerStyles.OriginalFrameFor).OfType<string>().Distinct())
if(GalaxyOriginalIcons.Map(frame) is {} icon)yield return "ms-appx:///"+icon.AssetPath;
}
else if(WorldMarkerIcon(key) is {} asset)yield return asset;
}
var assets=(GalaxyMapFilter.Keys(system,GalaxyFilterCategory.Markers)??[]).Where(k=>(values is null||values.Contains(k))&&Filter?.IsMarkerVisible(k)!=false).SelectMany(Assets).Distinct().ToArray();
// Separate actual types at the same system, including simultaneous
// active and completed missions. They must not cover each other.
for(var index=0;index<assets.Length;index++)yield return new(system,assets[index],28*(index-(assets.Length-1)*.5));
}
worldMarkerSamples=ordered.SelectMany(s=>Samples(s.System)).DistinctBy(s=>(s.System.Name,s.Asset)).Take(4096).ToArray();
}
private void DrawWorldMarkers(GalaxyCamera camera)
{
worldMarkerHits.Clear();worldMarkerRenderedNames.Clear();
DrawCurrentLocationMarker(camera);
var count=0;
if(worldMarkerSamples.Length>0&&Filter?.ShowMarkers!=false)
{
if(StellarLodActive)for(var i=0;i<hitCount;i++)worldMarkerRenderedNames.Add(hits[i].System.Name);
foreach(var sample in worldMarkerSamples)
{
if(count>=256)break;
var projected=camera.ProjectForCatalogue(sample.System.Position);
if(!projected.IsVisible||StellarLodActive&&!worldMarkerRenderedNames.Contains(sample.System.Name))continue;
var bounds=new Rect(projected.X-13+sample.HorizontalOffset,projected.Y-(sample.System.Name.Equals(CurrentSystem,StringComparison.OrdinalIgnoreCase)?76:38),26,26);
if(bounds.Left<0||bounds.Right>camera.Width||bounds.Top<0||bounds.Bottom>camera.Height)continue;
if(currentLocationHit is {} current&¤t.Bounds.Left<bounds.Right&¤t.Bounds.Right>bounds.Left&¤t.Bounds.Top<bounds.Bottom&¤t.Bounds.Bottom>bounds.Top)continue;
var collides=false;foreach(var placed in worldMarkerHits)if(placed.Bounds.Left<bounds.Right&&placed.Bounds.Right>bounds.Left&&placed.Bounds.Top<bounds.Bottom&&placed.Bounds.Bottom>bounds.Top){collides=true;break;}
if(collides)continue;
if(worldMarkerVisuals.Count==count)
{
var position=new TranslateTransform();var icon=new Image{Width=26,Height=26,Stretch=Stretch.Uniform,IsHitTestVisible=false,RenderTransform=position,Opacity=0};
labels.Children.Add(icon);worldMarkerVisuals.Add(new(icon,position));
}
var visual=worldMarkerVisuals[count++];
if(visual.Asset!=sample.Asset){visual.Asset=sample.Asset;visual.Icon.Source=sample.Asset.EndsWith(".svg",StringComparison.OrdinalIgnoreCase)?new SvgImageSource(new Uri(sample.Asset)):new BitmapImage(new Uri(sample.Asset));}
visual.Position.X=bounds.X;visual.Position.Y=bounds.Y;
if(!visual.Visible){visual.Visible=true;visual.Icon.Opacity=1;}
worldMarkerHits.Add((sample.System,bounds));
}
}
for(var i=count;i<worldMarkerVisuals.Count;i++)if(worldMarkerVisuals[i].Visible)
{worldMarkerVisuals[i].Visible=false;worldMarkerVisuals[i].Icon.Opacity=0;}
DrawOriginalRegionNames(camera);DrawNebulaNames(camera);
}
private void DrawCurrentLocationMarker(GalaxyCamera camera)
{
currentLocationHit=null;
if(Filter?.ShowMarkers!=false&&!string.IsNullOrEmpty(CurrentSystem))for(var i=0;i<hitCount;i++)
{
var system=hits[i].System;if(!system.Name.Equals(CurrentSystem,StringComparison.OrdinalIgnoreCase))continue;
var p=camera.ProjectForCatalogue(system.Position);if(!p.IsVisible)break;
var bounds=new Rect(p.X-18,p.Y-47,36,36);
if(bounds.Left<0||bounds.Right>camera.Width||bounds.Top<0||bounds.Bottom>camera.Height)break;
if(currentLocationIcon is null&&GalaxyOriginalIcons.Map("CurrentLocationMarker") is {} asset)
{
currentLocationIcon=new Image{Width=36,Height=36,Stretch=Stretch.Uniform,IsHitTestVisible=false,RenderTransform=currentLocationPosition,
Source=new SvgImageSource(new Uri("ms-appx:///"+asset.AssetPath)),Opacity=0};
labels.Children.Add(currentLocationIcon);
}
if(currentLocationIcon is null)break;
currentLocationPosition.X=bounds.X;currentLocationPosition.Y=bounds.Y;currentLocationIcon.Opacity=1;
currentLocationHit=(system,bounds);return;
}
if(currentLocationIcon is not null)currentLocationIcon.Opacity=0;
}
private void DrawNebulaNames(GalaxyCamera camera)
{
var show=Filter?.ShowNebulaNames==true;
var count=0;var boxes=annotationBounds;
if(show)foreach(var nebula in EliteNebulae.Reference.Nebulae.OrderBy(n=>n.Position.DistanceTo(camera.Focus)))
{
if(count>=24)break;var p=camera.Project(nebula.Position);if(!p.IsVisible)continue;
var size=MeasureAnnotation(nebula.Name,12);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(nebulaNameLabels.Count==count)
{
var position=new TranslateTransform();var text=new TextBlock{FontSize=12,Foreground=Brush(170,184,202),RenderTransform=position,IsHitTestVisible=false};
labels.Children.Add(text);nebulaNameLabels.Add((text,position));
}
var label=nebulaNameLabels[count++];if(label.Text.Text!=nebula.Name)label.Text.Text=nebula.Name;
label.Position.X=box.X;label.Position.Y=box.Y;label.Text.Opacity=1;boxes.Add(box);
}
for(var i=count;i<nebulaNameLabels.Count;i++)nebulaNameLabels[i].Text.Opacity=0;
VisibleNebulaNameLabels=count;
emptyText.Opacity=ordered.Length==0&&count==0&&VisibleRegionNameLabels==0&&VisibleGalacticZoneLabels==0?1:0;
}
private GalacticSystem? FindWorldMarkerAt(Point point)
{
if(Filter?.ShowMarkers==false)return null;
if(currentLocationHit is {} current&¤t.Bounds.Contains(point)&&Filter?.Matches(current.System)!=false)return current.System;
foreach(var marker in worldMarkerHits)if(marker.Bounds.Contains(point)&&Filter?.Matches(marker.System)!=false)return marker.System;
return null;
}
}
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Windows.Foundation;
using XFEEDToolBox.Core.Galaxy;
using XFEEDToolBox.Core.Rendering;
namespace XFEEDToolBox.Controls;
public sealed partial class GalaxyMapView
{
private sealed record MarkerSample(GalacticSystem System,string Asset,double HorizontalOffset);
private sealed class MarkerVisual(Image icon,TranslateTransform position)
{
internal Image Icon=icon;
internal string Asset="";
internal TranslateTransform Position=position;
internal bool Visible;
}
private MarkerSample[] worldMarkerSamples=[];
private readonly List<MarkerVisual> worldMarkerVisuals=[];
private readonly List<(GalacticSystem System,Rect Bounds)> worldMarkerHits=[];
private readonly HashSet<string> worldMarkerRenderedNames=new(StringComparer.OrdinalIgnoreCase);
private readonly List<(TextBlock Text,TranslateTransform Position)> nebulaNameLabels=[];
private Image? currentLocationIcon;
private readonly TranslateTransform currentLocationPosition=new();
private (GalacticSystem System,Rect Bounds)? currentLocationHit;
internal bool VisibleCurrentLocationMarker=>currentLocationHit is not null;
internal int VisibleNebulaNameLabels {get;private set;}
internal int VisibleWorldMarkers=>worldMarkerHits.Count;
internal IReadOnlyList<(GalacticSystem System,Rect Bounds)> WorldMarkerBoundsForProbe=>worldMarkerHits;
// Current mapui Icons frames retain their original colours, borders and
// alpha. Legacy bookmark-editor IDs never select current world glyphs.
private static string? WorldMarkerIcon(string key)
{
var frame=key switch
{
"engineer"=>"GM_DisplayOptions_Engineers","mission"=>"GM_DisplayOptions_Missions",
"fleetcarrier"=>"GM_DisplayOptions_CarrierLocation","drydock"=>"GM_DisplayOptions_DryDock",
"conflictzone"=>"GM_DisplayOptions_CZ","starterzone"=>"GM_DisplayOptions_StarterBookmarks",
"navigation"=>"GM_DisplayOptions_Navigation","rescuemegaship"=>"GM_Marker_RescueMegaship",
"bookmark"=>"GM_DisplayOptions_Bookmarks","squadronbookmark"=>"GM_DisplayOptions_BookmarksSquadron",
"ship"=>"GM_DisplayOptions_StoredShips","communitygoal"=>"GM_DisplayOptions_CommunityGoals",
"friend"=>"GM_DisplayOptions_Friends","wing"=>"GM_DisplayOptions_Wings",
"damaged"=>"GM_DisplayOptions_StationDamaged","repairing"=>"GM_DisplayOptions_StationRepairing",
"underattack"=>"GM_Marker_StationUnderAttack","abandoned"=>"GM_Marker_StationAbandoned",
_=>null
};
return frame is not null&&GalaxyOriginalIcons.Map(frame) is {} icon?"ms-appx:///"+icon.AssetPath:null;
}
private void RebuildWorldMarkers()
{
if(Filter?.ShowMarkers==false){worldMarkerSamples=[];return;}
var values=Filter is {IsQueryActive:true,Category:GalaxyFilterCategory.Markers}?Filter.Values:null;
IEnumerable<MarkerSample> Samples(GalacticSystem system)
{
IEnumerable<string> Assets(string key)
{
if(key=="mission"&&system.Data.NativeAccountMarkers?.MissionMarkerTypes is {Count:>0} types)
{
foreach(var frame in types.Select(GalaxyMissionMarkerStyles.OriginalFrameFor).OfType<string>().Distinct())
if(GalaxyOriginalIcons.Map(frame) is {} icon)yield return "ms-appx:///"+icon.AssetPath;
}
else if(WorldMarkerIcon(key) is {} asset)yield return asset;
}
var assets=(GalaxyMapFilter.Keys(system,GalaxyFilterCategory.Markers)??[]).Where(k=>(values is null||values.Contains(k))&&Filter?.IsMarkerVisible(k)!=false).SelectMany(Assets).Distinct().ToArray();
// Separate actual types at the same system, including simultaneous
// active and completed missions. They must not cover each other.
for(var index=0;index<assets.Length;index++)yield return new(system,assets[index],28*(index-(assets.Length-1)*.5));
}
worldMarkerSamples=ordered.SelectMany(s=>Samples(s.System)).DistinctBy(s=>(s.System.Name,s.Asset)).Take(4096).ToArray();
}
private void DrawWorldMarkers(GalaxyCamera camera)
{
worldMarkerHits.Clear();worldMarkerRenderedNames.Clear();
DrawCurrentLocationMarker(camera);
var count=0;
if(worldMarkerSamples.Length>0&&Filter?.ShowMarkers!=false)
{
if(StellarLodActive)for(var i=0;i<hitCount;i++)worldMarkerRenderedNames.Add(hits[i].System.Name);
foreach(var sample in worldMarkerSamples)
{
if(count>=256)break;
var projected=camera.ProjectForCatalogue(sample.System.Position);
if(!projected.IsVisible||StellarLodActive&&!worldMarkerRenderedNames.Contains(sample.System.Name))continue;
var bounds=new Rect(projected.X-13+sample.HorizontalOffset,projected.Y-(sample.System.Name.Equals(CurrentSystem,StringComparison.OrdinalIgnoreCase)?76:38),26,26);
if(bounds.Left<0||bounds.Right>camera.Width||bounds.Top<0||bounds.Bottom>camera.Height)continue;
if(currentLocationHit is {} current&¤t.Bounds.Left<bounds.Right&¤t.Bounds.Right>bounds.Left&¤t.Bounds.Top<bounds.Bottom&¤t.Bounds.Bottom>bounds.Top)continue;
var collides=false;foreach(var placed in worldMarkerHits)if(placed.Bounds.Left<bounds.Right&&placed.Bounds.Right>bounds.Left&&placed.Bounds.Top<bounds.Bottom&&placed.Bounds.Bottom>bounds.Top){collides=true;break;}
if(collides)continue;
if(worldMarkerVisuals.Count==count)
{
var position=new TranslateTransform();var icon=new Image{Width=26,Height=26,Stretch=Stretch.Uniform,IsHitTestVisible=false,RenderTransform=position,Opacity=0};
labels.Children.Add(icon);worldMarkerVisuals.Add(new(icon,position));
}
var visual=worldMarkerVisuals[count++];
if(visual.Asset!=sample.Asset){visual.Asset=sample.Asset;visual.Icon.Source=sample.Asset.EndsWith(".svg",StringComparison.OrdinalIgnoreCase)?new SvgImageSource(new Uri(sample.Asset)):new BitmapImage(new Uri(sample.Asset));}
visual.Position.X=bounds.X;visual.Position.Y=bounds.Y;
if(!visual.Visible){visual.Visible=true;visual.Icon.Opacity=1;}
worldMarkerHits.Add((sample.System,bounds));
}
}
for(var i=count;i<worldMarkerVisuals.Count;i++)if(worldMarkerVisuals[i].Visible)
{worldMarkerVisuals[i].Visible=false;worldMarkerVisuals[i].Icon.Opacity=0;}
DrawOriginalRegionNames(camera);DrawNebulaNames(camera);
}
private void DrawCurrentLocationMarker(GalaxyCamera camera)
{
currentLocationHit=null;
if(Filter?.ShowMarkers!=false&&!string.IsNullOrEmpty(CurrentSystem))for(var i=0;i<hitCount;i++)
{
var system=hits[i].System;if(!system.Name.Equals(CurrentSystem,StringComparison.OrdinalIgnoreCase))continue;
var p=camera.ProjectForCatalogue(system.Position);if(!p.IsVisible)break;
var bounds=new Rect(p.X-18,p.Y-47,36,36);
if(bounds.Left<0||bounds.Right>camera.Width||bounds.Top<0||bounds.Bottom>camera.Height)break;
if(currentLocationIcon is null&&GalaxyOriginalIcons.Map("CurrentLocationMarker") is {} asset)
{
currentLocationIcon=new Image{Width=36,Height=36,Stretch=Stretch.Uniform,IsHitTestVisible=false,RenderTransform=currentLocationPosition,
Source=new SvgImageSource(new Uri("ms-appx:///"+asset.AssetPath)),Opacity=0};
labels.Children.Add(currentLocationIcon);
}
if(currentLocationIcon is null)break;
currentLocationPosition.X=bounds.X;currentLocationPosition.Y=bounds.Y;currentLocationIcon.Opacity=1;
currentLocationHit=(system,bounds);return;
}
if(currentLocationIcon is not null)currentLocationIcon.Opacity=0;
}
private void DrawNebulaNames(GalaxyCamera camera)
{
var show=Filter?.ShowNebulaNames==true;
var count=0;var boxes=annotationBounds;
if(show)foreach(var nebula in EliteNebulae.Reference.Nebulae.OrderBy(n=>n.Position.DistanceTo(camera.Focus)))
{
if(count>=24)break;var p=camera.Project(nebula.Position);if(!p.IsVisible)continue;
var size=MeasureAnnotation(nebula.Name,12);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(nebulaNameLabels.Count==count)
{
var position=new TranslateTransform();var text=new TextBlock{FontSize=12,Foreground=Brush(170,184,202),RenderTransform=position,IsHitTestVisible=false};
labels.Children.Add(text);nebulaNameLabels.Add((text,position));
}
var label=nebulaNameLabels[count++];if(label.Text.Text!=nebula.Name)label.Text.Text=nebula.Name;
label.Position.X=box.X;label.Position.Y=box.Y;label.Text.Opacity=1;boxes.Add(box);
}
for(var i=count;i<nebulaNameLabels.Count;i++)nebulaNameLabels[i].Text.Opacity=0;
VisibleNebulaNameLabels=count;
emptyText.Opacity=ordered.Length==0&&count==0&&VisibleRegionNameLabels==0&&VisibleGalacticZoneLabels==0?1:0;
}
private GalacticSystem? FindWorldMarkerAt(Point point)
{
if(Filter?.ShowMarkers==false)return null;
if(currentLocationHit is {} current&¤t.Bounds.Contains(point)&&Filter?.Matches(current.System)!=false)return current.System;
foreach(var marker in worldMarkerHits)if(marker.Bounds.Contains(point)&&Filter?.Matches(marker.System)!=false)return marker.System;
return null;
}
}