Editor/EffigyEditor/EffigyViewport.WeightPainting.cs
using Editor;
using Effigy;
using Sandbox;
using System;
namespace Marionette.EditorTools;
/// <summary>
/// Weight painting in the viewport: rays in, a heat-map texture out.
///
/// NOT VERTEX COLOURS. Paint already proved that a CAD cage cannot carry colour at vertex
/// density, and the material that read a COLOR stream was the workaround the atlas path deleted.
/// Skin weights stay per-vertex — the compiler skins those — but the display is a UV atlas of
/// the same barycentric interpolation the GPU will do at deform time. See WeightRamp.
/// </summary>
internal sealed partial class EffigyViewport
{
public WeightPaintSession WeightSession { get; private set; }
public bool IsWeightPainting => WeightSession is not null;
public Action WeightStrokeFinished { get; set; }
private MeshHit? _weightCursor;
private Widget _weightBarOverlay;
private bool _weightPreviewStale;
private Texture _weightTexture;
private Material _weightMaterial;
public void AddWeightOverlay( Widget bar )
{
_weightBarOverlay = bar;
bar.Position = OverlayMargin + new Vector2( 0f, 46f );
bar.Visible = false;
}
public void BeginWeightPaint( WeightPaintSession session )
{
WeightSession = session ?? throw new ArgumentNullException( nameof( session ) );
UploadWeightRamp();
_weightPreviewStale = true;
}
public void EndWeightPaint()
{
if ( WeightSession is { IsStroking: true } )
WeightSession.EndStroke();
WeightSession = null;
_weightCursor = null;
_weightTexture = null;
_weightMaterial = null;
}
public void RefreshWeightRamp()
{
if ( WeightSession is null )
return;
UploadWeightRamp();
_weightPreviewStale = true;
}
void UploadWeightRamp()
{
var canvas = WeightSession.Ramp( 256 );
var opaque = PaintMaterial.OpaqueRgba( canvas );
_weightTexture = Texture.Create( canvas.Width, canvas.Height, ImageFormat.RGBA8888 )
.WithData( opaque )
.Finish();
_weightMaterial = Material.Load( "materials/default.vmat" )?.CreateCopy( "effigy_weights" );
_weightMaterial?.Set( "g_tColor", _weightTexture );
}
void RefreshWeightPreview()
{
if ( WeightSession is null || _weightMaterial is null )
return;
var model = EffigyPreview.Build( WeightSession.Mesh, _weightMaterial );
if ( model is null )
return;
if ( _renderer is not null )
_renderer.Model = model;
else
SetModel( model, frameCamera: false );
}
private void WeightPaintFrame()
{
if ( WeightSession is null )
return;
_weightCursor = null;
var stroking = WeightSession.IsStroking;
if ( _canvasHasCursor )
{
var ray = Gizmo.CurrentRay;
var origin = new Vec3( ray.Position.x, ray.Position.y, ray.Position.z );
var direction = new Vec3( ray.Forward.x, ray.Forward.y, ray.Forward.z );
_weightCursor = WeightSession.Hover( origin, direction );
if ( !stroking && Gizmo.WasLeftMousePressed )
{
if ( WeightSession.BeginStroke( origin, direction ) )
{
stroking = true;
_weightPreviewStale = true;
}
}
else if ( stroking && Gizmo.IsLeftMouseDown )
{
if ( WeightSession.MoveTo( origin, direction ) > 0 )
_weightPreviewStale = true;
}
}
if ( stroking && !Gizmo.IsLeftMouseDown )
{
var edit = WeightSession.EndStroke();
if ( edit is not null )
WeightStrokeFinished?.Invoke();
UploadWeightRamp();
_weightPreviewStale = true;
}
if ( _weightPreviewStale )
{
RefreshWeightPreview();
_weightPreviewStale = false;
}
DrawWeightCursor();
}
void DrawWeightCursor()
{
if ( _weightCursor is not { } hit )
return;
var normal = new Vector3( hit.Normal.x, hit.Normal.y, hit.Normal.z ).Normal;
var centre = new Vector3( hit.Point.x, hit.Point.y, hit.Point.z );
var reference = MathF.Abs( normal.z ) > 0.9f ? new Vector3( 1f, 0f, 0f ) : new Vector3( 0f, 0f, 1f );
var right = Vector3.Cross( normal, reference ).Normal;
var up = Vector3.Cross( normal, right ).Normal;
var radius = WeightSession.Radius;
Gizmo.Draw.IgnoreDepth = true;
Gizmo.Draw.LineThickness = 1.5f;
Gizmo.Draw.Color = new Color( 1f, 0.55f, 0.2f, 0.9f );
var lift = normal * ( radius * 0.01f );
const int Segments = 40;
var previous = centre + right * radius + lift;
for ( var i = 1; i <= Segments; i++ )
{
var angle = i / (float)Segments * MathF.PI * 2f;
var point = centre + ( right * MathF.Cos( angle ) + up * MathF.Sin( angle ) ) * radius + lift;
Gizmo.Draw.Line( previous, point );
previous = point;
}
Gizmo.Draw.Line( centre, centre + normal * ( radius * 0.35f ) );
}
}