{"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 \u0022The selected native skin resource is unavailable.\u0022;\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 \u0022The selected native human model is unavailable.\u0022;\r\n\t\tvar clothes = new List\u003CClothing\u003E\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 =\u003E !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 \u0022A selected native hairstyle resource is unavailable.\u0022;\r\n\t\t\tif (path == data.HairPath \u0026\u0026 !ClothingResources.IsHair(clothing) || path == data.FacialHairPath \u0026\u0026 !ClothingResources.IsFacialHair(clothing))\r\n\t\t\t\treturn \u0022The selected item does not belong in this hair slot.\u0022;\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(\u0022human\u0022);\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 \u0022A selected hairstyle does not support this native human body.\u0022;\r\n\t\t\tvar model = Model.Load(path);\r\n\t\t\tif (!model.IsValid || model.IsError)\r\n\t\t\t\treturn \u0022A selected native hairstyle model is unavailable.\u0022;\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/// \u003Csummary\u003EPortable appearance values. No live models, components or engine color converters are required to read a save.\u003C/summary\u003E\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; } = \u0022man-adult\u0022;\r\n\tpublic string SkinPath { get; set; } = \u0022models/citizen_clothes/body/human03.clothing\u0022;\r\n\tpublic string HairPath { get; set; } = \u0022models/citizen_clothes/hair/hair_shortscruffy/hair_shortscruffy_brown.clothing\u0022;\r\n\tpublic string FacialHairPath { get; set; } = \u0022\u0022;\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 =\u003E 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 =\u003E 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 =\u003E 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() =\u003E 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) =\u003E data is null ? \u0022\u0022 : $\u0022{(data.BaseShape is null ? data.SkinPath : UnifiedHuman.ModelPath \u002B UnifiedHuman.PresentationSkin(data))}|{data.BaseShape?.ClothingVariant}|{data.HairPath}|{data.FacialHairPath}|{HumanMarkingModels.NeedsArmRegions(data.Makeup)}|{string.Join(\u0027;\u0027, WardrobeCatalog.Paths(data))}\u0022;\r\n\t/// \u003Csummary\u003ECold-safe validation used by save lists and menu preflight. Resources are resolved separately when applied.\u003C/summary\u003E\r\n\tpublic static string Validate(CharacterAppearanceData data)\r\n\t{\r\n\t\tif (data is null)\r\n\t\t\treturn \u0022Choose a character appearance.\u0022;\r\n\t\tif (data.Version is not (1 or 2))\r\n\t\t\treturn \u0022This appearance version is unsupported.\u0022;\r\n\t\tif (data.BaseShape is not null \u0026\u0026 (data.Version != 2 || !data.BaseShape.Valid))\r\n\t\t\treturn \u0022Base shape requires version 2, valid face influences and custom face data, and body controls between zero and one.\u0022;\r\n\t\tif (data.Version == 1 \u0026\u0026 data.ClothingPaths is not null)\r\n\t\t\treturn \u0022Saved clothing requires appearance version 2.\u0022;\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 \u0022Choose a supported character voice.\u0022;\r\n\t\tif (data.Makeup?.Valid != true)\r\n\t\t\treturn \u0022Makeup colors and strength must be finite values between zero and one, with a supported tattoo style.\u0022;\r\n\t\tif (data.HairPath is null || data.FacialHairPath is null)\r\n\t\t\treturn \u0022Use an empty path for no hair or facial hair.\u0022;\r\n\t\tvar skin = AppearanceCatalog.Skins.FirstOrDefault(x =\u003E string.Equals(x.ResourcePath, data.SkinPath, StringComparison.OrdinalIgnoreCase));\r\n\t\tif (skin is null)\r\n\t\t\treturn \u0022Choose a supported native human face and skin.\u0022;\r\n\t\tif (data.HairPath.Length \u003E 0 \u0026\u0026 !ClothingResources.ValidReference(data.HairPath))\r\n\t\t\treturn \u0022Choose a supported native hairstyle.\u0022;\r\n\t\tvar facial = AppearanceCatalog.FacialHair.FirstOrDefault(x =\u003E string.Equals(x.ResourcePath, data.FacialHairPath ?? \u0022\u0022, StringComparison.OrdinalIgnoreCase));\r\n\t\tif ((data.FacialHairPath.Length \u003E 0 \u0026\u0026 !ClothingResources.ValidReference(data.FacialHairPath)) || (facial is not null \u0026\u0026 data.BaseShape is null \u0026\u0026 !string.IsNullOrEmpty(facial.BodyType) \u0026\u0026 facial.BodyType != skin.BodyType))\r\n\t\t\treturn \u0022That facial hair does not fit the selected body.\u0022;\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 =\u003E !float.IsFinite(x) || x \u003C 0 || x \u003E 1))\r\n\t\t\treturn \u0022Hair color must contain finite values between zero and one.\u0022;\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 =\u003E !float.IsFinite(x) || x \u003C 0 || x \u003E 1))\r\n\t\t\treturn \u0022Skin color must contain finite values between zero and one.\u0022;\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 =\u003E !float.IsFinite(x) || x \u003C 0 || x \u003E 1))\r\n\t\t\treturn \u0022Eye color must contain finite values between zero and one.\u0022;\r\n\t\tif (AppearanceCatalog.Sliders.Any(x =\u003E !float.IsFinite(x.Get(data)) || x.Get(data) \u003C x.Min || x.Get(data) \u003E x.Max))\r\n\t\t\treturn \u0022Appearance sliders must contain finite values between zero and one.\u0022;\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/// \u003Csummary\u003EAuthored human cuts fitted automatically to both unified body endpoints.\u003C/summary\u003E\r\npublic static partial class UnifiedWardrobe\r\n{\r\n\t/// \u003Csummary\u003EHead replacements can retain skin or deliberately replace the entire neck.\u003C/summary\u003E\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\u0027s native skin presentation. A transparent/non-skin\r\n\t\t// head material must not be replaced with the unified human\u0027s skin neck.\r\n\t\treturn skin.MeshInfo.Meshes.Where(m =\u003E m.Name == \u0022head_01\u0022)\r\n\t\t\t.SelectMany(m =\u003E m.DrawCalls).Any(d =\u003E d.Material?.ShaderName == \u0022shaders/skin.shader\u0022);\r\n\t}\r\n\r\n\tpublic static string ModelPath(Clothing item, IEnumerable\u003CClothing\u003E 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 \u0026\u0026 native != item.HumanAltModel \u0026\u0026 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 \u0026\u0026 !string.IsNullOrEmpty(item.HumanAltModel) \u0026\u0026 !string.IsNullOrEmpty(item.HumanAltFemaleModel))\r\n\t\t{\r\n\t\t\tvar choice = data.BaseShape.ClothingVariant == \u0022male\u0022 ? 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 \u003E= .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\u003CClothing\u003E 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) \u0026\u0026 string.IsNullOrEmpty(item.HumanAltFemaleModel)))\r\n\t\t\treturn native;\r\n\t\tif (item.Category.ToString().StartsWith(\u0022FacialHair\u0022) || item.Category.ToString().StartsWith(\u0022Hair\u0022))\r\n\t\t\treturn native;\r\n\t\tbool female = path == item.HumanAltFemaleModel \u0026\u0026 path != item.HumanAltModel;\r\n\t\tif (path != item.HumanAltModel \u0026\u0026 path != item.HumanAltFemaleModel)\r\n\t\t\tfemale = tags.Has(\u0022female\u0022);\r\n\t\treturn AutomaticWardrobeFit.Fit(native, female, fitFace: (item.HideBody \u0026 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 \u0026\u0026 AutomaticWardrobeFit.IsFitted(renderer.Model))\r\n\t\t\trenderer.Morphs.Set(\u0022ce_body_female\u0022, 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 = \u0022\u0022, string Icon = \u0022\u0022);\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 =\u003E 0;\r\n\tpublic float Max =\u003E 1;\r\n\tpublic float Neutral =\u003E Id is \u0022eartaper\u0022 or \u0022belly\u0022 ? 0 : 0.5f;\r\n\r\n\tpublic string LabelFor(CharacterAppearanceData data) =\u003E Id == \u0022bodyfeature\u0022 ? (AppearanceCatalog.IsFemale(data) ? \u0022Breast size\u0022 : \u0022Bulge size\u0022) : 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) =\u003E Id switch\r\n\t{\r\n\t\t\u0022neck\u0022 =\u003E data.NeckSize,\r\n\t\t\u0022trapezius\u0022 =\u003E data.TrapeziusSize,\r\n\t\t\u0022collarbone\u0022 =\u003E data.CollarboneHeight,\r\n\t\t\u0022belly\u0022 =\u003E data.BellySize,\r\n\t\t\u0022waist\u0022 =\u003E data.WaistSize,\r\n\t\t\u0022chest\u0022 =\u003E data.ChestSize,\r\n\t\t\u0022fitness\u0022 =\u003E data.Fitness,\r\n\t\t\u0022head\u0022 =\u003E data.HeadShape,\r\n\t\t\u0022nose\u0022 =\u003E data.NoseSize,\r\n\t\t\u0022nosewidth\u0022 =\u003E data.NoseWidth,\r\n\t\t\u0022chin\u0022 =\u003E data.ChinSize,\r\n\t\t\u0022chinwidth\u0022 =\u003E data.ChinWidth,\r\n\t\t\u0022jaw\u0022 =\u003E data.JawWidth,\r\n\t\t\u0022nostrils\u0022 =\u003E data.NostrilWidth,\r\n\t\t\u0022forehead\u0022 =\u003E data.ForeheadWidth,\r\n\t\t\u0022cheekbones\u0022 =\u003E data.CheekboneWidth,\r\n\t\t\u0022ears\u0022 =\u003E data.EarSize,\r\n\t\t\u0022eyes\u0022 =\u003E data.EyeSize,\r\n\t\t\u0022cheeks\u0022 =\u003E data.CheekFullness,\r\n\t\t\u0022nostrilfullness\u0022 =\u003E data.NostrilFullness,\r\n\t\t\u0022noseheight\u0022 =\u003E data.NoseHeight,\r\n\t\t\u0022chinheight\u0022 =\u003E data.ChinHeight,\r\n\t\t\u0022submental\u0022 =\u003E data.SubmentalFullness,\r\n\t\t\u0022brow\u0022 =\u003E data.BrowProminence,\r\n\t\t\u0022bodyfeature\u0022 =\u003E data.BodyFeatureSize,\r\n\t\t\u0022butt\u0022 =\u003E data.ButtSize,\r\n\t\t\u0022feet\u0022 =\u003E data.FootSize,\r\n\t\t\u0022eartaper\u0022 =\u003E data.EarTaper,\r\n\t\t\u0022philtrum\u0022 =\u003E data.PhiltrumWidth,\r\n\t\t\u0022columella\u0022 =\u003E data.ColumellaWidth,\r\n\t\t\u0022mouthwidth\u0022 =\u003E data.MouthWidth,\r\n\t\t\u0022mouthprojection\u0022 =\u003E data.MouthProjection,\r\n\t\t\u0022upperlip\u0022 =\u003E data.UpperLipFullness,\r\n\t\t\u0022lowerlip\u0022 =\u003E data.LowerLipFullness,\r\n\t\t_ =\u003E 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 \u0022neck\u0022:\r\n\t\t\t\tdata.NeckSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022trapezius\u0022:\r\n\t\t\t\tdata.TrapeziusSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022collarbone\u0022:\r\n\t\t\t\tdata.CollarboneHeight = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022belly\u0022:\r\n\t\t\t\tdata.BellySize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022waist\u0022:\r\n\t\t\t\tdata.WaistSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022chest\u0022:\r\n\t\t\t\tdata.ChestSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022fitness\u0022:\r\n\t\t\t\tdata.Fitness = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022head\u0022:\r\n\t\t\t\tdata.HeadShape = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022nose\u0022:\r\n\t\t\t\tdata.NoseSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022nosewidth\u0022:\r\n\t\t\t\tdata.NoseWidth = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022chin\u0022:\r\n\t\t\t\tdata.ChinSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022chinwidth\u0022:\r\n\t\t\t\tdata.ChinWidth = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022jaw\u0022:\r\n\t\t\t\tdata.JawWidth = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022nostrils\u0022:\r\n\t\t\t\tdata.NostrilWidth = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022forehead\u0022:\r\n\t\t\t\tdata.ForeheadWidth = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022cheekbones\u0022:\r\n\t\t\t\tdata.CheekboneWidth = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022ears\u0022:\r\n\t\t\t\tdata.EarSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022eyes\u0022:\r\n\t\t\t\tdata.EyeSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022cheeks\u0022:\r\n\t\t\t\tdata.CheekFullness = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022nostrilfullness\u0022:\r\n\t\t\t\tdata.NostrilFullness = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022noseheight\u0022:\r\n\t\t\t\tdata.NoseHeight = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022chinheight\u0022:\r\n\t\t\t\tdata.ChinHeight = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022submental\u0022:\r\n\t\t\t\tdata.SubmentalFullness = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022brow\u0022:\r\n\t\t\t\tdata.BrowProminence = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022bodyfeature\u0022:\r\n\t\t\t\tdata.BodyFeatureSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022butt\u0022:\r\n\t\t\t\tdata.ButtSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022feet\u0022:\r\n\t\t\t\tdata.FootSize = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022eartaper\u0022:\r\n\t\t\t\tdata.EarTaper = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022philtrum\u0022:\r\n\t\t\t\tdata.PhiltrumWidth = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022columella\u0022:\r\n\t\t\t\tdata.ColumellaWidth = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022mouthwidth\u0022:\r\n\t\t\t\tdata.MouthWidth = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022mouthprojection\u0022:\r\n\t\t\t\tdata.MouthProjection = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022upperlip\u0022:\r\n\t\t\t\tdata.UpperLipFullness = value;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \u0022lowerlip\u0022:\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/// \u003Csummary\u003ECurated installed, non-inventory native assets. IDs and paths are stable; no cloud discovery or external download is needed.\u003C/summary\u003E\r\npublic static class AppearanceCatalog\r\n{\r\n\tstatic readonly Dictionary\u003Cstring, Task\u003E resourceLoads = new(StringComparer.OrdinalIgnoreCase);\r\n\tpublic static bool Loading =\u003E resourceLoads.Values.Any(x =\u003E x is not null \u0026\u0026 !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 = \u0022models/citizen_clothes/\u0022;\r\n\tstatic AppearanceOption Skin(string number, string label, string body) =\u003E new(\u0022human\u0022 \u002B number, label, Root \u002B \u0022body/human\u0022 \u002B number \u002B \u0022.clothing\u0022, body, Root \u002B \u0022body/human\u0022 \u002B number \u002B \u0022.clothing.png\u0022);\r\n\tstatic AppearanceOption Style(string id, string label, string relative, string body = \u0022\u0022) =\u003E new(id, label, Root \u002B \u0022hair/\u0022 \u002B relative \u002B \u0022.clothing\u0022, body, Root \u002B \u0022hair/\u0022 \u002B relative \u002B \u0022.clothing.png\u0022);\r\n\tpublic static IReadOnlyList\u003CAppearanceOption\u003E Skins { get; } = Array.AsReadOnly(new[] { Skin(\u002201\u0022, \u0022Face 1 \u00B7 dark \u00B7 male\u0022, \u0022male\u0022), Skin(\u002202\u0022, \u0022Face 2 \u00B7 dark \u00B7 female\u0022, \u0022female\u0022), Skin(\u002203\u0022, \u0022Face 3 \u00B7 light \u00B7 male\u0022, \u0022male\u0022), Skin(\u002204\u0022, \u0022Face 4 \u00B7 light \u00B7 female\u0022, \u0022female\u0022), Skin(\u002205\u0022, \u0022Face 5 \u00B7 light \u00B7 male\u0022, \u0022male\u0022), Skin(\u002206\u0022, \u0022Face 6 \u00B7 dark \u00B7 male\u0022, \u0022male\u0022), Skin(\u002207\u0022, \u0022Face 7 \u00B7 light \u00B7 male\u0022, \u0022male\u0022) });\r\n\tpublic static IReadOnlyList\u003CAppearanceOption\u003E Hair { get; } = Array.AsReadOnly(new[] { new AppearanceOption(\u0022none\u0022, \u0022No hair\u0022, \u0022\u0022), Style(\u0022scruffy\u0022, \u0022Short scruffy\u0022, \u0022hair_shortscruffy/hair_shortscruffy_brown\u0022), Style(\u0022short_braids\u0022, \u0022Short braids\u0022, \u0022hair_short_braids/hair_short_braids\u0022), Style(\u0022short_dreads\u0022, \u0022Short dreadlocks\u0022, \u0022hair_short_dreadlocks/short_dreadlocks\u0022), Style(\u0022bob\u0022, \u0022Bob cut\u0022, \u0022hair_bobcut/hair_bobcut\u0022), Style(\u0022long_bob\u0022, \u0022Long bob\u0022, \u0022hair_longbob/hair_longbob\u0022), Style(\u0022messy_bun\u0022, \u0022Messy bun\u0022, \u0022hair_messy_bun/hair_messy_bun\u0022), Style(\u0022ponytail\u0022, \u0022Tight ponytail\u0022, \u0022hair_tight_ponytail/hair_tight_ponytail_brown\u0022), Style(\u0022long_curly\u0022, \u0022Long curly\u0022, \u0022hair_longcurly/hair_longcurly_brown\u0022), Style(\u0022long_bangs\u0022, \u0022Long bangs\u0022, \u0022hair_long_bangs/hair_long_bangs\u0022), Style(\u0022long_dreads\u0022, \u0022Long dreadlocks\u0022, \u0022hair_long_dreadlocks/hair_long_dreadlocks\u0022), Style(\u0022tied_braids\u0022, \u0022Tied braids\u0022, \u0022hair_tied_braids/hair_tied_braids\u0022), Style(\u0022tied_dreads\u0022, \u0022Tied dreadlocks\u0022, \u0022hair_tied_dreads/hair_tied_dreads\u0022), Style(\u0022slicked\u0022, \u0022Slicked back\u0022, \u0022hair_slickedback/hair_slickedback\u0022), Style(\u0022balding\u0022, \u0022Balding\u0022, \u0022hair_balding/hair_baldingbrown\u0022), Style(\u0022thinning\u0022, \u0022Thinning short\u0022, \u0022hair_thinning_short/thinning_short\u0022), Style(\u0022pigtails\u0022, \u0022Pigtails\u0022, \u0022hair_pigtails/hair_pigtails\u0022), Style(\u0022long_brown\u0022, \u0022Long hair\u0022, \u0022hair_longbrown/Models/hair_longbrown\u0022), Style(\u0022loose\u0022, \u0022Loose hair\u0022, \u0022hair_looseblonde/hair.loose.grey\u0022), Style(\u0022slicked_balding\u0022, \u0022Slicked back balding\u0022, \u0022hair_slicked_back_balding/hair_slicked_back_balding\u0022) });\r\n\tpublic static IReadOnlyList\u003CAppearanceOption\u003E FacialHair { get; } = Array.AsReadOnly(new[] { new AppearanceOption(\u0022none\u0022, \u0022No facial hair\u0022, \u0022\u0022), Style(\u0022patchy\u0022, \u0022Patchy beard\u0022, \u0022patchy_beard/patchy_beard\u0022, \u0022male\u0022), Style(\u0022medium\u0022, \u0022Medium beard\u0022, \u0022medium_beard/medium_beard\u0022, \u0022male\u0022), Style(\u0022goatee\u0022, \u0022Goatee\u0022, \u0022goatee/goatee\u0022, \u0022male\u0022), Style(\u0022moustache\u0022, \u0022Moustache\u0022, \u0022moustache/moustache\u0022, \u0022male\u0022), Style(\u0022handlebar\u0022, \u0022Handlebar moustache\u0022, \u0022handlebar_moustache/handlebar_moustache\u0022, \u0022male\u0022), Style(\u0022chops\u0022, \u0022Sideburns\u0022, \u0022mutton_chops/mutton_chops\u0022, \u0022male\u0022), Style(\u0022braided\u0022, \u0022Braided beard\u0022, \u0022braided_long_beard/braided_long_beard\u0022, \u0022male\u0022), Style(\u0022decorated\u0022, \u0022Decorated beard\u0022, \u0022decorated_beard/decorated_beard\u0022, \u0022male\u0022) });\r\n\tpublic static IReadOnlyList\u003CAppearanceColorOption\u003E Colors { get; } = Array.AsReadOnly(new[] { new AppearanceColorOption(\u0022black\u0022, \u0022Black\u0022, new Color(0.045f, 0.035f, 0.028f)), new AppearanceColorOption(\u0022brown\u0022, \u0022Brown\u0022, new Color(0.22f, 0.13f, 0.08f)), new AppearanceColorOption(\u0022auburn\u0022, \u0022Auburn\u0022, new Color(0.48f, 0.17f, 0.07f)), new AppearanceColorOption(\u0022blonde\u0022, \u0022Blonde\u0022, new Color(0.74f, 0.57f, 0.31f)), new AppearanceColorOption(\u0022silver\u0022, \u0022Silver\u0022, new Color(0.66f, 0.67f, 0.7f)), new AppearanceColorOption(\u0022white\u0022, \u0022White\u0022, 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) =\u003E data?.BaseShape is not null ? data.BaseShape.MaleBody \u003C .5f : Skins.FirstOrDefault(x =\u003E string.Equals(x.ResourcePath, data?.SkinPath, StringComparison.OrdinalIgnoreCase))?.BodyType == \u0022female\u0022;\r\n\tstatic IReadOnlyList\u003CAppearanceColorOption\u003E humanSkinColors;\r\n\tpublic static IReadOnlyList\u003CAppearanceColorOption\u003E 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\u003CAppearanceColorOption\u003E CreateHumanSkinColors() =\u003E Array.AsReadOnly(new[] { new AppearanceColorOption(\u0022native\u0022, \u0022Native\u0022, Color.White), new AppearanceColorOption(\u0022fair\u0022, \u0022Fair\u0022, Color.White, true), new AppearanceColorOption(\u0022rose\u0022, \u0022Rose\u0022, new Color(1, 0.96f, 0.96f), true), new AppearanceColorOption(\u0022golden\u0022, \u0022Golden\u0022, new Color(1, 0.96f, 0.88f), true), new AppearanceColorOption(\u0022olive\u0022, \u0022Olive\u0022, new Color(0.91f, 0.88f, 0.77f), true), new AppearanceColorOption(\u0022tan\u0022, \u0022Tan\u0022, new Color(0.72f, 0.6f, 0.5f), true), new AppearanceColorOption(\u0022umber\u0022, \u0022Umber\u0022, new Color(0.42f, 0.3f, 0.23f), true) });\r\n\tstatic IReadOnlyList\u003CAppearanceColorOption\u003E fantasySkinColors;\r\n\tpublic static IReadOnlyList\u003CAppearanceColorOption\u003E FantasySkinColors\r\n\t{\r\n\t\tget\r\n\t\t{\r\n\t\t\tif (fantasySkinColors?.Count != 6 || fantasySkinColors.Any(x =\u003E !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\u003CAppearanceColorOption\u003E CreateFantasySkinColors() =\u003E Array.AsReadOnly(new[] { new AppearanceColorOption(\u0022blue\u0022, \u0022Blue\u0022, new Color(0.18f, 0.4f, 1), true), new AppearanceColorOption(\u0022purple\u0022, \u0022Purple\u0022, new Color(0.6f, 0.25f, 1), true), new AppearanceColorOption(\u0022green\u0022, \u0022Green\u0022, new Color(0.3f, 0.85f, 0.4f), true), new AppearanceColorOption(\u0022red\u0022, \u0022Crimson\u0022, new Color(1, 0.25f, 0.22f), true), new AppearanceColorOption(\u0022teal\u0022, \u0022Teal\u0022, new Color(0.18f, 0.85f, 0.9f), true), new AppearanceColorOption(\u0022grey\u0022, \u0022Slate\u0022, 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\u003CAppearanceSlider\u003E 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\u003CAppearanceSlider\u003E contourControls;\r\n\tstatic IReadOnlyList\u003CAppearanceSlider\u003E CreateContourControls() =\u003E Array.AsReadOnly(new[] { new AppearanceSlider(\u0022neck\u0022, \u0022Neck size\u0022, \u0022Body\u0022), new AppearanceSlider(\u0022trapezius\u0022, \u0022Trapezius\u0022, \u0022Body\u0022), new AppearanceSlider(\u0022collarbone\u0022, \u0022Collarbone height\u0022, \u0022Body\u0022), new AppearanceSlider(\u0022belly\u0022, \u0022Belly size\u0022, \u0022Body\u0022), new AppearanceSlider(\u0022waist\u0022, \u0022Waist size\u0022, \u0022Body\u0022), new AppearanceSlider(\u0022chest\u0022, \u0022Chest size\u0022, \u0022Body\u0022), new AppearanceSlider(\u0022fitness\u0022, \u0022Fitness\u0022, \u0022Body\u0022), new AppearanceSlider(\u0022bodyfeature\u0022, \u0022Body contour\u0022, \u0022Body\u0022), new AppearanceSlider(\u0022butt\u0022, \u0022Butt size\u0022, \u0022Body\u0022), new AppearanceSlider(\u0022feet\u0022, \u0022Foot size\u0022, \u0022Body\u0022), new AppearanceSlider(\u0022head\u0022, \u0022Head shape\u0022, \u0022Face\u0022), new AppearanceSlider(\u0022forehead\u0022, \u0022Forehead width\u0022, \u0022Face\u0022), new AppearanceSlider(\u0022cheekbones\u0022, \u0022Cheekbone width\u0022, \u0022Face\u0022), new AppearanceSlider(\u0022cheeks\u0022, \u0022Cheek fullness\u0022, \u0022Face\u0022), new AppearanceSlider(\u0022jaw\u0022, \u0022Jaw width\u0022, \u0022Jaw \u0026 chin\u0022), new AppearanceSlider(\u0022chin\u0022, \u0022Chin projection\u0022, \u0022Jaw \u0026 chin\u0022), new AppearanceSlider(\u0022chinwidth\u0022, \u0022Chin width\u0022, \u0022Jaw \u0026 chin\u0022), new AppearanceSlider(\u0022chinheight\u0022, \u0022Chin height\u0022, \u0022Jaw \u0026 chin\u0022), new AppearanceSlider(\u0022submental\u0022, \u0022Under-chin fullness\u0022, \u0022Jaw \u0026 chin\u0022), new AppearanceSlider(\u0022nose\u0022, \u0022Nose projection\u0022, \u0022Nose\u0022), new AppearanceSlider(\u0022nosewidth\u0022, \u0022Nose width\u0022, \u0022Nose\u0022), new AppearanceSlider(\u0022nostrils\u0022, \u0022Nostril width\u0022, \u0022Nose\u0022), new AppearanceSlider(\u0022nostrilfullness\u0022, \u0022Nostril-wing fullness\u0022, \u0022Nose\u0022), new AppearanceSlider(\u0022noseheight\u0022, \u0022Nose tip height\u0022, \u0022Nose\u0022), new AppearanceSlider(\u0022columella\u0022, \u0022Columella width\u0022, \u0022Nose\u0022), new AppearanceSlider(\u0022philtrum\u0022, \u0022Philtrum width\u0022, \u0022Mouth\u0022), new AppearanceSlider(\u0022mouthwidth\u0022, \u0022Mouth width\u0022, \u0022Mouth\u0022), new AppearanceSlider(\u0022mouthprojection\u0022, \u0022Mouth projection\u0022, \u0022Mouth\u0022), new AppearanceSlider(\u0022upperlip\u0022, \u0022Upper lip fullness\u0022, \u0022Mouth\u0022), new AppearanceSlider(\u0022lowerlip\u0022, \u0022Lower lip fullness\u0022, \u0022Mouth\u0022), new AppearanceSlider(\u0022eyes\u0022, \u0022Eye size\u0022, \u0022Eyes \u0026 ears\u0022), new AppearanceSlider(\u0022ears\u0022, \u0022Ear size\u0022, \u0022Eyes \u0026 ears\u0022), new AppearanceSlider(\u0022brow\u0022, \u0022Brow prominence\u0022, \u0022Eyes \u0026 ears\u0022), new AppearanceSlider(\u0022eartaper\u0022, \u0022Ear taper\u0022, \u0022Eyes \u0026 ears\u0022) });\r\n\tstatic CharacterAppearanceData Preset(string skin, string hair, string facial, string color) =\u003E new()\r\n\t{\r\n\t\tSkinPath = Skins.Single(x =\u003E x.Id == skin).ResourcePath,\r\n\t\tHairPath = Hair.Single(x =\u003E x.Id == hair).ResourcePath,\r\n\t\tFacialHairPath = FacialHair.Single(x =\u003E x.Id == facial).ResourcePath,\r\n\t\tHairColor = Colors.Single(x =\u003E x.Id == color).Color\r\n\t};\r\n\tstatic IReadOnlyList\u003CAppearancePreset\u003E presets;\r\n\tpublic static IReadOnlyList\u003CAppearancePreset\u003E 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(\u0022traveller\u0022, \u0022Traveller\u0022, Preset(\u0022human03\u0022, \u0022scruffy\u0022, \u0022none\u0022, \u0022brown\u0022)),\r\n\t\t\t\t\tnew AppearancePreset(\u0022scout\u0022, \u0022Scout\u0022, Preset(\u0022human02\u0022, \u0022short_braids\u0022, \u0022none\u0022, \u0022black\u0022)),\r\n\t\t\t\t\tnew AppearancePreset(\u0022scholar\u0022, \u0022Scholar\u0022, Preset(\u0022human04\u0022, \u0022messy_bun\u0022, \u0022none\u0022, \u0022auburn\u0022)),\r\n\t\t\t\t\tnew AppearancePreset(\u0022veteran\u0022, \u0022Veteran\u0022, Preset(\u0022human07\u0022, \u0022balding\u0022, \u0022medium\u0022, \u0022silver\u0022)),\r\n\t\t\t\t\tnew AppearancePreset(\u0022wanderer\u0022, \u0022Wanderer\u0022, Preset(\u0022human06\u0022, \u0022short_dreads\u0022, \u0022none\u0022, \u0022black\u0022)),\r\n\t\t\t\t\tnew AppearancePreset(\u0022artisan\u0022, \u0022Artisan\u0022, Preset(\u0022human05\u0022, \u0022slicked\u0022, \u0022goatee\u0022, \u0022brown\u0022)),\r\n\t\t\t\t\tnew AppearancePreset(\u0022explorer\u0022, \u0022Explorer\u0022, Preset(\u0022human01\u0022, \u0022tied_braids\u0022, \u0022none\u0022, \u0022black\u0022))\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() =\u003E ApplyPreset(\u0022traveller\u0022);\r\n\tpublic static CharacterAppearanceData ApplyPreset(string id) =\u003E Presets.FirstOrDefault(x =\u003E 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 ? \u0022male\u0022 : \u0022female\u0022;\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 =\u003E blendHeads ? (body == \u0022male\u0022 || x.Id is not (\u0022human05\u0022 or \u0022human07\u0022)) : 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 =\u003E x.BodyType == \u0022\u0022 || x.BodyType == body).ToArray();\r\n\t\tvar tones = HumanSkinColors.Where(x =\u003E body == \u0022female\u0022 \u0026\u0026 skin.Id == \u0022human06\u0022 ? x.Id is \u0022native\u0022 or \u0022tan\u0022 or \u0022umber\u0022 : 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 =\u003E x.Id != \u0022belly\u0022))\r\n\t\t\tslider.Set(data, 0.3f \u002B (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() \u003C 0.10 ? 0.1f \u002B (float)rng.NextDouble() * 0.3f : 0;\r\n\t\tif (body == \u0022female\u0022 \u0026\u0026 rng.NextDouble() \u003C 0.60)\r\n\t\t{\r\n\t\t\tAddMakeup(\u0022Lipstick\u0022, 0.70, 0.35f, 0.85f, 4);\r\n\t\t\tAddMakeup(\u0022Eyeshadow\u0022, 0.45, 0.20f, 0.65f, 3);\r\n\t\t\tAddMakeup(\u0022Blush\u0022, 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\u0027s native body.\r\n\t\t\tbool female = body == \u0022female\u0022;\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\u0027s 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 \u002B (float)rng.NextDouble() * .20f,\r\n\t\t\t\t\tHeadWeights = RandomHeadWeights(rng, female, skin.Id, region is \u0022jaw\u0022 or \u0022throat\u0022)\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[\u0022throat\u0022] = data.BaseShape.Regions[\u0022jaw\u0022].DeepCopy();\r\n\t\t}\r\n\r\n\t\tdata.ClothingPaths = WardrobeCatalog.Randomize(rng, body == \u0022female\u0022);\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() \u003E= 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 \u002B (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 \u002B (float)rng.NextDouble() * .05f : .85f \u002B (float)rng.NextDouble() * .15f;\r\n\t\tfloat femaleShare = female ? ownShare : skin == \u0022human04\u0022 ? 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 \u003C weights.Length; i\u002B\u002B)\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 \u002B (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 \u002B (float)rng.NextDouble();\r\n\t\t\t\t\ttotal \u002B= 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\u003CAppearanceOption\u003E RandomHairFor(string bodyType) =\u003E Hair.Where(x =\u003E bodyType == \u0022female\u0022 ? x.Id is not (\u0022balding\u0022 or \u0022thinning\u0022 or \u0022slicked_balding\u0022) : x.Id is \u0022none\u0022 or \u0022scruffy\u0022 or \u0022short_braids\u0022 or \u0022short_dreads\u0022 or \u0022long_dreads\u0022 or \u0022tied_braids\u0022 or \u0022tied_dreads\u0022 or \u0022slicked\u0022 or \u0022balding\u0022 or \u0022thinning\u0022 or \u0022ponytail\u0022 or \u0022long_curly\u0022 or \u0022long_brown\u0022 or \u0022slicked_balding\u0022).ToArray();\r\n\t/// \u003Csummary\u003ELoads only curated native resources, either the whole creator catalog or one selected/saved appearance.\u003C/summary\u003E\r\n\tpublic static async Task EnsureLoadedAsync(CharacterAppearanceData data = null)\r\n\t{\r\n\t\tif (data is not null \u0026\u0026 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 =\u003E 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 =\u003E !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 =\u003E 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(\u0022The unified human model is unavailable.\u0022);\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) \u0026\u0026 pending is not null \u0026\u0026 !pending.IsFaulted \u0026\u0026 !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 \u003C 3; attempt\u002B\u002B)\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 \u0026\u0026 !clothing.IsError)\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tif (attempt \u003C 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(\u0022A native appearance resource is unavailable: \u0022 \u002B 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 =\u003E !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(\u0022A native appearance model is unavailable: \u0022 \u002B modelPath);\r\n\t\t\t}\r\n\r\n\t\t\tRevision\u002B\u002B;\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/// \u003Csummary\u003ENative 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.\u003C/summary\u003E\r\npublic static class HumanMarkingModels\r\n{\r\n\tconst string ArmMaterials = \u0022materials/makeup/arms/\u0022;\r\n\tpublic static bool NeedsArmRegions(CharacterMakeupData makeup) =\u003E makeup?.Markings?.Any(x =\u003E x.Style != \u0022none\u0022 \u0026\u0026 x.Region is \u0022left-arm\u0022 or \u0022right-arm\u0022 or \u0022chest\u0022 or \u0022back\u0022) == true;\r\n\tpublic static string ModelPath(string native, CharacterMakeupData makeup) =\u003E NeedsArmRegions(makeup) ? native switch\r\n\t{\r\n\t\t\u0022models/citizen_human/citizen_human_male.vmdl\u0022 =\u003E \u0022models/makeup/human_male_arm_regions.vmdl\u0022,\r\n\t\t\u0022models/citizen_human/citizen_human_female.vmdl\u0022 =\u003E \u0022models/makeup/human_female_arm_regions.vmdl\u0022,\r\n\t\t_ =\u003E 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\u003CClothing\u003E(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(\u0022The independently marked human arms are unavailable.\u0022);\r\n\t}\r\n\r\n\tpublic static int ArmSide(Material material) =\u003E !material.Name.StartsWith(ArmMaterials, StringComparison.OrdinalIgnoreCase) ? 0 : material.Name.Contains(\u0022_left_\u0022, 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(\u0022/female_\u0022, StringComparison.OrdinalIgnoreCase);\r\n\t\tvar light = slot.Name.EndsWith(\u0022_light.vmat\u0022, StringComparison.OrdinalIgnoreCase);\r\n\t\tvar name = female ? (light ? \u0022female_body_mode_light\u0022 : \u0022female_body_mode_dark\u0022) : (light ? \u0022body_male_light\u0022 : \u0022body_mode_dark\u0022);\r\n\t\treturn Material.Load($\u0022models/citizen_human/bodies/{(female ? \u0022female\u0022 : \u0022male\u0022)}/{name}.vmat\u0022);\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\u003Croot\u003E\r\n@if(IsOpen)\r\n{\r\n \u003Cdiv class=\u0022menu-shade\u0022\u003E\u003Cdiv class=\u0022menu-card\u0022\u003E\r\n @if(IsConfirming)\r\n {\r\n  \u003Ch2\u003EDiscard unsaved edits?\u003C/h2\u003E\u003Cp\u003EYour saved characters will stay as they are.\u003C/p\u003E\r\n  \u003Cbutton onclick=@CancelNavigation\u003EKeep editing\u003C/button\u003E\r\n  \u003Cbutton class=\u0022primary\u0022 onclick=@ConfirmNavigation\u003EDiscard edits and continue\u003C/button\u003E\r\n }\r\n else\r\n {\r\n  \u003Cdiv class=\u0022menu-title\u0022\u003ECharacter Editor\u003C/div\u003E\r\n  \u003Cdiv class=\u0022menu-tabs\u0022\u003E\u003Cbutton class=@(!Experimental?\u0022chosen\u0022:\u0022\u0022) onclick=@(()=\u003EExperimental=false)\u003EMenu\u003C/button\u003E@if(EnableExperiments){\u003Cbutton class=@(Experimental?\u0022chosen\u0022:\u0022\u0022) onclick=@(()=\u003EExperimental=true)\u003EExperimental\u003C/button\u003E}\u003C/div\u003E\r\n  @if(Experimental \u0026\u0026 EnableExperiments)\r\n  {\r\n   \u003Ch2\u003EExperiments\u003C/h2\u003E\u003Cp\u003EDevelopment tools and research. These do not create saved characters.\u003C/p\u003E\r\n   \u003Cbutton onclick=@(()=\u003ERequestNavigation(OpenLab))\u003EFace lab\u003C/button\u003E\r\n  }\r\n  else\r\n  {\r\n   \u003Cbutton class=\u0022primary\u0022 onclick=@Close\u003EResume\u003C/button\u003E\r\n   \u003Cbutton onclick=@(()=\u003ERequestNavigation(MainMenu))\u003EMain menu\u003C/button\u003E\r\n   \u003Cbutton onclick=@(()=\u003ERequestNavigation(()=\u003EGame.Close()))\u003EQuit to s\u0026amp;box\u003C/button\u003E\r\n  }\r\n  \u003Cdiv class=\u0022menu-hint\u0022\u003EEscape to return\u003C/div\u003E\r\n }\r\n \u003C/div\u003E\u003C/div\u003E\r\n}\r\n\u003C/root\u003E\r\n\r\n@code {\r\n\tpublic Action MainMenu { get; set; }\r\n\tpublic Action OpenLab { get; set; }\r\n\tpublic Func\u003Cbool\u003E HasUnsavedEdits { get; set; }\r\n\tpublic bool EnableExperiments { get; set; }\r\n\tpublic bool IsOpen { get; private set; }\r\n\tpublic bool IsConfirming =\u003E 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() =\u003E 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 \u003E 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 \u003E 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/// \u003Csummary\u003EVersion 2 identity shape, independent of skin. Weights are in human01..human07 order.\u003C/summary\u003E\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\u003CPhotoFace\u003E PhotoFaces { get; set; } = new();\r\n\tpublic Dictionary\u003Cstring, FaceRegionBlend\u003E Regions { get; set; } = new();\r\n\r\n\t[System.Text.Json.Serialization.JsonIgnore]\r\n\tpublic float TotalInfluence =\u003E HeadWeights.Sum() \u002B (PhotoFaces?.Sum(x =\u003E x.Influence) ?? 0);\r\n\r\n\tpublic float NativeShare(int i) =\u003E TotalInfluence \u003E 0 ? HeadWeights[i] / TotalInfluence : (i == 2 ? 1 : 0);\r\n\tpublic float PhotoShare(PhotoFace face) =\u003E TotalInfluence \u003E 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 =\u003E HeadWeights?.Length == 7 \u0026\u0026 HeadWeights.All(Unit) \u0026\u0026 PhotoFaces is not null \u0026\u0026 PhotoFaces.Count \u003C= 4 \u0026\u0026 PhotoFaces.All(x =\u003E x?.Valid == true) \u0026\u0026 PhotoFaces.Select(x =\u003E x.Id).Distinct().Count() == PhotoFaces.Count \u0026\u0026 Regions is not null \u0026\u0026 Regions.Count \u003C= FaceRegions.Ids.Length \u0026\u0026 Regions.All(x =\u003E FaceRegions.Ids.Contains(x.Key) \u0026\u0026 x.Value?.Valid(PhotoFaces) == true) \u0026\u0026 Unit(MaleBody) \u0026\u0026 Unit(BreastSize) \u0026\u0026 Unit(BulgeSize) \u0026\u0026 ClothingVariant is null or \u0022male\u0022 or \u0022female\u0022;\r\n\r\n\tstatic bool Unit(float x) =\u003E float.IsFinite(x) \u0026\u0026 x \u003E= 0 \u0026\u0026 x \u003C= 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 \u003E 0 ? HeadWeights.Select((x, i) =\u003E (x \u002B PhotoFaces.Sum(f =\u003E 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() =\u003E new()\r\n\t{\r\n\t\tHeadWeights = HeadWeights?.ToArray(),\r\n\t\tPhotoFaces = PhotoFaces?.Select(x =\u003E x?.DeepCopy()).ToList(),\r\n\t\tRegions = Regions?.ToDictionary(x =\u003E x.Key, x =\u003E 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 = \u0022models/characterkit/unified_human.vmdl\u0022;\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 =\u003E 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) =\u003E a.BaseShape?.MaleBody ?? (AppearanceCatalog.IsFemale(a) ? 0 : 1);\r\n\tpublic static string PresentationSkin(CharacterAppearanceData a) =\u003E a.BaseShape is null ? a.SkinPath : (MaleAmount(a) \u003E= .5f ? \u0022models/citizen_clothes/body/human03.clothing\u0022 : \u0022models/citizen_clothes/body/human04.clothing\u0022);\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 \u003C 7; i\u002B\u002B)\r\n\t\t\tif (i != 2)\r\n\t\t\t\tbody.Morphs.Set($\u0022ce_human{i \u002B 1:00}\u0022, weights[i]);\r\n\t\tFaceRegions.Apply(body, a.BaseShape, weights);\r\n\t\tbody.Morphs.Set(\u0022ce_body_female\u0022, 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\u0022human01\u0022 =\u003E \u0022malehead01\u0022,\r\n\t\t\t\u0022human02\u0022 =\u003E \u0022femalehead01\u0022,\r\n\t\t\t\u0022human03\u0022 =\u003E \u0022malehead02\u0022,\r\n\t\t\t\u0022human04\u0022 =\u003E \u0022femalehead02\u0022,\r\n\t\t\t\u0022human05\u0022 =\u003E \u0022malehead03\u0022,\r\n\t\t\t\u0022human06\u0022 =\u003E \u0022malehead04\u0022,\r\n\t\t\t_ =\u003E \u0022malehead05\u0022\r\n\t\t};\r\n\t\treturn $\u0022models/citizen_human/heads/citizen_human_{suffix}/citizen_human_{suffix}.vmat\u0022;\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(\u0022/heads/\u0022) \u0026\u0026 original.ShaderName == \u0022shaders/skin.shader\u0022 \u0026\u0026 !original.Name.EndsWith(\u0022/mouth.vmat\u0022))\r\n\t\t\treturn Material.Load(HeadMaterial(skin));\r\n\t\tif (original.Name.Contains(\u0022/bodies/\u0022) \u0026\u0026 original.ShaderName == \u0022shaders/skin.shader\u0022)\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 \u0026\u0026 (id is \u0022human02\u0022 or \u0022human04\u0022), dark = id is \u0022human01\u0022 or \u0022human02\u0022 or \u0022human06\u0022;\r\n\t\t\treturn Material.Load($\u0022models/citizen_human/bodies/{(female ? \u0022female\u0022 : \u0022male\u0022)}/{(female ? (dark ? \u0022female_body_mode_dark\u0022 : \u0022female_body_mode_light\u0022) : (dark ? \u0022body_mode_dark\u0022 : \u0022body_male_light\u0022))}.vmat\u0022);\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(\u0022/bodies/\u0022))\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\u0027 authored complexion.\r\n\t\treturn System.IO.Path.GetFileNameWithoutExtension(skin) switch\r\n\t\t{\r\n\t\t\t\u0022human02\u0022 =\u003E new Color(1.0192f, .9194f, .8924f),\r\n\t\t\t\u0022human04\u0022 =\u003E new Color(1.0856f, 1.0137f, .8969f),\r\n\t\t\t_ =\u003E 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/// \u003Csummary\u003EOne independently editable ink layer. Placement is normalized within a curated surface region.\u003C/summary\u003E\r\npublic sealed class CharacterMarkingData\r\n{\r\n\tpublic string Style { get; set; } = \u0022crescent\u0022;\r\n\tpublic string Region { get; set; } = \u0022face\u0022;\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 =\u003E MarkingCatalog.ValidPlacement(Style, Region) \u0026\u0026 Ink?.Valid == true \u0026\u0026 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 =\u003E float.IsFinite(x) \u0026\u0026 x \u003E= 0 \u0026\u0026 x \u003C= 1) \u0026\u0026 (Style != \u0022barbed-band\u0022 || (Horizontal == .5f \u0026\u0026 Rotation == .5f));\r\n\r\n\tpublic CharacterMarkingData DeepCopy() =\u003E 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( \u0022AddonTitle\u0022, \u0022Character Editor\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022AddonIdent\u0022, \u0022character_editor\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022OrgIdent\u0022, \u0022vendetta\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022Ident\u0022, \u0022vendetta.character_editor\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022EngineVersion\u0022, \u002229\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022EngineMinorVersion\u0022, \u00221\u0022 )]\r\n\r\n[assembly: System.Runtime.Versioning.TargetFramework( \u0022.NETCoreApp,Version=v9.0\u0022, FrameworkDisplayName = \u0022.NET 9.0\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022CompileTime\u0022, \u00222026-10-05T23:16:04.3069273Z\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyVersion(\u00220.0.382.0\u0022)]\r\n[assembly: global::System.Reflection.AssemblyFileVersion(\u00220.0.382.0\u0022)]"},{"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/// \u003Csummary\u003EFits loaded human clothes against shared body surfaces, without garment authoring files.\u003C/summary\u003E\r\npublic static class AutomaticWardrobeFit\r\n{\r\n\tconst string ReferencePath = \u0022models/characterkit/wardrobe-reference.bin\u0022;\r\n\tpublic const string Morph = \u0022ce_body_female\u0022;\r\n\t// Cache by resource identity and authored cut, never by the current slider value.\r\n\tstatic readonly Dictionary\u003C(Model, bool, bool), Model\u003E fittedModels = new();\r\n\tstatic readonly Dictionary\u003C(Model, bool, bool), string\u003E 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 =\u003E 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/// \u003Csummary\u003EKeep the lower neck when clothing replaces the head bodygroup.\u003C/summary\u003E\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) =\u003E model?.Name.StartsWith(\u0022automatic-human-fit:\u0022, 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 \u003E 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 = \u0022Cannot automatically fit \u0022 \u002B source.Name \u002B \u0022: \u0022 \u002B 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(\u0022Unsupported human fitting reference.\u0022);\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\u003CVector3\u003E();\r\n\t\tvar triangles = new List\u003Cint\u003E();\r\n\t\tfor (int mi = 0; mi \u003C buffers.Count; mi\u002B\u002B)\r\n\t\t{\r\n\t\t\tvar info = source.MeshInfo.Meshes[mi];\r\n\t\t\tif (!info.Name.StartsWith(\u0022head\u0022, 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 =\u003E v.Position));\r\n\t\t\t\ttriangles.AddRange(indices.Skip(call.StartIndex).Take(call.Indices).Select(i =\u003E i \u002B call.BaseVertex \u002B offset));\r\n\t\t\t\tcallIndex\u002B\u002B;\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\u003Cstring\u003E(), Array.Empty\u003C(int, float)[]\u003E());\r\n\t}\r\n\r\n\tstatic Vector3 V(BinaryReader r) =\u003E 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 \u003C names.Length; i\u002B\u002B)\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 \u003C count; i\u002B\u002B)\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 \u003C weights[i].Length; j\u002B\u002B)\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 \u003C triangles.Length; i\u002B\u002B)\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\u003CBuffers\u003E 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 \u002B r.ReadInt32();\r\n\t\tvar blocks = r.ReadInt32();\r\n\t\tvar result = new List\u003CBuffers\u003E();\r\n\t\tfor (int i = 0; i \u003C blocks; i\u002B\u002B)\r\n\t\t{\r\n\t\t\tr.BaseStream.Position = table \u002B 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 == \u0022MDAT\u0022)\r\n\t\t\t{\r\n\t\t\t\tr.BaseStream.Position = anchor \u002B 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 != \u0022MBUF\u0022)\r\n\t\t\t\tcontinue;\r\n\t\t\tvar start = anchor \u002B 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 =\u003E ReadBuffer(r, start \u002B vo \u002B j * 24)).ToArray(), Enumerable.Range(0, ic).Select(j =\u003E ReadBuffer(r, start \u002B 8 \u002B io \u002B 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($\u0022{source.Name}: missing serialized mesh buffers.\u0022);\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() \u0026 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 \u003C 0 || count \u003E 2000000 || stride \u003C 1 || stride \u003E 1024 || bytes != count * stride)\r\n\t\t\tthrow new InvalidOperationException(\u0022Unexpected serialized human clothing buffer.\u0022);\r\n\t\tvar attributes = new Attribute[ac];\r\n\t\tfor (int i = 0; i \u003C ac; i\u002B\u002B)\r\n\t\t{\r\n\t\t\tr.BaseStream.Position = ap \u002B ao \u002B i * 56;\r\n\t\t\tvar name = Encoding.ASCII.GetString(r.ReadBytes(32)).TrimEnd(\u0027\\0\u0027);\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 \u002B 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) =\u003E BitConverter.ToSingle(b, p);\r\n\tstatic ushort U16(byte[] b, int p) =\u003E BitConverter.ToUInt16(b, p);\r\n\tstatic uint U32(byte[] b, int p) =\u003E BitConverter.ToUInt32(b, p);\r\n\tstatic Color32 Bytes(byte[] b, int p) =\u003E new(b[p], b[p \u002B 1], b[p \u002B 2], b[p \u002B 3]);\r\n\tstatic Vector4 Values(byte[] b, int p, int format)\r\n\t{\r\n\t\tfloat HalfAt(int i) =\u003E (float)BitConverter.UInt16BitsToHalf(U16(b, p \u002B i * 2));\r\n\t\treturn format switch\r\n\t\t{\r\n\t\t\t2 =\u003E new(F(b, p), F(b, p \u002B 4), F(b, p \u002B 8), F(b, p \u002B 12)),\r\n\t\t\t6 =\u003E new(F(b, p), F(b, p \u002B 4), F(b, p \u002B 8), 0),\r\n\t\t\t16 =\u003E new(F(b, p), F(b, p \u002B 4), 0, 0),\r\n\t\t\t10 =\u003E new(HalfAt(0), HalfAt(1), HalfAt(2), HalfAt(3)),\r\n\t\t\t34 =\u003E new(HalfAt(0), HalfAt(1), 0, 0),\r\n\t\t\t28 =\u003E new(b[p] / 255f, b[p \u002B 1] / 255f, b[p \u002B 2] / 255f, b[p \u002B 3] / 255f),\r\n\t\t\t_ =\u003E throw new InvalidOperationException(\u0022Unsupported clothing vertex format \u0022 \u002B 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 \u003C b.Count; i\u002B\u002B)\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 \u002B a.Offset;\r\n\t\t\t\tswitch (a.Name)\r\n\t\t\t\t{\r\n\t\t\t\t\tcase \u0022POSITION\u0022:\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 \u0022TEXCOORD\u0022:\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 \u0022BLENDINDICES\u0022:\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 =\u003E (int)U16(b.Data, p \u002B j * 2)).ToArray();\r\n\t\t\t\t\t\t\tif (ids.Any(j =\u003E j \u003E= boneCount || j \u003E 255))\r\n\t\t\t\t\t\t\t\tthrow new InvalidOperationException(\u0022Clothing bone index exceeds fitting layout.\u0022);\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(\u0022Unsupported clothing bone-index format \u0022 \u002B a.Format);\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase \u0022BLENDWEIGHT\u0022:\r\n\t\t\t\t\t\tif (a.Format != 28)\r\n\t\t\t\t\t\t\tthrow new InvalidOperationException(\u0022Unsupported clothing weight format \u0022 \u002B 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 \u0022COLOR\u0022:\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 \u0022NORMAL\u0022:\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 \u0022TANGENT\u0022:\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 \u003C .01f \u0026\u0026 native is not null \u0026\u0026 native.Length == b.Count \u0026\u0026 (native[i].Position - v.Position).Length \u003C .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 =\u003E j \u003E= boneCount))\r\n\t\t\t\tthrow new InvalidOperationException(\u0022Serialized garment bone map does not match its skeleton.\u0022);\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) =\u003E Enumerable.Range(0, b.Count).Select(i =\u003E 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 \u002B 2 \u003C indices.Length; i \u002B= 3)\r\n\t\t{\r\n\t\t\tvar a = indices[i];\r\n\t\t\tvar b = indices[i \u002B 1];\r\n\t\t\tvar c = indices[i \u002B 2];\r\n\t\t\tvar cross = Vector3.Cross(p[b] - p[a], p[c] - p[a]);\r\n\t\t\tn[a] \u002B= cross;\r\n\t\t\tn[b] \u002B= cross;\r\n\t\t\tn[c] \u002B= cross;\r\n\t\t}\r\n\r\n\t\treturn n.Select(v =\u003E 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 \u002B 2 \u003C indices.Length; i \u002B= 3)\r\n\t\t{\r\n\t\t\tint a = indices[i], c = indices[i \u002B 1], d = indices[i \u002B 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) \u003C 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] \u002B= tangent;\r\n\t\t\t\tb[id] \u002B= bitangent;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn Enumerable.Range(0, p.Length).Select(i =\u003E\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]) \u003C 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(\u0022automatic-human-fit:\u0022 \u002B source.Name \u002B \u0022:\u0022 \u002B (neckOnly ? \u0022replacement-neck\u0022 : female ? \u0022female\u0022 : \u0022male\u0022));\r\n\t\tfor (int i = 0; i \u003C source.BoneCount; i\u002B\u002B)\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 \u003C 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 \u003E 0 ? source.GetMaterials(0) : source.Materials).ToArray();\r\n\t\tvar meshMaterials = new List\u003CMaterial\u003E();\r\n\t\tvar work = new List\u003CAction\u003E();\r\n\t\tvar clothMale = new List\u003CVector3\u003E();\r\n\t\tvar clothFemale = new List\u003CVector3\u003E();\r\n\t\tvar clothIndices = new List\u003Cint\u003E();\r\n\t\tvar head = neckOnly || !fitFace ? null : HeadSurface();\r\n\t\tfor (int mi = 0; mi \u003C buffers.Count; mi\u002B\u002B)\r\n\t\t{\r\n\t\t\tvar info = source.MeshInfo.Meshes[mi];\r\n\t\t\tif (neckOnly \u0026\u0026 !info.Name.StartsWith(\u0022head\u0022, StringComparison.OrdinalIgnoreCase)) continue;\r\n\t\t\tvar buffer = buffers[mi];\r\n\t\t\tvar boneMap = buffer.Bones.Select(name =\u003E Enumerable.Range(0, source.BoneCount).First(i =\u003E 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 \u003E= buffer.Vertices.Length || ib \u003E= buffer.Indices.Length)\r\n\t\t\t\t\tthrow new InvalidOperationException(\u0022Unexpected garment draw-call buffer mapping.\u0022);\r\n\t\t\t\tvar verts = Vertices(buffer.Vertices[vb], mi == 0 \u0026\u0026 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 \u003C verts.Length; i\u002B\u002B)\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\u002B\u002B;\r\n\t\t\t\tvar selectedIndices = indices.Skip(call.StartIndex).Take(call.Indices).Select(i =\u003E i \u002B 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 =\u003E Enumerable.Range(0, 3).All(j =\u003E verts[selectedIndices[t * 3 \u002B j]].Position.z \u003C= 60))\r\n\t\t\t\t\t\t.SelectMany(t =\u003E 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) =\u003E (id, i)).ToDictionary(x =\u003E x.id, x =\u003E x.i);\r\n\t\t\t\tvar localIndices = selectedIndices.Select(i =\u003E lookup[i]).ToArray();\r\n\t\t\t\tvar v = used.Select(i =\u003E verts[i]).ToArray();\r\n\t\t\t\tvar original = v.Select(x =\u003E 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(\u0022/bodies/\u0022, StringComparison.OrdinalIgnoreCase) == true;\r\n\t\t\t\tfor (int i = 0; i \u003C v.Length; i\u002B\u002B)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar p = original[i];\r\n\t\t\t\t\tif (!skin \u0026\u0026 !neckOnly \u0026\u0026 fitFace \u0026\u0026 p.z \u003E= 57 \u0026\u0026 p.z \u003C= 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 \u003C 1.2f \u0026\u0026 hit.Distance \u003C 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 \u003E 0) p \u002B= 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 \u003E 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 \u0026\u0026 offset.Length \u003C .12f;\r\n\t\t\t\t\tmale[i] = m \u002B (snap ? Vector3.Zero : offset);\r\n\t\t\t\t\ttarget[i] = f \u002B (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 =\u003E i \u002B 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(() =\u003E\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 \u003C v.Length; i\u002B\u002B)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tmale[i] = clothMale[clothOffset \u002B i];\r\n\t\t\t\t\t\t\ttarget[i] = clothFemale[clothOffset \u002B 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 \u003C v.Length; i\u002B\u002B)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (v[i].Normal.LengthSquared \u003C .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 \u002B 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) =\u003E Vector3.Min(a, b)), male.Concat(target).Aggregate((a, b) =\u003E 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 \u003C source.MeshInfo.LodCount; lod\u002B\u002B)\r\n\t\t\t\t\t\t\tif ((lods \u0026 (1 \u003C\u003C 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 \u003E 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 \u003C source.MaterialGroupCount; group\u002B\u002B)\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 \u003E= 0 \u0026\u0026 index \u003C materials.Length ? materials[index] : material);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tfor (int lod = 1; lod \u003C source.MeshInfo.LodSwitchDistances.Length; lod\u002B\u002B)\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) =\u003E Vector3.Min(a, b)) - Vector3.One * 1.2f;\r\n\t\tvar max = points.Aggregate((a, b) =\u003E Vector3.Max(a, b)) \u002B Vector3.One * 1.2f;\r\n\t\tvar candidates = body.Points.Where(p =\u003E p.x \u003E= min.x \u0026\u0026 p.x \u003C= max.x \u0026\u0026 p.y \u003E= min.y \u0026\u0026 p.y \u003C= max.y \u0026\u0026 p.z \u003E= min.z \u0026\u0026 p.z \u003C= max.z).ToArray();\r\n\t\tvar pushes = new List\u003C(Vector3 Point, Vector3 Push)\u003E();\r\n\t\tvar fabric = new Surface(points, points, triangles, Array.Empty\u003Cstring\u003E(), Array.Empty\u003C(int, float)[]\u003E());\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 \u003E 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)) \u003C .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))) \u003C .25f)\r\n\t\t\t\tcontinue;\r\n\t\t\tvar penetration = Vector3.Dot(p - hit.Point, n) \u002B .14f;\r\n\t\t\tif (penetration \u003C= 0 || penetration \u003E 1.2f)\r\n\t\t\t\tcontinue;\r\n\t\t\tpushes.Add((hit.Point, n * MathF.Min(.7f, penetration \u002B .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) =\u003E ((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 =\u003E Cell(p.Point)).ToDictionary(g =\u003E g.Key, g =\u003E g.ToArray());\r\n\t\tfor (int i = 0; i \u003C points.Length; i\u002B\u002B)\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 \u003C= 1; x\u002B\u002B)\r\n\t\t\t\tfor (int y = -1; y \u003C= 1; y\u002B\u002B)\r\n\t\t\t\t\tfor (int z = -1; z \u003C= 1; z\u002B\u002B)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (!buckets.TryGetValue((cell.Item1 \u002B x, cell.Item2 \u002B y, cell.Item3 \u002B 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 \u003E= 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 \u003E 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] \u002B= 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) =\u003E Points[Triangles[t * 3 \u002B corner]];\r\n\t\tNode BuildTree(int[] faces)\r\n\t\t{\r\n\t\t\tvar p = faces.SelectMany(i =\u003E 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) =\u003E Vector3.Min(a, b)),\r\n\t\t\t\tMax = p.Aggregate((a, b) =\u003E Vector3.Max(a, b))\r\n\t\t\t};\r\n\t\t\tif (faces.Length \u003C= 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 \u003E extent.y ? (extent.x \u003E extent.z ? 0 : 2) : (extent.y \u003E extent.z ? 1 : 2);\r\n\t\t\tfloat Coord(Vector3 v) =\u003E axis == 0 ? v.x : axis == 1 ? v.y : v.z;\r\n\t\t\tvar sorted = faces.OrderBy(i =\u003E Coord(A(i, 0) \u002B A(i, 1) \u002B 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) =\u003E A(t, 0) * w.x \u002B A(t, 1) * w.y \u002B A(t, 2) * w.z;\r\n\t\tpublic Vector3 FaceNormal(int t) =\u003E 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) =\u003E (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) \u003E 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) \u003E 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 \u003C 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 \u003C= 0 \u0026\u0026 d2 \u003C= 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 \u003E= 0 \u0026\u0026 d4 \u003C= 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 \u003C= 0 \u0026\u0026 d1 \u003E= 0 \u0026\u0026 d3 \u003C= 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 \u003E= 0 \u0026\u0026 d5 \u003C= 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 \u003C= 0 \u0026\u0026 d2 \u003E= 0 \u0026\u0026 d6 \u003C= 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 \u003C= 0 \u0026\u0026 d4 - d3 \u003E= 0 \u0026\u0026 d5 - d6 \u003E= 0)\r\n\t\t\t{\r\n\t\t\t\tvar w = (d4 - d3) / (d4 - d3 \u002B 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 \u002B vb \u002B vc;\r\n\t\t\tif (MathF.Abs(sum) \u003C 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/// \u003Csummary\u003EStable native rest-space volumes; shape edits update coefficients without resetting merged skeletons.\u003C/summary\u003E\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) =\u003E skin?.ToLowerInvariant() switch\r\n\t{\r\n\t\t\u0022models/citizen_clothes/body/human01.clothing\u0022 =\u003E new(5.8f, 62.35f, 0.28f, 5.85f, 62.82f, 61.88f, 0.26f, 61.38f, 0.21f),\r\n\t\t\u0022models/citizen_clothes/body/human02.clothing\u0022 =\u003E new(5.5f, 62.3f, 0.22f, 5.7f, 62.65f, 61.82f, 0.25f, 61.27f, 0.23f),\r\n\t\t\u0022models/citizen_clothes/body/human04.clothing\u0022 =\u003E new(5.5f, 62.17f, 0.21f, 5.75f, 62.58f, 61.76f, 0.21f, 61.30f, 0.23f),\r\n\t\t\u0022models/citizen_clothes/body/human05.clothing\u0022 =\u003E new(5.7f, 62.26f, 0.23f, 5.85f, 62.67f, 61.79f, 0.20f, 61.43f, 0.16f),\r\n\t\t\u0022models/citizen_clothes/body/human06.clothing\u0022 =\u003E new(5.75f, 62.35f, 0.28f, 5.8f, 62.82f, 61.87f, 0.26f, 61.39f, 0.22f),\r\n\t\t\u0022models/citizen_clothes/body/human07.clothing\u0022 =\u003E new(5.7f, 62.26f, 0.23f, 5.75f, 62.72f, 61.80f, 0.20f, 61.42f, 0.21f),\r\n\t\t_ =\u003E 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) =\u003E skin?.ToLowerInvariant() switch\r\n\t{\r\n\t\t\u0022models/citizen_clothes/body/human01.clothing\u0022 =\u003E new(61.65f, 61.1f, 60.4f, 1),\r\n\t\t\u0022models/citizen_clothes/body/human02.clothing\u0022 =\u003E new(61.65f, 61.1f, 60.4f, 0.93f),\r\n\t\t\u0022models/citizen_clothes/body/human04.clothing\u0022 =\u003E new(61.65f, 61.1f, 60.4f, 0.92f),\r\n\t\t\u0022models/citizen_clothes/body/human05.clothing\u0022 or \u0022models/citizen_clothes/body/human07.clothing\u0022 =\u003E new(61.65f, 61.2f, 60.4f, 0.97f),\r\n\t\t_ =\u003E 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\u003Cstring, float[]\u003E();\r\n\t\tfloat Blend(Func\u003Cstring, float\u003E f) =\u003E headWeights is null ? f(a.SkinPath) : AppearanceCatalog.Skins.Select((s, i) =\u003E f(s.ResourcePath) * headWeights[i]).Sum();\r\n\t\tfloat BlendRegion(string region, Func\u003Cstring, float\u003E 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) =\u003E f(s.ResourcePath) * weights[i]).Sum();\r\n\t\t}\r\n\r\n\t\tfloat Signed(float value) =\u003E (value - 0.5f) * 2;\r\n\t\tVector3 Scale(Vector3 maximum, float value) =\u003E Vector3.One \u002B (maximum - Vector3.One) * Signed(value);\r\n\t\tfloat Extended(float value, float maximum) =\u003E Signed(value) * (value \u003E 0.5f ? maximum : 1);\r\n\t\tfloat Range(float value, float negative, float positive) =\u003E Signed(value) * (value \u003C 0.5f ? negative : positive);\r\n\t\tVector3 AmountScale(Vector3 maximum, float amount) =\u003E Vector3.One \u002B (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 \u003C 0.5f ? 0.4f : 0.8f);\r\n\t\tVector3 Build(Vector3 soft, Vector3 athletic) =\u003E Vector3.One \u002B ((a.Fitness \u003C 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(\u0022nose\u0022, s =\u003E string.Equals(s, \u0022models/citizen_clothes/body/human04.clothing\u0022, StringComparison.OrdinalIgnoreCase) ? 1 : 0);\r\n\t\tVector3 WingCenter(float side) =\u003E 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(\u0022mouth\u0022, s =\u003E MouthFor(s).SurfaceX), BlendRegion(\u0022mouth\u0022, s =\u003E MouthFor(s).PhiltrumZ), BlendRegion(\u0022mouth\u0022, s =\u003E MouthFor(s).PhiltrumHeight), BlendRegion(\u0022nose\u0022, s =\u003E MouthFor(s).ColumellaX), BlendRegion(\u0022nose\u0022, s =\u003E MouthFor(s).ColumellaZ), BlendRegion(\u0022mouth\u0022, s =\u003E MouthFor(s).UpperZ), BlendRegion(\u0022mouth\u0022, s =\u003E MouthFor(s).UpperHeight), BlendRegion(\u0022mouth\u0022, s =\u003E MouthFor(s).LowerZ), BlendRegion(\u0022mouth\u0022, s =\u003E MouthFor(s).LowerHeight));\r\n\t\tvar jaw = new JawAnatomy(BlendRegion(\u0022jaw\u0022, s =\u003E JawFor(s).AngleZ), BlendRegion(\u0022jaw\u0022, s =\u003E JawFor(s).MiddleZ), BlendRegion(\u0022jaw\u0022, s =\u003E JawFor(s).FrontZ), BlendRegion(\u0022jaw\u0022, s =\u003E 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 \u003C 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(\u0022jaw\u0022, s =\u003E s?.ToLowerInvariant() switch\r\n\t\t{\r\n\t\t\t\u0022models/citizen_clothes/body/human01.clothing\u0022 =\u003E 59.37f,\r\n\t\t\t\u0022models/citizen_clothes/body/human02.clothing\u0022 =\u003E 59.92f,\r\n\t\t\t\u0022models/citizen_clothes/body/human04.clothing\u0022 =\u003E 59.74f,\r\n\t\t\t\u0022models/citizen_clothes/body/human05.clothing\u0022 =\u003E 59.43f,\r\n\t\t\t\u0022models/citizen_clothes/body/human06.clothing\u0022 =\u003E 59.5f,\r\n\t\t\t\u0022models/citizen_clothes/body/human07.clothing\u0022 =\u003E 59.59f,\r\n\t\t\t_ =\u003E 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 \u002B (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) =\u003E new(0.15f * headScale.x, side * 3.65f * headScale.y, 63 \u002B 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(\u0022Neck size\u0022, 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(\u0022Left collarbone height\u0022, 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(\u0022Right collarbone height\u0022, 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(\u0022Left trapezius\u0022, new(-1.8f, 4.3f, 57.6f \u002B .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(\u0022Right trapezius\u0022, new(-1.8f, -4.3f, 57.6f \u002B .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(\u0022Waist size\u0022, 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(\u0022Chest size\u0022, 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(\u0022Left chest shoulder\u0022, 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(\u0022Right chest shoulder\u0022, 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(\u0022Build midsection\u0022, 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(\u0022Build chest\u0022, 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(\u0022Build left arm\u0022, 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(\u0022Build right arm\u0022, 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(\u0022Build left shoulder\u0022, 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(\u0022Build right shoulder\u0022, 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(\u0022Build left forearm\u0022, 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(\u0022Build right forearm\u0022, 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(\u0022Build left calf\u0022, 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(\u0022Build right calf\u0022, 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(\u0022Build left thigh\u0022, 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(\u0022Build right thigh\u0022, 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(\u0022Left body contour\u0022, 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(\u0022Right body contour\u0022, 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(\u0022Left butt size\u0022, 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(\u0022Right butt size\u0022, 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(\u0022Belly size\u0022, 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(\u0022Left foot size\u0022, 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(\u0022Right foot size\u0022, 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(\u0022Head shape\u0022, new(0, 0, 63), 10, Vector3.One, Scale(new(1.06f, 1.08f, 1.025f), a.HeadShape), Vector3.Zero),\r\n\t\t\t// \u002BX is forward on the native human rig. Translation changes profile depth\r\n\t\t\t// without radial inflation\u0027s 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\u0027s mask, rather than a jaw/face sphere.\r\n\t\t\tnew Edit(\u0022Nose projection\u0022, 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(\u0022Nose width\u0022, 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(\u0022Nose tip height\u0022, 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(\u0022Chin projection\u0022, 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(\u0022Chin width\u0022, 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(\u0022Chin height\u0022, 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(\u0022Submental fullness\u0022, new(3.65f * headScale.x, 0, 63 \u002B (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(\u0022Left jaw angle\u0022, new(1.9f, 2.7f * jaw.Width, jaw.AngleZ), new(1.1f, 1.1f, 0.6f), 0.085f, 42),\r\n\t\t\tJawEdit(\u0022Right jaw angle\u0022, new(1.9f, -2.7f * jaw.Width, jaw.AngleZ), new(1.1f, 1.1f, 0.6f), -0.085f, 42),\r\n\t\t\tJawEdit(\u0022Left jaw width\u0022, new(3, 2.25f * jaw.Width, jaw.MiddleZ), new(2.1f, 1.6f, 0.7f), 0.07f, 26),\r\n\t\t\tJawEdit(\u0022Right jaw width\u0022, new(3, -2.25f * jaw.Width, jaw.MiddleZ), new(2.1f, 1.6f, 0.7f), -0.07f, 26),\r\n\t\t\tJawEdit(\u0022Left jaw front\u0022, new(4.35f, 1.4f * jaw.Width, jaw.FrontZ), new(1.55f, 1.7f, 0.65f), 0.045f, 10),\r\n\t\t\tJawEdit(\u0022Right jaw front\u0022, 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(\u0022Left cheek fullness\u0022, 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(\u0022Right cheek fullness\u0022, 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(\u0022Left nostril width\u0022, 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(\u0022Right nostril width\u0022, 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\u0027s rounded cap in depth/height, independent of spacing.\r\n\t\t\tnew Edit(\u0022Left nostril-wing fullness\u0022, 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(\u0022Right nostril-wing fullness\u0022, 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(\u0022Forehead width\u0022, 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(\u0022Left cheekbone width\u0022, 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(\u0022Right cheekbone width\u0022, 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\u0027s 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(\u0022Left ear taper\u0022, EarCenter(1), 1, earMask, new(1 - 0.4f * a.EarTaper, 1, 1 \u002B 0.35f * a.EarTaper), new(-0.3f * a.EarTaper, 0, 0.35f * a.EarTaper)),\r\n\t\t\tnew Edit(\u0022Right ear taper\u0022, EarCenter(-1), 1, earMask, new(1 - 0.4f * a.EarTaper, 1, 1 \u002B 0.35f * a.EarTaper), new(-0.3f * a.EarTaper, 0, 0.35f * a.EarTaper)),\r\n\t\t\tnew Edit(\u0022Left ear size\u0022, 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(\u0022Right ear size\u0022, 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(\u0022Left eye size\u0022, 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(\u0022Right eye size\u0022, 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(\u0022Brow prominence\u0022, 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(\u0022Philtrum width\u0022, new(mouth.SurfaceX - 0.05f, 0, (mouth.PhiltrumZ \u002B mouth.UpperZ) / 2 - 0.08f \u002B philtrumLipClearance \u002B philtrumUpperExtension / 2), 1, new(0.75f, 0.9f, (mouth.PhiltrumZ - mouth.UpperZ) / 2 \u002B 0.2f - philtrumLipClearance \u002B philtrumUpperExtension / 2), Scale(new(1, 1.28f, 1), a.PhiltrumWidth), Vector3.Zero),\r\n\t\t\tnew Edit(\u0022Mouth width\u0022, 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(\u0022Mouth projection\u0022, 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(\u0022Upper lip fullness\u0022, 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(\u0022Lower lip fullness\u0022, 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(\u0022Columella width\u0022, 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(\u0022Left bulge\u0022, new(4, .65f, 34), 1, new(2.7f, 2.5f, 3), Vector3.One, new(.23f * bulge, 0, 0)), new Edit(\u0022Right bulge\u0022, 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) =\u003E !((appearance.BaseShape is not null || AppearanceCatalog.IsFemale(appearance)) \u0026\u0026 (appearance.BaseShape?.BreastSize ?? appearance.BodyFeatureSize) \u003C 0.5f \u0026\u0026 (volumeName is \u0022Left body contour\u0022 or \u0022Right body contour\u0022));\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 = \u0022Local human shape volumes\u0022,\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 \u003C edits.Length; i\u002B\u002B)\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\u003CModelDeformer\u003E();\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(\u0022jaw\u0022) ? 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\u003CModelDeformer\u003E(true, true).OrderBy(x =\u003E x.Priority).ToArray() : Array.Empty\u003CModelDeformer\u003E();\r\n\t\tif (volumes.Length != (appearance.BaseShape is null ? VolumeCount : VolumeCount \u002B 2) || volumes.Any(x =\u003E 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 \u003C edits.Length; i\u002B\u002B)\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(\u0022jaw\u0022) ? 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/// \u003Csummary\u003ESurface-specific authoring limits; arbitrary transforms never enter appearance saves.\u003C/summary\u003E\r\npublic static class MarkingCatalog\r\n{\r\n\tpublic const int Maximum = 6;\r\n\tpublic static IReadOnlyList\u003CMarkingStyle\u003E Styles { get; } = new[]\r\n\t{\r\n\t\tnew MarkingStyle(\u0022none\u0022, \u0022None\u0022, 0),\r\n\t\tnew(\u0022crescent\u0022, \u0022Crescent\u0022, 1),\r\n\t\tnew(\u0022teardrop\u0022, \u0022Teardrop\u0022, 2),\r\n\t\tnew(\u0022star\u0022, \u0022Star\u0022, 3),\r\n\t\tnew(\u0022rune\u0022, \u0022Rune\u0022, 4),\r\n\t\tnew(\u0022barbed-band\u0022, \u0022Barbed wire band\u0022, 5, true)\r\n\t};\r\n\tpublic static IReadOnlyList\u003CMarkingRegion\u003E Regions =\u003E ShoulderSafeRegionDefinitions;\r\n\r\n\tstatic readonly IReadOnlyList\u003CMarkingRegion\u003E ShoulderSafeRegionDefinitions = new[]\r\n\t{\r\n\t\tnew MarkingRegion(\u0022face\u0022, \u0022Face\u0022, 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(\u0022left-cheek\u0022, \u0022Left cheek\u0022, 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(\u0022right-cheek\u0022, \u0022Right cheek\u0022, 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(\u0022forehead\u0022, \u0022Forehead\u0022, 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(\u0022chest\u0022, \u0022Front torso\u0022, 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(\u0022back\u0022, \u0022Back\u0022, 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(\u0022left-arm\u0022, \u0022Left arm\u0022, 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(\u0022right-arm\u0022, \u0022Right arm\u0022, 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(\u0022left-leg\u0022, \u0022Left leg\u0022, 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(\u0022right-leg\u0022, \u0022Right leg\u0022, 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(\u0022neck\u0022, \u0022Neck choker\u0022, 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(\u0022torso\u0022, \u0022Torso belt\u0022, 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(\u0022head\u0022, \u0022Head band\u0022, 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) =\u003E Styles.FirstOrDefault(x =\u003E x.Id == id);\r\n\tpublic static MarkingRegion Region(string id) =\u003E Regions.FirstOrDefault(x =\u003E x.Id == id);\r\n\tstatic bool Compatible(MarkingRegion region, bool band) =\u003E band ? (!region.StampOnly \u0026\u0026 region.Id is not \u0022left-cheek\u0022 and not \u0022right-cheek\u0022 and not \u0022forehead\u0022) : !region.BandOnly;\r\n\tpublic static IEnumerable\u003CMarkingRegion\u003E Locations(string style) =\u003E Regions.Where(x =\u003E Compatible(x, Style(style)?.Band == true) \u0026\u0026 (Style(style)?.Band == true || x.Id is not \u0022left-cheek\u0022 and not \u0022right-cheek\u0022 and not \u0022forehead\u0022));\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) =\u003E Style(style)is { } design \u0026\u0026 Region(region)is { } location \u0026\u0026 Compatible(location, design.Band);\r\n\tpublic static bool OnHead(CharacterMarkingData data) =\u003E Region(data.Region).Head;\r\n\tpublic static bool OnMaterial(CharacterMarkingData data, int armSide) =\u003E armSide == 1 ? data.Region == \u0022left-arm\u0022 : armSide == -1 ? data.Region == \u0022right-arm\u0022 : OnBody(data) \u0026\u0026 data.Region is not \u0022left-arm\u0022 and not \u0022right-arm\u0022;\r\n\tpublic static bool OnBody(CharacterMarkingData data) =\u003E !Region(data.Region).Head || data.Region == \u0022neck\u0022;\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 == \u0022face\u0022 ? .68f : .5f;\r\n\t\tdata.Vertical = data.Region == \u0022face\u0022 ? .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\u0022chest\u0022 =\u003E (41.5f, 54.5f),\r\n\t\t\t\u0022back\u0022 =\u003E (41.2f, 55f),\r\n\t\t\t\u0022left-arm\u0022 or \u0022right-arm\u0022 =\u003E (38.2f, Style(data.Style).Band ? 52.48f - MathF.Max(0, BandScale(data) - 1) * .55f : 53.2f),\r\n\t\t\t\u0022left-leg\u0022 or \u0022right-leg\u0022 =\u003E (5.8f, 35.5f),\r\n\t\t\t\u0022face\u0022 =\u003E (60.9f, 67.5f),\r\n\t\t\t\u0022neck\u0022 =\u003E (58.3f, 60.4f),\r\n\t\t\t\u0022torso\u0022 =\u003E (41.2f, 49.5f),\r\n\t\t\t\u0022head\u0022 =\u003E (59.9f, 68.5f),\r\n\t\t\t_ =\u003E (region.Center.z - region.MoveY, region.Center.z \u002B region.MoveY)};\r\n\t\tvar neutral = region.Center.z;\r\n\t\tif (region.Id is \u0022chest\u0022 or \u0022back\u0022)\r\n\t\t{\r\n\t\t\tvar theta = (region.Id == \u0022back\u0022 ? MathF.PI : 0) \u002B (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)) \u002B .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 \u002B (limits.Item2 - limits.Item1) * (region.Id == \u0022chest\u0022 ? .81f : .565f));\r\n\t\t}\r\n\r\n\t\tvar z = data.Vertical \u003C .5f ? limits.Item1 \u002B (neutral - limits.Item1) * data.Vertical * 2 : neutral \u002B (limits.Item2 - neutral) * (data.Vertical - .5f) * 2;\r\n\t\tvar center = region.Center \u002B region.Up * ((z - region.Center.z) / region.Up.z);\r\n\t\tif (region.Id is \u0022left-arm\u0022 or \u0022right-arm\u0022)\r\n\t\t{\r\n\t\t\tvar lower = z \u003C 46;\r\n\t\t\tvar t = lower ? (z - 39) / 7 : (z - 46) / 7.2f;\r\n\t\t\tcenter = new(lower ? 1.1f \u002B (-2.6f) * t : -1.5f \u002B .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 \u0022left-leg\u0022 or \u0022right-leg\u0022)\r\n\t\t\tcenter = LegAxis(z, MathF.Sign(region.Center.y));\r\n\t\tif (region.Id == \u0022neck\u0022)\r\n\t\t\tcenter = new(.3f * (z - 59.3f) \u002B .2f, 0, z);\r\n\t\tif (region.Id is \u0022chest\u0022 or \u0022back\u0022)\r\n\t\t{\r\n\t\t\tvar theta = (region.Id == \u0022back\u0022 ? MathF.PI : 0) \u002B (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 == \u0022face\u0022)\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 \u0022left-cheek\u0022 or \u0022right-cheek\u0022)\r\n\t\t{\r\n\t\t\tvar angle = MathF.Atan2(MathF.Abs(region.Center.y), region.Center.x) \u002B (data.Horizontal - .5f) * 2 * (data.Horizontal \u003E .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 \u002B region.Right * (data.Horizontal - .5f) * region.MoveX * 2;\r\n\t}\r\n\r\n\tpublic static bool IsLimb(string id) =\u003E id is \u0022left-arm\u0022 or \u0022right-arm\u0022 or \u0022left-leg\u0022 or \u0022right-leg\u0022;\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 \u0022left-arm\u0022 or \u0022right-arm\u0022)\r\n\t\t\t\taxis = new Vector3(center.z \u003C 46 ? -.3714f : .0556f, MathF.Sign(region.Center.y) * (center.z \u003C 46 ? -.9429f : -.6667f), 1).Normal;\r\n\t\t\tif (region.Id is \u0022left-leg\u0022 or \u0022right-leg\u0022)\r\n\t\t\t\taxis = (LegAxis(center.z \u002B .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 == \u0022left-arm\u0022 ? MathF.PI / 2 : region.Id == \u0022right-arm\u0022 ? -MathF.PI / 2 : 0;\r\n\t\t\treturn new(center, right, axis, normal, IsLimb(region.Id) ? 2 : 0, phase \u002B (data.Horizontal - .5f) * MathF.PI * 2);\r\n\t\t}\r\n\r\n\t\tif (region.Id is \u0022chest\u0022 or \u0022back\u0022)\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 == \u0022face\u0022)\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 \u0022left-cheek\u0022 or \u0022right-cheek\u0022 ? 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) \u002B 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) =\u003E Center(data) \u002B (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 \u003C CalibratedLegCenters.Length - 2 \u0026\u0026 z \u003E CalibratedLegCenters[i \u002B 1].z)\r\n\t\t\ti\u002B\u002B;\r\n\t\tvar a = CalibratedLegCenters[i];\r\n\t\tvar b = CalibratedLegCenters[i \u002B 1];\r\n\t\tvar value = a \u002B (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) =\u003E data.Scale \u003C= .5f ? .5f \u002B data.Scale : 1 \u002B (data.Scale - .5f) * 4;\r\n\tpublic static float BandScale(CharacterMarkingData data) =\u003E data.Scale \u003C .5f ? .35f \u002B data.Scale * 1.3f : 1 \u002B (data.Scale - .5f) * 4;\r\n\tpublic static float BandSpacing(CharacterMarkingData data) =\u003E data.Spacing \u003C .5f ? .35f \u002B data.Spacing * 1.3f : 1 \u002B (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\u003Croot\u003E\r\n \u003Cdiv class=\u0022labheader\u0022\u003E\u003Cdiv\u003E\u003Cdiv class=\u0022eyebrow\u0022\u003ECHARACTER EDITOR / EXPERIMENTS\u003C/div\u003E\u003Ch1\u003EOne face, toward another.\u003C/h1\u003E\u003Cp\u003ECompare shape alone. Same surface topology, material, camera and lighting.\u003C/p\u003E\u003C/div\u003E\u003Cbutton onclick=@Back\u003EBack to collection\u003C/button\u003E\u003C/div\u003E\r\n \u003Cdiv class=\u0022toolbar\u0022\u003E\r\n  \u003Cbutton class=@(Pair==\u0022human03-to-human07\u0022?\u0022chosen\u0022:\u0022\u0022) onclick=@(()=\u003EPair=\u0022human03-to-human07\u0022)\u003EHuman 03 \u2192 07\u003C/button\u003E\r\n  \u003Cbutton class=@(Pair==\u0022human02-to-human04\u0022?\u0022chosen\u0022:\u0022\u0022) onclick=@(()=\u003EPair=\u0022human02-to-human04\u0022)\u003EHuman 02 \u2192 04\u003C/button\u003E\r\n  \u003Cdiv class=\u0022spacer\u0022\u003E\u003C/div\u003E\r\n  \u003Cbutton onclick=@(()=\u003EYaw=0)\u003EFront\u003C/button\u003E\u003Cbutton onclick=@(()=\u003EYaw=35)\u003EThree-quarter\u003C/button\u003E\u003Cbutton onclick=@(()=\u003EYaw=90)\u003EProfile\u003C/button\u003E\r\n \u003C/div\u003E\r\n \u003Cdiv class=\u0022views\u0022\u003E\r\n  \u003Cdiv class=\u0022view\u0022\u003E\u003Cdiv class=\u0022caption\u0022\u003E\u003Ch2\u003ESource\u003C/h2\u003E\u003Cspan\u003EUnmodified starting face\u003C/span\u003E\u003C/div\u003E\u003CFaceTransferPreview Pair=@Pair Mode=\u0022Source\u0022 Yaw=@Yaw /\u003E\u003C/div\u003E\r\n  \u003Cdiv class=\u0022view\u0022\u003E\u003Cdiv class=\u0022caption\u0022\u003E\u003Ch2\u003E@ModeLabel\u003C/h2\u003E\u003Cspan\u003E@($\u0022{Amount:P0} applied\u0022)\u003C/span\u003E\u003C/div\u003E\u003CFaceTransferPreview Pair=@Pair Mode=@Mode Amount=@Amount Yaw=@Yaw /\u003E\u003C/div\u003E\r\n  \u003Cdiv class=\u0022view\u0022\u003E\u003Cdiv class=\u0022caption\u0022\u003E\u003Ch2\u003ETarget\u003C/h2\u003E\u003Cspan\u003EReference head surface\u003C/span\u003E\u003C/div\u003E\u003CFaceTransferPreview Pair=@Pair Mode=\u0022Target\u0022 Amount=@(1f) Yaw=@Yaw /\u003E\u003C/div\u003E\r\n \u003C/div\u003E\r\n \u003Cdiv class=\u0022controls\u0022\u003E\u003Cdiv class=\u0022toolbar\u0022\u003E\r\n  \u003Cbutton class=@(Mode==\u0022Existing\u0022?\u0022chosen\u0022:\u0022\u0022) onclick=@(()=\u003E{Mode=\u0022Existing\u0022;Amount=1;})\u003EFitted editor sliders\u003C/button\u003E\r\n  \u003Cbutton class=@(Mode==\u0022Experimental\u0022?\u0022chosen\u0022:\u0022\u0022) onclick=@(()=\u003E{Mode=\u0022Experimental\u0022;Amount=1;})\u003EExperimental volumes\u003C/button\u003E\r\n  \u003Cbutton class=@(Mode==\u0022Target\u0022?\u0022chosen\u0022:\u0022\u0022) onclick=@(()=\u003E{Mode=\u0022Target\u0022;Amount=1;})\u003EDirect vertex morph\u003C/button\u003E\r\n \u003C/div\u003E\u003Cdiv class=\u0022blend\u0022\u003E\u003Cspan\u003ESource\u003C/span\u003E\u003CSliderControl Value=@Amount OnValueChanged=@((float v)=\u003E{Amount=v;}) Min=@(0f) Max=@(1f) Step=@(.01f) ShowTextEntry=@false /\u003E\u003Cspan\u003EFull result\u003C/span\u003E\u003C/div\u003E\r\n \u003Cp\u003E@Explanation\u003C/p\u003E\u003Cdiv class=\u0022footnote\u0022\u003ERest-pose head surface only \u2022 Eye interiors, teeth, skin detail and animation excluded \u2022 Results measure geometry, not likeness\u003C/div\u003E\u003C/div\u003E\r\n \u003CStudioEscapeMenu @ref=EscapeMenu MainMenu=@Back EnableExperiments=@false /\u003E\r\n\u003C/root\u003E\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 \u0026\u0026 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; } = \u0022human03-to-human07\u0022;\r\n\tpublic string Mode { get; set; } = \u0022Existing\u0022;\r\n\tpublic float Amount { get; set; } = 1;\r\n\tpublic float Yaw { get; set; }\r\n\r\n\tstring ModeLabel =\u003E Mode == \u0022Existing\u0022 ? \u0022Fitted editor sliders\u0022 : Mode == \u0022Experimental\u0022 ? \u0022Experimental volumes\u0022 : \u0022Direct vertex morph\u0022;\r\n\r\n\tstring Explanation =\u003E Mode == \u0022Target\u0022 ? \u0022The 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.\u0022 : Mode == \u0022Existing\u0022 ? (Pair == \u0022human03-to-human07\u0022 ? \u002224 facial controls: front-surface RMS error falls from 1.84 mm to 1.51 mm (18%).\u0022 : \u002224 facial controls: front-surface RMS error falls from 4.07 mm to 3.84 mm (6%).\u0022) : \u0022A 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.\u0022;\r\n\r\n\tvoid Back() =\u003E StudioSession.OpenScene(\u0022scenes/main_menu.scene\u0022);\r\n\tprotected override int BuildHash() =\u003E 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\u003Croot class=\u0022cloudwardrobe\u0022\u003E\r\n \u003Cdiv class=\u0022optionheading\u0022\u003ECloud clothing\u003C/div\u003E\r\n \u003Cdiv class=\u0022optiondescription\u0022\u003EFind 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.\u003C/div\u003E\r\n \u003CTextEntry Value=@Query OnTextEdited=@((string value)=\u003E{Query=value;}) Placeholder=\u0022Search cloud clothing\u0022 /\u003E\r\n \u003Cdiv class=\u0022choicegrid\u0022\u003E\r\n  \u003Cbutton class=\u0022creatorbutton\u0022 disabled=@Busy onclick=@(()=\u003ESearch(false))\u003ESearch cloud\u003C/button\u003E\r\n  \u003Cbutton class=\u0022creatorbutton quiet\u0022 disabled=@Busy onclick=@ShowInventory\u003EYour purchased cosmetics\u003C/button\u003E\r\n \u003C/div\u003E\r\n \u003CTextEntry Value=@AssetReference OnTextEdited=@((string value)=\u003E{AssetReference=value;}) Placeholder=\u0022Clothing asset path or cloud:creator.item\u0022 /\u003E\r\n \u003Cbutton class=\u0022creatorbutton quiet\u0022 disabled=@Busy onclick=@(()=\u003EAddClothing(AssetReference.Trim()))\u003EAdd by reference\u003C/button\u003E\r\n @if(!string.IsNullOrEmpty(Message)){\u003Cdiv class=\u0022optiondescription\u0022\u003E@Message\u003C/div\u003E}\r\n @if(InventoryVisible)\r\n {\r\n  \u003Cdiv class=\u0022choicegrid thumbnailgrid\u0022\u003E\r\n  @foreach(var item in Sandbox.Services.Inventory.Definitions.Where(x=\u003ESandbox.Services.Inventory.HasItem(x.Id) \u0026\u0026 !string.IsNullOrEmpty(x.PackageIdent) \u0026\u0026 (string.IsNullOrWhiteSpace(Query)||x.Name.Contains(Query,StringComparison.OrdinalIgnoreCase))))\r\n  {\r\n   \u003Cbutton class=@TileClass(\u0022cloud:\u0022\u002Bitem.PackageIdent) disabled=@Busy onclick=@(()=\u003EAddClothing(\u0022cloud:\u0022\u002Bitem.PackageIdent))\u003E\n    \u003Cdiv class=\u0022thumbnailframe\u0022\u003E\u003Cdiv class=\u0022itemthumbnail\u0022 style=@($\u0022background-image: url(\u0027{item.IconUrl}\u0027);\u0022)\u003E\u003C/div\u003E@if(LoadingReference==\u0022cloud:\u0022\u002Bitem.PackageIdent){\u003Cdiv class=\u0022clothingloading\u0022\u003E\u003Cspan class=\u0022material-icons loadingspinner\u0022\u003Eautorenew\u003C/span\u003E\u003Cspan\u003ELoading\u2026\u003C/span\u003E\u003C/div\u003E}\u003C/div\u003E\u003Cspan class=\u0022itemlabel\u0022\u003E@item.Name\u003C/span\u003E\n   \u003C/button\u003E\r\n  }\r\n  \u003C/div\u003E\r\n }\r\n else\r\n {\r\n  \u003Cdiv class=\u0022choicegrid thumbnailgrid\u0022\u003E\r\n  @foreach(var package in Results)\r\n  {\r\n   \u003Cbutton class=@TileClass(\u0022cloud:\u0022\u002Bpackage.FullIdent) disabled=@Busy onclick=@(()=\u003EAddClothing(\u0022cloud:\u0022\u002Bpackage.FullIdent))\u003E\n    \u003Cdiv class=\u0022thumbnailframe\u0022\u003E\u003Cdiv class=\u0022itemthumbnail\u0022 style=@($\u0022background-image: url(\u0027{package.ThumbWide}\u0027);\u0022)\u003E\u003C/div\u003E@if(LoadingReference==\u0022cloud:\u0022\u002Bpackage.FullIdent){\u003Cdiv class=\u0022clothingloading\u0022\u003E\u003Cspan class=\u0022material-icons loadingspinner\u0022\u003Eautorenew\u003C/span\u003E\u003Cspan\u003ELoading\u2026\u003C/span\u003E\u003C/div\u003E}\u003C/div\u003E\u003Cspan class=\u0022itemlabel\u0022\u003E@package.Title\u003C/span\u003E\n   \u003C/button\u003E\r\n  }\r\n  \u003C/div\u003E\r\n  @if(Results.Count\u003E0 \u0026\u0026 Results.Count\u003CTotal){\u003Cbutton class=\u0022creatorbutton quiet\u0022 disabled=@Busy onclick=@(()=\u003ESearch(true))\u003ELoad more\u003C/button\u003E}\r\n }\r\n\u003C/root\u003E\r\n\r\n@code {\r\n public Action\u003CWardrobeItem\u003E Selected { get; set; }\n public CharacterAppearanceData Appearance { get; set; }\n string Query=\u0022\u0022, AssetReference=\u0022\u0022, Message;\n string LoadingReference;\n string ActiveQuery;\r\n bool Busy, InventoryVisible;\r\n int Generation;\r\n long Total;\r\n List\u003CPackage\u003E Results=new();\n IEnumerable\u003Cstring\u003E Equipped =\u003E Appearance is null ? Enumerable.Empty\u003Cstring\u003E() : WardrobeCatalog.Paths(Appearance).Concat(new[] { Appearance.HairPath, Appearance.FacialHairPath }).Where(p=\u003E!string.IsNullOrEmpty(p));\n bool IsEquipped(string reference) =\u003E Equipped.Any(path=\u003Estring.Equals(path,reference,StringComparison.OrdinalIgnoreCase) || (ClothingResources.Get(reference) is { } item \u0026\u0026 ClothingResources.Get(path)==item));\n string TileClass(string reference) =\u003E $\u0022creatorbutton wardrobechoice {(IsEquipped(reference)?\u0022chosen\u0022:\u0022\u0022)} {(LoadingReference==reference?\u0022loading\u0022:\u0022\u0022)}\u0022;\n void ShowInventory()\r\n {\r\n  if(Busy)return;\r\n  InventoryVisible=true;\r\n  Message=\u0022Purchased items use your s\u0026box inventory. Items without a human model cannot be worn here.\u0022;\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=\u002B\u002BGeneration;\r\n  Message=\u0022Searching\u2026\u0022;\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(\u0022 \u0022, (ActiveQuery??\u0022\u0022).Split(\u0027 \u0027,StringSplitOptions.RemoveEmptyEntries).Where(x=\u003E!x.Contains(\u0027:\u0027)));\r\n   var result=await Package.FindAsync($\u0022{words} type:clothing sort:newest\u0022,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?\u0022No clothing found.\u0022:$\u0022{Total} cloud items. Click a preview to add it.\u0022;\r\n  }\r\n  catch(Exception e){if(IsValid)Message=\u0022Cloud search failed: \u0022\u002Be.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=\u0022Loading clothing\u2026\u0022;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(\u0022Choose a clothing or hair item; base skins are selected in the Skin tab.\u0022);\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)?\u0022Equipped \u0022:\u0022Removed \u0022)\u002Bclothing.Title\u002B\u0022.\u0022;\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 \u0026\u0026 !Busy) await Search(false);\r\n }\r\n protected override int BuildHash()=\u003EHashCode.Combine(Query,Message,Busy,InventoryVisible,Results.Count,Total,HashCode.Combine(Sandbox.Services.Inventory.Items?.Count,AssetReference,LoadingReference,ClothingResources.Revision),string.Join(\u0022|\u0022,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\u003Croot\u003E\r\n\r\n \u003Cdiv class=\u0022optionheading\u0022\u003EMake a face from photos\u003C/div\u003E\r\n\r\n \u003Cdiv class=\u0022optiondescription\u0022\u003EAdd 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.\u003C/div\u003E\r\n\r\n \u003Cdiv class=\u0022optiondescription\u0022\u003EImages are read by the background analyzer on this device. Some image hosts block this access. Photos and links are not included in saved characters.\u003C/div\u003E\r\n\r\n \u003CTextEntry class=\u0022photo-url\u0022 Value=@Url OnTextEdited=@((string value)=\u003E{Url=value;}) Placeholder=\u0022https://\u2026/portrait.jpg\u0022 MaxLength=@(2048) /\u003E\r\n\r\n \u003Cbutton class=\u0022creatorbutton add-photo\u0022 onclick=@AddReference\u003ELoad reference\u003C/button\u003E\r\n @if(Rows.Count\u003E0){\u003Cbutton class=\u0022creatorbutton quiet refresh-photos\u0022 onclick=@RefreshReferences\u003ERefresh analyses\u003C/button\u003E}\r\n @if(!string.IsNullOrEmpty(Message)){\u003Cdiv class=\u0022optiondescription\u0022\u003E@Message\u003C/div\u003E}\r\n\r\n @foreach(var row in Rows)\r\n\r\n {\r\n\r\n  \u003Cdiv class=\u0022photo-reference\u0022\u003E\r\n\r\n   @if(row.Measurement is not null){\u003Cimg class=\u0022photo-thumb\u0022 src=@row.Url /\u003E}\r\n\r\n   \u003Cdiv class=\u0022photo-reference-text\u0022\u003E\u003Cdiv class=\u0022fieldlabel\u0022\u003EReference @(Rows.IndexOf(row)\u002B1)\u003C/div\u003E\u003Cdiv class=\u0022optiondescription\u0022\u003E@row.Status\u003C/div\u003E\u003Cbutton class=@($\u0022creatorbutton quiet remove-photo-{Rows.IndexOf(row)}\u0022) onclick=@(()=\u003ERemove(row))\u003ERemove\u003C/button\u003E\u003C/div\u003E\r\n\r\n  \u003C/div\u003E\r\n\r\n }\r\n\r\n \u003Cdiv class=\u0022optiondescription\u0022\u003E@(Rows.Any(x=\u003Ex.Measurement?.FrontUsable==true)?\u0022Front widths: covered.\u0022:\u0022Add a front view for face widths.\u0022) @(Rows.Any(x=\u003Ex.Measurement?.Yaw\u003E=45)?\u0022Profile depth: covered.\u0022:Rows.Any(x=\u003Ex.Measurement?.ProfileUsable==true)?\u0022Some depth from angled views; a clearer side view helps.\u0022:\u0022Add an angled or side view for nose and chin depth.\u0022)\u003C/div\u003E\r\n\r\n \u003Cdiv class=\u0022fieldlabel\u0022\u003EStarting heads\u003C/div\u003E\r\n \u003Cdiv class=\u0022optiondescription\u0022\u003EChoose the foundation to keep while fitting. Selected skin uses that skin\u0027s original head family; it does not guess from the photos. Current blend preserves your chosen balance of male and female heads.\u003C/div\u003E\r\n @foreach(var choice in new[]{(\u0022skin\u0022,\u0022Selected skin\u0022),(\u0022female\u0022,\u0022Female heads\u0022),(\u0022male\u0022,\u0022Male heads\u0022),(\u0022current\u0022,\u0022Current blend\u0022)})\r\n {\r\n  \u003Cbutton class=@($\u0022creatorbutton {(Basis==choice.Item1?\u0022primary\u0022:\u0022quiet\u0022)}\u0022) onclick=@(()=\u003EBasis=choice.Item1)\u003E@choice.Item2\u003C/button\u003E\r\n }\r\n \u003Cdiv class=\u0022fieldlabel\u0022\u003EName your base face\u003C/div\u003E\r\n \u003CTextEntry class=\u0022photo-name\u0022 Value=@FaceName OnTextEdited=@((string value)=\u003E{FaceName=value;}) MaxLength=@(40) Placeholder=\u0022My photo face\u0022 /\u003E\r\n \u003Cbutton class=\u0022creatorbutton primary generate-photo-face\u0022 onclick=@Generate\u003EGenerate base face\u003C/button\u003E\r\n\r\n \u003Cdiv class=\u0022optiondescription\u0022\u003EGeneration 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.\u003C/div\u003E\r\n\u003C/root\u003E\r\n\r\n@code {\r\n\tpublic CharacterAppearanceData Appearance { get; set; }\r\n\tpublic Action\u003CPhotoFace\u003E 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\u003CReference\u003E Rows = new();\r\n\tstring Url = \u0022\u0022, FaceName = \u0022My photo face\u0022, Message;\r\n\tstring Basis = \u0022skin\u0022;\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 \u003E= 5)\r\n\t\t{\r\n\t\t\tMessage = \u0022You can use up to five references. Remove one to add another.\u0022;\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 = \u0022Enter a direct HTTPS link to a JPEG, PNG or WebP image.\u0022;\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif (Rows.Any(x =\u003E x.Url == value))\r\n\t\t{\r\n\t\t\tMessage = \u0022That reference is already in the list.\u0022;\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tRows.Add(new() { Url = value, Status = \u0022Waiting\u2026\u0022 });\r\n\t\tUrl = \u0022\u0022;\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 = \u0022Waiting\u2026\u0022;\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 =\u003E x.Status == \u0022Waiting\u2026\u0022);\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 = \u0022Ready \u00B7 \u0022 \u002B 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 =\u003E x.Status == \u0022Waiting\u2026\u0022))\r\n\t\t{\r\n\t\t\tMessage = \u0022Wait for the references to finish loading first.\u0022;\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar good = Rows.Where(x =\u003E x.Measurement is not null).ToArray();\r\n\t\tif (good.Length == 0)\r\n\t\t{\r\n\t\t\tMessage = \u0022Load at least one usable reference first.\u0022;\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 = \u0022Remove the failed references or replace them before generating.\u0022;\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif (Appearance.BaseShape?.PhotoFaces.Count \u003E= 4)\r\n\t\t{\r\n\t\t\tMessage = \u0022You can keep four custom faces in a character. Remove one in Face \u0026 body first.\u0022;\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 =\u003E 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() =\u003E HashCode.Combine(Url, FaceName, Message, Basis, string.Join(\u0022|\u0022, Rows.Select(x =\u003E x.Url \u002B 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/// \u003Csummary\u003EHead albedos with only the lower-neck band matched to the body atlas.\u003C/summary\u003E\npublic static class UnifiedNeckAssets\r\n{\r\n\tstatic readonly Dictionary\u003Cstring, Material\u003E 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($\u0022materials/makeup/neck/{id}.vmat\u0022);\r\n\t\tvar texture = material?.GetTexture(\u0022g_tColor\u0022);\r\n\t\tif (texture is null || texture.IsError)\r\n\t\t\tthrow new InvalidOperationException(\u0022The matched neck texture is unavailable: \u0022 \u002B 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) =\u003E carriers[System.IO.Path.GetFileNameWithoutExtension(skin)].GetTexture(\u0022g_tColor\u0022);\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\u003CWardrobeItem\u003E Items=\u003Eentries??=CreateEntries();\r\n static WardrobeItem[] entries;\r\n static WardrobeItem[] CreateEntries()=\u003Enew WardrobeItem[]{\r\n  new(\u0022Denim Skirt Black\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/dress/denim_skirt/denim_skirt_black.clothing\u0022,\u0022LeftThigh, RightThigh, LeftKnee, RightKnee\u0022,\u0022\u0022,\u0022\u0022,\u0022Skirt\u0022),\r\n  new(\u0022Denim Skirt Blue\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/dress/denim_skirt/denim_skirt_blue.clothing\u0022,\u0022LeftThigh, RightThigh, LeftKnee, RightKnee\u0022,\u0022\u0022,\u0022\u0022,\u0022Skirt\u0022),\r\n  new(\u0022Denim Skirt Green\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/dress/denim_skirt/denim_skirt_green.clothing\u0022,\u0022LeftThigh, RightThigh, LeftKnee, RightKnee\u0022,\u0022\u0022,\u0022\u0022,\u0022Skirt\u0022),\r\n  new(\u0022Denim Skirt Leapard\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/dress/denim_skirt/denim_skirt_leapard.clothing\u0022,\u0022LeftThigh, RightThigh, LeftKnee, RightKnee\u0022,\u0022\u0022,\u0022\u0022,\u0022Skirt\u0022),\r\n  new(\u0022Dotted Dress\u0022,\u0022Outfits\u0022,\u0022Outfits\u0022,\u0022models/citizen_clothes/dress/dotted_dress/dotted_dress.clothing\u0022,\u0022Chest, Groin, LeftThigh, RightThigh\u0022,\u0022\u0022,\u0022Chest\u0022,\u0022Dress\u0022),\r\n  new(\u0022Grand Ball Gown\u0022,\u0022Outfits\u0022,\u0022Outfits\u0022,\u0022models/citizen_clothes/dress/posh_dress/models/posh_dress.clothing\u0022,\u0022Chest, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022Chest, Legs\u0022,\u0022Dress\u0022),\r\n  new(\u0022Simple Dress\u0022,\u0022Outfits\u0022,\u0022Outfits\u0022,\u0022models/citizen_clothes/dress/simple_dress/simple_dress.clothing\u0022,\u0022Chest, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022Chest\u0022,\u0022Dress\u0022),\r\n  new(\u0022Simple Dress Black\u0022,\u0022Outfits\u0022,\u0022Outfits\u0022,\u0022models/citizen_clothes/dress/simple_dress/simple_dress_black.clothing\u0022,\u0022Chest, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022Chest\u0022,\u0022Dress\u0022),\r\n  new(\u0022Simple Dress Blue\u0022,\u0022Outfits\u0022,\u0022Outfits\u0022,\u0022models/citizen_clothes/dress/simple_dress/simple_dress_blue.clothing\u0022,\u0022Chest, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022Chest\u0022,\u0022Dress\u0022),\r\n  new(\u0022Standard Skirt\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/dress/standard_skirt/standard_skirt.clothing\u0022,\u0022LeftThigh, RightThigh, LeftKnee, RightKnee\u0022,\u0022\u0022,\u0022\u0022,\u0022Skirt\u0022),\r\n  new(\u0022Pirate Outfit\u0022,\u0022Outfits\u0022,\u0022Outfits\u0022,\u0022models/citizen_clothes/full_outfit/pirate_outfit/pirate_outfit.clothing\u0022,\u0022Chest, LeftArm, RightArm, LeftWrist, RightWrist, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot\u0022,\u0022Chest, LeftArm, RightArm, LeftWrist, RightWrist, LeftHand, RightHand, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot, Waist\u0022,\u0022Chest, Legs, Feet\u0022,\u0022Costume\u0022),\r\n  new(\u0022Nerdy Glasses\u0022,\u0022Glasses\u0022,\u0022Glasses\u0022,\u0022models/citizen_clothes/glasses/nerdy_glasses/nerdy_glasses.clothing\u0022,\u0022Glasses\u0022,\u0022\u0022,\u0022\u0022,\u0022GlassesEye\u0022),\r\n  new(\u0022Party Glasses\u0022,\u0022Glasses\u0022,\u0022Glasses\u0022,\u0022models/citizen_clothes/glasses/party_glasses/party_glasses.clothing\u0022,\u0022Glasses\u0022,\u0022\u0022,\u0022\u0022,\u0022GlassesSun\u0022),\r\n  new(\u0022Boxing Gloves\u0022,\u0022Gloves\u0022,\u0022Gloves\u0022,\u0022models/citizen_clothes/gloves/boxing_gloves/boxing_gloves.clothing\u0022,\u0022LeftHand, RightHand\u0022,\u0022\u0022,\u0022Hands\u0022,\u0022Gloves\u0022),\r\n  new(\u0022Boxing Gloves Blue\u0022,\u0022Gloves\u0022,\u0022Gloves\u0022,\u0022models/citizen_clothes/gloves/boxing_gloves/boxing_gloves_blue.clothing\u0022,\u0022LeftHand, RightHand\u0022,\u0022\u0022,\u0022Hands\u0022,\u0022Gloves\u0022),\r\n  new(\u0022Boxing Gloves Red\u0022,\u0022Gloves\u0022,\u0022Gloves\u0022,\u0022models/citizen_clothes/gloves/boxing_gloves/boxing_gloves_red.clothing\u0022,\u0022LeftHand, RightHand\u0022,\u0022\u0022,\u0022Hands\u0022,\u0022Gloves\u0022),\r\n  new(\u0022Fingerless Gloves\u0022,\u0022Gloves\u0022,\u0022Gloves\u0022,\u0022models/citizen_clothes/gloves/fingerless_gloves/fingerless_gloves.clothing\u0022,\u0022LeftHand, RightHand\u0022,\u0022\u0022,\u0022Hands\u0022,\u0022Gloves\u0022),\r\n  new(\u0022Goalie Gloves\u0022,\u0022Gloves\u0022,\u0022Gloves\u0022,\u0022models/citizen_clothes/gloves/goalie_gloves/goalie_gloves.clothing\u0022,\u0022LeftHand, RightHand\u0022,\u0022\u0022,\u0022Hands\u0022,\u0022Gloves\u0022),\r\n  new(\u0022Blue Rubber Gloves\u0022,\u0022Gloves\u0022,\u0022Gloves\u0022,\u0022models/citizen_clothes/gloves/rubber_gloves/rubber_gloves.clothing\u0022,\u0022LeftHand, RightHand\u0022,\u0022\u0022,\u0022Hands\u0022,\u0022Gloves\u0022),\r\n  new(\u0022Black Rubber Gloves\u0022,\u0022Gloves\u0022,\u0022Gloves\u0022,\u0022models/citizen_clothes/gloves/rubber_gloves/rubber_gloves_black.clothing\u0022,\u0022LeftHand, RightHand\u0022,\u0022\u0022,\u0022Hands\u0022,\u0022Gloves\u0022),\r\n  new(\u0022Mask\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/balaclava/balaclava.clothing\u0022,\u0022HeadTop, HeadBottom\u0022,\u0022\u0022,\u0022\u0022,\u0022HatBeanie\u0022),\r\n  new(\u0022Bandana Mask\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/bandana_mask/bandana_mask.clothing\u0022,\u0022\u0022,\u0022\u0022,\u0022\u0022,\u0022HeadSpecial\u0022),\r\n  new(\u0022Baseball Cap\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/baseball_cap/baseball_cap.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022Hat\u0022),\r\n  new(\u0022Baseball Cap Black\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/baseball_cap/baseball_cap_black.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022Hat\u0022),\r\n  new(\u0022Baseball Cap Blue White\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/baseball_cap/baseball_cap_blue_white.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022Hat\u0022),\r\n  new(\u0022Baseball Cap Brown\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/baseball_cap/baseball_cap_brown.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022Hat\u0022),\r\n  new(\u0022Beanie\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/beanie/beanie.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatBeanie\u0022),\r\n  new(\u0022Beanie Green\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/beanie/beanie_green.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatBeanie\u0022),\r\n  new(\u0022Beanie Red\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/beanie/beanie_red.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatBeanie\u0022),\r\n  new(\u0022Boxing Headgear\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/boxing_headgear/boxing_headgear.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatCostume\u0022),\r\n  new(\u0022Boxing Headgear Blue\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/boxing_headgear/boxing_headgear_blue.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatCostume\u0022),\r\n  new(\u0022Boxing Headgear Red\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/boxing_headgear/boxing_headgear_red.clothing\u0022,\u0022HeadTop, HeadBottom\u0022,\u0022\u0022,\u0022\u0022,\u0022HatCostume\u0022),\r\n  new(\u0022Bucket Hat\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/bucket_hat/bucket_hat.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022Hat\u0022),\r\n  new(\u0022Bucket Hat Black\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/bucket_hat/bucket_hat_black.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022Hat\u0022),\r\n  new(\u0022Chef\u0027s Hat\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/chefs_hat/chefs_hat.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatUniform\u0022),\r\n  new(\u0022Cowboy Hat\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/cowboy_hat/cowboy_hat.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatCostume\u0022),\r\n  new(\u0022Fedora\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/fedora/fedora.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatFormal\u0022),\r\n  new(\u0022Fedora Brown\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/fedora/fedora_brown.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatFormal\u0022),\r\n  new(\u0022Flatcap\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/flatcap/flatcap.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022Hat\u0022),\r\n  new(\u0022Gasmask\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/gasmask/gasmask.clothing\u0022,\u0022HeadBottom\u0022,\u0022\u0022,\u0022\u0022,\u0022HatUniform\u0022),\r\n  new(\u0022Hair Net\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/hair_net/hair_net.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatUniform\u0022),\r\n  new(\u0022Hair Net White\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/hair_net/hair_net_white.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatUniform\u0022),\r\n  new(\u0022Hard Hat\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/hard_hat/hard_hat.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatUniform\u0022),\r\n  new(\u0022Headphones\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/headphones/models/headphones.clothing\u0022,\u0022\u0022,\u0022\u0022,\u0022\u0022,\u0022HeadTech\u0022),\r\n  new(\u0022Police Hat\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/police_hat/police_hat.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatUniform\u0022),\r\n  new(\u0022Racer Helmet\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/racer_helmet/racer_helmet.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatCostume\u0022),\r\n  new(\u0022Scifi Trooper Helmet\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/scifi_trooper_helmet/scifi_trooper_helmet.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatCostume\u0022),\r\n  new(\u0022Skater Helmet\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/skater_helmet/skater_helmet.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatUniform\u0022),\r\n  new(\u0022Skater Helmet Camo\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/skater_helmet/skater_helmet_camo.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatUniform\u0022),\r\n  new(\u0022Skater Helmet White\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/skater_helmet/skater_helmet_white.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatUniform\u0022),\r\n  new(\u0022Tactical Helmet\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/tactical_helmet/tactical_helmet.clothing\u0022,\u0022HeadTop, Glasses\u0022,\u0022\u0022,\u0022\u0022,\u0022HatUniform\u0022),\r\n  new(\u0022Army Helmet\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/tactical_helmet/tactical_helmet_army.clothing\u0022,\u0022HeadTop, Glasses\u0022,\u0022\u0022,\u0022\u0022,\u0022HatUniform\u0022),\r\n  new(\u0022Trapper Hat\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/trapper_hat/trapper_hat.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatBeanie\u0022),\r\n  new(\u0022Trapper Hat Cloth\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/trapper_hat/trapper_hat_cloth.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatBeanie\u0022),\r\n  new(\u0022War Helmet\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/hat/war_helmet/war_helmet.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatUniform\u0022),\r\n  new(\u0022Biker Jacket\u0022,\u0022Outerwear\u0022,\u0022Outerwear\u0022,\u0022models/citizen_clothes/jacket/biker_jacket/biker_jacket.clothing\u0022,\u0022\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022Jacket\u0022),\r\n  new(\u0022Bomber Jacket\u0022,\u0022Outerwear\u0022,\u0022Outerwear\u0022,\u0022models/citizen_clothes/jacket/bomber_jacket/bomber_jacket.clothing\u0022,\u0022\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022Jacket\u0022),\r\n  new(\u0022Black Leather Jacket\u0022,\u0022Outerwear\u0022,\u0022Outerwear\u0022,\u0022models/citizen_clothes/jacket/brown_leather_jacket/black_leather_jacket.clothing\u0022,\u0022\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022Jacket\u0022),\r\n  new(\u0022Brown Leather Jacket\u0022,\u0022Outerwear\u0022,\u0022Outerwear\u0022,\u0022models/citizen_clothes/jacket/brown_leather_jacket/brown_leather_jacket.clothing\u0022,\u0022\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022Jacket\u0022),\r\n  new(\u0022Hoodie\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/jacket/hoodie/hoodie.clothing\u0022,\u0022Chest\u0022,\u0022LeftArm, RightArm\u0022,\u0022Chest\u0022,\u0022Hoodie\u0022),\r\n  new(\u0022Black Hoodie\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/jacket/hoodie/hoodie_black.clothing\u0022,\u0022LeftArm, RightArm, LeftWrist, RightWrist\u0022,\u0022Chest\u0022,\u0022Chest\u0022,\u0022Hoodie\u0022),\r\n  new(\u0022Lab Coat\u0022,\u0022Outerwear\u0022,\u0022Outerwear\u0022,\u0022models/citizen_clothes/jacket/labcoat/labcoat.clothing\u0022,\u0022\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022Coat\u0022),\r\n  new(\u0022Puffer Jacket\u0022,\u0022Outerwear\u0022,\u0022Outerwear\u0022,\u0022models/citizen_clothes/jacket/puffer_jacket/puffer_jacket.clothing\u0022,\u0022\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022Jacket\u0022),\r\n  new(\u0022Puffer Jacket Blue\u0022,\u0022Outerwear\u0022,\u0022Outerwear\u0022,\u0022models/citizen_clothes/jacket/puffer_jacket/puffer_jacket_blue.clothing\u0022,\u0022\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022Jacket\u0022),\r\n  new(\u0022Puffer Jacket Orange\u0022,\u0022Outerwear\u0022,\u0022Outerwear\u0022,\u0022models/citizen_clothes/jacket/puffer_jacket/puffer_jacket_orange.clothing\u0022,\u0022\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022Jacket\u0022),\r\n  new(\u0022Rain Coat\u0022,\u0022Outerwear\u0022,\u0022Outerwear\u0022,\u0022models/citizen_clothes/jacket/raincoat/raincoat.clothing\u0022,\u0022\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022Coat\u0022),\r\n  new(\u0022Sleeveless Jack\u0022,\u0022Outerwear\u0022,\u0022Outerwear\u0022,\u0022models/citizen_clothes/jacket/sleeveless_jacket/sleeveless_jacket.clothing\u0022,\u0022\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022Gilet\u0022),\r\n  new(\u0022Wind Breaker\u0022,\u0022Outerwear\u0022,\u0022Outerwear\u0022,\u0022models/citizen_clothes/jacket/wind_breaker/wind_breaker.clothing\u0022,\u0022\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022Coat\u0022),\r\n  new(\u0022Winter Coat\u0022,\u0022Outerwear\u0022,\u0022Outerwear\u0022,\u0022models/citizen_clothes/jacket/winter_coat/winter_coat.clothing\u0022,\u0022\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022Coat\u0022),\r\n  new(\u0022Cardigan\u0022,\u0022Outerwear\u0022,\u0022Outerwear\u0022,\u0022models/citizen_clothes/jumper/cardigan/cardigan.clothing\u0022,\u0022\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022Cardigan\u0022),\r\n  new(\u0022Cardigan Pink\u0022,\u0022Outerwear\u0022,\u0022Outerwear\u0022,\u0022models/citizen_clothes/jumper/cardigan/cardigan_colored.clothing\u0022,\u0022\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022Cardigan\u0022),\r\n  new(\u0022Cardigan Dark\u0022,\u0022Outerwear\u0022,\u0022Outerwear\u0022,\u0022models/citizen_clothes/jumper/cardigan/cardigan_dark.clothing\u0022,\u0022\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022Cardigan\u0022),\r\n  new(\u0022Cardigan Dirty\u0022,\u0022Outerwear\u0022,\u0022Outerwear\u0022,\u0022models/citizen_clothes/jumper/cardigan/cardigan_dirty.clothing\u0022,\u0022\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022Cardigan\u0022),\r\n  new(\u0022Loose Jumper\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/jumper/loose_jumper/loose_jumper.clothing\u0022,\u0022Chest\u0022,\u0022LeftArm, RightArm\u0022,\u0022Chest\u0022,\u0022Knitwear\u0022),\r\n  new(\u0022Goalie Jumper\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/jumper/loose_jumper/loose_jumper_goalie.clothing\u0022,\u0022Chest\u0022,\u0022LeftArm, RightArm\u0022,\u0022Chest\u0022,\u0022Knitwear\u0022),\r\n  new(\u0022Loose Jumper Wooly\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/jumper/loose_jumper/loose_jumper_wooly.clothing\u0022,\u0022Chest\u0022,\u0022LeftArm, RightArm\u0022,\u0022Chest\u0022,\u0022Knitwear\u0022),\r\n  new(\u0022Sleeveless Jumper\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/jumper/sleeveless_jumper/sleeveless_jumper.clothing\u0022,\u0022\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022Knitwear\u0022),\r\n  new(\u0022Sleeveless Jumper Grey\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/jumper/sleeveless_jumper/sleeveless_jumper_grey.clothing\u0022,\u0022\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022Knitwear\u0022),\r\n  new(\u0022Sleeveless Jumper Red\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/jumper/sleeveless_jumper/sleeveless_jumper_red.clothing\u0022,\u0022\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022Knitwear\u0022),\r\n  new(\u0022Pearl Necklace\u0022,\u0022Accessories\u0022,\u0022necklace\u0022,\u0022models/citizen_clothes/necklace/pearl_necklace/pearl_necklace.clothing\u0022,\u0022Chin\u0022,\u0022\u0022,\u0022\u0022,\u0022NecklacePendant\u0022),\r\n  new(\u0022Vintage Necklace\u0022,\u0022Accessories\u0022,\u0022necklace\u0022,\u0022models/citizen_clothes/necklace/vintage_necklace/vintage_necklace.clothing\u0022,\u0022Chin\u0022,\u0022\u0022,\u0022\u0022,\u0022NecklacePendant\u0022),\r\n  new(\u0022Santa Hat 2024\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/seasonal/2024/santa/santahat.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatCostume\u0022),\r\n  new(\u0022Santa Jacket 2024\u0022,\u0022Outerwear\u0022,\u0022Outerwear\u0022,\u0022models/citizen_clothes/seasonal/2024/santa/santajacket.clothing\u0022,\u0022LeftArm, RightArm\u0022,\u0022LeftArm, RightArm\u0022,\u0022Chest\u0022,\u0022Coat\u0022),\r\n  new(\u0022Santa Trousers 2024\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/seasonal/2024/santa/santatrousers.clothing\u0022,\u0022Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot\u0022,\u0022Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot\u0022,\u0022Legs, Feet\u0022,\u0022Trousers\u0022),\r\n  new(\u0022Easter Bonnet\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/seasonal/2025/easter_farmer/easter_bonnet/easter_bonnet.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatCostume\u0022),\r\n  new(\u0022Easter Dungarees\u0022,\u0022Outfits\u0022,\u0022Outfits\u0022,\u0022models/citizen_clothes/seasonal/2025/easter_farmer/easter_dungarees/models/easter_dungarees.clothing\u0022,\u0022Chest, LeftArm, RightArm, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022Chest, LeftArm, RightArm, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022Chest, Legs\u0022,\u0022Suit\u0022),\r\n  new(\u0022Easter Slippers\u0022,\u0022Shoes\u0022,\u0022Shoes\u0022,\u0022models/citizen_clothes/seasonal/2025/easter_farmer/easter_slippers/models/easter_slippers.clothing\u0022,\u0022LeftFoot, RightFoot\u0022,\u0022\u0022,\u0022Feet\u0022,\u0022Footwear\u0022),\r\n  new(\u0022Valentines Bear Head 2025\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/seasonal/2025/valentines_bear/bear_head.clothing\u0022,\u0022HeadTop, HeadBottom\u0022,\u0022HeadTop, HeadBottom\u0022,\u0022Head\u0022,\u0022HatCostume\u0022),\r\n  new(\u0022Valentines Bear Top 2025\u0022,\u0022Outfits\u0022,\u0022Outfits\u0022,\u0022models/citizen_clothes/seasonal/2025/valentines_bear/bear_top.clothing\u0022,\u0022Chest, LeftArm, RightArm, LeftWrist, RightWrist, LeftHand, RightHand\u0022,\u0022LeftArm, RightArm, LeftWrist, RightWrist, LeftHand, RightHand\u0022,\u0022Chest, Hands\u0022,\u0022Costume\u0022),\r\n  new(\u0022Valentines Bear Trousers 2025\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/seasonal/2025/valentines_bear/bear_trousers.clothing\u0022,\u0022Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot\u0022,\u0022Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot\u0022,\u0022Legs, Feet\u0022,\u0022Trousers\u0022),\r\n  new(\u0022Wizard Beard\u0022,\u0022Accessories\u0022,\u0022costume_beard\u0022,\u0022models/citizen_clothes/seasonal/2025/wizard_outfit/wizard_beard/wizard_beard.clothing\u0022,\u0022Face\u0022,\u0022\u0022,\u0022\u0022,\u0022FacialHairBeard\u0022),\r\n  new(\u0022Wizard Gown\u0022,\u0022Outerwear\u0022,\u0022Outerwear\u0022,\u0022models/citizen_clothes/seasonal/2025/wizard_outfit/wizard_gown/wizard_gown.clothing\u0022,\u0022Chest, LeftArm, RightArm\u0022,\u0022Chest, LeftArm, RightArm, LeftWrist\u0022,\u0022Chest\u0022,\u0022Coat\u0022),\r\n  new(\u0022Wizard Hat\u0022,\u0022Hats\u0022,\u0022Hats\u0022,\u0022models/citizen_clothes/seasonal/2025/wizard_outfit/wizard_hat/wizard_hat.clothing\u0022,\u0022HeadTop\u0022,\u0022\u0022,\u0022\u0022,\u0022HatCostume\u0022),\r\n  new(\u0022Wizard Trousers\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/seasonal/2025/wizard_outfit/wizard_trousers/wizard_trousers.clothing\u0022,\u0022Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot\u0022,\u0022\u0022,\u0022Legs, Feet\u0022,\u0022Trousers\u0022),\r\n  new(\u0022Army Shirt\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/shirt/army_shirt/army_shirt.clothing\u0022,\u0022Chest, LeftArm, RightArm\u0022,\u0022\u0022,\u0022Chest\u0022,\u0022Shirt\u0022),\r\n  new(\u0022Chef Shirt\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/shirt/chef_shirt/chef_shirt.clothing\u0022,\u0022Chest, LeftArm, RightArm\u0022,\u0022\u0022,\u0022Chest\u0022,\u0022Shirt\u0022),\r\n  new(\u0022Fisherman Jumpsuit\u0022,\u0022Outfits\u0022,\u0022Outfits\u0022,\u0022models/citizen_clothes/shirt/fisherman_jumpsuit/fisherman_jumpsuit.clothing\u0022,\u0022Chest, LeftArm, RightArm, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin, LeftFoot, RightFoot\u0022,\u0022\u0022,\u0022Chest, Legs, Feet\u0022,\u0022Suit\u0022),\r\n  new(\u0022Flannel Shirt\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/shirt/flannel_shirt/flannel_shirt.clothing\u0022,\u0022Chest, LeftArm, RightArm\u0022,\u0022\u0022,\u0022Chest\u0022,\u0022Shirt\u0022),\r\n  new(\u0022Blue Shirt\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/shirt/flannel_shirt/variations/blue_shirt/blue_shirt.clothing\u0022,\u0022Chest, LeftArm, RightArm\u0022,\u0022\u0022,\u0022Chest\u0022,\u0022Shirt\u0022),\r\n  new(\u0022Hawaiian Shirt\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/shirt/hawaiian_shirt/hawaiian shirt.clothing\u0022,\u0022Chest, LeftArm, RightArm\u0022,\u0022\u0022,\u0022Chest\u0022,\u0022Shirt\u0022),\r\n  new(\u0022Blue Jumpsuit\u0022,\u0022Outfits\u0022,\u0022Outfits\u0022,\u0022models/citizen_clothes/shirt/jumpsuit/blue_jumpsuit.clothing\u0022,\u0022Chest, LeftArm, RightArm, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022Chest, Legs\u0022,\u0022Suit\u0022),\r\n  new(\u0022Prison Jumpsuit\u0022,\u0022Outfits\u0022,\u0022Outfits\u0022,\u0022models/citizen_clothes/shirt/jumpsuit/prison_jumpsuit.clothing\u0022,\u0022Chest, LeftArm, RightArm, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022Chest, Legs\u0022,\u0022Suit\u0022),\r\n  new(\u0022Leotard\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/shirt/leotard/leotard.clothing\u0022,\u0022Chest, LeftArm, RightArm, LeftWrist, RightWrist, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022\u0022,\u0022TShirt\u0022),\r\n  new(\u0022Leotard Blue\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/shirt/leotard/leotard_blue.clothing\u0022,\u0022Chest, LeftArm, RightArm, LeftWrist, RightWrist, Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022\u0022,\u0022Shirt\u0022),\r\n  new(\u0022Loose Crop Top\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/shirt/loose_crop_top/loose_crop_top.clothing\u0022,\u0022Chest, LeftArm, RightArm\u0022,\u0022\u0022,\u0022Chest\u0022,\u0022Shirt\u0022),\r\n  new(\u0022Loose Crop Top Pattern\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/shirt/loose_crop_top/loose_crop_top_pattern.clothing\u0022,\u0022Chest, LeftArm, RightArm\u0022,\u0022\u0022,\u0022Chest\u0022,\u0022Shirt\u0022),\r\n  new(\u0022Loose Crop Top Purple\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/shirt/loose_crop_top/loose_crop_top_purple.clothing\u0022,\u0022Chest, LeftArm, RightArm\u0022,\u0022\u0022,\u0022Chest\u0022,\u0022Shirt\u0022),\r\n  new(\u0022Loose Crop Top White\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/shirt/loose_crop_top/loose_crop_top_white.clothing\u0022,\u0022Chest, LeftArm, RightArm\u0022,\u0022\u0022,\u0022Chest\u0022,\u0022Shirt\u0022),\r\n  new(\u0022Loose Shirt\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/shirt/loose_shirt/loose_shirt.clothing\u0022,\u0022Chest, LeftArm, RightArm, LeftWrist, RightWrist\u0022,\u0022\u0022,\u0022Chest\u0022,\u0022TShirt\u0022),\r\n  new(\u0022Polo Shirt\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/shirt/polo_shirt/polo_shirt.clothing\u0022,\u0022Chest, LeftArm, RightArm\u0022,\u0022\u0022,\u0022Chest\u0022,\u0022TShirt\u0022),\r\n  new(\u0022Polo Shirt White\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/shirt/polo_shirt/polo_shirt_white.clothing\u0022,\u0022Chest, LeftArm, RightArm\u0022,\u0022\u0022,\u0022Chest\u0022,\u0022TShirt\u0022),\r\n  new(\u0022Tank Top \u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/shirt/tanktop/tanktop.clothing\u0022,\u0022Chest\u0022,\u0022\u0022,\u0022\u0022,\u0022TShirt\u0022),\r\n  new(\u0022Football Tshirt\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/shirt/tshirt/football_tshirt.clothing\u0022,\u0022Chest, LeftArm, RightArm\u0022,\u0022\u0022,\u0022Chest\u0022,\u0022TShirt\u0022),\r\n  new(\u0022Tshirt\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/shirt/tshirt/tshirt.clothing\u0022,\u0022Chest, LeftArm, RightArm\u0022,\u0022\u0022,\u0022Chest\u0022,\u0022Tops\u0022),\r\n  new(\u0022Waistcoat and Shirt\u0022,\u0022Tops\u0022,\u0022Tops\u0022,\u0022models/citizen_clothes/shirt/waistcoat_and_shirt/waistcoat_and_shirt.clothing\u0022,\u0022Chest, LeftArm, RightArm\u0022,\u0022\u0022,\u0022Chest\u0022,\u0022Shirt\u0022),\r\n  new(\u0022Boot Heels Black\u0022,\u0022Shoes\u0022,\u0022Shoes\u0022,\u0022models/citizen_clothes/shoes/boot_heels/boot_heels_black.clothing\u0022,\u0022LeftFoot, RightFoot\u0022,\u0022\u0022,\u0022Feet\u0022,\u0022Heels\u0022),\r\n  new(\u0022Boot Heels Brown\u0022,\u0022Shoes\u0022,\u0022Shoes\u0022,\u0022models/citizen_clothes/shoes/boot_heels/boot_heels_brown.clothing\u0022,\u0022LeftFoot, RightFoot\u0022,\u0022\u0022,\u0022Feet\u0022,\u0022Heels\u0022),\r\n  new(\u0022Boot Heels Leopard\u0022,\u0022Shoes\u0022,\u0022Shoes\u0022,\u0022models/citizen_clothes/shoes/boot_heels/boot_heels_leopard.clothing\u0022,\u0022LeftFoot, RightFoot\u0022,\u0022\u0022,\u0022Feet\u0022,\u0022Heels\u0022),\r\n  new(\u0022Boot Heels Red\u0022,\u0022Shoes\u0022,\u0022Shoes\u0022,\u0022models/citizen_clothes/shoes/boot_heels/boot_heels_red.clothing\u0022,\u0022LeftFoot, RightFoot\u0022,\u0022\u0022,\u0022Feet\u0022,\u0022Heels\u0022),\r\n  new(\u0022Army Boots\u0022,\u0022Shoes\u0022,\u0022Shoes\u0022,\u0022models/citizen_clothes/shoes/boots/army_boots.clothing\u0022,\u0022LeftFoot, RightFoot\u0022,\u0022\u0022,\u0022Feet\u0022,\u0022Boots\u0022),\r\n  new(\u0022Black Boots\u0022,\u0022Shoes\u0022,\u0022Shoes\u0022,\u0022models/citizen_clothes/shoes/boots/black_boots.clothing\u0022,\u0022LeftFoot, RightFoot\u0022,\u0022\u0022,\u0022Feet\u0022,\u0022Boots\u0022),\r\n  new(\u0022Boxing Boots\u0022,\u0022Shoes\u0022,\u0022Shoes\u0022,\u0022models/citizen_clothes/shoes/boxing_boots/boxing_boots.clothing\u0022,\u0022LeftFoot, RightFoot\u0022,\u0022\u0022,\u0022Feet\u0022,\u0022Boots\u0022),\r\n  new(\u0022Boxing Boots Blue\u0022,\u0022Shoes\u0022,\u0022Shoes\u0022,\u0022models/citizen_clothes/shoes/boxing_boots/boxing_boots_blue.clothing\u0022,\u0022LeftFoot, RightFoot\u0022,\u0022\u0022,\u0022Feet\u0022,\u0022Boots\u0022),\r\n  new(\u0022Clogs and Socks\u0022,\u0022Shoes\u0022,\u0022Shoes\u0022,\u0022models/citizen_clothes/shoes/crocs_and_socks/clogs_and_socks.clothing\u0022,\u0022LeftFoot, RightFoot\u0022,\u0022\u0022,\u0022Feet\u0022,\u0022Slippers\u0022),\r\n  new(\u0022Flat Shoes\u0022,\u0022Shoes\u0022,\u0022Shoes\u0022,\u0022models/citizen_clothes/shoes/flatshoes/textures/flat_shoes.clothing\u0022,\u0022LeftFoot, RightFoot\u0022,\u0022\u0022,\u0022\u0022,\u0022Shoes\u0022),\r\n  new(\u0022Football Shoes\u0022,\u0022Shoes\u0022,\u0022Shoes\u0022,\u0022models/citizen_clothes/shoes/football_shoes/football_shoes.clothing\u0022,\u0022LeftFoot, RightFoot\u0022,\u0022\u0022,\u0022Feet\u0022,\u0022Trainers\u0022),\r\n  new(\u0022Slippers\u0022,\u0022Shoes\u0022,\u0022Shoes\u0022,\u0022models/citizen_clothes/shoes/slippers/slippers.clothing\u0022,\u0022LeftFoot, RightFoot\u0022,\u0022\u0022,\u0022\u0022,\u0022Slippers\u0022),\r\n  new(\u0022Blue Sneakers\u0022,\u0022Shoes\u0022,\u0022Shoes\u0022,\u0022models/citizen_clothes/shoes/sneakers/blue_sneakers.clothing\u0022,\u0022LeftFoot, RightFoot\u0022,\u0022\u0022,\u0022Feet\u0022,\u0022Shoes\u0022),\r\n  new(\u0022Sneakers\u0022,\u0022Shoes\u0022,\u0022Shoes\u0022,\u0022models/citizen_clothes/shoes/sneakers/sneakers.clothing\u0022,\u0022LeftFoot, RightFoot\u0022,\u0022\u0022,\u0022Feet\u0022,\u0022Shoes\u0022),\r\n  new(\u0022Trainers\u0022,\u0022Shoes\u0022,\u0022Shoes\u0022,\u0022models/citizen_clothes/shoes/trainers/trainers.clothing\u0022,\u0022LeftFoot, RightFoot\u0022,\u0022\u0022,\u0022Feet\u0022,\u0022Trainers\u0022),\r\n  new(\u0022Boxing Shorts\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/shorts/boxing_shorts/boxing_shorts.clothing\u0022,\u0022Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022Legs\u0022,\u0022Shorts\u0022),\r\n  new(\u0022Boxing Shorts Blue\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/shorts/boxing_shorts/boxing_shorts_blue.clothing\u0022,\u0022Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022Legs\u0022,\u0022Shorts\u0022),\r\n  new(\u0022Boxing Shorts Red\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/shorts/boxing_shorts/boxing_shorts_red.clothing\u0022,\u0022Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022Legs\u0022,\u0022Shorts\u0022),\r\n  new(\u0022Football Shorts\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/shorts/football_shorts/football_shorts.clothing\u0022,\u0022Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022Legs\u0022,\u0022Shorts\u0022),\r\n  new(\u0022Shorts\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/shorts/shorts/shorts.clothing\u0022,\u0022Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022Legs\u0022,\u0022Shorts\u0022),\r\n  new(\u0022Summer Shorts\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/shorts/summer_shorts/summer shorts.clothing\u0022,\u0022LeftThigh, RightThigh\u0022,\u0022\u0022,\u0022Legs\u0022,\u0022Shorts\u0022),\r\n  new(\u0022Bin Man Trousers\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/trousers/cargopants/binman_trousers.clothing\u0022,\u0022Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022Legs\u0022,\u0022Trousers\u0022),\r\n  new(\u0022Brown Cargo Pants\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/trousers/cargopants/brown_cargo_pants.clothing\u0022,\u0022Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022Legs\u0022,\u0022Trousers\u0022),\r\n  new(\u0022Army Cargo Pants\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/trousers/cargopants/cargo_pants_army.clothing\u0022,\u0022Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022Legs\u0022,\u0022Trousers\u0022),\r\n  new(\u0022Jeans\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/trousers/jeans/jeans.clothing\u0022,\u0022Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022Legs\u0022,\u0022Jeans\u0022),\r\n  new(\u0022Black Jeans\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/trousers/jeans/jeans_black.clothing\u0022,\u0022Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022Legs\u0022,\u0022Jeans\u0022),\r\n  new(\u0022Loose Jeans\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/trousers/loosejeans/loose_jeans.clothing\u0022,\u0022Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022Legs\u0022,\u0022Jeans\u0022),\r\n  new(\u0022Loose Trousers\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/trousers/loosetrousers/loose_trousers.clothing\u0022,\u0022Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022Legs\u0022,\u0022Trousers\u0022),\r\n  new(\u0022Smart Trousers\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/trousers/smarttrousers/trousers.smart.clothing\u0022,\u0022Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022Legs\u0022,\u0022Trousers\u0022),\r\n  new(\u0022Trackie Bottoms\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/trousers/trackiebottoms/trackie_bottoms.clothing\u0022,\u0022Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022Legs\u0022,\u0022Trousers\u0022),\r\n  new(\u0022Black Trackie Bottoms\u0022,\u0022Bottoms\u0022,\u0022Bottoms\u0022,\u0022models/citizen_clothes/trousers/trackiebottoms/trackie_bottoms_black.clothing\u0022,\u0022Groin, LeftThigh, RightThigh, LeftKnee, RightKnee, LeftShin, RightShin\u0022,\u0022\u0022,\u0022Legs\u0022,\u0022Trousers\u0022),\r\n  new(\u0022Chef\u0027s Apron\u0022,\u0022Outerwear\u0022,\u0022Outerwear\u0022,\u0022models/citizen_clothes/vest/chefs_apron/chefs_apron.clothing\u0022,\u0022\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022Vest\u0022),\r\n  new(\u0022Tactical Vest\u0022,\u0022Outerwear\u0022,\u0022Outerwear\u0022,\u0022models/citizen_clothes/vest/tactical_vest/models/tactical_vest.clothing\u0022,\u0022\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022Vest\u0022),\r\n  new(\u0022Army Vest\u0022,\u0022Outerwear\u0022,\u0022Outerwear\u0022,\u0022models/citizen_clothes/vest/tactical_vest/models/tactical_vest_army.clothing\u0022,\u0022LeftArm, RightArm\u0022,\u0022\u0022,\u0022\u0022,\u0022Vest\u0022),\r\n  new(\u0022Modern Watch\u0022,\u0022Accessories\u0022,\u0022watch\u0022,\u0022models/citizen_clothes/watch/modern_watch/modern_watch.clothing\u0022,\u0022LeftWrist\u0022,\u0022\u0022,\u0022\u0022,\u0022WristWatch\u0022),\r\n  new(\u0022Black Modern Watch\u0022,\u0022Accessories\u0022,\u0022watch\u0022,\u0022models/citizen_clothes/watch/modern_watch/modern_watch_black.clothing\u0022,\u0022LeftWrist\u0022,\u0022\u0022,\u0022\u0022,\u0022WristWatch\u0022)\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/// \u003Csummary\u003EIsolated rest-surface experiment. These morphs are not animated avatar presets.\u003C/summary\u003E\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; } = \u0022human03-to-human07\u0022;\r\n\tpublic string Mode { get; set; } = \u0022Target\u0022;\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\u003CCameraComponent\u003E();\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) =\u003E 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] \u002B= n;\r\n\t\t}\r\n\r\n\t\treturn normals.Select(n =\u003E 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(\u0022materials/default.vmat\u0022));\r\n\t\tvar vertices = p.Select((v, i) =\u003E 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\u003CVertex\u003E(vertices.Length, vertices.AsSpan());\r\n\t\tvar indices = data.Triangles.SelectMany(t =\u003E 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\u0022Existing\u0022,\r\n\t\t\t\u0022Experimental\u0022,\r\n\t\t\t\u0022Target\u0022\r\n\t\t}\r\n\r\n\t\t)\r\n\t\t{\r\n\t\t\tvar target = (mode == \u0022Existing\u0022 ? data.Existing : mode == \u0022Experimental\u0022 ? 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) =\u003E 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 != \u0022human03-to-human07\u0022 \u0026\u0026 Pair != \u0022human02-to-human04\u0022)\r\n\t\t\t\t\tthrow new ArgumentException(\u0022Unknown experiment pair.\u0022);\r\n\t\t\t\tvar data = JsonSerializer.Deserialize\u003CGeometry\u003E(FileSystem.Mounted.ReadAllText($\u0022experiments/face_transfer/{Pair}.json\u0022));\r\n\t\t\t\tBody = new GameObject().Components.Create\u003CSkinnedModelRenderer\u003E();\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($\u0022Face lab: {Error}\u0022);\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\u0022Existing\u0022,\r\n\t\t\t\t\u0022Experimental\u0022,\r\n\t\t\t\t\u0022Target\u0022\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"}]}