s&box Package Code Search

Search C# source code, UI razor templates, shaders, and configs across s&box packages.

Showing code results for query: * (53 total matches found)
vendetta.character_editor / CharacterKit/CharacterAppearance.cs
Game game
using System;

namespace CharacterEditor;
public static class CharacterAppearance
{
	public static string ValidateForApply(CharacterAppearanceData data)
	{
		if (CharacterAppearanceData.Validate(data)is string error)
			return error;
		var skin = ClothingResources.Get(UnifiedHuman.PresentationSkin(data));
		if (skin?.HasHumanSkin != true)
			return "The selected native skin resource is unavailable.";
		var body = Model.Load(data.BaseShape is null ? HumanMarkingModels.ModelPath(skin.HumanSkinModel, data.Makeup) : UnifiedHuman.ModelPath);
		if (!body.IsValid || body.IsError)
			return "The selected native human model is unavailable.";
		var clothes = new List<Clothing>
		{
			skin
		};
		foreach (var path in new[]
		{
			data.HairPath,
			data.FacialHairPath
		}.Where(x => !string.IsNullOrEmpty(x)))
		{
			var clothing = ClothingResources.Get(path);
			if (clothing is null)
				return "A selected native hairstyle resource is unavailable.";
			if (path == data.HairPath && !ClothingResources.IsHair(clothing) || path == data.FacialHairPath && !ClothingResources.IsFacialHair(clothing))
				return "The selected item does not belong in this hair slot.";
			clothes.Add(clothing);
		}

		var tags = new TagSet(skin.HumanSkinTags.TryGetAll());
		tags.Add("human");
		foreach (var clothing in clothes.Skip(1))
		{
			var path = clothing.GetModel(clothes, tags);
			if (string.IsNullOrEmpty(path))
				return "A selected hairstyle does not support this native human body.";
			var model = Model.Load(path);
			if (!model.IsValid || model.IsError)
				return "A selected native hairstyle model is unavailable.";
		}

		return null;
	}
}
vendetta.character_editor / CharacterKit/CharacterAppearanceData.cs
Game game
using System;
using System.Text.Json.Serialization;

namespace CharacterEditor;
/// <summary>Portable appearance values. No live models, components or engine color converters are required to read a save.</summary>
public sealed class CharacterAppearanceData
{
	public int Version { get; set; } = 1;
	public CharacterBaseShape BaseShape { get; set; }
	public string[] ClothingPaths { get; set; }
	public string VoiceId { get; set; } = "man-adult";
	public string SkinPath { get; set; } = "models/citizen_clothes/body/human03.clothing";
	public string HairPath { get; set; } = "models/citizen_clothes/hair/hair_shortscruffy/hair_shortscruffy_brown.clothing";
	public string FacialHairPath { get; set; } = "";
	public float HairColorR { get; set; } = 0.22f;
	public float HairColorG { get; set; } = 0.13f;
	public float HairColorB { get; set; } = 0.08f;
	public float SkinColorR { get; set; } = 1;
	public float SkinColorG { get; set; } = 1;
	public float SkinColorB { get; set; } = 1;
	public bool CustomEyeColor { get; set; }
	public float EyeColorR { get; set; } = .24f;
	public float EyeColorG { get; set; } = .12f;
	public float EyeColorB { get; set; } = .055f;

	[JsonIgnore]
	public Color EyeColor
	{
		get => new(EyeColorR, EyeColorG, EyeColorB);
		set
		{
			EyeColorR = value.r;
			EyeColorG = value.g;
			EyeColorB = value.b;
		}
	}

	public bool NormalizeSkinTone { get; set; }
	public CharacterMakeupData Makeup { get; set; } = new();
	public float NeckSize { get; set; } = 0.5f;
	public float TrapeziusSize { get; set; } = 0.5f;
	public float CollarboneHeight { get; set; } = 0.5f;
	// Zero leaves the existing abdomen unchanged, including in older saves.
	public float BellySize { get; set; } = 0;
	public float WaistSize { get; set; } = 0.5f;
	public float ChestSize { get; set; } = 0.5f;
	public float HeadShape { get; set; } = 0.5f;
	public float NoseSize { get; set; } = 0.5f;
	public float ChinSize { get; set; } = 0.5f;
	public float NoseWidth { get; set; } = 0.5f;
	public float ChinWidth { get; set; } = 0.5f;
	public float Fitness { get; set; } = 0.5f;
	public float JawWidth { get; set; } = 0.5f;
	public float NostrilWidth { get; set; } = 0.5f;
	public float ForeheadWidth { get; set; } = 0.5f;
	public float CheekboneWidth { get; set; } = 0.5f;
	public float EarSize { get; set; } = 0.5f;
	public float EyeSize { get; set; } = 0.5f;
	public float CheekFullness { get; set; } = 0.5f;
	public float NostrilFullness { get; set; } = 0.5f;
	public float NoseHeight { get; set; } = 0.5f;
	public float ChinHeight { get; set; } = 0.5f;
	public float SubmentalFullness { get; set; } = 0.5f;
	public float BrowProminence { get; set; } = 0.5f;
	public float BodyFeatureSize { get; set; } = 0.5f;
	public float ButtSize { get; set; } = 0.5f;
	public float FootSize { get; set; } = 0.5f;
	public float PhiltrumWidth { get; set; } = 0.5f;
	public float ColumellaWidth { get; set; } = 0.5f;
	public float MouthWidth { get; set; } = 0.5f;
	public float MouthProjection { get; set; } = 0.5f;
	public float UpperLipFullness { get; set; } = 0.5f;
	public float LowerLipFullness { get; set; } = 0.5f;
	// Normal ears are the minimum, including when reading an older save.
	public float EarTaper { get; set; } = 0;

	[JsonIgnore]
	public Color HairColor
	{
		get => new(HairColorR, HairColorG, HairColorB);
		set
		{
			HairColorR = value.r;
			HairColorG = value.g;
			HairColorB = value.b;
		}
	}

	[JsonIgnore]
	public Color SkinColor
	{
		get => new(SkinColorR, SkinColorG, SkinColorB);
		set
		{
			SkinColorR = value.r;
			SkinColorG = value.g;
			SkinColorB = value.b;
		}
	}

	public CharacterAppearanceData DeepCopy() => new()
	{
		Version = Version,
		BaseShape = BaseShape?.DeepCopy(),
		ClothingPaths = ClothingPaths?.ToArray(),
		VoiceId = VoiceId,
		SkinPath = SkinPath,
		HairPath = HairPath,
		FacialHairPath = FacialHairPath,
		HairColorR = HairColorR,
		HairColorG = HairColorG,
		HairColorB = HairColorB,
		SkinColorR = SkinColorR,
		SkinColorG = SkinColorG,
		SkinColorB = SkinColorB,
		NormalizeSkinTone = NormalizeSkinTone,
		CustomEyeColor = CustomEyeColor,
		EyeColorR = EyeColorR,
		EyeColorG = EyeColorG,
		EyeColorB = EyeColorB,
		Makeup = Makeup?.DeepCopy(),
		NeckSize = NeckSize,
		TrapeziusSize = TrapeziusSize,
		CollarboneHeight = CollarboneHeight,
		BellySize = BellySize,
		WaistSize = WaistSize,
		ChestSize = ChestSize,
		HeadShape = HeadShape,
		NoseSize = NoseSize,
		ChinSize = ChinSize,
		NoseWidth = NoseWidth,
		ChinWidth = ChinWidth,
		Fitness = Fitness,
		JawWidth = JawWidth,
		NostrilWidth = NostrilWidth,
		ForeheadWidth = ForeheadWidth,
		CheekboneWidth = CheekboneWidth,
		EarSize = EarSize,
		EyeSize = EyeSize,
		CheekFullness = CheekFullness,
		NostrilFullness = NostrilFullness,
		NoseHeight = NoseHeight,
		ChinHeight = ChinHeight,
		SubmentalFullness = SubmentalFullness,
		BrowProminence = BrowProminence,
		BodyFeatureSize = BodyFeatureSize,
		ButtSize = ButtSize,
		FootSize = FootSize,
		EarTaper = EarTaper,
		PhiltrumWidth = PhiltrumWidth,
		ColumellaWidth = ColumellaWidth,
		MouthWidth = MouthWidth,
		MouthProjection = MouthProjection,
		UpperLipFullness = UpperLipFullness,
		LowerLipFullness = LowerLipFullness
	};
	public static string CompositionKey(CharacterAppearanceData data) => data is null ? "" : $"{(data.BaseShape is null ? data.SkinPath : UnifiedHuman.ModelPath + UnifiedHuman.PresentationSkin(data))}|{data.BaseShape?.ClothingVariant}|{data.HairPath}|{data.FacialHairPath}|{HumanMarkingModels.NeedsArmRegions(data.Makeup)}|{string.Join(';', WardrobeCatalog.Paths(data))}";
	/// <summary>Cold-safe validation used by save lists and menu preflight. Resources are resolved separately when applied.</summary>
	public static string Validate(CharacterAppearanceData data)
	{
		if (data is null)
			return "Choose a character appearance.";
		if (data.Version is not (1 or 2))
			return "This appearance version is unsupported.";
		if (data.BaseShape is not null && (data.Version != 2 || !data.BaseShape.Valid))
			return "Base shape requires version 2, valid face influences and custom face data, and body controls between zero and one.";
		if (data.Version == 1 && data.ClothingPaths is not null)
			return "Saved clothing requires appearance version 2.";
		if (WardrobeCatalog.Validate(data.ClothingPaths)is string wardrobeError)
			return wardrobeError;
		if (!VoiceIds.IsSupported(data.VoiceId))
			return "Choose a supported character voice.";
		if (data.Makeup?.Valid != true)
			return "Makeup colors and strength must be finite values between zero and one, with a supported tattoo style.";
		if (data.HairPath is null || data.FacialHairPath is null)
			return "Use an empty path for no hair or facial hair.";
		var skin = AppearanceCatalog.Skins.FirstOrDefault(x => string.Equals(x.ResourcePath, data.SkinPath, StringComparison.OrdinalIgnoreCase));
		if (skin is null)
			return "Choose a supported native human face and skin.";
		if (data.HairPath.Length > 0 && !ClothingResources.ValidReference(data.HairPath))
			return "Choose a supported native hairstyle.";
		var facial = AppearanceCatalog.FacialHair.FirstOrDefault(x => string.Equals(x.ResourcePath, data.FacialHairPath ?? "", StringComparison.OrdinalIgnoreCase));
		if ((data.FacialHairPath.Length > 0 && !ClothingResources.ValidReference(data.FacialHairPath)) || (facial is not null && data.BaseShape is null && !string.IsNullOrEmpty(facial.BodyType) && facial.BodyType != skin.BodyType))
			return "That facial hair does not fit the selected body.";
		if (new[]
		{
			data.HairColorR,
			data.HairColorG,
			data.HairColorB
		}.Any(x => !float.IsFinite(x) || x < 0 || x > 1))
			return "Hair color must contain finite values between zero and one.";
		if (new[]
		{
			data.SkinColorR,
			data.SkinColorG,
			data.SkinColorB
		}.Any(x => !float.IsFinite(x) || x < 0 || x > 1))
			return "Skin color must contain finite values between zero and one.";
		if (new[]
		{
			data.EyeColorR,
			data.EyeColorG,
			data.EyeColorB
		}.Any(x => !float.IsFinite(x) || x < 0 || x > 1))
			return "Eye color must contain finite values between zero and one.";
		if (AppearanceCatalog.Sliders.Any(x => !float.IsFinite(x.Get(data)) || x.Get(data) < x.Min || x.Get(data) > x.Max))
			return "Appearance sliders must contain finite values between zero and one.";
		return null;
	}
}
vendetta.character_editor / CharacterKit/UnifiedWardrobe.cs
Game game
namespace CharacterEditor;
/// <summary>Authored human cuts fitted automatically to both unified body endpoints.</summary>
public static partial class UnifiedWardrobe
{
	/// <summary>Head replacements can retain skin or deliberately replace the entire neck.</summary>
	public static bool RetainsHumanNeck(Clothing item)
	{
		if (string.IsNullOrEmpty(item.HumanSkinModel)) return true;
		var skin = Model.Load(item.HumanSkinModel);
		// Use the replacement's native skin presentation. A transparent/non-skin
		// head material must not be replaced with the unified human's skin neck.
		return skin.MeshInfo.Meshes.Where(m => m.Name == "head_01")
			.SelectMany(m => m.DrawCalls).Any(d => d.Material?.ShaderName == "shaders/skin.shader");
	}

	public static string ModelPath(Clothing item, IEnumerable<Clothing> selected, TagSet tags, CharacterAppearanceData data)
	{
		var native = item.GetModel(selected.ToArray(), tags);
		if (data.BaseShape is null)
			return native;
		// Preserve conditional model overrides selected by native clothing metadata.
		if (native != item.Model && native != item.HumanAltModel && native != item.HumanAltFemaleModel)
			return native;
		// Explicit preference selects only the authored cut; the morph still uses
		// the actual body blend. Single-variant items retain their usual fallback.
		if (data.BaseShape.ClothingVariant is not null && !string.IsNullOrEmpty(item.HumanAltModel) && !string.IsNullOrEmpty(item.HumanAltFemaleModel))
		{
			var choice = data.BaseShape.ClothingVariant == "male" ? item.HumanAltModel : item.HumanAltFemaleModel;
			return choice;
		}

		// A single authored human variant is usable across the full body range.
		var preferred = data.BaseShape.MaleBody >= .5f ? item.HumanAltModel : item.HumanAltFemaleModel;
		if (string.IsNullOrEmpty(preferred))
			preferred = string.IsNullOrEmpty(item.HumanAltModel) ? item.HumanAltFemaleModel : item.HumanAltModel;
		return !string.IsNullOrEmpty(preferred) ? preferred : native;
	}

	public static Model Resolve(Clothing item, IEnumerable<Clothing> selected, TagSet tags, CharacterAppearanceData data)
	{
		var path = ModelPath(item, selected, tags, data);
		var native = Model.Load(path);
		if (data.BaseShape is null || string.IsNullOrEmpty(path) || (string.IsNullOrEmpty(item.HumanAltModel) && string.IsNullOrEmpty(item.HumanAltFemaleModel)))
			return native;
		if (item.Category.ToString().StartsWith("FacialHair") || item.Category.ToString().StartsWith("Hair"))
			return native;
		bool female = path == item.HumanAltFemaleModel && path != item.HumanAltModel;
		if (path != item.HumanAltModel && path != item.HumanAltFemaleModel)
			female = tags.Has("female");
		return AutomaticWardrobeFit.Fit(native, female, fitFace: (item.HideBody & Clothing.BodyGroups.Head) == 0);
	}

	public static void Apply(SkinnedModelRenderer renderer, CharacterAppearanceData data)
	{
		if (data.BaseShape is not null && AutomaticWardrobeFit.IsFitted(renderer.Model))
			renderer.Morphs.Set("ce_body_female", 1 - data.BaseShape.MaleBody);
	}
}// Keep hair outside body fitting.

vendetta.character_editor / CharacterKit/AppearanceCatalog.cs
Game game
using System;
using System.Threading.Tasks;

namespace CharacterEditor;
public sealed record AppearanceOption(string Id, string Label, string ResourcePath, string BodyType = "", string Icon = "");
public sealed record AppearancePreset(string Id, string Label, CharacterAppearanceData Appearance);
public sealed record AppearanceColorOption(string Id, string Label, Color Color, bool NormalizeSkinTone = false);
public sealed class AppearanceSlider
{
	public string Id { get; }
	public string Label { get; }
	public string Group { get; }
	public float Min => 0;
	public float Max => 1;
	public float Neutral => Id is "eartaper" or "belly" ? 0 : 0.5f;

	public string LabelFor(CharacterAppearanceData data) => Id == "bodyfeature" ? (AppearanceCatalog.IsFemale(data) ? "Breast size" : "Bulge size") : Label;
	public AppearanceSlider(string id, string label, string group)
	{
		Id = id;
		Label = label;
		Group = group;
	}

	public float Get(CharacterAppearanceData data) => Id switch
	{
		"neck" => data.NeckSize,
		"trapezius" => data.TrapeziusSize,
		"collarbone" => data.CollarboneHeight,
		"belly" => data.BellySize,
		"waist" => data.WaistSize,
		"chest" => data.ChestSize,
		"fitness" => data.Fitness,
		"head" => data.HeadShape,
		"nose" => data.NoseSize,
		"nosewidth" => data.NoseWidth,
		"chin" => data.ChinSize,
		"chinwidth" => data.ChinWidth,
		"jaw" => data.JawWidth,
		"nostrils" => data.NostrilWidth,
		"forehead" => data.ForeheadWidth,
		"cheekbones" => data.CheekboneWidth,
		"ears" => data.EarSize,
		"eyes" => data.EyeSize,
		"cheeks" => data.CheekFullness,
		"nostrilfullness" => data.NostrilFullness,
		"noseheight" => data.NoseHeight,
		"chinheight" => data.ChinHeight,
		"submental" => data.SubmentalFullness,
		"brow" => data.BrowProminence,
		"bodyfeature" => data.BodyFeatureSize,
		"butt" => data.ButtSize,
		"feet" => data.FootSize,
		"eartaper" => data.EarTaper,
		"philtrum" => data.PhiltrumWidth,
		"columella" => data.ColumellaWidth,
		"mouthwidth" => data.MouthWidth,
		"mouthprojection" => data.MouthProjection,
		"upperlip" => data.UpperLipFullness,
		"lowerlip" => data.LowerLipFullness,
		_ => Neutral
	};
	public void Set(CharacterAppearanceData data, float value)
	{
		if (data is null || !float.IsFinite(value))
			return;
		value = Math.Clamp(value, Min, Max);
		// Store data rather than delegates in the catalog so native hotload can preserve these selectors.
		switch (Id)
		{
			case "neck":
				data.NeckSize = value;
				break;
			case "trapezius":
				data.TrapeziusSize = value;
				break;
			case "collarbone":
				data.CollarboneHeight = value;
				break;
			case "belly":
				data.BellySize = value;
				break;
			case "waist":
				data.WaistSize = value;
				break;
			case "chest":
				data.ChestSize = value;
				break;
			case "fitness":
				data.Fitness = value;
				break;
			case "head":
				data.HeadShape = value;
				break;
			case "nose":
				data.NoseSize = value;
				break;
			case "nosewidth":
				data.NoseWidth = value;
				break;
			case "chin":
				data.ChinSize = value;
				break;
			case "chinwidth":
				data.ChinWidth = value;
				break;
			case "jaw":
				data.JawWidth = value;
				break;
			case "nostrils":
				data.NostrilWidth = value;
				break;
			case "forehead":
				data.ForeheadWidth = value;
				break;
			case "cheekbones":
				data.CheekboneWidth = value;
				break;
			case "ears":
				data.EarSize = value;
				break;
			case "eyes":
				data.EyeSize = value;
				break;
			case "cheeks":
				data.CheekFullness = value;
				break;
			case "nostrilfullness":
				data.NostrilFullness = value;
				break;
			case "noseheight":
				data.NoseHeight = value;
				break;
			case "chinheight":
				data.ChinHeight = value;
				break;
			case "submental":
				data.SubmentalFullness = value;
				break;
			case "brow":
				data.BrowProminence = value;
				break;
			case "bodyfeature":
				data.BodyFeatureSize = value;
				break;
			case "butt":
				data.ButtSize = value;
				break;
			case "feet":
				data.FootSize = value;
				break;
			case "eartaper":
				data.EarTaper = value;
				break;
			case "philtrum":
				data.PhiltrumWidth = value;
				break;
			case "columella":
				data.ColumellaWidth = value;
				break;
			case "mouthwidth":
				data.MouthWidth = value;
				break;
			case "mouthprojection":
				data.MouthProjection = value;
				break;
			case "upperlip":
				data.UpperLipFullness = value;
				break;
			case "lowerlip":
				data.LowerLipFullness = value;
				break;
		}
	}
}

/// <summary>Curated installed, non-inventory native assets. IDs and paths are stable; no cloud discovery or external download is needed.</summary>
public static class AppearanceCatalog
{
	static readonly Dictionary<string, Task> resourceLoads = new(StringComparer.OrdinalIgnoreCase);
	public static bool Loading => resourceLoads.Values.Any(x => x is not null && !x.IsCompleted);
	public static string Error { get; private set; }
	public static int Revision { get; private set; }

	const string Root = "models/citizen_clothes/";
	static AppearanceOption Skin(string number, string label, string body) => new("human" + number, label, Root + "body/human" + number + ".clothing", body, Root + "body/human" + number + ".clothing.png");
	static AppearanceOption Style(string id, string label, string relative, string body = "") => new(id, label, Root + "hair/" + relative + ".clothing", body, Root + "hair/" + relative + ".clothing.png");
	public static IReadOnlyList<AppearanceOption> Skins { get; } = Array.AsReadOnly(new[] { Skin("01", "Face 1 · dark · male", "male"), Skin("02", "Face 2 · dark · female", "female"), Skin("03", "Face 3 · light · male", "male"), Skin("04", "Face 4 · light · female", "female"), Skin("05", "Face 5 · light · male", "male"), Skin("06", "Face 6 · dark · male", "male"), Skin("07", "Face 7 · light · male", "male") });
	public static IReadOnlyList<AppearanceOption> Hair { get; } = Array.AsReadOnly(new[] { new AppearanceOption("none", "No hair", ""), Style("scruffy", "Short scruffy", "hair_shortscruffy/hair_shortscruffy_brown"), Style("short_braids", "Short braids", "hair_short_braids/hair_short_braids"), Style("short_dreads", "Short dreadlocks", "hair_short_dreadlocks/short_dreadlocks"), Style("bob", "Bob cut", "hair_bobcut/hair_bobcut"), Style("long_bob", "Long bob", "hair_longbob/hair_longbob"), Style("messy_bun", "Messy bun", "hair_messy_bun/hair_messy_bun"), Style("ponytail", "Tight ponytail", "hair_tight_ponytail/hair_tight_ponytail_brown"), Style("long_curly", "Long curly", "hair_longcurly/hair_longcurly_brown"), Style("long_bangs", "Long bangs", "hair_long_bangs/hair_long_bangs"), Style("long_dreads", "Long dreadlocks", "hair_long_dreadlocks/hair_long_dreadlocks"), Style("tied_braids", "Tied braids", "hair_tied_braids/hair_tied_braids"), Style("tied_dreads", "Tied dreadlocks", "hair_tied_dreads/hair_tied_dreads"), Style("slicked", "Slicked back", "hair_slickedback/hair_slickedback"), Style("balding", "Balding", "hair_balding/hair_baldingbrown"), Style("thinning", "Thinning short", "hair_thinning_short/thinning_short"), Style("pigtails", "Pigtails", "hair_pigtails/hair_pigtails"), Style("long_brown", "Long hair", "hair_longbrown/Models/hair_longbrown"), Style("loose", "Loose hair", "hair_looseblonde/hair.loose.grey"), Style("slicked_balding", "Slicked back balding", "hair_slicked_back_balding/hair_slicked_back_balding") });
	public static IReadOnlyList<AppearanceOption> FacialHair { get; } = Array.AsReadOnly(new[] { new AppearanceOption("none", "No facial hair", ""), Style("patchy", "Patchy beard", "patchy_beard/patchy_beard", "male"), Style("medium", "Medium beard", "medium_beard/medium_beard", "male"), Style("goatee", "Goatee", "goatee/goatee", "male"), Style("moustache", "Moustache", "moustache/moustache", "male"), Style("handlebar", "Handlebar moustache", "handlebar_moustache/handlebar_moustache", "male"), Style("chops", "Sideburns", "mutton_chops/mutton_chops", "male"), Style("braided", "Braided beard", "braided_long_beard/braided_long_beard", "male"), Style("decorated", "Decorated beard", "decorated_beard/decorated_beard", "male") });
	public static IReadOnlyList<AppearanceColorOption> Colors { get; } = Array.AsReadOnly(new[] { new AppearanceColorOption("black", "Black", new Color(0.045f, 0.035f, 0.028f)), new AppearanceColorOption("brown", "Brown", new Color(0.22f, 0.13f, 0.08f)), new AppearanceColorOption("auburn", "Auburn", new Color(0.48f, 0.17f, 0.07f)), new AppearanceColorOption("blonde", "Blonde", new Color(0.74f, 0.57f, 0.31f)), new AppearanceColorOption("silver", "Silver", new Color(0.66f, 0.67f, 0.7f)), new AppearanceColorOption("white", "White", new Color(0.92f, 0.89f, 0.82f)) });

	public const int ShapeControlCount = 34;
	public static bool IsFemale(CharacterAppearanceData data) => data?.BaseShape is not null ? data.BaseShape.MaleBody < .5f : Skins.FirstOrDefault(x => string.Equals(x.ResourcePath, data?.SkinPath, StringComparison.OrdinalIgnoreCase))?.BodyType == "female";
	static IReadOnlyList<AppearanceColorOption> humanSkinColors;
	public static IReadOnlyList<AppearanceColorOption> HumanSkinColors
	{
		get
		{
			if (humanSkinColors?.Count != 7 || !humanSkinColors[1].NormalizeSkinTone)
				humanSkinColors = CreateHumanSkinColors();
			return humanSkinColors;
		}
	}

	static IReadOnlyList<AppearanceColorOption> CreateHumanSkinColors() => Array.AsReadOnly(new[] { new AppearanceColorOption("native", "Native", Color.White), new AppearanceColorOption("fair", "Fair", Color.White, true), new AppearanceColorOption("rose", "Rose", new Color(1, 0.96f, 0.96f), true), new AppearanceColorOption("golden", "Golden", new Color(1, 0.96f, 0.88f), true), new AppearanceColorOption("olive", "Olive", new Color(0.91f, 0.88f, 0.77f), true), new AppearanceColorOption("tan", "Tan", new Color(0.72f, 0.6f, 0.5f), true), new AppearanceColorOption("umber", "Umber", new Color(0.42f, 0.3f, 0.23f), true) });
	static IReadOnlyList<AppearanceColorOption> fantasySkinColors;
	public static IReadOnlyList<AppearanceColorOption> FantasySkinColors
	{
		get
		{
			if (fantasySkinColors?.Count != 6 || fantasySkinColors.Any(x => !x.NormalizeSkinTone))
				fantasySkinColors = CreateFantasySkinColors();
			return fantasySkinColors;
		}
	}

	static IReadOnlyList<AppearanceColorOption> CreateFantasySkinColors() => Array.AsReadOnly(new[] { new AppearanceColorOption("blue", "Blue", new Color(0.18f, 0.4f, 1), true), new AppearanceColorOption("purple", "Purple", new Color(0.6f, 0.25f, 1), true), new AppearanceColorOption("green", "Green", new Color(0.3f, 0.85f, 0.4f), true), new AppearanceColorOption("red", "Crimson", new Color(1, 0.25f, 0.22f), true), new AppearanceColorOption("teal", "Teal", new Color(0.18f, 0.85f, 0.9f), true), new AppearanceColorOption("grey", "Slate", new Color(0.5f, 0.6f, 0.74f), true) });
	// Native hotload can retain the previous static list when the catalog grows.
	// Build the supported immutable catalog lazily so new controls are available
	// in an already running editor as well as a cold-start game.
	public static IReadOnlyList<AppearanceSlider> Sliders
	{
		get
		{
			if (contourControls?.Count != ShapeControlCount)
				contourControls = CreateContourControls();
			return contourControls;
		}
	}

	static IReadOnlyList<AppearanceSlider> contourControls;
	static IReadOnlyList<AppearanceSlider> CreateContourControls() => Array.AsReadOnly(new[] { new AppearanceSlider("neck", "Neck size", "Body"), new AppearanceSlider("trapezius", "Trapezius", "Body"), new AppearanceSlider("collarbone", "Collarbone height", "Body"), new AppearanceSlider("belly", "Belly size", "Body"), new AppearanceSlider("waist", "Waist size", "Body"), new AppearanceSlider("chest", "Chest size", "Body"), new AppearanceSlider("fitness", "Fitness", "Body"), new AppearanceSlider("bodyfeature", "Body contour", "Body"), new AppearanceSlider("butt", "Butt size", "Body"), new AppearanceSlider("feet", "Foot size", "Body"), new AppearanceSlider("head", "Head shape", "Face"), new AppearanceSlider("forehead", "Forehead width", "Face"), new AppearanceSlider("cheekbones", "Cheekbone width", "Face"), new AppearanceSlider("cheeks", "Cheek fullness", "Face"), new AppearanceSlider("jaw", "Jaw width", "Jaw & chin"), new AppearanceSlider("chin", "Chin projection", "Jaw & chin"), new AppearanceSlider("chinwidth", "Chin width", "Jaw & chin"), new AppearanceSlider("chinheight", "Chin height", "Jaw & chin"), new AppearanceSlider("submental", "Under-chin fullness", "Jaw & chin"), new AppearanceSlider("nose", "Nose projection", "Nose"), new AppearanceSlider("nosewidth", "Nose width", "Nose"), new AppearanceSlider("nostrils", "Nostril width", "Nose"), new AppearanceSlider("nostrilfullness", "Nostril-wing fullness", "Nose"), new AppearanceSlider("noseheight", "Nose tip height", "Nose"), new AppearanceSlider("columella", "Columella width", "Nose"), new AppearanceSlider("philtrum", "Philtrum width", "Mouth"), new AppearanceSlider("mouthwidth", "Mouth width", "Mouth"), new AppearanceSlider("mouthprojection", "Mouth projection", "Mouth"), new AppearanceSlider("upperlip", "Upper lip fullness", "Mouth"), new AppearanceSlider("lowerlip", "Lower lip fullness", "Mouth"), new AppearanceSlider("eyes", "Eye size", "Eyes & ears"), new AppearanceSlider("ears", "Ear size", "Eyes & ears"), new AppearanceSlider("brow", "Brow prominence", "Eyes & ears"), new AppearanceSlider("eartaper", "Ear taper", "Eyes & ears") });
	static CharacterAppearanceData Preset(string skin, string hair, string facial, string color) => new()
	{
		SkinPath = Skins.Single(x => x.Id == skin).ResourcePath,
		HairPath = Hair.Single(x => x.Id == hair).ResourcePath,
		FacialHairPath = FacialHair.Single(x => x.Id == facial).ResourcePath,
		HairColor = Colors.Single(x => x.Id == color).Color
	};
	static IReadOnlyList<AppearancePreset> presets;
	public static IReadOnlyList<AppearancePreset> Presets
	{
		get
		{
			if (presets?.Count != 7)
				presets = Array.AsReadOnly(new[]
				{
					new AppearancePreset("traveller", "Traveller", Preset("human03", "scruffy", "none", "brown")),
					new AppearancePreset("scout", "Scout", Preset("human02", "short_braids", "none", "black")),
					new AppearancePreset("scholar", "Scholar", Preset("human04", "messy_bun", "none", "auburn")),
					new AppearancePreset("veteran", "Veteran", Preset("human07", "balding", "medium", "silver")),
					new AppearancePreset("wanderer", "Wanderer", Preset("human06", "short_dreads", "none", "black")),
					new AppearancePreset("artisan", "Artisan", Preset("human05", "slicked", "goatee", "brown")),
					new AppearancePreset("explorer", "Explorer", Preset("human01", "tied_braids", "none", "black"))
				});
			return presets;
		}
	}

	public static CharacterAppearanceData CreateDefault() => ApplyPreset("traveller");
	public static CharacterAppearanceData ApplyPreset(string id) => Presets.FirstOrDefault(x => x.Id == id)?.Appearance.DeepCopy() ?? new CharacterAppearanceData();
	public static void ResetShapes(CharacterAppearanceData data)
	{
		if (data is null)
			return;
		foreach (var slider in Sliders)
			slider.Set(data, slider.Neutral);
		if (data.BaseShape is not null)
		{
			data.BaseShape.BreastSize = .5f;
			data.BaseShape.BulgeSize = .5f;
		}
	}

	public static CharacterAppearanceData Randomize(Random rng = null, bool blendHeads = false)
	{
		rng ??= new Random();
		var body = rng.Next(2) == 0 ? "male" : "female";
		// Cross-body skins need weighted heads; native (v1) characters keep their native pool.
		var skins = Skins.Where(x => blendHeads ? (body == "male" || x.Id is not ("human05" or "human07")) : x.BodyType == body).ToArray();
		var skin = skins[rng.Next(skins.Length)];
		var hair = RandomHairFor(body);
		var facial = FacialHair.Where(x => x.BodyType == "" || x.BodyType == body).ToArray();
		var tones = HumanSkinColors.Where(x => body == "female" && skin.Id == "human06" ? x.Id is "native" or "tan" or "umber" : x.NormalizeSkinTone).ToArray();
		var tone = tones[rng.Next(tones.Length)];
		var data = new CharacterAppearanceData
		{
			SkinPath = skin.ResourcePath,
			HairPath = hair[rng.Next(hair.Count)].ResourcePath,
			FacialHairPath = facial[rng.Next(facial.Length)].ResourcePath,
			HairColor = Colors[rng.Next(Colors.Count)].Color,
			SkinColor = tone.Color,
			NormalizeSkinTone = tone.NormalizeSkinTone
		};
		foreach (var slider in Sliders.Where(x => x.Id != "belly"))
			slider.Set(data, 0.3f + (float)rng.NextDouble() * 0.4f);
		// An added belly is an occasional, modest feature rather than a default.
		data.BellySize = rng.NextDouble() < 0.10 ? 0.1f + (float)rng.NextDouble() * 0.3f : 0;
		if (body == "female" && rng.NextDouble() < 0.60)
		{
			AddMakeup("Lipstick", 0.70, 0.35f, 0.85f, 4);
			AddMakeup("Eyeshadow", 0.45, 0.20f, 0.65f, 3);
			AddMakeup("Blush", 0.55, 0.15f, 0.55f, 4);
		}

		if (blendHeads)
		{
			UnifiedHuman.Enable(data);
			// Body identity must follow the roll, not the selected skin's native body.
			bool female = body == "female";
			data.BaseShape.MaleBody = female ? 0 : 1;
			data.BaseShape.BreastSize = female ? data.BodyFeatureSize : .5f;
			data.BaseShape.BulgeSize = female ? .5f : data.BodyFeatureSize;
			data.BaseShape.HeadWeights = RandomHeadWeights(rng, female, skin.Id, false);
			foreach (var region in FaceRegions.Ids)
			{
				// Each part gets its own dominant source rather than averaging every head
				// equally. Keep jaw/neck identity especially close to the body's family.
				data.BaseShape.Regions[region] = new()
				{
					OverallInfluence = .15f + (float)rng.NextDouble() * .20f,
					HeadWeights = RandomHeadWeights(rng, female, skin.Id, region is "jaw" or "throat")
				};
			}

			// A shared jaw/throat source keeps the lower-face silhouette coherent.
			data.BaseShape.Regions["throat"] = data.BaseShape.Regions["jaw"].DeepCopy();
		}

		data.ClothingPaths = WardrobeCatalog.Randomize(rng, body == "female");
		data.Version = 2;
		return data;
		void AddMakeup(string layer, double chance, float low, float high, int paletteCount)
		{
			if (rng.NextDouble() >= chance)
				return;
			var options = MakeupCatalog.Colors(layer);
			var target = MakeupCatalog.Layer(data.Makeup, layer);
			target.Color = options[rng.Next(Math.Min(paletteCount, options.Count))].Color;
			target.Amount = low + (float)rng.NextDouble() * (high - low);
		}
	}

	static float[] RandomHeadWeights(Random rng, bool female, string skin, bool structural)
	{
		// Allocate by family before choosing individual heads: five masculine faces
		// must not overwhelm the two feminine ones. Skin 04 on men excludes them.
		float ownShare = structural ? .95f + (float)rng.NextDouble() * .05f : .85f + (float)rng.NextDouble() * .15f;
		float femaleShare = female ? ownShare : skin == "human04" ? 0 : 1 - ownShare;
		var weights = new float[7];
		Fill(new[] { 1, 3 }, femaleShare);
		Fill(new[] { 0, 2, 4, 5, 6 }, 1 - femaleShare);
		float max = weights.Max();
		for (int i = 0; i < weights.Length; i++)
			weights[i] /= max;
		return weights;
		void Fill(int[] indices, float share)
		{
			int primary = indices[rng.Next(indices.Length)];
			float dominance = .50f + (float)rng.NextDouble() * .40f;
			weights[primary] = share * dominance;
			float total = 0;
			foreach (int i in indices)
				if (i != primary)
				{
					weights[i] = .05f + (float)rng.NextDouble();
					total += weights[i];
				}

			foreach (int i in indices)
				if (i != primary)
					weights[i] *= share * (1 - dominance) / total;
		}
	}

	// Preferences apply only to Randomize. Every hairstyle remains manually
	// selectable and valid on either human body, including male pigtails.
	public static IReadOnlyList<AppearanceOption> RandomHairFor(string bodyType) => Hair.Where(x => bodyType == "female" ? x.Id is not ("balding" or "thinning" or "slicked_balding") : x.Id is "none" or "scruffy" or "short_braids" or "short_dreads" or "long_dreads" or "tied_braids" or "tied_dreads" or "slicked" or "balding" or "thinning" or "ponytail" or "long_curly" or "long_brown" or "slicked_balding").ToArray();
	/// <summary>Loads only curated native resources, either the whole creator catalog or one selected/saved appearance.</summary>
	public static async Task EnsureLoadedAsync(CharacterAppearanceData data = null)
	{
		if (data is not null && CharacterAppearanceData.Validate(data)is string error)
			throw new InvalidOperationException(error);
		var paths = data is null ? Skins.Concat(Hair).Concat(FacialHair).Select(x => x.ResourcePath) : new[]
		{
			data.SkinPath,
			UnifiedHuman.PresentationSkin(data),
			data.HairPath,
			data.FacialHairPath
		};
		try
		{
			foreach (var path in paths.Where(x => !string.IsNullOrEmpty(x)).Distinct(StringComparer.OrdinalIgnoreCase))
				await Load(path);
			if (data is null || data.Makeup.Active)
				foreach (var skin in data is null ? Skins : Skins.Where(x => x.ResourcePath == data.SkinPath))
					await HumanMakeupAssets.EnsureLoadedAsync(skin.Id);
			if (data is null || data.CustomEyeColor)
				await EyeColorPresentation.EnsureLoadedAsync();
			if (data is not null)
				await HumanMarkingModels.EnsureLoadedAsync(data);
			if (data?.BaseShape is not null)
			{
				var model = await Model.LoadAsync(UnifiedHuman.ModelPath);
				if (model is null || model.IsError)
					throw new InvalidOperationException("The unified human model is unavailable.");
				await UnifiedNeckAssets.LoadHeadAsync(data.SkinPath);
			}

			Error = null;
		}
		catch (Exception e)
		{
			Error = e.Message;
			throw;
		}

		static Task Load(string path)
		{
			if (resourceLoads.TryGetValue(path, out var pending) && pending is not null && !pending.IsFaulted && !pending.IsCanceled)
				return pending;
			return resourceLoads[path] = LoadResource(path);
		}

		static async Task LoadResource(string path)
		{
			Clothing clothing = null;
			for (var attempt = 0; attempt < 3; attempt++)
			{
				clothing = await ClothingResources.Load(path);
				if (clothing is not null && !clothing.IsError)
					break;
				if (attempt < 2)
					await Task.Delay(150);
			}

			if (clothing is null || clothing.IsError)
				throw new InvalidOperationException("A native appearance resource is unavailable: " + path);
			foreach (var modelPath in new[]
			{
				clothing.HumanSkinModel,
				clothing.HumanAltModel,
				clothing.HumanAltFemaleModel
			}.Where(x => !string.IsNullOrEmpty(x)).Distinct(StringComparer.OrdinalIgnoreCase))
			{
				var model = await Model.LoadAsync(modelPath);
				if (model is null || model.IsError)
					throw new InvalidOperationException("A native appearance model is unavailable: " + modelPath);
			}

			Revision++;
		}
	}
}
vendetta.character_editor / CharacterKit/HumanMarkingModels.cs
Game game
using System;
using System.Threading.Tasks;

namespace CharacterEditor;
/// <summary>Native human derivatives differ only in arm polygon material assignments.
/// The source Clothing choice stays authoritative; no replacement rig or mesh is sculpted here.</summary>
public static class HumanMarkingModels
{
	const string ArmMaterials = "materials/makeup/arms/";
	public static bool NeedsArmRegions(CharacterMakeupData makeup) => makeup?.Markings?.Any(x => x.Style != "none" && x.Region is "left-arm" or "right-arm" or "chest" or "back") == true;
	public static string ModelPath(string native, CharacterMakeupData makeup) => NeedsArmRegions(makeup) ? native switch
	{
		"models/citizen_human/citizen_human_male.vmdl" => "models/makeup/human_male_arm_regions.vmdl",
		"models/citizen_human/citizen_human_female.vmdl" => "models/makeup/human_female_arm_regions.vmdl",
		_ => native
	} : native;
	public static async Task EnsureLoadedAsync(CharacterAppearanceData data)
	{
		if (!NeedsArmRegions(data?.Makeup))
			return;
		var skin = ResourceLibrary.Get<Clothing>(data.SkinPath);
		var path = ModelPath(skin.HumanSkinModel, data.Makeup);
		var model = await Model.LoadAsync(path);
		if (model is null || model.IsError)
			throw new InvalidOperationException("The independently marked human arms are unavailable.");
	}

	public static int ArmSide(Material material) => !material.Name.StartsWith(ArmMaterials, StringComparison.OrdinalIgnoreCase) ? 0 : material.Name.Contains("_left_", StringComparison.OrdinalIgnoreCase) ? 1 : -1;
	public static Material NativeMaterial(Material slot)
	{
		if (ArmSide(slot) == 0)
			return slot;
		var female = slot.Name.Contains("/female_", StringComparison.OrdinalIgnoreCase);
		var light = slot.Name.EndsWith("_light.vmat", StringComparison.OrdinalIgnoreCase);
		var name = female ? (light ? "female_body_mode_light" : "female_body_mode_dark") : (light ? "body_male_light" : "body_mode_dark");
		return Material.Load($"models/citizen_human/bodies/{(female ? "female" : "male")}/{name}.vmat");
	}
}
vendetta.character_editor / Studio/StudioEscapeMenu.razor
Game game
@using System
@using Sandbox
@using Sandbox.UI
@namespace CharacterEditor
@inherits Panel

<root>
@if(IsOpen)
{
 <div class="menu-shade"><div class="menu-card">
 @if(IsConfirming)
 {
  <h2>Discard unsaved edits?</h2><p>Your saved characters will stay as they are.</p>
  <button onclick=@CancelNavigation>Keep editing</button>
  <button class="primary" onclick=@ConfirmNavigation>Discard edits and continue</button>
 }
 else
 {
  <div class="menu-title">Character Editor</div>
  <div class="menu-tabs"><button class=@(!Experimental?"chosen":"") onclick=@(()=>Experimental=false)>Menu</button>@if(EnableExperiments){<button class=@(Experimental?"chosen":"") onclick=@(()=>Experimental=true)>Experimental</button>}</div>
  @if(Experimental && EnableExperiments)
  {
   <h2>Experiments</h2><p>Development tools and research. These do not create saved characters.</p>
   <button onclick=@(()=>RequestNavigation(OpenLab))>Face lab</button>
  }
  else
  {
   <button class="primary" onclick=@Close>Resume</button>
   <button onclick=@(()=>RequestNavigation(MainMenu))>Main menu</button>
   <button onclick=@(()=>RequestNavigation(()=>Game.Close()))>Quit to s&amp;box</button>
  }
  <div class="menu-hint">Escape to return</div>
 }
 </div></div>
}
</root>

@code {
	public Action MainMenu { get; set; }
	public Action OpenLab { get; set; }
	public Func<bool> HasUnsavedEdits { get; set; }
	public bool EnableExperiments { get; set; }
	public bool IsOpen { get; private set; }
	public bool IsConfirming => pending is not null;

	bool Experimental;
	Action pending;
	public void Toggle()
	{
		if (IsConfirming)
			CancelNavigation();
		else if (IsOpen)
			Close();
		else
		{
			IsOpen = true;
			Experimental = false;
		}
	}

	public void Close()
	{
		IsOpen = false;
		Experimental = false;
		pending = null;
	}

	public void RequestNavigation(Action action)
	{
		if (action is null)
			return;
		if (HasUnsavedEdits?.Invoke() == true)
			pending = action;
		else
		{
			Close();
			action();
		}
	}

	public void CancelNavigation()
	{
		pending = null;
		Close();
	}

	public void ConfirmNavigation()
	{
		var action = pending;
		Close();
		action?.Invoke();
	}

	protected override int BuildHash() => HashCode.Combine(IsOpen, Experimental, IsConfirming, EnableExperiments);
}
vendetta.character_editor / studio/studiopanel.razor.scss
Game game
StudioPanel {
 position: absolute; width: 100%; height: 100%; font-family: Inter; color: #f0eee6; pointer-events: none;
 button { pointer-events: all; cursor: pointer; padding: 11px 17px; border-radius: 6px; background-color: #ffffff0a; border: 1px solid #ffffff20; color: #e9ebe6; font-size: 14px; flex-shrink: 0; align-items: center; justify-content: center; gap: 10px; }
 button:hover { background-color: #ffffff20; border-color: #c5e4a7; }
 button.primary { background-color: #d6ebaf; border-color: #d6ebaf; color: #263425; font-weight: 600; }
 button.primary:hover { background-color: #eaf9cc; }
 button.chosen { color: #d6ebaf; border-color: #d6ebaf88; }
 h1, h2, p { margin: 0; flex-shrink: 0; }
 h1 { font-size: 48px; font-weight: 500; letter-spacing: -1.5px; }
 h2 { font-size: 22px; font-weight: 500; }
 p { font-size: 15px; line-height: 25px; color: #aeb8ab; white-space: normal; }
 .eyebrow { font-size: 11px; letter-spacing: 2px; color: #c6d9b2; margin-bottom: 13px; }
 .collection { position: absolute; width: 100%; height: 100%; background-color: #17221ff9; padding: 30px 54px 20px; flex-direction: column; pointer-events: all; }
 .topbar { justify-content: space-between; align-items: center; flex-shrink: 0; }
 .brand { gap: 13px; align-items: center; letter-spacing: 2px; font-size: 12px; font-weight: 600; }
 .brandmark { width: 37px; height: 37px; border-radius: 50%; border: 2px solid #d6ebaf; color: #d6ebaf; font-size: 25px; align-items: center; justify-content: center; }
 .actions { gap: 10px; align-items: center; flex-shrink: 0; }
 .intro { margin: 42px 0 30px; justify-content: space-between; align-items: center; flex-shrink: 0; }
 .intro > div { flex-direction: column; gap: 9px; }
 .intro .eyebrow { margin-bottom: 0; }
 .plazalink { width: 235px; height: 100px; background-color: #354b3e; justify-content: space-between; padding: 25px; font-size: 19px; border-color: #a5c08466; }
 .arrow { font-size: 36px; color: #d6ebaf; }
 .librarybar { justify-content: space-between; align-items: center; border-bottom: 1px solid #ffffff20; padding-bottom: 17px; flex-shrink: 0; }
 TextEntry.search { width: 240px; padding: 12px 15px; background-color: #0e1916; border: 1px solid #ffffff20; border-radius: 6px; color: #edf2e5; font-size: 14px; }
 .cards { flex-grow: 1; min-height: 0; overflow-y: auto; flex-wrap: wrap; align-content: flex-start; gap: 20px; padding: 22px 0; }
 .card { width: 220px; flex-shrink: 0; flex-direction: column; background-color: #25312c; border: 1px solid #ffffff18; border-radius: 9px; overflow: hidden; }
 button.portrait { width: 100%; height: 190px; padding: 0; border: 0; border-radius: 0; background-color: #435146; position: relative; }
 .portrait CharacterCreatorPreview { position: absolute; width: 100%; height: 100%; pointer-events: none; }
 .cardtitle { align-items: center; justify-content: space-between; padding: 10px 14px 4px; }
 .cardtitle h2 { font-size: 17px; max-width: 153px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; }
 button.favorite { padding: 0; border: 0; background-color: transparent; font-size: 26px; }
 .cardactions { gap: 5px; padding: 8px 12px 14px; }
 .cardactions button { font-size: 11px; padding: 7px 10px; }
 button.delete { color: #d5aaa1; }
 .empty { width: 100%; padding: 45px 0; flex-direction: column; align-items: center; gap: 17px; }
 .empty p { max-width: 450px; text-align: center; }
 .emptyicon { font-size: 54px; color: #d6ebaf; }
 .bottomline { justify-content: space-between; color: #809487; font-size: 11px; padding-top: 14px; border-top: 1px solid #ffffff18; flex-shrink: 0; }
 .plazahead { position: absolute; left: 38px; right: 38px; top: 30px; align-items: flex-start; justify-content: space-between; text-shadow: 0 2px 4px #000a; }
 .plazahead > div { flex-direction: column; gap: 5px; }
 .plazahead h1 { font-size: 34px; }
 .plazahead button { background-color: #17221fee; }
 .plazahead p { color: #edf2e5; }
 .residents { position: absolute; left: 30px; top: 190px; width: 245px; max-height: 65%; overflow-y: auto; flex-direction: column; padding: 20px; background-color: #17221fe8; border: 1px solid #ffffff25; border-radius: 8px; pointer-events: all; gap: 8px; }
 .resident { gap: 6px; flex-shrink: 0; }
 .resident button { font-size: 12px; padding: 8px; }
 .resident button:first-child { flex-grow: 1; justify-content: flex-start; }
 .plazatools { position: absolute; bottom: 25px; right: 30px; gap: 6px; padding: 10px; background-color: #17221fe8; border-radius: 8px; }
 .plazatools button { font-size: 12px; padding: 10px; }
 .shade { position: absolute; width: 100%; height: 100%; background-color: #07140eea; align-items: center; justify-content: center; pointer-events: all; z-index: 50; }
 .dialog { width: 490px; padding: 35px; background-color: #25352b; border: 1px solid #acc79666; border-radius: 12px; flex-direction: column; gap: 22px; }
 .dialog .actions { justify-content: flex-end; }
 .transfer { width: 700px; }
 TextEntry.json { height: 260px; padding: 15px; background-color: #121f18; color: #d6ebaf; font-size: 12px; overflow-y: auto; white-space: pre-wrap; }
 .toast { position: absolute; bottom: 65px; left: 35%; max-width: 60%; padding: 14px 22px; background-color: #d6ebaf; color: #263425; border-radius: 8px; pointer-events: all; z-index: 100; font-size: 14px; }
 .warning { position: absolute; bottom: 110px; left: 30px; max-width: 70%; background-color: #633f24; padding: 15px; pointer-events: all; z-index: 101; }
 button.savecopy { position: absolute; right: 250px; bottom: 27px; z-index: 6; background-color: #25352b; }
}
vendetta.character_editor / Assembly.cs
Game game
global using Sandbox;
global using System.Collections.Generic;
global using System.Linq;
vendetta.character_editor / CharacterKit/CharacterBaseShape.cs
Game game
using System;

namespace CharacterEditor;
/// <summary>Version 2 identity shape, independent of skin. Weights are in human01..human07 order.</summary>
public sealed class CharacterBaseShape
{
	public float[] HeadWeights { get; set; } = new[]
	{
		0f,
		0f,
		1f,
		0f,
		0f,
		0f,
		0f
	};
	public List<PhotoFace> PhotoFaces { get; set; } = new();
	public Dictionary<string, FaceRegionBlend> Regions { get; set; } = new();

	[System.Text.Json.Serialization.JsonIgnore]
	public float TotalInfluence => HeadWeights.Sum() + (PhotoFaces?.Sum(x => x.Influence) ?? 0);

	public float NativeShare(int i) => TotalInfluence > 0 ? HeadWeights[i] / TotalInfluence : (i == 2 ? 1 : 0);
	public float PhotoShare(PhotoFace face) => TotalInfluence > 0 ? face.Influence / TotalInfluence : 0;
	public float MaleBody { get; set; } = 1;
	public string ClothingVariant { get; set; }
	public float BreastSize { get; set; } = .5f;
	public float BulgeSize { get; set; } = .5f;

	[System.Text.Json.Serialization.JsonIgnore]
	public bool Valid => HeadWeights?.Length == 7 && HeadWeights.All(Unit) && PhotoFaces is not null && PhotoFaces.Count <= 4 && PhotoFaces.All(x => x?.Valid == true) && PhotoFaces.Select(x => x.Id).Distinct().Count() == PhotoFaces.Count && Regions is not null && Regions.Count <= FaceRegions.Ids.Length && Regions.All(x => FaceRegions.Ids.Contains(x.Key) && x.Value?.Valid(PhotoFaces) == true) && Unit(MaleBody) && Unit(BreastSize) && Unit(BulgeSize) && ClothingVariant is null or "male" or "female";

	static bool Unit(float x) => float.IsFinite(x) && x >= 0 && x <= 1;
	public float[] Normalized()
	{
		if (!Valid)
			return new[]
			{
				0f,
				0f,
				1f,
				0f,
				0f,
				0f,
				0f
			};
		float sum = TotalInfluence;
		return sum > 0 ? HeadWeights.Select((x, i) => (x + PhotoFaces.Sum(f => f.Influence * f.HeadWeights[i])) / sum).ToArray() : new[]
		{
			0f,
			0f,
			1f,
			0f,
			0f,
			0f,
			0f
		};
	}

	public CharacterBaseShape DeepCopy() => new()
	{
		HeadWeights = HeadWeights?.ToArray(),
		PhotoFaces = PhotoFaces?.Select(x => x?.DeepCopy()).ToList(),
		Regions = Regions?.ToDictionary(x => x.Key, x => x.Value.DeepCopy()),
		MaleBody = MaleBody,
		BreastSize = BreastSize,
		BulgeSize = BulgeSize,
		ClothingVariant = ClothingVariant
	};
}

public static class UnifiedHuman
{
	public const string ModelPath = "models/characterkit/unified_human.vmdl";
	public static void Enable(CharacterAppearanceData data)
	{
		if (data.BaseShape is not null)
			return;
		bool female = AppearanceCatalog.IsFemale(data);
		var index = AppearanceCatalog.Skins.ToList().FindIndex(s => s.ResourcePath == data.SkinPath);
		data.BaseShape = new()
		{
			MaleBody = female ? 0 : 1,
			HeadWeights = new float[7],
			BreastSize = female ? data.BodyFeatureSize : .5f,
			BulgeSize = female ? .5f : data.BodyFeatureSize
		};
		data.BaseShape.HeadWeights[Math.Max(0, index)] = 1;
		data.Version = 2;
	}

	public static float MaleAmount(CharacterAppearanceData a) => a.BaseShape?.MaleBody ?? (AppearanceCatalog.IsFemale(a) ? 0 : 1);
	public static string PresentationSkin(CharacterAppearanceData a) => a.BaseShape is null ? a.SkinPath : (MaleAmount(a) >= .5f ? "models/citizen_clothes/body/human03.clothing" : "models/citizen_clothes/body/human04.clothing");
	public static void Apply(SkinnedModelRenderer body, CharacterAppearanceData a)
	{
		if (a.BaseShape is null)
			return;
		var weights = FaceRegions.ReferenceWeights(a.BaseShape);
		for (int i = 0; i < 7; i++)
			if (i != 2)
				body.Morphs.Set($"ce_human{i + 1:00}", weights[i]);
		FaceRegions.Apply(body, a.BaseShape, weights);
		body.Morphs.Set("ce_body_female", 1 - a.BaseShape.MaleBody);
	}

	public static string HeadMaterial(string skin)
	{
		var id = System.IO.Path.GetFileNameWithoutExtension(skin);
		var suffix = id switch
		{
			"human01" => "malehead01",
			"human02" => "femalehead01",
			"human03" => "malehead02",
			"human04" => "femalehead02",
			"human05" => "malehead03",
			"human06" => "malehead04",
			_ => "malehead05"
		};
		return $"models/citizen_human/heads/citizen_human_{suffix}/citizen_human_{suffix}.vmat";
	}

	public static Material SkinMaterial(Material slot, string skin, bool unifiedBody = true)
	{
		var original = HumanMarkingModels.NativeMaterial(slot);
		if (original.Name.Contains("/heads/") && original.ShaderName == "shaders/skin.shader" && !original.Name.EndsWith("/mouth.vmat"))
			return Material.Load(HeadMaterial(skin));
		if (original.Name.Contains("/bodies/") && original.ShaderName == "shaders/skin.shader")
		{
			// The unified surface uses one body material family. Female chest normal maps
			// encode breast contours that become distorted on the flattened male endpoint.
			var id = System.IO.Path.GetFileNameWithoutExtension(skin);
			bool female = !unifiedBody && (id is "human02" or "human04"), dark = id is "human01" or "human02" or "human06";
			return Material.Load($"models/citizen_human/bodies/{(female ? "female" : "male")}/{(female ? (dark ? "female_body_mode_dark" : "female_body_mode_light") : (dark ? "body_mode_dark" : "body_male_light"))}.vmat");
		}

		return original;
	}

	public static Color BodyToneTint(Material material, string skin)
	{
		if (!material.Name.Contains("/bodies/"))
			return Color.White;
		return BodyToneTint(skin);
	}

	public static Color BodyToneTint(string skin)
	{
		// Albedo ratios of the native female/male materials sampled just inside
		// the shared neck boundary. Keep the female faces' authored complexion.
		return System.IO.Path.GetFileNameWithoutExtension(skin) switch
		{
			"human02" => new Color(1.0192f, .9194f, .8924f),
			"human04" => new Color(1.0856f, 1.0137f, .8969f),
			_ => Color.White
		};
	}
}
vendetta.character_editor / CharacterKit/CharacterMarkingData.cs
Game game
using System;
using System.Text.Json.Serialization;

namespace CharacterEditor;
/// <summary>One independently editable ink layer. Placement is normalized within a curated surface region.</summary>
public sealed class CharacterMarkingData
{
	public string Style { get; set; } = "crescent";
	public string Region { get; set; } = "face";
	public float Horizontal { get; set; } = .5f;
	public float Vertical { get; set; } = .5f;
	public float Scale { get; set; } = .5f;
	public float Rotation { get; set; } = .5f;
	public float Spacing { get; set; } = .5f;
	public MakeupLayerData Ink { get; set; } = new()
	{
		Red = .1f,
		Green = .12f,
		Blue = .16f,
		Amount = .8f
	};

	[JsonIgnore]
	public bool Valid => MarkingCatalog.ValidPlacement(Style, Region) && Ink?.Valid == true && new[]
	{
		Horizontal,
		Vertical,
		Scale,
		Rotation,
		Spacing
	}.All(x => float.IsFinite(x) && x >= 0 && x <= 1) && (Style != "barbed-band" || (Horizontal == .5f && Rotation == .5f));

	public CharacterMarkingData DeepCopy() => new()
	{
		Style = Style,
		Region = Region,
		Horizontal = Horizontal,
		Vertical = Vertical,
		Scale = Scale,
		Rotation = Rotation,
		Spacing = Spacing,
		Ink = Ink?.DeepCopy()
	};
}
vendetta.character_editor / .obj/__compiler_extra.cs
Game game
global using static Sandbox.Internal.GlobalGameNamespace;
global using Microsoft.AspNetCore.Components;
global using Microsoft.AspNetCore.Components.Rendering;
[assembly: global::System.Reflection.AssemblyMetadata( "AddonTitle", "Character Editor" )]
[assembly: global::System.Reflection.AssemblyMetadata( "AddonIdent", "character_editor" )]
[assembly: global::System.Reflection.AssemblyMetadata( "OrgIdent", "vendetta" )]
[assembly: global::System.Reflection.AssemblyMetadata( "Ident", "vendetta.character_editor" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineVersion", "29" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineMinorVersion", "1" )]

[assembly: System.Runtime.Versioning.TargetFramework( ".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0" )]
[assembly: global::System.Reflection.AssemblyMetadata( "CompileTime", "2026-10-05T23:16:04.3069273Z" )]
[assembly: global::System.Reflection.AssemblyVersion("0.0.382.0")]
[assembly: global::System.Reflection.AssemblyFileVersion("0.0.382.0")]
vendetta.character_editor / CharacterKit/AutomaticWardrobeFit.cs
Game game
using System;
using System.IO;
using System.Text;
using System.Runtime.InteropServices;

namespace CharacterEditor;
/// <summary>Fits loaded human clothes against shared body surfaces, without garment authoring files.</summary>
public static class AutomaticWardrobeFit
{
	const string ReferencePath = "models/characterkit/wardrobe-reference.bin";
	public const string Morph = "ce_body_female";
	// Cache by resource identity and authored cut, never by the current slider value.
	static readonly Dictionary<(Model, bool, bool), Model> fittedModels = new();
	static readonly Dictionary<(Model, bool, bool), string> fittingFailures = new();
	static Surface originalMale, unifiedMale, unifiedFemale;
	static Surface headSurface;
	static Model replacementNeck;
	public static int CachedModels => fittedModels.Count;

	public static void ClearCache()
	{
		fittedModels.Clear();
		fittingFailures.Clear();
		replacementNeck = null;
		headSurface = null;
	}

	/// <summary>Keep the lower neck when clothing replaces the head bodygroup.</summary>
	public static Model ReplacementNeck()
	{
		if (replacementNeck is not null) return replacementNeck;
		References();
		return replacementNeck = Build(Model.Load(UnifiedHuman.ModelPath), false, neckOnly: true);
	}

	public static bool IsFitted(Model model) => model?.Name.StartsWith("automatic-human-fit:", StringComparison.Ordinal) == true;
	public static Model Fit(Model source, bool female, bool fitFace = true)
	{
		if (source is null || source.IsError || IsFitted(source) || source.BoneCount == 0 || source.Bounds.Mins.z > 60)
			return source;
		if (fittedModels.TryGetValue((source, female, fitFace), out var ready))
			return ready;
		if (fittingFailures.TryGetValue((source, female, fitFace), out var problem))
			throw new InvalidOperationException(problem);
		try
		{
			References();
			var fitted = Build(source, female, fitFace: fitFace);
			fittedModels[(source, female, fitFace)] = fitted;
			return fitted;
		}
		catch (Exception error)
		{
			var message = "Cannot automatically fit " + source.Name + ": " + error.Message;
			fittingFailures[(source, female, fitFace)] = message;
			throw new InvalidOperationException(message, error);
		}
	}

	static void References()
	{
		if (unifiedMale is not null)
			return;
		using var stream = new MemoryStream(FileSystem.Mounted.ReadAllBytes(ReferencePath).ToArray());
		using var r = new BinaryReader(stream);
		if (r.ReadInt32() != 1)
			throw new InvalidOperationException("Unsupported human fitting reference.");
		originalMale = ReadSurface(r);
		unifiedMale = ReadSurface(r);
		unifiedFemale = new(unifiedMale.Female, unifiedMale.Female, unifiedMale.Triangles, unifiedMale.Names, unifiedMale.Weights);
	}

	static Surface HeadSurface()
	{
		if (headSurface is not null) return headSurface;
		var source = Model.Load(UnifiedHuman.ModelPath);
		var buffers = ReadBuffers(source);
		var points = new List<Vector3>();
		var triangles = new List<int>();
		for (int mi = 0; mi < buffers.Count; mi++)
		{
			var info = source.MeshInfo.Meshes[mi];
			if (!info.Name.StartsWith("head", StringComparison.OrdinalIgnoreCase)) continue;
			var buffer = buffers[mi];
			int callIndex = 0;
			foreach (var call in info.DrawCalls)
			{
				int vb = buffer.Vertices.Length == 1 ? 0 : callIndex;
				int ib = buffer.Indices.Length == 1 ? 0 : callIndex;
				var vertices = Vertices(buffer.Vertices[vb], null, buffer.Bones.Length);
				var indices = Indices(buffer.Indices[ib]);
				int offset = points.Count;
				points.AddRange(vertices.Select(v => v.Position));
				triangles.AddRange(indices.Skip(call.StartIndex).Take(call.Indices).Select(i => i + call.BaseVertex + offset));
				callIndex++;
			}
		}
		var p = points.ToArray();
		return headSurface = new(p, p, triangles.ToArray(), Array.Empty<string>(), Array.Empty<(int, float)[]>());
	}

	static Vector3 V(BinaryReader r) => new(r.ReadSingle(), r.ReadSingle(), r.ReadSingle());
	static Surface ReadSurface(BinaryReader r)
	{
		var names = new string[r.ReadInt32()];
		for (int i = 0; i < names.Length; i++)
			names[i] = Encoding.UTF8.GetString(r.ReadBytes(r.ReadInt32()));
		var count = r.ReadInt32();
		var triangles = new int[r.ReadInt32() * 3];
		var p = new Vector3[count];
		var f = new Vector3[count];
		var weights = new (int, float)[count][];
		for (int i = 0; i < count; i++)
		{
			p[i] = V(r);
			f[i] = V(r);
			weights[i] = new (int, float)[r.ReadInt32()];
			for (int j = 0; j < weights[i].Length; j++)
				weights[i][j] = (r.ReadInt32(), r.ReadSingle());
		}

		for (int i = 0; i < triangles.Length; i++)
			triangles[i] = r.ReadInt32();
		return new(p, f, triangles, names, weights);
	}

	[StructLayout(LayoutKind.Sequential)]
	struct SkinVertex
	{
		[VertexLayout.Position]
		public Vector3 Position;
		[VertexLayout.Normal]
		public Vector3 Normal;
		[VertexLayout.Tangent]
		public Vector4 Tangent;
		[VertexLayout.TexCoord(0)]
		public Vector4 UV;
		[VertexLayout.TexCoord(1)]
		public Vector4 UV2;
		[VertexLayout.Color]
		public Color32 Color;
		[VertexLayout.BlendIndices]
		public Color32 Indices;
		[VertexLayout.BlendWeight]
		public Color32 Weights;
	}

	readonly record struct Attribute(string Name, int Index, int Format, int Offset);
	sealed record Buffer(int Count, int Stride, Attribute[] Attributes, byte[] Data);
	sealed record Buffers(Buffer[] Vertices, Buffer[] Indices, string[] Bones);
	// SaveToVmdl emits unpacked MBUF data, even when the installed resource uses
	// compressed buffers. Reading that public serialization retains authored weights.
	static List<Buffers> ReadBuffers(Model source)
	{
		var data = source.SaveToVmdl();
		using var r = new BinaryReader(new MemoryStream(data));
		r.BaseStream.Position = 8;
		var table = 8 + r.ReadInt32();
		var blocks = r.ReadInt32();
		var result = new List<Buffers>();
		for (int i = 0; i < blocks; i++)
		{
			r.BaseStream.Position = table + i * 12;
			var tag = Encoding.ASCII.GetString(r.ReadBytes(4));
			var anchor = (int)r.BaseStream.Position;
			var offset = r.ReadInt32();
			var size = r.ReadInt32();
			if (tag == "MDAT")
			{
				r.BaseStream.Position = anchor + offset;
				var bones = SerializedMeshBones.Read(r.ReadBytes(size));
				result[^1] = result[^1] with
				{
					Bones = bones
				};
				continue;
			}

			if (tag != "MBUF")
				continue;
			var start = anchor + offset;
			r.BaseStream.Position = start;
			var vo = r.ReadInt32();
			var vc = r.ReadInt32();
			var io = r.ReadInt32();
			var ic = r.ReadInt32();
			result.Add(new(Enumerable.Range(0, vc).Select(j => ReadBuffer(r, start + vo + j * 24)).ToArray(), Enumerable.Range(0, ic).Select(j => ReadBuffer(r, start + 8 + io + j * 24)).ToArray(), null));
		}

		if (result.Count != source.MeshCount)
			throw new InvalidOperationException($"{source.Name}: missing serialized mesh buffers.");
		return result;
	}

	static Buffer ReadBuffer(BinaryReader r, int position)
	{
		r.BaseStream.Position = position;
		var count = r.ReadInt32();
		var stride = r.ReadInt32() & 0x3ffffff;
		var ap = (int)r.BaseStream.Position;
		var ao = r.ReadInt32();
		var ac = r.ReadInt32();
		var dp = (int)r.BaseStream.Position;
		var offset = r.ReadInt32();
		var bytes = r.ReadInt32();
		if (count < 0 || count > 2000000 || stride < 1 || stride > 1024 || bytes != count * stride)
			throw new InvalidOperationException("Unexpected serialized human clothing buffer.");
		var attributes = new Attribute[ac];
		for (int i = 0; i < ac; i++)
		{
			r.BaseStream.Position = ap + ao + i * 56;
			var name = Encoding.ASCII.GetString(r.ReadBytes(32)).TrimEnd('\0');
			attributes[i] = new(name, r.ReadInt32(), r.ReadInt32(), r.ReadInt32());
		}

		r.BaseStream.Position = dp + offset;
		return new(count, stride, attributes, r.ReadBytes(bytes));
	}

	static float F(byte[] b, int p) => BitConverter.ToSingle(b, p);
	static ushort U16(byte[] b, int p) => BitConverter.ToUInt16(b, p);
	static uint U32(byte[] b, int p) => BitConverter.ToUInt32(b, p);
	static Color32 Bytes(byte[] b, int p) => new(b[p], b[p + 1], b[p + 2], b[p + 3]);
	static Vector4 Values(byte[] b, int p, int format)
	{
		float HalfAt(int i) => (float)BitConverter.UInt16BitsToHalf(U16(b, p + i * 2));
		return format switch
		{
			2 => new(F(b, p), F(b, p + 4), F(b, p + 8), F(b, p + 12)),
			6 => new(F(b, p), F(b, p + 4), F(b, p + 8), 0),
			16 => new(F(b, p), F(b, p + 4), 0, 0),
			10 => new(HalfAt(0), HalfAt(1), HalfAt(2), HalfAt(3)),
			34 => new(HalfAt(0), HalfAt(1), 0, 0),
			28 => new(b[p] / 255f, b[p + 1] / 255f, b[p + 2] / 255f, b[p + 3] / 255f),
			_ => throw new InvalidOperationException("Unsupported clothing vertex format " + format)};
	}

	static SkinVertex[] Vertices(Buffer b, Vertex[] native, int boneCount)
	{
		var vertices = new SkinVertex[b.Count];
		for (int i = 0; i < b.Count; i++)
		{
			var v = new SkinVertex
			{
				Color = new(255, 255, 255, 255),
				Weights = new(255, 0, 0, 0)
			};
			foreach (var a in b.Attributes)
			{
				var p = i * b.Stride + a.Offset;
				switch (a.Name)
				{
					case "POSITION":
						var xyz = Values(b.Data, p, a.Format);
						v.Position = new(xyz.x, xyz.y, xyz.z);
						break;
					case "TEXCOORD":
						if (a.Index == 0)
							v.UV = Values(b.Data, p, a.Format);
						else if (a.Index == 1)
							v.UV2 = Values(b.Data, p, a.Format);
						break;
					case "BLENDINDICES":
						if (a.Format == 30)
							v.Indices = Bytes(b.Data, p);
						else if (a.Format == 12)
						{
							var ids = Enumerable.Range(0, 4).Select(j => (int)U16(b.Data, p + j * 2)).ToArray();
							if (ids.Any(j => j >= boneCount || j > 255))
								throw new InvalidOperationException("Clothing bone index exceeds fitting layout.");
							v.Indices = new((byte)ids[0], (byte)ids[1], (byte)ids[2], (byte)ids[3]);
						}
						else
							throw new InvalidOperationException("Unsupported clothing bone-index format " + a.Format);
						break;
					case "BLENDWEIGHT":
						if (a.Format != 28)
							throw new InvalidOperationException("Unsupported clothing weight format " + a.Format);
						v.Weights = Bytes(b.Data, p);
						break;
					case "COLOR":
						if (a.Format == 28)
							v.Color = Bytes(b.Data, p);
						break;
					case "NORMAL":
						if (a.Format == 6)
						{
							var n = Values(b.Data, p, a.Format);
							v.Normal = new(n.x, n.y, n.z);
						}

						break;
					case "TANGENT":
						v.Tangent = Values(b.Data, p, a.Format);
						break;
				}
			}

			if (v.Normal.LengthSquared < .01f && native is not null && native.Length == b.Count && (native[i].Position - v.Position).Length < .001f)
			{
				v.Normal = native[i].Normal;
				v.Tangent = native[i].Tangent;
			}

			if (new[]
			{
				v.Indices.r,
				v.Indices.g,
				v.Indices.b,
				v.Indices.a
			}.Any(j => j >= boneCount))
				throw new InvalidOperationException("Serialized garment bone map does not match its skeleton.");
			vertices[i] = v;
		}

		return vertices;
	}

	static int[] Indices(Buffer b) => Enumerable.Range(0, b.Count).Select(i => b.Stride == 2 ? (int)U16(b.Data, i * 2) : checked((int)U32(b.Data, i * 4))).ToArray();
	static Vector3[] Normals(Vector3[] p, int[] indices)
	{
		var n = new Vector3[p.Length];
		for (int i = 0; i + 2 < indices.Length; i += 3)
		{
			var a = indices[i];
			var b = indices[i + 1];
			var c = indices[i + 2];
			var cross = Vector3.Cross(p[b] - p[a], p[c] - p[a]);
			n[a] += cross;
			n[b] += cross;
			n[c] += cross;
		}

		return n.Select(v => v.Normal).ToArray();
	}

	static Vector4[] Tangents(SkinVertex[] vertices, Vector3[] p, Vector3[] normals, int[] indices)
	{
		var t = new Vector3[p.Length];
		var b = new Vector3[p.Length];
		for (int i = 0; i + 2 < indices.Length; i += 3)
		{
			int a = indices[i], c = indices[i + 1], d = indices[i + 2];
			var e = p[c] - p[a];
			var f = p[d] - p[a];
			var uv = vertices[c].UV - vertices[a].UV;
			var uw = vertices[d].UV - vertices[a].UV;
			float determinant = uv.x * uw.y - uv.y * uw.x;
			if (MathF.Abs(determinant) < 1e-8f)
				continue;
			var tangent = (e * uw.y - f * uv.y) / determinant;
			var bitangent = (f * uv.x - e * uw.x) / determinant;
			foreach (int id in new[]
			{
				a,
				c,
				d
			}

			)
			{
				t[id] += tangent;
				b[id] += bitangent;
			}
		}

		return Enumerable.Range(0, p.Length).Select(i =>
		{
			var n = normals[i];
			var tangent = (t[i] - n * Vector3.Dot(n, t[i])).Normal;
			return new Vector4(tangent, Vector3.Dot(Vector3.Cross(n, tangent), b[i]) < 0 ? -1 : 1);
		}).ToArray();
	}

	static Model Build(Model source, bool female, bool neckOnly = false, bool fitFace = true)
	{
		var buffers = ReadBuffers(source);
		var builder = Model.Builder.WithName("automatic-human-fit:" + source.Name + ":" + (neckOnly ? "replacement-neck" : female ? "female" : "male"));
		for (int i = 0; i < source.BoneCount; i++)
		{
			var transform = source.GetBoneTransform(i);
			var parent = source.GetBoneParent(i);
			builder.AddBone(source.GetBoneName(i), transform.Position, transform.Rotation, parent < 0 ? null : source.GetBoneName(parent));
		}

		var native = source.GetVertices();
		var baseMaterials = (source.MaterialGroupCount > 0 ? source.GetMaterials(0) : source.Materials).ToArray();
		var meshMaterials = new List<Material>();
		var work = new List<Action>();
		var clothMale = new List<Vector3>();
		var clothFemale = new List<Vector3>();
		var clothIndices = new List<int>();
		var head = neckOnly || !fitFace ? null : HeadSurface();
		for (int mi = 0; mi < buffers.Count; mi++)
		{
			var info = source.MeshInfo.Meshes[mi];
			if (neckOnly && !info.Name.StartsWith("head", StringComparison.OrdinalIgnoreCase)) continue;
			var buffer = buffers[mi];
			var boneMap = buffer.Bones.Select(name => Enumerable.Range(0, source.BoneCount).First(i => source.GetBoneName(i) == name)).ToArray();
			int callIndex = 0;
			foreach (var call in info.DrawCalls)
			{
				// SaveToVmdl emits individual buffers in draw-call order when a mesh uses
				// separate streams (its metadata currently labels each stream as zero).
				int vb = buffer.Vertices.Length == 1 ? 0 : callIndex;
				int ib = buffer.Indices.Length == 1 ? 0 : callIndex;
				if (vb >= buffer.Vertices.Length || ib >= buffer.Indices.Length)
					throw new InvalidOperationException("Unexpected garment draw-call buffer mapping.");
				var verts = Vertices(buffer.Vertices[vb], mi == 0 && buffer.Vertices.Length == 1 ? native : null, buffer.Bones.Length);
				var indices = Indices(buffer.Indices[ib]);
				for (int i = 0; i < verts.Length; i++)
				{
					var ids = verts[i].Indices;
					verts[i].Indices = new((byte)boneMap[ids.r], (byte)boneMap[ids.g], (byte)boneMap[ids.b], (byte)boneMap[ids.a]);
				}

				callIndex++;
				var selectedIndices = indices.Skip(call.StartIndex).Take(call.Indices).Select(i => i + call.BaseVertex).ToArray();
				if (neckOnly)
					selectedIndices = Enumerable.Range(0, selectedIndices.Length / 3)
						.Where(t => Enumerable.Range(0, 3).All(j => verts[selectedIndices[t * 3 + j]].Position.z <= 60))
						.SelectMany(t => selectedIndices.Skip(t * 3).Take(3)).ToArray();
				if (selectedIndices.Length == 0) continue;
				var used = selectedIndices.Distinct().ToArray();
				var lookup = used.Select((id, i) => (id, i)).ToDictionary(x => x.id, x => x.i);
				var localIndices = selectedIndices.Select(i => lookup[i]).ToArray();
				var v = used.Select(i => verts[i]).ToArray();
				var original = v.Select(x => x.Position).ToArray();
				var male = new Vector3[v.Length];
				var target = new Vector3[v.Length];
				bool skin = neckOnly || call.Material?.Name.Contains("/bodies/", StringComparison.OrdinalIgnoreCase) == true;
				for (int i = 0; i < v.Length; i++)
				{
					var p = original[i];
					if (!skin && !neckOnly && fitFace && p.z >= 57 && p.z <= 73)
					{
						var hit = head.Nearest(p);
						if (hit.Distance < 1.2f && hit.Distance < originalMale.Nearest(p).Distance)
						{
							var normal = head.FaceNormal(hit.Triangle);
							float clearance = Vector3.Dot(p - hit.Point, normal);
							// Close face coverings need space for the difference in falloff
							// between the skin and fabric under inherited facial deformers.
							float margin = .60f * Math.Clamp((62.2f - p.z) / 1.0f, 0, 1);
							if (margin > 0) p += normal * Math.Clamp(margin - clearance, 0, .85f);
							male[i] = target[i] = p;
							continue;
						}
					}
					if (p.z > 60)
					{
						male[i] = p;
						target[i] = p;
						continue;
					}

					var from = (neckOnly ? unifiedMale : female ? unifiedFemale : originalMale).Nearest(p);
					var mapped = female || neckOnly ? from : unifiedMale.Nearest(from.Point);
					var m = unifiedMale.Interpolate(mapped.Triangle, mapped.Barycentric);
					var f = unifiedFemale.Interpolate(mapped.Triangle, mapped.Barycentric);
					// Preserve authored clearance, including compressed skin under clothing.
					var offset = p - from.Point;
					bool snap = skin && offset.Length < .12f;
					male[i] = m + (snap ? Vector3.Zero : offset);
					target[i] = f + (snap ? Vector3.Zero : offset);
				}

				int clothOffset = clothMale.Count;
				if (!skin)
				{
					clothIndices.AddRange(localIndices.Select(i => i + clothOffset));
					clothMale.AddRange(male);
					clothFemale.AddRange(target);
				}

				work.Add(() =>
				{
					if (!skin)
					{
						for (int i = 0; i < v.Length; i++)
						{
							male[i] = clothMale[clothOffset + i];
							target[i] = clothFemale[clothOffset + i];
						}
					}

					var originalNormals = Normals(original, localIndices);
					var maleNormals = Normals(male, localIndices);
					var femaleNormals = Normals(target, localIndices);
					var tangents = Tangents(v, male, maleNormals, localIndices);
					var morph = new MorphDelta[v.Length];
					for (int i = 0; i < v.Length; i++)
					{
						if (v[i].Normal.LengthSquared < .01f)
							v[i].Normal = originalNormals[i];
						v[i].Normal = (v[i].Normal + maleNormals[i] - originalNormals[i]).Normal;
						v[i].Tangent = tangents[i];
						v[i].Position = male[i];
						morph[i] = new(i, target[i] - male[i], femaleNormals[i] - maleNormals[i]);
					}

					var mesh = new Mesh(call.Material, (MeshPrimitiveType)call.PrimitiveType);
					mesh.UvDensity = call.UvDensity;
					mesh.CreateVertexBuffer(v.Length, v.ToList());
					mesh.CreateIndexBuffer(localIndices.Length, localIndices.ToList());
					mesh.AddMorph(Morph, morph);
					mesh.Bounds = new BBox(male.Concat(target).Aggregate((a, b) => Vector3.Min(a, b)), male.Concat(target).Aggregate((a, b) => Vector3.Max(a, b)));
					var lods = info.LodMask;
					if (source.MeshInfo.LodCount == 0)
						builder.AddMesh(mesh);
					else
						for (int lod = 0; lod < source.MeshInfo.LodCount; lod++)
							if ((lods & (1 << lod)) != 0)
								builder.AddMesh(mesh, lod);
					meshMaterials.Add(call.Material);
				});
			}
		}

		if (clothMale.Count > 0)
		{
			var m = clothMale.ToArray();
			var f = clothFemale.ToArray();
			var t = clothIndices.ToArray();
			ClearTriangles(unifiedMale, m, t);
			ClearTriangles(unifiedFemale, f, t);
			clothMale = m.ToList();
			clothFemale = f.ToList();
		}

		foreach (var build in work)
			build();
		for (int group = 0; group < source.MaterialGroupCount; group++)
		{
			var materials = source.GetMaterials(group).ToArray();
			var g = builder.AddMaterialGroup(source.GetMaterialGroupName(group));
			foreach (var material in meshMaterials)
			{
				var index = Array.IndexOf(baseMaterials, material);
				g.AddMaterial(index >= 0 && index < materials.Length ? materials[index] : material);
			}
		}

		for (int lod = 1; lod < source.MeshInfo.LodSwitchDistances.Length; lod++)
			builder.WithLodDistance(lod, source.MeshInfo.LodSwitchDistances[lod]);
		return builder.Create();
	}

	static void ClearTriangles(Surface body, Vector3[] points, int[] triangles)
	{
		// A smooth spatial clearance field moves folded/layered fabric together.
		// Local per-triangle pushes can pull an inner layer through its outer flap.
		var source = points.ToArray();
		var min = points.Aggregate((a, b) => Vector3.Min(a, b)) - Vector3.One * 1.2f;
		var max = points.Aggregate((a, b) => Vector3.Max(a, b)) + Vector3.One * 1.2f;
		var candidates = body.Points.Where(p => p.x >= min.x && p.x <= max.x && p.y >= min.y && p.y <= max.y && p.z >= min.z && p.z <= max.z).ToArray();
		var pushes = new List<(Vector3 Point, Vector3 Push)>();
		var fabric = new Surface(points, points, triangles, Array.Empty<string>(), Array.Empty<(int, float)[]>());
		foreach (var p in candidates)
		{
			var hit = fabric.Nearest(p);
			if (hit.Distance > 1.2f)
				continue;
			var w = hit.Barycentric;
			if (MathF.Min(w.x, MathF.Min(w.y, w.z)) < .015f)
				continue;
			var n = body.FaceNormal(body.Nearest(p).Triangle);
			if (MathF.Abs(Vector3.Dot(n, fabric.FaceNormal(hit.Triangle))) < .25f)
				continue;
			var penetration = Vector3.Dot(p - hit.Point, n) + .14f;
			if (penetration <= 0 || penetration > 1.2f)
				continue;
			pushes.Add((hit.Point, n * MathF.Min(.7f, penetration + .04f)));
		}

		// Spatial buckets bound the search and apply identical displacement to UV
		// duplicates, overlapping material sections, and neighbouring LOD vertices.
		const float radius = 2.5f;
		(int, int, int) Cell(Vector3 p) => ((int)MathF.Floor(p.x / radius), (int)MathF.Floor(p.y / radius), (int)MathF.Floor(p.z / radius));
		var buckets = pushes.GroupBy(p => Cell(p.Point)).ToDictionary(g => g.Key, g => g.ToArray());
		for (int i = 0; i < points.Length; i++)
		{
			var cell = Cell(source[i]);
			Vector3 best = default;
			for (int x = -1; x <= 1; x++)
				for (int y = -1; y <= 1; y++)
					for (int z = -1; z <= 1; z++)
					{
						if (!buckets.TryGetValue((cell.Item1 + x, cell.Item2 + y, cell.Item3 + z), out var values))
							continue;
						foreach (var p in values)
						{
							var d = (source[i] - p.Point).Length / radius;
							if (d >= 1)
								continue;
							var falloff = 1 - d * d * (3 - 2 * d);
							var push = p.Push * falloff;
							if (push.LengthSquared > best.LengthSquared)
								best = push;
						}
					}

			points[i] += best;
		}
	}

	readonly record struct Hit(int Triangle, Vector3 Barycentric, Vector3 Point, float Distance);
	sealed class Surface
	{
		public readonly Vector3[] Points, Female;
		public readonly int[] Triangles;
		public readonly string[] Names;
		public readonly (int, float)[][] Weights;
		readonly Node tree;
		sealed class Node
		{
			public Vector3 Min, Max;
			public Node Left, Right;
			public int[] Faces;
		}

		public Surface(Vector3[] p, Vector3[] f, int[] t, string[] names, (int, float)[][] weights)
		{
			Points = p;
			Female = f;
			Triangles = t;
			Names = names;
			Weights = weights;
			tree = BuildTree(Enumerable.Range(0, t.Length / 3).ToArray());
		}

		Vector3 A(int t, int corner) => Points[Triangles[t * 3 + corner]];
		Node BuildTree(int[] faces)
		{
			var p = faces.SelectMany(i => new[] { A(i, 0), A(i, 1), A(i, 2) }).ToArray();
			var node = new Node
			{
				Min = p.Aggregate((a, b) => Vector3.Min(a, b)),
				Max = p.Aggregate((a, b) => Vector3.Max(a, b))
			};
			if (faces.Length <= 12)
			{
				node.Faces = faces;
				return node;
			}

			var extent = node.Max - node.Min;
			int axis = extent.x > extent.y ? (extent.x > extent.z ? 0 : 2) : (extent.y > extent.z ? 1 : 2);
			float Coord(Vector3 v) => axis == 0 ? v.x : axis == 1 ? v.y : v.z;
			var sorted = faces.OrderBy(i => Coord(A(i, 0) + A(i, 1) + A(i, 2))).ToArray();
			int half = sorted.Length / 2;
			node.Left = BuildTree(sorted.Take(half).ToArray());
			node.Right = BuildTree(sorted.Skip(half).ToArray());
			return node;
		}

		public Vector3 Interpolate(int t, Vector3 w) => A(t, 0) * w.x + A(t, 1) * w.y + A(t, 2) * w.z;
		public Vector3 FaceNormal(int t) => Vector3.Cross(A(t, 1) - A(t, 0), A(t, 2) - A(t, 0)).Normal;
		static float Distance(Node n, Vector3 p) => (p - Vector3.Max(n.Min, Vector3.Min(n.Max, p))).LengthSquared;
		public Hit Nearest(Vector3 p)
		{
			float best = float.MaxValue;
			int face = 0;
			Vector3 bary = default, point = default;
			void Search(Node node)
			{
				if (Distance(node, p) > best)
					return;
				if (node.Faces is null)
				{
					var a = node.Left;
					var b = node.Right;
					if (Distance(a, p) > Distance(b, p))
						(a, b) = (b, a);
					Search(a);
					Search(b);
					return;
				}

				foreach (var t in node.Faces)
				{
					var w = Closest(p, A(t, 0), A(t, 1), A(t, 2));
					var q = Interpolate(t, w);
					var distance = (p - q).LengthSquared;
					if (distance < best)
					{
						best = distance;
						face = t;
						bary = w;
						point = q;
					}
				}
			}

			Search(tree);
			return new(face, bary, point, MathF.Sqrt(best));
		}

		static Vector3 Closest(Vector3 p, Vector3 a, Vector3 b, Vector3 c)
		{
			var ab = b - a;
			var ac = c - a;
			var ap = p - a;
			float d1 = Vector3.Dot(ab, ap), d2 = Vector3.Dot(ac, ap);
			if (d1 <= 0 && d2 <= 0)
				return new(1, 0, 0);
			var bp = p - b;
			float d3 = Vector3.Dot(ab, bp), d4 = Vector3.Dot(ac, bp);
			if (d3 >= 0 && d4 <= d3)
				return new(0, 1, 0);
			float vc = d1 * d4 - d3 * d2;
			if (vc <= 0 && d1 >= 0 && d3 <= 0)
			{
				var v = d1 / (d1 - d3);
				return new(1 - v, v, 0);
			}

			var cp = p - c;
			float d5 = Vector3.Dot(ab, cp), d6 = Vector3.Dot(ac, cp);
			if (d6 >= 0 && d5 <= d6)
				return new(0, 0, 1);
			float vb = d5 * d2 - d1 * d6;
			if (vb <= 0 && d2 >= 0 && d6 <= 0)
			{
				var w = d2 / (d2 - d6);
				return new(1 - w, 0, w);
			}

			float va = d3 * d6 - d5 * d4;
			if (va <= 0 && d4 - d3 >= 0 && d5 - d6 >= 0)
			{
				var w = (d4 - d3) / (d4 - d3 + d5 - d6);
				return new(0, 1 - w, w);
			}

			float sum = va + vb + vc;
			if (MathF.Abs(sum) < 1e-12f)
				return new(1, 0, 0);
			return new(va / sum, vb / sum, vc / sum);
		}
	}
}// Runtime fitting integration.

vendetta.character_editor / CharacterKit/CharacterDeformation.cs
Game game
using System;

namespace CharacterEditor;
/// <summary>Stable native rest-space volumes; shape edits update coefficients without resetting merged skeletons.</summary>
public static class CharacterDeformation
{
	public const int VolumeCount = 64;
	readonly record struct Edit(string Name, Vector3 Center, float Radius, Vector3 MaskScale, Vector3 Scale, Vector3 Translation, float Pitch = 0, float Roll = 0);
	// Native orthographic front/profile captures locate the vermilion separately
	// from surrounding skin. Resource identity, not display name, selects anatomy.
	readonly record struct MouthAnatomy(float SurfaceX, float PhiltrumZ, float PhiltrumHeight, float ColumellaX, float ColumellaZ, float UpperZ, float UpperHeight, float LowerZ, float LowerHeight);
	static MouthAnatomy MouthFor(string skin) => skin?.ToLowerInvariant() switch
	{
		"models/citizen_clothes/body/human01.clothing" => new(5.8f, 62.35f, 0.28f, 5.85f, 62.82f, 61.88f, 0.26f, 61.38f, 0.21f),
		"models/citizen_clothes/body/human02.clothing" => new(5.5f, 62.3f, 0.22f, 5.7f, 62.65f, 61.82f, 0.25f, 61.27f, 0.23f),
		"models/citizen_clothes/body/human04.clothing" => new(5.5f, 62.17f, 0.21f, 5.75f, 62.58f, 61.76f, 0.21f, 61.30f, 0.23f),
		"models/citizen_clothes/body/human05.clothing" => new(5.7f, 62.26f, 0.23f, 5.85f, 62.67f, 61.79f, 0.20f, 61.43f, 0.16f),
		"models/citizen_clothes/body/human06.clothing" => new(5.75f, 62.35f, 0.28f, 5.8f, 62.82f, 61.87f, 0.26f, 61.39f, 0.22f),
		"models/citizen_clothes/body/human07.clothing" => new(5.7f, 62.26f, 0.23f, 5.75f, 62.72f, 61.80f, 0.20f, 61.42f, 0.21f),
		_ => new(5.8f, 62.35f, 0.28f, 5.85f, 62.82f, 61.80f, 0.22f, 61.43f, 0.16f)};
	// Rest-space side landmarks run down and forward from below the ear to the chin.
	// Pitch aligns the narrow masks with that slope rather than a horizontal neck band.
	readonly record struct JawAnatomy(float AngleZ, float MiddleZ, float FrontZ, float Width);
	static JawAnatomy JawFor(string skin) => skin?.ToLowerInvariant() switch
	{
		"models/citizen_clothes/body/human01.clothing" => new(61.65f, 61.1f, 60.4f, 1),
		"models/citizen_clothes/body/human02.clothing" => new(61.65f, 61.1f, 60.4f, 0.93f),
		"models/citizen_clothes/body/human04.clothing" => new(61.65f, 61.1f, 60.4f, 0.92f),
		"models/citizen_clothes/body/human05.clothing" or "models/citizen_clothes/body/human07.clothing" => new(61.65f, 61.2f, 60.4f, 0.97f),
		_ => new(61.65f, 61.1f, 60.4f, 1)};
	static Edit[] Edits(CharacterAppearanceData a)
	{
		a = PhotoFace.ApplyOffsets(a);
		var headWeights = a.BaseShape?.Normalized();
		var regionWeights = new Dictionary<string, float[]>();
		float Blend(Func<string, float> f) => headWeights is null ? f(a.SkinPath) : AppearanceCatalog.Skins.Select((s, i) => f(s.ResourcePath) * headWeights[i]).Sum();
		float BlendRegion(string region, Func<string, float> f)
		{
			if (a.BaseShape is null)
				return Blend(f);
			if (!regionWeights.TryGetValue(region, out var weights))
				regionWeights[region] = weights = FaceRegions.Weights(a.BaseShape, region);
			return AppearanceCatalog.Skins.Select((s, i) => f(s.ResourcePath) * weights[i]).Sum();
		}

		float Signed(float value) => (value - 0.5f) * 2;
		Vector3 Scale(Vector3 maximum, float value) => Vector3.One + (maximum - Vector3.One) * Signed(value);
		float Extended(float value, float maximum) => Signed(value) * (value > 0.5f ? maximum : 1);
		float Range(float value, float negative, float positive) => Signed(value) * (value < 0.5f ? negative : positive);
		Vector3 AmountScale(Vector3 maximum, float amount) => Vector3.One + (maximum - Vector3.One) * amount;
		// Preserve the neutral shape; map the new endpoints to the old 30% and 90%.
		var chest = Signed(a.ChestSize) * (a.ChestSize < 0.5f ? 0.4f : 0.8f);
		Vector3 Build(Vector3 soft, Vector3 athletic) => Vector3.One + ((a.Fitness < 0.5f ? soft : athletic) - Vector3.One) * MathF.Abs(Signed(a.Fitness));
		// A gentle resize range and broader mask keep the already-tapered rim from collapsing or hitting the finite boundary.
		var earScale = Scale(new(1.35f, 1.35f, 1.35f), a.EarSize);
		var earPivotCorrection = 0.65f / 1.05f * (earScale.y - 1);
		var maleAmount = UnifiedHuman.MaleAmount(a);
		var female = a.BaseShape is not null || AppearanceCatalog.IsFemale(a);
		var contour = a.BaseShape is null ? Extended(a.BodyFeatureSize, female ? 1.3f : 1) : Extended(a.BaseShape.BreastSize, 1.3f) * (1 - maleAmount);
		var butt = Extended(a.ButtSize, 1.3f);
		// Face 4 needs a lower, taller cap encompassing its nostril wing rather than the patch above it.
		var smallWings = BlendRegion("nose", s => string.Equals(s, "models/citizen_clothes/body/human04.clothing", StringComparison.OrdinalIgnoreCase) ? 1 : 0);
		Vector3 WingCenter(float side) => Vector3.Lerp(new(5.45f, side * .85f, 63.1f), new(5.5f, side * .6f, 62.82f), smallWings);
		var wingMask = Vector3.Lerp(new(.7f, .6f, .48f), new(.65f, .45f, .46f), smallWings);
		var mouth = new MouthAnatomy(BlendRegion("mouth", s => MouthFor(s).SurfaceX), BlendRegion("mouth", s => MouthFor(s).PhiltrumZ), BlendRegion("mouth", s => MouthFor(s).PhiltrumHeight), BlendRegion("nose", s => MouthFor(s).ColumellaX), BlendRegion("nose", s => MouthFor(s).ColumellaZ), BlendRegion("mouth", s => MouthFor(s).UpperZ), BlendRegion("mouth", s => MouthFor(s).UpperHeight), BlendRegion("mouth", s => MouthFor(s).LowerZ), BlendRegion("mouth", s => MouthFor(s).LowerHeight));
		var jaw = new JawAnatomy(BlendRegion("jaw", s => JawFor(s).AngleZ), BlendRegion("jaw", s => JawFor(s).MiddleZ), BlendRegion("jaw", s => JawFor(s).FrontZ), BlendRegion("jaw", s => JawFor(s).Width));
		var cheekbones = Extended(a.CheekboneWidth, 1.35f);
		var chinProjection = Range(a.ChinSize, 1.12f, 1.45f);
		var chinHeight = Range(a.ChinHeight, 1.8f, 1.2f);
		var mouthProjection = Extended(a.MouthProjection, 1.4f);
		var upperLip = Extended(a.UpperLipFullness, 1.4f);
		var lowerLip = Extended(a.LowerLipFullness, 1.4f);
		var brow = Range(a.BrowProminence, 1.12f, 1.4f);
		var philtrumLipClearance = mouth.UpperZ - mouth.LowerZ < 0.4f ? 0.04f : 0;
		// Raise only the upper mask boundary; retain the accepted lower lip reach.
		var philtrumUpperExtension = MathF.Min(0.18f, mouth.ColumellaZ - mouth.PhiltrumZ - 0.24f);
		var headScale = Scale(new(1.06f, 1.08f, 1.025f), a.HeadShape);
		// Rest-space underside landmarks differ from the chin tip and anterior
		// throat. Fit this cap to each head, then follow the preceding head scale.
		var submentalZ = BlendRegion("jaw", s => s?.ToLowerInvariant() switch
		{
			"models/citizen_clothes/body/human01.clothing" => 59.37f,
			"models/citizen_clothes/body/human02.clothing" => 59.92f,
			"models/citizen_clothes/body/human04.clothing" => 59.74f,
			"models/citizen_clothes/body/human05.clothing" => 59.43f,
			"models/citizen_clothes/body/human06.clothing" => 59.5f,
			"models/citizen_clothes/body/human07.clothing" => 59.59f,
			_ => 59.35f
		});
		var jawWidth = Extended(a.JawWidth, 2);
		Edit JawEdit(string name, Vector3 center, Vector3 mask, float translation, float pitch)
		{
			// The head operation precedes the jaw. Follow its scaled landmarks so a
			// smaller head cannot move the ear/neck into a mask fitted to neutral.
			center = new(center.x * headScale.x, center.y * headScale.y, 63 + (center.z - 63) * headScale.z);
			mask *= headScale;
			// The stronger positive contour needs a little more vertical clearance
			// from the neck on very small heads. Minimum and neutral masks stay exact.
			mask.z *= 1 - 0.08f * MathF.Max(0, 1 - 2 * a.HeadShape) * MathF.Max(0, Signed(a.JawWidth));
			return new(name, center, 1, mask, Vector3.One, new(0, translation * jawWidth, 0), pitch);
		}

		Vector3 EarCenter(float side) => new(0.15f * headScale.x, side * 3.65f * headScale.y, 63 + 1.05f * headScale.z);
		var earMask = new Vector3(1.15f * headScale.x, 0.5f * headScale.y, 1.6f * headScale.z);
		var result = new[]
		{
			new Edit("Neck size", new(-0.25f, 0, 58), 1, new(4.2f, 4.2f, 2.9f), Scale(new(1.18f, 1.4f, 1), a.NeckSize), Vector3.Zero),
			// Preserve the accepted old shoulder-height operation as its own control.
			new Edit("Left collarbone height", new(-1.1f, 4.2f, 55.3f), 1, new(3.8f, 4, 2.4f), Scale(new(1.12f, 1.04f, 1.3f), a.CollarboneHeight), new(0, 0, 0.1f * Signed(a.CollarboneHeight))),
			new Edit("Right collarbone height", new(-1.1f, -4.2f, 55.3f), 1, new(3.8f, 4, 2.4f), Scale(new(1.12f, 1.04f, 1.3f), a.CollarboneHeight), new(0, 0, 0.1f * Signed(a.CollarboneHeight))),
			// Upper trapezius lies behind the clavicle and slopes from neck to shoulder.
			new Edit("Left trapezius", new(-1.8f, 4.3f, 57.6f + .3f * maleAmount), 1, new(3, 4, 1.25f), Vector3.One, new(-0.18f * Signed(a.TrapeziusSize), 0, 0.28f * Signed(a.TrapeziusSize)), 0, -28),
			new Edit("Right trapezius", new(-1.8f, -4.3f, 57.6f + .3f * maleAmount), 1, new(3, 4, 1.25f), Vector3.One, new(-0.18f * Signed(a.TrapeziusSize), 0, 0.28f * Signed(a.TrapeziusSize)), 0, 28),
			new Edit("Waist size", new(0, 0, 37), 17, new(1, 1, 0.8f), Scale(new(1.15f, 1.15f, 1), a.WaistSize), Vector3.Zero),
			new Edit("Chest size", new(0, 0, 51.5f), 1, new(7, 11.5f, 7.5f), AmountScale(new(1.18f, 1.25f, 1), chest), Vector3.Zero),
			new Edit("Left chest shoulder", new(0, 8, 53), 1, new(5.5f, 5, 5.5f), Vector3.One, new(0, 0.18f * chest, 0)),
			new Edit("Right chest shoulder", new(0, -8, 53), 1, new(5.5f, 5, 5.5f), Vector3.One, new(0, -0.18f * chest, 0)),
			new Edit("Build midsection", new(0, 0, 36), 15, new(1, 1, 0.75f), Build(new(1.16f, 1.18f, 1), new(0.94f, 0.95f, 1)), Vector3.Zero),
			new Edit("Build chest", new(1, 0, 51), 1, new(7, 10.5f, 7), Build(new(1.035f, 1.06f, 1), new(1.2f, 1.14f, 1.025f)), Vector3.Zero),
			new Edit("Build left arm", new(0, 11.5f, 49), 1, new(5, 5, 7), Build(new(1.015f, 1.04f, 1.02f), new(1.45f, 1.22f, 1.2f)), Vector3.Zero),
			new Edit("Build right arm", new(0, -11.5f, 49), 1, new(5, 5, 7), Build(new(1.015f, 1.04f, 1.02f), new(1.45f, 1.22f, 1.2f)), Vector3.Zero),
			new Edit("Build left shoulder", new(-0.5f, 8, 53), 1, new(5, 4.5f, 5), Build(new(1.02f, 1.02f, 1), new(1.3f, 1.25f, 1.15f)), Vector3.Zero),
			new Edit("Build right shoulder", new(-0.5f, -8, 53), 1, new(5, 4.5f, 5), Build(new(1.02f, 1.02f, 1), new(1.3f, 1.25f, 1.15f)), Vector3.Zero),
			new Edit("Build left forearm", new(0.05f, 17.4f, 41.6f), 1, new(3.1f, 4.4f, 3.8f), Build(Vector3.One, new(1.22f, 1.1f, 1.06f)), Vector3.Zero),
			new Edit("Build right forearm", new(0.05f, -17.4f, 41.6f), 1, new(3.1f, 4.4f, 3.8f), Build(Vector3.One, new(1.22f, 1.1f, 1.06f)), Vector3.Zero),
			new Edit("Build left calf", new(-2, 6.25f, 13), 1, new(3.4f, 2.8f, 6.5f), Build(Vector3.One, new(1.22f, 1.2f, 1)), Vector3.Zero),
			new Edit("Build right calf", new(-2, -6.25f, 13), 1, new(3.4f, 2.8f, 6.5f), Build(Vector3.One, new(1.22f, 1.2f, 1)), Vector3.Zero),
			new Edit("Build left thigh", new(0, 4, 22), 11, new(0.8f, 0.7f, 1), Build(new(1.06f, 1.08f, 1), new(1.12f, 1.12f, 1)), Vector3.Zero),
			new Edit("Build right thigh", new(0, -4, 22), 11, new(0.8f, 0.7f, 1), Build(new(1.06f, 1.08f, 1), new(1.12f, 1.12f, 1)), Vector3.Zero),
			// These surfaces remain covered by the native underwear body groups.
			new Edit("Left body contour", female ? new(4, 3.1f, 49.8f) : new(4, 0.65f, 34), 1, female ? new(3.5f, 3, 3.5f) : new(2.7f, 2.5f, 3), AmountScale(female ? new(1.28f, 1.12f, 1.12f) : Vector3.One, contour), new((female ? 0.12f : 0.23f) * contour, 0, 0)),
			new Edit("Right body contour", female ? new(4, -3.1f, 49.8f) : new(4, -0.65f, 34), 1, female ? new(3.5f, 3, 3.5f) : new(2.7f, 2.5f, 3), AmountScale(female ? new(1.28f, 1.12f, 1.12f) : Vector3.One, contour), new((female ? 0.12f : 0.23f) * contour, 0, 0)),
			new Edit("Left butt size", new(-3.5f, 3, 33.5f), 1, new(4.5f, 3.8f, 5), AmountScale(new(1.22f, 1.08f, 1.05f), butt), new(-0.15f * butt, 0, 0)),
			new Edit("Right butt size", new(-3.5f, -3, 33.5f), 1, new(4.5f, 3.8f, 5), AmountScale(new(1.22f, 1.08f, 1.05f), butt), new(-0.15f * butt, 0, 0)),
			// Front-only abdominal cap: the back, chest and pelvis are outside the mask.
			new Edit("Belly size", new(8, 0, 41), 1, new(8, 9, 7.5f), Vector3.One, new(0.55f * a.BellySize, 0, 0)),
			// Length and width only: soles keep their original height and legs stay outside.
			new Edit("Left foot size", new(0, 7.8f, 1.8f), 1, new(9, 4, 4.2f), Scale(new(1.22f, 1.18f, 1), a.FootSize), Vector3.Zero),
			new Edit("Right foot size", new(0, -7.8f, 1.8f), 1, new(9, 4, 4.2f), Scale(new(1.22f, 1.18f, 1), a.FootSize), Vector3.Zero),
			new Edit("Head shape", new(0, 0, 63), 10, Vector3.One, Scale(new(1.06f, 1.08f, 1.025f), a.HeadShape), Vector3.Zero),
			// +X is forward on the native human rig. Translation changes profile depth
			// without radial inflation's boundary clamp or volume-preserving squash.
			// Tight ellipsoids end above the upper lip and in front of the throat.
			// Projection and width share each feature's mask, rather than a jaw/face sphere.
			new Edit("Nose projection", new(6.2f, 0, 63.65f), 1, new(1.55f, 1.35f, 1.15f), Vector3.One, new(0.32f * Signed(a.NoseSize), 0, 0)),
			new Edit("Nose width", new(6.2f, 0, 63.65f), 1, new(1.55f, 1.35f, 1.15f), Scale(new(1, 1.28f, 1), a.NoseWidth), Vector3.Zero),
			new Edit("Nose tip height", new(6.2f, 0, 63.65f), 1, new(1.55f, 1.35f, 1.15f), Vector3.One, new(0, 0, 0.3f * Signed(a.NoseHeight))),
			new Edit("Chin projection", new(5.1f, 0, 60.25f), 1, new(1.25f, 2.05f, 0.8f), Vector3.One, new(0.24f * chinProjection, 0, 0)),
			new Edit("Chin width", new(5.1f, 0, 60.25f), 1, new(1.25f, 2.05f, 0.8f), Scale(new(1, 1.18f, 1), a.ChinWidth), Vector3.Zero),
			new Edit("Chin height", new(5.1f, 0, 60), 1, new(1.5f, 2.05f, 1), Vector3.One, new(0, 0, 0.3f * chinHeight)),
			new Edit("Submental fullness", new(3.65f * headScale.x, 0, 63 + (submentalZ - 63) * headScale.z), 1, new(1.5f * headScale.x, 2.35f * headScale.y, 0.75f * headScale.z), Vector3.One, new(0, 0, -Range(a.SubmentalFullness, 0.25f, 0.35f))),
			// Lateral translation follows the mandibular rim without inflating the neck.
			// The posterior section climbs toward the ear; the front remains below the lips.
			JawEdit("Left jaw angle", new(1.9f, 2.7f * jaw.Width, jaw.AngleZ), new(1.1f, 1.1f, 0.6f), 0.085f, 42),
			JawEdit("Right jaw angle", new(1.9f, -2.7f * jaw.Width, jaw.AngleZ), new(1.1f, 1.1f, 0.6f), -0.085f, 42),
			JawEdit("Left jaw width", new(3, 2.25f * jaw.Width, jaw.MiddleZ), new(2.1f, 1.6f, 0.7f), 0.07f, 26),
			JawEdit("Right jaw width", new(3, -2.25f * jaw.Width, jaw.MiddleZ), new(2.1f, 1.6f, 0.7f), -0.07f, 26),
			JawEdit("Left jaw front", new(4.35f, 1.4f * jaw.Width, jaw.FrontZ), new(1.55f, 1.7f, 0.65f), 0.045f, 10),
			JawEdit("Right jaw front", new(4.35f, -1.4f * jaw.Width, jaw.FrontZ), new(1.55f, 1.7f, 0.65f), -0.045f, 10),
			new Edit("Left cheek fullness", new(4, 0.8f, 62), 1, new(3.6f, 5, 1.5f), Scale(new(1, 1.06f, 1), a.CheekFullness), new(0, 0.0096f * Signed(a.CheekFullness), 0)),
			new Edit("Right cheek fullness", new(4, -0.8f, 62), 1, new(3.6f, 5, 1.5f), Scale(new(1, 1.06f, 1), a.CheekFullness), new(0, -0.0096f * Signed(a.CheekFullness), 0)),
			// The alae (nostril wings) sit beside and below the tip; keep the lip outside.
			new Edit("Left nostril width", new(5.5f, 0.85f, 62.95f), 1, new(0.65f, 0.7f, 0.35f), Vector3.One, new(0, 0.2f * Signed(a.NostrilWidth), 0)),
			new Edit("Right nostril width", new(5.5f, -0.85f, 62.95f), 1, new(0.65f, 0.7f, 0.35f), Vector3.One, new(0, -0.2f * Signed(a.NostrilWidth), 0)),
			// Fullness changes the wing's rounded cap in depth/height, independent of spacing.
			new Edit("Left nostril-wing fullness", WingCenter(1), 1, wingMask, Scale(Vector3.Lerp(new(1.35f, 1.05f, 1.3f), new(1.28f, 1.05f, 1.24f), smallWings), a.NostrilFullness), new((.1f - .02f * smallWings) * Signed(a.NostrilFullness), 0, 0)),
			new Edit("Right nostril-wing fullness", WingCenter(-1), 1, wingMask, Scale(Vector3.Lerp(new(1.35f, 1.05f, 1.3f), new(1.28f, 1.05f, 1.24f), smallWings), a.NostrilFullness), new((.1f - .02f * smallWings) * Signed(a.NostrilFullness), 0, 0)),
			new Edit("Forehead width", new(3, 0, 65.8f), 1, new(3.8f, 5, 1.2f), Scale(new(1, 1.16f, 1), a.ForeheadWidth), Vector3.Zero),
			new Edit("Left cheekbone width", new(3.8f, 2.4f, 63.05f), 1, new(2.3f, 1.7f, 1.1f), Vector3.One, new(0, 0.08f * cheekbones, 0)),
			new Edit("Right cheekbone width", new(3.8f, -2.4f, 63.05f), 1, new(2.3f, 1.7f, 1.1f), Vector3.One, new(0, -0.08f * cheekbones, 0)),
			// Include the forward, inward lower lobe while retaining a narrow fade into the scalp.
			// Translate the scale operation's pivot inward while keeping the whole-ear/lobe mask.
			// Shape the upper helix in native rest space before resizing. The inner mask
			// boundary stays outside the skull, and no lateral translation moves the root.
			new Edit("Left ear taper", EarCenter(1), 1, earMask, new(1 - 0.4f * a.EarTaper, 1, 1 + 0.35f * a.EarTaper), new(-0.3f * a.EarTaper, 0, 0.35f * a.EarTaper)),
			new Edit("Right ear taper", EarCenter(-1), 1, earMask, new(1 - 0.4f * a.EarTaper, 1, 1 + 0.35f * a.EarTaper), new(-0.3f * a.EarTaper, 0, 0.35f * a.EarTaper)),
			new Edit("Left ear size", new(0.35f, 3.5f, 63.15f), 1, new(2.15f, 1.05f, 3.15f), earScale, new(0, earPivotCorrection, 0)),
			new Edit("Right ear size", new(0.35f, -3.5f, 63.15f), 1, new(2.15f, 1.05f, 3.15f), earScale, new(0, -earPivotCorrection, 0)),
			new Edit("Left eye size", new(4.7f, 1.65f, 64.5f), 1, new(1.6f, 1.25f, 0.7f), Scale(new(1.12f, 1.18f, 1.18f), a.EyeSize), Vector3.Zero),
			new Edit("Right eye size", new(4.7f, -1.65f, 64.5f), 1, new(1.6f, 1.25f, 0.7f), Scale(new(1.12f, 1.18f, 1.18f), a.EyeSize), Vector3.Zero),
			new Edit("Brow prominence", new(4.6f, 0, 65.2f), 1, new(1.9f, 3.9f, 0.65f), Vector3.One, new(0.08f * brow, 0, 0)),
			// Separate small masks for the philtrum and each lip. Mouth width/projection
			// share a lip envelope ending below the nose and above the chin.
			new Edit("Philtrum width", new(mouth.SurfaceX - 0.05f, 0, (mouth.PhiltrumZ + mouth.UpperZ) / 2 - 0.08f + philtrumLipClearance + philtrumUpperExtension / 2), 1, new(0.75f, 0.9f, (mouth.PhiltrumZ - mouth.UpperZ) / 2 + 0.2f - philtrumLipClearance + philtrumUpperExtension / 2), Scale(new(1, 1.28f, 1), a.PhiltrumWidth), Vector3.Zero),
			new Edit("Mouth width", new(5, 0, 61.8f), 1, new(1.5f, 2.5f, 0.7f), Scale(new(1, 1.2f, 1), a.MouthWidth), Vector3.Zero),
			new Edit("Mouth projection", new(5, 0, 61.8f), 1, new(1.5f, 2.5f, 0.7f), Vector3.One, new(0.12f * mouthProjection, 0, 0)),
			new Edit("Upper lip fullness", new(mouth.SurfaceX, 0, mouth.UpperZ), 1, new(0.7f, 1.85f, mouth.UpperHeight), AmountScale(new(1, 1, 1.4f), upperLip), new(0.08f * upperLip, 0, 0)),
			new Edit("Lower lip fullness", new(mouth.SurfaceX - 0.07f, 0, mouth.LowerZ), 1, new(0.7f, 1.85f, mouth.LowerHeight), AmountScale(new(1, 1, 1.4f), lowerLip), new(0.08f * lowerLip, 0, 0)),
			new Edit("Columella width", new(mouth.ColumellaX, 0, mouth.ColumellaZ), 1, new(0.65f, 0.5f, 0.19f), Scale(new(1, 1.32f, 1), a.ColumellaWidth), Vector3.Zero)
		};
		if (a.BaseShape is null)
			return result;
		var bulge = Signed(a.BaseShape.BulgeSize) * maleAmount;
		return result.Concat(new[] { new Edit("Left bulge", new(4, .65f, 34), 1, new(2.7f, 2.5f, 3), Vector3.One, new(.23f * bulge, 0, 0)), new Edit("Right bulge", new(4, -.65f, 34), 1, new(2.7f, 2.5f, 3), Vector3.One, new(.23f * bulge, 0, 0)) }).ToArray();
	}

	public static bool PropagatesToClothing(string volumeName, CharacterAppearanceData appearance) => !((appearance.BaseShape is not null || AppearanceCatalog.IsFemale(appearance)) && (appearance.BaseShape?.BreastSize ?? appearance.BodyFeatureSize) < 0.5f && (volumeName is "Left body contour" or "Right body contour"));
	public static GameObject Create(SkinnedModelRenderer body, CharacterAppearanceData appearance)
	{
		var root = new GameObject
		{
			Parent = body.GameObject,
			Name = "Local human shape volumes",
			NetworkMode = NetworkMode.Never
		};
		root.LocalTransform = Transform.Zero;
		root.Flags |= GameObjectFlags.NotSaved;
		var edits = Edits(appearance);
		for (var i = 0; i < edits.Length; i++)
		{
			var edit = edits[i];
			var go = new GameObject
			{
				Parent = root,
				Name = edit.Name,
				NetworkMode = NetworkMode.Never
			};
			go.LocalPosition = edit.Center;
			go.LocalRotation = Rotation.FromPitch(edit.Pitch) * Rotation.FromRoll(edit.Roll);
			go.LocalScale = edit.MaskScale;
			var deform = go.Components.Create<ModelDeformer>();
			var volume = deform.SceneVolume;
			volume.Sphere = new()
			{
				Center = Vector3.Zero,
				Radius = edit.Radius
			};
			deform.SceneVolume = volume;
			deform.Operation = ModelDeformer.OperationType.Transform;
			deform.Weight = 1;
			deform.Falloff = edit.Name.Contains("jaw") ? 1 : 0.65f;
			deform.TransformBlend = 0.65f;
			deform.NormalMode = ModelDeformer.NormalModeType.Accurate;
			deform.ApplyToBoneMergedChildren = PropagatesToClothing(edit.Name, appearance);
			deform.Priority = i * 10;
			deform.ScaleFactor = edit.Scale;
			deform.Translation = edit.Translation;
			// A transform with identity coefficients has no visual effect. Keep its
			// authoring component, but remove it from native rendering until edited.
			deform.Enabled = edit.Scale != Vector3.One || edit.Translation != Vector3.Zero;
		}

		return root;
	}

	public static GameObject Update(GameObject root, SkinnedModelRenderer body, CharacterAppearanceData appearance)
	{
		var volumes = root.IsValid() ? root.GetComponentsInChildren<ModelDeformer>(true, true).OrderBy(x => x.Priority).ToArray() : Array.Empty<ModelDeformer>();
		if (volumes.Length != (appearance.BaseShape is null ? VolumeCount : VolumeCount + 2) || volumes.Any(x => x.Target != body))
		{
			if (root.IsValid())
				root.Destroy();
			return Create(body, appearance);
		}

		var edits = Edits(appearance);
		for (var i = 0; i < edits.Length; i++)
		{
			// Untouched controls retain both their components and exact coefficients.
			// Keep existing native components in sync when an authored mask is revised.
			if (volumes[i].GameObject.LocalPosition != edits[i].Center)
				volumes[i].GameObject.LocalPosition = edits[i].Center;
			if (volumes[i].GameObject.Name != edits[i].Name)
				volumes[i].GameObject.Name = edits[i].Name;
			if (volumes[i].GameObject.LocalScale != edits[i].MaskScale)
				volumes[i].GameObject.LocalScale = edits[i].MaskScale;
			var rotation = Rotation.FromPitch(edits[i].Pitch) * Rotation.FromRoll(edits[i].Roll);
			if (volumes[i].GameObject.LocalRotation != rotation)
				volumes[i].GameObject.LocalRotation = rotation;
			if (volumes[i].SceneVolume.Sphere.Radius != edits[i].Radius)
			{
				var volume = volumes[i].SceneVolume;
				volume.Sphere = new()
				{
					Center = Vector3.Zero,
					Radius = edits[i].Radius
				};
				volumes[i].SceneVolume = volume;
			}

			var falloff = edits[i].Name.Contains("jaw") ? 1f : 0.65f;
			if (volumes[i].Falloff != falloff)
				volumes[i].Falloff = falloff;
			if (volumes[i].ScaleFactor != edits[i].Scale)
				volumes[i].ScaleFactor = edits[i].Scale;
			if (volumes[i].Translation != edits[i].Translation)
				volumes[i].Translation = edits[i].Translation;
			volumes[i].ApplyToBoneMergedChildren = PropagatesToClothing(edits[i].Name, appearance);
			var enabled = edits[i].Scale != Vector3.One || edits[i].Translation != Vector3.Zero;
			if (volumes[i].Enabled != enabled)
				volumes[i].Enabled = enabled;
		}

		return root;
	}
}
vendetta.character_editor / CharacterKit/MarkingCatalog.cs
Game game
using System;

namespace CharacterEditor;
public sealed record MarkingStyle(string Id, string Label, int ShaderId, bool Band = false);
public sealed record MarkingRegion(string Id, string Label, bool Head, Vector3 Center, Vector3 Right, Vector3 Up, Vector3 Normal, Vector3 Min, Vector3 Max, float MoveX, float MoveY, float Size = 1, bool BandOnly = false, bool StampOnly = false, float Radius = 2);
public readonly record struct MarkingFrame(Vector3 Center, Vector3 Right, Vector3 Up, Vector3 Normal, int Projection, float Phase);
/// <summary>Surface-specific authoring limits; arbitrary transforms never enter appearance saves.</summary>
public static class MarkingCatalog
{
	public const int Maximum = 6;
	public static IReadOnlyList<MarkingStyle> Styles { get; } = new[]
	{
		new MarkingStyle("none", "None", 0),
		new("crescent", "Crescent", 1),
		new("teardrop", "Teardrop", 2),
		new("star", "Star", 3),
		new("rune", "Rune", 4),
		new("barbed-band", "Barbed wire band", 5, true)
	};
	public static IReadOnlyList<MarkingRegion> Regions => ShoulderSafeRegionDefinitions;

	static readonly IReadOnlyList<MarkingRegion> ShoulderSafeRegionDefinitions = new[]
	{
		new MarkingRegion("face", "Face", true, new(4.6f, 0, 63.3f), Vector3.Left, Vector3.Up, Vector3.Forward, new(-3, -5.5f, 59.3f), new(8, 5.5f, 69.2f), 3, 4, 1.25f, false, true),
		new("left-cheek", "Left cheek", true, new(4, 2.35f, 63.18f), Vector3.Left, Vector3.Up, Vector3.Forward, new(-4, .1f, 60.8f), new(7, 5.5f, 65), 1.4f, 1.3f),
		new("right-cheek", "Right cheek", true, new(4, -2.35f, 63.18f), Vector3.Right, Vector3.Up, Vector3.Forward, new(-4, -5.5f, 60.8f), new(7, -.1f, 65), 1.4f, 1.3f),
		new("forehead", "Forehead", true, new(3.8f, 0, 66.5f), Vector3.Left, Vector3.Up, Vector3.Forward, new(1.8f, -3.7f, 65.2f), new(7, 3.7f, 68.1f), 1.6f, .55f, 1),
		new("chest", "Front torso", false, new(3, 0, 52), Vector3.Left, Vector3.Up, Vector3.Forward, new(-8, -8, 30.5f), new(8, 8, 59.5f), 4.2f, 20, 2.2f, false, true),
		new("back", "Back", false, new(-3, 0, 49), Vector3.Right, Vector3.Up, Vector3.Backward, new(-8, -8, 32.5f), new(8, 8, 59.5f), 4.2f, 15, 2.2f, false, true),
		new("left-arm", "Left arm", false, new(-1.5f, 13.3f, 46), Vector3.Forward, new Vector3(0, -.62f, .785f).Normal, Vector3.Left, new(-7, 6.3f, 36.5f), new(8, 24, 55.5f), 1, 9, 1.5f),
		new("right-arm", "Right arm", false, new(-1.5f, -13.3f, 46), Vector3.Backward, new Vector3(0, .62f, .785f).Normal, Vector3.Right, new(-7, -24, 36.5f), new(8, -6.3f, 55.5f), 1, 9, 1.5f),
		new("left-leg", "Left leg", false, new(1, 6, 20), Vector3.Left, new Vector3(0, -.075f, .997f).Normal, Vector3.Forward, new(-7, 1.5f, 4.7f), new(8, 11.5f, 38.5f), 1.1f, 15, 1.8f, false, false, 2.5f),
		new("right-leg", "Right leg", false, new(1, -6, 20), Vector3.Right, new Vector3(0, .075f, .997f).Normal, Vector3.Forward, new(-7, -11.5f, 4.7f), new(8, -1.5f, 38.5f), 1.1f, 15, 1.8f, false, false, 2.5f),
		new("neck", "Neck choker", true, new(0, 0, 59.3f), Vector3.Forward, new Vector3(.3f, 0, 1).Normal, Vector3.Left, new(-6, -4.5f, 56.8f), new(7, 4.5f, 62.5f), 0, 1.5f, 1, true, false, 2.5f),
		new("torso", "Torso belt", false, new(0, 0, 43.7f), Vector3.Forward, Vector3.Up, Vector3.Left, new(-8, -7.5f, 32.5f), new(8, 7.5f, 50.8f), 0, 10, 1, true, false, 5),
		new("head", "Head band", true, new(0, 0, 66.7f), Vector3.Forward, Vector3.Up, Vector3.Left, new(-6, -5.5f, 58.5f), new(8, 5.5f, 70), 0, 7, 1, true, false, 3.8f)
	};
	public static MarkingStyle Style(string id) => Styles.FirstOrDefault(x => x.Id == id);
	public static MarkingRegion Region(string id) => Regions.FirstOrDefault(x => x.Id == id);
	static bool Compatible(MarkingRegion region, bool band) => band ? (!region.StampOnly && region.Id is not "left-cheek" and not "right-cheek" and not "forehead") : !region.BandOnly;
	public static IEnumerable<MarkingRegion> Locations(string style) => Regions.Where(x => Compatible(x, Style(style)?.Band == true) && (Style(style)?.Band == true || x.Id is not "left-cheek" and not "right-cheek" and not "forehead"));
	// Legacy location IDs stay valid without cluttering the new general Face location.
	public static bool ValidPlacement(string style, string region) => Style(style)is { } design && Region(region)is { } location && Compatible(location, design.Band);
	public static bool OnHead(CharacterMarkingData data) => Region(data.Region).Head;
	public static bool OnMaterial(CharacterMarkingData data, int armSide) => armSide == 1 ? data.Region == "left-arm" : armSide == -1 ? data.Region == "right-arm" : OnBody(data) && data.Region is not "left-arm" and not "right-arm";
	public static bool OnBody(CharacterMarkingData data) => !Region(data.Region).Head || data.Region == "neck";
	public static void ChangeStyle(CharacterMarkingData data, string style)
	{
		if (Style(style)is null)
			return;
		data.Style = style;
		if (!ValidPlacement(style, data.Region))
			ChangeRegion(data, Locations(style).First().Id);
		if (Style(style).Band)
		{
			data.Horizontal = .5f;
			data.Rotation = .5f;
		}
	}

	public static void ChangeRegion(CharacterMarkingData data, string region)
	{
		if (!ValidPlacement(data.Style, region))
			return;
		data.Region = region;
		ResetPosition(data);
	}

	public static void ResetPosition(CharacterMarkingData data)
	{
		data.Horizontal = data.Region == "face" ? .68f : .5f;
		data.Vertical = data.Region == "face" ? .45f : .5f;
	}

	public static Vector3 Center(CharacterMarkingData data)
	{
		var region = Region(data.Region);
		var limits = region.Id switch
		{
			"chest" => (41.5f, 54.5f),
			"back" => (41.2f, 55f),
			"left-arm" or "right-arm" => (38.2f, Style(data.Style).Band ? 52.48f - MathF.Max(0, BandScale(data) - 1) * .55f : 53.2f),
			"left-leg" or "right-leg" => (5.8f, 35.5f),
			"face" => (60.9f, 67.5f),
			"neck" => (58.3f, 60.4f),
			"torso" => (41.2f, 49.5f),
			"head" => (59.9f, 68.5f),
			_ => (region.Center.z - region.MoveY, region.Center.z + region.MoveY)};
		var neutral = region.Center.z;
		if (region.Id is "chest" or "back")
		{
			var theta = (region.Id == "back" ? MathF.PI : 0) + (data.Horizontal - .5f) * 250 * MathF.PI / 180;
			var rotation = (data.Rotation - .5f) * MathF.PI * 2;
			var size = region.Size * StampScale(data);
			var verticalExtent = size * (.45f * MathF.Abs(MathF.Cos(rotation)) + .38f * MathF.Abs(MathF.Sin(rotation)));
			// A whole stamp fits below the deltoid/neck junction, even when rotated or enlarged.
			var safeTop = 56.8f - 4.8f * MathF.Pow(MathF.Abs(MathF.Sin(theta)), 2) - verticalExtent - .1f;
			limits.Item2 = MathF.Min(limits.Item2, safeTop);
			neutral = MathF.Min(neutral, limits.Item1 + (limits.Item2 - limits.Item1) * (region.Id == "chest" ? .81f : .565f));
		}

		var z = data.Vertical < .5f ? limits.Item1 + (neutral - limits.Item1) * data.Vertical * 2 : neutral + (limits.Item2 - neutral) * (data.Vertical - .5f) * 2;
		var center = region.Center + region.Up * ((z - region.Center.z) / region.Up.z);
		if (region.Id is "left-arm" or "right-arm")
		{
			var lower = z < 46;
			var t = lower ? (z - 39) / 7 : (z - 46) / 7.2f;
			center = new(lower ? 1.1f + (-2.6f) * t : -1.5f + .4f * t, MathF.Sign(region.Center.y) * (lower ? 19.9f - 6.6f * t : 13.3f - 4.8f * t), z);
		}

		if (region.Id is "left-leg" or "right-leg")
			center = LegAxis(z, MathF.Sign(region.Center.y));
		if (region.Id == "neck")
			center = new(.3f * (z - 59.3f) + .2f, 0, z);
		if (region.Id is "chest" or "back")
		{
			var theta = (region.Id == "back" ? MathF.PI : 0) + (data.Horizontal - .5f) * 250 * MathF.PI / 180;
			return new(4.3f * MathF.Cos(theta), 6 * MathF.Sin(theta), z);
		}

		if (region.Id == "face")
		{
			var theta = (data.Horizontal - .5f) * 130 * MathF.PI / 180;
			return new(4.6f * MathF.Cos(theta), 4.6f * MathF.Sin(theta), z);
		}

		if (Style(data.Style).Band || IsLimb(region.Id))
			return center;
		if (region.Id is "left-cheek" or "right-cheek")
		{
			var angle = MathF.Atan2(MathF.Abs(region.Center.y), region.Center.x) + (data.Horizontal - .5f) * 2 * (data.Horizontal > .5f ? 30 : 20) * MathF.PI / 180;
			var radius = new Vector2(region.Center.x, region.Center.y).Length;
			return new(radius * MathF.Cos(angle), MathF.Sign(region.Center.y) * radius * MathF.Sin(angle), z);
		}

		return center + region.Right * (data.Horizontal - .5f) * region.MoveX * 2;
	}

	public static bool IsLimb(string id) => id is "left-arm" or "right-arm" or "left-leg" or "right-leg";
	public static MarkingFrame Frame(CharacterMarkingData data)
	{
		var region = Region(data.Region);
		var center = Center(data);
		if (Style(data.Style).Band || IsLimb(region.Id))
		{
			var axis = region.Up;
			if (region.Id is "left-arm" or "right-arm")
				axis = new Vector3(center.z < 46 ? -.3714f : .0556f, MathF.Sign(region.Center.y) * (center.z < 46 ? -.9429f : -.6667f), 1).Normal;
			if (region.Id is "left-leg" or "right-leg")
				axis = (LegAxis(center.z + .1f, MathF.Sign(region.Center.y)) - LegAxis(center.z - .1f, MathF.Sign(region.Center.y))).Normal;
			var right = (Vector3.Forward - axis * axis.x).Normal;
			var normal = Vector3.Cross(axis, right).Normal;
			var phase = region.Id == "left-arm" ? MathF.PI / 2 : region.Id == "right-arm" ? -MathF.PI / 2 : 0;
			return new(center, right, axis, normal, IsLimb(region.Id) ? 2 : 0, phase + (data.Horizontal - .5f) * MathF.PI * 2);
		}

		if (region.Id is "chest" or "back")
			return new(center, new(4.3f, 6, 0), Vector3.Up, new Vector3(center.x / (4.3f * 4.3f), center.y / 36, 0).Normal, 3, MathF.Atan2(center.y / 6, center.x / 4.3f));
		if (region.Id == "face")
			return new(center, Vector3.Left, Vector3.Up, new Vector3(center.x, center.y, 0).Normal, 4, MathF.Atan2(center.y, center.x));
		return new(center, region.Right, region.Up, region.Normal, region.Id is "left-cheek" or "right-cheek" ? 1 : 0, 0);
	}

	public static Vector3 Outward(CharacterMarkingData data)
	{
		var frame = Frame(data);
		if (IsLimb(data.Region))
			return frame.Right * MathF.Cos(frame.Phase) + frame.Normal * MathF.Sin(frame.Phase);
		if (frame.Projection is 1 or 4)
			return new Vector3(frame.Center.x, frame.Center.y, 0).Normal;
		return Region(data.Region).BandOnly ? frame.Right : frame.Normal;
	}

	public static Vector3 Focus(CharacterMarkingData data) => Center(data) + (IsLimb(data.Region) ? Outward(data) * Region(data.Region).Radius : Vector3.Zero);
	static readonly Vector3[] CalibratedLegCenters =
	{
		new(-2.17f, 7.15f, 5.8f),
		new(-2.18f, 7.1f, 7),
		new(-2.13f, 6.7f, 10),
		new(-2.12f, 6.28f, 13),
		new(-1.89f, 6.1f, 16),
		new(-.75f, 5.3f, 19),
		new(-.41f, 4.91f, 22),
		new(-.24f, 4.53f, 25),
		new(0, 4.38f, 28),
		new(-.3f, 4.3f, 35.5f)
	};
	static Vector3 LegAxis(float z, float side)
	{
		var i = 0;
		while (i < CalibratedLegCenters.Length - 2 && z > CalibratedLegCenters[i + 1].z)
			i++;
		var a = CalibratedLegCenters[i];
		var b = CalibratedLegCenters[i + 1];
		var value = a + (b - a) * ((z - a.z) / (b.z - a.z));
		return new(value.x, value.y * side, z);
	}

	public static float StampScale(CharacterMarkingData data) => data.Scale <= .5f ? .5f + data.Scale : 1 + (data.Scale - .5f) * 4;
	public static float BandScale(CharacterMarkingData data) => data.Scale < .5f ? .35f + data.Scale * 1.3f : 1 + (data.Scale - .5f) * 4;
	public static float BandSpacing(CharacterMarkingData data) => data.Spacing < .5f ? .35f + data.Spacing * 1.3f : 1 + (data.Spacing - .5f) * 3;
}
vendetta.character_editor / Experiments/FaceTransferLab.razor
Game game
@using Sandbox
@using Sandbox.UI
@using System
@namespace CharacterEditor
@inherits PanelComponent

<root>
 <div class="labheader"><div><div class="eyebrow">CHARACTER EDITOR / EXPERIMENTS</div><h1>One face, toward another.</h1><p>Compare shape alone. Same surface topology, material, camera and lighting.</p></div><button onclick=@Back>Back to collection</button></div>
 <div class="toolbar">
  <button class=@(Pair=="human03-to-human07"?"chosen":"") onclick=@(()=>Pair="human03-to-human07")>Human 03 → 07</button>
  <button class=@(Pair=="human02-to-human04"?"chosen":"") onclick=@(()=>Pair="human02-to-human04")>Human 02 → 04</button>
  <div class="spacer"></div>
  <button onclick=@(()=>Yaw=0)>Front</button><button onclick=@(()=>Yaw=35)>Three-quarter</button><button onclick=@(()=>Yaw=90)>Profile</button>
 </div>
 <div class="views">
  <div class="view"><div class="caption"><h2>Source</h2><span>Unmodified starting face</span></div><FaceTransferPreview Pair=@Pair Mode="Source" Yaw=@Yaw /></div>
  <div class="view"><div class="caption"><h2>@ModeLabel</h2><span>@($"{Amount:P0} applied")</span></div><FaceTransferPreview Pair=@Pair Mode=@Mode Amount=@Amount Yaw=@Yaw /></div>
  <div class="view"><div class="caption"><h2>Target</h2><span>Reference head surface</span></div><FaceTransferPreview Pair=@Pair Mode="Target" Amount=@(1f) Yaw=@Yaw /></div>
 </div>
 <div class="controls"><div class="toolbar">
  <button class=@(Mode=="Existing"?"chosen":"") onclick=@(()=>{Mode="Existing";Amount=1;})>Fitted editor sliders</button>
  <button class=@(Mode=="Experimental"?"chosen":"") onclick=@(()=>{Mode="Experimental";Amount=1;})>Experimental volumes</button>
  <button class=@(Mode=="Target"?"chosen":"") onclick=@(()=>{Mode="Target";Amount=1;})>Direct vertex morph</button>
 </div><div class="blend"><span>Source</span><SliderControl Value=@Amount OnValueChanged=@((float v)=>{Amount=v;}) Min=@(0f) Max=@(1f) Step=@(.01f) ShowTextEntry=@false /><span>Full result</span></div>
 <p>@Explanation</p><div class="footnote">Rest-pose head surface only • Eye interiors, teeth, skin detail and animation excluded • Results measure geometry, not likeness</div></div>
 <StudioEscapeMenu @ref=EscapeMenu MainMenu=@Back EnableExperiments=@false />
</root>

@code {
	public StudioEscapeMenu EscapeMenu { get; set; }

	protected override void OnUpdate()
	{
		base.OnUpdate();
		if (EscapeMenu is not null && Input.EscapePressed)
		{
			Input.EscapePressed = false;
			EscapeMenu.Toggle();
		}
	}

	public string Pair { get; set; } = "human03-to-human07";
	public string Mode { get; set; } = "Existing";
	public float Amount { get; set; } = 1;
	public float Yaw { get; set; }

	string ModeLabel => Mode == "Existing" ? "Fitted editor sliders" : Mode == "Experimental" ? "Experimental volumes" : "Direct vertex morph";

	string Explanation => Mode == "Target" ? "The direct morph adds the measured per-vertex difference. At 100%, it reaches the target vertex positions. This is a reference shape, not a new avatar preset." : Mode == "Existing" ? (Pair == "human03-to-human07" ? "24 facial controls: front-surface RMS error falls from 1.84 mm to 1.51 mm (18%)." : "24 facial controls: front-surface RMS error falls from 4.07 mm to 3.84 mm (6%).") : "A separate fit using local translation volumes. It improves some regions, but does not consistently beat the existing controls. Intermediate blends interpolate the fitted surface, not the control values.";

	void Back() => StudioSession.OpenScene("scenes/main_menu.scene");
	protected override int BuildHash() => HashCode.Combine(Pair, Mode, Amount, Yaw);
}
vendetta.character_editor / CharacterKit/CloudClothingBrowser.razor
Game game
@using Sandbox
@using Sandbox.UI
@using System
@using System.Threading.Tasks
@namespace CharacterEditor

<root class="cloudwardrobe">
 <div class="optionheading">Cloud clothing</div>
 <div class="optiondescription">Find clothing and hair published by the community. Add an item to your session wardrobe, then wear it like any other item. Human-compatible models are required.</div>
 <TextEntry Value=@Query OnTextEdited=@((string value)=>{Query=value;}) Placeholder="Search cloud clothing" />
 <div class="choicegrid">
  <button class="creatorbutton" disabled=@Busy onclick=@(()=>Search(false))>Search cloud</button>
  <button class="creatorbutton quiet" disabled=@Busy onclick=@ShowInventory>Your purchased cosmetics</button>
 </div>
 <TextEntry Value=@AssetReference OnTextEdited=@((string value)=>{AssetReference=value;}) Placeholder="Clothing asset path or cloud:creator.item" />
 <button class="creatorbutton quiet" disabled=@Busy onclick=@(()=>AddClothing(AssetReference.Trim()))>Add by reference</button>
 @if(!string.IsNullOrEmpty(Message)){<div class="optiondescription">@Message</div>}
 @if(InventoryVisible)
 {
  <div class="choicegrid thumbnailgrid">
  @foreach(var item in Sandbox.Services.Inventory.Definitions.Where(x=>Sandbox.Services.Inventory.HasItem(x.Id) && !string.IsNullOrEmpty(x.PackageIdent) && (string.IsNullOrWhiteSpace(Query)||x.Name.Contains(Query,StringComparison.OrdinalIgnoreCase))))
  {
   <button class=@TileClass("cloud:"+item.PackageIdent) disabled=@Busy onclick=@(()=>AddClothing("cloud:"+item.PackageIdent))>
    <div class="thumbnailframe"><div class="itemthumbnail" style=@($"background-image: url('{item.IconUrl}');")></div>@if(LoadingReference=="cloud:"+item.PackageIdent){<div class="clothingloading"><span class="material-icons loadingspinner">autorenew</span><span>Loading…</span></div>}</div><span class="itemlabel">@item.Name</span>
   </button>
  }
  </div>
 }
 else
 {
  <div class="choicegrid thumbnailgrid">
  @foreach(var package in Results)
  {
   <button class=@TileClass("cloud:"+package.FullIdent) disabled=@Busy onclick=@(()=>AddClothing("cloud:"+package.FullIdent))>
    <div class="thumbnailframe"><div class="itemthumbnail" style=@($"background-image: url('{package.ThumbWide}');")></div>@if(LoadingReference=="cloud:"+package.FullIdent){<div class="clothingloading"><span class="material-icons loadingspinner">autorenew</span><span>Loading…</span></div>}</div><span class="itemlabel">@package.Title</span>
   </button>
  }
  </div>
  @if(Results.Count>0 && Results.Count<Total){<button class="creatorbutton quiet" disabled=@Busy onclick=@(()=>Search(true))>Load more</button>}
 }
</root>

@code {
 public Action<WardrobeItem> Selected { get; set; }
 public CharacterAppearanceData Appearance { get; set; }
 string Query="", AssetReference="", Message;
 string LoadingReference;
 string ActiveQuery;
 bool Busy, InventoryVisible;
 int Generation;
 long Total;
 List<Package> Results=new();
 IEnumerable<string> Equipped => Appearance is null ? Enumerable.Empty<string>() : WardrobeCatalog.Paths(Appearance).Concat(new[] { Appearance.HairPath, Appearance.FacialHairPath }).Where(p=>!string.IsNullOrEmpty(p));
 bool IsEquipped(string reference) => Equipped.Any(path=>string.Equals(path,reference,StringComparison.OrdinalIgnoreCase) || (ClothingResources.Get(reference) is { } item && ClothingResources.Get(path)==item));
 string TileClass(string reference) => $"creatorbutton wardrobechoice {(IsEquipped(reference)?"chosen":"")} {(LoadingReference==reference?"loading":"")}";
 void ShowInventory()
 {
  if(Busy)return;
  InventoryVisible=true;
  Message="Purchased items use your s&box inventory. Items without a human model cannot be worn here.";
 }
 async Task Search(bool more)
 {
  if(Busy)return;
  Busy=true;
  InventoryVisible=false;
  var generation=++Generation;
  Message="Searching…";
  StateHasChanged();
  try
  {
   if(!more){Results.Clear();ActiveQuery=Query;}
   // Escape filters in user input so results remain clothing-only.
   var words=string.Join(" ", (ActiveQuery??"").Split(' ',StringSplitOptions.RemoveEmptyEntries).Where(x=>!x.Contains(':')));
   var result=await Package.FindAsync($"{words} type:clothing sort:newest",24,Results.Count);
   if(!IsValid || generation!=Generation)return;
   Results.AddRange(result.Packages);
   Total=result.TotalCount;
   Message=Total==0?"No clothing found.":$"{Total} cloud items. Click a preview to add it.";
  }
  catch(Exception e){if(IsValid)Message="Cloud search failed: "+e.Message;}
  finally{Busy=false;if(IsValid)StateHasChanged();}
 }
 async Task AddClothing(string reference)
 {
  if(Busy)return;
  Busy=true;LoadingReference=reference;Message="Loading clothing…";StateHasChanged();
  try
  {
   var clothing=await ClothingResources.Load(reference);
   if(!IsValid)return;
   if(ClothingResources.IsBaseSkin(clothing))throw new InvalidOperationException("Choose a clothing or hair item; base skins are selected in the Skin tab.");
   Selected?.Invoke(ClothingResources.Describe(reference,clothing));
   StateHasChanged();
   if(Appearance is not null) await CharacterVisual.Load(Appearance.DeepCopy());
   if(!IsValid)return;
   Message=(IsEquipped(reference)?"Equipped ":"Removed ")+clothing.Title+".";
  }
  catch(Exception e){if(IsValid)Message=e.Message;}
  finally{Busy=false;LoadingReference=null;if(IsValid)StateHasChanged();}
 }
 protected override async Task OnParametersSetAsync()
 {
  if(ActiveQuery is null && !Busy) await Search(false);
 }
 protected override int BuildHash()=>HashCode.Combine(Query,Message,Busy,InventoryVisible,Results.Count,Total,HashCode.Combine(Sandbox.Services.Inventory.Items?.Count,AssetReference,LoadingReference,ClothingResources.Revision),string.Join("|",Equipped));
}
vendetta.character_editor / CharacterKit/ReferenceFacePanel.razor
Game game
@using Sandbox

@using Sandbox.UI

@using System

@namespace CharacterEditor

@inherits Panel

<root>

 <div class="optionheading">Make a face from photos</div>

 <div class="optiondescription">Add up to five direct image links of the same person. Combine a front photo with an angled or side photo and a relaxed expression. Front views guide widths; side views guide the nose, lips and chin. The result is an approximate starting shape; refine it in Shape afterwards.</div>

 <div class="optiondescription">Images are read by the background analyzer on this device. Some image hosts block this access. Photos and links are not included in saved characters.</div>

 <TextEntry class="photo-url" Value=@Url OnTextEdited=@((string value)=>{Url=value;}) Placeholder="https://…/portrait.jpg" MaxLength=@(2048) />

 <button class="creatorbutton add-photo" onclick=@AddReference>Load reference</button>
 @if(Rows.Count>0){<button class="creatorbutton quiet refresh-photos" onclick=@RefreshReferences>Refresh analyses</button>}
 @if(!string.IsNullOrEmpty(Message)){<div class="optiondescription">@Message</div>}

 @foreach(var row in Rows)

 {

  <div class="photo-reference">

   @if(row.Measurement is not null){<img class="photo-thumb" [email protected] />}

   <div class="photo-reference-text"><div class="fieldlabel">Reference @(Rows.IndexOf(row)+1)</div><div class="optiondescription">@row.Status</div><button class=@($"creatorbutton quiet remove-photo-{Rows.IndexOf(row)}") onclick=@(()=>Remove(row))>Remove</button></div>

  </div>

 }

 <div class="optiondescription">@(Rows.Any(x=>x.Measurement?.FrontUsable==true)?"Front widths: covered.":"Add a front view for face widths.") @(Rows.Any(x=>x.Measurement?.Yaw>=45)?"Profile depth: covered.":Rows.Any(x=>x.Measurement?.ProfileUsable==true)?"Some depth from angled views; a clearer side view helps.":"Add an angled or side view for nose and chin depth.")</div>

 <div class="fieldlabel">Starting heads</div>
 <div class="optiondescription">Choose the foundation to keep while fitting. Selected skin uses that skin's original head family; it does not guess from the photos. Current blend preserves your chosen balance of male and female heads.</div>
 @foreach(var choice in new[]{("skin","Selected skin"),("female","Female heads"),("male","Male heads"),("current","Current blend")})
 {
  <button class=@($"creatorbutton {(Basis==choice.Item1?"primary":"quiet")}") onclick=@(()=>Basis=choice.Item1)>@choice.Item2</button>
 }
 <div class="fieldlabel">Name your base face</div>
 <TextEntry class="photo-name" Value=@FaceName OnTextEdited=@((string value)=>{FaceName=value;}) MaxLength=@(40) Placeholder="My photo face" />
 <button class="creatorbutton primary generate-photo-face" onclick=@Generate>Generate base face</button>

 <div class="optiondescription">Generation preserves the starting balance of male and female heads while fitting proportions. It adds a custom face at full influence; existing face influences are set to zero. Your Shape edits remain applied on top.</div>
</root>

@code {
	public CharacterAppearanceData Appearance { get; set; }
	public Action<PhotoFace> Generated { get; set; }
	public string AnalyzerPage { get; set; } = FacePhotoBridge.Page;

	public sealed class Reference
	{
		public string Url, Status;
		public FaceMeasurement Measurement;
	}

	readonly List<Reference> Rows = new();
	string Url = "", FaceName = "My photo face", Message;
	string Basis = "skin";
	FacePhotoBridge Bridge;
	Reference Active;
	public void AddUrl(string url)
	{
		Url = url;
		AddReference();
	}

	void AddReference()
	{
		if (Rows.Count >= 5)
		{
			Message = "You can use up to five references. Remove one to add another.";
			return;
		}

		var value = Url.Trim();
		if (!FacePhotoBridge.ValidUrl(value))
		{
			Message = "Enter a direct HTTPS link to a JPEG, PNG or WebP image.";
			return;
		}

		if (Rows.Any(x => x.Url == value))
		{
			Message = "That reference is already in the list.";
			return;
		}

		Rows.Add(new() { Url = value, Status = "Waiting…" });
		Url = "";
		Message = null;
	}

	void Remove(Reference row)
	{
		if (Active == row)
		{
			Bridge?.Dispose();
			Bridge = null;
			Active = null;
		}

		Rows.Remove(row);
		Message = null;
	}

	public void RefreshReferences()
	{
		Bridge?.Dispose();
		Bridge = null;
		Active = null;
		Message = null;
		foreach (var row in Rows)
		{
			row.Measurement = null;
			row.Status = "Waiting…";
		}
	}

	public override void Tick()
	{
		base.Tick();
		if (Bridge is null)
		{
			Active = Rows.FirstOrDefault(x => x.Status == "Waiting…");
			if (Active is not null)
				try
				{
					Bridge = new(this, Active.Url, AnalyzerPage);
				}
				catch (Exception e)
				{
					Active.Status = e.Message;
					Active = null;
				}
		}

		if (Bridge is null)
			return;
		Bridge.Tick();
		Active.Status = Bridge.Status;
		if (Bridge.Complete)
		{
			Active.Measurement = Bridge.Result;
			if (Active.Measurement is not null)
				Active.Status = "Ready · " + Active.Measurement.Description;
			Bridge.Dispose();
			Bridge = null;
			Active = null;
		}
	}

	void Generate()
	{
		if (Bridge is not null || Rows.Any(x => x.Status == "Waiting…"))
		{
			Message = "Wait for the references to finish loading first.";
			return;
		}

		var good = Rows.Where(x => x.Measurement is not null).ToArray();
		if (good.Length == 0)
		{
			Message = "Load at least one usable reference first.";
			return;
		}

		if (good.Length != Rows.Count)
		{
			Message = "Remove the failed references or replace them before generating.";
			return;
		}

		if (Appearance.BaseShape?.PhotoFaces.Count >= 4)
		{
			Message = "You can keep four custom faces in a character. Remove one in Face & body first.";
			return;
		}

		try
		{
			UnifiedHuman.Enable(Appearance);
			Generated?.Invoke(PhotoFaceFit.Generate(FaceName, good.Select(x => x.Measurement).ToArray(), PhotoFaceFit.StartingHeads(Appearance, Basis)));
		}
		catch (Exception e)
		{
			Message = e.Message;
		}
	}

	public override void Delete(bool immediate = false)
	{
		Bridge?.Dispose();
		base.Delete(immediate);
	}

	protected override int BuildHash() => HashCode.Combine(Url, FaceName, Message, Basis, string.Join("|", Rows.Select(x => x.Url + x.Status)));
}
vendetta.character_editor / CharacterKit/UnifiedNeckAssets.cs
Game game
using System;
using System.Threading.Tasks;

namespace CharacterEditor;
/// <summary>Head albedos with only the lower-neck band matched to the body atlas.</summary>
public static class UnifiedNeckAssets
{
	static readonly Dictionary<string, Material> carriers = new();
	public static async Task LoadHeadAsync(string skin)
	{
		var id = System.IO.Path.GetFileNameWithoutExtension(skin);
		if (!carriers.TryGetValue(id, out var material))
			material = await Material.LoadAsync($"materials/makeup/neck/{id}.vmat");
		var texture = material?.GetTexture("g_tColor");
		if (texture is null || texture.IsError)
			throw new InvalidOperationException("The matched neck texture is unavailable: " + id);
		texture.MarkUsed(Math.Max(texture.Width, texture.Height));
		carriers[id] = material;
	}

	public static Texture ColorTexture(string skin) => carriers[System.IO.Path.GetFileNameWithoutExtension(skin)].GetTexture("g_tColor");
}
vendetta.character_editor / CharacterKit/WardrobeCatalog.Generated.cs
Game game
namespace CharacterEditor;
public static partial class WardrobeCatalog
{
 public static IReadOnlyList<WardrobeItem> Items=>entries??=CreateEntries();
 static WardrobeItem[] entries;
 static WardrobeItem[] CreateEntries()=>new WardrobeItem[]{
  new("Denim Skirt Black","Bottoms","Bottoms","models/citizen_clothes/dress/denim_skirt/denim_skirt_black.clothing","LeftThigh, RightThigh, LeftKnee, RightKnee","","","Skirt"),
  new("Denim Skirt Blue","Bottoms","Bottoms","models/citizen_clothes/dress/denim_skirt/denim_skirt_blue.clothing","LeftThigh, RightThigh, LeftKnee, RightKnee","","","Skirt"),
  new("Denim Skirt Green","Bottoms","Bottoms","models/citizen_clothes/dress/denim_skirt/denim_skirt_green.clothing","LeftThigh, RightThigh, LeftKnee, RightKnee","","","Skirt"),
  new("Denim Skirt Leapard","Bottoms","Bottoms","models/citizen_clothes/dress/denim_skirt/denim_skirt_leapard.clothing","LeftThigh, RightThigh, LeftKnee, RightKnee","","","Skirt"),
  new("Dotted Dress","Outfits","Outfits","models/citizen_clothes/dress/dotted_dress/dotted_dress.clothing","Chest, Groin, LeftThigh, RightThigh","","Chest","Dress"),
  new("Grand Ball Gown","Outfits","Outfits","models/citizen_clothes/dress/posh_dress/models/posh_dress.clothing","Chest, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","Chest, Legs","Dress"),
  new("Simple Dress","Outfits","Outfits","models/citizen_clothes/dress/simple_dress/simple_dress.clothing","Chest, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","Chest","Dress"),
  new("Simple Dress Black","Outfits","Outfits","models/citizen_clothes/dress/simple_dress/simple_dress_black.clothing","Chest, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","Chest","Dress"),
  new("Simple Dress Blue","Outfits","Outfits","models/citizen_clothes/dress/simple_dress/simple_dress_blue.clothing","Chest, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","Chest","Dress"),
  new("Standard Skirt","Bottoms","Bottoms","models/citizen_clothes/dress/standard_skirt/standard_skirt.clothing","LeftThigh, RightThigh, LeftKnee, RightKnee","","","Skirt"),
  new("Pirate Outfit","Outfits","Outfits","models/citizen_clothes/full_outfit/pirate_outfit/pirate_outfit.clothing","Chest, LeftArm, RightArm, LeftWrist, RightWrist, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot","Chest, LeftArm, RightArm, LeftWrist, RightWrist, LeftHand, RightHand, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot, Waist","Chest, Legs, Feet","Costume"),
  new("Nerdy Glasses","Glasses","Glasses","models/citizen_clothes/glasses/nerdy_glasses/nerdy_glasses.clothing","Glasses","","","GlassesEye"),
  new("Party Glasses","Glasses","Glasses","models/citizen_clothes/glasses/party_glasses/party_glasses.clothing","Glasses","","","GlassesSun"),
  new("Boxing Gloves","Gloves","Gloves","models/citizen_clothes/gloves/boxing_gloves/boxing_gloves.clothing","LeftHand, RightHand","","Hands","Gloves"),
  new("Boxing Gloves Blue","Gloves","Gloves","models/citizen_clothes/gloves/boxing_gloves/boxing_gloves_blue.clothing","LeftHand, RightHand","","Hands","Gloves"),
  new("Boxing Gloves Red","Gloves","Gloves","models/citizen_clothes/gloves/boxing_gloves/boxing_gloves_red.clothing","LeftHand, RightHand","","Hands","Gloves"),
  new("Fingerless Gloves","Gloves","Gloves","models/citizen_clothes/gloves/fingerless_gloves/fingerless_gloves.clothing","LeftHand, RightHand","","Hands","Gloves"),
  new("Goalie Gloves","Gloves","Gloves","models/citizen_clothes/gloves/goalie_gloves/goalie_gloves.clothing","LeftHand, RightHand","","Hands","Gloves"),
  new("Blue Rubber Gloves","Gloves","Gloves","models/citizen_clothes/gloves/rubber_gloves/rubber_gloves.clothing","LeftHand, RightHand","","Hands","Gloves"),
  new("Black Rubber Gloves","Gloves","Gloves","models/citizen_clothes/gloves/rubber_gloves/rubber_gloves_black.clothing","LeftHand, RightHand","","Hands","Gloves"),
  new("Mask","Hats","Hats","models/citizen_clothes/hat/balaclava/balaclava.clothing","HeadTop, HeadBottom","","","HatBeanie"),
  new("Bandana Mask","Hats","Hats","models/citizen_clothes/hat/bandana_mask/bandana_mask.clothing","","","","HeadSpecial"),
  new("Baseball Cap","Hats","Hats","models/citizen_clothes/hat/baseball_cap/baseball_cap.clothing","HeadTop","","","Hat"),
  new("Baseball Cap Black","Hats","Hats","models/citizen_clothes/hat/baseball_cap/baseball_cap_black.clothing","HeadTop","","","Hat"),
  new("Baseball Cap Blue White","Hats","Hats","models/citizen_clothes/hat/baseball_cap/baseball_cap_blue_white.clothing","HeadTop","","","Hat"),
  new("Baseball Cap Brown","Hats","Hats","models/citizen_clothes/hat/baseball_cap/baseball_cap_brown.clothing","HeadTop","","","Hat"),
  new("Beanie","Hats","Hats","models/citizen_clothes/hat/beanie/beanie.clothing","HeadTop","","","HatBeanie"),
  new("Beanie Green","Hats","Hats","models/citizen_clothes/hat/beanie/beanie_green.clothing","HeadTop","","","HatBeanie"),
  new("Beanie Red","Hats","Hats","models/citizen_clothes/hat/beanie/beanie_red.clothing","HeadTop","","","HatBeanie"),
  new("Boxing Headgear","Hats","Hats","models/citizen_clothes/hat/boxing_headgear/boxing_headgear.clothing","HeadTop","","","HatCostume"),
  new("Boxing Headgear Blue","Hats","Hats","models/citizen_clothes/hat/boxing_headgear/boxing_headgear_blue.clothing","HeadTop","","","HatCostume"),
  new("Boxing Headgear Red","Hats","Hats","models/citizen_clothes/hat/boxing_headgear/boxing_headgear_red.clothing","HeadTop, HeadBottom","","","HatCostume"),
  new("Bucket Hat","Hats","Hats","models/citizen_clothes/hat/bucket_hat/bucket_hat.clothing","HeadTop","","","Hat"),
  new("Bucket Hat Black","Hats","Hats","models/citizen_clothes/hat/bucket_hat/bucket_hat_black.clothing","HeadTop","","","Hat"),
  new("Chef's Hat","Hats","Hats","models/citizen_clothes/hat/chefs_hat/chefs_hat.clothing","HeadTop","","","HatUniform"),
  new("Cowboy Hat","Hats","Hats","models/citizen_clothes/hat/cowboy_hat/cowboy_hat.clothing","HeadTop","","","HatCostume"),
  new("Fedora","Hats","Hats","models/citizen_clothes/hat/fedora/fedora.clothing","HeadTop","","","HatFormal"),
  new("Fedora Brown","Hats","Hats","models/citizen_clothes/hat/fedora/fedora_brown.clothing","HeadTop","","","HatFormal"),
  new("Flatcap","Hats","Hats","models/citizen_clothes/hat/flatcap/flatcap.clothing","HeadTop","","","Hat"),
  new("Gasmask","Hats","Hats","models/citizen_clothes/hat/gasmask/gasmask.clothing","HeadBottom","","","HatUniform"),
  new("Hair Net","Hats","Hats","models/citizen_clothes/hat/hair_net/hair_net.clothing","HeadTop","","","HatUniform"),
  new("Hair Net White","Hats","Hats","models/citizen_clothes/hat/hair_net/hair_net_white.clothing","HeadTop","","","HatUniform"),
  new("Hard Hat","Hats","Hats","models/citizen_clothes/hat/hard_hat/hard_hat.clothing","HeadTop","","","HatUniform"),
  new("Headphones","Hats","Hats","models/citizen_clothes/hat/headphones/models/headphones.clothing","","","","HeadTech"),
  new("Police Hat","Hats","Hats","models/citizen_clothes/hat/police_hat/police_hat.clothing","HeadTop","","","HatUniform"),
  new("Racer Helmet","Hats","Hats","models/citizen_clothes/hat/racer_helmet/racer_helmet.clothing","HeadTop","","","HatCostume"),
  new("Scifi Trooper Helmet","Hats","Hats","models/citizen_clothes/hat/scifi_trooper_helmet/scifi_trooper_helmet.clothing","HeadTop","","","HatCostume"),
  new("Skater Helmet","Hats","Hats","models/citizen_clothes/hat/skater_helmet/skater_helmet.clothing","HeadTop","","","HatUniform"),
  new("Skater Helmet Camo","Hats","Hats","models/citizen_clothes/hat/skater_helmet/skater_helmet_camo.clothing","HeadTop","","","HatUniform"),
  new("Skater Helmet White","Hats","Hats","models/citizen_clothes/hat/skater_helmet/skater_helmet_white.clothing","HeadTop","","","HatUniform"),
  new("Tactical Helmet","Hats","Hats","models/citizen_clothes/hat/tactical_helmet/tactical_helmet.clothing","HeadTop, Glasses","","","HatUniform"),
  new("Army Helmet","Hats","Hats","models/citizen_clothes/hat/tactical_helmet/tactical_helmet_army.clothing","HeadTop, Glasses","","","HatUniform"),
  new("Trapper Hat","Hats","Hats","models/citizen_clothes/hat/trapper_hat/trapper_hat.clothing","HeadTop","","","HatBeanie"),
  new("Trapper Hat Cloth","Hats","Hats","models/citizen_clothes/hat/trapper_hat/trapper_hat_cloth.clothing","HeadTop","","","HatBeanie"),
  new("War Helmet","Hats","Hats","models/citizen_clothes/hat/war_helmet/war_helmet.clothing","HeadTop","","","HatUniform"),
  new("Biker Jacket","Outerwear","Outerwear","models/citizen_clothes/jacket/biker_jacket/biker_jacket.clothing","","LeftArm, RightArm","","Jacket"),
  new("Bomber Jacket","Outerwear","Outerwear","models/citizen_clothes/jacket/bomber_jacket/bomber_jacket.clothing","","LeftArm, RightArm","","Jacket"),
  new("Black Leather Jacket","Outerwear","Outerwear","models/citizen_clothes/jacket/brown_leather_jacket/black_leather_jacket.clothing","","LeftArm, RightArm","","Jacket"),
  new("Brown Leather Jacket","Outerwear","Outerwear","models/citizen_clothes/jacket/brown_leather_jacket/brown_leather_jacket.clothing","","LeftArm, RightArm","","Jacket"),
  new("Hoodie","Tops","Tops","models/citizen_clothes/jacket/hoodie/hoodie.clothing","Chest","LeftArm, RightArm","Chest","Hoodie"),
  new("Black Hoodie","Tops","Tops","models/citizen_clothes/jacket/hoodie/hoodie_black.clothing","LeftArm, RightArm, LeftWrist, RightWrist","Chest","Chest","Hoodie"),
  new("Lab Coat","Outerwear","Outerwear","models/citizen_clothes/jacket/labcoat/labcoat.clothing","","LeftArm, RightArm","","Coat"),
  new("Puffer Jacket","Outerwear","Outerwear","models/citizen_clothes/jacket/puffer_jacket/puffer_jacket.clothing","","LeftArm, RightArm","","Jacket"),
  new("Puffer Jacket Blue","Outerwear","Outerwear","models/citizen_clothes/jacket/puffer_jacket/puffer_jacket_blue.clothing","","LeftArm, RightArm","","Jacket"),
  new("Puffer Jacket Orange","Outerwear","Outerwear","models/citizen_clothes/jacket/puffer_jacket/puffer_jacket_orange.clothing","","LeftArm, RightArm","","Jacket"),
  new("Rain Coat","Outerwear","Outerwear","models/citizen_clothes/jacket/raincoat/raincoat.clothing","","LeftArm, RightArm","","Coat"),
  new("Sleeveless Jack","Outerwear","Outerwear","models/citizen_clothes/jacket/sleeveless_jacket/sleeveless_jacket.clothing","","LeftArm, RightArm","","Gilet"),
  new("Wind Breaker","Outerwear","Outerwear","models/citizen_clothes/jacket/wind_breaker/wind_breaker.clothing","","LeftArm, RightArm","","Coat"),
  new("Winter Coat","Outerwear","Outerwear","models/citizen_clothes/jacket/winter_coat/winter_coat.clothing","","LeftArm, RightArm","","Coat"),
  new("Cardigan","Outerwear","Outerwear","models/citizen_clothes/jumper/cardigan/cardigan.clothing","","LeftArm, RightArm","","Cardigan"),
  new("Cardigan Pink","Outerwear","Outerwear","models/citizen_clothes/jumper/cardigan/cardigan_colored.clothing","","LeftArm, RightArm","","Cardigan"),
  new("Cardigan Dark","Outerwear","Outerwear","models/citizen_clothes/jumper/cardigan/cardigan_dark.clothing","","LeftArm, RightArm","","Cardigan"),
  new("Cardigan Dirty","Outerwear","Outerwear","models/citizen_clothes/jumper/cardigan/cardigan_dirty.clothing","","LeftArm, RightArm","","Cardigan"),
  new("Loose Jumper","Tops","Tops","models/citizen_clothes/jumper/loose_jumper/loose_jumper.clothing","Chest","LeftArm, RightArm","Chest","Knitwear"),
  new("Goalie Jumper","Tops","Tops","models/citizen_clothes/jumper/loose_jumper/loose_jumper_goalie.clothing","Chest","LeftArm, RightArm","Chest","Knitwear"),
  new("Loose Jumper Wooly","Tops","Tops","models/citizen_clothes/jumper/loose_jumper/loose_jumper_wooly.clothing","Chest","LeftArm, RightArm","Chest","Knitwear"),
  new("Sleeveless Jumper","Tops","Tops","models/citizen_clothes/jumper/sleeveless_jumper/sleeveless_jumper.clothing","","LeftArm, RightArm","","Knitwear"),
  new("Sleeveless Jumper Grey","Tops","Tops","models/citizen_clothes/jumper/sleeveless_jumper/sleeveless_jumper_grey.clothing","","LeftArm, RightArm","","Knitwear"),
  new("Sleeveless Jumper Red","Tops","Tops","models/citizen_clothes/jumper/sleeveless_jumper/sleeveless_jumper_red.clothing","","LeftArm, RightArm","","Knitwear"),
  new("Pearl Necklace","Accessories","necklace","models/citizen_clothes/necklace/pearl_necklace/pearl_necklace.clothing","Chin","","","NecklacePendant"),
  new("Vintage Necklace","Accessories","necklace","models/citizen_clothes/necklace/vintage_necklace/vintage_necklace.clothing","Chin","","","NecklacePendant"),
  new("Santa Hat 2024","Hats","Hats","models/citizen_clothes/seasonal/2024/santa/santahat.clothing","HeadTop","","","HatCostume"),
  new("Santa Jacket 2024","Outerwear","Outerwear","models/citizen_clothes/seasonal/2024/santa/santajacket.clothing","LeftArm, RightArm","LeftArm, RightArm","Chest","Coat"),
  new("Santa Trousers 2024","Bottoms","Bottoms","models/citizen_clothes/seasonal/2024/santa/santatrousers.clothing","Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot","Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot","Legs, Feet","Trousers"),
  new("Easter Bonnet","Hats","Hats","models/citizen_clothes/seasonal/2025/easter_farmer/easter_bonnet/easter_bonnet.clothing","HeadTop","","","HatCostume"),
  new("Easter Dungarees","Outfits","Outfits","models/citizen_clothes/seasonal/2025/easter_farmer/easter_dungarees/models/easter_dungarees.clothing","Chest, LeftArm, RightArm, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","Chest, LeftArm, RightArm, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","Chest, Legs","Suit"),
  new("Easter Slippers","Shoes","Shoes","models/citizen_clothes/seasonal/2025/easter_farmer/easter_slippers/models/easter_slippers.clothing","LeftFoot, RightFoot","","Feet","Footwear"),
  new("Valentines Bear Head 2025","Hats","Hats","models/citizen_clothes/seasonal/2025/valentines_bear/bear_head.clothing","HeadTop, HeadBottom","HeadTop, HeadBottom","Head","HatCostume"),
  new("Valentines Bear Top 2025","Outfits","Outfits","models/citizen_clothes/seasonal/2025/valentines_bear/bear_top.clothing","Chest, LeftArm, RightArm, LeftWrist, RightWrist, LeftHand, RightHand","LeftArm, RightArm, LeftWrist, RightWrist, LeftHand, RightHand","Chest, Hands","Costume"),
  new("Valentines Bear Trousers 2025","Bottoms","Bottoms","models/citizen_clothes/seasonal/2025/valentines_bear/bear_trousers.clothing","Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot","Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot","Legs, Feet","Trousers"),
  new("Wizard Beard","Accessories","costume_beard","models/citizen_clothes/seasonal/2025/wizard_outfit/wizard_beard/wizard_beard.clothing","Face","","","FacialHairBeard"),
  new("Wizard Gown","Outerwear","Outerwear","models/citizen_clothes/seasonal/2025/wizard_outfit/wizard_gown/wizard_gown.clothing","Chest, LeftArm, RightArm","Chest, LeftArm, RightArm, LeftWrist","Chest","Coat"),
  new("Wizard Hat","Hats","Hats","models/citizen_clothes/seasonal/2025/wizard_outfit/wizard_hat/wizard_hat.clothing","HeadTop","","","HatCostume"),
  new("Wizard Trousers","Bottoms","Bottoms","models/citizen_clothes/seasonal/2025/wizard_outfit/wizard_trousers/wizard_trousers.clothing","Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot","","Legs, Feet","Trousers"),
  new("Army Shirt","Tops","Tops","models/citizen_clothes/shirt/army_shirt/army_shirt.clothing","Chest, LeftArm, RightArm","","Chest","Shirt"),
  new("Chef Shirt","Tops","Tops","models/citizen_clothes/shirt/chef_shirt/chef_shirt.clothing","Chest, LeftArm, RightArm","","Chest","Shirt"),
  new("Fisherman Jumpsuit","Outfits","Outfits","models/citizen_clothes/shirt/fisherman_jumpsuit/fisherman_jumpsuit.clothing","Chest, LeftArm, RightArm, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot","","Chest, Legs, Feet","Suit"),
  new("Flannel Shirt","Tops","Tops","models/citizen_clothes/shirt/flannel_shirt/flannel_shirt.clothing","Chest, LeftArm, RightArm","","Chest","Shirt"),
  new("Blue Shirt","Tops","Tops","models/citizen_clothes/shirt/flannel_shirt/variations/blue_shirt/blue_shirt.clothing","Chest, LeftArm, RightArm","","Chest","Shirt"),
  new("Hawaiian Shirt","Tops","Tops","models/citizen_clothes/shirt/hawaiian_shirt/hawaiian shirt.clothing","Chest, LeftArm, RightArm","","Chest","Shirt"),
  new("Blue Jumpsuit","Outfits","Outfits","models/citizen_clothes/shirt/jumpsuit/blue_jumpsuit.clothing","Chest, LeftArm, RightArm, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","Chest, Legs","Suit"),
  new("Prison Jumpsuit","Outfits","Outfits","models/citizen_clothes/shirt/jumpsuit/prison_jumpsuit.clothing","Chest, LeftArm, RightArm, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","Chest, Legs","Suit"),
  new("Leotard","Tops","Tops","models/citizen_clothes/shirt/leotard/leotard.clothing","Chest, LeftArm, RightArm, LeftWrist, RightWrist, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","","TShirt"),
  new("Leotard Blue","Tops","Tops","models/citizen_clothes/shirt/leotard/leotard_blue.clothing","Chest, LeftArm, RightArm, LeftWrist, RightWrist, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","","Shirt"),
  new("Loose Crop Top","Tops","Tops","models/citizen_clothes/shirt/loose_crop_top/loose_crop_top.clothing","Chest, LeftArm, RightArm","","Chest","Shirt"),
  new("Loose Crop Top Pattern","Tops","Tops","models/citizen_clothes/shirt/loose_crop_top/loose_crop_top_pattern.clothing","Chest, LeftArm, RightArm","","Chest","Shirt"),
  new("Loose Crop Top Purple","Tops","Tops","models/citizen_clothes/shirt/loose_crop_top/loose_crop_top_purple.clothing","Chest, LeftArm, RightArm","","Chest","Shirt"),
  new("Loose Crop Top White","Tops","Tops","models/citizen_clothes/shirt/loose_crop_top/loose_crop_top_white.clothing","Chest, LeftArm, RightArm","","Chest","Shirt"),
  new("Loose Shirt","Tops","Tops","models/citizen_clothes/shirt/loose_shirt/loose_shirt.clothing","Chest, LeftArm, RightArm, LeftWrist, RightWrist","","Chest","TShirt"),
  new("Polo Shirt","Tops","Tops","models/citizen_clothes/shirt/polo_shirt/polo_shirt.clothing","Chest, LeftArm, RightArm","","Chest","TShirt"),
  new("Polo Shirt White","Tops","Tops","models/citizen_clothes/shirt/polo_shirt/polo_shirt_white.clothing","Chest, LeftArm, RightArm","","Chest","TShirt"),
  new("Tank Top ","Tops","Tops","models/citizen_clothes/shirt/tanktop/tanktop.clothing","Chest","","","TShirt"),
  new("Football Tshirt","Tops","Tops","models/citizen_clothes/shirt/tshirt/football_tshirt.clothing","Chest, LeftArm, RightArm","","Chest","TShirt"),
  new("Tshirt","Tops","Tops","models/citizen_clothes/shirt/tshirt/tshirt.clothing","Chest, LeftArm, RightArm","","Chest","Tops"),
  new("Waistcoat and Shirt","Tops","Tops","models/citizen_clothes/shirt/waistcoat_and_shirt/waistcoat_and_shirt.clothing","Chest, LeftArm, RightArm","","Chest","Shirt"),
  new("Boot Heels Black","Shoes","Shoes","models/citizen_clothes/shoes/boot_heels/boot_heels_black.clothing","LeftFoot, RightFoot","","Feet","Heels"),
  new("Boot Heels Brown","Shoes","Shoes","models/citizen_clothes/shoes/boot_heels/boot_heels_brown.clothing","LeftFoot, RightFoot","","Feet","Heels"),
  new("Boot Heels Leopard","Shoes","Shoes","models/citizen_clothes/shoes/boot_heels/boot_heels_leopard.clothing","LeftFoot, RightFoot","","Feet","Heels"),
  new("Boot Heels Red","Shoes","Shoes","models/citizen_clothes/shoes/boot_heels/boot_heels_red.clothing","LeftFoot, RightFoot","","Feet","Heels"),
  new("Army Boots","Shoes","Shoes","models/citizen_clothes/shoes/boots/army_boots.clothing","LeftFoot, RightFoot","","Feet","Boots"),
  new("Black Boots","Shoes","Shoes","models/citizen_clothes/shoes/boots/black_boots.clothing","LeftFoot, RightFoot","","Feet","Boots"),
  new("Boxing Boots","Shoes","Shoes","models/citizen_clothes/shoes/boxing_boots/boxing_boots.clothing","LeftFoot, RightFoot","","Feet","Boots"),
  new("Boxing Boots Blue","Shoes","Shoes","models/citizen_clothes/shoes/boxing_boots/boxing_boots_blue.clothing","LeftFoot, RightFoot","","Feet","Boots"),
  new("Clogs and Socks","Shoes","Shoes","models/citizen_clothes/shoes/crocs_and_socks/clogs_and_socks.clothing","LeftFoot, RightFoot","","Feet","Slippers"),
  new("Flat Shoes","Shoes","Shoes","models/citizen_clothes/shoes/flatshoes/textures/flat_shoes.clothing","LeftFoot, RightFoot","","","Shoes"),
  new("Football Shoes","Shoes","Shoes","models/citizen_clothes/shoes/football_shoes/football_shoes.clothing","LeftFoot, RightFoot","","Feet","Trainers"),
  new("Slippers","Shoes","Shoes","models/citizen_clothes/shoes/slippers/slippers.clothing","LeftFoot, RightFoot","","","Slippers"),
  new("Blue Sneakers","Shoes","Shoes","models/citizen_clothes/shoes/sneakers/blue_sneakers.clothing","LeftFoot, RightFoot","","Feet","Shoes"),
  new("Sneakers","Shoes","Shoes","models/citizen_clothes/shoes/sneakers/sneakers.clothing","LeftFoot, RightFoot","","Feet","Shoes"),
  new("Trainers","Shoes","Shoes","models/citizen_clothes/shoes/trainers/trainers.clothing","LeftFoot, RightFoot","","Feet","Trainers"),
  new("Boxing Shorts","Bottoms","Bottoms","models/citizen_clothes/shorts/boxing_shorts/boxing_shorts.clothing","Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","Legs","Shorts"),
  new("Boxing Shorts Blue","Bottoms","Bottoms","models/citizen_clothes/shorts/boxing_shorts/boxing_shorts_blue.clothing","Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","Legs","Shorts"),
  new("Boxing Shorts Red","Bottoms","Bottoms","models/citizen_clothes/shorts/boxing_shorts/boxing_shorts_red.clothing","Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","Legs","Shorts"),
  new("Football Shorts","Bottoms","Bottoms","models/citizen_clothes/shorts/football_shorts/football_shorts.clothing","Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","Legs","Shorts"),
  new("Shorts","Bottoms","Bottoms","models/citizen_clothes/shorts/shorts/shorts.clothing","Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","Legs","Shorts"),
  new("Summer Shorts","Bottoms","Bottoms","models/citizen_clothes/shorts/summer_shorts/summer shorts.clothing","LeftThigh, RightThigh","","Legs","Shorts"),
  new("Bin Man Trousers","Bottoms","Bottoms","models/citizen_clothes/trousers/cargopants/binman_trousers.clothing","Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","Legs","Trousers"),
  new("Brown Cargo Pants","Bottoms","Bottoms","models/citizen_clothes/trousers/cargopants/brown_cargo_pants.clothing","Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","Legs","Trousers"),
  new("Army Cargo Pants","Bottoms","Bottoms","models/citizen_clothes/trousers/cargopants/cargo_pants_army.clothing","Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","Legs","Trousers"),
  new("Jeans","Bottoms","Bottoms","models/citizen_clothes/trousers/jeans/jeans.clothing","Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","Legs","Jeans"),
  new("Black Jeans","Bottoms","Bottoms","models/citizen_clothes/trousers/jeans/jeans_black.clothing","Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","Legs","Jeans"),
  new("Loose Jeans","Bottoms","Bottoms","models/citizen_clothes/trousers/loosejeans/loose_jeans.clothing","Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","Legs","Jeans"),
  new("Loose Trousers","Bottoms","Bottoms","models/citizen_clothes/trousers/loosetrousers/loose_trousers.clothing","Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","Legs","Trousers"),
  new("Smart Trousers","Bottoms","Bottoms","models/citizen_clothes/trousers/smarttrousers/trousers.smart.clothing","Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","Legs","Trousers"),
  new("Trackie Bottoms","Bottoms","Bottoms","models/citizen_clothes/trousers/trackiebottoms/trackie_bottoms.clothing","Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","Legs","Trousers"),
  new("Black Trackie Bottoms","Bottoms","Bottoms","models/citizen_clothes/trousers/trackiebottoms/trackie_bottoms_black.clothing","Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin","","Legs","Trousers"),
  new("Chef's Apron","Outerwear","Outerwear","models/citizen_clothes/vest/chefs_apron/chefs_apron.clothing","","LeftArm, RightArm","","Vest"),
  new("Tactical Vest","Outerwear","Outerwear","models/citizen_clothes/vest/tactical_vest/models/tactical_vest.clothing","","LeftArm, RightArm","","Vest"),
  new("Army Vest","Outerwear","Outerwear","models/citizen_clothes/vest/tactical_vest/models/tactical_vest_army.clothing","LeftArm, RightArm","","","Vest"),
  new("Modern Watch","Accessories","watch","models/citizen_clothes/watch/modern_watch/modern_watch.clothing","LeftWrist","","","WristWatch"),
  new("Black Modern Watch","Accessories","watch","models/citizen_clothes/watch/modern_watch/modern_watch_black.clothing","LeftWrist","","","WristWatch")
 };
}
vendetta.character_editor / Experiments/FaceTransferPreview.cs
Game game
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();
	}
}
Debug: View Raw JSON Response
{
    "TotalCount": 53,
    "Files": [
        {
            "Ident": "vendetta.character_editor",
            "Path": "CharacterKit/CharacterAppearance.cs",
            "FileName": "CharacterAppearance.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 410954,
            "IsPrivate": false,
            "Code": "using System;\r\n\r\nnamespace CharacterEditor;\r\npublic static class CharacterAppearance\r\n{\r\n\tpublic static string ValidateForApply(CharacterAppearanceData data)\r\n\t{\r\n\t\tif (CharacterAppearanceData.Validate(data)is string error)\r\n\t\t\treturn error;\r\n\t\tvar skin = ClothingResources.Get(UnifiedHuman.PresentationSkin(data));\r\n\t\tif (skin?.HasHumanSkin != true)\r\n\t\t\treturn \"The selected native skin resource is unavailable.\";\r\n\t\tvar body = Model.Load(data.BaseShape is null ? HumanMarkingModels.ModelPath(skin.HumanSkinModel, data.Makeup) : UnifiedHuman.ModelPath);\r\n\t\tif (!body.IsValid || body.IsError)\r\n\t\t\treturn \"The selected native human model is unavailable.\";\r\n\t\tvar clothes = new List<Clothing>\r\n\t\t{\r\n\t\t\tskin\r\n\t\t};\r\n\t\tforeach (var path in new[]\r\n\t\t{\r\n\t\t\tdata.HairPath,\r\n\t\t\tdata.FacialHairPath\r\n\t\t}.Where(x => !string.IsNullOrEmpty(x)))\r\n\t\t{\r\n\t\t\tvar clothing = ClothingResources.Get(path);\r\n\t\t\tif (clothing is null)\r\n\t\t\t\treturn \"A selected native hairstyle resource is unavailable.\";\r\n\t\t\tif (path == data.HairPath && !ClothingResources.IsHair(clothing) || path == data.FacialHairPath && !ClothingResources.IsFacialHair(clothing))\r\n\t\t\t\treturn \"The selected item does not belong in this hair slot.\";\r\n\t\t\tclothes.Add(clothing);\r\n\t\t}\r\n\r\n\t\tvar tags = new TagSet(skin.HumanSkinTags.TryGetAll());\r\n\t\ttags.Add(\"human\");\r\n\t\tforeach (var clothing in clothes.Skip(1))\r\n\t\t{\r\n\t\t\tvar path = clothing.GetModel(clothes, tags);\r\n\t\t\tif (string.IsNullOrEmpty(path))\r\n\t\t\t\treturn \"A selected hairstyle does not support this native human body.\";\r\n\t\t\tvar model = Model.Load(path);\r\n\t\t\tif (!model.IsValid || model.IsError)\r\n\t\t\t\treturn \"A selected native hairstyle model is unavailable.\";\r\n\t\t}\r\n\r\n\t\treturn null;\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "vendetta.character_editor",
            "Path": "CharacterKit/CharacterAppearanceData.cs",
            "FileName": "CharacterAppearanceData.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 410954,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.Text.Json.Serialization;\r\n\r\nnamespace CharacterEditor;\r\n/// <summary>Portable appearance values. No live models, components or engine color converters are required to read a save.</summary>\r\npublic sealed class CharacterAppearanceData\r\n{\r\n\tpublic int Version { get; set; } = 1;\r\n\tpublic CharacterBaseShape BaseShape { get; set; }\r\n\tpublic string[] ClothingPaths { get; set; }\r\n\tpublic string VoiceId { get; set; } = \"man-adult\";\r\n\tpublic string SkinPath { get; set; } = \"models/citizen_clothes/body/human03.clothing\";\r\n\tpublic string HairPath { get; set; } = \"models/citizen_clothes/hair/hair_shortscruffy/hair_shortscruffy_brown.clothing\";\r\n\tpublic string FacialHairPath { get; set; } = \"\";\r\n\tpublic float HairColorR { get; set; } = 0.22f;\r\n\tpublic float HairColorG { get; set; } = 0.13f;\r\n\tpublic float HairColorB { get; set; } = 0.08f;\r\n\tpublic float SkinColorR { get; set; } = 1;\r\n\tpublic float SkinColorG { get; set; } = 1;\r\n\tpublic float SkinColorB { get; set; } = 1;\r\n\tpublic bool CustomEyeColor { get; set; }\r\n\tpublic float EyeColorR { get; set; } = .24f;\r\n\tpublic float EyeColorG { get; set; } = .12f;\r\n\tpublic float EyeColorB { get; set; } = .055f;\r\n\r\n\t[JsonIgnore]\r\n\tpublic Color EyeColor\r\n\t{\r\n\t\tget => new(EyeColorR, EyeColorG, EyeColorB);\r\n\t\tset\r\n\t\t{\r\n\t\t\tEyeColorR = value.r;\r\n\t\t\tEyeColorG = value.g;\r\n\t\t\tEyeColorB = value.b;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic bool NormalizeSkinTone { get; set; }\r\n\tpublic CharacterMakeupData Makeup { get; set; } = new();\r\n\tpublic float NeckSize { get; set; } = 0.5f;\r\n\tpublic float TrapeziusSize { get; set; } = 0.5f;\r\n\tpublic float CollarboneHeight { get; set; } = 0.5f;\r\n\t// Zero leaves the existing abdomen unchanged, including in older saves.\r\n\tpublic float BellySize { get; set; } = 0;\r\n\tpublic float WaistSize { get; set; } = 0.5f;\r\n\tpublic float ChestSize { get; set; } = 0.5f;\r\n\tpublic float HeadShape { get; set; } = 0.5f;\r\n\tpublic float NoseSize { get; set; } = 0.5f;\r\n\tpublic float ChinSize { get; set; } = 0.5f;\r\n\tpublic float NoseWidth { get; set; } = 0.5f;\r\n\tpublic float ChinWidth { get; set; } = 0.5f;\r\n\tpublic float Fitness { get; set; } = 0.5f;\r\n\tpublic float JawWidth { get; set; } = 0.5f;\r\n\tpublic float NostrilWidth { get; set; } = 0.5f;\r\n\tpublic float ForeheadWidth { get; set; } = 0.5f;\r\n\tpublic float CheekboneWidth { get; set; } = 0.5f;\r\n\tpublic float EarSize { get; set; } = 0.5f;\r\n\tpublic float EyeSize { get; set; } = 0.5f;\r\n\tpublic float CheekFullness { get; set; } = 0.5f;\r\n\tpublic float NostrilFullness { get; set; } = 0.5f;\r\n\tpublic float NoseHeight { get; set; } = 0.5f;\r\n\tpublic float ChinHeight { get; set; } = 0.5f;\r\n\tpublic float SubmentalFullness { get; set; } = 0.5f;\r\n\tpublic float BrowProminence { get; set; } = 0.5f;\r\n\tpublic float BodyFeatureSize { get; set; } = 0.5f;\r\n\tpublic float ButtSize { get; set; } = 0.5f;\r\n\tpublic float FootSize { get; set; } = 0.5f;\r\n\tpublic float PhiltrumWidth { get; set; } = 0.5f;\r\n\tpublic float ColumellaWidth { get; set; } = 0.5f;\r\n\tpublic float MouthWidth { get; set; } = 0.5f;\r\n\tpublic float MouthProjection { get; set; } = 0.5f;\r\n\tpublic float UpperLipFullness { get; set; } = 0.5f;\r\n\tpublic float LowerLipFullness { get; set; } = 0.5f;\r\n\t// Normal ears are the minimum, including when reading an older save.\r\n\tpublic float EarTaper { get; set; } = 0;\r\n\r\n\t[JsonIgnore]\r\n\tpublic Color HairColor\r\n\t{\r\n\t\tget => new(HairColorR, HairColorG, HairColorB);\r\n\t\tset\r\n\t\t{\r\n\t\t\tHairColorR = value.r;\r\n\t\t\tHairColorG = value.g;\r\n\t\t\tHairColorB = value.b;\r\n\t\t}\r\n\t}\r\n\r\n\t[JsonIgnore]\r\n\tpublic Color SkinColor\r\n\t{\r\n\t\tget => new(SkinColorR, SkinColorG, SkinColorB);\r\n\t\tset\r\n\t\t{\r\n\t\t\tSkinColorR = value.r;\r\n\t\t\tSkinColorG = value.g;\r\n\t\t\tSkinColorB = value.b;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic CharacterAppearanceData DeepCopy() => new()\r\n\t{\r\n\t\tVersion = Version,\r\n\t\tBaseShape = BaseShape?.DeepCopy(),\r\n\t\tClothingPaths = ClothingPaths?.ToArray(),\r\n\t\tVoiceId = VoiceId,\r\n\t\tSkinPath = SkinPath,\r\n\t\tHairPath = HairPath,\r\n\t\tFacialHairPath = FacialHairPath,\r\n\t\tHairColorR = HairColorR,\r\n\t\tHairColorG = HairColorG,\r\n\t\tHairColorB = HairColorB,\r\n\t\tSkinColorR = SkinColorR,\r\n\t\tSkinColorG = SkinColorG,\r\n\t\tSkinColorB = SkinColorB,\r\n\t\tNormalizeSkinTone = NormalizeSkinTone,\r\n\t\tCustomEyeColor = CustomEyeColor,\r\n\t\tEyeColorR = EyeColorR,\r\n\t\tEyeColorG = EyeColorG,\r\n\t\tEyeColorB = EyeColorB,\r\n\t\tMakeup = Makeup?.DeepCopy(),\r\n\t\tNeckSize = NeckSize,\r\n\t\tTrapeziusSize = TrapeziusSize,\r\n\t\tCollarboneHeight = CollarboneHeight,\r\n\t\tBellySize = BellySize,\r\n\t\tWaistSize = WaistSize,\r\n\t\tChestSize = ChestSize,\r\n\t\tHeadShape = HeadShape,\r\n\t\tNoseSize = NoseSize,\r\n\t\tChinSize = ChinSize,\r\n\t\tNoseWidth = NoseWidth,\r\n\t\tChinWidth = ChinWidth,\r\n\t\tFitness = Fitness,\r\n\t\tJawWidth = JawWidth,\r\n\t\tNostrilWidth = NostrilWidth,\r\n\t\tForeheadWidth = ForeheadWidth,\r\n\t\tCheekboneWidth = CheekboneWidth,\r\n\t\tEarSize = EarSize,\r\n\t\tEyeSize = EyeSize,\r\n\t\tCheekFullness = CheekFullness,\r\n\t\tNostrilFullness = NostrilFullness,\r\n\t\tNoseHeight = NoseHeight,\r\n\t\tChinHeight = ChinHeight,\r\n\t\tSubmentalFullness = SubmentalFullness,\r\n\t\tBrowProminence = BrowProminence,\r\n\t\tBodyFeatureSize = BodyFeatureSize,\r\n\t\tButtSize = ButtSize,\r\n\t\tFootSize = FootSize,\r\n\t\tEarTaper = EarTaper,\r\n\t\tPhiltrumWidth = PhiltrumWidth,\r\n\t\tColumellaWidth = ColumellaWidth,\r\n\t\tMouthWidth = MouthWidth,\r\n\t\tMouthProjection = MouthProjection,\r\n\t\tUpperLipFullness = UpperLipFullness,\r\n\t\tLowerLipFullness = LowerLipFullness\r\n\t};\r\n\tpublic static string CompositionKey(CharacterAppearanceData data) => data is null ? \"\" : $\"{(data.BaseShape is null ? data.SkinPath : UnifiedHuman.ModelPath + UnifiedHuman.PresentationSkin(data))}|{data.BaseShape?.ClothingVariant}|{data.HairPath}|{data.FacialHairPath}|{HumanMarkingModels.NeedsArmRegions(data.Makeup)}|{string.Join(';', WardrobeCatalog.Paths(data))}\";\r\n\t/// <summary>Cold-safe validation used by save lists and menu preflight. Resources are resolved separately when applied.</summary>\r\n\tpublic static string Validate(CharacterAppearanceData data)\r\n\t{\r\n\t\tif (data is null)\r\n\t\t\treturn \"Choose a character appearance.\";\r\n\t\tif (data.Version is not (1 or 2))\r\n\t\t\treturn \"This appearance version is unsupported.\";\r\n\t\tif (data.BaseShape is not null && (data.Version != 2 || !data.BaseShape.Valid))\r\n\t\t\treturn \"Base shape requires version 2, valid face influences and custom face data, and body controls between zero and one.\";\r\n\t\tif (data.Version == 1 && data.ClothingPaths is not null)\r\n\t\t\treturn \"Saved clothing requires appearance version 2.\";\r\n\t\tif (WardrobeCatalog.Validate(data.ClothingPaths)is string wardrobeError)\r\n\t\t\treturn wardrobeError;\r\n\t\tif (!VoiceIds.IsSupported(data.VoiceId))\r\n\t\t\treturn \"Choose a supported character voice.\";\r\n\t\tif (data.Makeup?.Valid != true)\r\n\t\t\treturn \"Makeup colors and strength must be finite values between zero and one, with a supported tattoo style.\";\r\n\t\tif (data.HairPath is null || data.FacialHairPath is null)\r\n\t\t\treturn \"Use an empty path for no hair or facial hair.\";\r\n\t\tvar skin = AppearanceCatalog.Skins.FirstOrDefault(x => string.Equals(x.ResourcePath, data.SkinPath, StringComparison.OrdinalIgnoreCase));\r\n\t\tif (skin is null)\r\n\t\t\treturn \"Choose a supported native human face and skin.\";\r\n\t\tif (data.HairPath.Length > 0 && !ClothingResources.ValidReference(data.HairPath))\r\n\t\t\treturn \"Choose a supported native hairstyle.\";\r\n\t\tvar facial = AppearanceCatalog.FacialHair.FirstOrDefault(x => string.Equals(x.ResourcePath, data.FacialHairPath ?? \"\", StringComparison.OrdinalIgnoreCase));\r\n\t\tif ((data.FacialHairPath.Length > 0 && !ClothingResources.ValidReference(data.FacialHairPath)) || (facial is not null && data.BaseShape is null && !string.IsNullOrEmpty(facial.BodyType) && facial.BodyType != skin.BodyType))\r\n\t\t\treturn \"That facial hair does not fit the selected body.\";\r\n\t\tif (new[]\r\n\t\t{\r\n\t\t\tdata.HairColorR,\r\n\t\t\tdata.HairColorG,\r\n\t\t\tdata.HairColorB\r\n\t\t}.Any(x => !float.IsFinite(x) || x < 0 || x > 1))\r\n\t\t\treturn \"Hair color must contain finite values between zero and one.\";\r\n\t\tif (new[]\r\n\t\t{\r\n\t\t\tdata.SkinColorR,\r\n\t\t\tdata.SkinColorG,\r\n\t\t\tdata.SkinColorB\r\n\t\t}.Any(x => !float.IsFinite(x) || x < 0 || x > 1))\r\n\t\t\treturn \"Skin color must contain finite values between zero and one.\";\r\n\t\tif (new[]\r\n\t\t{\r\n\t\t\tdata.EyeColorR,\r\n\t\t\tdata.EyeColorG,\r\n\t\t\tdata.EyeColorB\r\n\t\t}.Any(x => !float.IsFinite(x) || x < 0 || x > 1))\r\n\t\t\treturn \"Eye color must contain finite values between zero and one.\";\r\n\t\tif (AppearanceCatalog.Sliders.Any(x => !float.IsFinite(x.Get(data)) || x.Get(data) < x.Min || x.Get(data) > x.Max))\r\n\t\t\treturn \"Appearance sliders must contain finite values between zero and one.\";\r\n\t\treturn null;\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "vendetta.character_editor",
            "Path": "CharacterKit/UnifiedWardrobe.cs",
            "FileName": "UnifiedWardrobe.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 410954,
            "IsPrivate": false,
            "Code": "namespace CharacterEditor;\r\n/// <summary>Authored human cuts fitted automatically to both unified body endpoints.</summary>\r\npublic static partial class UnifiedWardrobe\r\n{\r\n\t/// <summary>Head replacements can retain skin or deliberately replace the entire neck.</summary>\r\n\tpublic static bool RetainsHumanNeck(Clothing item)\r\n\t{\r\n\t\tif (string.IsNullOrEmpty(item.HumanSkinModel)) return true;\r\n\t\tvar skin = Model.Load(item.HumanSkinModel);\r\n\t\t// Use the replacement's native skin presentation. A transparent/non-skin\r\n\t\t// head material must not be replaced with the unified human's skin neck.\r\n\t\treturn skin.MeshInfo.Meshes.Where(m => m.Name == \"head_01\")\r\n\t\t\t.SelectMany(m => m.DrawCalls).Any(d => d.Material?.ShaderName == \"shaders/skin.shader\");\r\n\t}\r\n\r\n\tpublic static string ModelPath(Clothing item, IEnumerable<Clothing> selected, TagSet tags, CharacterAppearanceData data)\r\n\t{\r\n\t\tvar native = item.GetModel(selected.ToArray(), tags);\r\n\t\tif (data.BaseShape is null)\r\n\t\t\treturn native;\r\n\t\t// Preserve conditional model overrides selected by native clothing metadata.\r\n\t\tif (native != item.Model && native != item.HumanAltModel && native != item.HumanAltFemaleModel)\r\n\t\t\treturn native;\r\n\t\t// Explicit preference selects only the authored cut; the morph still uses\r\n\t\t// the actual body blend. Single-variant items retain their usual fallback.\r\n\t\tif (data.BaseShape.ClothingVariant is not null && !string.IsNullOrEmpty(item.HumanAltModel) && !string.IsNullOrEmpty(item.HumanAltFemaleModel))\r\n\t\t{\r\n\t\t\tvar choice = data.BaseShape.ClothingVariant == \"male\" ? item.HumanAltModel : item.HumanAltFemaleModel;\r\n\t\t\treturn choice;\r\n\t\t}\r\n\r\n\t\t// A single authored human variant is usable across the full body range.\r\n\t\tvar preferred = data.BaseShape.MaleBody >= .5f ? item.HumanAltModel : item.HumanAltFemaleModel;\r\n\t\tif (string.IsNullOrEmpty(preferred))\r\n\t\t\tpreferred = string.IsNullOrEmpty(item.HumanAltModel) ? item.HumanAltFemaleModel : item.HumanAltModel;\r\n\t\treturn !string.IsNullOrEmpty(preferred) ? preferred : native;\r\n\t}\r\n\r\n\tpublic static Model Resolve(Clothing item, IEnumerable<Clothing> selected, TagSet tags, CharacterAppearanceData data)\r\n\t{\r\n\t\tvar path = ModelPath(item, selected, tags, data);\r\n\t\tvar native = Model.Load(path);\r\n\t\tif (data.BaseShape is null || string.IsNullOrEmpty(path) || (string.IsNullOrEmpty(item.HumanAltModel) && string.IsNullOrEmpty(item.HumanAltFemaleModel)))\r\n\t\t\treturn native;\r\n\t\tif (item.Category.ToString().StartsWith(\"FacialHair\") || item.Category.ToString().StartsWith(\"Hair\"))\r\n\t\t\treturn native;\r\n\t\tbool female = path == item.HumanAltFemaleModel && path != item.HumanAltModel;\r\n\t\tif (path != item.HumanAltModel && path != item.HumanAltFemaleModel)\r\n\t\t\tfemale = tags.Has(\"female\");\r\n\t\treturn AutomaticWardrobeFit.Fit(native, female, fitFace: (item.HideBody & Clothing.BodyGroups.Head) == 0);\r\n\t}\r\n\r\n\tpublic static void Apply(SkinnedModelRenderer renderer, CharacterAppearanceData data)\r\n\t{\r\n\t\tif (data.BaseShape is not null && AutomaticWardrobeFit.IsFitted(renderer.Model))\r\n\t\t\trenderer.Morphs.Set(\"ce_body_female\", 1 - data.BaseShape.MaleBody);\r\n\t}\r\n}// Keep hair outside body fitting.\r\n\r\n"
        },
        {
            "Ident": "vendetta.character_editor",
            "Path": "CharacterKit/AppearanceCatalog.cs",
            "FileName": "AppearanceCatalog.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 410954,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.Threading.Tasks;\r\n\r\nnamespace CharacterEditor;\r\npublic sealed record AppearanceOption(string Id, string Label, string ResourcePath, string BodyType = \"\", string Icon = \"\");\r\npublic sealed record AppearancePreset(string Id, string Label, CharacterAppearanceData Appearance);\r\npublic sealed record AppearanceColorOption(string Id, string Label, Color Color, bool NormalizeSkinTone = false);\r\npublic sealed class AppearanceSlider\r\n{\r\n\tpublic string Id { get; }\r\n\tpublic string Label { get; }\r\n\tpublic string Group { get; }\r\n\tpublic float Min => 0;\r\n\tpublic float Max => 1;\r\n\tpublic float Neutral => Id is \"eartaper\" or \"belly\" ? 0 : 0.5f;\r\n\r\n\tpublic string LabelFor(CharacterAppearanceData data) => Id == \"bodyfeature\" ? (AppearanceCatalog.IsFemale(data) ? \"Breast size\" : \"Bulge size\") : Label;\r\n\tpublic AppearanceSlider(string id, string label, string group)\r\n\t{\r\n\t\tId = id;\r\n\t\tLabel = label;\r\n\t\tGroup = group;\r\n\t}\r\n\r\n\tpublic float Get(CharacterAppearanceData data) => Id switch\r\n\t{\r\n\t\t\"neck\" => data.NeckSize,\r\n\t\t\"trapezius\" => data.TrapeziusSize,\r\n\t\t\"collarbone\" => data.CollarboneHeight,\r\n\t\t\"belly\" => data.BellySize,\r\n\t\t\"waist\" => data.WaistSize,\r\n\t\t\"chest\" => data.ChestSize,\r\n\t\t\"fitness\" => data.Fitness,\r\n\t\t\"head\" => data.HeadShape,\r\n\t\t\"nose\" => data.NoseSize,\r\n\t\t\"nosewidth\" => data.NoseWidth,\r\n\t\t\"chin\" => data.ChinSize,\r\n\t\t\"chinwidth\" => data.ChinWidth,\r\n\t\t\"jaw\" => data.JawWidth,\r\n\t\t\"nostrils\" => data.NostrilWidth,\r\n\t\t\"forehead\" => data.ForeheadWidth,\r\n\t\t\"cheekbones\" => data.CheekboneWidth,\r\n\t\t\"ears\" => data.EarSize,\r\n\t\t\"eyes\" => data.EyeSize,\r\n\t\t\"cheeks\" => data.CheekFullness,\r\n\t\t\"nostrilfullness\" => data.NostrilFullness,\r\n\t\t\"noseheight\" => data.NoseHeight,\r\n\t\t\"chinheight\" => data.ChinHeight,\r\n\t\t\"submental\" => data.SubmentalFullness,\r\n\t\t\"brow\" => data.BrowProminence,\r\n\t\t\"bodyfeature\" => data.BodyFeatureSize,\r\n\t\t\"butt\" => data.ButtSize,\r\n\t\t\"feet\" => data.FootSize,\r\n\t\t\"eartaper\" => data.EarTaper,\r\n\t\t\"philtrum\" => data.PhiltrumWidth,\r\n\t\t\"columella\" => data.ColumellaWidth,\r\n\t\t\"mouthwidth\" => data.MouthWidth,\r\n\t\t\"mouthprojection\" => data.MouthProjection,\r\n\t\t\"upperlip\" => data.UpperLipFullness,\r\n\t\t\"lowerlip\" => data.LowerLipFullness,\r\n\t\t_ => Neutral\r\n\t};\r\n\tpublic void Set(CharacterAppearanceData data, float value)\r\n\t{\r\n\t\tif (data is null || !float.IsFinite(value))\r\n\t\t\treturn;\r\n\t\tvalue = Math.Clamp(value, Min, Max);\r\n\t\t// Store data rather than delegates in the catalog so native hotload can preserve these selectors.\r\n\t\tswitch (Id)\r\n\t\t{\r\n\t\t\tcase \"neck\":\r\n\t\t\t\tdata.NeckSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"trapezius\":\r\n\t\t\t\tdata.TrapeziusSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"collarbone\":\r\n\t\t\t\tdata.CollarboneHeight = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"belly\":\r\n\t\t\t\tdata.BellySize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"waist\":\r\n\t\t\t\tdata.WaistSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"chest\":\r\n\t\t\t\tdata.ChestSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"fitness\":\r\n\t\t\t\tdata.Fitness = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"head\":\r\n\t\t\t\tdata.HeadShape = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"nose\":\r\n\t\t\t\tdata.NoseSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"nosewidth\":\r\n\t\t\t\tdata.NoseWidth = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"chin\":\r\n\t\t\t\tdata.ChinSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"chinwidth\":\r\n\t\t\t\tdata.ChinWidth = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"jaw\":\r\n\t\t\t\tdata.JawWidth = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"nostrils\":\r\n\t\t\t\tdata.NostrilWidth = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"forehead\":\r\n\t\t\t\tdata.ForeheadWidth = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"cheekbones\":\r\n\t\t\t\tdata.CheekboneWidth = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"ears\":\r\n\t\t\t\tdata.EarSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"eyes\":\r\n\t\t\t\tdata.EyeSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"cheeks\":\r\n\t\t\t\tdata.CheekFullness = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"nostrilfullness\":\r\n\t\t\t\tdata.NostrilFullness = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"noseheight\":\r\n\t\t\t\tdata.NoseHeight = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"chinheight\":\r\n\t\t\t\tdata.ChinHeight = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"submental\":\r\n\t\t\t\tdata.SubmentalFullness = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"brow\":\r\n\t\t\t\tdata.BrowProminence = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"bodyfeature\":\r\n\t\t\t\tdata.BodyFeatureSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"butt\":\r\n\t\t\t\tdata.ButtSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"feet\":\r\n\t\t\t\tdata.FootSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"eartaper\":\r\n\t\t\t\tdata.EarTaper = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"philtrum\":\r\n\t\t\t\tdata.PhiltrumWidth = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"columella\":\r\n\t\t\t\tdata.ColumellaWidth = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"mouthwidth\":\r\n\t\t\t\tdata.MouthWidth = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"mouthprojection\":\r\n\t\t\t\tdata.MouthProjection = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"upperlip\":\r\n\t\t\t\tdata.UpperLipFullness = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"lowerlip\":\r\n\t\t\t\tdata.LowerLipFullness = value;\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n}\r\n\r\n/// <summary>Curated installed, non-inventory native assets. IDs and paths are stable; no cloud discovery or external download is needed.</summary>\r\npublic static class AppearanceCatalog\r\n{\r\n\tstatic readonly Dictionary<string, Task> resourceLoads = new(StringComparer.OrdinalIgnoreCase);\r\n\tpublic static bool Loading => resourceLoads.Values.Any(x => x is not null && !x.IsCompleted);\r\n\tpublic static string Error { get; private set; }\r\n\tpublic static int Revision { get; private set; }\r\n\r\n\tconst string Root = \"models/citizen_clothes/\";\r\n\tstatic AppearanceOption Skin(string number, string label, string body) => new(\"human\" + number, label, Root + \"body/human\" + number + \".clothing\", body, Root + \"body/human\" + number + \".clothing.png\");\r\n\tstatic AppearanceOption Style(string id, string label, string relative, string body = \"\") => new(id, label, Root + \"hair/\" + relative + \".clothing\", body, Root + \"hair/\" + relative + \".clothing.png\");\r\n\tpublic static IReadOnlyList<AppearanceOption> Skins { get; } = Array.AsReadOnly(new[] { Skin(\"01\", \"Face 1 \u00b7 dark \u00b7 male\", \"male\"), Skin(\"02\", \"Face 2 \u00b7 dark \u00b7 female\", \"female\"), Skin(\"03\", \"Face 3 \u00b7 light \u00b7 male\", \"male\"), Skin(\"04\", \"Face 4 \u00b7 light \u00b7 female\", \"female\"), Skin(\"05\", \"Face 5 \u00b7 light \u00b7 male\", \"male\"), Skin(\"06\", \"Face 6 \u00b7 dark \u00b7 male\", \"male\"), Skin(\"07\", \"Face 7 \u00b7 light \u00b7 male\", \"male\") });\r\n\tpublic static IReadOnlyList<AppearanceOption> Hair { get; } = Array.AsReadOnly(new[] { new AppearanceOption(\"none\", \"No hair\", \"\"), Style(\"scruffy\", \"Short scruffy\", \"hair_shortscruffy/hair_shortscruffy_brown\"), Style(\"short_braids\", \"Short braids\", \"hair_short_braids/hair_short_braids\"), Style(\"short_dreads\", \"Short dreadlocks\", \"hair_short_dreadlocks/short_dreadlocks\"), Style(\"bob\", \"Bob cut\", \"hair_bobcut/hair_bobcut\"), Style(\"long_bob\", \"Long bob\", \"hair_longbob/hair_longbob\"), Style(\"messy_bun\", \"Messy bun\", \"hair_messy_bun/hair_messy_bun\"), Style(\"ponytail\", \"Tight ponytail\", \"hair_tight_ponytail/hair_tight_ponytail_brown\"), Style(\"long_curly\", \"Long curly\", \"hair_longcurly/hair_longcurly_brown\"), Style(\"long_bangs\", \"Long bangs\", \"hair_long_bangs/hair_long_bangs\"), Style(\"long_dreads\", \"Long dreadlocks\", \"hair_long_dreadlocks/hair_long_dreadlocks\"), Style(\"tied_braids\", \"Tied braids\", \"hair_tied_braids/hair_tied_braids\"), Style(\"tied_dreads\", \"Tied dreadlocks\", \"hair_tied_dreads/hair_tied_dreads\"), Style(\"slicked\", \"Slicked back\", \"hair_slickedback/hair_slickedback\"), Style(\"balding\", \"Balding\", \"hair_balding/hair_baldingbrown\"), Style(\"thinning\", \"Thinning short\", \"hair_thinning_short/thinning_short\"), Style(\"pigtails\", \"Pigtails\", \"hair_pigtails/hair_pigtails\"), Style(\"long_brown\", \"Long hair\", \"hair_longbrown/Models/hair_longbrown\"), Style(\"loose\", \"Loose hair\", \"hair_looseblonde/hair.loose.grey\"), Style(\"slicked_balding\", \"Slicked back balding\", \"hair_slicked_back_balding/hair_slicked_back_balding\") });\r\n\tpublic static IReadOnlyList<AppearanceOption> FacialHair { get; } = Array.AsReadOnly(new[] { new AppearanceOption(\"none\", \"No facial hair\", \"\"), Style(\"patchy\", \"Patchy beard\", \"patchy_beard/patchy_beard\", \"male\"), Style(\"medium\", \"Medium beard\", \"medium_beard/medium_beard\", \"male\"), Style(\"goatee\", \"Goatee\", \"goatee/goatee\", \"male\"), Style(\"moustache\", \"Moustache\", \"moustache/moustache\", \"male\"), Style(\"handlebar\", \"Handlebar moustache\", \"handlebar_moustache/handlebar_moustache\", \"male\"), Style(\"chops\", \"Sideburns\", \"mutton_chops/mutton_chops\", \"male\"), Style(\"braided\", \"Braided beard\", \"braided_long_beard/braided_long_beard\", \"male\"), Style(\"decorated\", \"Decorated beard\", \"decorated_beard/decorated_beard\", \"male\") });\r\n\tpublic static IReadOnlyList<AppearanceColorOption> Colors { get; } = Array.AsReadOnly(new[] { new AppearanceColorOption(\"black\", \"Black\", new Color(0.045f, 0.035f, 0.028f)), new AppearanceColorOption(\"brown\", \"Brown\", new Color(0.22f, 0.13f, 0.08f)), new AppearanceColorOption(\"auburn\", \"Auburn\", new Color(0.48f, 0.17f, 0.07f)), new AppearanceColorOption(\"blonde\", \"Blonde\", new Color(0.74f, 0.57f, 0.31f)), new AppearanceColorOption(\"silver\", \"Silver\", new Color(0.66f, 0.67f, 0.7f)), new AppearanceColorOption(\"white\", \"White\", new Color(0.92f, 0.89f, 0.82f)) });\r\n\r\n\tpublic const int ShapeControlCount = 34;\r\n\tpublic static bool IsFemale(CharacterAppearanceData data) => data?.BaseShape is not null ? data.BaseShape.MaleBody < .5f : Skins.FirstOrDefault(x => string.Equals(x.ResourcePath, data?.SkinPath, StringComparison.OrdinalIgnoreCase))?.BodyType == \"female\";\r\n\tstatic IReadOnlyList<AppearanceColorOption> humanSkinColors;\r\n\tpublic static IReadOnlyList<AppearanceColorOption> HumanSkinColors\r\n\t{\r\n\t\tget\r\n\t\t{\r\n\t\t\tif (humanSkinColors?.Count != 7 || !humanSkinColors[1].NormalizeSkinTone)\r\n\t\t\t\thumanSkinColors = CreateHumanSkinColors();\r\n\t\t\treturn humanSkinColors;\r\n\t\t}\r\n\t}\r\n\r\n\tstatic IReadOnlyList<AppearanceColorOption> CreateHumanSkinColors() => Array.AsReadOnly(new[] { new AppearanceColorOption(\"native\", \"Native\", Color.White), new AppearanceColorOption(\"fair\", \"Fair\", Color.White, true), new AppearanceColorOption(\"rose\", \"Rose\", new Color(1, 0.96f, 0.96f), true), new AppearanceColorOption(\"golden\", \"Golden\", new Color(1, 0.96f, 0.88f), true), new AppearanceColorOption(\"olive\", \"Olive\", new Color(0.91f, 0.88f, 0.77f), true), new AppearanceColorOption(\"tan\", \"Tan\", new Color(0.72f, 0.6f, 0.5f), true), new AppearanceColorOption(\"umber\", \"Umber\", new Color(0.42f, 0.3f, 0.23f), true) });\r\n\tstatic IReadOnlyList<AppearanceColorOption> fantasySkinColors;\r\n\tpublic static IReadOnlyList<AppearanceColorOption> FantasySkinColors\r\n\t{\r\n\t\tget\r\n\t\t{\r\n\t\t\tif (fantasySkinColors?.Count != 6 || fantasySkinColors.Any(x => !x.NormalizeSkinTone))\r\n\t\t\t\tfantasySkinColors = CreateFantasySkinColors();\r\n\t\t\treturn fantasySkinColors;\r\n\t\t}\r\n\t}\r\n\r\n\tstatic IReadOnlyList<AppearanceColorOption> CreateFantasySkinColors() => Array.AsReadOnly(new[] { new AppearanceColorOption(\"blue\", \"Blue\", new Color(0.18f, 0.4f, 1), true), new AppearanceColorOption(\"purple\", \"Purple\", new Color(0.6f, 0.25f, 1), true), new AppearanceColorOption(\"green\", \"Green\", new Color(0.3f, 0.85f, 0.4f), true), new AppearanceColorOption(\"red\", \"Crimson\", new Color(1, 0.25f, 0.22f), true), new AppearanceColorOption(\"teal\", \"Teal\", new Color(0.18f, 0.85f, 0.9f), true), new AppearanceColorOption(\"grey\", \"Slate\", new Color(0.5f, 0.6f, 0.74f), true) });\r\n\t// Native hotload can retain the previous static list when the catalog grows.\r\n\t// Build the supported immutable catalog lazily so new controls are available\r\n\t// in an already running editor as well as a cold-start game.\r\n\tpublic static IReadOnlyList<AppearanceSlider> Sliders\r\n\t{\r\n\t\tget\r\n\t\t{\r\n\t\t\tif (contourControls?.Count != ShapeControlCount)\r\n\t\t\t\tcontourControls = CreateContourControls();\r\n\t\t\treturn contourControls;\r\n\t\t}\r\n\t}\r\n\r\n\tstatic IReadOnlyList<AppearanceSlider> contourControls;\r\n\tstatic IReadOnlyList<AppearanceSlider> CreateContourControls() => Array.AsReadOnly(new[] { new AppearanceSlider(\"neck\", \"Neck size\", \"Body\"), new AppearanceSlider(\"trapezius\", \"Trapezius\", \"Body\"), new AppearanceSlider(\"collarbone\", \"Collarbone height\", \"Body\"), new AppearanceSlider(\"belly\", \"Belly size\", \"Body\"), new AppearanceSlider(\"waist\", \"Waist size\", \"Body\"), new AppearanceSlider(\"chest\", \"Chest size\", \"Body\"), new AppearanceSlider(\"fitness\", \"Fitness\", \"Body\"), new AppearanceSlider(\"bodyfeature\", \"Body contour\", \"Body\"), new AppearanceSlider(\"butt\", \"Butt size\", \"Body\"), new AppearanceSlider(\"feet\", \"Foot size\", \"Body\"), new AppearanceSlider(\"head\", \"Head shape\", \"Face\"), new AppearanceSlider(\"forehead\", \"Forehead width\", \"Face\"), new AppearanceSlider(\"cheekbones\", \"Cheekbone width\", \"Face\"), new AppearanceSlider(\"cheeks\", \"Cheek fullness\", \"Face\"), new AppearanceSlider(\"jaw\", \"Jaw width\", \"Jaw & chin\"), new AppearanceSlider(\"chin\", \"Chin projection\", \"Jaw & chin\"), new AppearanceSlider(\"chinwidth\", \"Chin width\", \"Jaw & chin\"), new AppearanceSlider(\"chinheight\", \"Chin height\", \"Jaw & chin\"), new AppearanceSlider(\"submental\", \"Under-chin fullness\", \"Jaw & chin\"), new AppearanceSlider(\"nose\", \"Nose projection\", \"Nose\"), new AppearanceSlider(\"nosewidth\", \"Nose width\", \"Nose\"), new AppearanceSlider(\"nostrils\", \"Nostril width\", \"Nose\"), new AppearanceSlider(\"nostrilfullness\", \"Nostril-wing fullness\", \"Nose\"), new AppearanceSlider(\"noseheight\", \"Nose tip height\", \"Nose\"), new AppearanceSlider(\"columella\", \"Columella width\", \"Nose\"), new AppearanceSlider(\"philtrum\", \"Philtrum width\", \"Mouth\"), new AppearanceSlider(\"mouthwidth\", \"Mouth width\", \"Mouth\"), new AppearanceSlider(\"mouthprojection\", \"Mouth projection\", \"Mouth\"), new AppearanceSlider(\"upperlip\", \"Upper lip fullness\", \"Mouth\"), new AppearanceSlider(\"lowerlip\", \"Lower lip fullness\", \"Mouth\"), new AppearanceSlider(\"eyes\", \"Eye size\", \"Eyes & ears\"), new AppearanceSlider(\"ears\", \"Ear size\", \"Eyes & ears\"), new AppearanceSlider(\"brow\", \"Brow prominence\", \"Eyes & ears\"), new AppearanceSlider(\"eartaper\", \"Ear taper\", \"Eyes & ears\") });\r\n\tstatic CharacterAppearanceData Preset(string skin, string hair, string facial, string color) => new()\r\n\t{\r\n\t\tSkinPath = Skins.Single(x => x.Id == skin).ResourcePath,\r\n\t\tHairPath = Hair.Single(x => x.Id == hair).ResourcePath,\r\n\t\tFacialHairPath = FacialHair.Single(x => x.Id == facial).ResourcePath,\r\n\t\tHairColor = Colors.Single(x => x.Id == color).Color\r\n\t};\r\n\tstatic IReadOnlyList<AppearancePreset> presets;\r\n\tpublic static IReadOnlyList<AppearancePreset> Presets\r\n\t{\r\n\t\tget\r\n\t\t{\r\n\t\t\tif (presets?.Count != 7)\r\n\t\t\t\tpresets = Array.AsReadOnly(new[]\r\n\t\t\t\t{\r\n\t\t\t\t\tnew AppearancePreset(\"traveller\", \"Traveller\", Preset(\"human03\", \"scruffy\", \"none\", \"brown\")),\r\n\t\t\t\t\tnew AppearancePreset(\"scout\", \"Scout\", Preset(\"human02\", \"short_braids\", \"none\", \"black\")),\r\n\t\t\t\t\tnew AppearancePreset(\"scholar\", \"Scholar\", Preset(\"human04\", \"messy_bun\", \"none\", \"auburn\")),\r\n\t\t\t\t\tnew AppearancePreset(\"veteran\", \"Veteran\", Preset(\"human07\", \"balding\", \"medium\", \"silver\")),\r\n\t\t\t\t\tnew AppearancePreset(\"wanderer\", \"Wanderer\", Preset(\"human06\", \"short_dreads\", \"none\", \"black\")),\r\n\t\t\t\t\tnew AppearancePreset(\"artisan\", \"Artisan\", Preset(\"human05\", \"slicked\", \"goatee\", \"brown\")),\r\n\t\t\t\t\tnew AppearancePreset(\"explorer\", \"Explorer\", Preset(\"human01\", \"tied_braids\", \"none\", \"black\"))\r\n\t\t\t\t});\r\n\t\t\treturn presets;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic static CharacterAppearanceData CreateDefault() => ApplyPreset(\"traveller\");\r\n\tpublic static CharacterAppearanceData ApplyPreset(string id) => Presets.FirstOrDefault(x => x.Id == id)?.Appearance.DeepCopy() ?? new CharacterAppearanceData();\r\n\tpublic static void ResetShapes(CharacterAppearanceData data)\r\n\t{\r\n\t\tif (data is null)\r\n\t\t\treturn;\r\n\t\tforeach (var slider in Sliders)\r\n\t\t\tslider.Set(data, slider.Neutral);\r\n\t\tif (data.BaseShape is not null)\r\n\t\t{\r\n\t\t\tdata.BaseShape.BreastSize = .5f;\r\n\t\t\tdata.BaseShape.BulgeSize = .5f;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic static CharacterAppearanceData Randomize(Random rng = null, bool blendHeads = false)\r\n\t{\r\n\t\trng ??= new Random();\r\n\t\tvar body = rng.Next(2) == 0 ? \"male\" : \"female\";\r\n\t\t// Cross-body skins need weighted heads; native (v1) characters keep their native pool.\r\n\t\tvar skins = Skins.Where(x => blendHeads ? (body == \"male\" || x.Id is not (\"human05\" or \"human07\")) : x.BodyType == body).ToArray();\r\n\t\tvar skin = skins[rng.Next(skins.Length)];\r\n\t\tvar hair = RandomHairFor(body);\r\n\t\tvar facial = FacialHair.Where(x => x.BodyType == \"\" || x.BodyType == body).ToArray();\r\n\t\tvar tones = HumanSkinColors.Where(x => body == \"female\" && skin.Id == \"human06\" ? x.Id is \"native\" or \"tan\" or \"umber\" : x.NormalizeSkinTone).ToArray();\r\n\t\tvar tone = tones[rng.Next(tones.Length)];\r\n\t\tvar data = new CharacterAppearanceData\r\n\t\t{\r\n\t\t\tSkinPath = skin.ResourcePath,\r\n\t\t\tHairPath = hair[rng.Next(hair.Count)].ResourcePath,\r\n\t\t\tFacialHairPath = facial[rng.Next(facial.Length)].ResourcePath,\r\n\t\t\tHairColor = Colors[rng.Next(Colors.Count)].Color,\r\n\t\t\tSkinColor = tone.Color,\r\n\t\t\tNormalizeSkinTone = tone.NormalizeSkinTone\r\n\t\t};\r\n\t\tforeach (var slider in Sliders.Where(x => x.Id != \"belly\"))\r\n\t\t\tslider.Set(data, 0.3f + (float)rng.NextDouble() * 0.4f);\r\n\t\t// An added belly is an occasional, modest feature rather than a default.\r\n\t\tdata.BellySize = rng.NextDouble() < 0.10 ? 0.1f + (float)rng.NextDouble() * 0.3f : 0;\r\n\t\tif (body == \"female\" && rng.NextDouble() < 0.60)\r\n\t\t{\r\n\t\t\tAddMakeup(\"Lipstick\", 0.70, 0.35f, 0.85f, 4);\r\n\t\t\tAddMakeup(\"Eyeshadow\", 0.45, 0.20f, 0.65f, 3);\r\n\t\t\tAddMakeup(\"Blush\", 0.55, 0.15f, 0.55f, 4);\r\n\t\t}\r\n\r\n\t\tif (blendHeads)\r\n\t\t{\r\n\t\t\tUnifiedHuman.Enable(data);\r\n\t\t\t// Body identity must follow the roll, not the selected skin's native body.\r\n\t\t\tbool female = body == \"female\";\r\n\t\t\tdata.BaseShape.MaleBody = female ? 0 : 1;\r\n\t\t\tdata.BaseShape.BreastSize = female ? data.BodyFeatureSize : .5f;\r\n\t\t\tdata.BaseShape.BulgeSize = female ? .5f : data.BodyFeatureSize;\r\n\t\t\tdata.BaseShape.HeadWeights = RandomHeadWeights(rng, female, skin.Id, false);\r\n\t\t\tforeach (var region in FaceRegions.Ids)\r\n\t\t\t{\r\n\t\t\t\t// Each part gets its own dominant source rather than averaging every head\r\n\t\t\t\t// equally. Keep jaw/neck identity especially close to the body's family.\r\n\t\t\t\tdata.BaseShape.Regions[region] = new()\r\n\t\t\t\t{\r\n\t\t\t\t\tOverallInfluence = .15f + (float)rng.NextDouble() * .20f,\r\n\t\t\t\t\tHeadWeights = RandomHeadWeights(rng, female, skin.Id, region is \"jaw\" or \"throat\")\r\n\t\t\t\t};\r\n\t\t\t}\r\n\r\n\t\t\t// A shared jaw/throat source keeps the lower-face silhouette coherent.\r\n\t\t\tdata.BaseShape.Regions[\"throat\"] = data.BaseShape.Regions[\"jaw\"].DeepCopy();\r\n\t\t}\r\n\r\n\t\tdata.ClothingPaths = WardrobeCatalog.Randomize(rng, body == \"female\");\r\n\t\tdata.Version = 2;\r\n\t\treturn data;\r\n\t\tvoid AddMakeup(string layer, double chance, float low, float high, int paletteCount)\r\n\t\t{\r\n\t\t\tif (rng.NextDouble() >= chance)\r\n\t\t\t\treturn;\r\n\t\t\tvar options = MakeupCatalog.Colors(layer);\r\n\t\t\tvar target = MakeupCatalog.Layer(data.Makeup, layer);\r\n\t\t\ttarget.Color = options[rng.Next(Math.Min(paletteCount, options.Count))].Color;\r\n\t\t\ttarget.Amount = low + (float)rng.NextDouble() * (high - low);\r\n\t\t}\r\n\t}\r\n\r\n\tstatic float[] RandomHeadWeights(Random rng, bool female, string skin, bool structural)\r\n\t{\r\n\t\t// Allocate by family before choosing individual heads: five masculine faces\r\n\t\t// must not overwhelm the two feminine ones. Skin 04 on men excludes them.\r\n\t\tfloat ownShare = structural ? .95f + (float)rng.NextDouble() * .05f : .85f + (float)rng.NextDouble() * .15f;\r\n\t\tfloat femaleShare = female ? ownShare : skin == \"human04\" ? 0 : 1 - ownShare;\r\n\t\tvar weights = new float[7];\r\n\t\tFill(new[] { 1, 3 }, femaleShare);\r\n\t\tFill(new[] { 0, 2, 4, 5, 6 }, 1 - femaleShare);\r\n\t\tfloat max = weights.Max();\r\n\t\tfor (int i = 0; i < weights.Length; i++)\r\n\t\t\tweights[i] /= max;\r\n\t\treturn weights;\r\n\t\tvoid Fill(int[] indices, float share)\r\n\t\t{\r\n\t\t\tint primary = indices[rng.Next(indices.Length)];\r\n\t\t\tfloat dominance = .50f + (float)rng.NextDouble() * .40f;\r\n\t\t\tweights[primary] = share * dominance;\r\n\t\t\tfloat total = 0;\r\n\t\t\tforeach (int i in indices)\r\n\t\t\t\tif (i != primary)\r\n\t\t\t\t{\r\n\t\t\t\t\tweights[i] = .05f + (float)rng.NextDouble();\r\n\t\t\t\t\ttotal += weights[i];\r\n\t\t\t\t}\r\n\r\n\t\t\tforeach (int i in indices)\r\n\t\t\t\tif (i != primary)\r\n\t\t\t\t\tweights[i] *= share * (1 - dominance) / total;\r\n\t\t}\r\n\t}\r\n\r\n\t// Preferences apply only to Randomize. Every hairstyle remains manually\r\n\t// selectable and valid on either human body, including male pigtails.\r\n\tpublic static IReadOnlyList<AppearanceOption> RandomHairFor(string bodyType) => Hair.Where(x => bodyType == \"female\" ? x.Id is not (\"balding\" or \"thinning\" or \"slicked_balding\") : x.Id is \"none\" or \"scruffy\" or \"short_braids\" or \"short_dreads\" or \"long_dreads\" or \"tied_braids\" or \"tied_dreads\" or \"slicked\" or \"balding\" or \"thinning\" or \"ponytail\" or \"long_curly\" or \"long_brown\" or \"slicked_balding\").ToArray();\r\n\t/// <summary>Loads only curated native resources, either the whole creator catalog or one selected/saved appearance.</summary>\r\n\tpublic static async Task EnsureLoadedAsync(CharacterAppearanceData data = null)\r\n\t{\r\n\t\tif (data is not null && CharacterAppearanceData.Validate(data)is string error)\r\n\t\t\tthrow new InvalidOperationException(error);\r\n\t\tvar paths = data is null ? Skins.Concat(Hair).Concat(FacialHair).Select(x => x.ResourcePath) : new[]\r\n\t\t{\r\n\t\t\tdata.SkinPath,\r\n\t\t\tUnifiedHuman.PresentationSkin(data),\r\n\t\t\tdata.HairPath,\r\n\t\t\tdata.FacialHairPath\r\n\t\t};\r\n\t\ttry\r\n\t\t{\r\n\t\t\tforeach (var path in paths.Where(x => !string.IsNullOrEmpty(x)).Distinct(StringComparer.OrdinalIgnoreCase))\r\n\t\t\t\tawait Load(path);\r\n\t\t\tif (data is null || data.Makeup.Active)\r\n\t\t\t\tforeach (var skin in data is null ? Skins : Skins.Where(x => x.ResourcePath == data.SkinPath))\r\n\t\t\t\t\tawait HumanMakeupAssets.EnsureLoadedAsync(skin.Id);\r\n\t\t\tif (data is null || data.CustomEyeColor)\r\n\t\t\t\tawait EyeColorPresentation.EnsureLoadedAsync();\r\n\t\t\tif (data is not null)\r\n\t\t\t\tawait HumanMarkingModels.EnsureLoadedAsync(data);\r\n\t\t\tif (data?.BaseShape is not null)\r\n\t\t\t{\r\n\t\t\t\tvar model = await Model.LoadAsync(UnifiedHuman.ModelPath);\r\n\t\t\t\tif (model is null || model.IsError)\r\n\t\t\t\t\tthrow new InvalidOperationException(\"The unified human model is unavailable.\");\r\n\t\t\t\tawait UnifiedNeckAssets.LoadHeadAsync(data.SkinPath);\r\n\t\t\t}\r\n\r\n\t\t\tError = null;\r\n\t\t}\r\n\t\tcatch (Exception e)\r\n\t\t{\r\n\t\t\tError = e.Message;\r\n\t\t\tthrow;\r\n\t\t}\r\n\r\n\t\tstatic Task Load(string path)\r\n\t\t{\r\n\t\t\tif (resourceLoads.TryGetValue(path, out var pending) && pending is not null && !pending.IsFaulted && !pending.IsCanceled)\r\n\t\t\t\treturn pending;\r\n\t\t\treturn resourceLoads[path] = LoadResource(path);\r\n\t\t}\r\n\r\n\t\tstatic async Task LoadResource(string path)\r\n\t\t{\r\n\t\t\tClothing clothing = null;\r\n\t\t\tfor (var attempt = 0; attempt < 3; attempt++)\r\n\t\t\t{\r\n\t\t\t\tclothing = await ClothingResources.Load(path);\r\n\t\t\t\tif (clothing is not null && !clothing.IsError)\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tif (attempt < 2)\r\n\t\t\t\t\tawait Task.Delay(150);\r\n\t\t\t}\r\n\r\n\t\t\tif (clothing is null || clothing.IsError)\r\n\t\t\t\tthrow new InvalidOperationException(\"A native appearance resource is unavailable: \" + path);\r\n\t\t\tforeach (var modelPath in new[]\r\n\t\t\t{\r\n\t\t\t\tclothing.HumanSkinModel,\r\n\t\t\t\tclothing.HumanAltModel,\r\n\t\t\t\tclothing.HumanAltFemaleModel\r\n\t\t\t}.Where(x => !string.IsNullOrEmpty(x)).Distinct(StringComparer.OrdinalIgnoreCase))\r\n\t\t\t{\r\n\t\t\t\tvar model = await Model.LoadAsync(modelPath);\r\n\t\t\t\tif (model is null || model.IsError)\r\n\t\t\t\t\tthrow new InvalidOperationException(\"A native appearance model is unavailable: \" + modelPath);\r\n\t\t\t}\r\n\r\n\t\t\tRevision++;\r\n\t\t}\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "vendetta.character_editor",
            "Path": "CharacterKit/HumanMarkingModels.cs",
            "FileName": "HumanMarkingModels.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 410954,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.Threading.Tasks;\r\n\r\nnamespace CharacterEditor;\r\n/// <summary>Native human derivatives differ only in arm polygon material assignments.\r\n/// The source Clothing choice stays authoritative; no replacement rig or mesh is sculpted here.</summary>\r\npublic static class HumanMarkingModels\r\n{\r\n\tconst string ArmMaterials = \"materials/makeup/arms/\";\r\n\tpublic static bool NeedsArmRegions(CharacterMakeupData makeup) => makeup?.Markings?.Any(x => x.Style != \"none\" && x.Region is \"left-arm\" or \"right-arm\" or \"chest\" or \"back\") == true;\r\n\tpublic static string ModelPath(string native, CharacterMakeupData makeup) => NeedsArmRegions(makeup) ? native switch\r\n\t{\r\n\t\t\"models/citizen_human/citizen_human_male.vmdl\" => \"models/makeup/human_male_arm_regions.vmdl\",\r\n\t\t\"models/citizen_human/citizen_human_female.vmdl\" => \"models/makeup/human_female_arm_regions.vmdl\",\r\n\t\t_ => native\r\n\t} : native;\r\n\tpublic static async Task EnsureLoadedAsync(CharacterAppearanceData data)\r\n\t{\r\n\t\tif (!NeedsArmRegions(data?.Makeup))\r\n\t\t\treturn;\r\n\t\tvar skin = ResourceLibrary.Get<Clothing>(data.SkinPath);\r\n\t\tvar path = ModelPath(skin.HumanSkinModel, data.Makeup);\r\n\t\tvar model = await Model.LoadAsync(path);\r\n\t\tif (model is null || model.IsError)\r\n\t\t\tthrow new InvalidOperationException(\"The independently marked human arms are unavailable.\");\r\n\t}\r\n\r\n\tpublic static int ArmSide(Material material) => !material.Name.StartsWith(ArmMaterials, StringComparison.OrdinalIgnoreCase) ? 0 : material.Name.Contains(\"_left_\", StringComparison.OrdinalIgnoreCase) ? 1 : -1;\r\n\tpublic static Material NativeMaterial(Material slot)\r\n\t{\r\n\t\tif (ArmSide(slot) == 0)\r\n\t\t\treturn slot;\r\n\t\tvar female = slot.Name.Contains(\"/female_\", StringComparison.OrdinalIgnoreCase);\r\n\t\tvar light = slot.Name.EndsWith(\"_light.vmat\", StringComparison.OrdinalIgnoreCase);\r\n\t\tvar name = female ? (light ? \"female_body_mode_light\" : \"female_body_mode_dark\") : (light ? \"body_male_light\" : \"body_mode_dark\");\r\n\t\treturn Material.Load($\"models/citizen_human/bodies/{(female ? \"female\" : \"male\")}/{name}.vmat\");\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "vendetta.character_editor",
            "Path": "Studio/StudioEscapeMenu.razor",
            "FileName": "StudioEscapeMenu.razor",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 410954,
            "IsPrivate": false,
            "Code": "@using System\r\n@using Sandbox\r\n@using Sandbox.UI\r\n@namespace CharacterEditor\r\n@inherits Panel\r\n\r\n<root>\r\n@if(IsOpen)\r\n{\r\n <div class=\"menu-shade\"><div class=\"menu-card\">\r\n @if(IsConfirming)\r\n {\r\n  <h2>Discard unsaved edits?</h2><p>Your saved characters will stay as they are.</p>\r\n  <button onclick=@CancelNavigation>Keep editing</button>\r\n  <button class=\"primary\" onclick=@ConfirmNavigation>Discard edits and continue</button>\r\n }\r\n else\r\n {\r\n  <div class=\"menu-title\">Character Editor</div>\r\n  <div class=\"menu-tabs\"><button class=@(!Experimental?\"chosen\":\"\") onclick=@(()=>Experimental=false)>Menu</button>@if(EnableExperiments){<button class=@(Experimental?\"chosen\":\"\") onclick=@(()=>Experimental=true)>Experimental</button>}</div>\r\n  @if(Experimental && EnableExperiments)\r\n  {\r\n   <h2>Experiments</h2><p>Development tools and research. These do not create saved characters.</p>\r\n   <button onclick=@(()=>RequestNavigation(OpenLab))>Face lab</button>\r\n  }\r\n  else\r\n  {\r\n   <button class=\"primary\" onclick=@Close>Resume</button>\r\n   <button onclick=@(()=>RequestNavigation(MainMenu))>Main menu</button>\r\n   <button onclick=@(()=>RequestNavigation(()=>Game.Close()))>Quit to s&amp;box</button>\r\n  }\r\n  <div class=\"menu-hint\">Escape to return</div>\r\n }\r\n </div></div>\r\n}\r\n</root>\r\n\r\n@code {\r\n\tpublic Action MainMenu { get; set; }\r\n\tpublic Action OpenLab { get; set; }\r\n\tpublic Func<bool> HasUnsavedEdits { get; set; }\r\n\tpublic bool EnableExperiments { get; set; }\r\n\tpublic bool IsOpen { get; private set; }\r\n\tpublic bool IsConfirming => pending is not null;\r\n\r\n\tbool Experimental;\r\n\tAction pending;\r\n\tpublic void Toggle()\r\n\t{\r\n\t\tif (IsConfirming)\r\n\t\t\tCancelNavigation();\r\n\t\telse if (IsOpen)\r\n\t\t\tClose();\r\n\t\telse\r\n\t\t{\r\n\t\t\tIsOpen = true;\r\n\t\t\tExperimental = false;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void Close()\r\n\t{\r\n\t\tIsOpen = false;\r\n\t\tExperimental = false;\r\n\t\tpending = null;\r\n\t}\r\n\r\n\tpublic void RequestNavigation(Action action)\r\n\t{\r\n\t\tif (action is null)\r\n\t\t\treturn;\r\n\t\tif (HasUnsavedEdits?.Invoke() == true)\r\n\t\t\tpending = action;\r\n\t\telse\r\n\t\t{\r\n\t\t\tClose();\r\n\t\t\taction();\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void CancelNavigation()\r\n\t{\r\n\t\tpending = null;\r\n\t\tClose();\r\n\t}\r\n\r\n\tpublic void ConfirmNavigation()\r\n\t{\r\n\t\tvar action = pending;\r\n\t\tClose();\r\n\t\taction?.Invoke();\r\n\t}\r\n\r\n\tprotected override int BuildHash() => HashCode.Combine(IsOpen, Experimental, IsConfirming, EnableExperiments);\r\n}\r\n"
        },
        {
            "Ident": "vendetta.character_editor",
            "Path": "studio/studiopanel.razor.scss",
            "FileName": "studiopanel.razor.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 410954,
            "IsPrivate": false,
            "Code": "StudioPanel {\n position: absolute; width: 100%; height: 100%; font-family: Inter; color: #f0eee6; pointer-events: none;\n button { pointer-events: all; cursor: pointer; padding: 11px 17px; border-radius: 6px; background-color: #ffffff0a; border: 1px solid #ffffff20; color: #e9ebe6; font-size: 14px; flex-shrink: 0; align-items: center; justify-content: center; gap: 10px; }\n button:hover { background-color: #ffffff20; border-color: #c5e4a7; }\n button.primary { background-color: #d6ebaf; border-color: #d6ebaf; color: #263425; font-weight: 600; }\n button.primary:hover { background-color: #eaf9cc; }\n button.chosen { color: #d6ebaf; border-color: #d6ebaf88; }\n h1, h2, p { margin: 0; flex-shrink: 0; }\n h1 { font-size: 48px; font-weight: 500; letter-spacing: -1.5px; }\n h2 { font-size: 22px; font-weight: 500; }\n p { font-size: 15px; line-height: 25px; color: #aeb8ab; white-space: normal; }\n .eyebrow { font-size: 11px; letter-spacing: 2px; color: #c6d9b2; margin-bottom: 13px; }\n .collection { position: absolute; width: 100%; height: 100%; background-color: #17221ff9; padding: 30px 54px 20px; flex-direction: column; pointer-events: all; }\n .topbar { justify-content: space-between; align-items: center; flex-shrink: 0; }\n .brand { gap: 13px; align-items: center; letter-spacing: 2px; font-size: 12px; font-weight: 600; }\n .brandmark { width: 37px; height: 37px; border-radius: 50%; border: 2px solid #d6ebaf; color: #d6ebaf; font-size: 25px; align-items: center; justify-content: center; }\n .actions { gap: 10px; align-items: center; flex-shrink: 0; }\n .intro { margin: 42px 0 30px; justify-content: space-between; align-items: center; flex-shrink: 0; }\n .intro > div { flex-direction: column; gap: 9px; }\n .intro .eyebrow { margin-bottom: 0; }\n .plazalink { width: 235px; height: 100px; background-color: #354b3e; justify-content: space-between; padding: 25px; font-size: 19px; border-color: #a5c08466; }\n .arrow { font-size: 36px; color: #d6ebaf; }\n .librarybar { justify-content: space-between; align-items: center; border-bottom: 1px solid #ffffff20; padding-bottom: 17px; flex-shrink: 0; }\n TextEntry.search { width: 240px; padding: 12px 15px; background-color: #0e1916; border: 1px solid #ffffff20; border-radius: 6px; color: #edf2e5; font-size: 14px; }\n .cards { flex-grow: 1; min-height: 0; overflow-y: auto; flex-wrap: wrap; align-content: flex-start; gap: 20px; padding: 22px 0; }\n .card { width: 220px; flex-shrink: 0; flex-direction: column; background-color: #25312c; border: 1px solid #ffffff18; border-radius: 9px; overflow: hidden; }\n button.portrait { width: 100%; height: 190px; padding: 0; border: 0; border-radius: 0; background-color: #435146; position: relative; }\n .portrait CharacterCreatorPreview { position: absolute; width: 100%; height: 100%; pointer-events: none; }\n .cardtitle { align-items: center; justify-content: space-between; padding: 10px 14px 4px; }\n .cardtitle h2 { font-size: 17px; max-width: 153px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; }\n button.favorite { padding: 0; border: 0; background-color: transparent; font-size: 26px; }\n .cardactions { gap: 5px; padding: 8px 12px 14px; }\n .cardactions button { font-size: 11px; padding: 7px 10px; }\n button.delete { color: #d5aaa1; }\n .empty { width: 100%; padding: 45px 0; flex-direction: column; align-items: center; gap: 17px; }\n .empty p { max-width: 450px; text-align: center; }\n .emptyicon { font-size: 54px; color: #d6ebaf; }\n .bottomline { justify-content: space-between; color: #809487; font-size: 11px; padding-top: 14px; border-top: 1px solid #ffffff18; flex-shrink: 0; }\n .plazahead { position: absolute; left: 38px; right: 38px; top: 30px; align-items: flex-start; justify-content: space-between; text-shadow: 0 2px 4px #000a; }\n .plazahead > div { flex-direction: column; gap: 5px; }\n .plazahead h1 { font-size: 34px; }\n .plazahead button { background-color: #17221fee; }\n .plazahead p { color: #edf2e5; }\n .residents { position: absolute; left: 30px; top: 190px; width: 245px; max-height: 65%; overflow-y: auto; flex-direction: column; padding: 20px; background-color: #17221fe8; border: 1px solid #ffffff25; border-radius: 8px; pointer-events: all; gap: 8px; }\n .resident { gap: 6px; flex-shrink: 0; }\n .resident button { font-size: 12px; padding: 8px; }\n .resident button:first-child { flex-grow: 1; justify-content: flex-start; }\n .plazatools { position: absolute; bottom: 25px; right: 30px; gap: 6px; padding: 10px; background-color: #17221fe8; border-radius: 8px; }\n .plazatools button { font-size: 12px; padding: 10px; }\n .shade { position: absolute; width: 100%; height: 100%; background-color: #07140eea; align-items: center; justify-content: center; pointer-events: all; z-index: 50; }\n .dialog { width: 490px; padding: 35px; background-color: #25352b; border: 1px solid #acc79666; border-radius: 12px; flex-direction: column; gap: 22px; }\n .dialog .actions { justify-content: flex-end; }\n .transfer { width: 700px; }\n TextEntry.json { height: 260px; padding: 15px; background-color: #121f18; color: #d6ebaf; font-size: 12px; overflow-y: auto; white-space: pre-wrap; }\n .toast { position: absolute; bottom: 65px; left: 35%; max-width: 60%; padding: 14px 22px; background-color: #d6ebaf; color: #263425; border-radius: 8px; pointer-events: all; z-index: 100; font-size: 14px; }\n .warning { position: absolute; bottom: 110px; left: 30px; max-width: 70%; background-color: #633f24; padding: 15px; pointer-events: all; z-index: 101; }\n button.savecopy { position: absolute; right: 250px; bottom: 27px; z-index: 6; background-color: #25352b; }\n}\n"
        },
        {
            "Ident": "vendetta.character_editor",
            "Path": "Assembly.cs",
            "FileName": "Assembly.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 410954,
            "IsPrivate": false,
            "Code": "global using Sandbox;\r\nglobal using System.Collections.Generic;\r\nglobal using System.Linq;\r\n"
        },
        {
            "Ident": "vendetta.character_editor",
            "Path": "CharacterKit/CharacterBaseShape.cs",
            "FileName": "CharacterBaseShape.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 410954,
            "IsPrivate": false,
            "Code": "using System;\r\n\r\nnamespace CharacterEditor;\r\n/// <summary>Version 2 identity shape, independent of skin. Weights are in human01..human07 order.</summary>\r\npublic sealed class CharacterBaseShape\r\n{\r\n\tpublic float[] HeadWeights { get; set; } = new[]\r\n\t{\r\n\t\t0f,\r\n\t\t0f,\r\n\t\t1f,\r\n\t\t0f,\r\n\t\t0f,\r\n\t\t0f,\r\n\t\t0f\r\n\t};\r\n\tpublic List<PhotoFace> PhotoFaces { get; set; } = new();\r\n\tpublic Dictionary<string, FaceRegionBlend> Regions { get; set; } = new();\r\n\r\n\t[System.Text.Json.Serialization.JsonIgnore]\r\n\tpublic float TotalInfluence => HeadWeights.Sum() + (PhotoFaces?.Sum(x => x.Influence) ?? 0);\r\n\r\n\tpublic float NativeShare(int i) => TotalInfluence > 0 ? HeadWeights[i] / TotalInfluence : (i == 2 ? 1 : 0);\r\n\tpublic float PhotoShare(PhotoFace face) => TotalInfluence > 0 ? face.Influence / TotalInfluence : 0;\r\n\tpublic float MaleBody { get; set; } = 1;\r\n\tpublic string ClothingVariant { get; set; }\r\n\tpublic float BreastSize { get; set; } = .5f;\r\n\tpublic float BulgeSize { get; set; } = .5f;\r\n\r\n\t[System.Text.Json.Serialization.JsonIgnore]\r\n\tpublic bool Valid => HeadWeights?.Length == 7 && HeadWeights.All(Unit) && PhotoFaces is not null && PhotoFaces.Count <= 4 && PhotoFaces.All(x => x?.Valid == true) && PhotoFaces.Select(x => x.Id).Distinct().Count() == PhotoFaces.Count && Regions is not null && Regions.Count <= FaceRegions.Ids.Length && Regions.All(x => FaceRegions.Ids.Contains(x.Key) && x.Value?.Valid(PhotoFaces) == true) && Unit(MaleBody) && Unit(BreastSize) && Unit(BulgeSize) && ClothingVariant is null or \"male\" or \"female\";\r\n\r\n\tstatic bool Unit(float x) => float.IsFinite(x) && x >= 0 && x <= 1;\r\n\tpublic float[] Normalized()\r\n\t{\r\n\t\tif (!Valid)\r\n\t\t\treturn new[]\r\n\t\t\t{\r\n\t\t\t\t0f,\r\n\t\t\t\t0f,\r\n\t\t\t\t1f,\r\n\t\t\t\t0f,\r\n\t\t\t\t0f,\r\n\t\t\t\t0f,\r\n\t\t\t\t0f\r\n\t\t\t};\r\n\t\tfloat sum = TotalInfluence;\r\n\t\treturn sum > 0 ? HeadWeights.Select((x, i) => (x + PhotoFaces.Sum(f => f.Influence * f.HeadWeights[i])) / sum).ToArray() : new[]\r\n\t\t{\r\n\t\t\t0f,\r\n\t\t\t0f,\r\n\t\t\t1f,\r\n\t\t\t0f,\r\n\t\t\t0f,\r\n\t\t\t0f,\r\n\t\t\t0f\r\n\t\t};\r\n\t}\r\n\r\n\tpublic CharacterBaseShape DeepCopy() => new()\r\n\t{\r\n\t\tHeadWeights = HeadWeights?.ToArray(),\r\n\t\tPhotoFaces = PhotoFaces?.Select(x => x?.DeepCopy()).ToList(),\r\n\t\tRegions = Regions?.ToDictionary(x => x.Key, x => x.Value.DeepCopy()),\r\n\t\tMaleBody = MaleBody,\r\n\t\tBreastSize = BreastSize,\r\n\t\tBulgeSize = BulgeSize,\r\n\t\tClothingVariant = ClothingVariant\r\n\t};\r\n}\r\n\r\npublic static class UnifiedHuman\r\n{\r\n\tpublic const string ModelPath = \"models/characterkit/unified_human.vmdl\";\r\n\tpublic static void Enable(CharacterAppearanceData data)\r\n\t{\r\n\t\tif (data.BaseShape is not null)\r\n\t\t\treturn;\r\n\t\tbool female = AppearanceCatalog.IsFemale(data);\r\n\t\tvar index = AppearanceCatalog.Skins.ToList().FindIndex(s => s.ResourcePath == data.SkinPath);\r\n\t\tdata.BaseShape = new()\r\n\t\t{\r\n\t\t\tMaleBody = female ? 0 : 1,\r\n\t\t\tHeadWeights = new float[7],\r\n\t\t\tBreastSize = female ? data.BodyFeatureSize : .5f,\r\n\t\t\tBulgeSize = female ? .5f : data.BodyFeatureSize\r\n\t\t};\r\n\t\tdata.BaseShape.HeadWeights[Math.Max(0, index)] = 1;\r\n\t\tdata.Version = 2;\r\n\t}\r\n\r\n\tpublic static float MaleAmount(CharacterAppearanceData a) => a.BaseShape?.MaleBody ?? (AppearanceCatalog.IsFemale(a) ? 0 : 1);\r\n\tpublic static string PresentationSkin(CharacterAppearanceData a) => a.BaseShape is null ? a.SkinPath : (MaleAmount(a) >= .5f ? \"models/citizen_clothes/body/human03.clothing\" : \"models/citizen_clothes/body/human04.clothing\");\r\n\tpublic static void Apply(SkinnedModelRenderer body, CharacterAppearanceData a)\r\n\t{\r\n\t\tif (a.BaseShape is null)\r\n\t\t\treturn;\r\n\t\tvar weights = FaceRegions.ReferenceWeights(a.BaseShape);\r\n\t\tfor (int i = 0; i < 7; i++)\r\n\t\t\tif (i != 2)\r\n\t\t\t\tbody.Morphs.Set($\"ce_human{i + 1:00}\", weights[i]);\r\n\t\tFaceRegions.Apply(body, a.BaseShape, weights);\r\n\t\tbody.Morphs.Set(\"ce_body_female\", 1 - a.BaseShape.MaleBody);\r\n\t}\r\n\r\n\tpublic static string HeadMaterial(string skin)\r\n\t{\r\n\t\tvar id = System.IO.Path.GetFileNameWithoutExtension(skin);\r\n\t\tvar suffix = id switch\r\n\t\t{\r\n\t\t\t\"human01\" => \"malehead01\",\r\n\t\t\t\"human02\" => \"femalehead01\",\r\n\t\t\t\"human03\" => \"malehead02\",\r\n\t\t\t\"human04\" => \"femalehead02\",\r\n\t\t\t\"human05\" => \"malehead03\",\r\n\t\t\t\"human06\" => \"malehead04\",\r\n\t\t\t_ => \"malehead05\"\r\n\t\t};\r\n\t\treturn $\"models/citizen_human/heads/citizen_human_{suffix}/citizen_human_{suffix}.vmat\";\r\n\t}\r\n\r\n\tpublic static Material SkinMaterial(Material slot, string skin, bool unifiedBody = true)\r\n\t{\r\n\t\tvar original = HumanMarkingModels.NativeMaterial(slot);\r\n\t\tif (original.Name.Contains(\"/heads/\") && original.ShaderName == \"shaders/skin.shader\" && !original.Name.EndsWith(\"/mouth.vmat\"))\r\n\t\t\treturn Material.Load(HeadMaterial(skin));\r\n\t\tif (original.Name.Contains(\"/bodies/\") && original.ShaderName == \"shaders/skin.shader\")\r\n\t\t{\r\n\t\t\t// The unified surface uses one body material family. Female chest normal maps\r\n\t\t\t// encode breast contours that become distorted on the flattened male endpoint.\r\n\t\t\tvar id = System.IO.Path.GetFileNameWithoutExtension(skin);\r\n\t\t\tbool female = !unifiedBody && (id is \"human02\" or \"human04\"), dark = id is \"human01\" or \"human02\" or \"human06\";\r\n\t\t\treturn Material.Load($\"models/citizen_human/bodies/{(female ? \"female\" : \"male\")}/{(female ? (dark ? \"female_body_mode_dark\" : \"female_body_mode_light\") : (dark ? \"body_mode_dark\" : \"body_male_light\"))}.vmat\");\r\n\t\t}\r\n\r\n\t\treturn original;\r\n\t}\r\n\r\n\tpublic static Color BodyToneTint(Material material, string skin)\r\n\t{\r\n\t\tif (!material.Name.Contains(\"/bodies/\"))\r\n\t\t\treturn Color.White;\r\n\t\treturn BodyToneTint(skin);\r\n\t}\r\n\r\n\tpublic static Color BodyToneTint(string skin)\r\n\t{\r\n\t\t// Albedo ratios of the native female/male materials sampled just inside\r\n\t\t// the shared neck boundary. Keep the female faces' authored complexion.\r\n\t\treturn System.IO.Path.GetFileNameWithoutExtension(skin) switch\r\n\t\t{\r\n\t\t\t\"human02\" => new Color(1.0192f, .9194f, .8924f),\r\n\t\t\t\"human04\" => new Color(1.0856f, 1.0137f, .8969f),\r\n\t\t\t_ => Color.White\r\n\t\t};\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "vendetta.character_editor",
            "Path": "CharacterKit/CharacterMarkingData.cs",
            "FileName": "CharacterMarkingData.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 410954,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.Text.Json.Serialization;\r\n\r\nnamespace CharacterEditor;\r\n/// <summary>One independently editable ink layer. Placement is normalized within a curated surface region.</summary>\r\npublic sealed class CharacterMarkingData\r\n{\r\n\tpublic string Style { get; set; } = \"crescent\";\r\n\tpublic string Region { get; set; } = \"face\";\r\n\tpublic float Horizontal { get; set; } = .5f;\r\n\tpublic float Vertical { get; set; } = .5f;\r\n\tpublic float Scale { get; set; } = .5f;\r\n\tpublic float Rotation { get; set; } = .5f;\r\n\tpublic float Spacing { get; set; } = .5f;\r\n\tpublic MakeupLayerData Ink { get; set; } = new()\r\n\t{\r\n\t\tRed = .1f,\r\n\t\tGreen = .12f,\r\n\t\tBlue = .16f,\r\n\t\tAmount = .8f\r\n\t};\r\n\r\n\t[JsonIgnore]\r\n\tpublic bool Valid => MarkingCatalog.ValidPlacement(Style, Region) && Ink?.Valid == true && new[]\r\n\t{\r\n\t\tHorizontal,\r\n\t\tVertical,\r\n\t\tScale,\r\n\t\tRotation,\r\n\t\tSpacing\r\n\t}.All(x => float.IsFinite(x) && x >= 0 && x <= 1) && (Style != \"barbed-band\" || (Horizontal == .5f && Rotation == .5f));\r\n\r\n\tpublic CharacterMarkingData DeepCopy() => new()\r\n\t{\r\n\t\tStyle = Style,\r\n\t\tRegion = Region,\r\n\t\tHorizontal = Horizontal,\r\n\t\tVertical = Vertical,\r\n\t\tScale = Scale,\r\n\t\tRotation = Rotation,\r\n\t\tSpacing = Spacing,\r\n\t\tInk = Ink?.DeepCopy()\r\n\t};\r\n}\r\n"
        },
        {
            "Ident": "vendetta.character_editor",
            "Path": ".obj/__compiler_extra.cs",
            "FileName": "__compiler_extra.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 410954,
            "IsPrivate": false,
            "Code": "global using static Sandbox.Internal.GlobalGameNamespace;\r\nglobal using Microsoft.AspNetCore.Components;\r\nglobal using Microsoft.AspNetCore.Components.Rendering;\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"AddonTitle\", \"Character Editor\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"AddonIdent\", \"character_editor\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"OrgIdent\", \"vendetta\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"Ident\", \"vendetta.character_editor\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"EngineVersion\", \"29\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"EngineMinorVersion\", \"1\" )]\r\n\r\n[assembly: System.Runtime.Versioning.TargetFramework( \".NETCoreApp,Version=v9.0\", FrameworkDisplayName = \".NET 9.0\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"CompileTime\", \"2026-10-05T23:16:04.3069273Z\" )]\r\n[assembly: global::System.Reflection.AssemblyVersion(\"0.0.382.0\")]\r\n[assembly: global::System.Reflection.AssemblyFileVersion(\"0.0.382.0\")]"
        },
        {
            "Ident": "vendetta.character_editor",
            "Path": "CharacterKit/AutomaticWardrobeFit.cs",
            "FileName": "AutomaticWardrobeFit.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 410954,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.IO;\r\nusing System.Text;\r\nusing System.Runtime.InteropServices;\r\n\r\nnamespace CharacterEditor;\r\n/// <summary>Fits loaded human clothes against shared body surfaces, without garment authoring files.</summary>\r\npublic static class AutomaticWardrobeFit\r\n{\r\n\tconst string ReferencePath = \"models/characterkit/wardrobe-reference.bin\";\r\n\tpublic const string Morph = \"ce_body_female\";\r\n\t// Cache by resource identity and authored cut, never by the current slider value.\r\n\tstatic readonly Dictionary<(Model, bool, bool), Model> fittedModels = new();\r\n\tstatic readonly Dictionary<(Model, bool, bool), string> fittingFailures = new();\r\n\tstatic Surface originalMale, unifiedMale, unifiedFemale;\r\n\tstatic Surface headSurface;\r\n\tstatic Model replacementNeck;\r\n\tpublic static int CachedModels => fittedModels.Count;\r\n\r\n\tpublic static void ClearCache()\r\n\t{\r\n\t\tfittedModels.Clear();\r\n\t\tfittingFailures.Clear();\r\n\t\treplacementNeck = null;\r\n\t\theadSurface = null;\r\n\t}\r\n\r\n\t/// <summary>Keep the lower neck when clothing replaces the head bodygroup.</summary>\r\n\tpublic static Model ReplacementNeck()\r\n\t{\r\n\t\tif (replacementNeck is not null) return replacementNeck;\r\n\t\tReferences();\r\n\t\treturn replacementNeck = Build(Model.Load(UnifiedHuman.ModelPath), false, neckOnly: true);\r\n\t}\r\n\r\n\tpublic static bool IsFitted(Model model) => model?.Name.StartsWith(\"automatic-human-fit:\", StringComparison.Ordinal) == true;\r\n\tpublic static Model Fit(Model source, bool female, bool fitFace = true)\r\n\t{\r\n\t\tif (source is null || source.IsError || IsFitted(source) || source.BoneCount == 0 || source.Bounds.Mins.z > 60)\r\n\t\t\treturn source;\r\n\t\tif (fittedModels.TryGetValue((source, female, fitFace), out var ready))\r\n\t\t\treturn ready;\r\n\t\tif (fittingFailures.TryGetValue((source, female, fitFace), out var problem))\r\n\t\t\tthrow new InvalidOperationException(problem);\r\n\t\ttry\r\n\t\t{\r\n\t\t\tReferences();\r\n\t\t\tvar fitted = Build(source, female, fitFace: fitFace);\r\n\t\t\tfittedModels[(source, female, fitFace)] = fitted;\r\n\t\t\treturn fitted;\r\n\t\t}\r\n\t\tcatch (Exception error)\r\n\t\t{\r\n\t\t\tvar message = \"Cannot automatically fit \" + source.Name + \": \" + error.Message;\r\n\t\t\tfittingFailures[(source, female, fitFace)] = message;\r\n\t\t\tthrow new InvalidOperationException(message, error);\r\n\t\t}\r\n\t}\r\n\r\n\tstatic void References()\r\n\t{\r\n\t\tif (unifiedMale is not null)\r\n\t\t\treturn;\r\n\t\tusing var stream = new MemoryStream(FileSystem.Mounted.ReadAllBytes(ReferencePath).ToArray());\r\n\t\tusing var r = new BinaryReader(stream);\r\n\t\tif (r.ReadInt32() != 1)\r\n\t\t\tthrow new InvalidOperationException(\"Unsupported human fitting reference.\");\r\n\t\toriginalMale = ReadSurface(r);\r\n\t\tunifiedMale = ReadSurface(r);\r\n\t\tunifiedFemale = new(unifiedMale.Female, unifiedMale.Female, unifiedMale.Triangles, unifiedMale.Names, unifiedMale.Weights);\r\n\t}\r\n\r\n\tstatic Surface HeadSurface()\r\n\t{\r\n\t\tif (headSurface is not null) return headSurface;\r\n\t\tvar source = Model.Load(UnifiedHuman.ModelPath);\r\n\t\tvar buffers = ReadBuffers(source);\r\n\t\tvar points = new List<Vector3>();\r\n\t\tvar triangles = new List<int>();\r\n\t\tfor (int mi = 0; mi < buffers.Count; mi++)\r\n\t\t{\r\n\t\t\tvar info = source.MeshInfo.Meshes[mi];\r\n\t\t\tif (!info.Name.StartsWith(\"head\", StringComparison.OrdinalIgnoreCase)) continue;\r\n\t\t\tvar buffer = buffers[mi];\r\n\t\t\tint callIndex = 0;\r\n\t\t\tforeach (var call in info.DrawCalls)\r\n\t\t\t{\r\n\t\t\t\tint vb = buffer.Vertices.Length == 1 ? 0 : callIndex;\r\n\t\t\t\tint ib = buffer.Indices.Length == 1 ? 0 : callIndex;\r\n\t\t\t\tvar vertices = Vertices(buffer.Vertices[vb], null, buffer.Bones.Length);\r\n\t\t\t\tvar indices = Indices(buffer.Indices[ib]);\r\n\t\t\t\tint offset = points.Count;\r\n\t\t\t\tpoints.AddRange(vertices.Select(v => v.Position));\r\n\t\t\t\ttriangles.AddRange(indices.Skip(call.StartIndex).Take(call.Indices).Select(i => i + call.BaseVertex + offset));\r\n\t\t\t\tcallIndex++;\r\n\t\t\t}\r\n\t\t}\r\n\t\tvar p = points.ToArray();\r\n\t\treturn headSurface = new(p, p, triangles.ToArray(), Array.Empty<string>(), Array.Empty<(int, float)[]>());\r\n\t}\r\n\r\n\tstatic Vector3 V(BinaryReader r) => new(r.ReadSingle(), r.ReadSingle(), r.ReadSingle());\r\n\tstatic Surface ReadSurface(BinaryReader r)\r\n\t{\r\n\t\tvar names = new string[r.ReadInt32()];\r\n\t\tfor (int i = 0; i < names.Length; i++)\r\n\t\t\tnames[i] = Encoding.UTF8.GetString(r.ReadBytes(r.ReadInt32()));\r\n\t\tvar count = r.ReadInt32();\r\n\t\tvar triangles = new int[r.ReadInt32() * 3];\r\n\t\tvar p = new Vector3[count];\r\n\t\tvar f = new Vector3[count];\r\n\t\tvar weights = new (int, float)[count][];\r\n\t\tfor (int i = 0; i < count; i++)\r\n\t\t{\r\n\t\t\tp[i] = V(r);\r\n\t\t\tf[i] = V(r);\r\n\t\t\tweights[i] = new (int, float)[r.ReadInt32()];\r\n\t\t\tfor (int j = 0; j < weights[i].Length; j++)\r\n\t\t\t\tweights[i][j] = (r.ReadInt32(), r.ReadSingle());\r\n\t\t}\r\n\r\n\t\tfor (int i = 0; i < triangles.Length; i++)\r\n\t\t\ttriangles[i] = r.ReadInt32();\r\n\t\treturn new(p, f, triangles, names, weights);\r\n\t}\r\n\r\n\t[StructLayout(LayoutKind.Sequential)]\r\n\tstruct SkinVertex\r\n\t{\r\n\t\t[VertexLayout.Position]\r\n\t\tpublic Vector3 Position;\r\n\t\t[VertexLayout.Normal]\r\n\t\tpublic Vector3 Normal;\r\n\t\t[VertexLayout.Tangent]\r\n\t\tpublic Vector4 Tangent;\r\n\t\t[VertexLayout.TexCoord(0)]\r\n\t\tpublic Vector4 UV;\r\n\t\t[VertexLayout.TexCoord(1)]\r\n\t\tpublic Vector4 UV2;\r\n\t\t[VertexLayout.Color]\r\n\t\tpublic Color32 Color;\r\n\t\t[VertexLayout.BlendIndices]\r\n\t\tpublic Color32 Indices;\r\n\t\t[VertexLayout.BlendWeight]\r\n\t\tpublic Color32 Weights;\r\n\t}\r\n\r\n\treadonly record struct Attribute(string Name, int Index, int Format, int Offset);\r\n\tsealed record Buffer(int Count, int Stride, Attribute[] Attributes, byte[] Data);\r\n\tsealed record Buffers(Buffer[] Vertices, Buffer[] Indices, string[] Bones);\r\n\t// SaveToVmdl emits unpacked MBUF data, even when the installed resource uses\r\n\t// compressed buffers. Reading that public serialization retains authored weights.\r\n\tstatic List<Buffers> ReadBuffers(Model source)\r\n\t{\r\n\t\tvar data = source.SaveToVmdl();\r\n\t\tusing var r = new BinaryReader(new MemoryStream(data));\r\n\t\tr.BaseStream.Position = 8;\r\n\t\tvar table = 8 + r.ReadInt32();\r\n\t\tvar blocks = r.ReadInt32();\r\n\t\tvar result = new List<Buffers>();\r\n\t\tfor (int i = 0; i < blocks; i++)\r\n\t\t{\r\n\t\t\tr.BaseStream.Position = table + i * 12;\r\n\t\t\tvar tag = Encoding.ASCII.GetString(r.ReadBytes(4));\r\n\t\t\tvar anchor = (int)r.BaseStream.Position;\r\n\t\t\tvar offset = r.ReadInt32();\r\n\t\t\tvar size = r.ReadInt32();\r\n\t\t\tif (tag == \"MDAT\")\r\n\t\t\t{\r\n\t\t\t\tr.BaseStream.Position = anchor + offset;\r\n\t\t\t\tvar bones = SerializedMeshBones.Read(r.ReadBytes(size));\r\n\t\t\t\tresult[^1] = result[^1] with\r\n\t\t\t\t{\r\n\t\t\t\t\tBones = bones\r\n\t\t\t\t};\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tif (tag != \"MBUF\")\r\n\t\t\t\tcontinue;\r\n\t\t\tvar start = anchor + offset;\r\n\t\t\tr.BaseStream.Position = start;\r\n\t\t\tvar vo = r.ReadInt32();\r\n\t\t\tvar vc = r.ReadInt32();\r\n\t\t\tvar io = r.ReadInt32();\r\n\t\t\tvar ic = r.ReadInt32();\r\n\t\t\tresult.Add(new(Enumerable.Range(0, vc).Select(j => ReadBuffer(r, start + vo + j * 24)).ToArray(), Enumerable.Range(0, ic).Select(j => ReadBuffer(r, start + 8 + io + j * 24)).ToArray(), null));\r\n\t\t}\r\n\r\n\t\tif (result.Count != source.MeshCount)\r\n\t\t\tthrow new InvalidOperationException($\"{source.Name}: missing serialized mesh buffers.\");\r\n\t\treturn result;\r\n\t}\r\n\r\n\tstatic Buffer ReadBuffer(BinaryReader r, int position)\r\n\t{\r\n\t\tr.BaseStream.Position = position;\r\n\t\tvar count = r.ReadInt32();\r\n\t\tvar stride = r.ReadInt32() & 0x3ffffff;\r\n\t\tvar ap = (int)r.BaseStream.Position;\r\n\t\tvar ao = r.ReadInt32();\r\n\t\tvar ac = r.ReadInt32();\r\n\t\tvar dp = (int)r.BaseStream.Position;\r\n\t\tvar offset = r.ReadInt32();\r\n\t\tvar bytes = r.ReadInt32();\r\n\t\tif (count < 0 || count > 2000000 || stride < 1 || stride > 1024 || bytes != count * stride)\r\n\t\t\tthrow new InvalidOperationException(\"Unexpected serialized human clothing buffer.\");\r\n\t\tvar attributes = new Attribute[ac];\r\n\t\tfor (int i = 0; i < ac; i++)\r\n\t\t{\r\n\t\t\tr.BaseStream.Position = ap + ao + i * 56;\r\n\t\t\tvar name = Encoding.ASCII.GetString(r.ReadBytes(32)).TrimEnd('\\0');\r\n\t\t\tattributes[i] = new(name, r.ReadInt32(), r.ReadInt32(), r.ReadInt32());\r\n\t\t}\r\n\r\n\t\tr.BaseStream.Position = dp + offset;\r\n\t\treturn new(count, stride, attributes, r.ReadBytes(bytes));\r\n\t}\r\n\r\n\tstatic float F(byte[] b, int p) => BitConverter.ToSingle(b, p);\r\n\tstatic ushort U16(byte[] b, int p) => BitConverter.ToUInt16(b, p);\r\n\tstatic uint U32(byte[] b, int p) => BitConverter.ToUInt32(b, p);\r\n\tstatic Color32 Bytes(byte[] b, int p) => new(b[p], b[p + 1], b[p + 2], b[p + 3]);\r\n\tstatic Vector4 Values(byte[] b, int p, int format)\r\n\t{\r\n\t\tfloat HalfAt(int i) => (float)BitConverter.UInt16BitsToHalf(U16(b, p + i * 2));\r\n\t\treturn format switch\r\n\t\t{\r\n\t\t\t2 => new(F(b, p), F(b, p + 4), F(b, p + 8), F(b, p + 12)),\r\n\t\t\t6 => new(F(b, p), F(b, p + 4), F(b, p + 8), 0),\r\n\t\t\t16 => new(F(b, p), F(b, p + 4), 0, 0),\r\n\t\t\t10 => new(HalfAt(0), HalfAt(1), HalfAt(2), HalfAt(3)),\r\n\t\t\t34 => new(HalfAt(0), HalfAt(1), 0, 0),\r\n\t\t\t28 => new(b[p] / 255f, b[p + 1] / 255f, b[p + 2] / 255f, b[p + 3] / 255f),\r\n\t\t\t_ => throw new InvalidOperationException(\"Unsupported clothing vertex format \" + format)};\r\n\t}\r\n\r\n\tstatic SkinVertex[] Vertices(Buffer b, Vertex[] native, int boneCount)\r\n\t{\r\n\t\tvar vertices = new SkinVertex[b.Count];\r\n\t\tfor (int i = 0; i < b.Count; i++)\r\n\t\t{\r\n\t\t\tvar v = new SkinVertex\r\n\t\t\t{\r\n\t\t\t\tColor = new(255, 255, 255, 255),\r\n\t\t\t\tWeights = new(255, 0, 0, 0)\r\n\t\t\t};\r\n\t\t\tforeach (var a in b.Attributes)\r\n\t\t\t{\r\n\t\t\t\tvar p = i * b.Stride + a.Offset;\r\n\t\t\t\tswitch (a.Name)\r\n\t\t\t\t{\r\n\t\t\t\t\tcase \"POSITION\":\r\n\t\t\t\t\t\tvar xyz = Values(b.Data, p, a.Format);\r\n\t\t\t\t\t\tv.Position = new(xyz.x, xyz.y, xyz.z);\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase \"TEXCOORD\":\r\n\t\t\t\t\t\tif (a.Index == 0)\r\n\t\t\t\t\t\t\tv.UV = Values(b.Data, p, a.Format);\r\n\t\t\t\t\t\telse if (a.Index == 1)\r\n\t\t\t\t\t\t\tv.UV2 = Values(b.Data, p, a.Format);\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase \"BLENDINDICES\":\r\n\t\t\t\t\t\tif (a.Format == 30)\r\n\t\t\t\t\t\t\tv.Indices = Bytes(b.Data, p);\r\n\t\t\t\t\t\telse if (a.Format == 12)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar ids = Enumerable.Range(0, 4).Select(j => (int)U16(b.Data, p + j * 2)).ToArray();\r\n\t\t\t\t\t\t\tif (ids.Any(j => j >= boneCount || j > 255))\r\n\t\t\t\t\t\t\t\tthrow new InvalidOperationException(\"Clothing bone index exceeds fitting layout.\");\r\n\t\t\t\t\t\t\tv.Indices = new((byte)ids[0], (byte)ids[1], (byte)ids[2], (byte)ids[3]);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\tthrow new InvalidOperationException(\"Unsupported clothing bone-index format \" + a.Format);\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase \"BLENDWEIGHT\":\r\n\t\t\t\t\t\tif (a.Format != 28)\r\n\t\t\t\t\t\t\tthrow new InvalidOperationException(\"Unsupported clothing weight format \" + a.Format);\r\n\t\t\t\t\t\tv.Weights = Bytes(b.Data, p);\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase \"COLOR\":\r\n\t\t\t\t\t\tif (a.Format == 28)\r\n\t\t\t\t\t\t\tv.Color = Bytes(b.Data, p);\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase \"NORMAL\":\r\n\t\t\t\t\t\tif (a.Format == 6)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar n = Values(b.Data, p, a.Format);\r\n\t\t\t\t\t\t\tv.Normal = new(n.x, n.y, n.z);\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase \"TANGENT\":\r\n\t\t\t\t\t\tv.Tangent = Values(b.Data, p, a.Format);\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (v.Normal.LengthSquared < .01f && native is not null && native.Length == b.Count && (native[i].Position - v.Position).Length < .001f)\r\n\t\t\t{\r\n\t\t\t\tv.Normal = native[i].Normal;\r\n\t\t\t\tv.Tangent = native[i].Tangent;\r\n\t\t\t}\r\n\r\n\t\t\tif (new[]\r\n\t\t\t{\r\n\t\t\t\tv.Indices.r,\r\n\t\t\t\tv.Indices.g,\r\n\t\t\t\tv.Indices.b,\r\n\t\t\t\tv.Indices.a\r\n\t\t\t}.Any(j => j >= boneCount))\r\n\t\t\t\tthrow new InvalidOperationException(\"Serialized garment bone map does not match its skeleton.\");\r\n\t\t\tvertices[i] = v;\r\n\t\t}\r\n\r\n\t\treturn vertices;\r\n\t}\r\n\r\n\tstatic int[] Indices(Buffer b) => Enumerable.Range(0, b.Count).Select(i => b.Stride == 2 ? (int)U16(b.Data, i * 2) : checked((int)U32(b.Data, i * 4))).ToArray();\r\n\tstatic Vector3[] Normals(Vector3[] p, int[] indices)\r\n\t{\r\n\t\tvar n = new Vector3[p.Length];\r\n\t\tfor (int i = 0; i + 2 < indices.Length; i += 3)\r\n\t\t{\r\n\t\t\tvar a = indices[i];\r\n\t\t\tvar b = indices[i + 1];\r\n\t\t\tvar c = indices[i + 2];\r\n\t\t\tvar cross = Vector3.Cross(p[b] - p[a], p[c] - p[a]);\r\n\t\t\tn[a] += cross;\r\n\t\t\tn[b] += cross;\r\n\t\t\tn[c] += cross;\r\n\t\t}\r\n\r\n\t\treturn n.Select(v => v.Normal).ToArray();\r\n\t}\r\n\r\n\tstatic Vector4[] Tangents(SkinVertex[] vertices, Vector3[] p, Vector3[] normals, int[] indices)\r\n\t{\r\n\t\tvar t = new Vector3[p.Length];\r\n\t\tvar b = new Vector3[p.Length];\r\n\t\tfor (int i = 0; i + 2 < indices.Length; i += 3)\r\n\t\t{\r\n\t\t\tint a = indices[i], c = indices[i + 1], d = indices[i + 2];\r\n\t\t\tvar e = p[c] - p[a];\r\n\t\t\tvar f = p[d] - p[a];\r\n\t\t\tvar uv = vertices[c].UV - vertices[a].UV;\r\n\t\t\tvar uw = vertices[d].UV - vertices[a].UV;\r\n\t\t\tfloat determinant = uv.x * uw.y - uv.y * uw.x;\r\n\t\t\tif (MathF.Abs(determinant) < 1e-8f)\r\n\t\t\t\tcontinue;\r\n\t\t\tvar tangent = (e * uw.y - f * uv.y) / determinant;\r\n\t\t\tvar bitangent = (f * uv.x - e * uw.x) / determinant;\r\n\t\t\tforeach (int id in new[]\r\n\t\t\t{\r\n\t\t\t\ta,\r\n\t\t\t\tc,\r\n\t\t\t\td\r\n\t\t\t}\r\n\r\n\t\t\t)\r\n\t\t\t{\r\n\t\t\t\tt[id] += tangent;\r\n\t\t\t\tb[id] += bitangent;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn Enumerable.Range(0, p.Length).Select(i =>\r\n\t\t{\r\n\t\t\tvar n = normals[i];\r\n\t\t\tvar tangent = (t[i] - n * Vector3.Dot(n, t[i])).Normal;\r\n\t\t\treturn new Vector4(tangent, Vector3.Dot(Vector3.Cross(n, tangent), b[i]) < 0 ? -1 : 1);\r\n\t\t}).ToArray();\r\n\t}\r\n\r\n\tstatic Model Build(Model source, bool female, bool neckOnly = false, bool fitFace = true)\r\n\t{\r\n\t\tvar buffers = ReadBuffers(source);\r\n\t\tvar builder = Model.Builder.WithName(\"automatic-human-fit:\" + source.Name + \":\" + (neckOnly ? \"replacement-neck\" : female ? \"female\" : \"male\"));\r\n\t\tfor (int i = 0; i < source.BoneCount; i++)\r\n\t\t{\r\n\t\t\tvar transform = source.GetBoneTransform(i);\r\n\t\t\tvar parent = source.GetBoneParent(i);\r\n\t\t\tbuilder.AddBone(source.GetBoneName(i), transform.Position, transform.Rotation, parent < 0 ? null : source.GetBoneName(parent));\r\n\t\t}\r\n\r\n\t\tvar native = source.GetVertices();\r\n\t\tvar baseMaterials = (source.MaterialGroupCount > 0 ? source.GetMaterials(0) : source.Materials).ToArray();\r\n\t\tvar meshMaterials = new List<Material>();\r\n\t\tvar work = new List<Action>();\r\n\t\tvar clothMale = new List<Vector3>();\r\n\t\tvar clothFemale = new List<Vector3>();\r\n\t\tvar clothIndices = new List<int>();\r\n\t\tvar head = neckOnly || !fitFace ? null : HeadSurface();\r\n\t\tfor (int mi = 0; mi < buffers.Count; mi++)\r\n\t\t{\r\n\t\t\tvar info = source.MeshInfo.Meshes[mi];\r\n\t\t\tif (neckOnly && !info.Name.StartsWith(\"head\", StringComparison.OrdinalIgnoreCase)) continue;\r\n\t\t\tvar buffer = buffers[mi];\r\n\t\t\tvar boneMap = buffer.Bones.Select(name => Enumerable.Range(0, source.BoneCount).First(i => source.GetBoneName(i) == name)).ToArray();\r\n\t\t\tint callIndex = 0;\r\n\t\t\tforeach (var call in info.DrawCalls)\r\n\t\t\t{\r\n\t\t\t\t// SaveToVmdl emits individual buffers in draw-call order when a mesh uses\r\n\t\t\t\t// separate streams (its metadata currently labels each stream as zero).\r\n\t\t\t\tint vb = buffer.Vertices.Length == 1 ? 0 : callIndex;\r\n\t\t\t\tint ib = buffer.Indices.Length == 1 ? 0 : callIndex;\r\n\t\t\t\tif (vb >= buffer.Vertices.Length || ib >= buffer.Indices.Length)\r\n\t\t\t\t\tthrow new InvalidOperationException(\"Unexpected garment draw-call buffer mapping.\");\r\n\t\t\t\tvar verts = Vertices(buffer.Vertices[vb], mi == 0 && buffer.Vertices.Length == 1 ? native : null, buffer.Bones.Length);\r\n\t\t\t\tvar indices = Indices(buffer.Indices[ib]);\r\n\t\t\t\tfor (int i = 0; i < verts.Length; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar ids = verts[i].Indices;\r\n\t\t\t\t\tverts[i].Indices = new((byte)boneMap[ids.r], (byte)boneMap[ids.g], (byte)boneMap[ids.b], (byte)boneMap[ids.a]);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tcallIndex++;\r\n\t\t\t\tvar selectedIndices = indices.Skip(call.StartIndex).Take(call.Indices).Select(i => i + call.BaseVertex).ToArray();\r\n\t\t\t\tif (neckOnly)\r\n\t\t\t\t\tselectedIndices = Enumerable.Range(0, selectedIndices.Length / 3)\r\n\t\t\t\t\t\t.Where(t => Enumerable.Range(0, 3).All(j => verts[selectedIndices[t * 3 + j]].Position.z <= 60))\r\n\t\t\t\t\t\t.SelectMany(t => selectedIndices.Skip(t * 3).Take(3)).ToArray();\r\n\t\t\t\tif (selectedIndices.Length == 0) continue;\r\n\t\t\t\tvar used = selectedIndices.Distinct().ToArray();\r\n\t\t\t\tvar lookup = used.Select((id, i) => (id, i)).ToDictionary(x => x.id, x => x.i);\r\n\t\t\t\tvar localIndices = selectedIndices.Select(i => lookup[i]).ToArray();\r\n\t\t\t\tvar v = used.Select(i => verts[i]).ToArray();\r\n\t\t\t\tvar original = v.Select(x => x.Position).ToArray();\r\n\t\t\t\tvar male = new Vector3[v.Length];\r\n\t\t\t\tvar target = new Vector3[v.Length];\r\n\t\t\t\tbool skin = neckOnly || call.Material?.Name.Contains(\"/bodies/\", StringComparison.OrdinalIgnoreCase) == true;\r\n\t\t\t\tfor (int i = 0; i < v.Length; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar p = original[i];\r\n\t\t\t\t\tif (!skin && !neckOnly && fitFace && p.z >= 57 && p.z <= 73)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar hit = head.Nearest(p);\r\n\t\t\t\t\t\tif (hit.Distance < 1.2f && hit.Distance < originalMale.Nearest(p).Distance)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar normal = head.FaceNormal(hit.Triangle);\r\n\t\t\t\t\t\t\tfloat clearance = Vector3.Dot(p - hit.Point, normal);\r\n\t\t\t\t\t\t\t// Close face coverings need space for the difference in falloff\r\n\t\t\t\t\t\t\t// between the skin and fabric under inherited facial deformers.\r\n\t\t\t\t\t\t\tfloat margin = .60f * Math.Clamp((62.2f - p.z) / 1.0f, 0, 1);\r\n\t\t\t\t\t\t\tif (margin > 0) p += normal * Math.Clamp(margin - clearance, 0, .85f);\r\n\t\t\t\t\t\t\tmale[i] = target[i] = p;\r\n\t\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (p.z > 60)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tmale[i] = p;\r\n\t\t\t\t\t\ttarget[i] = p;\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tvar from = (neckOnly ? unifiedMale : female ? unifiedFemale : originalMale).Nearest(p);\r\n\t\t\t\t\tvar mapped = female || neckOnly ? from : unifiedMale.Nearest(from.Point);\r\n\t\t\t\t\tvar m = unifiedMale.Interpolate(mapped.Triangle, mapped.Barycentric);\r\n\t\t\t\t\tvar f = unifiedFemale.Interpolate(mapped.Triangle, mapped.Barycentric);\r\n\t\t\t\t\t// Preserve authored clearance, including compressed skin under clothing.\r\n\t\t\t\t\tvar offset = p - from.Point;\r\n\t\t\t\t\tbool snap = skin && offset.Length < .12f;\r\n\t\t\t\t\tmale[i] = m + (snap ? Vector3.Zero : offset);\r\n\t\t\t\t\ttarget[i] = f + (snap ? Vector3.Zero : offset);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tint clothOffset = clothMale.Count;\r\n\t\t\t\tif (!skin)\r\n\t\t\t\t{\r\n\t\t\t\t\tclothIndices.AddRange(localIndices.Select(i => i + clothOffset));\r\n\t\t\t\t\tclothMale.AddRange(male);\r\n\t\t\t\t\tclothFemale.AddRange(target);\r\n\t\t\t\t}\r\n\r\n\t\t\t\twork.Add(() =>\r\n\t\t\t\t{\r\n\t\t\t\t\tif (!skin)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfor (int i = 0; i < v.Length; i++)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tmale[i] = clothMale[clothOffset + i];\r\n\t\t\t\t\t\t\ttarget[i] = clothFemale[clothOffset + i];\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tvar originalNormals = Normals(original, localIndices);\r\n\t\t\t\t\tvar maleNormals = Normals(male, localIndices);\r\n\t\t\t\t\tvar femaleNormals = Normals(target, localIndices);\r\n\t\t\t\t\tvar tangents = Tangents(v, male, maleNormals, localIndices);\r\n\t\t\t\t\tvar morph = new MorphDelta[v.Length];\r\n\t\t\t\t\tfor (int i = 0; i < v.Length; i++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (v[i].Normal.LengthSquared < .01f)\r\n\t\t\t\t\t\t\tv[i].Normal = originalNormals[i];\r\n\t\t\t\t\t\tv[i].Normal = (v[i].Normal + maleNormals[i] - originalNormals[i]).Normal;\r\n\t\t\t\t\t\tv[i].Tangent = tangents[i];\r\n\t\t\t\t\t\tv[i].Position = male[i];\r\n\t\t\t\t\t\tmorph[i] = new(i, target[i] - male[i], femaleNormals[i] - maleNormals[i]);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tvar mesh = new Mesh(call.Material, (MeshPrimitiveType)call.PrimitiveType);\r\n\t\t\t\t\tmesh.UvDensity = call.UvDensity;\r\n\t\t\t\t\tmesh.CreateVertexBuffer(v.Length, v.ToList());\r\n\t\t\t\t\tmesh.CreateIndexBuffer(localIndices.Length, localIndices.ToList());\r\n\t\t\t\t\tmesh.AddMorph(Morph, morph);\r\n\t\t\t\t\tmesh.Bounds = new BBox(male.Concat(target).Aggregate((a, b) => Vector3.Min(a, b)), male.Concat(target).Aggregate((a, b) => Vector3.Max(a, b)));\r\n\t\t\t\t\tvar lods = info.LodMask;\r\n\t\t\t\t\tif (source.MeshInfo.LodCount == 0)\r\n\t\t\t\t\t\tbuilder.AddMesh(mesh);\r\n\t\t\t\t\telse\r\n\t\t\t\t\t\tfor (int lod = 0; lod < source.MeshInfo.LodCount; lod++)\r\n\t\t\t\t\t\t\tif ((lods & (1 << lod)) != 0)\r\n\t\t\t\t\t\t\t\tbuilder.AddMesh(mesh, lod);\r\n\t\t\t\t\tmeshMaterials.Add(call.Material);\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (clothMale.Count > 0)\r\n\t\t{\r\n\t\t\tvar m = clothMale.ToArray();\r\n\t\t\tvar f = clothFemale.ToArray();\r\n\t\t\tvar t = clothIndices.ToArray();\r\n\t\t\tClearTriangles(unifiedMale, m, t);\r\n\t\t\tClearTriangles(unifiedFemale, f, t);\r\n\t\t\tclothMale = m.ToList();\r\n\t\t\tclothFemale = f.ToList();\r\n\t\t}\r\n\r\n\t\tforeach (var build in work)\r\n\t\t\tbuild();\r\n\t\tfor (int group = 0; group < source.MaterialGroupCount; group++)\r\n\t\t{\r\n\t\t\tvar materials = source.GetMaterials(group).ToArray();\r\n\t\t\tvar g = builder.AddMaterialGroup(source.GetMaterialGroupName(group));\r\n\t\t\tforeach (var material in meshMaterials)\r\n\t\t\t{\r\n\t\t\t\tvar index = Array.IndexOf(baseMaterials, material);\r\n\t\t\t\tg.AddMaterial(index >= 0 && index < materials.Length ? materials[index] : material);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tfor (int lod = 1; lod < source.MeshInfo.LodSwitchDistances.Length; lod++)\r\n\t\t\tbuilder.WithLodDistance(lod, source.MeshInfo.LodSwitchDistances[lod]);\r\n\t\treturn builder.Create();\r\n\t}\r\n\r\n\tstatic void ClearTriangles(Surface body, Vector3[] points, int[] triangles)\r\n\t{\r\n\t\t// A smooth spatial clearance field moves folded/layered fabric together.\r\n\t\t// Local per-triangle pushes can pull an inner layer through its outer flap.\r\n\t\tvar source = points.ToArray();\r\n\t\tvar min = points.Aggregate((a, b) => Vector3.Min(a, b)) - Vector3.One * 1.2f;\r\n\t\tvar max = points.Aggregate((a, b) => Vector3.Max(a, b)) + Vector3.One * 1.2f;\r\n\t\tvar candidates = body.Points.Where(p => p.x >= min.x && p.x <= max.x && p.y >= min.y && p.y <= max.y && p.z >= min.z && p.z <= max.z).ToArray();\r\n\t\tvar pushes = new List<(Vector3 Point, Vector3 Push)>();\r\n\t\tvar fabric = new Surface(points, points, triangles, Array.Empty<string>(), Array.Empty<(int, float)[]>());\r\n\t\tforeach (var p in candidates)\r\n\t\t{\r\n\t\t\tvar hit = fabric.Nearest(p);\r\n\t\t\tif (hit.Distance > 1.2f)\r\n\t\t\t\tcontinue;\r\n\t\t\tvar w = hit.Barycentric;\r\n\t\t\tif (MathF.Min(w.x, MathF.Min(w.y, w.z)) < .015f)\r\n\t\t\t\tcontinue;\r\n\t\t\tvar n = body.FaceNormal(body.Nearest(p).Triangle);\r\n\t\t\tif (MathF.Abs(Vector3.Dot(n, fabric.FaceNormal(hit.Triangle))) < .25f)\r\n\t\t\t\tcontinue;\r\n\t\t\tvar penetration = Vector3.Dot(p - hit.Point, n) + .14f;\r\n\t\t\tif (penetration <= 0 || penetration > 1.2f)\r\n\t\t\t\tcontinue;\r\n\t\t\tpushes.Add((hit.Point, n * MathF.Min(.7f, penetration + .04f)));\r\n\t\t}\r\n\r\n\t\t// Spatial buckets bound the search and apply identical displacement to UV\r\n\t\t// duplicates, overlapping material sections, and neighbouring LOD vertices.\r\n\t\tconst float radius = 2.5f;\r\n\t\t(int, int, int) Cell(Vector3 p) => ((int)MathF.Floor(p.x / radius), (int)MathF.Floor(p.y / radius), (int)MathF.Floor(p.z / radius));\r\n\t\tvar buckets = pushes.GroupBy(p => Cell(p.Point)).ToDictionary(g => g.Key, g => g.ToArray());\r\n\t\tfor (int i = 0; i < points.Length; i++)\r\n\t\t{\r\n\t\t\tvar cell = Cell(source[i]);\r\n\t\t\tVector3 best = default;\r\n\t\t\tfor (int x = -1; x <= 1; x++)\r\n\t\t\t\tfor (int y = -1; y <= 1; y++)\r\n\t\t\t\t\tfor (int z = -1; z <= 1; z++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (!buckets.TryGetValue((cell.Item1 + x, cell.Item2 + y, cell.Item3 + z), out var values))\r\n\t\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t\tforeach (var p in values)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar d = (source[i] - p.Point).Length / radius;\r\n\t\t\t\t\t\t\tif (d >= 1)\r\n\t\t\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t\t\tvar falloff = 1 - d * d * (3 - 2 * d);\r\n\t\t\t\t\t\t\tvar push = p.Push * falloff;\r\n\t\t\t\t\t\t\tif (push.LengthSquared > best.LengthSquared)\r\n\t\t\t\t\t\t\t\tbest = push;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\tpoints[i] += best;\r\n\t\t}\r\n\t}\r\n\r\n\treadonly record struct Hit(int Triangle, Vector3 Barycentric, Vector3 Point, float Distance);\r\n\tsealed class Surface\r\n\t{\r\n\t\tpublic readonly Vector3[] Points, Female;\r\n\t\tpublic readonly int[] Triangles;\r\n\t\tpublic readonly string[] Names;\r\n\t\tpublic readonly (int, float)[][] Weights;\r\n\t\treadonly Node tree;\r\n\t\tsealed class Node\r\n\t\t{\r\n\t\t\tpublic Vector3 Min, Max;\r\n\t\t\tpublic Node Left, Right;\r\n\t\t\tpublic int[] Faces;\r\n\t\t}\r\n\r\n\t\tpublic Surface(Vector3[] p, Vector3[] f, int[] t, string[] names, (int, float)[][] weights)\r\n\t\t{\r\n\t\t\tPoints = p;\r\n\t\t\tFemale = f;\r\n\t\t\tTriangles = t;\r\n\t\t\tNames = names;\r\n\t\t\tWeights = weights;\r\n\t\t\ttree = BuildTree(Enumerable.Range(0, t.Length / 3).ToArray());\r\n\t\t}\r\n\r\n\t\tVector3 A(int t, int corner) => Points[Triangles[t * 3 + corner]];\r\n\t\tNode BuildTree(int[] faces)\r\n\t\t{\r\n\t\t\tvar p = faces.SelectMany(i => new[] { A(i, 0), A(i, 1), A(i, 2) }).ToArray();\r\n\t\t\tvar node = new Node\r\n\t\t\t{\r\n\t\t\t\tMin = p.Aggregate((a, b) => Vector3.Min(a, b)),\r\n\t\t\t\tMax = p.Aggregate((a, b) => Vector3.Max(a, b))\r\n\t\t\t};\r\n\t\t\tif (faces.Length <= 12)\r\n\t\t\t{\r\n\t\t\t\tnode.Faces = faces;\r\n\t\t\t\treturn node;\r\n\t\t\t}\r\n\r\n\t\t\tvar extent = node.Max - node.Min;\r\n\t\t\tint axis = extent.x > extent.y ? (extent.x > extent.z ? 0 : 2) : (extent.y > extent.z ? 1 : 2);\r\n\t\t\tfloat Coord(Vector3 v) => axis == 0 ? v.x : axis == 1 ? v.y : v.z;\r\n\t\t\tvar sorted = faces.OrderBy(i => Coord(A(i, 0) + A(i, 1) + A(i, 2))).ToArray();\r\n\t\t\tint half = sorted.Length / 2;\r\n\t\t\tnode.Left = BuildTree(sorted.Take(half).ToArray());\r\n\t\t\tnode.Right = BuildTree(sorted.Skip(half).ToArray());\r\n\t\t\treturn node;\r\n\t\t}\r\n\r\n\t\tpublic Vector3 Interpolate(int t, Vector3 w) => A(t, 0) * w.x + A(t, 1) * w.y + A(t, 2) * w.z;\r\n\t\tpublic Vector3 FaceNormal(int t) => Vector3.Cross(A(t, 1) - A(t, 0), A(t, 2) - A(t, 0)).Normal;\r\n\t\tstatic float Distance(Node n, Vector3 p) => (p - Vector3.Max(n.Min, Vector3.Min(n.Max, p))).LengthSquared;\r\n\t\tpublic Hit Nearest(Vector3 p)\r\n\t\t{\r\n\t\t\tfloat best = float.MaxValue;\r\n\t\t\tint face = 0;\r\n\t\t\tVector3 bary = default, point = default;\r\n\t\t\tvoid Search(Node node)\r\n\t\t\t{\r\n\t\t\t\tif (Distance(node, p) > best)\r\n\t\t\t\t\treturn;\r\n\t\t\t\tif (node.Faces is null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar a = node.Left;\r\n\t\t\t\t\tvar b = node.Right;\r\n\t\t\t\t\tif (Distance(a, p) > Distance(b, p))\r\n\t\t\t\t\t\t(a, b) = (b, a);\r\n\t\t\t\t\tSearch(a);\r\n\t\t\t\t\tSearch(b);\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tforeach (var t in node.Faces)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar w = Closest(p, A(t, 0), A(t, 1), A(t, 2));\r\n\t\t\t\t\tvar q = Interpolate(t, w);\r\n\t\t\t\t\tvar distance = (p - q).LengthSquared;\r\n\t\t\t\t\tif (distance < best)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tbest = distance;\r\n\t\t\t\t\t\tface = t;\r\n\t\t\t\t\t\tbary = w;\r\n\t\t\t\t\t\tpoint = q;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tSearch(tree);\r\n\t\t\treturn new(face, bary, point, MathF.Sqrt(best));\r\n\t\t}\r\n\r\n\t\tstatic Vector3 Closest(Vector3 p, Vector3 a, Vector3 b, Vector3 c)\r\n\t\t{\r\n\t\t\tvar ab = b - a;\r\n\t\t\tvar ac = c - a;\r\n\t\t\tvar ap = p - a;\r\n\t\t\tfloat d1 = Vector3.Dot(ab, ap), d2 = Vector3.Dot(ac, ap);\r\n\t\t\tif (d1 <= 0 && d2 <= 0)\r\n\t\t\t\treturn new(1, 0, 0);\r\n\t\t\tvar bp = p - b;\r\n\t\t\tfloat d3 = Vector3.Dot(ab, bp), d4 = Vector3.Dot(ac, bp);\r\n\t\t\tif (d3 >= 0 && d4 <= d3)\r\n\t\t\t\treturn new(0, 1, 0);\r\n\t\t\tfloat vc = d1 * d4 - d3 * d2;\r\n\t\t\tif (vc <= 0 && d1 >= 0 && d3 <= 0)\r\n\t\t\t{\r\n\t\t\t\tvar v = d1 / (d1 - d3);\r\n\t\t\t\treturn new(1 - v, v, 0);\r\n\t\t\t}\r\n\r\n\t\t\tvar cp = p - c;\r\n\t\t\tfloat d5 = Vector3.Dot(ab, cp), d6 = Vector3.Dot(ac, cp);\r\n\t\t\tif (d6 >= 0 && d5 <= d6)\r\n\t\t\t\treturn new(0, 0, 1);\r\n\t\t\tfloat vb = d5 * d2 - d1 * d6;\r\n\t\t\tif (vb <= 0 && d2 >= 0 && d6 <= 0)\r\n\t\t\t{\r\n\t\t\t\tvar w = d2 / (d2 - d6);\r\n\t\t\t\treturn new(1 - w, 0, w);\r\n\t\t\t}\r\n\r\n\t\t\tfloat va = d3 * d6 - d5 * d4;\r\n\t\t\tif (va <= 0 && d4 - d3 >= 0 && d5 - d6 >= 0)\r\n\t\t\t{\r\n\t\t\t\tvar w = (d4 - d3) / (d4 - d3 + d5 - d6);\r\n\t\t\t\treturn new(0, 1 - w, w);\r\n\t\t\t}\r\n\r\n\t\t\tfloat sum = va + vb + vc;\r\n\t\t\tif (MathF.Abs(sum) < 1e-12f)\r\n\t\t\t\treturn new(1, 0, 0);\r\n\t\t\treturn new(va / sum, vb / sum, vc / sum);\r\n\t\t}\r\n\t}\r\n}// Runtime fitting integration.\r\n\r\n"
        },
        {
            "Ident": "vendetta.character_editor",
            "Path": "CharacterKit/CharacterDeformation.cs",
            "FileName": "CharacterDeformation.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 410954,
            "IsPrivate": false,
            "Code": "using System;\r\n\r\nnamespace CharacterEditor;\r\n/// <summary>Stable native rest-space volumes; shape edits update coefficients without resetting merged skeletons.</summary>\r\npublic static class CharacterDeformation\r\n{\r\n\tpublic const int VolumeCount = 64;\r\n\treadonly record struct Edit(string Name, Vector3 Center, float Radius, Vector3 MaskScale, Vector3 Scale, Vector3 Translation, float Pitch = 0, float Roll = 0);\r\n\t// Native orthographic front/profile captures locate the vermilion separately\r\n\t// from surrounding skin. Resource identity, not display name, selects anatomy.\r\n\treadonly record struct MouthAnatomy(float SurfaceX, float PhiltrumZ, float PhiltrumHeight, float ColumellaX, float ColumellaZ, float UpperZ, float UpperHeight, float LowerZ, float LowerHeight);\r\n\tstatic MouthAnatomy MouthFor(string skin) => skin?.ToLowerInvariant() switch\r\n\t{\r\n\t\t\"models/citizen_clothes/body/human01.clothing\" => new(5.8f, 62.35f, 0.28f, 5.85f, 62.82f, 61.88f, 0.26f, 61.38f, 0.21f),\r\n\t\t\"models/citizen_clothes/body/human02.clothing\" => new(5.5f, 62.3f, 0.22f, 5.7f, 62.65f, 61.82f, 0.25f, 61.27f, 0.23f),\r\n\t\t\"models/citizen_clothes/body/human04.clothing\" => new(5.5f, 62.17f, 0.21f, 5.75f, 62.58f, 61.76f, 0.21f, 61.30f, 0.23f),\r\n\t\t\"models/citizen_clothes/body/human05.clothing\" => new(5.7f, 62.26f, 0.23f, 5.85f, 62.67f, 61.79f, 0.20f, 61.43f, 0.16f),\r\n\t\t\"models/citizen_clothes/body/human06.clothing\" => new(5.75f, 62.35f, 0.28f, 5.8f, 62.82f, 61.87f, 0.26f, 61.39f, 0.22f),\r\n\t\t\"models/citizen_clothes/body/human07.clothing\" => new(5.7f, 62.26f, 0.23f, 5.75f, 62.72f, 61.80f, 0.20f, 61.42f, 0.21f),\r\n\t\t_ => new(5.8f, 62.35f, 0.28f, 5.85f, 62.82f, 61.80f, 0.22f, 61.43f, 0.16f)};\r\n\t// Rest-space side landmarks run down and forward from below the ear to the chin.\r\n\t// Pitch aligns the narrow masks with that slope rather than a horizontal neck band.\r\n\treadonly record struct JawAnatomy(float AngleZ, float MiddleZ, float FrontZ, float Width);\r\n\tstatic JawAnatomy JawFor(string skin) => skin?.ToLowerInvariant() switch\r\n\t{\r\n\t\t\"models/citizen_clothes/body/human01.clothing\" => new(61.65f, 61.1f, 60.4f, 1),\r\n\t\t\"models/citizen_clothes/body/human02.clothing\" => new(61.65f, 61.1f, 60.4f, 0.93f),\r\n\t\t\"models/citizen_clothes/body/human04.clothing\" => new(61.65f, 61.1f, 60.4f, 0.92f),\r\n\t\t\"models/citizen_clothes/body/human05.clothing\" or \"models/citizen_clothes/body/human07.clothing\" => new(61.65f, 61.2f, 60.4f, 0.97f),\r\n\t\t_ => new(61.65f, 61.1f, 60.4f, 1)};\r\n\tstatic Edit[] Edits(CharacterAppearanceData a)\r\n\t{\r\n\t\ta = PhotoFace.ApplyOffsets(a);\r\n\t\tvar headWeights = a.BaseShape?.Normalized();\r\n\t\tvar regionWeights = new Dictionary<string, float[]>();\r\n\t\tfloat Blend(Func<string, float> f) => headWeights is null ? f(a.SkinPath) : AppearanceCatalog.Skins.Select((s, i) => f(s.ResourcePath) * headWeights[i]).Sum();\r\n\t\tfloat BlendRegion(string region, Func<string, float> f)\r\n\t\t{\r\n\t\t\tif (a.BaseShape is null)\r\n\t\t\t\treturn Blend(f);\r\n\t\t\tif (!regionWeights.TryGetValue(region, out var weights))\r\n\t\t\t\tregionWeights[region] = weights = FaceRegions.Weights(a.BaseShape, region);\r\n\t\t\treturn AppearanceCatalog.Skins.Select((s, i) => f(s.ResourcePath) * weights[i]).Sum();\r\n\t\t}\r\n\r\n\t\tfloat Signed(float value) => (value - 0.5f) * 2;\r\n\t\tVector3 Scale(Vector3 maximum, float value) => Vector3.One + (maximum - Vector3.One) * Signed(value);\r\n\t\tfloat Extended(float value, float maximum) => Signed(value) * (value > 0.5f ? maximum : 1);\r\n\t\tfloat Range(float value, float negative, float positive) => Signed(value) * (value < 0.5f ? negative : positive);\r\n\t\tVector3 AmountScale(Vector3 maximum, float amount) => Vector3.One + (maximum - Vector3.One) * amount;\r\n\t\t// Preserve the neutral shape; map the new endpoints to the old 30% and 90%.\r\n\t\tvar chest = Signed(a.ChestSize) * (a.ChestSize < 0.5f ? 0.4f : 0.8f);\r\n\t\tVector3 Build(Vector3 soft, Vector3 athletic) => Vector3.One + ((a.Fitness < 0.5f ? soft : athletic) - Vector3.One) * MathF.Abs(Signed(a.Fitness));\r\n\t\t// A gentle resize range and broader mask keep the already-tapered rim from collapsing or hitting the finite boundary.\r\n\t\tvar earScale = Scale(new(1.35f, 1.35f, 1.35f), a.EarSize);\r\n\t\tvar earPivotCorrection = 0.65f / 1.05f * (earScale.y - 1);\r\n\t\tvar maleAmount = UnifiedHuman.MaleAmount(a);\r\n\t\tvar female = a.BaseShape is not null || AppearanceCatalog.IsFemale(a);\r\n\t\tvar contour = a.BaseShape is null ? Extended(a.BodyFeatureSize, female ? 1.3f : 1) : Extended(a.BaseShape.BreastSize, 1.3f) * (1 - maleAmount);\r\n\t\tvar butt = Extended(a.ButtSize, 1.3f);\r\n\t\t// Face 4 needs a lower, taller cap encompassing its nostril wing rather than the patch above it.\r\n\t\tvar smallWings = BlendRegion(\"nose\", s => string.Equals(s, \"models/citizen_clothes/body/human04.clothing\", StringComparison.OrdinalIgnoreCase) ? 1 : 0);\r\n\t\tVector3 WingCenter(float side) => Vector3.Lerp(new(5.45f, side * .85f, 63.1f), new(5.5f, side * .6f, 62.82f), smallWings);\r\n\t\tvar wingMask = Vector3.Lerp(new(.7f, .6f, .48f), new(.65f, .45f, .46f), smallWings);\r\n\t\tvar mouth = new MouthAnatomy(BlendRegion(\"mouth\", s => MouthFor(s).SurfaceX), BlendRegion(\"mouth\", s => MouthFor(s).PhiltrumZ), BlendRegion(\"mouth\", s => MouthFor(s).PhiltrumHeight), BlendRegion(\"nose\", s => MouthFor(s).ColumellaX), BlendRegion(\"nose\", s => MouthFor(s).ColumellaZ), BlendRegion(\"mouth\", s => MouthFor(s).UpperZ), BlendRegion(\"mouth\", s => MouthFor(s).UpperHeight), BlendRegion(\"mouth\", s => MouthFor(s).LowerZ), BlendRegion(\"mouth\", s => MouthFor(s).LowerHeight));\r\n\t\tvar jaw = new JawAnatomy(BlendRegion(\"jaw\", s => JawFor(s).AngleZ), BlendRegion(\"jaw\", s => JawFor(s).MiddleZ), BlendRegion(\"jaw\", s => JawFor(s).FrontZ), BlendRegion(\"jaw\", s => JawFor(s).Width));\r\n\t\tvar cheekbones = Extended(a.CheekboneWidth, 1.35f);\r\n\t\tvar chinProjection = Range(a.ChinSize, 1.12f, 1.45f);\r\n\t\tvar chinHeight = Range(a.ChinHeight, 1.8f, 1.2f);\r\n\t\tvar mouthProjection = Extended(a.MouthProjection, 1.4f);\r\n\t\tvar upperLip = Extended(a.UpperLipFullness, 1.4f);\r\n\t\tvar lowerLip = Extended(a.LowerLipFullness, 1.4f);\r\n\t\tvar brow = Range(a.BrowProminence, 1.12f, 1.4f);\r\n\t\tvar philtrumLipClearance = mouth.UpperZ - mouth.LowerZ < 0.4f ? 0.04f : 0;\r\n\t\t// Raise only the upper mask boundary; retain the accepted lower lip reach.\r\n\t\tvar philtrumUpperExtension = MathF.Min(0.18f, mouth.ColumellaZ - mouth.PhiltrumZ - 0.24f);\r\n\t\tvar headScale = Scale(new(1.06f, 1.08f, 1.025f), a.HeadShape);\r\n\t\t// Rest-space underside landmarks differ from the chin tip and anterior\r\n\t\t// throat. Fit this cap to each head, then follow the preceding head scale.\r\n\t\tvar submentalZ = BlendRegion(\"jaw\", s => s?.ToLowerInvariant() switch\r\n\t\t{\r\n\t\t\t\"models/citizen_clothes/body/human01.clothing\" => 59.37f,\r\n\t\t\t\"models/citizen_clothes/body/human02.clothing\" => 59.92f,\r\n\t\t\t\"models/citizen_clothes/body/human04.clothing\" => 59.74f,\r\n\t\t\t\"models/citizen_clothes/body/human05.clothing\" => 59.43f,\r\n\t\t\t\"models/citizen_clothes/body/human06.clothing\" => 59.5f,\r\n\t\t\t\"models/citizen_clothes/body/human07.clothing\" => 59.59f,\r\n\t\t\t_ => 59.35f\r\n\t\t});\r\n\t\tvar jawWidth = Extended(a.JawWidth, 2);\r\n\t\tEdit JawEdit(string name, Vector3 center, Vector3 mask, float translation, float pitch)\r\n\t\t{\r\n\t\t\t// The head operation precedes the jaw. Follow its scaled landmarks so a\r\n\t\t\t// smaller head cannot move the ear/neck into a mask fitted to neutral.\r\n\t\t\tcenter = new(center.x * headScale.x, center.y * headScale.y, 63 + (center.z - 63) * headScale.z);\r\n\t\t\tmask *= headScale;\r\n\t\t\t// The stronger positive contour needs a little more vertical clearance\r\n\t\t\t// from the neck on very small heads. Minimum and neutral masks stay exact.\r\n\t\t\tmask.z *= 1 - 0.08f * MathF.Max(0, 1 - 2 * a.HeadShape) * MathF.Max(0, Signed(a.JawWidth));\r\n\t\t\treturn new(name, center, 1, mask, Vector3.One, new(0, translation * jawWidth, 0), pitch);\r\n\t\t}\r\n\r\n\t\tVector3 EarCenter(float side) => new(0.15f * headScale.x, side * 3.65f * headScale.y, 63 + 1.05f * headScale.z);\r\n\t\tvar earMask = new Vector3(1.15f * headScale.x, 0.5f * headScale.y, 1.6f * headScale.z);\r\n\t\tvar result = new[]\r\n\t\t{\r\n\t\t\tnew Edit(\"Neck size\", new(-0.25f, 0, 58), 1, new(4.2f, 4.2f, 2.9f), Scale(new(1.18f, 1.4f, 1), a.NeckSize), Vector3.Zero),\r\n\t\t\t// Preserve the accepted old shoulder-height operation as its own control.\r\n\t\t\tnew Edit(\"Left collarbone height\", new(-1.1f, 4.2f, 55.3f), 1, new(3.8f, 4, 2.4f), Scale(new(1.12f, 1.04f, 1.3f), a.CollarboneHeight), new(0, 0, 0.1f * Signed(a.CollarboneHeight))),\r\n\t\t\tnew Edit(\"Right collarbone height\", new(-1.1f, -4.2f, 55.3f), 1, new(3.8f, 4, 2.4f), Scale(new(1.12f, 1.04f, 1.3f), a.CollarboneHeight), new(0, 0, 0.1f * Signed(a.CollarboneHeight))),\r\n\t\t\t// Upper trapezius lies behind the clavicle and slopes from neck to shoulder.\r\n\t\t\tnew Edit(\"Left trapezius\", new(-1.8f, 4.3f, 57.6f + .3f * maleAmount), 1, new(3, 4, 1.25f), Vector3.One, new(-0.18f * Signed(a.TrapeziusSize), 0, 0.28f * Signed(a.TrapeziusSize)), 0, -28),\r\n\t\t\tnew Edit(\"Right trapezius\", new(-1.8f, -4.3f, 57.6f + .3f * maleAmount), 1, new(3, 4, 1.25f), Vector3.One, new(-0.18f * Signed(a.TrapeziusSize), 0, 0.28f * Signed(a.TrapeziusSize)), 0, 28),\r\n\t\t\tnew Edit(\"Waist size\", new(0, 0, 37), 17, new(1, 1, 0.8f), Scale(new(1.15f, 1.15f, 1), a.WaistSize), Vector3.Zero),\r\n\t\t\tnew Edit(\"Chest size\", new(0, 0, 51.5f), 1, new(7, 11.5f, 7.5f), AmountScale(new(1.18f, 1.25f, 1), chest), Vector3.Zero),\r\n\t\t\tnew Edit(\"Left chest shoulder\", new(0, 8, 53), 1, new(5.5f, 5, 5.5f), Vector3.One, new(0, 0.18f * chest, 0)),\r\n\t\t\tnew Edit(\"Right chest shoulder\", new(0, -8, 53), 1, new(5.5f, 5, 5.5f), Vector3.One, new(0, -0.18f * chest, 0)),\r\n\t\t\tnew Edit(\"Build midsection\", new(0, 0, 36), 15, new(1, 1, 0.75f), Build(new(1.16f, 1.18f, 1), new(0.94f, 0.95f, 1)), Vector3.Zero),\r\n\t\t\tnew Edit(\"Build chest\", new(1, 0, 51), 1, new(7, 10.5f, 7), Build(new(1.035f, 1.06f, 1), new(1.2f, 1.14f, 1.025f)), Vector3.Zero),\r\n\t\t\tnew Edit(\"Build left arm\", new(0, 11.5f, 49), 1, new(5, 5, 7), Build(new(1.015f, 1.04f, 1.02f), new(1.45f, 1.22f, 1.2f)), Vector3.Zero),\r\n\t\t\tnew Edit(\"Build right arm\", new(0, -11.5f, 49), 1, new(5, 5, 7), Build(new(1.015f, 1.04f, 1.02f), new(1.45f, 1.22f, 1.2f)), Vector3.Zero),\r\n\t\t\tnew Edit(\"Build left shoulder\", new(-0.5f, 8, 53), 1, new(5, 4.5f, 5), Build(new(1.02f, 1.02f, 1), new(1.3f, 1.25f, 1.15f)), Vector3.Zero),\r\n\t\t\tnew Edit(\"Build right shoulder\", new(-0.5f, -8, 53), 1, new(5, 4.5f, 5), Build(new(1.02f, 1.02f, 1), new(1.3f, 1.25f, 1.15f)), Vector3.Zero),\r\n\t\t\tnew Edit(\"Build left forearm\", new(0.05f, 17.4f, 41.6f), 1, new(3.1f, 4.4f, 3.8f), Build(Vector3.One, new(1.22f, 1.1f, 1.06f)), Vector3.Zero),\r\n\t\t\tnew Edit(\"Build right forearm\", new(0.05f, -17.4f, 41.6f), 1, new(3.1f, 4.4f, 3.8f), Build(Vector3.One, new(1.22f, 1.1f, 1.06f)), Vector3.Zero),\r\n\t\t\tnew Edit(\"Build left calf\", new(-2, 6.25f, 13), 1, new(3.4f, 2.8f, 6.5f), Build(Vector3.One, new(1.22f, 1.2f, 1)), Vector3.Zero),\r\n\t\t\tnew Edit(\"Build right calf\", new(-2, -6.25f, 13), 1, new(3.4f, 2.8f, 6.5f), Build(Vector3.One, new(1.22f, 1.2f, 1)), Vector3.Zero),\r\n\t\t\tnew Edit(\"Build left thigh\", new(0, 4, 22), 11, new(0.8f, 0.7f, 1), Build(new(1.06f, 1.08f, 1), new(1.12f, 1.12f, 1)), Vector3.Zero),\r\n\t\t\tnew Edit(\"Build right thigh\", new(0, -4, 22), 11, new(0.8f, 0.7f, 1), Build(new(1.06f, 1.08f, 1), new(1.12f, 1.12f, 1)), Vector3.Zero),\r\n\t\t\t// These surfaces remain covered by the native underwear body groups.\r\n\t\t\tnew Edit(\"Left body contour\", female ? new(4, 3.1f, 49.8f) : new(4, 0.65f, 34), 1, female ? new(3.5f, 3, 3.5f) : new(2.7f, 2.5f, 3), AmountScale(female ? new(1.28f, 1.12f, 1.12f) : Vector3.One, contour), new((female ? 0.12f : 0.23f) * contour, 0, 0)),\r\n\t\t\tnew Edit(\"Right body contour\", female ? new(4, -3.1f, 49.8f) : new(4, -0.65f, 34), 1, female ? new(3.5f, 3, 3.5f) : new(2.7f, 2.5f, 3), AmountScale(female ? new(1.28f, 1.12f, 1.12f) : Vector3.One, contour), new((female ? 0.12f : 0.23f) * contour, 0, 0)),\r\n\t\t\tnew Edit(\"Left butt size\", new(-3.5f, 3, 33.5f), 1, new(4.5f, 3.8f, 5), AmountScale(new(1.22f, 1.08f, 1.05f), butt), new(-0.15f * butt, 0, 0)),\r\n\t\t\tnew Edit(\"Right butt size\", new(-3.5f, -3, 33.5f), 1, new(4.5f, 3.8f, 5), AmountScale(new(1.22f, 1.08f, 1.05f), butt), new(-0.15f * butt, 0, 0)),\r\n\t\t\t// Front-only abdominal cap: the back, chest and pelvis are outside the mask.\r\n\t\t\tnew Edit(\"Belly size\", new(8, 0, 41), 1, new(8, 9, 7.5f), Vector3.One, new(0.55f * a.BellySize, 0, 0)),\r\n\t\t\t// Length and width only: soles keep their original height and legs stay outside.\r\n\t\t\tnew Edit(\"Left foot size\", new(0, 7.8f, 1.8f), 1, new(9, 4, 4.2f), Scale(new(1.22f, 1.18f, 1), a.FootSize), Vector3.Zero),\r\n\t\t\tnew Edit(\"Right foot size\", new(0, -7.8f, 1.8f), 1, new(9, 4, 4.2f), Scale(new(1.22f, 1.18f, 1), a.FootSize), Vector3.Zero),\r\n\t\t\tnew Edit(\"Head shape\", new(0, 0, 63), 10, Vector3.One, Scale(new(1.06f, 1.08f, 1.025f), a.HeadShape), Vector3.Zero),\r\n\t\t\t// +X is forward on the native human rig. Translation changes profile depth\r\n\t\t\t// without radial inflation's boundary clamp or volume-preserving squash.\r\n\t\t\t// Tight ellipsoids end above the upper lip and in front of the throat.\r\n\t\t\t// Projection and width share each feature's mask, rather than a jaw/face sphere.\r\n\t\t\tnew Edit(\"Nose projection\", new(6.2f, 0, 63.65f), 1, new(1.55f, 1.35f, 1.15f), Vector3.One, new(0.32f * Signed(a.NoseSize), 0, 0)),\r\n\t\t\tnew Edit(\"Nose width\", new(6.2f, 0, 63.65f), 1, new(1.55f, 1.35f, 1.15f), Scale(new(1, 1.28f, 1), a.NoseWidth), Vector3.Zero),\r\n\t\t\tnew Edit(\"Nose tip height\", new(6.2f, 0, 63.65f), 1, new(1.55f, 1.35f, 1.15f), Vector3.One, new(0, 0, 0.3f * Signed(a.NoseHeight))),\r\n\t\t\tnew Edit(\"Chin projection\", new(5.1f, 0, 60.25f), 1, new(1.25f, 2.05f, 0.8f), Vector3.One, new(0.24f * chinProjection, 0, 0)),\r\n\t\t\tnew Edit(\"Chin width\", new(5.1f, 0, 60.25f), 1, new(1.25f, 2.05f, 0.8f), Scale(new(1, 1.18f, 1), a.ChinWidth), Vector3.Zero),\r\n\t\t\tnew Edit(\"Chin height\", new(5.1f, 0, 60), 1, new(1.5f, 2.05f, 1), Vector3.One, new(0, 0, 0.3f * chinHeight)),\r\n\t\t\tnew Edit(\"Submental fullness\", new(3.65f * headScale.x, 0, 63 + (submentalZ - 63) * headScale.z), 1, new(1.5f * headScale.x, 2.35f * headScale.y, 0.75f * headScale.z), Vector3.One, new(0, 0, -Range(a.SubmentalFullness, 0.25f, 0.35f))),\r\n\t\t\t// Lateral translation follows the mandibular rim without inflating the neck.\r\n\t\t\t// The posterior section climbs toward the ear; the front remains below the lips.\r\n\t\t\tJawEdit(\"Left jaw angle\", new(1.9f, 2.7f * jaw.Width, jaw.AngleZ), new(1.1f, 1.1f, 0.6f), 0.085f, 42),\r\n\t\t\tJawEdit(\"Right jaw angle\", new(1.9f, -2.7f * jaw.Width, jaw.AngleZ), new(1.1f, 1.1f, 0.6f), -0.085f, 42),\r\n\t\t\tJawEdit(\"Left jaw width\", new(3, 2.25f * jaw.Width, jaw.MiddleZ), new(2.1f, 1.6f, 0.7f), 0.07f, 26),\r\n\t\t\tJawEdit(\"Right jaw width\", new(3, -2.25f * jaw.Width, jaw.MiddleZ), new(2.1f, 1.6f, 0.7f), -0.07f, 26),\r\n\t\t\tJawEdit(\"Left jaw front\", new(4.35f, 1.4f * jaw.Width, jaw.FrontZ), new(1.55f, 1.7f, 0.65f), 0.045f, 10),\r\n\t\t\tJawEdit(\"Right jaw front\", new(4.35f, -1.4f * jaw.Width, jaw.FrontZ), new(1.55f, 1.7f, 0.65f), -0.045f, 10),\r\n\t\t\tnew Edit(\"Left cheek fullness\", new(4, 0.8f, 62), 1, new(3.6f, 5, 1.5f), Scale(new(1, 1.06f, 1), a.CheekFullness), new(0, 0.0096f * Signed(a.CheekFullness), 0)),\r\n\t\t\tnew Edit(\"Right cheek fullness\", new(4, -0.8f, 62), 1, new(3.6f, 5, 1.5f), Scale(new(1, 1.06f, 1), a.CheekFullness), new(0, -0.0096f * Signed(a.CheekFullness), 0)),\r\n\t\t\t// The alae (nostril wings) sit beside and below the tip; keep the lip outside.\r\n\t\t\tnew Edit(\"Left nostril width\", new(5.5f, 0.85f, 62.95f), 1, new(0.65f, 0.7f, 0.35f), Vector3.One, new(0, 0.2f * Signed(a.NostrilWidth), 0)),\r\n\t\t\tnew Edit(\"Right nostril width\", new(5.5f, -0.85f, 62.95f), 1, new(0.65f, 0.7f, 0.35f), Vector3.One, new(0, -0.2f * Signed(a.NostrilWidth), 0)),\r\n\t\t\t// Fullness changes the wing's rounded cap in depth/height, independent of spacing.\r\n\t\t\tnew Edit(\"Left nostril-wing fullness\", WingCenter(1), 1, wingMask, Scale(Vector3.Lerp(new(1.35f, 1.05f, 1.3f), new(1.28f, 1.05f, 1.24f), smallWings), a.NostrilFullness), new((.1f - .02f * smallWings) * Signed(a.NostrilFullness), 0, 0)),\r\n\t\t\tnew Edit(\"Right nostril-wing fullness\", WingCenter(-1), 1, wingMask, Scale(Vector3.Lerp(new(1.35f, 1.05f, 1.3f), new(1.28f, 1.05f, 1.24f), smallWings), a.NostrilFullness), new((.1f - .02f * smallWings) * Signed(a.NostrilFullness), 0, 0)),\r\n\t\t\tnew Edit(\"Forehead width\", new(3, 0, 65.8f), 1, new(3.8f, 5, 1.2f), Scale(new(1, 1.16f, 1), a.ForeheadWidth), Vector3.Zero),\r\n\t\t\tnew Edit(\"Left cheekbone width\", new(3.8f, 2.4f, 63.05f), 1, new(2.3f, 1.7f, 1.1f), Vector3.One, new(0, 0.08f * cheekbones, 0)),\r\n\t\t\tnew Edit(\"Right cheekbone width\", new(3.8f, -2.4f, 63.05f), 1, new(2.3f, 1.7f, 1.1f), Vector3.One, new(0, -0.08f * cheekbones, 0)),\r\n\t\t\t// Include the forward, inward lower lobe while retaining a narrow fade into the scalp.\r\n\t\t\t// Translate the scale operation's pivot inward while keeping the whole-ear/lobe mask.\r\n\t\t\t// Shape the upper helix in native rest space before resizing. The inner mask\r\n\t\t\t// boundary stays outside the skull, and no lateral translation moves the root.\r\n\t\t\tnew Edit(\"Left ear taper\", EarCenter(1), 1, earMask, new(1 - 0.4f * a.EarTaper, 1, 1 + 0.35f * a.EarTaper), new(-0.3f * a.EarTaper, 0, 0.35f * a.EarTaper)),\r\n\t\t\tnew Edit(\"Right ear taper\", EarCenter(-1), 1, earMask, new(1 - 0.4f * a.EarTaper, 1, 1 + 0.35f * a.EarTaper), new(-0.3f * a.EarTaper, 0, 0.35f * a.EarTaper)),\r\n\t\t\tnew Edit(\"Left ear size\", new(0.35f, 3.5f, 63.15f), 1, new(2.15f, 1.05f, 3.15f), earScale, new(0, earPivotCorrection, 0)),\r\n\t\t\tnew Edit(\"Right ear size\", new(0.35f, -3.5f, 63.15f), 1, new(2.15f, 1.05f, 3.15f), earScale, new(0, -earPivotCorrection, 0)),\r\n\t\t\tnew Edit(\"Left eye size\", new(4.7f, 1.65f, 64.5f), 1, new(1.6f, 1.25f, 0.7f), Scale(new(1.12f, 1.18f, 1.18f), a.EyeSize), Vector3.Zero),\r\n\t\t\tnew Edit(\"Right eye size\", new(4.7f, -1.65f, 64.5f), 1, new(1.6f, 1.25f, 0.7f), Scale(new(1.12f, 1.18f, 1.18f), a.EyeSize), Vector3.Zero),\r\n\t\t\tnew Edit(\"Brow prominence\", new(4.6f, 0, 65.2f), 1, new(1.9f, 3.9f, 0.65f), Vector3.One, new(0.08f * brow, 0, 0)),\r\n\t\t\t// Separate small masks for the philtrum and each lip. Mouth width/projection\r\n\t\t\t// share a lip envelope ending below the nose and above the chin.\r\n\t\t\tnew Edit(\"Philtrum width\", new(mouth.SurfaceX - 0.05f, 0, (mouth.PhiltrumZ + mouth.UpperZ) / 2 - 0.08f + philtrumLipClearance + philtrumUpperExtension / 2), 1, new(0.75f, 0.9f, (mouth.PhiltrumZ - mouth.UpperZ) / 2 + 0.2f - philtrumLipClearance + philtrumUpperExtension / 2), Scale(new(1, 1.28f, 1), a.PhiltrumWidth), Vector3.Zero),\r\n\t\t\tnew Edit(\"Mouth width\", new(5, 0, 61.8f), 1, new(1.5f, 2.5f, 0.7f), Scale(new(1, 1.2f, 1), a.MouthWidth), Vector3.Zero),\r\n\t\t\tnew Edit(\"Mouth projection\", new(5, 0, 61.8f), 1, new(1.5f, 2.5f, 0.7f), Vector3.One, new(0.12f * mouthProjection, 0, 0)),\r\n\t\t\tnew Edit(\"Upper lip fullness\", new(mouth.SurfaceX, 0, mouth.UpperZ), 1, new(0.7f, 1.85f, mouth.UpperHeight), AmountScale(new(1, 1, 1.4f), upperLip), new(0.08f * upperLip, 0, 0)),\r\n\t\t\tnew Edit(\"Lower lip fullness\", new(mouth.SurfaceX - 0.07f, 0, mouth.LowerZ), 1, new(0.7f, 1.85f, mouth.LowerHeight), AmountScale(new(1, 1, 1.4f), lowerLip), new(0.08f * lowerLip, 0, 0)),\r\n\t\t\tnew Edit(\"Columella width\", new(mouth.ColumellaX, 0, mouth.ColumellaZ), 1, new(0.65f, 0.5f, 0.19f), Scale(new(1, 1.32f, 1), a.ColumellaWidth), Vector3.Zero)\r\n\t\t};\r\n\t\tif (a.BaseShape is null)\r\n\t\t\treturn result;\r\n\t\tvar bulge = Signed(a.BaseShape.BulgeSize) * maleAmount;\r\n\t\treturn result.Concat(new[] { new Edit(\"Left bulge\", new(4, .65f, 34), 1, new(2.7f, 2.5f, 3), Vector3.One, new(.23f * bulge, 0, 0)), new Edit(\"Right bulge\", new(4, -.65f, 34), 1, new(2.7f, 2.5f, 3), Vector3.One, new(.23f * bulge, 0, 0)) }).ToArray();\r\n\t}\r\n\r\n\tpublic static bool PropagatesToClothing(string volumeName, CharacterAppearanceData appearance) => !((appearance.BaseShape is not null || AppearanceCatalog.IsFemale(appearance)) && (appearance.BaseShape?.BreastSize ?? appearance.BodyFeatureSize) < 0.5f && (volumeName is \"Left body contour\" or \"Right body contour\"));\r\n\tpublic static GameObject Create(SkinnedModelRenderer body, CharacterAppearanceData appearance)\r\n\t{\r\n\t\tvar root = new GameObject\r\n\t\t{\r\n\t\t\tParent = body.GameObject,\r\n\t\t\tName = \"Local human shape volumes\",\r\n\t\t\tNetworkMode = NetworkMode.Never\r\n\t\t};\r\n\t\troot.LocalTransform = Transform.Zero;\r\n\t\troot.Flags |= GameObjectFlags.NotSaved;\r\n\t\tvar edits = Edits(appearance);\r\n\t\tfor (var i = 0; i < edits.Length; i++)\r\n\t\t{\r\n\t\t\tvar edit = edits[i];\r\n\t\t\tvar go = new GameObject\r\n\t\t\t{\r\n\t\t\t\tParent = root,\r\n\t\t\t\tName = edit.Name,\r\n\t\t\t\tNetworkMode = NetworkMode.Never\r\n\t\t\t};\r\n\t\t\tgo.LocalPosition = edit.Center;\r\n\t\t\tgo.LocalRotation = Rotation.FromPitch(edit.Pitch) * Rotation.FromRoll(edit.Roll);\r\n\t\t\tgo.LocalScale = edit.MaskScale;\r\n\t\t\tvar deform = go.Components.Create<ModelDeformer>();\r\n\t\t\tvar volume = deform.SceneVolume;\r\n\t\t\tvolume.Sphere = new()\r\n\t\t\t{\r\n\t\t\t\tCenter = Vector3.Zero,\r\n\t\t\t\tRadius = edit.Radius\r\n\t\t\t};\r\n\t\t\tdeform.SceneVolume = volume;\r\n\t\t\tdeform.Operation = ModelDeformer.OperationType.Transform;\r\n\t\t\tdeform.Weight = 1;\r\n\t\t\tdeform.Falloff = edit.Name.Contains(\"jaw\") ? 1 : 0.65f;\r\n\t\t\tdeform.TransformBlend = 0.65f;\r\n\t\t\tdeform.NormalMode = ModelDeformer.NormalModeType.Accurate;\r\n\t\t\tdeform.ApplyToBoneMergedChildren = PropagatesToClothing(edit.Name, appearance);\r\n\t\t\tdeform.Priority = i * 10;\r\n\t\t\tdeform.ScaleFactor = edit.Scale;\r\n\t\t\tdeform.Translation = edit.Translation;\r\n\t\t\t// A transform with identity coefficients has no visual effect. Keep its\r\n\t\t\t// authoring component, but remove it from native rendering until edited.\r\n\t\t\tdeform.Enabled = edit.Scale != Vector3.One || edit.Translation != Vector3.Zero;\r\n\t\t}\r\n\r\n\t\treturn root;\r\n\t}\r\n\r\n\tpublic static GameObject Update(GameObject root, SkinnedModelRenderer body, CharacterAppearanceData appearance)\r\n\t{\r\n\t\tvar volumes = root.IsValid() ? root.GetComponentsInChildren<ModelDeformer>(true, true).OrderBy(x => x.Priority).ToArray() : Array.Empty<ModelDeformer>();\r\n\t\tif (volumes.Length != (appearance.BaseShape is null ? VolumeCount : VolumeCount + 2) || volumes.Any(x => x.Target != body))\r\n\t\t{\r\n\t\t\tif (root.IsValid())\r\n\t\t\t\troot.Destroy();\r\n\t\t\treturn Create(body, appearance);\r\n\t\t}\r\n\r\n\t\tvar edits = Edits(appearance);\r\n\t\tfor (var i = 0; i < edits.Length; i++)\r\n\t\t{\r\n\t\t\t// Untouched controls retain both their components and exact coefficients.\r\n\t\t\t// Keep existing native components in sync when an authored mask is revised.\r\n\t\t\tif (volumes[i].GameObject.LocalPosition != edits[i].Center)\r\n\t\t\t\tvolumes[i].GameObject.LocalPosition = edits[i].Center;\r\n\t\t\tif (volumes[i].GameObject.Name != edits[i].Name)\r\n\t\t\t\tvolumes[i].GameObject.Name = edits[i].Name;\r\n\t\t\tif (volumes[i].GameObject.LocalScale != edits[i].MaskScale)\r\n\t\t\t\tvolumes[i].GameObject.LocalScale = edits[i].MaskScale;\r\n\t\t\tvar rotation = Rotation.FromPitch(edits[i].Pitch) * Rotation.FromRoll(edits[i].Roll);\r\n\t\t\tif (volumes[i].GameObject.LocalRotation != rotation)\r\n\t\t\t\tvolumes[i].GameObject.LocalRotation = rotation;\r\n\t\t\tif (volumes[i].SceneVolume.Sphere.Radius != edits[i].Radius)\r\n\t\t\t{\r\n\t\t\t\tvar volume = volumes[i].SceneVolume;\r\n\t\t\t\tvolume.Sphere = new()\r\n\t\t\t\t{\r\n\t\t\t\t\tCenter = Vector3.Zero,\r\n\t\t\t\t\tRadius = edits[i].Radius\r\n\t\t\t\t};\r\n\t\t\t\tvolumes[i].SceneVolume = volume;\r\n\t\t\t}\r\n\r\n\t\t\tvar falloff = edits[i].Name.Contains(\"jaw\") ? 1f : 0.65f;\r\n\t\t\tif (volumes[i].Falloff != falloff)\r\n\t\t\t\tvolumes[i].Falloff = falloff;\r\n\t\t\tif (volumes[i].ScaleFactor != edits[i].Scale)\r\n\t\t\t\tvolumes[i].ScaleFactor = edits[i].Scale;\r\n\t\t\tif (volumes[i].Translation != edits[i].Translation)\r\n\t\t\t\tvolumes[i].Translation = edits[i].Translation;\r\n\t\t\tvolumes[i].ApplyToBoneMergedChildren = PropagatesToClothing(edits[i].Name, appearance);\r\n\t\t\tvar enabled = edits[i].Scale != Vector3.One || edits[i].Translation != Vector3.Zero;\r\n\t\t\tif (volumes[i].Enabled != enabled)\r\n\t\t\t\tvolumes[i].Enabled = enabled;\r\n\t\t}\r\n\r\n\t\treturn root;\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "vendetta.character_editor",
            "Path": "CharacterKit/MarkingCatalog.cs",
            "FileName": "MarkingCatalog.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 410954,
            "IsPrivate": false,
            "Code": "using System;\r\n\r\nnamespace CharacterEditor;\r\npublic sealed record MarkingStyle(string Id, string Label, int ShaderId, bool Band = false);\r\npublic sealed record MarkingRegion(string Id, string Label, bool Head, Vector3 Center, Vector3 Right, Vector3 Up, Vector3 Normal, Vector3 Min, Vector3 Max, float MoveX, float MoveY, float Size = 1, bool BandOnly = false, bool StampOnly = false, float Radius = 2);\r\npublic readonly record struct MarkingFrame(Vector3 Center, Vector3 Right, Vector3 Up, Vector3 Normal, int Projection, float Phase);\r\n/// <summary>Surface-specific authoring limits; arbitrary transforms never enter appearance saves.</summary>\r\npublic static class MarkingCatalog\r\n{\r\n\tpublic const int Maximum = 6;\r\n\tpublic static IReadOnlyList<MarkingStyle> Styles { get; } = new[]\r\n\t{\r\n\t\tnew MarkingStyle(\"none\", \"None\", 0),\r\n\t\tnew(\"crescent\", \"Crescent\", 1),\r\n\t\tnew(\"teardrop\", \"Teardrop\", 2),\r\n\t\tnew(\"star\", \"Star\", 3),\r\n\t\tnew(\"rune\", \"Rune\", 4),\r\n\t\tnew(\"barbed-band\", \"Barbed wire band\", 5, true)\r\n\t};\r\n\tpublic static IReadOnlyList<MarkingRegion> Regions => ShoulderSafeRegionDefinitions;\r\n\r\n\tstatic readonly IReadOnlyList<MarkingRegion> ShoulderSafeRegionDefinitions = new[]\r\n\t{\r\n\t\tnew MarkingRegion(\"face\", \"Face\", true, new(4.6f, 0, 63.3f), Vector3.Left, Vector3.Up, Vector3.Forward, new(-3, -5.5f, 59.3f), new(8, 5.5f, 69.2f), 3, 4, 1.25f, false, true),\r\n\t\tnew(\"left-cheek\", \"Left cheek\", true, new(4, 2.35f, 63.18f), Vector3.Left, Vector3.Up, Vector3.Forward, new(-4, .1f, 60.8f), new(7, 5.5f, 65), 1.4f, 1.3f),\r\n\t\tnew(\"right-cheek\", \"Right cheek\", true, new(4, -2.35f, 63.18f), Vector3.Right, Vector3.Up, Vector3.Forward, new(-4, -5.5f, 60.8f), new(7, -.1f, 65), 1.4f, 1.3f),\r\n\t\tnew(\"forehead\", \"Forehead\", true, new(3.8f, 0, 66.5f), Vector3.Left, Vector3.Up, Vector3.Forward, new(1.8f, -3.7f, 65.2f), new(7, 3.7f, 68.1f), 1.6f, .55f, 1),\r\n\t\tnew(\"chest\", \"Front torso\", false, new(3, 0, 52), Vector3.Left, Vector3.Up, Vector3.Forward, new(-8, -8, 30.5f), new(8, 8, 59.5f), 4.2f, 20, 2.2f, false, true),\r\n\t\tnew(\"back\", \"Back\", false, new(-3, 0, 49), Vector3.Right, Vector3.Up, Vector3.Backward, new(-8, -8, 32.5f), new(8, 8, 59.5f), 4.2f, 15, 2.2f, false, true),\r\n\t\tnew(\"left-arm\", \"Left arm\", false, new(-1.5f, 13.3f, 46), Vector3.Forward, new Vector3(0, -.62f, .785f).Normal, Vector3.Left, new(-7, 6.3f, 36.5f), new(8, 24, 55.5f), 1, 9, 1.5f),\r\n\t\tnew(\"right-arm\", \"Right arm\", false, new(-1.5f, -13.3f, 46), Vector3.Backward, new Vector3(0, .62f, .785f).Normal, Vector3.Right, new(-7, -24, 36.5f), new(8, -6.3f, 55.5f), 1, 9, 1.5f),\r\n\t\tnew(\"left-leg\", \"Left leg\", false, new(1, 6, 20), Vector3.Left, new Vector3(0, -.075f, .997f).Normal, Vector3.Forward, new(-7, 1.5f, 4.7f), new(8, 11.5f, 38.5f), 1.1f, 15, 1.8f, false, false, 2.5f),\r\n\t\tnew(\"right-leg\", \"Right leg\", false, new(1, -6, 20), Vector3.Right, new Vector3(0, .075f, .997f).Normal, Vector3.Forward, new(-7, -11.5f, 4.7f), new(8, -1.5f, 38.5f), 1.1f, 15, 1.8f, false, false, 2.5f),\r\n\t\tnew(\"neck\", \"Neck choker\", true, new(0, 0, 59.3f), Vector3.Forward, new Vector3(.3f, 0, 1).Normal, Vector3.Left, new(-6, -4.5f, 56.8f), new(7, 4.5f, 62.5f), 0, 1.5f, 1, true, false, 2.5f),\r\n\t\tnew(\"torso\", \"Torso belt\", false, new(0, 0, 43.7f), Vector3.Forward, Vector3.Up, Vector3.Left, new(-8, -7.5f, 32.5f), new(8, 7.5f, 50.8f), 0, 10, 1, true, false, 5),\r\n\t\tnew(\"head\", \"Head band\", true, new(0, 0, 66.7f), Vector3.Forward, Vector3.Up, Vector3.Left, new(-6, -5.5f, 58.5f), new(8, 5.5f, 70), 0, 7, 1, true, false, 3.8f)\r\n\t};\r\n\tpublic static MarkingStyle Style(string id) => Styles.FirstOrDefault(x => x.Id == id);\r\n\tpublic static MarkingRegion Region(string id) => Regions.FirstOrDefault(x => x.Id == id);\r\n\tstatic bool Compatible(MarkingRegion region, bool band) => band ? (!region.StampOnly && region.Id is not \"left-cheek\" and not \"right-cheek\" and not \"forehead\") : !region.BandOnly;\r\n\tpublic static IEnumerable<MarkingRegion> Locations(string style) => Regions.Where(x => Compatible(x, Style(style)?.Band == true) && (Style(style)?.Band == true || x.Id is not \"left-cheek\" and not \"right-cheek\" and not \"forehead\"));\r\n\t// Legacy location IDs stay valid without cluttering the new general Face location.\r\n\tpublic static bool ValidPlacement(string style, string region) => Style(style)is { } design && Region(region)is { } location && Compatible(location, design.Band);\r\n\tpublic static bool OnHead(CharacterMarkingData data) => Region(data.Region).Head;\r\n\tpublic static bool OnMaterial(CharacterMarkingData data, int armSide) => armSide == 1 ? data.Region == \"left-arm\" : armSide == -1 ? data.Region == \"right-arm\" : OnBody(data) && data.Region is not \"left-arm\" and not \"right-arm\";\r\n\tpublic static bool OnBody(CharacterMarkingData data) => !Region(data.Region).Head || data.Region == \"neck\";\r\n\tpublic static void ChangeStyle(CharacterMarkingData data, string style)\r\n\t{\r\n\t\tif (Style(style)is null)\r\n\t\t\treturn;\r\n\t\tdata.Style = style;\r\n\t\tif (!ValidPlacement(style, data.Region))\r\n\t\t\tChangeRegion(data, Locations(style).First().Id);\r\n\t\tif (Style(style).Band)\r\n\t\t{\r\n\t\t\tdata.Horizontal = .5f;\r\n\t\t\tdata.Rotation = .5f;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic static void ChangeRegion(CharacterMarkingData data, string region)\r\n\t{\r\n\t\tif (!ValidPlacement(data.Style, region))\r\n\t\t\treturn;\r\n\t\tdata.Region = region;\r\n\t\tResetPosition(data);\r\n\t}\r\n\r\n\tpublic static void ResetPosition(CharacterMarkingData data)\r\n\t{\r\n\t\tdata.Horizontal = data.Region == \"face\" ? .68f : .5f;\r\n\t\tdata.Vertical = data.Region == \"face\" ? .45f : .5f;\r\n\t}\r\n\r\n\tpublic static Vector3 Center(CharacterMarkingData data)\r\n\t{\r\n\t\tvar region = Region(data.Region);\r\n\t\tvar limits = region.Id switch\r\n\t\t{\r\n\t\t\t\"chest\" => (41.5f, 54.5f),\r\n\t\t\t\"back\" => (41.2f, 55f),\r\n\t\t\t\"left-arm\" or \"right-arm\" => (38.2f, Style(data.Style).Band ? 52.48f - MathF.Max(0, BandScale(data) - 1) * .55f : 53.2f),\r\n\t\t\t\"left-leg\" or \"right-leg\" => (5.8f, 35.5f),\r\n\t\t\t\"face\" => (60.9f, 67.5f),\r\n\t\t\t\"neck\" => (58.3f, 60.4f),\r\n\t\t\t\"torso\" => (41.2f, 49.5f),\r\n\t\t\t\"head\" => (59.9f, 68.5f),\r\n\t\t\t_ => (region.Center.z - region.MoveY, region.Center.z + region.MoveY)};\r\n\t\tvar neutral = region.Center.z;\r\n\t\tif (region.Id is \"chest\" or \"back\")\r\n\t\t{\r\n\t\t\tvar theta = (region.Id == \"back\" ? MathF.PI : 0) + (data.Horizontal - .5f) * 250 * MathF.PI / 180;\r\n\t\t\tvar rotation = (data.Rotation - .5f) * MathF.PI * 2;\r\n\t\t\tvar size = region.Size * StampScale(data);\r\n\t\t\tvar verticalExtent = size * (.45f * MathF.Abs(MathF.Cos(rotation)) + .38f * MathF.Abs(MathF.Sin(rotation)));\r\n\t\t\t// A whole stamp fits below the deltoid/neck junction, even when rotated or enlarged.\r\n\t\t\tvar safeTop = 56.8f - 4.8f * MathF.Pow(MathF.Abs(MathF.Sin(theta)), 2) - verticalExtent - .1f;\r\n\t\t\tlimits.Item2 = MathF.Min(limits.Item2, safeTop);\r\n\t\t\tneutral = MathF.Min(neutral, limits.Item1 + (limits.Item2 - limits.Item1) * (region.Id == \"chest\" ? .81f : .565f));\r\n\t\t}\r\n\r\n\t\tvar z = data.Vertical < .5f ? limits.Item1 + (neutral - limits.Item1) * data.Vertical * 2 : neutral + (limits.Item2 - neutral) * (data.Vertical - .5f) * 2;\r\n\t\tvar center = region.Center + region.Up * ((z - region.Center.z) / region.Up.z);\r\n\t\tif (region.Id is \"left-arm\" or \"right-arm\")\r\n\t\t{\r\n\t\t\tvar lower = z < 46;\r\n\t\t\tvar t = lower ? (z - 39) / 7 : (z - 46) / 7.2f;\r\n\t\t\tcenter = new(lower ? 1.1f + (-2.6f) * t : -1.5f + .4f * t, MathF.Sign(region.Center.y) * (lower ? 19.9f - 6.6f * t : 13.3f - 4.8f * t), z);\r\n\t\t}\r\n\r\n\t\tif (region.Id is \"left-leg\" or \"right-leg\")\r\n\t\t\tcenter = LegAxis(z, MathF.Sign(region.Center.y));\r\n\t\tif (region.Id == \"neck\")\r\n\t\t\tcenter = new(.3f * (z - 59.3f) + .2f, 0, z);\r\n\t\tif (region.Id is \"chest\" or \"back\")\r\n\t\t{\r\n\t\t\tvar theta = (region.Id == \"back\" ? MathF.PI : 0) + (data.Horizontal - .5f) * 250 * MathF.PI / 180;\r\n\t\t\treturn new(4.3f * MathF.Cos(theta), 6 * MathF.Sin(theta), z);\r\n\t\t}\r\n\r\n\t\tif (region.Id == \"face\")\r\n\t\t{\r\n\t\t\tvar theta = (data.Horizontal - .5f) * 130 * MathF.PI / 180;\r\n\t\t\treturn new(4.6f * MathF.Cos(theta), 4.6f * MathF.Sin(theta), z);\r\n\t\t}\r\n\r\n\t\tif (Style(data.Style).Band || IsLimb(region.Id))\r\n\t\t\treturn center;\r\n\t\tif (region.Id is \"left-cheek\" or \"right-cheek\")\r\n\t\t{\r\n\t\t\tvar angle = MathF.Atan2(MathF.Abs(region.Center.y), region.Center.x) + (data.Horizontal - .5f) * 2 * (data.Horizontal > .5f ? 30 : 20) * MathF.PI / 180;\r\n\t\t\tvar radius = new Vector2(region.Center.x, region.Center.y).Length;\r\n\t\t\treturn new(radius * MathF.Cos(angle), MathF.Sign(region.Center.y) * radius * MathF.Sin(angle), z);\r\n\t\t}\r\n\r\n\t\treturn center + region.Right * (data.Horizontal - .5f) * region.MoveX * 2;\r\n\t}\r\n\r\n\tpublic static bool IsLimb(string id) => id is \"left-arm\" or \"right-arm\" or \"left-leg\" or \"right-leg\";\r\n\tpublic static MarkingFrame Frame(CharacterMarkingData data)\r\n\t{\r\n\t\tvar region = Region(data.Region);\r\n\t\tvar center = Center(data);\r\n\t\tif (Style(data.Style).Band || IsLimb(region.Id))\r\n\t\t{\r\n\t\t\tvar axis = region.Up;\r\n\t\t\tif (region.Id is \"left-arm\" or \"right-arm\")\r\n\t\t\t\taxis = new Vector3(center.z < 46 ? -.3714f : .0556f, MathF.Sign(region.Center.y) * (center.z < 46 ? -.9429f : -.6667f), 1).Normal;\r\n\t\t\tif (region.Id is \"left-leg\" or \"right-leg\")\r\n\t\t\t\taxis = (LegAxis(center.z + .1f, MathF.Sign(region.Center.y)) - LegAxis(center.z - .1f, MathF.Sign(region.Center.y))).Normal;\r\n\t\t\tvar right = (Vector3.Forward - axis * axis.x).Normal;\r\n\t\t\tvar normal = Vector3.Cross(axis, right).Normal;\r\n\t\t\tvar phase = region.Id == \"left-arm\" ? MathF.PI / 2 : region.Id == \"right-arm\" ? -MathF.PI / 2 : 0;\r\n\t\t\treturn new(center, right, axis, normal, IsLimb(region.Id) ? 2 : 0, phase + (data.Horizontal - .5f) * MathF.PI * 2);\r\n\t\t}\r\n\r\n\t\tif (region.Id is \"chest\" or \"back\")\r\n\t\t\treturn new(center, new(4.3f, 6, 0), Vector3.Up, new Vector3(center.x / (4.3f * 4.3f), center.y / 36, 0).Normal, 3, MathF.Atan2(center.y / 6, center.x / 4.3f));\r\n\t\tif (region.Id == \"face\")\r\n\t\t\treturn new(center, Vector3.Left, Vector3.Up, new Vector3(center.x, center.y, 0).Normal, 4, MathF.Atan2(center.y, center.x));\r\n\t\treturn new(center, region.Right, region.Up, region.Normal, region.Id is \"left-cheek\" or \"right-cheek\" ? 1 : 0, 0);\r\n\t}\r\n\r\n\tpublic static Vector3 Outward(CharacterMarkingData data)\r\n\t{\r\n\t\tvar frame = Frame(data);\r\n\t\tif (IsLimb(data.Region))\r\n\t\t\treturn frame.Right * MathF.Cos(frame.Phase) + frame.Normal * MathF.Sin(frame.Phase);\r\n\t\tif (frame.Projection is 1 or 4)\r\n\t\t\treturn new Vector3(frame.Center.x, frame.Center.y, 0).Normal;\r\n\t\treturn Region(data.Region).BandOnly ? frame.Right : frame.Normal;\r\n\t}\r\n\r\n\tpublic static Vector3 Focus(CharacterMarkingData data) => Center(data) + (IsLimb(data.Region) ? Outward(data) * Region(data.Region).Radius : Vector3.Zero);\r\n\tstatic readonly Vector3[] CalibratedLegCenters =\r\n\t{\r\n\t\tnew(-2.17f, 7.15f, 5.8f),\r\n\t\tnew(-2.18f, 7.1f, 7),\r\n\t\tnew(-2.13f, 6.7f, 10),\r\n\t\tnew(-2.12f, 6.28f, 13),\r\n\t\tnew(-1.89f, 6.1f, 16),\r\n\t\tnew(-.75f, 5.3f, 19),\r\n\t\tnew(-.41f, 4.91f, 22),\r\n\t\tnew(-.24f, 4.53f, 25),\r\n\t\tnew(0, 4.38f, 28),\r\n\t\tnew(-.3f, 4.3f, 35.5f)\r\n\t};\r\n\tstatic Vector3 LegAxis(float z, float side)\r\n\t{\r\n\t\tvar i = 0;\r\n\t\twhile (i < CalibratedLegCenters.Length - 2 && z > CalibratedLegCenters[i + 1].z)\r\n\t\t\ti++;\r\n\t\tvar a = CalibratedLegCenters[i];\r\n\t\tvar b = CalibratedLegCenters[i + 1];\r\n\t\tvar value = a + (b - a) * ((z - a.z) / (b.z - a.z));\r\n\t\treturn new(value.x, value.y * side, z);\r\n\t}\r\n\r\n\tpublic static float StampScale(CharacterMarkingData data) => data.Scale <= .5f ? .5f + data.Scale : 1 + (data.Scale - .5f) * 4;\r\n\tpublic static float BandScale(CharacterMarkingData data) => data.Scale < .5f ? .35f + data.Scale * 1.3f : 1 + (data.Scale - .5f) * 4;\r\n\tpublic static float BandSpacing(CharacterMarkingData data) => data.Spacing < .5f ? .35f + data.Spacing * 1.3f : 1 + (data.Spacing - .5f) * 3;\r\n}\r\n"
        },
        {
            "Ident": "vendetta.character_editor",
            "Path": "Experiments/FaceTransferLab.razor",
            "FileName": "FaceTransferLab.razor",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 410954,
            "IsPrivate": false,
            "Code": "@using Sandbox\r\n@using Sandbox.UI\r\n@using System\r\n@namespace CharacterEditor\r\n@inherits PanelComponent\r\n\r\n<root>\r\n <div class=\"labheader\"><div><div class=\"eyebrow\">CHARACTER EDITOR / EXPERIMENTS</div><h1>One face, toward another.</h1><p>Compare shape alone. Same surface topology, material, camera and lighting.</p></div><button onclick=@Back>Back to collection</button></div>\r\n <div class=\"toolbar\">\r\n  <button class=@(Pair==\"human03-to-human07\"?\"chosen\":\"\") onclick=@(()=>Pair=\"human03-to-human07\")>Human 03 \u2192 07</button>\r\n  <button class=@(Pair==\"human02-to-human04\"?\"chosen\":\"\") onclick=@(()=>Pair=\"human02-to-human04\")>Human 02 \u2192 04</button>\r\n  <div class=\"spacer\"></div>\r\n  <button onclick=@(()=>Yaw=0)>Front</button><button onclick=@(()=>Yaw=35)>Three-quarter</button><button onclick=@(()=>Yaw=90)>Profile</button>\r\n </div>\r\n <div class=\"views\">\r\n  <div class=\"view\"><div class=\"caption\"><h2>Source</h2><span>Unmodified starting face</span></div><FaceTransferPreview Pair=@Pair Mode=\"Source\" Yaw=@Yaw /></div>\r\n  <div class=\"view\"><div class=\"caption\"><h2>@ModeLabel</h2><span>@($\"{Amount:P0} applied\")</span></div><FaceTransferPreview Pair=@Pair Mode=@Mode Amount=@Amount Yaw=@Yaw /></div>\r\n  <div class=\"view\"><div class=\"caption\"><h2>Target</h2><span>Reference head surface</span></div><FaceTransferPreview Pair=@Pair Mode=\"Target\" Amount=@(1f) Yaw=@Yaw /></div>\r\n </div>\r\n <div class=\"controls\"><div class=\"toolbar\">\r\n  <button class=@(Mode==\"Existing\"?\"chosen\":\"\") onclick=@(()=>{Mode=\"Existing\";Amount=1;})>Fitted editor sliders</button>\r\n  <button class=@(Mode==\"Experimental\"?\"chosen\":\"\") onclick=@(()=>{Mode=\"Experimental\";Amount=1;})>Experimental volumes</button>\r\n  <button class=@(Mode==\"Target\"?\"chosen\":\"\") onclick=@(()=>{Mode=\"Target\";Amount=1;})>Direct vertex morph</button>\r\n </div><div class=\"blend\"><span>Source</span><SliderControl Value=@Amount OnValueChanged=@((float v)=>{Amount=v;}) Min=@(0f) Max=@(1f) Step=@(.01f) ShowTextEntry=@false /><span>Full result</span></div>\r\n <p>@Explanation</p><div class=\"footnote\">Rest-pose head surface only \u2022 Eye interiors, teeth, skin detail and animation excluded \u2022 Results measure geometry, not likeness</div></div>\r\n <StudioEscapeMenu @ref=EscapeMenu MainMenu=@Back EnableExperiments=@false />\r\n</root>\r\n\r\n@code {\r\n\tpublic StudioEscapeMenu EscapeMenu { get; set; }\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tbase.OnUpdate();\r\n\t\tif (EscapeMenu is not null && Input.EscapePressed)\r\n\t\t{\r\n\t\t\tInput.EscapePressed = false;\r\n\t\t\tEscapeMenu.Toggle();\r\n\t\t}\r\n\t}\r\n\r\n\tpublic string Pair { get; set; } = \"human03-to-human07\";\r\n\tpublic string Mode { get; set; } = \"Existing\";\r\n\tpublic float Amount { get; set; } = 1;\r\n\tpublic float Yaw { get; set; }\r\n\r\n\tstring ModeLabel => Mode == \"Existing\" ? \"Fitted editor sliders\" : Mode == \"Experimental\" ? \"Experimental volumes\" : \"Direct vertex morph\";\r\n\r\n\tstring Explanation => Mode == \"Target\" ? \"The direct morph adds the measured per-vertex difference. At 100%, it reaches the target vertex positions. This is a reference shape, not a new avatar preset.\" : Mode == \"Existing\" ? (Pair == \"human03-to-human07\" ? \"24 facial controls: front-surface RMS error falls from 1.84 mm to 1.51 mm (18%).\" : \"24 facial controls: front-surface RMS error falls from 4.07 mm to 3.84 mm (6%).\") : \"A separate fit using local translation volumes. It improves some regions, but does not consistently beat the existing controls. Intermediate blends interpolate the fitted surface, not the control values.\";\r\n\r\n\tvoid Back() => StudioSession.OpenScene(\"scenes/main_menu.scene\");\r\n\tprotected override int BuildHash() => HashCode.Combine(Pair, Mode, Amount, Yaw);\r\n}\r\n"
        },
        {
            "Ident": "vendetta.character_editor",
            "Path": "CharacterKit/CloudClothingBrowser.razor",
            "FileName": "CloudClothingBrowser.razor",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 410954,
            "IsPrivate": false,
            "Code": "@using Sandbox\r\n@using Sandbox.UI\r\n@using System\r\n@using System.Threading.Tasks\r\n@namespace CharacterEditor\r\n\r\n<root class=\"cloudwardrobe\">\r\n <div class=\"optionheading\">Cloud clothing</div>\r\n <div class=\"optiondescription\">Find clothing and hair published by the community. Add an item to your session wardrobe, then wear it like any other item. Human-compatible models are required.</div>\r\n <TextEntry Value=@Query OnTextEdited=@((string value)=>{Query=value;}) Placeholder=\"Search cloud clothing\" />\r\n <div class=\"choicegrid\">\r\n  <button class=\"creatorbutton\" disabled=@Busy onclick=@(()=>Search(false))>Search cloud</button>\r\n  <button class=\"creatorbutton quiet\" disabled=@Busy onclick=@ShowInventory>Your purchased cosmetics</button>\r\n </div>\r\n <TextEntry Value=@AssetReference OnTextEdited=@((string value)=>{AssetReference=value;}) Placeholder=\"Clothing asset path or cloud:creator.item\" />\r\n <button class=\"creatorbutton quiet\" disabled=@Busy onclick=@(()=>AddClothing(AssetReference.Trim()))>Add by reference</button>\r\n @if(!string.IsNullOrEmpty(Message)){<div class=\"optiondescription\">@Message</div>}\r\n @if(InventoryVisible)\r\n {\r\n  <div class=\"choicegrid thumbnailgrid\">\r\n  @foreach(var item in Sandbox.Services.Inventory.Definitions.Where(x=>Sandbox.Services.Inventory.HasItem(x.Id) && !string.IsNullOrEmpty(x.PackageIdent) && (string.IsNullOrWhiteSpace(Query)||x.Name.Contains(Query,StringComparison.OrdinalIgnoreCase))))\r\n  {\r\n   <button class=@TileClass(\"cloud:\"+item.PackageIdent) disabled=@Busy onclick=@(()=>AddClothing(\"cloud:\"+item.PackageIdent))>\n    <div class=\"thumbnailframe\"><div class=\"itemthumbnail\" style=@($\"background-image: url('{item.IconUrl}');\")></div>@if(LoadingReference==\"cloud:\"+item.PackageIdent){<div class=\"clothingloading\"><span class=\"material-icons loadingspinner\">autorenew</span><span>Loading\u2026</span></div>}</div><span class=\"itemlabel\">@item.Name</span>\n   </button>\r\n  }\r\n  </div>\r\n }\r\n else\r\n {\r\n  <div class=\"choicegrid thumbnailgrid\">\r\n  @foreach(var package in Results)\r\n  {\r\n   <button class=@TileClass(\"cloud:\"+package.FullIdent) disabled=@Busy onclick=@(()=>AddClothing(\"cloud:\"+package.FullIdent))>\n    <div class=\"thumbnailframe\"><div class=\"itemthumbnail\" style=@($\"background-image: url('{package.ThumbWide}');\")></div>@if(LoadingReference==\"cloud:\"+package.FullIdent){<div class=\"clothingloading\"><span class=\"material-icons loadingspinner\">autorenew</span><span>Loading\u2026</span></div>}</div><span class=\"itemlabel\">@package.Title</span>\n   </button>\r\n  }\r\n  </div>\r\n  @if(Results.Count>0 && Results.Count<Total){<button class=\"creatorbutton quiet\" disabled=@Busy onclick=@(()=>Search(true))>Load more</button>}\r\n }\r\n</root>\r\n\r\n@code {\r\n public Action<WardrobeItem> Selected { get; set; }\n public CharacterAppearanceData Appearance { get; set; }\n string Query=\"\", AssetReference=\"\", Message;\n string LoadingReference;\n string ActiveQuery;\r\n bool Busy, InventoryVisible;\r\n int Generation;\r\n long Total;\r\n List<Package> Results=new();\n IEnumerable<string> Equipped => Appearance is null ? Enumerable.Empty<string>() : WardrobeCatalog.Paths(Appearance).Concat(new[] { Appearance.HairPath, Appearance.FacialHairPath }).Where(p=>!string.IsNullOrEmpty(p));\n bool IsEquipped(string reference) => Equipped.Any(path=>string.Equals(path,reference,StringComparison.OrdinalIgnoreCase) || (ClothingResources.Get(reference) is { } item && ClothingResources.Get(path)==item));\n string TileClass(string reference) => $\"creatorbutton wardrobechoice {(IsEquipped(reference)?\"chosen\":\"\")} {(LoadingReference==reference?\"loading\":\"\")}\";\n void ShowInventory()\r\n {\r\n  if(Busy)return;\r\n  InventoryVisible=true;\r\n  Message=\"Purchased items use your s&box inventory. Items without a human model cannot be worn here.\";\r\n }\r\n async Task Search(bool more)\r\n {\r\n  if(Busy)return;\r\n  Busy=true;\r\n  InventoryVisible=false;\r\n  var generation=++Generation;\r\n  Message=\"Searching\u2026\";\r\n  StateHasChanged();\r\n  try\r\n  {\r\n   if(!more){Results.Clear();ActiveQuery=Query;}\r\n   // Escape filters in user input so results remain clothing-only.\r\n   var words=string.Join(\" \", (ActiveQuery??\"\").Split(' ',StringSplitOptions.RemoveEmptyEntries).Where(x=>!x.Contains(':')));\r\n   var result=await Package.FindAsync($\"{words} type:clothing sort:newest\",24,Results.Count);\r\n   if(!IsValid || generation!=Generation)return;\r\n   Results.AddRange(result.Packages);\r\n   Total=result.TotalCount;\r\n   Message=Total==0?\"No clothing found.\":$\"{Total} cloud items. Click a preview to add it.\";\r\n  }\r\n  catch(Exception e){if(IsValid)Message=\"Cloud search failed: \"+e.Message;}\r\n  finally{Busy=false;if(IsValid)StateHasChanged();}\r\n }\r\n async Task AddClothing(string reference)\r\n {\r\n  if(Busy)return;\r\n  Busy=true;LoadingReference=reference;Message=\"Loading clothing\u2026\";StateHasChanged();\n  try\r\n  {\r\n   var clothing=await ClothingResources.Load(reference);\r\n   if(!IsValid)return;\r\n   if(ClothingResources.IsBaseSkin(clothing))throw new InvalidOperationException(\"Choose a clothing or hair item; base skins are selected in the Skin tab.\");\r\n   Selected?.Invoke(ClothingResources.Describe(reference,clothing));\n   StateHasChanged();\n   if(Appearance is not null) await CharacterVisual.Load(Appearance.DeepCopy());\n   if(!IsValid)return;\n   Message=(IsEquipped(reference)?\"Equipped \":\"Removed \")+clothing.Title+\".\";\n  }\r\n  catch(Exception e){if(IsValid)Message=e.Message;}\r\n  finally{Busy=false;LoadingReference=null;if(IsValid)StateHasChanged();}\n }\r\n protected override async Task OnParametersSetAsync()\r\n {\r\n  if(ActiveQuery is null && !Busy) await Search(false);\r\n }\r\n protected override int BuildHash()=>HashCode.Combine(Query,Message,Busy,InventoryVisible,Results.Count,Total,HashCode.Combine(Sandbox.Services.Inventory.Items?.Count,AssetReference,LoadingReference,ClothingResources.Revision),string.Join(\"|\",Equipped));\n}\r\n"
        },
        {
            "Ident": "vendetta.character_editor",
            "Path": "CharacterKit/ReferenceFacePanel.razor",
            "FileName": "ReferenceFacePanel.razor",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 410954,
            "IsPrivate": false,
            "Code": "@using Sandbox\r\n\r\n@using Sandbox.UI\r\n\r\n@using System\r\n\r\n@namespace CharacterEditor\r\n\r\n@inherits Panel\r\n\r\n<root>\r\n\r\n <div class=\"optionheading\">Make a face from photos</div>\r\n\r\n <div class=\"optiondescription\">Add up to five direct image links of the same person. Combine a front photo with an angled or side photo and a relaxed expression. Front views guide widths; side views guide the nose, lips and chin. The result is an approximate starting shape; refine it in Shape afterwards.</div>\r\n\r\n <div class=\"optiondescription\">Images are read by the background analyzer on this device. Some image hosts block this access. Photos and links are not included in saved characters.</div>\r\n\r\n <TextEntry class=\"photo-url\" Value=@Url OnTextEdited=@((string value)=>{Url=value;}) Placeholder=\"https://\u2026/portrait.jpg\" MaxLength=@(2048) />\r\n\r\n <button class=\"creatorbutton add-photo\" onclick=@AddReference>Load reference</button>\r\n @if(Rows.Count>0){<button class=\"creatorbutton quiet refresh-photos\" onclick=@RefreshReferences>Refresh analyses</button>}\r\n @if(!string.IsNullOrEmpty(Message)){<div class=\"optiondescription\">@Message</div>}\r\n\r\n @foreach(var row in Rows)\r\n\r\n {\r\n\r\n  <div class=\"photo-reference\">\r\n\r\n   @if(row.Measurement is not null){<img class=\"photo-thumb\" [email protected] />}\r\n\r\n   <div class=\"photo-reference-text\"><div class=\"fieldlabel\">Reference @(Rows.IndexOf(row)+1)</div><div class=\"optiondescription\">@row.Status</div><button class=@($\"creatorbutton quiet remove-photo-{Rows.IndexOf(row)}\") onclick=@(()=>Remove(row))>Remove</button></div>\r\n\r\n  </div>\r\n\r\n }\r\n\r\n <div class=\"optiondescription\">@(Rows.Any(x=>x.Measurement?.FrontUsable==true)?\"Front widths: covered.\":\"Add a front view for face widths.\") @(Rows.Any(x=>x.Measurement?.Yaw>=45)?\"Profile depth: covered.\":Rows.Any(x=>x.Measurement?.ProfileUsable==true)?\"Some depth from angled views; a clearer side view helps.\":\"Add an angled or side view for nose and chin depth.\")</div>\r\n\r\n <div class=\"fieldlabel\">Starting heads</div>\r\n <div class=\"optiondescription\">Choose the foundation to keep while fitting. Selected skin uses that skin's original head family; it does not guess from the photos. Current blend preserves your chosen balance of male and female heads.</div>\r\n @foreach(var choice in new[]{(\"skin\",\"Selected skin\"),(\"female\",\"Female heads\"),(\"male\",\"Male heads\"),(\"current\",\"Current blend\")})\r\n {\r\n  <button class=@($\"creatorbutton {(Basis==choice.Item1?\"primary\":\"quiet\")}\") onclick=@(()=>Basis=choice.Item1)>@choice.Item2</button>\r\n }\r\n <div class=\"fieldlabel\">Name your base face</div>\r\n <TextEntry class=\"photo-name\" Value=@FaceName OnTextEdited=@((string value)=>{FaceName=value;}) MaxLength=@(40) Placeholder=\"My photo face\" />\r\n <button class=\"creatorbutton primary generate-photo-face\" onclick=@Generate>Generate base face</button>\r\n\r\n <div class=\"optiondescription\">Generation preserves the starting balance of male and female heads while fitting proportions. It adds a custom face at full influence; existing face influences are set to zero. Your Shape edits remain applied on top.</div>\r\n</root>\r\n\r\n@code {\r\n\tpublic CharacterAppearanceData Appearance { get; set; }\r\n\tpublic Action<PhotoFace> Generated { get; set; }\r\n\tpublic string AnalyzerPage { get; set; } = FacePhotoBridge.Page;\r\n\r\n\tpublic sealed class Reference\r\n\t{\r\n\t\tpublic string Url, Status;\r\n\t\tpublic FaceMeasurement Measurement;\r\n\t}\r\n\r\n\treadonly List<Reference> Rows = new();\r\n\tstring Url = \"\", FaceName = \"My photo face\", Message;\r\n\tstring Basis = \"skin\";\r\n\tFacePhotoBridge Bridge;\r\n\tReference Active;\r\n\tpublic void AddUrl(string url)\r\n\t{\r\n\t\tUrl = url;\r\n\t\tAddReference();\r\n\t}\r\n\r\n\tvoid AddReference()\r\n\t{\r\n\t\tif (Rows.Count >= 5)\r\n\t\t{\r\n\t\t\tMessage = \"You can use up to five references. Remove one to add another.\";\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar value = Url.Trim();\r\n\t\tif (!FacePhotoBridge.ValidUrl(value))\r\n\t\t{\r\n\t\t\tMessage = \"Enter a direct HTTPS link to a JPEG, PNG or WebP image.\";\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif (Rows.Any(x => x.Url == value))\r\n\t\t{\r\n\t\t\tMessage = \"That reference is already in the list.\";\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tRows.Add(new() { Url = value, Status = \"Waiting\u2026\" });\r\n\t\tUrl = \"\";\r\n\t\tMessage = null;\r\n\t}\r\n\r\n\tvoid Remove(Reference row)\r\n\t{\r\n\t\tif (Active == row)\r\n\t\t{\r\n\t\t\tBridge?.Dispose();\r\n\t\t\tBridge = null;\r\n\t\t\tActive = null;\r\n\t\t}\r\n\r\n\t\tRows.Remove(row);\r\n\t\tMessage = null;\r\n\t}\r\n\r\n\tpublic void RefreshReferences()\r\n\t{\r\n\t\tBridge?.Dispose();\r\n\t\tBridge = null;\r\n\t\tActive = null;\r\n\t\tMessage = null;\r\n\t\tforeach (var row in Rows)\r\n\t\t{\r\n\t\t\trow.Measurement = null;\r\n\t\t\trow.Status = \"Waiting\u2026\";\r\n\t\t}\r\n\t}\r\n\r\n\tpublic override void Tick()\r\n\t{\r\n\t\tbase.Tick();\r\n\t\tif (Bridge is null)\r\n\t\t{\r\n\t\t\tActive = Rows.FirstOrDefault(x => x.Status == \"Waiting\u2026\");\r\n\t\t\tif (Active is not null)\r\n\t\t\t\ttry\r\n\t\t\t\t{\r\n\t\t\t\t\tBridge = new(this, Active.Url, AnalyzerPage);\r\n\t\t\t\t}\r\n\t\t\t\tcatch (Exception e)\r\n\t\t\t\t{\r\n\t\t\t\t\tActive.Status = e.Message;\r\n\t\t\t\t\tActive = null;\r\n\t\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (Bridge is null)\r\n\t\t\treturn;\r\n\t\tBridge.Tick();\r\n\t\tActive.Status = Bridge.Status;\r\n\t\tif (Bridge.Complete)\r\n\t\t{\r\n\t\t\tActive.Measurement = Bridge.Result;\r\n\t\t\tif (Active.Measurement is not null)\r\n\t\t\t\tActive.Status = \"Ready \u00b7 \" + Active.Measurement.Description;\r\n\t\t\tBridge.Dispose();\r\n\t\t\tBridge = null;\r\n\t\t\tActive = null;\r\n\t\t}\r\n\t}\r\n\r\n\tvoid Generate()\r\n\t{\r\n\t\tif (Bridge is not null || Rows.Any(x => x.Status == \"Waiting\u2026\"))\r\n\t\t{\r\n\t\t\tMessage = \"Wait for the references to finish loading first.\";\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar good = Rows.Where(x => x.Measurement is not null).ToArray();\r\n\t\tif (good.Length == 0)\r\n\t\t{\r\n\t\t\tMessage = \"Load at least one usable reference first.\";\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif (good.Length != Rows.Count)\r\n\t\t{\r\n\t\t\tMessage = \"Remove the failed references or replace them before generating.\";\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif (Appearance.BaseShape?.PhotoFaces.Count >= 4)\r\n\t\t{\r\n\t\t\tMessage = \"You can keep four custom faces in a character. Remove one in Face & body first.\";\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\ttry\r\n\t\t{\r\n\t\t\tUnifiedHuman.Enable(Appearance);\r\n\t\t\tGenerated?.Invoke(PhotoFaceFit.Generate(FaceName, good.Select(x => x.Measurement).ToArray(), PhotoFaceFit.StartingHeads(Appearance, Basis)));\r\n\t\t}\r\n\t\tcatch (Exception e)\r\n\t\t{\r\n\t\t\tMessage = e.Message;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic override void Delete(bool immediate = false)\r\n\t{\r\n\t\tBridge?.Dispose();\r\n\t\tbase.Delete(immediate);\r\n\t}\r\n\r\n\tprotected override int BuildHash() => HashCode.Combine(Url, FaceName, Message, Basis, string.Join(\"|\", Rows.Select(x => x.Url + x.Status)));\r\n}\r\n"
        },
        {
            "Ident": "vendetta.character_editor",
            "Path": "CharacterKit/UnifiedNeckAssets.cs",
            "FileName": "UnifiedNeckAssets.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 410954,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.Threading.Tasks;\r\n\r\nnamespace CharacterEditor;\r\n/// <summary>Head albedos with only the lower-neck band matched to the body atlas.</summary>\npublic static class UnifiedNeckAssets\r\n{\r\n\tstatic readonly Dictionary<string, Material> carriers = new();\r\n\tpublic static async Task LoadHeadAsync(string skin)\r\n\t{\r\n\t\tvar id = System.IO.Path.GetFileNameWithoutExtension(skin);\r\n\t\tif (!carriers.TryGetValue(id, out var material))\r\n\t\t\tmaterial = await Material.LoadAsync($\"materials/makeup/neck/{id}.vmat\");\r\n\t\tvar texture = material?.GetTexture(\"g_tColor\");\r\n\t\tif (texture is null || texture.IsError)\r\n\t\t\tthrow new InvalidOperationException(\"The matched neck texture is unavailable: \" + id);\r\n\t\ttexture.MarkUsed(Math.Max(texture.Width, texture.Height));\r\n\t\tcarriers[id] = material;\r\n\t}\r\n\r\n\tpublic static Texture ColorTexture(string skin) => carriers[System.IO.Path.GetFileNameWithoutExtension(skin)].GetTexture(\"g_tColor\");\r\n}\r\n"
        },
        {
            "Ident": "vendetta.character_editor",
            "Path": "CharacterKit/WardrobeCatalog.Generated.cs",
            "FileName": "WardrobeCatalog.Generated.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 410954,
            "IsPrivate": false,
            "Code": "namespace CharacterEditor;\r\npublic static partial class WardrobeCatalog\r\n{\r\n public static IReadOnlyList<WardrobeItem> Items=>entries??=CreateEntries();\r\n static WardrobeItem[] entries;\r\n static WardrobeItem[] CreateEntries()=>new WardrobeItem[]{\r\n  new(\"Denim Skirt Black\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/dress/denim_skirt/denim_skirt_black.clothing\",\"LeftThigh, RightThigh, LeftKnee, RightKnee\",\"\",\"\",\"Skirt\"),\r\n  new(\"Denim Skirt Blue\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/dress/denim_skirt/denim_skirt_blue.clothing\",\"LeftThigh, RightThigh, LeftKnee, RightKnee\",\"\",\"\",\"Skirt\"),\r\n  new(\"Denim Skirt Green\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/dress/denim_skirt/denim_skirt_green.clothing\",\"LeftThigh, RightThigh, LeftKnee, RightKnee\",\"\",\"\",\"Skirt\"),\r\n  new(\"Denim Skirt Leapard\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/dress/denim_skirt/denim_skirt_leapard.clothing\",\"LeftThigh, RightThigh, LeftKnee, RightKnee\",\"\",\"\",\"Skirt\"),\r\n  new(\"Dotted Dress\",\"Outfits\",\"Outfits\",\"models/citizen_clothes/dress/dotted_dress/dotted_dress.clothing\",\"Chest, Groin, LeftThigh, RightThigh\",\"\",\"Chest\",\"Dress\"),\r\n  new(\"Grand Ball Gown\",\"Outfits\",\"Outfits\",\"models/citizen_clothes/dress/posh_dress/models/posh_dress.clothing\",\"Chest, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"Chest, Legs\",\"Dress\"),\r\n  new(\"Simple Dress\",\"Outfits\",\"Outfits\",\"models/citizen_clothes/dress/simple_dress/simple_dress.clothing\",\"Chest, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"Chest\",\"Dress\"),\r\n  new(\"Simple Dress Black\",\"Outfits\",\"Outfits\",\"models/citizen_clothes/dress/simple_dress/simple_dress_black.clothing\",\"Chest, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"Chest\",\"Dress\"),\r\n  new(\"Simple Dress Blue\",\"Outfits\",\"Outfits\",\"models/citizen_clothes/dress/simple_dress/simple_dress_blue.clothing\",\"Chest, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"Chest\",\"Dress\"),\r\n  new(\"Standard Skirt\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/dress/standard_skirt/standard_skirt.clothing\",\"LeftThigh, RightThigh, LeftKnee, RightKnee\",\"\",\"\",\"Skirt\"),\r\n  new(\"Pirate Outfit\",\"Outfits\",\"Outfits\",\"models/citizen_clothes/full_outfit/pirate_outfit/pirate_outfit.clothing\",\"Chest, LeftArm, RightArm, LeftWrist, RightWrist, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot\",\"Chest, LeftArm, RightArm, LeftWrist, RightWrist, LeftHand, RightHand, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot, Waist\",\"Chest, Legs, Feet\",\"Costume\"),\r\n  new(\"Nerdy Glasses\",\"Glasses\",\"Glasses\",\"models/citizen_clothes/glasses/nerdy_glasses/nerdy_glasses.clothing\",\"Glasses\",\"\",\"\",\"GlassesEye\"),\r\n  new(\"Party Glasses\",\"Glasses\",\"Glasses\",\"models/citizen_clothes/glasses/party_glasses/party_glasses.clothing\",\"Glasses\",\"\",\"\",\"GlassesSun\"),\r\n  new(\"Boxing Gloves\",\"Gloves\",\"Gloves\",\"models/citizen_clothes/gloves/boxing_gloves/boxing_gloves.clothing\",\"LeftHand, RightHand\",\"\",\"Hands\",\"Gloves\"),\r\n  new(\"Boxing Gloves Blue\",\"Gloves\",\"Gloves\",\"models/citizen_clothes/gloves/boxing_gloves/boxing_gloves_blue.clothing\",\"LeftHand, RightHand\",\"\",\"Hands\",\"Gloves\"),\r\n  new(\"Boxing Gloves Red\",\"Gloves\",\"Gloves\",\"models/citizen_clothes/gloves/boxing_gloves/boxing_gloves_red.clothing\",\"LeftHand, RightHand\",\"\",\"Hands\",\"Gloves\"),\r\n  new(\"Fingerless Gloves\",\"Gloves\",\"Gloves\",\"models/citizen_clothes/gloves/fingerless_gloves/fingerless_gloves.clothing\",\"LeftHand, RightHand\",\"\",\"Hands\",\"Gloves\"),\r\n  new(\"Goalie Gloves\",\"Gloves\",\"Gloves\",\"models/citizen_clothes/gloves/goalie_gloves/goalie_gloves.clothing\",\"LeftHand, RightHand\",\"\",\"Hands\",\"Gloves\"),\r\n  new(\"Blue Rubber Gloves\",\"Gloves\",\"Gloves\",\"models/citizen_clothes/gloves/rubber_gloves/rubber_gloves.clothing\",\"LeftHand, RightHand\",\"\",\"Hands\",\"Gloves\"),\r\n  new(\"Black Rubber Gloves\",\"Gloves\",\"Gloves\",\"models/citizen_clothes/gloves/rubber_gloves/rubber_gloves_black.clothing\",\"LeftHand, RightHand\",\"\",\"Hands\",\"Gloves\"),\r\n  new(\"Mask\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/balaclava/balaclava.clothing\",\"HeadTop, HeadBottom\",\"\",\"\",\"HatBeanie\"),\r\n  new(\"Bandana Mask\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/bandana_mask/bandana_mask.clothing\",\"\",\"\",\"\",\"HeadSpecial\"),\r\n  new(\"Baseball Cap\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/baseball_cap/baseball_cap.clothing\",\"HeadTop\",\"\",\"\",\"Hat\"),\r\n  new(\"Baseball Cap Black\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/baseball_cap/baseball_cap_black.clothing\",\"HeadTop\",\"\",\"\",\"Hat\"),\r\n  new(\"Baseball Cap Blue White\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/baseball_cap/baseball_cap_blue_white.clothing\",\"HeadTop\",\"\",\"\",\"Hat\"),\r\n  new(\"Baseball Cap Brown\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/baseball_cap/baseball_cap_brown.clothing\",\"HeadTop\",\"\",\"\",\"Hat\"),\r\n  new(\"Beanie\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/beanie/beanie.clothing\",\"HeadTop\",\"\",\"\",\"HatBeanie\"),\r\n  new(\"Beanie Green\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/beanie/beanie_green.clothing\",\"HeadTop\",\"\",\"\",\"HatBeanie\"),\r\n  new(\"Beanie Red\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/beanie/beanie_red.clothing\",\"HeadTop\",\"\",\"\",\"HatBeanie\"),\r\n  new(\"Boxing Headgear\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/boxing_headgear/boxing_headgear.clothing\",\"HeadTop\",\"\",\"\",\"HatCostume\"),\r\n  new(\"Boxing Headgear Blue\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/boxing_headgear/boxing_headgear_blue.clothing\",\"HeadTop\",\"\",\"\",\"HatCostume\"),\r\n  new(\"Boxing Headgear Red\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/boxing_headgear/boxing_headgear_red.clothing\",\"HeadTop, HeadBottom\",\"\",\"\",\"HatCostume\"),\r\n  new(\"Bucket Hat\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/bucket_hat/bucket_hat.clothing\",\"HeadTop\",\"\",\"\",\"Hat\"),\r\n  new(\"Bucket Hat Black\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/bucket_hat/bucket_hat_black.clothing\",\"HeadTop\",\"\",\"\",\"Hat\"),\r\n  new(\"Chef's Hat\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/chefs_hat/chefs_hat.clothing\",\"HeadTop\",\"\",\"\",\"HatUniform\"),\r\n  new(\"Cowboy Hat\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/cowboy_hat/cowboy_hat.clothing\",\"HeadTop\",\"\",\"\",\"HatCostume\"),\r\n  new(\"Fedora\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/fedora/fedora.clothing\",\"HeadTop\",\"\",\"\",\"HatFormal\"),\r\n  new(\"Fedora Brown\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/fedora/fedora_brown.clothing\",\"HeadTop\",\"\",\"\",\"HatFormal\"),\r\n  new(\"Flatcap\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/flatcap/flatcap.clothing\",\"HeadTop\",\"\",\"\",\"Hat\"),\r\n  new(\"Gasmask\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/gasmask/gasmask.clothing\",\"HeadBottom\",\"\",\"\",\"HatUniform\"),\r\n  new(\"Hair Net\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/hair_net/hair_net.clothing\",\"HeadTop\",\"\",\"\",\"HatUniform\"),\r\n  new(\"Hair Net White\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/hair_net/hair_net_white.clothing\",\"HeadTop\",\"\",\"\",\"HatUniform\"),\r\n  new(\"Hard Hat\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/hard_hat/hard_hat.clothing\",\"HeadTop\",\"\",\"\",\"HatUniform\"),\r\n  new(\"Headphones\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/headphones/models/headphones.clothing\",\"\",\"\",\"\",\"HeadTech\"),\r\n  new(\"Police Hat\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/police_hat/police_hat.clothing\",\"HeadTop\",\"\",\"\",\"HatUniform\"),\r\n  new(\"Racer Helmet\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/racer_helmet/racer_helmet.clothing\",\"HeadTop\",\"\",\"\",\"HatCostume\"),\r\n  new(\"Scifi Trooper Helmet\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/scifi_trooper_helmet/scifi_trooper_helmet.clothing\",\"HeadTop\",\"\",\"\",\"HatCostume\"),\r\n  new(\"Skater Helmet\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/skater_helmet/skater_helmet.clothing\",\"HeadTop\",\"\",\"\",\"HatUniform\"),\r\n  new(\"Skater Helmet Camo\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/skater_helmet/skater_helmet_camo.clothing\",\"HeadTop\",\"\",\"\",\"HatUniform\"),\r\n  new(\"Skater Helmet White\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/skater_helmet/skater_helmet_white.clothing\",\"HeadTop\",\"\",\"\",\"HatUniform\"),\r\n  new(\"Tactical Helmet\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/tactical_helmet/tactical_helmet.clothing\",\"HeadTop, Glasses\",\"\",\"\",\"HatUniform\"),\r\n  new(\"Army Helmet\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/tactical_helmet/tactical_helmet_army.clothing\",\"HeadTop, Glasses\",\"\",\"\",\"HatUniform\"),\r\n  new(\"Trapper Hat\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/trapper_hat/trapper_hat.clothing\",\"HeadTop\",\"\",\"\",\"HatBeanie\"),\r\n  new(\"Trapper Hat Cloth\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/trapper_hat/trapper_hat_cloth.clothing\",\"HeadTop\",\"\",\"\",\"HatBeanie\"),\r\n  new(\"War Helmet\",\"Hats\",\"Hats\",\"models/citizen_clothes/hat/war_helmet/war_helmet.clothing\",\"HeadTop\",\"\",\"\",\"HatUniform\"),\r\n  new(\"Biker Jacket\",\"Outerwear\",\"Outerwear\",\"models/citizen_clothes/jacket/biker_jacket/biker_jacket.clothing\",\"\",\"LeftArm, RightArm\",\"\",\"Jacket\"),\r\n  new(\"Bomber Jacket\",\"Outerwear\",\"Outerwear\",\"models/citizen_clothes/jacket/bomber_jacket/bomber_jacket.clothing\",\"\",\"LeftArm, RightArm\",\"\",\"Jacket\"),\r\n  new(\"Black Leather Jacket\",\"Outerwear\",\"Outerwear\",\"models/citizen_clothes/jacket/brown_leather_jacket/black_leather_jacket.clothing\",\"\",\"LeftArm, RightArm\",\"\",\"Jacket\"),\r\n  new(\"Brown Leather Jacket\",\"Outerwear\",\"Outerwear\",\"models/citizen_clothes/jacket/brown_leather_jacket/brown_leather_jacket.clothing\",\"\",\"LeftArm, RightArm\",\"\",\"Jacket\"),\r\n  new(\"Hoodie\",\"Tops\",\"Tops\",\"models/citizen_clothes/jacket/hoodie/hoodie.clothing\",\"Chest\",\"LeftArm, RightArm\",\"Chest\",\"Hoodie\"),\r\n  new(\"Black Hoodie\",\"Tops\",\"Tops\",\"models/citizen_clothes/jacket/hoodie/hoodie_black.clothing\",\"LeftArm, RightArm, LeftWrist, RightWrist\",\"Chest\",\"Chest\",\"Hoodie\"),\r\n  new(\"Lab Coat\",\"Outerwear\",\"Outerwear\",\"models/citizen_clothes/jacket/labcoat/labcoat.clothing\",\"\",\"LeftArm, RightArm\",\"\",\"Coat\"),\r\n  new(\"Puffer Jacket\",\"Outerwear\",\"Outerwear\",\"models/citizen_clothes/jacket/puffer_jacket/puffer_jacket.clothing\",\"\",\"LeftArm, RightArm\",\"\",\"Jacket\"),\r\n  new(\"Puffer Jacket Blue\",\"Outerwear\",\"Outerwear\",\"models/citizen_clothes/jacket/puffer_jacket/puffer_jacket_blue.clothing\",\"\",\"LeftArm, RightArm\",\"\",\"Jacket\"),\r\n  new(\"Puffer Jacket Orange\",\"Outerwear\",\"Outerwear\",\"models/citizen_clothes/jacket/puffer_jacket/puffer_jacket_orange.clothing\",\"\",\"LeftArm, RightArm\",\"\",\"Jacket\"),\r\n  new(\"Rain Coat\",\"Outerwear\",\"Outerwear\",\"models/citizen_clothes/jacket/raincoat/raincoat.clothing\",\"\",\"LeftArm, RightArm\",\"\",\"Coat\"),\r\n  new(\"Sleeveless Jack\",\"Outerwear\",\"Outerwear\",\"models/citizen_clothes/jacket/sleeveless_jacket/sleeveless_jacket.clothing\",\"\",\"LeftArm, RightArm\",\"\",\"Gilet\"),\r\n  new(\"Wind Breaker\",\"Outerwear\",\"Outerwear\",\"models/citizen_clothes/jacket/wind_breaker/wind_breaker.clothing\",\"\",\"LeftArm, RightArm\",\"\",\"Coat\"),\r\n  new(\"Winter Coat\",\"Outerwear\",\"Outerwear\",\"models/citizen_clothes/jacket/winter_coat/winter_coat.clothing\",\"\",\"LeftArm, RightArm\",\"\",\"Coat\"),\r\n  new(\"Cardigan\",\"Outerwear\",\"Outerwear\",\"models/citizen_clothes/jumper/cardigan/cardigan.clothing\",\"\",\"LeftArm, RightArm\",\"\",\"Cardigan\"),\r\n  new(\"Cardigan Pink\",\"Outerwear\",\"Outerwear\",\"models/citizen_clothes/jumper/cardigan/cardigan_colored.clothing\",\"\",\"LeftArm, RightArm\",\"\",\"Cardigan\"),\r\n  new(\"Cardigan Dark\",\"Outerwear\",\"Outerwear\",\"models/citizen_clothes/jumper/cardigan/cardigan_dark.clothing\",\"\",\"LeftArm, RightArm\",\"\",\"Cardigan\"),\r\n  new(\"Cardigan Dirty\",\"Outerwear\",\"Outerwear\",\"models/citizen_clothes/jumper/cardigan/cardigan_dirty.clothing\",\"\",\"LeftArm, RightArm\",\"\",\"Cardigan\"),\r\n  new(\"Loose Jumper\",\"Tops\",\"Tops\",\"models/citizen_clothes/jumper/loose_jumper/loose_jumper.clothing\",\"Chest\",\"LeftArm, RightArm\",\"Chest\",\"Knitwear\"),\r\n  new(\"Goalie Jumper\",\"Tops\",\"Tops\",\"models/citizen_clothes/jumper/loose_jumper/loose_jumper_goalie.clothing\",\"Chest\",\"LeftArm, RightArm\",\"Chest\",\"Knitwear\"),\r\n  new(\"Loose Jumper Wooly\",\"Tops\",\"Tops\",\"models/citizen_clothes/jumper/loose_jumper/loose_jumper_wooly.clothing\",\"Chest\",\"LeftArm, RightArm\",\"Chest\",\"Knitwear\"),\r\n  new(\"Sleeveless Jumper\",\"Tops\",\"Tops\",\"models/citizen_clothes/jumper/sleeveless_jumper/sleeveless_jumper.clothing\",\"\",\"LeftArm, RightArm\",\"\",\"Knitwear\"),\r\n  new(\"Sleeveless Jumper Grey\",\"Tops\",\"Tops\",\"models/citizen_clothes/jumper/sleeveless_jumper/sleeveless_jumper_grey.clothing\",\"\",\"LeftArm, RightArm\",\"\",\"Knitwear\"),\r\n  new(\"Sleeveless Jumper Red\",\"Tops\",\"Tops\",\"models/citizen_clothes/jumper/sleeveless_jumper/sleeveless_jumper_red.clothing\",\"\",\"LeftArm, RightArm\",\"\",\"Knitwear\"),\r\n  new(\"Pearl Necklace\",\"Accessories\",\"necklace\",\"models/citizen_clothes/necklace/pearl_necklace/pearl_necklace.clothing\",\"Chin\",\"\",\"\",\"NecklacePendant\"),\r\n  new(\"Vintage Necklace\",\"Accessories\",\"necklace\",\"models/citizen_clothes/necklace/vintage_necklace/vintage_necklace.clothing\",\"Chin\",\"\",\"\",\"NecklacePendant\"),\r\n  new(\"Santa Hat 2024\",\"Hats\",\"Hats\",\"models/citizen_clothes/seasonal/2024/santa/santahat.clothing\",\"HeadTop\",\"\",\"\",\"HatCostume\"),\r\n  new(\"Santa Jacket 2024\",\"Outerwear\",\"Outerwear\",\"models/citizen_clothes/seasonal/2024/santa/santajacket.clothing\",\"LeftArm, RightArm\",\"LeftArm, RightArm\",\"Chest\",\"Coat\"),\r\n  new(\"Santa Trousers 2024\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/seasonal/2024/santa/santatrousers.clothing\",\"Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot\",\"Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot\",\"Legs, Feet\",\"Trousers\"),\r\n  new(\"Easter Bonnet\",\"Hats\",\"Hats\",\"models/citizen_clothes/seasonal/2025/easter_farmer/easter_bonnet/easter_bonnet.clothing\",\"HeadTop\",\"\",\"\",\"HatCostume\"),\r\n  new(\"Easter Dungarees\",\"Outfits\",\"Outfits\",\"models/citizen_clothes/seasonal/2025/easter_farmer/easter_dungarees/models/easter_dungarees.clothing\",\"Chest, LeftArm, RightArm, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"Chest, LeftArm, RightArm, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"Chest, Legs\",\"Suit\"),\r\n  new(\"Easter Slippers\",\"Shoes\",\"Shoes\",\"models/citizen_clothes/seasonal/2025/easter_farmer/easter_slippers/models/easter_slippers.clothing\",\"LeftFoot, RightFoot\",\"\",\"Feet\",\"Footwear\"),\r\n  new(\"Valentines Bear Head 2025\",\"Hats\",\"Hats\",\"models/citizen_clothes/seasonal/2025/valentines_bear/bear_head.clothing\",\"HeadTop, HeadBottom\",\"HeadTop, HeadBottom\",\"Head\",\"HatCostume\"),\r\n  new(\"Valentines Bear Top 2025\",\"Outfits\",\"Outfits\",\"models/citizen_clothes/seasonal/2025/valentines_bear/bear_top.clothing\",\"Chest, LeftArm, RightArm, LeftWrist, RightWrist, LeftHand, RightHand\",\"LeftArm, RightArm, LeftWrist, RightWrist, LeftHand, RightHand\",\"Chest, Hands\",\"Costume\"),\r\n  new(\"Valentines Bear Trousers 2025\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/seasonal/2025/valentines_bear/bear_trousers.clothing\",\"Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot\",\"Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot\",\"Legs, Feet\",\"Trousers\"),\r\n  new(\"Wizard Beard\",\"Accessories\",\"costume_beard\",\"models/citizen_clothes/seasonal/2025/wizard_outfit/wizard_beard/wizard_beard.clothing\",\"Face\",\"\",\"\",\"FacialHairBeard\"),\r\n  new(\"Wizard Gown\",\"Outerwear\",\"Outerwear\",\"models/citizen_clothes/seasonal/2025/wizard_outfit/wizard_gown/wizard_gown.clothing\",\"Chest, LeftArm, RightArm\",\"Chest, LeftArm, RightArm, LeftWrist\",\"Chest\",\"Coat\"),\r\n  new(\"Wizard Hat\",\"Hats\",\"Hats\",\"models/citizen_clothes/seasonal/2025/wizard_outfit/wizard_hat/wizard_hat.clothing\",\"HeadTop\",\"\",\"\",\"HatCostume\"),\r\n  new(\"Wizard Trousers\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/seasonal/2025/wizard_outfit/wizard_trousers/wizard_trousers.clothing\",\"Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot\",\"\",\"Legs, Feet\",\"Trousers\"),\r\n  new(\"Army Shirt\",\"Tops\",\"Tops\",\"models/citizen_clothes/shirt/army_shirt/army_shirt.clothing\",\"Chest, LeftArm, RightArm\",\"\",\"Chest\",\"Shirt\"),\r\n  new(\"Chef Shirt\",\"Tops\",\"Tops\",\"models/citizen_clothes/shirt/chef_shirt/chef_shirt.clothing\",\"Chest, LeftArm, RightArm\",\"\",\"Chest\",\"Shirt\"),\r\n  new(\"Fisherman Jumpsuit\",\"Outfits\",\"Outfits\",\"models/citizen_clothes/shirt/fisherman_jumpsuit/fisherman_jumpsuit.clothing\",\"Chest, LeftArm, RightArm, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot\",\"\",\"Chest, Legs, Feet\",\"Suit\"),\r\n  new(\"Flannel Shirt\",\"Tops\",\"Tops\",\"models/citizen_clothes/shirt/flannel_shirt/flannel_shirt.clothing\",\"Chest, LeftArm, RightArm\",\"\",\"Chest\",\"Shirt\"),\r\n  new(\"Blue Shirt\",\"Tops\",\"Tops\",\"models/citizen_clothes/shirt/flannel_shirt/variations/blue_shirt/blue_shirt.clothing\",\"Chest, LeftArm, RightArm\",\"\",\"Chest\",\"Shirt\"),\r\n  new(\"Hawaiian Shirt\",\"Tops\",\"Tops\",\"models/citizen_clothes/shirt/hawaiian_shirt/hawaiian shirt.clothing\",\"Chest, LeftArm, RightArm\",\"\",\"Chest\",\"Shirt\"),\r\n  new(\"Blue Jumpsuit\",\"Outfits\",\"Outfits\",\"models/citizen_clothes/shirt/jumpsuit/blue_jumpsuit.clothing\",\"Chest, LeftArm, RightArm, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"Chest, Legs\",\"Suit\"),\r\n  new(\"Prison Jumpsuit\",\"Outfits\",\"Outfits\",\"models/citizen_clothes/shirt/jumpsuit/prison_jumpsuit.clothing\",\"Chest, LeftArm, RightArm, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"Chest, Legs\",\"Suit\"),\r\n  new(\"Leotard\",\"Tops\",\"Tops\",\"models/citizen_clothes/shirt/leotard/leotard.clothing\",\"Chest, LeftArm, RightArm, LeftWrist, RightWrist, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"\",\"TShirt\"),\r\n  new(\"Leotard Blue\",\"Tops\",\"Tops\",\"models/citizen_clothes/shirt/leotard/leotard_blue.clothing\",\"Chest, LeftArm, RightArm, LeftWrist, RightWrist, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"\",\"Shirt\"),\r\n  new(\"Loose Crop Top\",\"Tops\",\"Tops\",\"models/citizen_clothes/shirt/loose_crop_top/loose_crop_top.clothing\",\"Chest, LeftArm, RightArm\",\"\",\"Chest\",\"Shirt\"),\r\n  new(\"Loose Crop Top Pattern\",\"Tops\",\"Tops\",\"models/citizen_clothes/shirt/loose_crop_top/loose_crop_top_pattern.clothing\",\"Chest, LeftArm, RightArm\",\"\",\"Chest\",\"Shirt\"),\r\n  new(\"Loose Crop Top Purple\",\"Tops\",\"Tops\",\"models/citizen_clothes/shirt/loose_crop_top/loose_crop_top_purple.clothing\",\"Chest, LeftArm, RightArm\",\"\",\"Chest\",\"Shirt\"),\r\n  new(\"Loose Crop Top White\",\"Tops\",\"Tops\",\"models/citizen_clothes/shirt/loose_crop_top/loose_crop_top_white.clothing\",\"Chest, LeftArm, RightArm\",\"\",\"Chest\",\"Shirt\"),\r\n  new(\"Loose Shirt\",\"Tops\",\"Tops\",\"models/citizen_clothes/shirt/loose_shirt/loose_shirt.clothing\",\"Chest, LeftArm, RightArm, LeftWrist, RightWrist\",\"\",\"Chest\",\"TShirt\"),\r\n  new(\"Polo Shirt\",\"Tops\",\"Tops\",\"models/citizen_clothes/shirt/polo_shirt/polo_shirt.clothing\",\"Chest, LeftArm, RightArm\",\"\",\"Chest\",\"TShirt\"),\r\n  new(\"Polo Shirt White\",\"Tops\",\"Tops\",\"models/citizen_clothes/shirt/polo_shirt/polo_shirt_white.clothing\",\"Chest, LeftArm, RightArm\",\"\",\"Chest\",\"TShirt\"),\r\n  new(\"Tank Top \",\"Tops\",\"Tops\",\"models/citizen_clothes/shirt/tanktop/tanktop.clothing\",\"Chest\",\"\",\"\",\"TShirt\"),\r\n  new(\"Football Tshirt\",\"Tops\",\"Tops\",\"models/citizen_clothes/shirt/tshirt/football_tshirt.clothing\",\"Chest, LeftArm, RightArm\",\"\",\"Chest\",\"TShirt\"),\r\n  new(\"Tshirt\",\"Tops\",\"Tops\",\"models/citizen_clothes/shirt/tshirt/tshirt.clothing\",\"Chest, LeftArm, RightArm\",\"\",\"Chest\",\"Tops\"),\r\n  new(\"Waistcoat and Shirt\",\"Tops\",\"Tops\",\"models/citizen_clothes/shirt/waistcoat_and_shirt/waistcoat_and_shirt.clothing\",\"Chest, LeftArm, RightArm\",\"\",\"Chest\",\"Shirt\"),\r\n  new(\"Boot Heels Black\",\"Shoes\",\"Shoes\",\"models/citizen_clothes/shoes/boot_heels/boot_heels_black.clothing\",\"LeftFoot, RightFoot\",\"\",\"Feet\",\"Heels\"),\r\n  new(\"Boot Heels Brown\",\"Shoes\",\"Shoes\",\"models/citizen_clothes/shoes/boot_heels/boot_heels_brown.clothing\",\"LeftFoot, RightFoot\",\"\",\"Feet\",\"Heels\"),\r\n  new(\"Boot Heels Leopard\",\"Shoes\",\"Shoes\",\"models/citizen_clothes/shoes/boot_heels/boot_heels_leopard.clothing\",\"LeftFoot, RightFoot\",\"\",\"Feet\",\"Heels\"),\r\n  new(\"Boot Heels Red\",\"Shoes\",\"Shoes\",\"models/citizen_clothes/shoes/boot_heels/boot_heels_red.clothing\",\"LeftFoot, RightFoot\",\"\",\"Feet\",\"Heels\"),\r\n  new(\"Army Boots\",\"Shoes\",\"Shoes\",\"models/citizen_clothes/shoes/boots/army_boots.clothing\",\"LeftFoot, RightFoot\",\"\",\"Feet\",\"Boots\"),\r\n  new(\"Black Boots\",\"Shoes\",\"Shoes\",\"models/citizen_clothes/shoes/boots/black_boots.clothing\",\"LeftFoot, RightFoot\",\"\",\"Feet\",\"Boots\"),\r\n  new(\"Boxing Boots\",\"Shoes\",\"Shoes\",\"models/citizen_clothes/shoes/boxing_boots/boxing_boots.clothing\",\"LeftFoot, RightFoot\",\"\",\"Feet\",\"Boots\"),\r\n  new(\"Boxing Boots Blue\",\"Shoes\",\"Shoes\",\"models/citizen_clothes/shoes/boxing_boots/boxing_boots_blue.clothing\",\"LeftFoot, RightFoot\",\"\",\"Feet\",\"Boots\"),\r\n  new(\"Clogs and Socks\",\"Shoes\",\"Shoes\",\"models/citizen_clothes/shoes/crocs_and_socks/clogs_and_socks.clothing\",\"LeftFoot, RightFoot\",\"\",\"Feet\",\"Slippers\"),\r\n  new(\"Flat Shoes\",\"Shoes\",\"Shoes\",\"models/citizen_clothes/shoes/flatshoes/textures/flat_shoes.clothing\",\"LeftFoot, RightFoot\",\"\",\"\",\"Shoes\"),\r\n  new(\"Football Shoes\",\"Shoes\",\"Shoes\",\"models/citizen_clothes/shoes/football_shoes/football_shoes.clothing\",\"LeftFoot, RightFoot\",\"\",\"Feet\",\"Trainers\"),\r\n  new(\"Slippers\",\"Shoes\",\"Shoes\",\"models/citizen_clothes/shoes/slippers/slippers.clothing\",\"LeftFoot, RightFoot\",\"\",\"\",\"Slippers\"),\r\n  new(\"Blue Sneakers\",\"Shoes\",\"Shoes\",\"models/citizen_clothes/shoes/sneakers/blue_sneakers.clothing\",\"LeftFoot, RightFoot\",\"\",\"Feet\",\"Shoes\"),\r\n  new(\"Sneakers\",\"Shoes\",\"Shoes\",\"models/citizen_clothes/shoes/sneakers/sneakers.clothing\",\"LeftFoot, RightFoot\",\"\",\"Feet\",\"Shoes\"),\r\n  new(\"Trainers\",\"Shoes\",\"Shoes\",\"models/citizen_clothes/shoes/trainers/trainers.clothing\",\"LeftFoot, RightFoot\",\"\",\"Feet\",\"Trainers\"),\r\n  new(\"Boxing Shorts\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/shorts/boxing_shorts/boxing_shorts.clothing\",\"Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"Legs\",\"Shorts\"),\r\n  new(\"Boxing Shorts Blue\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/shorts/boxing_shorts/boxing_shorts_blue.clothing\",\"Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"Legs\",\"Shorts\"),\r\n  new(\"Boxing Shorts Red\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/shorts/boxing_shorts/boxing_shorts_red.clothing\",\"Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"Legs\",\"Shorts\"),\r\n  new(\"Football Shorts\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/shorts/football_shorts/football_shorts.clothing\",\"Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"Legs\",\"Shorts\"),\r\n  new(\"Shorts\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/shorts/shorts/shorts.clothing\",\"Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"Legs\",\"Shorts\"),\r\n  new(\"Summer Shorts\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/shorts/summer_shorts/summer shorts.clothing\",\"LeftThigh, RightThigh\",\"\",\"Legs\",\"Shorts\"),\r\n  new(\"Bin Man Trousers\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/trousers/cargopants/binman_trousers.clothing\",\"Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"Legs\",\"Trousers\"),\r\n  new(\"Brown Cargo Pants\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/trousers/cargopants/brown_cargo_pants.clothing\",\"Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"Legs\",\"Trousers\"),\r\n  new(\"Army Cargo Pants\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/trousers/cargopants/cargo_pants_army.clothing\",\"Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"Legs\",\"Trousers\"),\r\n  new(\"Jeans\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/trousers/jeans/jeans.clothing\",\"Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"Legs\",\"Jeans\"),\r\n  new(\"Black Jeans\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/trousers/jeans/jeans_black.clothing\",\"Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"Legs\",\"Jeans\"),\r\n  new(\"Loose Jeans\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/trousers/loosejeans/loose_jeans.clothing\",\"Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"Legs\",\"Jeans\"),\r\n  new(\"Loose Trousers\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/trousers/loosetrousers/loose_trousers.clothing\",\"Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"Legs\",\"Trousers\"),\r\n  new(\"Smart Trousers\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/trousers/smarttrousers/trousers.smart.clothing\",\"Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"Legs\",\"Trousers\"),\r\n  new(\"Trackie Bottoms\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/trousers/trackiebottoms/trackie_bottoms.clothing\",\"Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"Legs\",\"Trousers\"),\r\n  new(\"Black Trackie Bottoms\",\"Bottoms\",\"Bottoms\",\"models/citizen_clothes/trousers/trackiebottoms/trackie_bottoms_black.clothing\",\"Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\",\"\",\"Legs\",\"Trousers\"),\r\n  new(\"Chef's Apron\",\"Outerwear\",\"Outerwear\",\"models/citizen_clothes/vest/chefs_apron/chefs_apron.clothing\",\"\",\"LeftArm, RightArm\",\"\",\"Vest\"),\r\n  new(\"Tactical Vest\",\"Outerwear\",\"Outerwear\",\"models/citizen_clothes/vest/tactical_vest/models/tactical_vest.clothing\",\"\",\"LeftArm, RightArm\",\"\",\"Vest\"),\r\n  new(\"Army Vest\",\"Outerwear\",\"Outerwear\",\"models/citizen_clothes/vest/tactical_vest/models/tactical_vest_army.clothing\",\"LeftArm, RightArm\",\"\",\"\",\"Vest\"),\r\n  new(\"Modern Watch\",\"Accessories\",\"watch\",\"models/citizen_clothes/watch/modern_watch/modern_watch.clothing\",\"LeftWrist\",\"\",\"\",\"WristWatch\"),\r\n  new(\"Black Modern Watch\",\"Accessories\",\"watch\",\"models/citizen_clothes/watch/modern_watch/modern_watch_black.clothing\",\"LeftWrist\",\"\",\"\",\"WristWatch\")\r\n };\r\n}\r\n"
        },
        {
            "Ident": "vendetta.character_editor",
            "Path": "Experiments/FaceTransferPreview.cs",
            "FileName": "FaceTransferPreview.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 410954,
            "IsPrivate": false,
            "Code": "using System;\r\nusing System.Text.Json;\r\nusing Sandbox.UI;\r\n\r\nnamespace CharacterEditor;\r\n/// <summary>Isolated rest-surface experiment. These morphs are not animated avatar presets.</summary>\npublic sealed class FaceTransferPreview : ScenePanel\r\n{\r\n\tpublic sealed class Geometry\r\n\t{\r\n\t\tpublic float[][] Source { get; set; }\r\n\t\tpublic float[][] Target { get; set; }\r\n\t\tpublic float[][] Existing { get; set; }\r\n\t\tpublic float[][] Experimental { get; set; }\r\n\t\tpublic int[][] Triangles { get; set; }\r\n\t}\r\n\r\n\tpublic string Pair { get; set; } = \"human03-to-human07\";\r\n\tpublic string Mode { get; set; } = \"Target\";\r\n\tpublic float Amount { get; set; } = 1;\r\n\tpublic float Yaw { get; set; }\r\n\tpublic string Error { get; private set; }\r\n\tpublic SkinnedModelRenderer Body { get; private set; }\r\n\tpublic CameraComponent ViewCamera { get; private set; }\r\n\r\n\tstring loaded;\r\n\tpublic FaceTransferPreview()\r\n\t{\r\n\t\tRenderScene = new Scene();\r\n\t\tusing var scope = RenderScene.Push();\r\n\t\tViewCamera = new GameObject().Components.Create<CameraComponent>();\r\n\t\tViewCamera.FieldOfView = 30;\r\n\t\tViewCamera.ZNear = .1f;\r\n\t\tViewCamera.BackgroundColor = new(.065f, .085f, .11f);\r\n\t\tCharacterStudioLighting.Create();\r\n\t}\r\n\r\n\tstatic Vector3 V(float[] p) => new(p[0], p[1], p[2]);\r\n\tstatic Vector3[] Normals(Vector3[] p, int[][] triangles)\r\n\t{\r\n\t\tvar normals = new Vector3[p.Length];\r\n\t\tforeach (var t in triangles)\r\n\t\t{\r\n\t\t\tvar n = Vector3.Cross(p[t[1]] - p[t[0]], p[t[2]] - p[t[0]]);\r\n\t\t\tforeach (var i in t)\r\n\t\t\t\tnormals[i] += n;\r\n\t\t}\r\n\r\n\t\treturn normals.Select(n => n.Normal).ToArray();\r\n\t}\r\n\r\n\tpublic static Model Build(Geometry data)\r\n\t{\r\n\t\tvar p = data.Source.Select(V).ToArray();\r\n\t\tvar normals = Normals(p, data.Triangles);\r\n\t\tvar mesh = new Mesh(Material.Load(\"materials/default.vmat\"));\r\n\t\tvar 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();\r\n\t\tmesh.CreateVertexBuffer<Vertex>(vertices.Length, vertices.AsSpan());\r\n\t\tvar indices = data.Triangles.SelectMany(t => t).ToArray();\r\n\t\tmesh.CreateIndexBuffer(indices.Length, indices.AsSpan());\r\n\t\tforeach (var mode in new[]\r\n\t\t{\r\n\t\t\t\"Existing\",\r\n\t\t\t\"Experimental\",\r\n\t\t\t\"Target\"\r\n\t\t}\r\n\r\n\t\t)\r\n\t\t{\r\n\t\t\tvar target = (mode == \"Existing\" ? data.Existing : mode == \"Experimental\" ? data.Experimental : data.Target).Select(V).ToArray();\r\n\t\t\tvar n = Normals(target, data.Triangles);\r\n\t\t\tmesh.AddMorph(mode, p.Select((v, i) => new MorphDelta(i, target[i] - v, n[i] - normals[i])).ToArray());\r\n\t\t}\r\n\r\n\t\tmesh.Bounds = new BBox(new Vector3(-8, -8, 54), new Vector3(9, 9, 72));\r\n\t\treturn Model.Builder.AddMesh(mesh).Create();\r\n\t}\r\n\r\n\tpublic override void Tick()\r\n\t{\r\n\t\tbase.Tick();\r\n\t\tusing var scope = RenderScene.Push();\r\n\t\tif (loaded != Pair)\r\n\t\t{\r\n\t\t\tloaded = Pair;\r\n\t\t\tBody?.GameObject.Destroy();\r\n\t\t\tError = null;\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tif (Pair != \"human03-to-human07\" && Pair != \"human02-to-human04\")\r\n\t\t\t\t\tthrow new ArgumentException(\"Unknown experiment pair.\");\r\n\t\t\t\tvar data = JsonSerializer.Deserialize<Geometry>(FileSystem.Mounted.ReadAllText($\"experiments/face_transfer/{Pair}.json\"));\r\n\t\t\t\tBody = new GameObject().Components.Create<SkinnedModelRenderer>();\r\n\t\t\t\tBody.Model = Build(data);\r\n\t\t\t\tBody.UseAnimGraph = false;\r\n\t\t\t\tBody.Tint = new(.65f, .69f, .74f);\r\n\t\t\t}\r\n\t\t\tcatch (Exception e)\r\n\t\t\t{\r\n\t\t\t\tError = e.Message;\r\n\t\t\t\tLog.Warning($\"Face lab: {Error}\");\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (Body.IsValid())\r\n\t\t{\r\n\t\t\tBody.LocalRotation = Rotation.FromYaw(Yaw);\r\n\t\t\tforeach (var mode in new[]\r\n\t\t\t{\r\n\t\t\t\t\"Existing\",\r\n\t\t\t\t\"Experimental\",\r\n\t\t\t\t\"Target\"\r\n\t\t\t}\r\n\r\n\t\t\t)\r\n\t\t\t\tBody.Morphs.Set(mode, Mode == mode ? Math.Clamp(Amount, 0, 1) : 0);\r\n\t\t}\r\n\r\n\t\tfloat distance = MathF.Max(25, 31 * Box.Rect.Width / MathF.Max(1, Box.Rect.Height));\r\n\t\tViewCamera.WorldPosition = new(distance, 0, 63.2f);\r\n\t\tViewCamera.WorldRotation = Rotation.LookAt(new Vector3(0, 0, 63.2f) - ViewCamera.WorldPosition);\r\n\t}\r\n\r\n\tpublic override void Delete(bool immediate = false)\r\n\t{\r\n\t\tvar scene = RenderScene;\r\n\t\tbase.Delete(immediate);\r\n\t\tscene?.Destroy();\r\n\t}\r\n}\r\n"
        }
    ]
}