using System.Collections.Generic; using System.Globalization; using System.Text; using UnityEngine; using UnityEngine.Scripting; namespace NecromancerTome { /// /// Свой хват рук от первого лица (BACKLOG «Заклинания с руки и жесты», 1.6.0). Хват - неподвижная /// поза: после аниматора (LateUpdate) кости рига EModelSDCS.baseRigFP ставятся в заданные локальные /// повороты. Аниматор игры не трогаем. Снимки necrobones 03.10: кулак браслета (FP_Block_Hold, /// HoldType 7) - суставы пальцев повёрнуты по локальной Z на 77-92°, у пустой руки 8-38°; /// направление руки - RightArm, RightArmRoll, RightForeArm (сгиб по Y), RightHand. /// Пока - инструмент подбора позы (команда necropose); готовую позу потом зашить как хват. /// public static class HandPose { public static bool Enabled; public static readonly Dictionary Overrides = new Dictionary(); static GameObject _rig; static readonly Dictionary _bones = new Dictionary(); static GameObject _host; public static void Init() { if (_host != null) { return; } _host = new GameObject("NecroHandPose"); Object.DontDestroyOnLoad(_host); _host.AddComponent(); } public static Transform Bone(string _name) { EntityPlayerLocal player = GameManager.Instance?.World?.GetPrimaryPlayer(); GameObject rig = (player?.emodel as EModelSDCS)?.baseRigFP; if (rig == null) { return null; } if (rig != _rig) { _rig = rig; _bones.Clear(); foreach (Transform t in rig.GetComponentsInChildren(true)) { if (!_bones.ContainsKey(t.name)) { _bones[t.name] = t; } } } if (_bones.TryGetValue(_name, out Transform b) && b == null) { _rig = null; // кость уничтожена (риг пересобран) - перечитать в следующий раз } return b; } /// Кости, повёрнутые относительно КАМЕРЫ, а не родителя: ставятся после Overrides /// (03.10: локальные углы кисти «плывут» при каждом изменении локтя - подбор вслепую). public static readonly Dictionary CamOverrides = new Dictionary(); public static Transform Camera() { return GameManager.Instance?.World?.GetPrimaryPlayer()?.cameraTransform; } /// Заморозка (03.10: «при нажатии на кнопки руки деформируются, пытаясь совершить старую /// анимацию»). Поза задаёт 20-30 костей, а удар двигает и остальные - позвоночник, ключицы, вспомогательные /// кости предплечья и кожи (*_sup_*, *Roll*). Поэтому при включении хвата в покое снимается локальное /// положение ВСЕХ костей под Hips, и каждый кадр они возвращаются на место, а поверх - поза. /// Снимок только в покое аниматора (ItemActionIndex = -1), иначе застыл бы кадр удара. public static bool Freeze = true; /// Замереть без своего хвата - на время осмотра со стороны (NecroView). public static bool HoldStill; static readonly List _frozen = new List(); static readonly List _frozenPos = new List(); static readonly List _frozenRot = new List(); static GameObject _frozenRig; static readonly int ItemActionIndexHash = Animator.StringToHash("ItemActionIndex"); /// Выход из мира: всё в исходное, ссылки на кости рига отпустить (HandHolds.Update). public static void ResetAll() { Enabled = false; KnifeToLeft = false; NecroView.Stop(); Overrides.Clear(); CamOverrides.Clear(); _jabStart = -1f; Unfreeze(); _bones.Clear(); _rig = null; } public static void Unfreeze() { _frozen.Clear(); _frozenPos.Clear(); _frozenRot.Clear(); _frozenRig = null; } static void FreezeBones() { Transform hips = Bone("Hips"); if (hips == null) { return; } if (_frozenRig != _rig || _frozen.Count == 0 || _frozen[0] == null) { Unfreeze(); Animator anim = _rig.GetComponent(); if (anim != null && (anim.GetInteger(ItemActionIndexHash) != -1 || anim.IsInTransition(0))) { return; // идёт действие - снимок позже, в покое } foreach (Transform t in hips.GetComponentsInChildren(true)) { _frozen.Add(t); _frozenPos.Add(t.localPosition); _frozenRot.Add(t.localRotation); } _frozenRig = _rig; Debug.Log("[NecromancerTome] HandPose: заморожено костей " + _frozen.Count); return; } for (int i = 0; i < _frozen.Count; i++) { Transform t = _frozen[i]; if (t != null) { t.localPosition = _frozenPos[i]; t.localRotation = _frozenRot[i]; } } } public static void Apply() { if (!Enabled || Overrides.Count == 0 && CamOverrides.Count == 0) { if (HoldStill) { FreezeBones(); // осмотр со стороны (necroview): поза замирает и без своего хвата } else if (_frozen.Count > 0) { Unfreeze(); } if (KnifeToLeft && Enabled) { ApplyKnifeToLeft(); } return; } if (Freeze) { FreezeBones(); } foreach (var kv in Overrides) { Transform b = Bone(kv.Key); if (b != null) { b.localRotation = Quaternion.Euler(kv.Value); } } if (KnifeToLeft) { ApplyKnifeToLeft(); } Transform cam = CamOverrides.Count > 0 || _jabStart >= 0f ? Camera() : null; ApplyJab(cam); if (cam != null) { foreach (var kv in CamOverrides) { Transform b = Bone(kv.Key); if (b != null) { b.rotation = cam.rotation * kv.Value; } } } } /// Толчок руки к прицелу (указание 05.10, «пиф-паф» на ЛКМ: «чуть сдвигает руку в сторону /// прицела и возвращает»). Плечо доворачивается так, чтобы кисть прошла JabAmount пути до линии /// взгляда и вернулась - по синусу за JabTime. Ставится до CamOverrides: кисть сохраняет поворот /// относительно камеры, сдвигается только её место. public const float JabTime = 0.25f; public const float JabAmount = 0.35f; static float _jabStart = -1f; public static void Jab() { _jabStart = Time.time; } static void ApplyJab(Transform _cam) { if (_jabStart < 0f) { return; } float t = (Time.time - _jabStart) / JabTime; if (t >= 1f) { _jabStart = -1f; return; } Transform arm = Bone("RightArm"); Transform hand = Bone("RightHand"); if (_cam == null || arm == null || hand == null) { return; } Vector3 toHand = hand.position - arm.position; Vector3 aim = _cam.position + _cam.forward * Vector3.Dot(hand.position - _cam.position, _cam.forward); Quaternion full = Quaternion.FromToRotation(toHand, aim - arm.position); arm.rotation = Quaternion.Slerp(Quaternion.identity, full, Mathf.Sin(t * Mathf.PI) * JabAmount) * arm.rotation; } /// Кисть в осях камеры. Оси кисти правой руки (лог necrobones 03.10): пальцы - локальная +X /// (суставы пальцев лежат по +X), ладонь - +Y (сгиб пальцев +Z ведёт +X к +Y), +Z - к мизинцу, /// большой палец - к -Z. Нулевая поза: пальцы вверх, ладонь от игрока, тыльная сторона к камере, /// большой палец влево. tilt - наклон пальцев влево (+) вокруг взгляда; back - кисть «от себя» /// (+, верх пальцев уходит вперёд); turn - разворот кисти вокруг вертикали (+ ладонью вправо). public static Quaternion PalmToCamera(float _tilt, float _back, float _turn) { Quaternion basis = Quaternion.LookRotation(Vector3.right, Vector3.forward); return Quaternion.AngleAxis(_turn, Vector3.up) * Quaternion.AngleAxis(_back, Vector3.right) * Quaternion.AngleAxis(_tilt, Vector3.forward) * basis; } static readonly string[] Fingers = { "Index", "Middle", "Ring", "Pinky" }; /// Все суставы четырёх пальцев: сгиб по Z (curl), веер по Y у первого сустава /// (указательный +spread, средний +spread/3, безымянный -spread/3, мизинец -spread). public static void SetFingers(string _side, float _curl, float _spread) { float[] fan = { 1f, 1f / 3f, -1f / 3f, -1f }; for (int f = 0; f < Fingers.Length; f++) { for (int j = 1; j <= 3; j++) { Overrides[_side + "Hand" + Fingers[f] + j] = new Vector3(0f, j == 1 ? _spread * fan[f] : 0f, _curl); } } } /// Заготовка «раскрытая ладонь»: рука - как у браслета (снимок 03.10), пальцы прямые. public static void PresetOpen() { Overrides.Clear(); Overrides["RightArm"] = new Vector3(330.8f, 34.9f, 24.3f); Overrides["RightArmRoll"] = new Vector3(349.7f, 0f, 0f); Overrides["RightForeArm"] = new Vector3(0f, 88.4f, 0f); Overrides["RightHand"] = new Vector3(13.3f, 325.8f, 7.7f); SetFingers("Right", 3f, 0f); Overrides["RightHandThumb1"] = new Vector3(55f, 68.6f, 40.6f); Overrides["RightHandThumb2"] = new Vector3(0f, 0f, 0f); Overrides["RightHandThumb3"] = new Vector3(0f, 0f, 0f); Enabled = true; } /// Левая рука вдоль туловища, вне кадра (общая для хватов «отрицание» и «пиф-паф», 03.10). static void LeftArmDown() { Overrides["LeftArm"] = new Vector3(30f, 13.6f, 57.3f); } /// Хват «коза» (до 05.10 - «отрицание»; утверждён пользователем 03.10): рука вперёд, как у браслета, ниже прицела, /// кисть в линию с предплечьем, пальцы раскрыты. 05.10: средний и безымянный поджаты, как в кулаке /// (сгиб - как у поджатых пальцев «пиф-пафа»), указательный и мизинец прямые. public static void PresetDeny() { KnifeToLeft = false; Overrides.Clear(); CamOverrides.Clear(); Overrides["RightArm"] = new Vector3(345.8f, 34.9f, 24.3f); Overrides["RightArmRoll"] = new Vector3(349.7f, 0f, 0f); Overrides["RightForeArm"] = new Vector3(0f, 88.4f, 0f); Overrides["RightHand"] = new Vector3(45f, 0f, 0f); SetFingers("Right", 3f, 12f); for (int j = 1; j <= 3; j++) { float curl = j == 3 ? 55f : 80f; Overrides["RightHandMiddle" + j] = new Vector3(0f, 0f, curl); Overrides["RightHandRing" + j] = new Vector3(0f, 0f, curl); } // Большой палец прижимает поджатые (05.10: «полноценная коза»). Подбор на глаз - уточнять necropose. Overrides["RightHandThumb1"] = new Vector3(55f, 68.6f, 75f); Overrides["RightHandThumb2"] = new Vector3(0f, 0f, 40f); Overrides["RightHandThumb3"] = new Vector3(0f, 0f, 30f); LeftArmDown(); Unfreeze(); Enabled = true; } /// Хват «пиф-паф» (утверждён 03.10, «Стоп. Замри»): пистолет из пальцев - указательный и средний /// сомкнуты и смотрят к прицелу, безымянный и мизинец поджаты, большой вверх. Кисть - от камеры. public static void PresetPifPaf() { KnifeToLeft = false; Overrides.Clear(); CamOverrides.Clear(); Overrides["RightArm"] = new Vector3(330.8f, 34.9f, 24.3f); Overrides["RightArmRoll"] = new Vector3(349.7f, 0f, 0f); Overrides["RightForeArm"] = new Vector3(0f, 88.4f, 0f); CamOverrides["RightHand"] = PalmToCamera(-90f, 0f, -119f); for (int j = 1; j <= 3; j++) { Overrides["RightHandIndex" + j] = new Vector3(0f, j == 1 ? -3f : 0f, 3f); Overrides["RightHandMiddle" + j] = new Vector3(0f, j == 1 ? 2f : 0f, 3f); float curl = j == 3 ? 55f : 80f; Overrides["RightHandRing" + j] = new Vector3(0f, 0f, curl); Overrides["RightHandPinky" + j] = new Vector3(0f, 0f, curl); } Overrides["RightHandThumb1"] = new Vector3(55f, 68.6f, 20f); Overrides["RightHandThumb2"] = new Vector3(0f, 0f, 0f); Overrides["RightHandThumb3"] = new Vector3(0f, 0f, 0f); LeftArmDown(); Unfreeze(); Enabled = true; } /// Нож в левой кисти (хват для нитей): корень модели в руке ставится каждый кадр от LeftHand. Смещение - /// снимок хвата 68 (деревянный лук) 16.10, где нож лежал в левой руке («Нож в левой руке»). public static bool KnifeToLeft; static readonly Vector3 KnifeLeftPos = new Vector3(-0.0832f, -0.0354f, 0.0186f); static readonly Quaternion KnifeLeftRot = new Quaternion(0.56471f, -0.62192f, -0.41297f, 0.35182f); static void ApplyKnifeToLeft() { Transform lh = Bone("LeftHand"); Transform m = HeldTweak.Model(); if (lh != null && m != null) { m.SetPositionAndRotation(lh.TransformPoint(KnifeLeftPos), lh.rotation * KnifeLeftRot); } } /// Хват для нитей (16.10, подбор с пользователем: «Нож в левую руку, а правую сделай как в том хвате, где /// ты говорил что она перебирает нити»): основа - HoldType 68 (деревянный лук, items.xml); левая рука поднята и /// отведена влево, нож в ней; правая - раскрытая ладонь хвата 64 (бинт), пересчитанная под плечо хвата 68 так, /// чтобы кисть стояла там же относительно камеры (scratchpad ik.py). public static void PresetThread() { Overrides.Clear(); CamOverrides.Clear(); Overrides["RightShoulder"] = new Vector3(0.0f, 180.0f, 180.0f); Overrides["Right_sup_shoulder_01"] = new Vector3(0.0f, 0.0f, 0.0f); Overrides["Right_sup_shoulder_02"] = new Vector3(0.0f, 0.0f, 0.0f); Overrides["Right_sup_trap_01"] = new Vector3(0.0f, 0.0f, 0.0f); Overrides["RightArm"] = new Vector3(311.4f, 24.9f, 39.2f); Overrides["Right_sup_arm_01"] = new Vector3(0.0f, 0.0f, 358.2f); Overrides["RightArmRoll"] = new Vector3(357.9f, 0.0f, 0.0f); Overrides["RightForeArm"] = new Vector3(0.0f, 79.9f, 0.0f); Overrides["Right_sup_elbow_00"] = new Vector3(0.0f, 332.8f, 0.0f); Overrides["RightForeArmRoll"] = new Vector3(8.7f, 0.0f, 0.0f); Overrides["RightHand"] = new Vector3(7.2f, 346.5f, 5.4f); Overrides["RightHandIndex0"] = new Vector3(0.0f, 0.0f, 0.0f); Overrides["Right_sup_index_00"] = new Vector3(0.0f, 0.0f, 17.3f); Overrides["Right_sup_index_00_palm"] = new Vector3(0.0f, 0.0f, 15.6f); Overrides["RightHandIndex1"] = new Vector3(2.6f, 2.9f, 31.3f); Overrides["Right_sup_index_01_under"] = new Vector3(0.0f, 0.0f, 3.5f); Overrides["RightHandIndex2"] = new Vector3(0.0f, 0.0f, 52.8f); Overrides["Right_sup_index_02"] = new Vector3(0.0f, 0.0f, 333.6f); Overrides["Right_sup_index_02_under"] = new Vector3(0.0f, 0.0f, 5.1f); Overrides["RightHandIndex3"] = new Vector3(0.0f, 0.0f, 77.0f); Overrides["Right_sup_index_03"] = new Vector3(0.0f, 0.0f, 321.5f); Overrides["RightHandIndex4"] = new Vector3(0.0f, 0.0f, 0.0f); Overrides["RightHandMiddle0"] = new Vector3(0.0f, 0.0f, 0.0f); Overrides["Right_sup_middle_00"] = new Vector3(0.0f, 0.0f, 32.1f); Overrides["Right_sup_middle_00_palm"] = new Vector3(0.0f, 0.0f, 28.8f); Overrides["RightHandMiddle1"] = new Vector3(0.0f, 0.0f, 57.7f); Overrides["Right_sup_middle_01_under"] = new Vector3(0.0f, 0.0f, 5.7f); Overrides["RightHandMiddle2"] = new Vector3(0.0f, 0.0f, 85.0f); Overrides["Right_sup_middle_02"] = new Vector3(0.0f, 0.0f, 317.5f); Overrides["Right_sup_middle_02_under"] = new Vector3(0.0f, 0.0f, 5.1f); Overrides["RightHandMiddle3"] = new Vector3(0.0f, 0.0f, 77.0f); Overrides["Right_sup_middle_03"] = new Vector3(0.0f, 0.0f, 321.5f); Overrides["RightHandMiddle4"] = new Vector3(0.0f, 0.0f, 0.0f); Overrides["RightHandPinky0"] = new Vector3(0.0f, 0.0f, 0.0f); Overrides["Right_sup_pinky_00"] = new Vector3(0.0f, 0.0f, 45.0f); Overrides["Right_sup_pinky_00_palm"] = new Vector3(0.0f, 0.0f, 45.0f); Overrides["RightHandPinky1"] = new Vector3(0.2f, 353.1f, 92.0f); Overrides["Right_sup_pinky_01_under"] = new Vector3(0.0f, 0.0f, 5.6f); Overrides["RightHandPinky2"] = new Vector3(0.0f, 0.0f, 84.0f); Overrides["Right_sup_pinky_02"] = new Vector3(0.0f, 0.0f, 318.0f); Overrides["Right_sup_pinky_02_under"] = new Vector3(0.0f, 0.0f, 5.2f); Overrides["RightHandPinky3"] = new Vector3(0.0f, 0.0f, 78.0f); Overrides["Right_sup_pinky_03"] = new Vector3(0.0f, 0.0f, 321.0f); Overrides["RightHandPinky4"] = new Vector3(0.0f, 0.0f, 0.0f); Overrides["RightHandRing0"] = new Vector3(0.0f, 0.0f, 0.0f); Overrides["Right_sup_ring_00"] = new Vector3(0.0f, 0.0f, 33.0f); Overrides["Right_sup_ring_00_palm"] = new Vector3(0.0f, 0.0f, 33.0f); Overrides["RightHandRing1"] = new Vector3(358.6f, 355.2f, 66.1f); Overrides["Right_sup_ring_01_under"] = new Vector3(0.0f, 0.0f, 5.5f); Overrides["RightHandRing2"] = new Vector3(0.0f, 0.0f, 83.0f); Overrides["Right_sup_ring_02"] = new Vector3(0.0f, 0.0f, 318.5f); Overrides["Right_sup_ring_02_under"] = new Vector3(0.0f, 0.0f, 5.1f); Overrides["RightHandRing3"] = new Vector3(0.0f, 0.0f, 77.0f); Overrides["Right_sup_ring_03"] = new Vector3(0.0f, 0.0f, 321.5f); Overrides["RightHandRing4"] = new Vector3(0.0f, 0.0f, 0.0f); Overrides["RightHandThumb1"] = new Vector3(27.9f, 75.4f, 33.2f); Overrides["RightHandThumb2"] = new Vector3(0.0f, 0.0f, 15.8f); Overrides["Right_sup_thumb_01"] = new Vector3(0.0f, 0.0f, 352.1f); Overrides["Right_sup_thumb_01_under"] = new Vector3(0.0f, 0.0f, 1.2f); Overrides["RightHandThumb3"] = new Vector3(0.0f, 0.0f, 17.9f); Overrides["Right_sup_thumb_02"] = new Vector3(0.0f, 0.0f, 351.0f); Overrides["RightHandThumb4"] = new Vector3(0.0f, 0.0f, 0.0f); Overrides["necroKnifePrefab(Clone)"] = new Vector3(0.0f, 0.0f, 0.0f); Overrides["boneShiv_LOD0"] = new Vector3(0.0f, 0.0f, 0.0f); Overrides["boneShiv_LOD1"] = new Vector3(0.0f, 0.0f, 0.0f); Overrides["boneShiv_LOD2"] = new Vector3(0.0f, 0.0f, 0.0f); Overrides["RightForeArmRoll1"] = new Vector3(3.5f, 0.0f, 0.0f); Overrides["RightForeArmRoll2"] = new Vector3(7.0f, 0.0f, 0.0f); Overrides["RightForeArmRoll3"] = new Vector3(10.5f, 0.0f, 0.0f); Overrides["RightForeArmRoll4"] = new Vector3(14.0f, 0.0f, 0.0f); Overrides["Right_sup_wrist_00"] = new Vector3(0.0f, 0.0f, 357.5f); Overrides["Right_sup_wrist_01"] = new Vector3(0.0f, 347.6f, 0.0f); Overrides["Right_sup_wrist_02"] = new Vector3(0.0f, 0.0f, 356.8f); Overrides["Right_sup_wrist_03"] = new Vector3(0.0f, 347.6f, 0.0f); Overrides["LeftArm"] = new Vector3(329.6f, 42.5f, 8.5f); KnifeToLeft = true; Unfreeze(); Enabled = true; } public static string F(float _v) => _v.ToString("0.#", CultureInfo.InvariantCulture); public static string F(Vector3 _v) => F(_v.x) + " " + F(_v.y) + " " + F(_v.z); } public class HandPoseDriver : MonoBehaviour { void LateUpdate() { HandHolds.Update(); HandPose.Apply(); HeldTweak.Apply(); NecroView.Update(); } } /// /// necropose - подбор позы рук от первого лица: /// on | off | clear | open (заготовка: ладонь, рука как у браслета) /// <Кость> x y z - локальный поворот кости (градусы) /// add <Кость> dx dy dz - добавить к текущему (заданному или от аниматора) /// fingers Right|Left сгиб [веер] - все суставы четырёх пальцев /// show <Кость> - текущий поворот кости /// save - вся поза в лог строками кода /// [Preserve] public class ConsoleCmdNecroPose : ConsoleCmdAbstract { public override string[] getCommands() => new[] { "necropose" }; public override string getDescription() => "NecromancerTome: поза рук от первого лица - on|off|clear|deny|pifpaf|freeze [off]|open|<кость> x y z|aim <кость> наклон от_себя разворот|unaim <кость>|add <кость> dx dy dz|fingers Right|Left сгиб [веер]|show <кость>|save"; static bool Num(string _s, out float _v) => float.TryParse(_s.Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out _v); void Out(string _s) { SdtdConsole.Instance.Output(_s); Debug.Log("[NecromancerTome] necropose: " + _s); } public override void Execute(List _params, CommandSenderInfo _senderInfo) { string cmd = _params.Count > 0 ? _params[0] : ""; switch (cmd) { case "on": HandPose.Enabled = true; Out("поза включена, костей " + HandPose.Overrides.Count); return; case "off": HandPose.Enabled = false; Out("поза выключена"); return; case "clear": HandPose.Overrides.Clear(); HandPose.CamOverrides.Clear(); Out("поза очищена"); return; case "deny": HandPose.PresetDeny(); Out("хват «отрицание»"); return; case "pifpaf": HandPose.PresetPifPaf(); Out("хват «пиф-паф»"); return; case "freeze": HandPose.Freeze = _params.Count < 2 || _params[1] != "off"; HandPose.Unfreeze(); Out("заморозка остальных костей " + (HandPose.Freeze ? "включена (снимок - в покое)" : "выключена")); return; case "open": HandPose.PresetOpen(); Out("заготовка «ладонь» включена, костей " + HandPose.Overrides.Count); return; case "save": { var sb = new StringBuilder(); foreach (var kv in HandPose.Overrides) { Out("Overrides[\"" + kv.Key + "\"] = new Vector3(" + HandPose.F(kv.Value.x) + "f, " + HandPose.F(kv.Value.y) + "f, " + HandPose.F(kv.Value.z) + "f);"); } foreach (var kv in HandPose.CamOverrides) { Transform cb = HandPose.Bone(kv.Key); if (cb != null) { Out("Overrides[\"" + kv.Key + "\"] = new Vector3(" + HandPose.F(cb.localEulerAngles.x) + "f, " + HandPose.F(cb.localEulerAngles.y) + "f, " + HandPose.F(cb.localEulerAngles.z) + "f); // от камеры (aim)"); } } Out("всего костей " + HandPose.Overrides.Count + " + от камеры " + HandPose.CamOverrides.Count + ", включена " + HandPose.Enabled); return; } case "show": { if (_params.Count < 2) { break; } Transform b = HandPose.Bone(_params[1]); Out(b == null ? "нет кости " + _params[1] : _params[1] + " = " + HandPose.F(b.localEulerAngles) + (HandPose.Overrides.ContainsKey(_params[1]) ? " (задана)" : " (от аниматора)")); return; } case "fingers": { if (_params.Count >= 3 && (_params[1] == "Right" || _params[1] == "Left") && Num(_params[2], out float curl)) { float spread = 0f; if (_params.Count >= 4) { Num(_params[3], out spread); } HandPose.SetFingers(_params[1], curl, spread); HandPose.Enabled = true; Out("пальцы " + _params[1] + ": сгиб " + HandPose.F(curl) + ", веер " + HandPose.F(spread)); return; } break; } case "aim": { // aim <Кость> [наклон] [от_себя] [разворот] - кисть в осях камеры (см. HandPose.PalmToCamera) if (_params.Count >= 2) { float tilt = 0f, back = 0f, turn = 0f; if (_params.Count >= 3) Num(_params[2], out tilt); if (_params.Count >= 4) Num(_params[3], out back); if (_params.Count >= 5) Num(_params[4], out turn); if (HandPose.Bone(_params[1]) == null) { Out("нет кости " + _params[1]); return; } HandPose.Overrides.Remove(_params[1]); HandPose.CamOverrides[_params[1]] = HandPose.PalmToCamera(tilt, back, turn); HandPose.Enabled = true; Out(_params[1] + " от камеры: наклон " + HandPose.F(tilt) + ", от себя " + HandPose.F(back) + ", разворот " + HandPose.F(turn)); return; } break; } case "unaim": { if (_params.Count >= 2 && HandPose.CamOverrides.Remove(_params[1])) { Out(_params[1] + " - снова от родителя (от аниматора или заданная)"); return; } break; } case "add": { if (_params.Count >= 5 && Num(_params[2], out float dx) && Num(_params[3], out float dy) && Num(_params[4], out float dz)) { string name = _params[1]; Transform b = HandPose.Bone(name); if (b == null) { Out("нет кости " + name); return; } Vector3 cur = HandPose.Overrides.TryGetValue(name, out Vector3 v) ? v : b.localEulerAngles; HandPose.Overrides[name] = cur + new Vector3(dx, dy, dz); HandPose.Enabled = true; Out(name + " = " + HandPose.F(HandPose.Overrides[name])); return; } break; } default: { if (_params.Count >= 4 && Num(_params[1], out float x) && Num(_params[2], out float y) && Num(_params[3], out float z)) { if (HandPose.Bone(cmd) == null) { Out("нет кости " + cmd); return; } HandPose.CamOverrides.Remove(cmd); HandPose.Overrides[cmd] = new Vector3(x, y, z); HandPose.Enabled = true; Out(cmd + " = " + HandPose.F(HandPose.Overrides[cmd])); return; } break; } } Out(getDescription()); } } }