Один из способов решения этой проблемы - использование скалярного произведения для определения косинуса угла между двумя векторами:
def line_test(a, b, p):
v_ap = tuple(m - n for n, m in zip(a, p))
v_ab = tuple(m - n for n, m in zip(a, b))
scp = sum(m * n for m, n in zip(v_ap, v_ab))
return scp > 0 and scp * scp == sum(n * n for n in v_ap) * sum(n * n for n in v_ab) and all(m <= n for m, n in zip(v_ap, v_ab))
Параметры вышеупомянутой функции - это конечные точки линии (a
и b
) и точка p
(c
на изображении), которую мы хотим проверить.
Шаг за шагом в каждой строке происходит следующее:
v_ap = tuple(m - n for n, m in zip(a, p))
Мы вычисляем вектор от a
до p
(v_ap
) v_ab = tuple(m - n for n, m in zip(a, b))
Вектор от a
до b
(v_ab
) scp = sum(m * n for m, n in zip(v_ap, v_ab))
В этой строке скалярное произведение v_ap
и v_ab
рассчитывается.В результате получается scp = cos(v_ab, v_ap) * euclidean_length(v_ab) * euclidean_length(v_ap)
, где евклидова длина вектора определяется как sqrt(sum(n * n for n in vector))
(стандартное определение геометрической длины вектора). return scp > 0 and scp * scp == sum(n * n for n in v_ap) * sum(n * n for n in v_ab) and all(m <= n for m, n in zip(v_ap, v_ab)
Эта строка довольно сложнапоэтому я разбью его на несколько частей:
scp * scp == sum(n * n for n in v_ap) * sum(n * n for n in v_ab)
Поскольку деление недопустимо, мы также не должны использовать квадратный корень, так как обычно это вычислениевключает в себя подразделения.Таким образом, вместо вычисления квадратного корня, мы берем квадрат как евклидовой длины обоих векторов, так и скалярного произведения, тем самым исключая вычисление квадратного корня:
scp = cos(v_ab, v_ap) * euclidean_length(v_ab) * euclidean_length(v_ap) =
= cos(v_ab, v_ap) * sqrt(sum(n ^ 2 for n in v_ab)) * sqrt(sum(n ^ 2 for n in v_ap))
scp ^ 2 = cos(v_ab, v_ap) ^ 2 * sum(n ^ 2 for n in v_ab) * sum(n ^ 2 for n in v_ap)
Косинус угла междудва вектора должны быть 1, если они указывают в одном направлении.Таким образом, квадрат скалярного произведения, если векторы имеют одинаковое направление, будет
euclidean_length(v_ap) ^ 2 * euclidean_length(v_ab) ^ 2
, который мы затем сравним с фактическим скалярным произведением scp
.
Это, однако, оставляет одну проблему:взятие квадрата исключает знак, который мы проверяем отдельно при сравнении scp > 0
.Поскольку евклидова длина всегда положительна, только знак косинуса определяет значение scp
.Отрицательное значение scp
означает, что угол между v_ap
и v_ab
составляет не менее pi / 4
и не более pi * 3/4
.Однако знак scp
get теряется при возведении в квадрат, что означает, что мы можем только проверить, параллельны ли два вектора, а не если они указывают в одном направлении.Эта проблема решается проверкой scp > 0
дополнительно.
И наконец, что не менее важно, мы должны проверить, меньше ли расстояние от a
до p
, чем расстояние от a
до b
.Это можно сделать, проверив, имеет ли v_ap
меньшую длину, чем v_ab
.Поскольку мы уже проверили, что два вектора указывают в одном и том же направлении, достаточно проверить, все ли элементы в v_ap
не больше, чем соответствующий элемент в v_ab
, что делается с помощью
all(m <= n for m, n in zip(v_ap, v_ab))