Editor/WeaponImporter/Core/Warmup.cs
#nullable enable annotations

using System.Numerics;
using WeaponImporter.Core.Analysis;
using WeaponImporter.Core.Geometry;
using WeaponImporter.Core.Grip;
using WeaponImporter.Core.Maths;
using WeaponImporter.Core.Rig;
using WeaponImporter.Core.Weapon;

namespace WeaponImporter.Core;

using Vector3 = System.Numerics.Vector3;

/// <summary>
/// Runs the importer's heavy code once on a tiny box weapon, in the background, when the tool
/// opens. The first real import otherwise pays for loading and compiling all of that code, which
/// stalled the editor for about two seconds (measured in the editor gate).
/// </summary>
public static class Warmup
{
    private static int _started;

    /// <summary>Runs once per session; safe to call from any thread.</summary>
    public static void Run()
    {
        if (Interlocked.Exchange(ref _started, 1) == 1)
            return;
        try
        {
            var asset = BoxPistol();
            var analysis = WeaponAnalyzer.Analyze(asset);
            GripRegions.Of(analysis);
            GripExtractor.ExtractOwn(analysis, "warmup");
            analysis.WeaponBvh.Raycast(Vector3.Zero, Vector3.UnitX, 10f);
        }
        catch (Exception)
        {
            // Warming up is best effort; the real import reports its own errors.
        }
    }

    private static WeaponAsset BoxPistol()
    {
        var positions = new List<Vector3>();
        var indices = new List<int>();
        var parts = new List<int>();
        var names = new List<string>();
        void Box(string part, Vector3 min, Vector3 max)
        {
            names.Add(part);
            var b = positions.Count;
            for (var i = 0; i < 8; i++)
                positions.Add(new Vector3((i & 1) == 0 ? min.X : max.X, (i & 2) == 0 ? min.Y : max.Y, (i & 4) == 0 ? min.Z : max.Z));
            int[] faces = { 0, 2, 1, 1, 2, 3, 4, 5, 6, 5, 7, 6, 0, 1, 4, 1, 5, 4, 2, 6, 3, 3, 6, 7, 0, 4, 2, 2, 4, 6, 1, 3, 5, 3, 7, 5 };
            foreach (var f in faces)
                indices.Add(b + f);
            for (var t = 0; t < 12; t++)
                parts.Add(names.Count - 1);
        }
        Box("slide", new Vector3(-2f, -0.45f, 0.5f), new Vector3(5.5f, 0.45f, 1.4f));   // slide
        Box("frame", new Vector3(-2f, -0.4f, -0.1f), new Vector3(4f, 0.4f, 0.5f));      // frame
        Box("grip", new Vector3(-2.2f, -0.55f, -3.6f), new Vector3(-0.6f, 0.55f, -0.1f)); // grip
        Box("trigger", new Vector3(0.1f, -0.1f, -0.7f), new Vector3(0.3f, 0.1f, -0.1f));  // trigger
        var skeleton = Skeleton.Create(new[] { new BoneDefinition("root", null, XForm.Identity) });
        var mesh = new TriMesh(positions.ToArray(), indices.ToArray(), new int[positions.Count], parts.ToArray(), names);
        var idle = new Clip("idle", 30f, true);
        idle.Frames.Add(new[] { XForm.Identity });
        idle.Frames.Add(new[] { XForm.Identity });
        return new WeaponAsset { Name = "warmup", Kind = SourceKind.Synthetic, SourcePath = "warmup.fbx", Skeleton = skeleton, Mesh = mesh, Clips = new List<Clip> { idle } };
    }
}