Учитывая следующие определения:
type Point = (Double, Double)
implicit class PointOps(p: Point) {
def +(other: Point) = (p._1 + other._1, p._2 + other._2)
def -(other: Point) = (p._1 - other._1, p._2 - other._2)
def dot(other: Point) = p._1 * other._1 + p._2 * other._2
def *(scalar: Double) = (p._1 * scalar, p._2 * scalar)
def normSquare: Double = p._1 * p._1 + p._2 * p._2
}
, означая, что
a + b // is vector addition
a - b // is vector subtraction
a dot b // is the dot product (scalar product)
a * f // is multiplication of a vector `a` with a scalar factor `f`
a.normSquare // is the squared length of a vector
вы получаете проекцию точки p
на линии, проходящей через точки line1
и line2
следующим образом:
/** Projects point `p` on line going through two points `line1` and `line2`. */
def projectPointOnLine(line1: Point, line2: Point, p: Point): Point = {
val v = p - line1
val d = line2 - line1
line1 + d * ((v dot d) / d.normSquare)
}
Пример:
println(projectPointOnLine((-1.0, 10.0), (7.0, 4.0), (6.0, 11.0)))
дает
(3.0, 7.0)
Это работает в 3D (или nD) точно так же.
За этим стоит некоторая математика (Как вывести ее с нуля)
(обозначения, как указано выше)
У нас есть триточки: l1
и l2
для линии и p
для целевой точки.Мы хотим проецировать точку p
ортогонально на линию, проходящую через l1
и l2
(при условии l1 != l2
).
Пусть d = l2 - l1
будет направлением от l1
до l2
.Тогда каждая точка на линии может быть представлена как
l1 + d * t
с некоторым скалярным коэффициентом t
.Теперь мы хотим найти t
такой, чтобы вектор, соединяющий p
и l1 + d * t
, был ортогонален d
, то есть:
(p - (l1 + d * t)) dot d == 0
Напомним, что
(v1 + v2) dot v3 = (v1 dot v3) + (v2 dot v3)
для всех векторов v1, v2, v3
, а для
(v1 * s) dot v2 = (v1 dot v2) * s
для скалярных факторов s
.Используя это и определение v.normSquared = v dot v
, мы получаем:
(p - l1 - d * t) dot d
= (p - l1) dot d - (d dot d) * t
= (p - l1) dot d - d.normSquare * t
, и это должно стать 0
.Разрешение для t
дает:
t = ((p - l1) dot d) / d.normSquare
, и именно эта формула используется в коде.
(Спасибо SergGr за добавление начального эскиза деривации)