Editor/WeaponImporter/Core/Analysis/WeaponAnalysis.cs
#nullable enable annotations
using System.Numerics;
using WeaponImporter.Core.Geometry;
using WeaponImporter.Core.Weapon;
namespace WeaponImporter.Core.Analysis;
using Vector3 = System.Numerics.Vector3;
public enum GripStyle
{
/// <summary>Fingers wrap a vertical handle: pistol grip, vertical foregrip.</summary>
Wrap,
/// <summary>Palm under a horizontal handguard, fingers up the side.</summary>
Cradle,
/// <summary>Hand around a shotgun pump / forend.</summary>
Pump,
/// <summary>Support hand cupping the primary hand on a pistol.</summary>
Overlay,
/// <summary>Melee handle held in a fist.</summary>
Handle,
}
/// <summary>A place on the weapon a hand should hold.</summary>
public sealed record GripCandidate
{
public required GripSurface Surface { get; init; }
public required GripStyle Style { get; init; }
public float Confidence { get; init; }
public string Reason { get; init; } = "";
public bool Manual { get; init; }
}
public sealed record TypeGuess(WeaponType Type, float Confidence, string Reason, IReadOnlyDictionary<WeaponType, float> Scores);
/// <summary>Overrides for the analyzer, set when the user corrects orientation or scale.</summary>
public sealed record AnalyzeOptions
{
public Quaternion? Rotation { get; init; }
public float? Scale { get; init; }
/// <summary>Clip whose first frame is treated as the assembled weapon.</summary>
public string? ReferenceClip { get; init; }
}
public sealed record OrientationGuess(Quaternion ToCanonical, float Confidence, string Reason)
{
public bool NeedsFix => MathF.Abs(ToCanonical.W) < 0.9999f;
}
/// <summary>Per-bone motion over all clips, relative to the parent bone.</summary>
public sealed record BoneMotion(int Bone, float MaxTranslation, float MaxRotationDeg, Vector3 MainDirection, string PeakClip, int PeakFrame);
/// <summary>The analyzer's findings. Every field can be overridden in the editor.</summary>
public sealed class WeaponAnalysis
{
/// <summary>The weapon as it was loaded.</summary>
public required WeaponAsset Source { get; init; }
/// <summary>Clip whose first frame was used as the assembled pose ("" for the bind pose).</summary>
public string ReferenceClip { get; init; } = "";
public int ReferenceFrame { get; init; }
/// <summary>The weapon in canonical space (muzzle +X, up +Z, realistic scale). All results use this space.</summary>
public required WeaponAsset Asset { get; init; }
/// <summary>Rotation from the model's own space into canonical space (applied before <see cref="Scale"/>).</summary>
public required Quaternion ModelToCanonical { get; init; }
/// <summary>Translation applied after rotation and scale so the weapon is centred on the origin.</summary>
public Vector3 CanonicalOffset { get; init; }
/// <summary>Uniform scale from the model's units into inches.</summary>
public required float Scale { get; init; }
public string ScaleReason { get; init; } = "";
public required int RootBone { get; init; }
public required IReadOnlySet<int> ArmBones { get; init; }
public required int[] WeaponTriangles { get; init; }
public required Bounds WeaponBounds { get; init; }
public required ShapeProfile Profile { get; init; }
public required OrientationGuess Orientation { get; init; }
public required IReadOnlyList<BoneMotion> Motion { get; init; }
/// <summary>Bore line: from the rear of the receiver to the muzzle along +X at this height/offset.</summary>
public required Vector3 BoreStart { get; init; }
public required Vector3 BoreEnd { get; init; }
public float BoreDiameter { get; init; }
public List<PartDetection> Parts { get; } = new();
public PointDetection? Muzzle { get; set; }
public PointDetection? Eject { get; set; }
public TypeGuess Type { get; set; } = new(WeaponType.Custom, 0f, "", new Dictionary<WeaponType, float>());
public GripCandidate? Primary { get; set; }
public GripCandidate? Support { get; set; }
/// <summary>Every plausible support-hand area, best first (the chosen one included).</summary>
public List<GripCandidate> SupportAreas { get; } = new();
public List<AnimationGuess> Animations { get; } = new();
public Dictionary<AnimationRole, AnimationGuess> AssignedAnimations { get; } = new();
public List<string> Notes { get; } = new();
public float Length => WeaponBounds.Size.X;
public PartDetection? Part(PartKind kind) => Parts.FirstOrDefault(p => p.Kind == kind);
private MeshBvh? _weaponBvh;
/// <summary>BVH over the weapon only (arms and hands excluded).</summary>
public MeshBvh WeaponBvh => _weaponBvh ??= new MeshBvh(Asset.Mesh, WeaponTriangles);
}