kumak1’s blog

kumak1のイラストや技術ログ

Unity で三角比を使って辺の長さや角度を求める

3D空間で、Shaderのテカリを制御したり、カメラやオブジェクトの挙動などを制御したい際には、とにかく角度や2点間の距離を求めることが多い。

そして残念ながら、使う側の人間(私)は度数法に染まりきっているため、ラジアンだとどれくらいの角度かパッとイメージできない。くやしい。 (「最大角度 0.2 rad まで許容」みたいな入力項目には「具体的にどんくらいだよ・・」ってなる残念な頭である。とはいえ、物理カメラの画角なども一般的には度数法で表すので、一般的・・のハズ)

なので、わたしでもビャッと使える関数群を用意してみた。よかったらお使いください(意外とネットにはこういったサンプルコードが転がってなかったので・・)

using UnityEngine;

public static class Util
{
    // 斜辺
    public static float HypotenuseByBoAn(float bottom, float angle) =>
        bottom / Mathf.Cos(angle * Mathf.Deg2Rad);
    public static float HypotenuseByBoHe(float bottom, float height) =>
        bottom / Mathf.Cos(Mathf.Atan2(height, bottom));
    public static float HypotenuseByHeAn(float height, float angle) =>
        height / Mathf.Sin(angle * Mathf.Deg2Rad);

    // 底辺
    public static float BottomByHeAn(float height, float angle) =>
        height / Mathf.Tan(angle * Mathf.Deg2Rad);
    public static float BottomByHyAn(float hypotenuse, float angle) =>
        hypotenuse * Mathf.Cos(angle * Mathf.Deg2Rad);
    public static float BottomByHeHy(float height, float hypotenuse) =>
        Mathf.Sqrt(Mathf.Pow(hypotenuse, 2) - Mathf.Pow(height, 2));

    // 高さ
    public static float HeightByBoAn(float bottom, float angle) =>
        bottom * Mathf.Tan(angle * Mathf.Deg2Rad);
    public static float HeightByHyAn(float hypotenuse, float angle) =>
        hypotenuse * Mathf.Sin(angle * Mathf.Deg2Rad);
    public static float HeightByBoHy(float bottom, float hypotenuse) =>
        Mathf.Sqrt(Mathf.Pow(hypotenuse, 2) - Mathf.Pow(bottom, 2));

    // 角度
    public static float AngleByBoHe(float bottom, float height) =>
        Mathf.Atan2(height, bottom) * Mathf.Rad2Deg;
    public static float AngleByBoHy(float bottom, float hypotenuse) =>
        Mathf.Acos(bottom / hypotenuse) * Mathf.Rad2Deg;
    public static float AngleByHeHy(float height, float hypotenuse) =>
        Mathf.Asin(height / hypotenuse) * Mathf.Rad2Deg;
}

浮動小数点の演算なので、許容誤差 0.00001 に設定、直角三角形を題材にしてテストを実施している。

[Test]
public void HypotenuseByBoAn()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(2, Util.HypotenuseByBoAn(route3, 30), delta);
    Assert.AreEqual(route2, Util.HypotenuseByBoAn(1, 45), delta);
    Assert.AreEqual(2, Util.HypotenuseByBoAn(1, 60), delta);
}

[Test]
public void HypotenuseByBoHe()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(2, Util.HypotenuseByBoHe(route3, 1), delta);
    Assert.AreEqual(route2, Util.HypotenuseByBoHe(1, 1), delta);
    Assert.AreEqual(2, Util.HypotenuseByBoHe(1, route3), delta);
}

[Test]
public void HypotenuseByHeAn()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(2, Util.HypotenuseByHeAn(1, 30), delta);
    Assert.AreEqual(route2, Util.HypotenuseByHeAn(1, 45), delta);
    Assert.AreEqual(2, Util.HypotenuseByHeAn(route3, 60), delta);
}

[Test]
public void BottomByHeAn()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(route3, Util.BottomByHeAn(1, 30), delta);
    Assert.AreEqual(1, Util.BottomByHeAn(1, 45), delta);
    Assert.AreEqual(1, Util.BottomByHeAn(route3, 60), delta);
}

[Test]
public void BottomByHyAn()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(route3, Util.BottomByHyAn(2, 30), delta);
    Assert.AreEqual(1, Util.BottomByHyAn(route2, 45), delta);
    Assert.AreEqual(1, Util.BottomByHyAn(2, 60), delta);
}

[Test]
public void BottomByHeHy()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(route3, Util.BottomByHeHy(1, 2), delta);
    Assert.AreEqual(1, Util.BottomByHeHy(1, route2), delta);
    Assert.AreEqual(1, Util.BottomByHeHy(route3, 2), delta);
}

[Test]
public void HeightByBoAn()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(1, Util.HeightByBoAn(route3, 30), delta);
    Assert.AreEqual(1, Util.HeightByBoAn(1, 45), delta);
    Assert.AreEqual(route3, Util.HeightByBoAn(1, 60), delta);
}

[Test]
public void HeightByHyAn()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(1, Util.HeightByHyAn(2, 30), delta);
    Assert.AreEqual(1, Util.HeightByHyAn(route2, 45), delta);
    Assert.AreEqual(route3, Util.HeightByHyAn(2, 60), delta);
}

[Test]
public void HeightByBoHy()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(1, Util.HeightByBoHy(route3, 2), delta);
    Assert.AreEqual(1, Util.HeightByBoHy(1, route2), delta);
    Assert.AreEqual(route3, Util.HeightByBoHy(1, 2), delta);
}

[Test]
public void AngleByBoHe()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(30f, Util.AngleByBoHe(route3, 1), delta);
    Assert.AreEqual(45f, Util.AngleByBoHe(1, 1), delta);
    Assert.AreEqual(60f, Util.AngleByBoHe(1, route3), delta);
}

[Test]
public void AngleByBoHy()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(30f, Util.AngleByBoHy(route3, 2), delta);
    Assert.AreEqual(45f, Util.AngleByBoHy(1, route2), delta);
    Assert.AreEqual(60f, Util.AngleByBoHy(1, 2), delta);
}

[Test]
public void AngleByHeHy()
{
    var delta = 0.00001f;
    var route2 = Mathf.Sqrt(2);
    var route3 = Mathf.Sqrt(3);

    Assert.AreEqual(30f, Util.AngleByHeHy(1, 2), delta);
    Assert.AreEqual(45f, Util.AngleByHeHy(1, route2), delta);
    Assert.AreEqual(60f, Util.ngleByHeHy(route3, 2), delta);
}