public NearestPointResolver(Animator avatarRoot, ShapeKey key, bool isGenericMode) { this.avatarRoot = avatarRoot; this.key = key; groups = new BoneGroup[key.bodyLines.Count]; for (var i = 0; i < key.bodyLines.Count; i++) { var b = key.bodyLines[i]; switch (b.bones.Count) { case 2: { var b0 = b.GetBoneTransform(0, avatarRoot, isGenericMode).position; var b1 = b.GetBoneTransform(1, avatarRoot, isGenericMode).position; groups[i] = new BoneGroup2( Vector3.Lerp(b0, b1, key.startMargin), Vector3.Lerp(b0, b1, 1f - key.endMargin), key.isLeaf ); break; } case 3: { var b0 = b.GetBoneTransform(0, avatarRoot, isGenericMode).position; var b1 = b.GetBoneTransform(1, avatarRoot, isGenericMode).position; var b2 = b.GetBoneTransform(2, avatarRoot, isGenericMode).position; groups[i] = new BoneGroup3( Vector3.Lerp(b0, b1, key.startMargin * 2f), b1, Vector3.Lerp(b1, b2, 1f - key.endMargin * 2f) ); break; } default: Debug.Log("Body line must have 2 or 3 bones"); break; } } }
public BoneGroup3(Vector3 pB0, Vector3 pB1, Vector3 pB2) { this.pB0 = pB0; this.pB1 = pB1; this.pB2 = pB2; // 1. 平面 B₀B₁B₂ の法線 N を、外積 B₁B₀×B₁B₂ から求める。 vB1B0 = pB0 - pB1; vB1B2 = pB2 - pB1; N = Vector3.Cross(vB1B0, vB1B2).normalized; // Issueの図では手前向き(左手座標系) // 2. 平面上の座標空間を以下のように定義する。 // - B₁ を原点 O とする // - B₁B₀ の単位ベクトルを取り、その方向を𝓍軸とする // - 𝓍軸を N の周りに反時計回り90度回転した方向を𝓎軸とする vX = vB1B0.normalized; vY = Vector3.Cross(vX, N).normalized; // 4. 平面上の𝓍・𝓎軸単位ベクトルとの内積から、平面上での B₀, B₂, Pₚ の座標を求める。 qB0 = new Vector2(Vector3.Dot(vB1B0, vX), Vector3.Dot(vB1B0, vY)); qB2 = new Vector2(Vector3.Dot(vB1B2, vX), Vector3.Dot(vB1B2, vY)); if (Mathf.Abs(qB2.y) < 1e-5f) { // 3ボーンが一直線上に並ぶときは2ボーン処理で代替 altForLinear = new BoneGroup2(pB0, pB2, false); return; } // 5. 次の2直線の交点を C とする。 // - B₀ を通り B₀O と垂直な直線:𝓍 = B₀x // - B₂ を通り B₂O と垂直な直線:𝓎-B₂y = (-B₂x/B₂y)(𝓍-B₂x) // 6. 上記2直線の式を連立方程式として解き、C の座標を求める。 // - Cx = B₀x // - Cy = (-B₂x/B₂y)(B₀x-B₂x) + B₂y qC = new Vector2(qB0.x, -qB2.x * (qB0.x - qB2.x) / qB2.y + qB2.y); }