LoginSignup
12
5

More than 5 years have passed since last update.

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