Siv3D / OpenSiv3D

C++20 framework for creative coding 🎮🎨🎹 / Cross-platform support (Windows, macOS, Linux, and the Web)
https://siv3d.github.io/
MIT License
1.02k stars 140 forks source link

Math::ClampAngle 追加 #1271

Closed sashi0034 closed 1 week ago

sashi0034 commented 2 weeks ago

Discord 文脈: https://discord.com/channels/443310697397354506/999983621408567326/1305003589588226211

最小値と最大値の範囲にクランプした角度を返す Math::ClampAngle を実装しました。

検索エンジンで ClampAngle を検索したとき上位に出てくる以下の実装を参考にしています。 https://gist.github.com/johnsoncodehk/2ecb0136304d4badbb92bd0c1dbd8bae

以下の Main.cpp で動作を確認できます。

# include <Siv3D.hpp> // Siv3D v0.6.15

namespace
{
    void Test_ClampAngle(double angleDeg, double minDeg, double maxDeg)
    {
        const double angleRad = ToRadians(angleDeg);
        const double minRad = ToRadians(minDeg);
        const double maxRad = ToRadians(maxDeg);

        Print << U"ClampAngle({}_deg, {}_deg, {}_deg) = {:.4f} (= {:.4f}_deg)"_fmt(
            angleDeg, minDeg, maxDeg,
            Math::ClampAngle(angleRad, minRad, maxRad),
            ToDegrees(Math::ClampAngle(angleRad, minRad, maxRad)));
    }
}

void Main()
{
    Window::Resize(1280, 720);

    Print << U"360_deg - 15_deg = {:.04f}"_fmt(360_deg - 15_deg);
    Print << U"720_deg - 15_deg = {:.04f}"_fmt(720_deg - 15_deg);

    Print << U"-----------------------------------------------";

    Test_ClampAngle(10, -15, 15);
    Test_ClampAngle(20, -15, 15);

    Print << U"-----------------------------------------------";

    Test_ClampAngle(350, -15, 15);
    Test_ClampAngle(340, -15, 15);
    Test_ClampAngle(700, -15, 15);

    while (System::Update())
    {
    }
}

実行結果

image

sashi0034 commented 2 weeks ago

失礼しました、先程の実装に誤りがあったので修正しました (上の動作確認用のコードは偶然 Clamp が成功していたようでした) 修正確認を兼ねて、GUI で動作を確認できる Main.cpp を作成しました。

# include <Siv3D.hpp> // Siv3D v0.6.15

void Main()
{
    double angleRad{};
    double minRad{};
    double maxRad{Math::HalfPi};

    while (System::Update())
    {
        constexpr double range = Math::TwoPi * 2;
        SimpleGUI::Headline(U"angleRad: {:.4f}"_fmt(angleRad), {20, 20});
        SimpleGUI::Slider(angleRad, -range, range, {200, 20}, 280);

        SimpleGUI::Headline(U"minRad: {:.4f}"_fmt(minRad), {20, 60});
        SimpleGUI::Slider(minRad, -range, range, {200, 60}, 280);

        SimpleGUI::Headline(U"maxRad: {:.4f}"_fmt(maxRad), {20, 100});
        SimpleGUI::Slider(maxRad, -range, range, {200, 100}, 280);

        const double clampedRad = Math::ClampAngle(angleRad, minRad, maxRad);
        SimpleGUI::Headline(U"ClampAngle(angleRad, minRad, maxRad) -> {:.4f}"_fmt(clampedRad), {20, 140});

        Circle(Scene::Center(), 100)
            .drawPie(minRad, maxRad - minRad, Palette::Gray)
            .drawFrame(1.0, Palette::White);

        Line{Scene::Center(), Circular{100, clampedRad}.toVec2() + Scene::Center()}.draw(5.0, Palette::Yellow);

        Line{Scene::Center(), Circular{100, angleRad}.toVec2() + Scene::Center()}.draw(2.0, Palette::Red);

        Line{Scene::Center(), Circular{100, minRad}.toVec2() + Scene::Center()}.draw(2.0, Palette::Green);

        Line{Scene::Center(), Circular{100, maxRad}.toVec2() + Scene::Center()}.draw(2.0, Palette::Blue);
    }
}

image

Reputeless commented 1 week ago

ありがとうございます。近日中に確認します。

Reputeless commented 1 week ago

追加の計算コストが発生しますが、

    double ClampAngle2(const double angle, const double min, double max) noexcept
    {
        const auto start = (min + max) * 0.5 - Pi;
        const auto floor = Floor((angle - start) / TwoPi) * TwoPi;
        return NormalizeAngle(Clamp(angle, min + floor, max + floor), (min + max) * 0.5);
    }

のように NormalizeAngle して、戻り値が必ず min 以上 max 以下になるようにするのはどうでしょう。戻り値に対する驚きが少なくなるような気がします。

一方で失われる情報もあるので、そうしたい場合はユーザが自前で NoarmalizeAngle() を書くのも選択肢です。

    Print << Math::ToDegrees(Math::ClampAngle(-3610_deg, -60_deg, 60_deg)); // -3610
    Print << Math::ToDegrees(Math::ClampAngle2(-3610_deg, -60_deg, 60_deg)); // -10

    Print << Math::ToDegrees(Math::ClampAngle(-350_deg, -60_deg, 60_deg)); // -350
    Print << Math::ToDegrees(Math::ClampAngle2(-350_deg, -60_deg, 60_deg)); // 10

    Print << Math::ToDegrees(Math::ClampAngle(10_deg, 340_deg, 380_deg)); // 10
    Print << Math::ToDegrees(Math::ClampAngle2(10_deg, 340_deg, 380_deg)); // 370

    Print << Math::ToDegrees(Math::ClampAngle(90_deg, 340_deg, 380_deg)); // 20
    Print << Math::ToDegrees(Math::ClampAngle2(90_deg, 340_deg, 380_deg)); // 380

↓ クランプ範囲が 2 π 以上のとき ClampAngle2 の結果が直感的でなくなるのは欠点です。

    Print << Math::ToDegrees(Math::ClampAngle(10_deg, 380_deg, 3580_deg)); // 10
    Print << Math::ToDegrees(Math::ClampAngle2(10_deg, 380_deg, 3580_deg)); // 1810
Reputeless commented 1 week ago
Reputeless commented 1 week ago

Merged. Thank you!