Skip to content

Commit 34d9ebf

Browse files
committed
Add 360 degree node viewer
1 parent 1a69da4 commit 34d9ebf

File tree

12 files changed

+769
-11
lines changed

12 files changed

+769
-11
lines changed

MystIVAssetExplorer/Extensions.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.Linq;
5+
6+
namespace MystIVAssetExplorer;
7+
8+
internal static class Extensions
9+
{
10+
public static bool TrySingle<T>(this IEnumerable<T> source, [MaybeNullWhen(false)] out T single)
11+
{
12+
using var enumerator = source.GetEnumerator();
13+
14+
if (enumerator.MoveNext())
15+
{
16+
var current = enumerator.Current;
17+
if (!enumerator.MoveNext())
18+
{
19+
single = current;
20+
return true;
21+
}
22+
}
23+
24+
single = default;
25+
return false;
26+
}
27+
28+
public static bool TrySingle<T>(this IEnumerable<T> source, Func<T, bool> predicate, [MaybeNullWhen(false)] out T single)
29+
{
30+
return source.Where(predicate).TrySingle(out single);
31+
}
32+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using MystIVAssetExplorer.Memory;
2+
using System;
3+
4+
namespace MystIVAssetExplorer.Formats;
5+
6+
public sealed class ZapImage
7+
{
8+
public required int Width { get; init; }
9+
public required int Height { get; init; }
10+
public required ReadOnlyMemory<byte> RgbChannels { get; init; }
11+
public required ReadOnlyMemory<byte> AlphaChannel { get; init; }
12+
13+
public static ZapImage Parse(ReadOnlyMemory<byte> zapFileData)
14+
{
15+
var reader = new SpanReader(zapFileData.Span);
16+
if (reader.ReadUInt32LittleEndian() != 32) throw new NotImplementedException();
17+
if (reader.ReadUInt32LittleEndian() != 2) throw new NotImplementedException();
18+
if (reader.ReadUInt32LittleEndian() != 10) throw new NotImplementedException();
19+
if (reader.ReadUInt32LittleEndian() != 10) throw new NotImplementedException();
20+
var dataLength1 = reader.ReadInt32LittleEndian();
21+
var dataLength2 = reader.ReadInt32LittleEndian();
22+
var width = reader.ReadInt32LittleEndian();
23+
var height = reader.ReadInt32LittleEndian();
24+
25+
var position = zapFileData.Length - reader.Span.Length;
26+
var image1 = zapFileData.Slice(position, dataLength1);
27+
position += dataLength1;
28+
var image2 = zapFileData.Slice(position, dataLength2);
29+
position += dataLength2;
30+
if (position != zapFileData.Length)
31+
throw new NotImplementedException();
32+
33+
return new ZapImage { Width = width, Height = height, RgbChannels = image1, AlphaChannel = image2 };
34+
}
35+
}

MystIVAssetExplorer/ILease.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using System;
2+
3+
namespace MystIVAssetExplorer;
4+
5+
public interface ILease<out T> : IDisposable
6+
{
7+
T LeasedInstance { get; }
8+
}

MystIVAssetExplorer/MystIVAssetExplorer.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<PackageReference Include="Avalonia.Controls.DataGrid" Version="$(AvaloniaVersion)" />
1616
<PackageReference Include="Avalonia.Fonts.Inter" Version="$(AvaloniaVersion)" />
1717
<PackageReference Include="Avalonia.ReactiveUI" Version="$(AvaloniaVersion)" />
18+
<PackageReference Include="Avalonia.Skia" Version="$(AvaloniaVersion)" />
1819
<PackageReference Include="FluentIcons.Avalonia" Version="1.1.303" />
1920
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
2021
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using System;
2+
using System.Threading;
3+
4+
namespace MystIVAssetExplorer;
5+
6+
public sealed class ReferenceCountedDisposable<T> where T : IDisposable
7+
{
8+
private readonly object lockObject = new();
9+
private T? instance;
10+
private int referenceCount = 1;
11+
12+
public ReferenceCountedDisposable(T instance, out ILease<T> initialLease)
13+
{
14+
this.instance = instance;
15+
initialLease = new DecrementLease(this);
16+
}
17+
18+
public ILease<T>? TryLease()
19+
{
20+
lock (lockObject)
21+
{
22+
if (referenceCount == 0)
23+
return null;
24+
25+
referenceCount++;
26+
return new DecrementLease(this);
27+
}
28+
}
29+
30+
private void Decrement()
31+
{
32+
var shouldDispose = false;
33+
var instanceToDispose = default(T);
34+
35+
lock (lockObject)
36+
{
37+
if (referenceCount == 0)
38+
throw new InvalidOperationException("More increments than decrements");
39+
40+
referenceCount--;
41+
if (referenceCount == 0)
42+
{
43+
shouldDispose = true;
44+
instanceToDispose = instance;
45+
instance = default;
46+
}
47+
}
48+
49+
if (shouldDispose)
50+
instanceToDispose!.Dispose();
51+
}
52+
53+
private sealed class DecrementLease(ReferenceCountedDisposable<T> owner) : ILease<T>
54+
{
55+
private ReferenceCountedDisposable<T>? owner = owner;
56+
57+
public T LeasedInstance => (Volatile.Read(ref owner) ?? throw new ObjectDisposedException(nameof(ILease<>))).instance!;
58+
59+
public void Dispose() => Interlocked.Exchange(ref owner, null)?.Decrement();
60+
}
61+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using Avalonia;
2+
using Avalonia.Media;
3+
using Avalonia.Platform;
4+
using Avalonia.Rendering.SceneGraph;
5+
using Avalonia.Skia;
6+
using SkiaSharp;
7+
using System;
8+
9+
namespace MystIVAssetExplorer.Skybox;
10+
11+
partial class SkyboxControl
12+
{
13+
private sealed class CheckerboardDrawOperation : ICustomDrawOperation
14+
{
15+
private readonly SkyboxControl owner;
16+
private readonly SKBitmap checkerboardTileBitmap;
17+
private readonly SKShader checkerboardShader;
18+
private readonly SKPaint checkerboardPaint;
19+
20+
public CheckerboardDrawOperation(SkyboxControl owner)
21+
{
22+
this.owner = owner;
23+
24+
checkerboardTileBitmap = new SKBitmap(2, 2);
25+
checkerboardTileBitmap.SetPixel(0, 0, new SKColor(0x55, 0x55, 0x55));
26+
checkerboardTileBitmap.SetPixel(1, 0, new SKColor(0x33, 0x33, 0x33));
27+
checkerboardTileBitmap.SetPixel(0, 1, new SKColor(0x33, 0x33, 0x33));
28+
checkerboardTileBitmap.SetPixel(1, 1, new SKColor(0x55, 0x55, 0x55));
29+
30+
checkerboardShader = SKShader.CreateBitmap(checkerboardTileBitmap, SKShaderTileMode.Repeat, SKShaderTileMode.Repeat, SKMatrix.CreateScale(8, 8));
31+
checkerboardPaint = new SKPaint { Shader = checkerboardShader };
32+
}
33+
34+
public Rect Bounds => new(owner.Bounds.Size);
35+
36+
public void Render(ImmediateDrawingContext context)
37+
{
38+
using var lease = context.TryGetFeature<ISkiaSharpApiLeaseFeature>()!.Lease();
39+
var canvas = lease.SkCanvas;
40+
41+
canvas.DrawRect(new SKRect(0, 0, (float)Bounds.Width, (float)Bounds.Height), checkerboardPaint);
42+
}
43+
44+
public void Dispose()
45+
{
46+
checkerboardPaint.Dispose();
47+
checkerboardShader.Dispose();
48+
checkerboardTileBitmap.Dispose();
49+
}
50+
51+
bool IEquatable<ICustomDrawOperation>.Equals(ICustomDrawOperation? other) => other == this;
52+
53+
bool ICustomDrawOperation.HitTest(Point p) => Bounds.Contains(p);
54+
}
55+
}

0 commit comments

Comments
 (0)