fix(crane_geometry): 円と線分の交点計算を二次方程式解に置き換え、テストを追加 - #1386
Merged
Merged
Conversation
HansRobo
force-pushed
the
fix/geometry-circle-segment-intersection
branch
from
September 4, 2026 03:53
e73ffee to
8b9ef88
Compare
HansRobo
force-pushed
the
fix/geometry-circle-segment-intersection
branch
2 times, most recently
from
September 17, 2026 09:08
4e6b7ca to
2d192fd
Compare
getIntersections(Circle, Segment) は「円中心から線分への垂直距離」を前提に 垂線足から弦半長だけ±する構成だったが、その距離の取得が二重に誤っていた。 1. bg::distance(circle, segment) は circle.hpp のオーバーロードにより max(0, 中心距離 - 半径) を返す。線分が円と交わる場合は常に 0 にクランプされ、 弦半長が常に radius になり垂線足が円中心へ潰れる。 2. そもそも「線分への距離」は線分にクランプされるため、垂線足が線分の外に 落ちる配置では垂直距離にならない。closest_point(segment, center) に 置き換えても 2. は解消しない。 実測(ランダム2万ケース、媒介変数の二次方程式解をオラクルとする)で 「円周上にない」または「線分上にない」点を返した割合は、 develop の実装(1. と 2. の両方)が 39.9%、 1. のみを修正して closest_point を使う実装でも 7.4% 残る。 線分を P(t) = first + t * (second - first), t in [0, 1] と媒介変数表示して |P(t) - center|^2 = radius^2 を直接解く実装に置き換える。クランプに関する 前提が一切不要になり、端点上の交点も t in [0, 1] に自然に含まれる。 接する場合は重解を 1 点に畳むため、戻り値の個数が交点数と一致する (従来は同一点を 2 個返していた)。交点は始点側から終点側の順に並ぶ。 この不具合が長く残った原因は test_geometry.cpp に getIntersections の カバレッジが皆無だったことなので、併せてテストを追加する。個別の期待値に 加えて「返る点が円周上にあり、かつ線分上にある」という不変条件を全ケースで 検証するヘルパを置き、シード固定のランダムケースにも適用する。 closest_point を使う実装では 6 テストが失敗することを確認済み (うち 1 件は本コミットで新たに規定した並び順の契約による)。 なお唯一の呼出元 getDefenseArcPoints は現在どこからも呼ばれていないため、 本修正による実行時の挙動変化はない。同関数の扱いは別途整理する。
HansRobo
force-pushed
the
fix/geometry-circle-segment-intersection
branch
from
September 17, 2026 09:24
2d192fd to
373f7a5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概要
crane_geometryのgetIntersections(Circle, Segment)が、交点として円周上にない点や線分上にない点を返していた問題を修正します。垂線足を経由する構成をやめ、媒介変数の二次方程式を直接解く実装に置き換えたうえで、欠落していたテストを追加します。問題
本関数は「円中心から線分への垂直距離
d」を求め、垂線足から線分方向に弦半長sqrt(r^2 - d^2)だけ±して 2 交点を算出する構成でした。しかしdの取得が二重に誤っていました。bg::distance(circle, segment)はcircle.hppのオーバーロードによりmax(0, 中心距離 - 半径)を返します。線分が円と交わる場合は常に 0 にクランプされるため、弦半長が常にrになり、垂線足が円中心へ潰れます。closest_point(segment, center)に置き換えても 2. は解消しません。実測(ランダム2万ケース、媒介変数の二次方程式解をオラクル)で「円周上にない」または「線分上にない」点を返した割合:
具体例:
C=(-6, 0) r=2.5,seg=(-5, 0.5)-(-4, 3.5)(垂線足が線分外): 初版は(-4.2929, 2.6213)を返すが|p-C| - r = +0.628で円周上ですらない。正解は(-4.5, 2.0)C=(2, 0) r=1.5,seg=(0, 0)-(1, 0): 初版は交点0個。実在する交点(0.5, 0)を取りこぼす(develop は偶然正しく返していた)case 1:(交点1個)分岐が事実上デッドだった修正内容
線分を
P(t) = first + t * (second - first),t ∈ [0, 1]と媒介変数表示し、|P(t) - center|^2 = radius^2を直接解きます。t ∈ [0, 1]に自然に含まれる(従来は> 0の狭義判定で落としていた)a = |dir|^2 > 0なので交点は始点側から終点側の順に並ぶテスト
この不具合が長く残った原因は
test_geometry.cppにgetIntersectionsのカバレッジが皆無だったことなので、併せて追加しました。個別の期待値に加えて、「返る点が円周上にあり、かつ線分上にある」という不変条件を全ケースで検証するヘルパを置いています。これ 1 つで上記 2 系統の欠陥を同時に捕捉できます。シード固定のランダム2000ケースにも適用しています。
検証:
closest_pointを使う実装(本PR初版)では 6 テストが失敗することを確認(うち 1 件は本PRで新たに規定した並び順の契約による)34 tests, 0 errors, 0 failures, 0 skipped(test_geometry.gtest.xmlは 10 → 17 テスト)pre-commit(clang-format / cpplint)通過影響範囲
getIntersections(Circle, Segment)の唯一の呼出元はcrane_sessions/src/defense_functions.cppのgetDefenseArcPointsですが、そのgetDefenseArcPoints自体がibis_ws/src全体から一度も呼ばれていない dead code です。したがって本修正による実行時の挙動変化はありません。防御位置計算に現存する不具合もありません。getDefenseArcPointsを残すか削除するかは別PRで整理します。別途対応(本PRの範囲外)
Circleは Boost.Geometry にpoint_tagとして登録されているため、引数順でdistanceの意味が変わり、何も警告されません。bg::distance(circle, p)→ 表面距離(0クランプ)…circle.hppの自前オーバーロードbg::distance(p, circle)→ 中心距離 … Boost の point-point フォールバックCapsule(segment_tag)も同形です。現状の呼出元はたまたま正しい側を使っていますが、本件の根本原因はここにあります。ただしtest_geometry.cppが表面距離の意味論をピン留めしているため、削除ではなくboost::geometry名前空間の外へのリネームが安全です。別issueとして切り出します。🤖 Generated with Claude Code