【Swift】平面座標系である点がある線分上にあるかを評価する方法


解説

タイトルの通りです。
環境:Xcode12.5.1

import Foundation

struct RectangularCoordinateSystemPoint {
    let x: Double
    let y: Double

    init(_ x: Double, _ y: Double) {
        self.x = x
        self.y = y
    }
}

extension RectangularCoordinateSystemPoint {
    // A--P-B という関係なら AP + PB < AB は成り立たないことを用いる 
    // ※ ここで AP + PB = AB で評価すると Double の端数で等式が成り立たなくなるので .ulpOfOne を用いる
    func isIncludeLineSegment(consistingOf endPoint: (a: RectangularCoordinateSystemPoint, b: RectangularCoordinateSystemPoint)) -> Bool {
        let distanceToA = self.distance(to: endPoint.a)
        let distanceToB = self.distance(to: endPoint.b)
        let distanceAB = endPoint.a.distance(to: endPoint.b)
        return distanceToA + distanceToB - distanceAB < .ulpOfOne
    }

    func distance(to target: RectangularCoordinateSystemPoint) -> Double {
        return sqrt(self.diffX(to: target) * self.diffX(to: target) + self.diffY(to: target) * self.diffY(to: target)) // 二乗はpow()でもよいがDouble型の指定があるため使いづらいため敬遠
    }

    func diffX(to target: RectangularCoordinateSystemPoint) -> Double {
        return self.x - target.x
    }

    func diffY(to target: RectangularCoordinateSystemPoint) -> Double {
        return self.y - target.y
    }
}

extension RectangularCoordinateSystemPoint {
    // 別の評価方法(問題があるかもしれません)
    func isIncludeLineSegmentOther(consistingOf endPoint: (a: RectangularCoordinateSystemPoint, b: RectangularCoordinateSystemPoint)) -> Bool {
        if (endPoint.a.x <= self.x && self.x <= endPoint.b.x) || (endPoint.b.x <= self.x && self.x <= endPoint.a.x) {
            if (endPoint.a.y <= self.y && self.y <= endPoint.b.y) || (endPoint.b.y <= self.y && self.y <= endPoint.a.y) {
                if (self.y * (endPoint.a.x - endPoint.b.x)) + (endPoint.a.y * (endPoint.b.x - self.x)) + (endPoint.b.y * (self.x - endPoint.a.x)) == 0 {
                    // 点Pが線分AB上にある
                    return true
                }
            }
        }
        return false
    }
}

/// テスト
/// y = 2x + 1
/// 5点列挙:(0, 1), (1, 3), (2,5), (3,7), (4,8)
/// (1,3) と (3, 7) の線分で考える
let point01 = RectangularCoordinateSystemPoint(0,1)
let point13 = RectangularCoordinateSystemPoint(1,3)
let point25 = RectangularCoordinateSystemPoint(2,5)
let point37 = RectangularCoordinateSystemPoint(3,7)
let point48 = RectangularCoordinateSystemPoint(4,8)
let point00 = RectangularCoordinateSystemPoint(0,0) // 直線上にないその他の点

/// isIncludeLineSegmentでのテスト
print(point01.isIncludeLineSegment(consistingOf: (point13, point37))) // false
print(point13.isIncludeLineSegment(consistingOf: (point13, point37))) // true
print(point25.isIncludeLineSegment(consistingOf: (point13, point37))) // true
print(point37.isIncludeLineSegment(consistingOf: (point13, point37))) // true
print(point48.isIncludeLineSegment(consistingOf: (point13, point37))) // false
print(point00.isIncludeLineSegment(consistingOf: (point13, point37))) // false

/// isIncludeLineSegmentOtherでのテスト
print(point01.isIncludeLineSegmentOther(consistingOf: (point13, point37))) // false
print(point13.isIncludeLineSegmentOther(consistingOf: (point13, point37))) // true
print(point25.isIncludeLineSegmentOther(consistingOf: (point13, point37))) // true
print(point37.isIncludeLineSegmentOther(consistingOf: (point13, point37))) // true
print(point48.isIncludeLineSegmentOther(consistingOf: (point13, point37))) // false
print(point00.isIncludeLineSegmentOther(consistingOf: (point13, point37))) // false