📐

直線上で線分に点が含まれるか判定する方法

2023/09/18に公開

目的

線分 ab に点 e が含まれるかを知りたい。

考え方

  1. 線分の両端から点に向かってベクトルを作る
  2. 二つのベクトルが向き合っていたら線分内にある

計算手順

a = V[320.0,  90.0]
b = V[480.0,  70.0]
e = V[400.0,  80.0]
ae = e - a  # => (80.0, -10.0)
be = e - b  # => (-80.0, 10.0)
ae.dot(be)  # => -6500.0

二つのベクトルが対向しているかどうかは、ベクトル同士の内積で判断できる。内積は、二つのベクトルの角度が近いほど大きな正値を返し、そうでない場合は大きな負値を返す。ここで重要なのは向きであるため、符号だけを考慮する。値の符号が負であれば、ベクトルが対向していると判断できるため、線分内に存在すると判断できる。

範囲外になるケース


どちらも同じ方向を向いているため正値になっているのがわかる。

参照

コード
class App < Base
  def initialize
    super

    self.width, self.height = V[800, 200]

    a = window_wh * V[0.40, 0.45]  # => (320.0, 90.0)
    b = window_wh * V[0.60, 0.35]  # => (480.0, 70.0)
    c = window_wh * V[0.50, 0.97]  # => (400.0, 194.0)
    d = window_wh * V[0.50, 0.80]  # => (400.0, 160.0)

    @points.concat([a, b, c, d])

    @mode = 0
  end

  def button_down(id)
    super

    if id == Gosu::KB_Z
      @mode = @mode.next.modulo(2)
    end
  end

  def draw
    super

    a, b, c, d = @points
    cd = d - c
    ab = b - a
    ac = c - a
    t = ab.cross(ac) / cd.cross(ab)
    e = c.lerp(d, t)
    ae = e - a
    be = e - b

    vputs "a: #{a.round}"
    vputs "b: #{b.round}"
    vputs "e: #{e.round}"
    vputs "ae・be = #{ae.dot(be).round}"

    segment_draw(a, b, "a", "b")
    vector_draw(c, d, "", "")
    point_draw(c.lerp(d, t))
    arrow_head(c, c.lerp(d, t), "e")
    line_draw(a - ab * 100, b + ab * 100)

    if @mode == 1
      vector_draw(a, e, "", "", color: Gosu::Color::BLUE)
      vector_draw(b, e, "", "", color: Gosu::Color::RED)
    end
  end

  show
end

Discussion