diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ff163e..cfcad10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,12 @@ - Braucht keinen neuen Server: die Infos kommen vom Master (msco.mbm.wtf) und funktionieren mit 2.8-Servern **Mitspieler als echte MSC-Menschen** -- Andere Spieler sehen aus wie die Leute im Spiel: MSCO nimmt eine Kopie eines MSC-Menschen (Körper, Gesicht, - Kleidung) und bewegt dessen Skelett mit der bisherigen Animation - laufen, springen, ducken, sitzen, Items halten -- Jeder Spieler bekommt (nach Spieler-Nummer) einen anderen Menschen; bei allen Mitspielern derselbe +- Andere Spieler sehen aus wie die Leute im Spiel: derselbe Körper wie alle MSC-Menschen, mit einem normalen Hemd, + einer Hose und einem Gesicht aus dem Spiel (keine Kleider/Vorhänge/Geister). Bewegt wird ein eigenes Skelett mit der + bisherigen Animation - laufen, springen, ducken, sitzen, Items halten +- Jeder Spieler bekommt (nach Spieler-Nummer) eine andere Kombination; alle Mitspieler sehen dieselbe - Konsole (F7): `model classic` zeigt wieder die MSCO-Figur, `model game` die MSC-Menschen; `rig` zeigt, wer welches - Modell hat. Findet MSCO keinen passenden Menschen, bleibt automatisch die MSCO-Figur + Modell hat. Findet MSCO den Körper nicht, bleibt automatisch die MSCO-Figur **Beifahrer** - **Automatisch einsteigen**: in die offene Beifahrertür an den Sitz treten - man setzt sich von selbst hin, keine Taste diff --git a/GameCharacter.cs b/GameCharacter.cs index d1e74ad..27d2ee2 100644 --- a/GameCharacter.cs +++ b/GameCharacter.cs @@ -1,15 +1,18 @@ -using System; using System.Collections.Generic; using UnityEngine; namespace MSCO { /// - /// Echte Spielfigur für Mitspieler: eine Kopie eines MSC-Menschen (SkinnedMeshRenderer "bodymesh" mit Skelett - /// pelvis/knee_left/ankle_left/shoulder_left/arm_left/hand_left/head ...). Die Knochen werden jedes Frame aus der - /// Pose der MSCO-Figur gesetzt (Hüfte, Knie, Knöchel, Schulter, Ellbogen, Hand, Kopf) - laufen, springen, ducken, - /// sitzen und Items halten funktionieren dadurch genau wie vorher. Die Bewegung wird über Richtungen übertragen - /// (Ruhelage aus der Bind-Pose des Modells), dadurch ist egal, wie die Knochen im Modell ausgerichtet sind. + /// Mitspieler als MSC-Mensch: Alle Leute im Spiel benutzen denselben Körper ("bodymesh", 20 Knochen) und + /// unterscheiden sich nur durch drei Materialien - Hemd (char_shirtXX), Hose (char_pantsXX) und Gesicht + /// (char_faceXX). MSCO nimmt nur dieses Mesh, baut ein eigenes Skelett aus seiner Bind-Pose und gibt jedem + /// Spieler ein normales Hemd/Hose/Gesicht (keine Kleider, Vorhänge oder Geister). Die Knochen werden jedes Frame + /// direkt aus den Gelenkpunkten der MSCO-Figur gesetzt (laufen, springen, ducken, sitzen, Items halten). + /// + /// Knochen-Reihenfolge ist bei allen MSC-Menschen gleich (nur die Namen unterscheiden sich): + /// 0 Becken, 1-2 Wirbelsäule, 3 Kopf, 4/9 Schlüsselbein, 5/10 Schulter, 6/11 Ellbogen, 7/12 Hand, 8/13 Finger, + /// 14/17 Hüfte, 15/18 Knie, 16/19 Knöchel (links/rechts). Bind-Pose: Z oben, X vorne, -Y links. /// public class GameCharacter { @@ -23,289 +26,217 @@ namespace MSCO public readonly Vector3[] Shoulder = new Vector3[2], Elbow = new Vector3[2], Hand = new Vector3[2]; } - // ---------------------------------------------------------------- Vorlagen im Spiel finden + const int BoneCount = 20; + const int Pelvis = 0, Head = 3; + static readonly int[] Torso = { 0, 1, 2, 4, 9 }; + static readonly int[] Shoulder = { 5, 10 }, Elbow = { 6, 11 }, Wrist = { 7, 12 }, Finger = { 8, 13 }; + static readonly int[] HipBone = { 14, 17 }, Knee = { 15, 18 }, Ankle = { 16, 19 }; - static readonly string[] RequiredBones = - { "pelvis", "head", "knee_left", "knee_right", "ankle_left", "ankle_right", "hand_left", "hand_right" }; + // ---------------------------------------------------------------- Mesh und Kleidung aus dem Spiel - static List templates; - static float nextTemplateSearch; + static Mesh bodyMesh; + static List shirts, pants, faces; + static float nextSearch; - /// Passende Menschen im Spiel (sortiert nach Pfad: bei allen Spielern gleiche Reihenfolge). - static List Templates() + static bool FindAssets() { - if (templates != null && templates.Count > 0 && templates.TrueForAll(t => t != null)) return templates; - if (Time.realtimeSinceStartup < nextTemplateSearch) return templates ?? new List(); - nextTemplateSearch = Time.realtimeSinceStartup + 10f; + if (bodyMesh != null && shirts.Count > 0 && pants.Count > 0 && faces.Count > 0) return true; + if (Time.realtimeSinceStartup < nextSearch) return false; + nextSearch = Time.realtimeSinceStartup + 10f; - var found = new List>(); - var seenMeshes = new HashSet(); + var users = new Dictionary(); + var shirtByName = new Dictionary(); + var pantsByName = new Dictionary(); + var faceByName = new Dictionary(); foreach (SkinnedMeshRenderer smr in Resources.FindObjectsOfTypeAll()) { - if (smr == null || smr.sharedMesh == null || !smr.name.StartsWith("bodymesh") || smr.name.Contains("kid")) continue; - if (smr.hideFlags != HideFlags.None || smr.bones == null || smr.bones.Length < 10) continue; - if (smr.sharedMesh.bindposes == null || smr.sharedMesh.bindposes.Length != smr.bones.Length) continue; - if (!HasBones(smr)) continue; - Transform root = CloneRoot(smr); - if (root == null || !SafeToClone(root)) continue; - if (!seenMeshes.Add(smr.sharedMesh)) continue; // gleiches Aussehen nur einmal - found.Add(new KeyValuePair(PathOf(smr.transform), smr)); + if (smr == null || smr.name != "bodymesh") continue; + Mesh m = smr.sharedMesh; + if (m == null || m.bindposes == null || m.bindposes.Length != BoneCount || m.subMeshCount != 3) continue; + int n; + users.TryGetValue(m, out n); + users[m] = n + 1; + Material[] mats = smr.sharedMaterials; + if (mats == null || mats.Length != 3) continue; + Add(shirtByName, mats[0], "char_shirt"); + Add(pantsByName, mats[1], "char_pants"); + if (mats[2] != null && !mats[2].name.Contains("woman")) Add(faceByName, mats[2], "char_face"); } - found.Sort((a, b) => string.CompareOrdinal(a.Key, b.Key)); - templates = found.ConvertAll(kv => kv.Value); - if (templates.Count > 0) MscoLog.Info("[MSCO] Player models: " + templates.Count + " MSC characters found"); - return templates; + // Das Mesh, das die meisten Leute benutzen = der normale Mann + Mesh best = null; + int bestUsers = 0; + foreach (KeyValuePair kv in users) + if (kv.Value > bestUsers && LooksHuman(kv.Key)) { best = kv.Key; bestUsers = kv.Value; } + bodyMesh = best; + shirts = Sorted(shirtByName); + pants = Sorted(pantsByName); + faces = Sorted(faceByName); + bool ok = bodyMesh != null && shirts.Count > 0 && pants.Count > 0 && faces.Count > 0; + if (ok) MscoLog.Info("[MSCO] Player models: MSC body with " + shirts.Count + " shirts, " + pants.Count + " trousers, " + faces.Count + " faces"); + return ok; } - static bool HasBones(SkinnedMeshRenderer smr) + static void Add(Dictionary into, Material m, string prefix) { - var names = new HashSet(); - foreach (Transform b in smr.bones) if (b != null) names.Add(b.name); - foreach (string r in RequiredBones) if (!names.Contains(r)) return false; - return true; + if (m != null && m.name.StartsWith(prefix) && !into.ContainsKey(m.name)) into[m.name] = m; } - /// Kleinster gemeinsamer Vorfahre von Mesh und Skelett - das wird kopiert. - static Transform CloneRoot(SkinnedMeshRenderer smr) + static List Sorted(Dictionary d) { - Transform bone = smr.rootBone != null ? smr.rootBone : smr.bones[0]; - if (bone == null) return null; - for (Transform a = smr.transform; a != null; a = a.parent) - if (bone == a || bone.IsChildOf(a)) return a; - return null; + var names = new List(d.Keys); + names.Sort(string.CompareOrdinal); // bei allen Spielern gleiche Reihenfolge -> gleiches Aussehen + return names.ConvertAll(n => d[n]); } - /// - /// Kopieren darf keine Spiel-Logik starten: entweder hängen in dem Teilbaum keine Skripte (PlayMaker ...), - /// oder er ist ohnehin gerade inaktiv (dann wird er für die Kopie kurz ausgeschaltet, ohne Folgen). - /// - static bool SafeToClone(Transform root) + /// Prüft die erwartete Knochen-Reihenfolge an der Bind-Pose (Kopf oben, Knöchel unten, Beine lang). + static bool LooksHuman(Mesh m) { - if (!root.gameObject.activeInHierarchy) return true; - return root.GetComponentsInChildren(true).Length == 0; + Vector3[] p = BindPositions(m); + if (p[Head].z < p[Pelvis].z + 0.4f) return false; + for (int i = 0; i < 2; i++) + { + if (p[Ankle[i]].z > p[Knee[i]].z || p[Knee[i]].z > p[HipBone[i]].z) return false; + if (p[Wrist[i]].z > p[Shoulder[i]].z) return false; + } + return p[Shoulder[0]].y < 0f && p[Shoulder[1]].y > 0f; // links = -Y } - static string PathOf(Transform t) + static Vector3[] BindPositions(Mesh m) { - string path = t.name; - for (Transform p = t.parent; p != null; p = p.parent) path = p.name + "/" + path; - return path; + var p = new Vector3[BoneCount]; + for (int i = 0; i < BoneCount; i++) p[i] = m.bindposes[i].inverse.GetColumn(3); + return p; } // ---------------------------------------------------------------- Instanz - class Bone - { - public Transform T; - public Quaternion RestRot; // relativ zum Spieler - public Vector3 RestPos; // relativ zum Spieler - public Vector3 RestDir; // zum Kind-Gelenk (Arme/Beine) - } - - GameObject clone; + GameObject model; Transform owner; - Bone pelvis, head; - readonly List spine = new List(); - readonly Bone[] upperLeg = new Bone[2], lowerLeg = new Bone[2], foot = new Bone[2]; - readonly Bone[] upperArm = new Bone[2], lowerArm = new Bone[2]; - Vector3 pelvisOffset; + readonly Transform[] bones = new Transform[BoneCount]; + readonly Vector3[] restPos = new Vector3[BoneCount]; // relativ zum Spieler + readonly Quaternion[] restRot = new Quaternion[BoneCount]; + Vector3 restHipCenter; public string Description { get; private set; } - /// Figur aus einer Spielvorlage bauen (variant wählt den Menschen). null, wenn keine passt. + /// Figur bauen (variant wählt Hemd, Hose und Gesicht). null, wenn das Spiel-Mesh nicht gefunden wird. public static GameCharacter Create(Transform owner, int variant) { - List list = Templates(); - if (list.Count == 0) return null; + if (!FindAssets()) return null; var c = new GameCharacter(); try { - if (c.Build(owner, list[Mathf.Abs(variant) % list.Count])) return c; + c.Build(owner, Mathf.Abs(variant)); + return c; } - catch (Exception e) + catch (System.Exception e) { MscoLog.Warning("[MSCO] Player model could not be built: " + e.Message); + c.Destroy(); + return null; } - c.Destroy(); - return null; } - bool Build(Transform owner, SkinnedMeshRenderer template) + void Build(Transform owner, int variant) { this.owner = owner; - Transform tplRoot = CloneRoot(template); - GameObject tpl = tplRoot.gameObject; + Vector3[] bind = BindPositions(bodyMesh); + float leg = (bind[Knee[0]] - bind[HipBone[0]]).magnitude + (bind[Ankle[0]] - bind[Knee[0]]).magnitude; + float scale = 0.84f / Mathf.Max(0.1f, leg); // Bein der MSCO-Figur - // Kopieren, ohne dass Skripte der Vorlage anlaufen (siehe SafeToClone) - bool wasActive = tpl.activeSelf; - bool toggle = wasActive && !tpl.activeInHierarchy; - if (toggle) tpl.SetActive(false); - try { clone = (GameObject)UnityEngine.Object.Instantiate(tpl); } - finally { if (toggle) tpl.SetActive(true); } - clone.name = "MSCO_Model"; - Strip(clone); + model = new GameObject("MSCO_Model"); + model.transform.SetParent(owner, false); + // Bind-Pose: X vorne, Z oben -> Spieler: Z vorne, Y oben + model.transform.localRotation = Quaternion.Inverse(Quaternion.LookRotation(Vector3.right, Vector3.forward)); + model.transform.localScale = Vector3.one * scale; - SkinnedMeshRenderer smr = null; - foreach (SkinnedMeshRenderer s in clone.GetComponentsInChildren(true)) - if (s.sharedMesh == template.sharedMesh) { smr = s; break; } - if (smr == null) return false; - Description = "MSC character " + PathOf(template.transform); - - // Knochen nach Namen - var byName = new Dictionary(); - foreach (Transform b in smr.bones) if (b != null && !byName.ContainsKey(b.name)) byName[b.name] = b; - - // In die Bind-Pose (Ruhelage des Modells) setzen: Eltern vor Kindern - Matrix4x4[] bind = smr.sharedMesh.bindposes; - var order = new List(); - for (int i = 0; i < smr.bones.Length; i++) if (smr.bones[i] != null) order.Add(i); - order.Sort((a, b) => Depth(smr.bones[a]).CompareTo(Depth(smr.bones[b]))); - foreach (int i in order) + Matrix4x4[] bindposes = bodyMesh.bindposes; + for (int i = 0; i < BoneCount; i++) { - Matrix4x4 m = smr.transform.localToWorldMatrix * bind[i].inverse; - smr.bones[i].position = m.GetColumn(3); - smr.bones[i].rotation = Quaternion.LookRotation(m.GetColumn(2), m.GetColumn(1)); + var b = new GameObject("bone" + i).transform; + b.SetParent(model.transform, false); + Matrix4x4 m = bindposes[i].inverse; + b.localPosition = m.GetColumn(3); + b.localRotation = Quaternion.LookRotation(m.GetColumn(2), m.GetColumn(1)); + bones[i] = b; + restPos[i] = owner.InverseTransformPoint(b.position); + restRot[i] = Quaternion.Inverse(owner.rotation) * b.rotation; } + restHipCenter = (restPos[HipBone[0]] + restPos[HipBone[1]]) * 0.5f; - // Gliedmaßen geometrisch bestimmen: in jeder Kette (Becken -> ... -> Knöchel -> Zehen bzw. Rumpf -> ... -> Hand - // -> Finger) sind die zwei längsten aufeinanderfolgenden Stücke Ober-/Unterschenkel bzw. Ober-/Unterarm. - // So ist egal, ob ein Knochen nach seinem Anfang oder seinem Ende benannt ist. - clone.transform.SetParent(owner, true); - Transform headT = byName["head"], pelvisT = byName["pelvis"]; - var trunk = new HashSet { pelvisT }; - for (Transform t = headT; t != null && t != pelvisT; t = t.parent) trunk.Add(t); - Transform[][] legs = { Limb(byName["ankle_left"], trunk), Limb(byName["ankle_right"], trunk) }; - Transform[][] arms = { Limb(byName["hand_left"], trunk), Limb(byName["hand_right"], trunk) }; - foreach (Transform[] l in legs) if (l == null) return false; - foreach (Transform[] l in arms) if (l == null) return false; - - Vector3 up = (headT.position - pelvisT.position).normalized; - Vector3 right = (arms[1][0].position - arms[0][0].position).normalized; - Vector3 forward = Vector3.Cross(right, up).normalized; - if (forward.sqrMagnitude < 0.5f) return false; - Quaternion facing = Quaternion.LookRotation(forward, up); - float legLength = (legs[0][1].position - legs[0][0].position).magnitude + (legs[0][2].position - legs[0][1].position).magnitude; - if (legLength < 0.01f) return false; - float scale = 0.84f / legLength; // Bein der MSCO-Figur (Oberschenkel + Schienbein + etwas Knöchel) - - // Erst drehen und skalieren, dann Ruhelage relativ zum Spieler merken - clone.transform.localScale = clone.transform.localScale * scale; - clone.transform.rotation = owner.rotation * Quaternion.Inverse(facing) * clone.transform.rotation; - clone.transform.position += owner.position - pelvisT.position; - - pelvis = MakeBone(pelvisT, null); - head = MakeBone(headT, null); - for (Transform s = headT.parent; s != null && s != pelvisT; s = s.parent) spine.Add(MakeBone(s, null)); - for (int i = 0; i < 2; i++) - { - upperLeg[i] = MakeBone(legs[i][0], legs[i][1]); - lowerLeg[i] = MakeBone(legs[i][1], legs[i][2]); - foot[i] = MakeBone(legs[i][2], null); - upperArm[i] = MakeBone(arms[i][0], arms[i][1]); - lowerArm[i] = MakeBone(arms[i][1], arms[i][2]); - } - Vector3 hipCenter = (upperLeg[0].RestPos + upperLeg[1].RestPos) * 0.5f; - pelvisOffset = pelvis.RestPos - hipCenter; - - smr.updateWhenOffscreen = true; // Knochen bewegen sich weit weg von der gespeicherten Hülle - foreach (Renderer r in clone.GetComponentsInChildren(true)) r.enabled = true; - clone.SetActive(true); - return true; - } - - /// - /// Gliedmaße als [Anfang oberes Stück, Gelenk (Knie/Ellbogen), Ende (Knöchel/Handgelenk)]: Kette vom Rumpf bis zum - /// Ende (über end hinaus entlang des längsten Kindes), dann die zwei längsten aufeinanderfolgenden Stücke. - /// - static Transform[] Limb(Transform end, HashSet trunk) - { - var chain = new List(); - for (Transform t = end; t != null && !trunk.Contains(t); t = t.parent) chain.Insert(0, t); - for (Transform t = end; t.childCount > 0; ) - { - Transform next = null; - float best = -1f; - foreach (Transform c in t) - { - float d = (c.position - t.position).sqrMagnitude; - if (d > best) { best = d; next = c; } - } - chain.Add(next); - t = next; - } - if (chain.Count < 3) return null; - int bestK = -1; - float bestLen = 0f; - for (int k = 0; k + 2 < chain.Count; k++) - { - float len = Mathf.Min((chain[k + 1].position - chain[k].position).magnitude, (chain[k + 2].position - chain[k + 1].position).magnitude); - if (len > bestLen) { bestLen = len; bestK = k; } - } - return bestK < 0 ? null : new[] { chain[bestK], chain[bestK + 1], chain[bestK + 2] }; - } - - static int Depth(Transform t) - { - int d = 0; - for (; t != null; t = t.parent) d++; - return d; - } - - Bone MakeBone(Transform t, Transform child) - { - var b = new Bone - { - T = t, - RestRot = Quaternion.Inverse(owner.rotation) * t.rotation, - RestPos = owner.InverseTransformPoint(t.position) - }; - if (child != null) b.RestDir = owner.InverseTransformPoint(child.position) - b.RestPos; - return b; - } - - /// Alles außer Aussehen entfernen (Skripte, Physik, Kollision, Ton, Animation). - static void Strip(GameObject go) - { - foreach (Joint j in go.GetComponentsInChildren(true)) UnityEngine.Object.DestroyImmediate(j); - foreach (Component c in go.GetComponentsInChildren(true)) - { - if (c == null || c is Transform || c is Renderer || c is MeshFilter) continue; - UnityEngine.Object.DestroyImmediate(c); - } + var smr = model.AddComponent(); + smr.sharedMesh = bodyMesh; + smr.bones = bones; + smr.rootBone = bones[Pelvis]; + smr.updateWhenOffscreen = true; // die Knochen bewegen sich weit weg von der gespeicherten Hülle + Material shirt = shirts[(variant * 7 + 3) % shirts.Count]; + Material trousers = pants[(variant * 3 + 1) % pants.Count]; + Material face = faces[(variant * 5 + 2) % faces.Count]; + smr.sharedMaterials = new[] { shirt, trousers, face }; + Description = "MSC body (" + shirt.name + ", " + trousers.name + ", " + face.name + ")"; } // ---------------------------------------------------------------- jedes Frame public void Apply(Rig rig) { - if (clone == null) return; + if (model == null) return; Quaternion root = owner.rotation; Quaternion lean = Quaternion.FromToRotation(Vector3.up, rig.Up); + Quaternion torso = root * lean; - pelvis.T.position = owner.TransformPoint(rig.Hip + lean * pelvisOffset); - pelvis.T.rotation = root * lean * pelvis.RestRot; - for (int i = spine.Count - 1; i >= 0; i--) spine[i].T.rotation = root * lean * spine[i].RestRot; // von unten nach oben - head.T.rotation = root * Quaternion.Euler(rig.Pitch, 0f, 0f) * head.RestRot; + foreach (int i in Torso) Place(i, TorsoPoint(rig, lean, restPos[i]), torso * restRot[i]); + Place(Head, TorsoPoint(rig, lean, restPos[Head]), root * Quaternion.Euler(rig.Pitch, 0f, 0f) * restRot[Head]); - for (int i = 0; i < 2; i++) + for (int s = 0; s < 2; s++) { - Aim(upperLeg[i], rig.Knee[i] - rig.HipJoint[i], root); - Aim(lowerLeg[i], rig.Ankle[i] - rig.Knee[i], root); - foot[i].T.rotation = root * foot[i].RestRot; // Füße bleiben flach - Aim(upperArm[i], rig.Elbow[i] - rig.Shoulder[i], root); - Aim(lowerArm[i], rig.Hand[i] - rig.Elbow[i], root); + // Bein: Hüfte am Becken, dann Richtung Knie bzw. Knöchel der MSCO-Figur + Vector3 hip = TorsoPoint(rig, lean, restPos[HipBone[s]]); + Quaternion thigh = AimDelta(HipBone[s], Knee[s], rig.Knee[s] - rig.HipJoint[s]); + Place(HipBone[s], hip, root * thigh * restRot[HipBone[s]]); + Vector3 knee = hip + root * (thigh * (restPos[Knee[s]] - restPos[HipBone[s]])); + Quaternion shin = AimDelta(Knee[s], Ankle[s], rig.Ankle[s] - rig.Knee[s]); + Place(Knee[s], knee, root * shin * restRot[Knee[s]]); + Vector3 ankle = knee + root * (shin * (restPos[Ankle[s]] - restPos[Knee[s]])); + Place(Ankle[s], ankle, root * restRot[Ankle[s]]); // Fuß bleibt flach + + // Arm: Schulter am Oberkörper, dann Richtung Ellbogen bzw. Hand + Vector3 shoulder = TorsoPoint(rig, lean, restPos[Shoulder[s]]); + Quaternion upper = AimDelta(Shoulder[s], Elbow[s], rig.Elbow[s] - rig.Shoulder[s]); + Place(Shoulder[s], shoulder, root * upper * restRot[Shoulder[s]]); + Vector3 elbow = shoulder + root * (upper * (restPos[Elbow[s]] - restPos[Shoulder[s]])); + Quaternion fore = AimDelta(Elbow[s], Wrist[s], rig.Hand[s] - rig.Elbow[s]); + Place(Elbow[s], elbow, root * fore * restRot[Elbow[s]]); + Vector3 wrist = elbow + root * (fore * (restPos[Wrist[s]] - restPos[Elbow[s]])); + Place(Wrist[s], wrist, root * fore * restRot[Wrist[s]]); + Vector3 finger = wrist + root * (fore * (restPos[Finger[s]] - restPos[Wrist[s]])); + Place(Finger[s], finger, root * fore * restRot[Finger[s]]); } } - static void Aim(Bone b, Vector3 desired, Quaternion root) + /// Punkt des Oberkörpers: an der Hüfte der MSCO-Figur, mit ihr geneigt. + Vector3 TorsoPoint(Rig rig, Quaternion lean, Vector3 rest) { - if (desired.sqrMagnitude < 1e-6f || b.RestDir.sqrMagnitude < 1e-6f) return; - b.T.rotation = root * Quaternion.FromToRotation(b.RestDir, desired) * b.RestRot; + return owner.TransformPoint(rig.Hip + lean * (rest - restHipCenter)); + } + + /// Drehung, die das Knochenstück from->to aus der Ruhelage in die gewünschte Richtung bringt. + Quaternion AimDelta(int from, int to, Vector3 desired) + { + Vector3 rest = restPos[to] - restPos[from]; + if (desired.sqrMagnitude < 1e-6f || rest.sqrMagnitude < 1e-6f) return Quaternion.identity; + return Quaternion.FromToRotation(rest, desired); + } + + void Place(int i, Vector3 pos, Quaternion rot) + { + bones[i].position = pos; + bones[i].rotation = rot; } public void Destroy() { - if (clone != null) UnityEngine.Object.Destroy(clone); - clone = null; + if (model != null) Object.Destroy(model); + model = null; } } }