A UI ScenePanel that loads a face mesh experiment from JSON, builds a Model with morph targets (Existing, Experimental, Target), and renders it with a SkinnedModelRenderer and camera for previewing face transfer morphs.
using System;
using System.Text.Json;
using Sandbox.UI;
namespace CharacterEditor;
/// <summary>Isolated rest-surface experiment. These morphs are not animated avatar presets.</summary>
public sealed class FaceTransferPreview : ScenePanel
{
public sealed class Geometry
{
public float[][] Source { get; set; }
public float[][] Target { get; set; }
public float[][] Existing { get; set; }
public float[][] Experimental { get; set; }
public int[][] Triangles { get; set; }
}
public string Pair { get; set; } = "human03-to-human07";
public string Mode { get; set; } = "Target";
public float Amount { get; set; } = 1;
public float Yaw { get; set; }
public string Error { get; private set; }
public SkinnedModelRenderer Body { get; private set; }
public CameraComponent ViewCamera { get; private set; }
string loaded;
public FaceTransferPreview()
{
RenderScene = new Scene();
using var scope = RenderScene.Push();
ViewCamera = new GameObject().Components.Create<CameraComponent>();
ViewCamera.FieldOfView = 30;
ViewCamera.ZNear = .1f;
ViewCamera.BackgroundColor = new(.065f, .085f, .11f);
CharacterStudioLighting.Create();
}
static Vector3 V(float[] p) => new(p[0], p[1], p[2]);
static Vector3[] Normals(Vector3[] p, int[][] triangles)
{
var normals = new Vector3[p.Length];
foreach (var t in triangles)
{
var n = Vector3.Cross(p[t[1]] - p[t[0]], p[t[2]] - p[t[0]]);
foreach (var i in t)
normals[i] += n;
}
return normals.Select(n => n.Normal).ToArray();
}
public static Model Build(Geometry data)
{
var p = data.Source.Select(V).ToArray();
var normals = Normals(p, data.Triangles);
var mesh = new Mesh(Material.Load("materials/default.vmat"));
var vertices = p.Select((v, i) => new Vertex { Position = v, Normal = normals[i], Tangent = new Vector4(0, 1, 0, 1), TexCoord0 = Vector2.Zero, Color = Color.White }).ToArray();
mesh.CreateVertexBuffer<Vertex>(vertices.Length, vertices.AsSpan());
var indices = data.Triangles.SelectMany(t => t).ToArray();
mesh.CreateIndexBuffer(indices.Length, indices.AsSpan());
foreach (var mode in new[]
{
"Existing",
"Experimental",
"Target"
}
)
{
var target = (mode == "Existing" ? data.Existing : mode == "Experimental" ? data.Experimental : data.Target).Select(V).ToArray();
var n = Normals(target, data.Triangles);
mesh.AddMorph(mode, p.Select((v, i) => new MorphDelta(i, target[i] - v, n[i] - normals[i])).ToArray());
}
mesh.Bounds = new BBox(new Vector3(-8, -8, 54), new Vector3(9, 9, 72));
return Model.Builder.AddMesh(mesh).Create();
}
public override void Tick()
{
base.Tick();
using var scope = RenderScene.Push();
if (loaded != Pair)
{
loaded = Pair;
Body?.GameObject.Destroy();
Error = null;
try
{
if (Pair != "human03-to-human07" && Pair != "human02-to-human04")
throw new ArgumentException("Unknown experiment pair.");
var data = JsonSerializer.Deserialize<Geometry>(FileSystem.Mounted.ReadAllText($"experiments/face_transfer/{Pair}.json"));
Body = new GameObject().Components.Create<SkinnedModelRenderer>();
Body.Model = Build(data);
Body.UseAnimGraph = false;
Body.Tint = new(.65f, .69f, .74f);
}
catch (Exception e)
{
Error = e.Message;
Log.Warning($"Face lab: {Error}");
}
}
if (Body.IsValid())
{
Body.LocalRotation = Rotation.FromYaw(Yaw);
foreach (var mode in new[]
{
"Existing",
"Experimental",
"Target"
}
)
Body.Morphs.Set(mode, Mode == mode ? Math.Clamp(Amount, 0, 1) : 0);
}
float distance = MathF.Max(25, 31 * Box.Rect.Width / MathF.Max(1, Box.Rect.Height));
ViewCamera.WorldPosition = new(distance, 0, 63.2f);
ViewCamera.WorldRotation = Rotation.LookAt(new Vector3(0, 0, 63.2f) - ViewCamera.WorldPosition);
}
public override void Delete(bool immediate = false)
{
var scene = RenderScene;
base.Delete(immediate);
scene?.Destroy();
}
}