サイトトップ

Director Flash 書籍 業務内容 プロフィール

Adobe Flash非公式テクニカルノート

2次元平面で2直線の交点を求める

ID: FN1112001 Product: Flash CS4 and above Platform: All Version: 10 and above/ActionScript 3.0

本稿では、2次元平面においてふたつの直線の交わる点を求めます。直線が1次関数で表されることについては、「直線の式(方程式)」をご参照ください。直線の1次関数は、Vector3Dオブジェクトで扱うことにします。


01 直線の式をVector3Dオブジェクトで表す
直線を表す1次関数は、傾きmとy切片nを使うと計算がしやすく、直線を描くにも便利です(図001)。ただし、y軸に平行な直線(x = 定数)が示せません。この直線は別に扱わなければならないのです。

y = mx + n

図001■傾きmでy切片がnの直線

つぎのようにふたつの係数(a、b)とひとつの定数(c)を使えば、y軸に平行な直線も含めて、直線が一般的に表せます。ただ、パラメータが増えるので計算は面倒になり、どのような直線になるのかが直感的に捉えにくくなります。

ax + by + c = 0

けれど、交点を求めるという目的からは、例外は少ないほど望ましいといえます。また、関数を定めてしまえば、計算の手間は初めにスクリプトを書くときだけです。したがって、上記の一般的な1次方程式のかたちを使うことにします。

さて、直線を表すためのオブジェクトを用意します。パラメータがちょうど3つあるので、Vector3Dオブジェクトが使えます[*1]。上記の1次方程式のa、b、cが変数だとすれば、直線のVector3Dオブジェクトはつぎのようにつくります。

var line:Vector3D = new Vector3D(a, b, c)

[*1] とくにVector3Dクラスのメソッドや、Vector3D.x/y/z以外のプロパティは使いません。ですから、ObjectインスタンスやNumberベース型のVectorオブジェクトを用いることも考えられます。汎用的につくるなら、クラスを定義して後述の関数はメソッドにしてしまえばよいでしょう。


02 直線を求める関数の定義
直線を表す一般的な1次方程式のふたつの係数および定数がわかれば、直線のVector3Dインスタンスは決まります。けれど前述のとおり、それは直感的にわかりにくいのが難点です。そこで、引数に渡した条件から直線のVector3Dオブジェクトが導ける関数を定めましょう。

[1] 傾きとy切片から直線を求める
もっとも、傾きがmでy切片はnの直線のVector3Dインスタンスをつくるのは、関数にするまでもありません。

y = mx + n
mx - y + n = 0

したがって、直線のVector3Dオブジェクトは、つぎのような引数を渡せば得られます。

var line:Vector3D = new Vector3D(m, -1, n)

[2] 傾きと1点から直線を求める
傾きと直線が通る1点の座標から直線のVector3Dオブジェクトを求める関数は、つぎのスクリプト001です。第1引数が傾き、第2引数と第3引数は通る点のxy座標で、すべて数値です。

スクリプト001■傾きと直線が通る1点の座標から直線のVector3Dオブジェクトを求める
  1. function xGetLineFromSlope(nSlope:Number, nX0:Number, nY0:Number):Vector3D {
  2.   var line:Vector3D = new Vector3D();
  3.   var intercept:Number = nY0 - nX0 * nSlope;
  4.   line.x = nSlope;
  5.   line.y = -1;
  6.   line.z = intercept;
  7.   return line;
  8. }

スクリプト001の関数(xGetLineFromSlope())では、直線の傾きは引数(nSlope)として与えられているので、y切片さえわかれば前述[1]にしたがって直線のVector3Dオブジェクトが求められます。第2および第3引数(nX0とnY0)も用いて、そのy切片(intercept)を導いているのが第3行目です。

[3]通過する2点から直線を求める
直線の通る2点から直線のVector3Dオブジェクトを求める関数が、つぎのスクリプト002です。4つの引数はすべて数値で、通過する2点のxy座標を順に渡します。

スクリプト002■直線が通る2点の座標から直線のVector3Dオブジェクトを求める
  1. function xGetLine(nX0:Number, nY0:Number, nX1:Number, nY1:Number):Vector3D {
  2.   var line:Vector3D;
  3.   var nDeltaX:Number = nX1 - nX0;
  4.   if (nDeltaX) {
  5.     var nDeltaY:Number = nY1 - nY0;
  6.     var nSlope:Number = nDeltaY / nDeltaX;
  7.     line = xGetLineFromSlope(nSlope, nX0, nY0);
  8.   } else {
  9.     line = new Vector3D(1, 0, -nX0);
  10.   }
  11.   return line;
  12. }

