12
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Swift愛好会Advent Calendar 2016

Day 21

Swift で 2つの線分の交点を求める

Last updated at Posted at 2016-12-21

本当はもう少しまともなのを書きたかったのですが、タイムアウトで間に合わなかったので、ありあわせのネタでお茶を濁します。

さて Swift で2点を交差する判定もしくは、その交差した点を求める必要が発生しました。やりたいことはこんな三角形の塊を作りたかったからです。

Screen Shot 2016-12-22 at 14.28.46.png

自分で一から考えるのはなんなので、元になるコードを探しましたが、あまり気に入ったコードは見つかりませんでした。その中で良さそうな C++ のコードをベースに Swift で清書しました。元ネタへのリンクは末尾に記載します。

外積には「×」オペレータを定義しました。誤差の判定に使っているエプシロン「ε」の扱いは微妙に思います。そして、以下がそのソースコードになります。

import Foundation
import CoreGraphics


infix operator ×


extension CGPoint {

	static func - (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
		return CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
	}

	static func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
		return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
	}

	static func * (lhs: CGPoint, rhs: CGFloat) -> CGPoint {
		return CGPoint(x: lhs.x * rhs, y: lhs.y * rhs)
	}

	static func / (lhs: CGPoint, rhs: CGFloat) -> CGPoint {
		return CGPoint(x: lhs.x / rhs, y: lhs.y / rhs)
	}
	
	static func * (lhs: CGPoint, rhs: CGPoint) -> CGFloat { // dot product
		return lhs.x * rhs.x + lhs.y * rhs.y
	}

	static func × (lhs: CGPoint, rhs: CGPoint) -> CGFloat { // cross product
		return lhs.x * rhs.y - lhs.y * rhs.x
	}
	
	var length²: CGFloat {
		return (x * x) + (y * y)
	}

	var length: CGFloat {
		return sqrt(self.length²)
	}

	var normalized: CGPoint {
		let length = self.length
		return CGPoint(x: x/length, y: y/length)
	}

}


struct Line {
	var from: CGPoint
	var to: CGPoint
	
	init(from: CGPoint, to: CGPoint) {
		self.from = from
		self.to = to
	}
	
	var vector: CGPoint { return to - from }
	var length: CGFloat { return (to - from).length }

	static func intersection(_ line1: Line, _ line2: Line, _ segment: Bool) -> CGPoint? {
		let v = line2.from - line1.from
		let v1 = line1.to - line1.from
		let v2 = line2.to - line2.from
		let cp = v1 × v2
		if cp == 0 { return nil }

		let cp1 = v × v1 // cross product
		let cp2 = v × v2 // cross product
		
		let t1 = cp2 / cp
		let t2 = cp1 / cp
		let ε = CGFloat(0).nextUp
		if segment {
			if t1 + ε < 0 || t1 - ε > 1 || t2 + ε < 0 || t2 - ε > 1 { return nil }
		}
		return line1.from + v1 * t1
	}

	static func angle(_ line1: Line, _ line2: Line) -> CGFloat {
		let a = line1.to - line1.from
		let b = line2.to - line2.from
		return atan2(b.y - a.y, b.x - a.x)
	}

}

Line は線分を示します。そして、intersection() に二つの線分を与えて呼び出します。三番目のパラメータは直線か線分かを Bool で指定します。延長線上で交差していても実際に交点がある場合に、その交点を戻す場合は true を、そして実際に交点がなくても、延長線上で交差している場合はその座標が欲しい場合は false を戻します。

以下に簡単ですがサンプルコードになります。第3引数の違いが結果に反映されている事が確認できるかと思います。

let p1 = CGPoint(x: 0, y: -200)
let p2 = CGPoint(x: 0, y: -100)
let p3 = CGPoint(x: 100, y: 0)
let p4 = CGPoint(x: 300, y: 0)

let l1 = Line(from: p1, to: p2)
let l2 = Line(from: p3, to: p4)

Line.intersection(l1, l2, true) // nil
Line.intersection(l1, l2, false) // {x 0 y 0}

元ネタのコードはこちらです。
http://marupeke296.com/COL_2D_No10_SegmentAndSegment.html

そして、gist からも入手可能になっています。
https://gist.github.com/codelynx/80077dbbb07e7d989016188573eab880

[執筆時点での環境の表記]

Xcode Version 8.2.1 (8C1002)
Apple Swift version 3.0.2 (swiftlang-800.0.63 clang-800.0.42.1)
12
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?