XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Text.Json;
using XFEEDToolBox.Core.Models;

namespace XFEEDToolBox.Core.Galaxy;

public sealed record GalaxyConstellationEdge(string FromName, string ToName);
public sealed record GalaxyConstellationDefinition(string Name, IReadOnlyList<GalaxyConstellationEdge> Edges);
public sealed record GalaxyConstellationLine(string FromName, string ToName, Coordinates From, Coordinates To);
public sealed record GalaxyResolvedConstellation(string Name, IReadOnlyList<GalaxyConstellationLine> Lines);
public sealed record GalaxyConstellationResolution(IReadOnlyList<GalaxyResolvedConstellation> Groups,
    IReadOnlyList<string> MissingEndpointNames, int PendingGroupCount)
{
    public bool IsComplete => PendingGroupCount == 0;
}

/// <summary>
/// The authored game graph, resolved exclusively against actual catalogue positions.
/// Original 3A28A40 stores compact XYZ / 32 without a shell or other spatial transform.
/// Coordinates here use the same light years with the catalogue's Sol origin subtracted.
/// </summary>
public static class GalaxyOriginalConstellations
{
    public const string SourceResource = "d30f11c57448cff8ec3dd0c5c80d9868482ccadb";
    public const string SourceSha256 = "ba4e049fd4eae066e72f29898a25d52692422e5905fc7a4a65105e44eb631ade";
    public const double FullOpacityDistance = 8;
    public const double InvisibleDistance = 24;
    public const float SubmissionAlpha = .1f;
    // Original rendercfg+190, style6 -> renderer+540. The group consumer
    // replaces only its alpha before the shared StarMapLine64B writer.
    public const int MaximumSegments = 206;
    public const float WorldHalfWidth = .1f;
    public const float TextureRepeatPerLightYear = 2.5f;

    public static IReadOnlyList<GalaxyConstellationDefinition> Definitions { get; } = Read();
    public static IReadOnlyList<string> EndpointNames { get; } = Array.AsReadOnly(Definitions
        .SelectMany(g => g.Edges.SelectMany(e => new[] { e.FromName, e.ToName }))
        .Distinct(StringComparer.OrdinalIgnoreCase).ToArray());

    /// <summary>
    /// A group is returned only when every endpoint is available, matching the original
    /// ready-handle gate. Missing facts remain pending; no coordinates are synthesized.
    /// Each distinct endpoint is read once so one resolution uses a consistent snapshot.
    /// </summary>
    public static GalaxyConstellationResolution Resolve(Func<string, Coordinates?> resolvePosition)
    {
        ArgumentNullException.ThrowIfNull(resolvePosition);
        var positions = new Dictionary<string, Coordinates>(StringComparer.OrdinalIgnoreCase);
        var missing = new List<string>();
        foreach (var name in EndpointNames)
        {
            if (resolvePosition(name) is { IsValid: true } position) positions.Add(name, position);
            else missing.Add(name);
        }
        var ready = new List<GalaxyResolvedConstellation>();
        foreach (var group in Definitions)
        {
            if (group.Edges.Any(e => !positions.ContainsKey(e.FromName) || !positions.ContainsKey(e.ToName))) continue;
            var lines = group.Edges.Select(e => new GalaxyConstellationLine(e.FromName, e.ToName,
                positions[e.FromName], positions[e.ToName])).ToArray();
            ready.Add(new(group.Name, Array.AsReadOnly(lines)));
        }
        return new(ready.AsReadOnly(), missing.AsReadOnly(), Definitions.Count - ready.Count);
    }

    /// <summary>
    /// Original 3A1CF60: squared forward-facing cosine, multiplied by the linear
    /// eye-to-Sol fade (opaque through 8 ly, zero at 24 ly). The group direction
    /// averages ONLY the first endpoint of each authored edge, preserving duplicates.
    /// The caller submits lines only when the returned alpha exceeds SubmissionAlpha.
    /// </summary>
    public static double Alpha(GalaxyResolvedConstellation group, Coordinates eye, Coordinates focus)
    {
        ArgumentNullException.ThrowIfNull(group);
        if (!eye.IsValid || !focus.IsValid || group.Lines.Count == 0 ||
            group.Lines.Any(line => !line.From.IsValid || !line.To.IsValid)) return 0;
        double x = 0, y = 0, z = 0;
        foreach (var line in group.Lines) { x += line.From.X; y += line.From.Y; z += line.From.Z; }
        x /= group.Lines.Count; y /= group.Lines.Count; z /= group.Lines.Count;
        var length = Math.Sqrt(x * x + y * y + z * z);
        if (length == 0) { x = 0; y = 0; z = 1; }
        else { x /= length; y /= length; z /= length; }
        var vx = focus.X - eye.X; var vy = focus.Y - eye.Y; var vz = focus.Z - eye.Z;
        var viewLength = Math.Sqrt(vx * vx + vy * vy + vz * vz);
        // The original zero-length view branch retains its initial directional alpha 1.
        var facing = viewLength > 0 ? Math.Clamp((vx * x + vy * y + vz * z) / viewLength, 0, 1) : 1;
        var distance = Math.Sqrt(eye.X * eye.X + eye.Y * eye.Y + eye.Z * eye.Z);
        return facing * facing * Math.Clamp((distance - InvisibleDistance) /
            (FullOpacityDistance - InvisibleDistance), 0, 1);
    }

    private static IReadOnlyList<GalaxyConstellationDefinition> Read()
    {
        using var stream = typeof(GalaxyOriginalConstellations).Assembly
            .GetManifestResourceStream("XFEEDToolBox.Core.Galaxy.original-constellations.json")
            ?? throw new InvalidDataException("原星座连接资源缺失。");
        using var document = JsonDocument.Parse(stream);
        var root = document.RootElement;
        if (root.GetProperty("sourceResource").GetString() != SourceResource ||
            root.GetProperty("sourceSha256").GetString() != SourceSha256)
            throw new InvalidDataException("原星座连接来源校验失败。");
        var groups = root.GetProperty("groups").EnumerateArray().Select(g => new GalaxyConstellationDefinition(
            g.GetProperty("name").GetString()!, Array.AsReadOnly(g.GetProperty("edges").EnumerateArray()
                .Select(e => new GalaxyConstellationEdge(e.GetProperty("fromName").GetString()!,
                    e.GetProperty("toName").GetString()!)).ToArray()))).ToArray();
        if (groups.Length != 16 || groups.Sum(g => g.Edges.Count) != 206 ||
            groups.Any(g => string.IsNullOrWhiteSpace(g.Name) || g.Edges.Count == 0 ||
                g.Edges.Any(e => string.IsNullOrWhiteSpace(e.FromName) || string.IsNullOrWhiteSpace(e.ToName))))
            throw new InvalidDataException("原星座连接图无效。");
        return Array.AsReadOnly(groups);
    }
}