📐

内積と外積の特徴

2023/09/08に公開

内積

垂直なら0

左に傾くと正

右に傾くと負

外積

平行なら0

左に傾くと負

右に傾くと正

公式を確認する

内積と外積は、四則演算のみを使って求める方法と、平方根や三角関数を駆使して求める二つの方法がある。前者はイメージしにくいが速い。後者はイメージしやすいが遅い。それぞれの方法で計算して結果を確認する。

a = V[40, 80]
b = V[40, 20]
c = V[50, 50]
d = V[60, 45]
ab = b - a  # => (0, -60)
cd = d - c  # => (10, -5)

内積 (x1 * x2 + y1 * y2)

値は同じ方向を向いているほど大きくなる。言いかえると逆を向いているほど負の値になる。最初の真横を向いている図をイメージしてみると、同じ方向と逆の中間なので 0 になるのも納得できる。

cd.dot(ab)                                         # => 300
cd.length * ab.length * cos(cd.angle_between(ab))  # => 300.0000000000001

cd を ab に正射影したときの影の長さを求める場合は相手の方を正規化する。

cd.dot(ab.normalize)                                         # => 5.0
cd.length * ab.normalize.length * cos(cd.angle_between(ab))  # => 5.000000000000002

正規化した長さは 1 なので上の2つ目の式は、次のようにも書ける。

cd.length * cos(cd.angle_between(ab))  # => 5.000000000000002

これは三角関数の公式「底辺 = 斜辺 * cosθ」になっているのがわかる。

上の方法では一方を正規化したが、もし両方を正規化した場合は cd の長さを 1 としたときの割合になる。

cd.normalize.dot(ab.normalize)                                         # => 0.4472135954999579
cd.normalize.length * ab.normalize.length * cos(cd.angle_between(ab))  # => 0.447213595499958

これも2つ目の式の なんとか.normalize.length は 1 なので二つ目は次のように書ける。

cos(cd.angle_between(ab))  # => 0.44721359549995804

外積 (x1 * y2 - x2 * y1)

ただ符号が知りたい場合や、2つの線分を辺と考えたときにできる平行四辺形の面積を知りたいときはこれ。

cd.cross(ab)                                       # => -600
cd.length * ab.length * sin(cd.angle_between(ab))  # => -600.0

直立投影(cd の真横から強い光を当てたとき壁にできる影の長さ)を求める場合は相手側を正規化する。

cd.cross(ab.normalize)                                       # => -10.0
cd.length * ab.normalize.length * sin(cd.angle_between(ab))  # => -10.0

この2つ目の式は内積のときと似ていて「対辺 = 斜辺 * sinθ」になっている。

もし両方を正規化した場合は cd の長さを 1 としたときの割合になる。

cd.normalize.cross(ab.normalize)                                       # => -0.8944271909999159
cd.normalize.length * ab.normalize.length * sin(cd.angle_between(ab))  # => -0.8944271909999157

こちらも正規化した長さは 1 なので

sin(cd.angle_between(ab))  # => -0.8944271909999159

と書ける。

このように、イメージしにくいけど速い方法と、イメージしやすいけど遅い方法、どちらの方法を使っても同じ値になるのがわかる。また内積も外積も似たようなもので乱暴に言えば求めたいのが横か縦かの違いにすぎない。

右辺と左辺を入れ替えるには?

内積の場合は掛け算なので入れ替えても結果は同じ。

cd.dot(ab)  # => 300
ab.dot(cd)  # => 300

一方、外積は入れ替えると符号が反転する。

cd.cross(ab)  # => -600
ab.cross(cd)  # => 600

だからといって入れ替えられないわけではなく入れ替えた上で全体にマイナス符号をつければ同じ式になる。

cd.cross(ab)   # => -600
-ab.cross(cd)  # => -600

横からの投影の名称について

始点が同じ2つのベクトルからできる三角形(片方が直線で片方が斜辺とする)では、内積は底辺が求まり、外積は対辺が求まる。このとき底辺のことを「正射影」と呼ぶ。しかし外積で求まる対辺の名称がずっとわからず「真横から強い光を当てて壁にできる影やジャンプしたときの高さ」と言っているのだが長い。調べてみてもどうも名称が定まっておらず

  • Vertical Projection
  • 直立射影
  • 垂直射影

などと書いてある。

参照

コード
class App < Base
  def initialize
    super

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

    a = window_wh * V[0.40, 0.80]
    b = window_wh * V[0.40, 0.20]
    c = window_wh * V[0.50, 0.50]
    d = window_wh * V[0.60, 0.50]

    @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
    ab = b - a
    cd = d - c

    if @mode == 0
      vputs "内積"
      vputs "ab・cd = #{ab.dot(cd).round}"
      vputs "|ab||cd|cosθ = #{(ab.length * cd.length * cos(ab.angle_between(cd))).round}"
    end

    if @mode == 1
      vputs "外積"
      vputs "ab×cd = #{ab.cross(cd).round}"
      vputs "|ab||cd|sinθ = #{(ab.length * cd.length * sin(ab.angle_between(cd))).round}"
    end

    vector_draw(a, b, "a", "b")
    vector_draw(c, d, "c", "d")
  end

  def font_size_default
    16
  end

  show
end

Discussion