スクリプト002の関数(xGetLine())は、2点のxy座標から直線の傾き(nSlope)を求めます(第3および第5〜6行目)。そうすれば、前述[2]で定めた関数(xGetLineFromSlope())を呼出して、直線が求められます(第7行目)。なお、2点を通る直線の方程式の導き方については、前出「直線の式(方程式)」の2「指定された点を通る直線の式」をご参照ください。

ただし、傾きの分母となるx座標の増分(nDeltaX)が0のときは、傾きが求まらず、直線はy軸に平行(x = 定数)となります(スクリプト第8〜10行目)。


03 2直線の交点を求める関数の定義
本題であるふたつの直線の交点を求めます。定める関数は、2直線のVector3Dオブジェクトを引数にして、戻り値は交点のPointインスタンスとします。そして2直線の交点は、つぎの公式により求められます。公式の導き方については、後述04「2直線の交点を求める公式」で解説しました。

【2直線の交点】
a1x + b1y + c1 = 0
a2x + b2y + c2 = 0

ふたつの直線の交点(x, y)はつぎのとおり。ただし、a1b2≠a2b1

x = (b1c2 - b2c1) / (a1b2 - a2b1)
y = (a2c1 - a1c2) / (a1b2 - a2b1)

つぎのスクリプト003で定めた関数(xGetCrossPoint())は、引数に受取ったふたつの直線のVector3Dオブジェクトから交点をPointオブジェクトで返します。第9〜10行目をご覧いただければわかるとおり、2直線の交点の公式をそのまま当てはめました。

スクリプト003■ふたつの直線のVector3Dオブジェクトから交点をPointオブジェクトで求める
  1. function xGetCrossPoint(line0:Vector3D, line1:Vector3D):Point {
  2.   var myPoint:Point;
  3.   var a1:Number = line0.x;
  4.   var b1:Number = line0.y;
  5.   var c1:Number = line0.z;
  6.   var a2:Number = line1.x;
  7.   var b2:Number = line1.y;
  8.   var c2:Number = line1.z;
  9.   var nX:Number = (b1 * c2 - b2 * c1) / (a1 * b2 - a2 * b1);
  10.   var nY:Number = (a2 * c1 - a1 * c2) / (a1 * b2 - a2 * b1);
  11.   myPoint = new Point(nX, nY);
  12.   return myPoint;
  13. }

それでは、関数を試すために、ステージに正三角形を描いてみます。正三角形は、底辺の両端から底角60度の直線を伸ばし、交点を結べばつくれます。

なお、角度θの直線の傾きはtanθになります。距離が1で角度θのxy座標は(cosθ, sinθ)です(図002)。したがって、この直線の傾きはsinθ/cosθ = tanθだからです(「角度と座標の計算 − Flash の三角関数を使う」2「tanと逆関数」参照)。

図002■距離が1で角度θのxy座標は(cosθ, sinθ)
図002

以下のスクリプト004は、座標(50, 120)を底辺の左端として、1辺100ピクセルの正三角形を描きます。底角60度の直線はスクリプト001の傾きと直線が通る1点の座標から直線のVector3Dオブジェクトを求める関数(xGetLineFromSlope())で定め、スクリプト003のふたつの直線のVector3Dオブジェクトから交点をPointオブジェクトで求める関数(xGetCrossPoint())により正三角形の頂点を導きました。

スクリプト004■ステージに正三角形を描く
  1. const DEGREES_TO_RADIANS:Number = Math.PI / 180;
  2. var nLength:Number = 100;
  3. var pointA:Point = new Point(50, 120);
  4. var pointB:Point = new Point(pointA.x + nLength, pointA.y);
  5. var nSlopeAC:Number = Math.tan(-60 * DEGREES_TO_RADIANS);
  6. var lineAC:Vector3D = xGetLineFromSlope(nSlopeAC, pointA.x, pointA.y);
  7. var lineBC:Vector3D = xGetLineFromSlope(-nSlopeAC, pointB.x, pointB.y);
  8. var pointC:Point = xGetCrossPoint(lineAC, lineBC);
  9. var canvas:Sprite = new Sprite();
  10. var myGraphics:Graphics = canvas.graphics;
  11. addChild(canvas);
  12. myGraphics.lineStyle(1, 0x0);
  13. myGraphics.moveTo(pointA.x, pointA.y);
  14. myGraphics.lineTo(pointB.x, pointB.y);
  15. myGraphics.lineTo(pointC.x, pointC.y);
  16. myGraphics.lineTo(pointA.x, pointA.y);

