Editor/HumanoidMocap/MocapContactSuggestions.cs
using System;
using System.Threading;
using System.Threading.Tasks;
using HumanoidMocap.Motion;
using Sandbox;

namespace HumanoidMocap.Editor;

public sealed partial class RetargetWindow
{
    internal async Task SuggestPropContactsAsync()
    {
        if(_editedMotion is null||_processing is not null)return;
        var expected=_editedMotion;var session=_editSession;var raw=_rawMotion;var cleanup=_appliedCleanup;
        _processing=new CancellationTokenSource();var token=_processing.Token;SetCaptureBusy(true);
        _captureStatus.Text="Checking observed hands against imported prop surfaces…";
        try
        {
            var result=await Task.Run(()=>{
                var source=raw.Copy();source.Contacts=expected.Copy().Contacts;
                var suggestions=PropContactSuggestions.Suggest(source,token);source.Contacts.AddRange(suggestions.Contacts);
                token.ThrowIfCancellationRequested();var edited=cleanup is null?source:MotionCleanup.Apply(source,cleanup);
                return (suggestions,edited);
            },token);
            await EditorPipeline.SwitchToMainThread();token.ThrowIfCancellationRequested();
            if(!this.IsValid()||session!=_editSession||expected!=_editedMotion)return;
            _editedMotion=result.edited;RefreshContacts();await RefreshMocapPreviewAsync();
            _captureStatus.Text=$"{result.suggestions.Contacts.Count} new contact suggestions. Review yellow intervals in Advanced before confirming. {result.suggestions.AmbiguousSamples} ambiguous samples left unconstrained.";
        }
        catch(OperationCanceledException){await EditorPipeline.SwitchToMainThread();if(this.IsValid())_captureStatus.Text="Contact search cancelled. Existing contacts preserved.";}
        catch(Exception error){await EditorPipeline.SwitchToMainThread();if(this.IsValid())_captureStatus.Text=error.Message;}
        finally{await EditorPipeline.SwitchToMainThread();_processing.Dispose();_processing=null;if(this.IsValid()){SetCaptureBusy(false);StartNextQueuedVideo();}}
    }
}