Skip to content

Commit

Permalink
Implement additional info flyout and other fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Elscrux committed May 2, 2024
1 parent 8f328e7 commit 333fbcf
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 107 deletions.
94 changes: 63 additions & 31 deletions Plugins/MapperPlugin/Services/HeatmapCreator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Collections.Concurrent;
using Avalonia;
using Avalonia.Media;
using CreationEditor.Avalonia;
Expand All @@ -22,44 +22,49 @@ public sealed class HeatmapCreator {

private Dictionary<MarkingMapping, MappingSpots>? _spotsPerMapping;

private readonly MappingSpots _spotsCache = new();
private readonly ConcurrentDictionary<IFormLinkIdentifier, Dictionary<Point, int>> _spotsCache = new();

private int _leftCoordinate;
private int _topCoordinate;
private double _widthImageScale;
private double _heightImageScale;
private int _markingSize;

public DrawingImage? GetDrawing(Size imageSize, int markingSize, int leftCell, int rightCell, int topCell, int bottomCell) {
if (_spotsPerMapping is null) return null;

var (imageWidth, imageHeight) = imageSize;
var mainGroup = GetDrawingGroup(imageWidth, imageHeight);

var worldspaceWidth = (rightCell - leftCell) * CellSize;
var worldspaceHeight = (bottomCell - topCell) * CellSize;
var leftCoordinate = leftCell * CellSize;
var topCoordinate = topCell * CellSize;
_markingSize = markingSize;
var worldspaceWidth = Math.Abs(rightCell - leftCell) * CellSize;
var worldspaceHeight = Math.Abs(topCell - bottomCell) * CellSize;
_leftCoordinate = leftCell * CellSize;
_topCoordinate = topCell * CellSize;
var rightCoordinate = rightCell * CellSize;
var bottomCoordinate = bottomCell * CellSize;

var widthScale = worldspaceWidth / imageWidth;
var heightScale = worldspaceHeight / imageHeight;
_widthImageScale = worldspaceWidth / imageWidth;
_heightImageScale = worldspaceHeight / imageHeight;

foreach (var (mapping, mappingSpots) in _spotsPerMapping) {
if (!mapping.Enable) continue;

foreach (var (record, spots) in mappingSpots) {
foreach (var (worldspacePosition, count) in spots) {
foreach (var (_, spots) in mappingSpots) {
var color = mapping is { UseQuery: true, UseRandomColorsInQuery: true }
? ColorExtension.RandomColorRgb()
: mapping.Color;

foreach (var (worldspacePosition, _) in spots) {
// Skip points outside worldspace bounds
if (worldspacePosition.X < leftCoordinate || worldspacePosition.X > rightCoordinate) continue;
if (worldspacePosition.Y < bottomCoordinate || worldspacePosition.Y > topCoordinate) continue;
if (worldspacePosition.X < _leftCoordinate || worldspacePosition.X > rightCoordinate) continue;
if (worldspacePosition.Y < bottomCoordinate || worldspacePosition.Y > _topCoordinate) continue;

var size = markingSize * mapping.Size * (1 + Math.Log(count) / 2);
var size = GetSize(mapping.Size);
// Convert worldspace position to image position
// Worldspace is centered at 0,0
// Image has top-left corner at 0,0
var imagePosition = new Point(
(worldspacePosition.X - leftCoordinate) / widthScale,
(worldspacePosition.Y - topCoordinate) / heightScale);

var color = mapping is { UseQuery: true, UseRandomColorsInQuery: true }
? ColorExtension.RandomColorRgb()
: mapping.Color;
var imagePosition = WorldspaceToImageCoordinates(worldspacePosition);

var geometryDrawing = new GeometryDrawing {
Brush = new SolidColorBrush(color),
Expand Down Expand Up @@ -98,7 +103,6 @@ public async Task CalculateSpots(IEnumerable<MarkingMapping> mappings, ILinkCach
RecordSpots GetSpots(IFormLinkIdentifier record) {
if (_spotsCache.TryGetValue(record, out var cachedSpots)) return (record, cachedSpots);

var timestamp = Stopwatch.GetTimestamp();
var spots = new Dictionary<Point, int>();
foreach (var reference in recordReferenceController.GetReferences(record.FormKey)) {
if (!reference.Type.InheritsFrom(typeof(IPlacedGetter))) continue;
Expand Down Expand Up @@ -128,9 +132,8 @@ RecordSpots GetSpots(IFormLinkIdentifier record) {
spots.Add(point, 1);
}
}
Console.WriteLine($"GetPoints for {record.FormKey}: {Stopwatch.GetElapsedTime(timestamp, Stopwatch.GetTimestamp())}");

_spotsCache.Add(record, spots);
_spotsCache.TryAdd(record, spots);
return (record, spots);

Point Cluster(P3Float position) {
Expand All @@ -144,24 +147,53 @@ Point Cluster(P3Float position) {

private static DrawingGroup GetDrawingGroup(double width, double height) {
var mainGroup = new DrawingGroup();
const int thickness = 10;
const int halfThickness = thickness / 2;
mainGroup.Children.Add(new GeometryDrawing {
Geometry = new LineGeometry(new Point(0, 0), new Point(0, height)),
Pen = new Pen(Brushes.Transparent, 100)
Geometry = new LineGeometry(new Point(halfThickness, halfThickness), new Point(halfThickness, height - halfThickness)),
Pen = new Pen(Brushes.Transparent, thickness)
});
mainGroup.Children.Add(new GeometryDrawing {
Geometry = new LineGeometry(new Point(width, 0), new Point(width, height)),
Pen = new Pen(Brushes.Transparent, 100)
Geometry = new LineGeometry(new Point(width - halfThickness, halfThickness), new Point(width - halfThickness, height - halfThickness)),
Pen = new Pen(Brushes.Transparent, thickness)
});
mainGroup.Children.Add(new GeometryDrawing {
Geometry = new LineGeometry(new Point(0, 0), new Point(width, 0)),
Pen = new Pen(Brushes.Transparent, 100)
Geometry = new LineGeometry(new Point(halfThickness, halfThickness), new Point(width - halfThickness, halfThickness)),
Pen = new Pen(Brushes.Transparent, thickness)
});
mainGroup.Children.Add(new GeometryDrawing {
Geometry = new LineGeometry(new Point(0, height), new Point(width, height)),
Pen = new Pen(Brushes.Transparent, 100)
Geometry = new LineGeometry(new Point(halfThickness, height - halfThickness), new Point(width - halfThickness, height - halfThickness)),
Pen = new Pen(Brushes.Transparent, thickness)
});
return mainGroup;
}

public IFormLinkIdentifier? GetMappingAt(Point imageCoordinates) {
if (_spotsPerMapping is null) return null;

foreach (var (mapping, mappingSpots) in _spotsPerMapping) {
var imageSize = GetSize(mapping.Size);

foreach (var (record, spots) in mappingSpots) {
foreach (var (worldspacePosition, _) in spots) {
var (x, y) = WorldspaceToImageCoordinates(worldspacePosition);

var xDist = Math.Abs(x - imageCoordinates.X);
var yDist = Math.Abs(y - imageCoordinates.Y);

if (xDist < imageSize && yDist < imageSize) {
return record;
}
}
}
}

return null;
}

private Point WorldspaceToImageCoordinates(Point worldspacePosition) => new(
(worldspacePosition.X - _leftCoordinate) / _widthImageScale,
(_topCoordinate - worldspacePosition.Y) / _heightImageScale);

private double GetSize(float mappingSize) => _markingSize * mappingSize * (1 + Math.Log(1) / 2);
}
47 changes: 24 additions & 23 deletions Plugins/MapperPlugin/ViewModels/MapperVM.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ public sealed class MapperVM : ViewModel, IMementoProvider<MapperMemento> {
private readonly Func<QueryVM> _queryVMFactory;
private readonly IList<QueryFromItem> _placeableQueryFromItems;

[Reactive] public bool IsBusy { get; set; }
public HeatmapCreator HeatmapCreator { get; }

[Reactive] public int BusyTasks { get; set; }

public ILinkCacheProvider LinkCacheProvider { get; }

Expand All @@ -53,7 +55,7 @@ public sealed class MapperVM : ViewModel, IMementoProvider<MapperMemento> {
public ReactiveCommand<string, Unit> SaveMap { get; }
public ReactiveCommand<Guid, Unit> LoadMap { get; }
public ReactiveCommand<StateIdentifier, Unit> DeleteMap { get; }
public List<StateIdentifier> SavedMaps { get; }
public IObservableCollection<StateIdentifier> SavedMaps { get; } = new ObservableCollectionExtended<StateIdentifier>();

public MapperVM(
Func<QueryVM> queryVMFactory,
Expand All @@ -64,10 +66,10 @@ public MapperVM(
IMutagenTypeProvider mutagenTypeProvider,
ILinkCacheProvider linkCacheProvider) {
_queryVMFactory = queryVMFactory;
var heatmapCreator = new HeatmapCreator();
HeatmapCreator = new HeatmapCreator();
LinkCacheProvider = linkCacheProvider;
var stateRepository = stateRepositoryFactory("Heatmap");
SavedMaps = stateRepository.LoadAllStateIdentifiers().ToList();
SavedMaps.AddRange(stateRepository.LoadAllStateIdentifiers());

var registrationsByGetterType = mutagenTypeProvider
.GetRegistrations(gameReleaseContext.Release)
Expand Down Expand Up @@ -110,32 +112,32 @@ public MapperVM(
SavedMaps.RemoveWhere(x => x.Id == id.Id);
});

// Logical update
var logicalMappingUpdates = Mappings
.WhenCollectionChanges()
.Select(_ => Mappings.Select(m => m.LogicalUpdates).CombineLatest())
.Switch();

var visualMappingUpdates = Mappings
.WhenCollectionChanges()
.Select(_ => Mappings.Select(m => m.VisualUpdates).CombineLatest())
.Switch();

// Logical update
this.WhenAnyValue(x => x.WorldspaceFormKey)
.Where(worldspace => !worldspace.IsNull)
.CombineLatest(logicalMappingUpdates, (x, _) => x)
.ThrottleMedium()
.ObserveOnGui()
.Do(_ => IsBusy = true)
.Do(_ => BusyTasks++)
.ObserveOnTaskpool()
.DoTask(async worldspace => await heatmapCreator.CalculateSpots(Mappings, LinkCacheProvider.LinkCache, recordReferenceController, worldspace))
.DoTask(async worldspace => await HeatmapCreator.CalculateSpots(Mappings, LinkCacheProvider.LinkCache, recordReferenceController, worldspace))
.ObserveOnGui()
.Do(_ => DrawingsImage = heatmapCreator.GetDrawing(ImageSource!.Size, MarkingSize, LeftCell, RightCell, TopCell, BottomCell))
.Do(_ => IsBusy = false)
.Do(_ => DrawingsImage = HeatmapCreator.GetDrawing(ImageSource!.Size, MarkingSize, LeftCell, RightCell, TopCell, BottomCell))
.Do(_ => BusyTasks--)
.Subscribe()
.DisposeWith(this);

// Visual update
var visualMappingUpdates = Mappings
.WhenCollectionChanges()
.Select(_ => Mappings.Select(m => m.VisualUpdates).CombineLatest())
.Switch();

this.WhenAnyValue(
x => x.ImageSource,
x => x.LeftCell,
Expand All @@ -147,9 +149,9 @@ public MapperVM(
.CombineLatest(visualMappingUpdates)
.ThrottleMedium()
.ObserveOnGui()
.Do(_ => IsBusy = true)
.Do(_ => DrawingsImage = heatmapCreator.GetDrawing(ImageSource!.Size, MarkingSize, LeftCell, RightCell, TopCell, BottomCell))
.Do(_ => IsBusy = false)
.Do(_ => BusyTasks++)
.Do(_ => DrawingsImage = HeatmapCreator.GetDrawing(ImageSource!.Size, MarkingSize, LeftCell, RightCell, TopCell, BottomCell))
.Do(_ => BusyTasks--)
.Subscribe()
.DisposeWith(this);

Expand Down Expand Up @@ -193,18 +195,17 @@ public MapperMemento CreateMemento() {
}

public void RestoreMemento(MapperMemento memento) {
Mappings.ReplaceWith(memento.MarkingMappings.Select(m => {
var markingMapping = CreateMapping();
markingMapping.RestoreMemento(m);
return markingMapping;
}));
ImageFilePath = memento.ImagePath;
WorldspaceFormKey = memento.Worldspace;
LeftCell = memento.LeftCell;
RightCell = memento.RightCell;
TopCell = memento.TopCell;
BottomCell = memento.BottomCell;
MarkingSize = memento.MarkingSize;

Mappings.ReplaceWith(memento.MarkingMappings.Select(m => {
var markingMapping = CreateMapping();
markingMapping.RestoreMemento(m);
return markingMapping;
}));
}
}
82 changes: 40 additions & 42 deletions Plugins/MapperPlugin/Views/MapperView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,17 @@
RowDefinitions="Auto,Auto,*,Auto,Auto"
ColumnDefinitions="Auto,*,Auto,Auto">
<TextBlock Grid.Row="0" Grid.Column="0"
Text="{Binding LeftCell}"/>
Text="{Binding LeftCell}"
VerticalAlignment="Center"/>
<TextBlock Grid.Row="0" Grid.Column="2"
Text="{Binding RightCell}"/>
Text="{Binding RightCell}"
VerticalAlignment="Center"/>
<TextBlock Grid.Row="1" Grid.Column="3"
Text="{Binding TopCell}"/>
Text="{Binding TopCell}"
HorizontalAlignment="Center"/>
<TextBlock Grid.Row="3" Grid.Column="3"
Text="{Binding BottomCell}"/>
Text="{Binding BottomCell}"
HorizontalAlignment="Center"/>

<controls:RangeSlider
Grid.Column="1"
Expand Down Expand Up @@ -109,51 +113,44 @@
BorderThickness="5"
BorderBrush="{DynamicResource SystemAccentColor}"
CornerRadius="5">
<ZoomBorder
Stretch="None" ZoomSpeed="1.2"
ClipToBounds="True" Focusable="True"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Interaction.Behaviors>
<zoomBorder:LimitPanAndZoomToSize/>
</Interaction.Behaviors>
<Grid>
<ZoomBorder
Stretch="None" ZoomSpeed="1.2"
ClipToBounds="True" Focusable="True"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Interaction.Behaviors>
<zoomBorder:LimitPanAndZoomToSize/>
</Interaction.Behaviors>

<Grid>
<Grid.Resources>
<converters:ReturnParameterIfTrueConverter
x:Key="IfTrueConverter"
x:TypeArguments="system:Double"
DefaultValue="5"/>
</Grid.Resources>

<Image x:Name="Map"
Source="{Binding ImageSource}">
<Image.Effect>
<Grid>
<Grid.Resources>
<converters:ReturnParameterIfTrueConverter
x:Key="IfTrueConverter"
x:TypeArguments="system:Double"
DefaultValue="5"/>
</Grid.Resources>
<Grid.Effect>
<BlurEffect
Radius="{Binding !IsBusy,
Radius="{Binding !BusyTasks,
Converter={StaticResource IfTrueConverter},
ConverterParameter=0}"/>
</Image.Effect>
</Image>
</Grid.Effect>

<Image x:Name="Drawings"
PointerPressed="Drawings_OnPointerPressed"
Source="{Binding DrawingsImage}">
<Image.Effect>
<BlurEffect
Radius="{Binding !IsBusy,
Converter={StaticResource IfTrueConverter},
ConverterParameter=0}"/>
</Image.Effect>
</Image>
<Image x:Name="Map"
Source="{Binding ImageSource}"/>

<avaloniaProgressRing:ProgressRing
IsActive="{Binding IsBusy}"
HorizontalAlignment="Center" VerticalAlignment="Center"
Width="200" Height="200"/>
</Grid>
</ZoomBorder>
</Border>
<Image x:Name="Drawings"
PointerPressed="Drawings_OnPointerPressed"
Source="{Binding DrawingsImage}"/>
</Grid>
</ZoomBorder>

<avaloniaProgressRing:ProgressRing
IsActive="{Binding BusyTasks}"
HorizontalAlignment="Center" VerticalAlignment="Center"
Width="200" Height="200"/>
</Grid>
</Border>
</Grid>
</Grid>

Expand Down Expand Up @@ -198,6 +195,7 @@
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button
HorizontalAlignment="Stretch"
Content="{Binding Name}"
Command="{ReflectionBinding #View.ViewModel.LoadMap}"
CommandParameter="{Binding Id}"/>
Expand Down
Loading

0 comments on commit 333fbcf

Please sign in to comment.