スクリプト004は、まず座標(50, 120)と水平に100ピクセル右の座標で三角形の底辺を定めます(第3〜4行目)。ふたつの座標はPointオブジェクト(pointAおよびpointB)にしました。つぎに、その両端から底角60度の直線をそれぞれ(関数xGetLineFromSlope()から)求めています(第5〜7行目)。そして、それら2直線の交点を(関数xGetCrossPoint()で)求めれば、正三角形の頂点(pointC)が定まります(第8行目)。

そのうえで、求めた正三角形の3頂点を直線で結びました(図003)。描画用にSpriteインスタンスをつくり、Graphicsクラスの描画メソッドで線描しています(スクリプト004第9〜16行目)。Graphicsクラスの描画メソッドによる直線の描き方については、「Vector3Dクラスの3次元空間座標とインスタンスへの描画」の「Spriteインスタンスに直線を描く」をお読みください。

図003■正三角形の3頂点を定めて線描する
図003


04 2直線の交点を求める公式
ふたつの直線の交点の求め方について、簡単に数学的な解説を加えます。まず、2直線をつぎのふたつの方程式で定めます。

a1x + b1y + c1 = 0 … (1)
a2x + b2y + c2 = 0 … (2)

式(1)と(2)の両辺にそれぞれb2とb1を乗じると、(3)と(4)の式になります。

a1b2x + b1b2y + b2c1 = 0 … (3)
a2b1x + b1b2y + b1c2 = 0 … (4)

式(3)から(4)を差引くと、つぎのようにxが導かれます。

(a1b2 - a2b1)x + (b2c1 - b1c2) = 0
x = (b1c2 - b2c1) / (a1b2 - a2b1)   ただし、a1b2≠a2b1

同様に、式(1)と(2)の両辺にそれぞれa2とa1を乗じて、式(5)と(6)を得ます。

a1a2x + a2b1y + a2c1 = 0 … (5)
a1a2x + a1b2y + a1c2 = 0 … (6)

式(5)から(6)を差引けば、yが求められます。

(a2b1 - a1b2)y + (a2c1 - a1c2) = 0
y = (a2c1 - a1c2) / (a1b2 - a2b1)   ただし、a1b2≠a2b1

つぎに、a1b2 = a2b1の場合について考えます。b1 = 0のとき、a1 = 0ですと式(1)が直線を表さなくなります。したがって、b2 = 0でなければなりません。

a1b2 = a2b1で、b1 = 0ならb2 = 0

つまり、2直線の式はつぎの(1)'と(2)'のようになり、ともにy軸に平行です。

a1x + c1 = 0 … (1)'
a2x + c2 = 0 … (2)'

b1≠0のとき、もしb2 = 0ですと、a1b2 = a2b1 = 0となり、a2 = 0でなければなりません。すると式(2)が直線を表しません。したがって、b2≠0になりますので、つぎの等式が成立ちます。

a1/b1 = a2/b2

この両辺は、それぞれ式(1)と(2)の直線の傾きを表します。つまり2直線の傾きが等しいので、互いに平行です。以上から、つぎの結論が導けます。

a1b2 = a2b1のとき2直線は互いに平行

したがって、2直線の交点は存在しません。まとめると、2直線の交点はつぎのようになります。

【2直線の交点】
a1x + b1y + c1 = 0
a2x + b2y + c2 = 0

a1b2≠a2b1のとき、ふたつの直線の交点(x, y)はつぎのとおり。

x = (b1c2 - b2c1) / (a1b2 - a2b1)
y = (a2c1 - a1c2) / (a1b2 - a2b1)

a1b2 = a2b1のとき、ふたつの直線は互いに平行で、交点は存在しない。

なお、前掲スクリプト003で定めた2直線の交点を求める関数(xGetCrossPoint())は、交点が存在しないとき第9〜10行目の右辺の分母がともに0になります。そのため、戻り値のPointオブジェクトは、Point.xおよびPoint.yプロパティの値がともにNaNになります。


作成者: 野中文雄
作成日: 2011年12月22日


Copyright © 2001-2011 Fumio Nonaka.  All rights reserved.