Допустим, у вас есть двухмерная плоскость с 2 точками (называемыми a и b), представленная целым числом x и целым числом y для каждой точки.
Как определить, находится ли другая точка c на отрезке, определяемом элементами a и b?
Я чаще всего использую python, но примеры на любом языке были бы полезны.
Представлена ли точка c двумя целыми числами? Если да, то хотите ли вы знать, находится ли c точно вдоль реальной прямой линии между a и b или лежит на растровой аппроксимации прямой линии между a и b? Это важное уточнение.
Здесь задан аналогичный вопрос: stackoverflow.com/q/31346862/1914034 с решением, когда необходимо буферное расстояние от линии
Связанный: Определите, находится ли точка Shapely в LineString / MultiLineString
Предупреждение для будущих читателей: изрядное количество ответов неверны или неполны. Несколько крайних случаев, которые часто не работают, - это горизонтальные и вертикальные линии.






Проверьте, является ли перекрестное произведение b-a и c-a0: это означает, что все точки коллинеарны. Если да, проверьте, находятся ли координаты c между a и b. Используйте координаты x или y, если a и b разделены на этой оси (или они одинаковы на обеих).
def is_on(a, b, c):
"Return true iff point c intersects the line segment from a to b."
# (or the degenerate case that all 3 points are coincident)
return (collinear(a, b, c)
and (within(a.x, c.x, b.x) if a.x != b.x else
within(a.y, c.y, b.y)))
def collinear(a, b, c):
"Return true iff a, b, and c all lie on the same line."
return (b.x - a.x) * (c.y - a.y) == (c.x - a.x) * (b.y - a.y)
def within(p, q, r):
"Return true iff q is between p and r (inclusive)."
return p <= q <= r or r <= q <= p
Раньше этот ответ состоял из трех обновлений. Полезная информация от них: глава Брайана Хейса в Красивый код охватывает пространство дизайна для функции проверки коллинеарности - полезный фон. Винсент ответ помог улучшить это. И именно Хейс предложил проверить только одну из координат x или y; изначально код имел and вместо if a.x != b.x else.
Поскольку проверка диапазона выполняется быстрее, было бы лучше сначала проверить диапазон, а затем проверить коллинеарность в ограничивающей рамке.
Функция is_on (a, b, c) неверна для случая, когда a == b! = C. В таком случае он вернет true, даже если c не пересекает отрезок прямой от a до b.
@SuperFlux, я просто попробовал запустить это, и получил False.
Я думаю, что этот ответ явно превосходит текущий принятый ответ.
поэтому в основном, если (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y) == 0, то они коллинеарны. Но что означают отрицательные и положительные значения?
@JohnDemetriou, это ориентированная область треугольника через a, b и c. «Область» может быть положительной или отрицательной, в зависимости от того, идет ли трассировка в этом порядке по часовой стрелке или против часовой стрелки (но сейчас мне лень говорить вам, в каком направлении будет положительный результат, а какой - отрицательный).
@ MikkoVirkkilä: return (p <= q <= r or r <= q <= p) and (p != r or p == q == r)
collinear (a, b, c) проверяет числа с плавающей запятой на равенство. Разве не следует использовать эпсилон / толерантность?
Убедитесь, что перекрестное произведение (b-a) и (c-a) равно 0, как говорит Дариус Бэкон, сообщает вам, выровнены ли точки a, b и c.
Но, поскольку вы хотите знать, находится ли c между a и b, вы также должны проверить, что скалярное произведение для (b-a) и (c-a) равно положительный и меньше, чем квадрат расстояния между a и b.
В неоптимизированном псевдокоде:
def isBetween(a, b, c):
crossproduct = (c.y - a.y) * (b.x - a.x) - (c.x - a.x) * (b.y - a.y)
# compare versus epsilon for floating point values, or != 0 if using integers
if abs(crossproduct) > epsilon:
return False
dotproduct = (c.x - a.x) * (b.x - a.x) + (c.y - a.y)*(b.y - a.y)
if dotproduct < 0:
return False
squaredlengthba = (b.x - a.x)*(b.x - a.x) + (b.y - a.y)*(b.y - a.y)
if dotproduct > squaredlengthba:
return False
return True
-epsilon < crossproduct < epsilon and min(a.x, b.x) <= c.x <= max(a.x, b.x) and min(a.y, b.y) <= c.y <= max(a.y, b.y) достаточно, не правда ли?
Да, глупый я. Это ответ Шридхара Айера с перекрестным произведением вместо наклонов. Как я уже сказал, есть несколько возможных ответов. :)
Абсолютное значение перекрестного произведения в два раза больше площади треугольника, образованного тремя точками (со знаком, указывающим сторону третьей точки), поэтому ИМХО вам следует использовать эпсилон, который пропорционален расстоянию между двумя конечными точками.
Это работает в реальном пространстве, а не в целочисленном, как просил плакат.
Можете ли вы сказать нам, почему это не работает с целыми числами? Я не вижу проблемы при условии, что эпсилон-проверка заменена на "! = 0".
Потому что либо а) приближенная линия между двумя точками важна, и эти векторы не совпадают в реальном выражении, либо б) вас интересует только точное совпадение, и в этом случае я уверен, что вы могли бы уменьшить его до наибольшего общего множителя тип расчета.
После проверки условия перекрестного произведения проще проверить, что (c-a). (B-c)> 0 (с равенством, когда c = a или c = b); это просто говорит о том, что параллельные векторы c-a и b-c должны указывать в одном направлении.
Я думаю, что это искажает мой ответ, который также проверяет условие промежуточности.
Это только я или что-то не хватает в выражении для squaredlengthba? Почему лишние скобки?
Да, лишние скобки - это всего лишь опечатка. Прошло 4 года, прежде чем кто-то что-то сказал. :)
Вам следует переименовать a, b, c, чтобы было понятнее, какие точки являются конечными точками сегмента, а какие - точкой запроса.
Разве это не должно быть if abs(crossproduct) < epsilon: Из-за того простого факта, что это может привести к 0-делению, если перекрестное произведение равно 0
Какое значение имеет эпсилон? Очень маленькое число, которое нужно учитывать при ошибках с плавающей запятой?
Я не получаю перекрестное произведение как ноль при использовании точек (.5,0), (2,1), (1.6, .6). Перекрестное произведение в -0,2. Что могло быть не так? Я использую следующий код для тестирования: python ax,ay = (.5,0) bx,by = (2,1) cx,cy = (1.6,.6) crossproduct = (cy - ay) * (bx - ax) - (cx - ax) * (by - ay)
@AmarC: эти 3 точки не совпадают, вот почему. Если бы C был (1,4, .6), например, это было бы 0.
если скалярное произведение равно 0, это означает, что угол равен 90 ° => два вектора ортогональны, и поэтому точки не находятся на одной линии. Разве мы не должны проверять dotproduct <= 0 вместо <0? На первом этапе мы уже проверили, что они лежат в одной строке, но не будет ли <= немного логичнее? Или у меня ошибочное предположение?
Проверка на квадрат длины я еще не поняла. Почему это предположение верно? Точечный результат дает только указание угла между двумя векторами, который должен быть ~ 0 °, когда мы делим результат по его длине и используем arccos, поскольку два вектора параллельны. Так почему же это работает, если я сравниваю квадрат длины ba (AB²) с скалярным произведением двух векторов (AC и AB)?
Скалярное произведение между (c-a) и (b-a) должно быть равно произведению их длин (это означает, что векторы (c-a) и (b-a) выровнены и имеют одинаковое направление). Более того, длина (c-a) должна быть меньше или равна длине (b-a). Псевдокод:
# epsilon = small constant
def isBetween(a, b, c):
lengthca2 = (c.x - a.x)*(c.x - a.x) + (c.y - a.y)*(c.y - a.y)
lengthba2 = (b.x - a.x)*(b.x - a.x) + (b.y - a.y)*(b.y - a.y)
if lengthca2 > lengthba2: return False
dotproduct = (c.x - a.x)*(b.x - a.x) + (c.y - a.y)*(b.y - a.y)
if dotproduct < 0.0: return False
if abs(dotproduct*dotproduct - lengthca2*lengthba2) > epsilon: return False
return True
Разве последнее условие не должно быть больше похоже на: ABS (product - lengthca * lengthba) <epsilon?
Разве вам не следует вместо этого сравнивать квадраты длины? Следует избегать квадратных корней. Кроме того, если это неизбежно из-за переполнения, вы можете использовать math.hypot вместо math.sqrt (с соответствующим изменением аргументов).
Меня тоже интересует этот эпсилон. Вы можете это объяснить? Конечно, если мы должны иметь дело с числами с плавающей запятой, мы должны быть осторожны с сравнениями, но мне не ясно, почему эпсилон делает это конкретное сравнение более точным.
Я согласен. На этот вопрос есть несколько хороших ответов, и это нормально. Но этот код нужно изменить, чтобы не использовать sqrt, и последнее сравнение исправлено.
@Jonathan: действительно, код более знакомый и элегантный, используя abs. Спасибо.
Что касается эпсилон-материала: это просто вопрос отказа от использования == для сравнения чисел с плавающей запятой. Никогда не используйте x == y с числами с плавающей запятой. Вместо этого вы должны использовать abs (x-y) <= epsilon (small). В исправленном коде я использую> epsilon для выражения! =.
Обратите внимание, что мне не нравятся решения, в которых используются кросс-продукты. Точечное произведение и / или расстояние работают также в 4-мерном или n-мерном пространстве, а перекрестное произведение (как операция над двумя векторами) - нет.
Теперь он больше не использует sqrt.
Извините, мой эпсилон-вопрос был глупым; Мне удалось запутать ваше равенство и сравнение меньше чем. Это хороший вопрос об обобщении на более высокие измерения; Я догадываюсь, что вы могли бы ответить на этот вопрос с помощью внешнего продукта, но я не могу понять это сейчас.
Мне понравилась идея сделать это без дорогостоящей операции извлечения квадратного корня, но я только что на собственном опыте убедился, что это не работает. У нас есть три точки: ab равно 4, bc равно 4, ac равно 6. a + b> c (4 + 4> 6) - это не линия. И все же a ^ 2 + b ^ 2 <c ^ 2 (16 + 16 <36).
Вот еще один подход:
Точка C (x3, y3) будет находиться между A и B, если:
При этом не учитываются ошибки округления (неточность координат).
Я думаю, это правильная идея, но в ней мало деталей (как мы можем проверить это уравнение на практике?) И немного ошибочно: последним y3 должно быть y2.
Используя более геометрический подход, рассчитайте следующие расстояния:
ab = sqrt((a.x-b.x)**2 + (a.y-b.y)**2)
ac = sqrt((a.x-c.x)**2 + (a.y-c.y)**2)
bc = sqrt((b.x-c.x)**2 + (b.y-c.y)**2)
и проверьте, равно ли ac + bcab:
is_on_segment = abs(ac + bc - ab) < EPSILON
Это потому, что есть три возможности:
Как упоминает Джонатан Леффлер в другом комментарии, у этого есть числовые проблемы, которых избегают другие подходы, такие как перекрестное произведение. Глава, на которую я ссылаюсь в своем ответе, объясняет.
Вот как бы я это сделал:
def distance(a,b):
return sqrt((a.x - b.x)**2 + (a.y - b.y)**2)
def is_between(a,c,b):
return distance(a,c) + distance(c,b) == distance(a,b)
Единственная проблема с этим - числовая стабильность - учет разностей чисел и т. д. Может привести к потере точности.
-epsilon < (distance(a, c) + distance(c, b) - distance(a, b)) < epsilon@jfs, что вы имеете в виду? Для чего нужен чек с эпсилоном?
@NeonWarge: sqrt () возвращает число с плавающей запятой. == в большинстве случаев не подходит для поплавков.. Вместо этого можно использовать math.isclose(). В 2008 году не было math.isclose(), и поэтому я указал явное неравенство с epsilon (abs_tol на языке math.isclose()).
Это абсолютно правильно, однако этот метод не очень хорош даже с math.isclose (). Когда координаты являются целыми числами, вы хотели бы провести точный тест. Мой другой ответ дает формулу для этого: stackoverflow.com/a/29301940/40078
Вот как я это делал в школе. Я забыл, почему это плохая идея.
Обновлено:
@Darius Bacon: цитирует книгу "Красивый код", который содержит объяснение, почему приведенный ниже код не является хорошей идеей.
#!/usr/bin/env python
from __future__ import division
epsilon = 1e-6
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
class LineSegment:
"""
>>> ls = LineSegment(Point(0,0), Point(2,4))
>>> Point(1, 2) in ls
True
>>> Point(.5, 1) in ls
True
>>> Point(.5, 1.1) in ls
False
>>> Point(-1, -2) in ls
False
>>> Point(.1, 0.20000001) in ls
True
>>> Point(.1, 0.2001) in ls
False
>>> ls = LineSegment(Point(1, 1), Point(3, 5))
>>> Point(2, 3) in ls
True
>>> Point(1.5, 2) in ls
True
>>> Point(0, -1) in ls
False
>>> ls = LineSegment(Point(1, 2), Point(1, 10))
>>> Point(1, 6) in ls
True
>>> Point(1, 1) in ls
False
>>> Point(2, 6) in ls
False
>>> ls = LineSegment(Point(-1, 10), Point(5, 10))
>>> Point(3, 10) in ls
True
>>> Point(6, 10) in ls
False
>>> Point(5, 10) in ls
True
>>> Point(3, 11) in ls
False
"""
def __init__(self, a, b):
if a.x > b.x:
a, b = b, a
(self.x0, self.y0, self.x1, self.y1) = (a.x, a.y, b.x, b.y)
self.slope = (self.y1 - self.y0) / (self.x1 - self.x0) if self.x1 != self.x0 else None
def __contains__(self, c):
return (self.x0 <= c.x <= self.x1 and
min(self.y0, self.y1) <= c.y <= max(self.y0, self.y1) and
(not self.slope or -epsilon < (c.y - self.y(c.x)) < epsilon))
def y(self, x):
return self.slope * (x - self.x0) + self.y0
if __name__ == '__main__':
import doctest
doctest.testmod()
Длина сегмента не важна, поэтому использование квадратного корня не требуется, и его следует избегать, поскольку мы можем потерять некоторую точность.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
class Segment:
def __init__(self, a, b):
self.a = a
self.b = b
def is_between(self, c):
# Check if slope of a to c is the same as a to b ;
# that is, when moving from a.x to c.x, c.y must be proportionally
# increased than it takes to get from a.x to b.x .
# Then, c.x must be between a.x and b.x, and c.y must be between a.y and b.y.
# => c is after a and before b, or the opposite
# that is, the absolute value of cmp(a, b) + cmp(b, c) is either 0 ( 1 + -1 )
# or 1 ( c == a or c == b)
a, b = self.a, self.b
return ((b.x - a.x) * (c.y - a.y) == (c.x - a.x) * (b.y - a.y) and
abs(cmp(a.x, c.x) + cmp(b.x, c.x)) <= 1 and
abs(cmp(a.y, c.y) + cmp(b.y, c.y)) <= 1)
Случайный пример использования:
a = Point(0,0)
b = Point(50,100)
c = Point(25,50)
d = Point(0,8)
print Segment(a,b).is_between(c)
print Segment(a,b).is_between(d)
Если c.x или c.y являются плавающими, то первый == в is_between() может выйти из строя (кстати, это замаскированный перекрестный продукт).
добавить в is_between(): a, b = self.a, self.b
Похоже, что это вернет истину, если все три точки одинаковы (что нормально, имхо), но ложь, если ровно две точки одинаковы - довольно непоследовательный способ определения промежуточности. Я разместил альтернативу в своем ответе.
исправил это с помощью другого трюка cmp, но этот код начинает пахнуть ;-)
Хорошо, много упоминаний линейной алгебры (перекрестное произведение векторов), и это работает в реальном (т.е. непрерывном или с плавающей запятой) пространстве, но в вопросе конкретно говорилось, что две точки были выражены как целые числа, и, таким образом, перекрестное произведение не является правильным решение, хотя оно может дать приблизительное решение.
Правильное решение - использовать Алгоритм линии Брезенхема между двумя точками и посмотреть, является ли третья точка одной из точек на линии. Если точки находятся на достаточно большом расстоянии, что вычисление алгоритма неэффективно (а для этого он должен быть действительно большим), я уверен, что вы могли бы покопаться и найти оптимизацию.
Он решает, как провести линию через двумерное целочисленное пространство между двумя произвольными точками, и это математически правильно. Если третья точка является одной из точек на этой линии, то она по определению находится между этими двумя точками.
Нет, алгоритм линии Брезенхема решает, как создать приближение сегмента линии в двумерном целочисленном пространстве. Из сообщения оригинального плаката я не вижу, что это был вопрос о растеризации.
«Допустим, у вас есть двухмерная плоскость с 2 точками (называемыми a и b), представленная x INTEGER и y INTEGER для каждой точки». (курсив добавлен мной).
Я думаю, что алгоритм линий Брезенхема дает целые точки шкафа для линии, которые затем можно использовать для рисования линии. Они могут не быть на связи. Например, если для значений от (0,0) до (11,13) алгоритм выдаст количество пикселей для рисования, но нет целых точек, кроме конечных точек, потому что 11 и 13 взаимно просты.
Как может решение, правильное для реального пространства (ℝ × ℝ), не быть правильным для целочисленного пространства (ℕ × ℕ), как ℕ∈ℝ. Или вы имеете в виду: «не оптимально для…» вместо «неправильно»?
как насчет того, чтобы просто убедиться, что наклон такой же, а точка находится между остальными?
заданные точки (x1, y1) и (x2, y2) (при x2> x1) и точка-кандидат (a, b)
если (b-y1) / (a-x1) = (y2-y2) / (x2-x1) и x1 <a <x2
Тогда (a, b) должен быть на линии между (x1, y1) и (x2, y2)
Как насчет сумасшедших проблем с точностью с плавающей запятой, когда некоторые координаты близки или идентичны?
Компьютеры плохо справляются с вычислениями с плавающей запятой. В компьютере нет такой вещи, как бесконечно плавно регулируемые значения. Итак, если вы используете плавающие точки, вы должны установить определение и использовать небольшое значение эпсилон в качестве детерминанта, и любые две точки, расположенные ближе, чем этот эпсилон, должны рассматриваться как одна и та же точка. Определите точку, которая находится на той же линии и на том же расстоянии от конечных точек. Если ваша кандидатная точка находится в пределах вашего эпсилона от этой вычисленной точки, назовите ее идентичной.
Я хотел сказать, что этот ответ непригоден для использования из-за проблем с точностью, когда вы фактически реализуете его в коде. Так что никто не должен его использовать. Прекрасный ответ на тест по математике. Но полный провал в компьютерном курсе. Я пришел сюда в поисках метода скалярного произведения (что верно); поэтому я подумал, что займу несколько минут, чтобы отметить многие ответы в этой цепочке, которые неверны, чтобы другие, знакомые с правильным решением, не испытали соблазна их использовать.
Вы правы в отношении проблем, возникающих из-за неспособности компьютеров представить все возможные действительные числа в строке. Вы ошибаетесь, что любое решение (включая метод скалярного произведения) может быть невосприимчивым к этим проблемам. Любое решение может страдать от этих проблем. Если вы не сделаете некоторые допущения для приемлемого эпсилон, точка, которая находится точно на линии (но чьи координаты не представлены в двоичном представлении с плавающей запятой ieee), также не пройдет тест скалярного произведения, поскольку компьютер будет представлять координаты точки неточно. на некоторую сумму.
Любая точка на линейном сегменте (а, б) (где а и б - векторы) может быть выражена как линейная комбинация двух векторов а и б:
Другими словами, если c лежит на линейном сегменте (а, б):
c = ma + (1 - m)b, where 0 <= m <= 1
Решая для
m = (c.x - b.x)/(a.x - b.x) = (c.y - b.y)/(a.y - b.y)
Итак, наш тест выглядит (на Python):
def is_on(a, b, c):
"""Is c on the line segment ab?"""
def _is_zero( val ):
return -epsilon < val < epsilon
x1 = a.x - b.x
x2 = c.x - b.x
y1 = a.y - b.y
y2 = c.y - b.y
if _is_zero(x1) and _is_zero(y1):
# a and b are the same point:
# so check that c is the same as a and b
return _is_zero(x2) and _is_zero(y2)
if _is_zero(x1):
# a and b are on same vertical line
m2 = y2 * 1.0 / y1
return _is_zero(x2) and 0 <= m2 <= 1
elif _is_zero(y1):
# a and b are on same horizontal line
m1 = x2 * 1.0 / x1
return _is_zero(y2) and 0 <= m1 <= 1
else:
m1 = x2 * 1.0 / x1
if m1 < 0 or m1 > 1:
return False
m2 = y2 * 1.0 / y1
return _is_zero(m2 - m1)
C# От http://www.faqs.org/faqs/graphics/algorithms-faq/ -> Тема 1.02: Как найти расстояние от точки до линии?
Boolean Contains(PointF from, PointF to, PointF pt, double epsilon)
{
double segmentLengthSqr = (to.X - from.X) * (to.X - from.X) + (to.Y - from.Y) * (to.Y - from.Y);
double r = ((pt.X - from.X) * (to.X - from.X) + (pt.Y - from.Y) * (to.Y - from.Y)) / segmentLengthSqr;
if (r<0 || r>1) return false;
double sl = ((from.Y - pt.Y) * (to.X - from.X) - (from.X - pt.X) * (to.Y - from.Y)) / System.Math.Sqrt(segmentLengthSqr);
return -epsilon <= sl && sl <= epsilon;
}
Правильный способ избежать проблем с точностью в большинстве других подходов. Также значительно более эффективен, чем большинство других подходов.
Мне это нужно для javascript для использования в холсте html5 для определения, находится ли курсор пользователя над определенной строкой или рядом с ней. Поэтому я преобразовал ответ Дариуса Бэкона в coffeescript:
is_on = (a,b,c) ->
# "Return true if point c intersects the line segment from a to b."
# (or the degenerate case that all 3 points are coincident)
return (collinear(a,b,c) and withincheck(a,b,c))
withincheck = (a,b,c) ->
if a[0] != b[0]
within(a[0],c[0],b[0])
else
within(a[1],c[1],b[1])
collinear = (a,b,c) ->
# "Return true if a, b, and c all lie on the same line."
((b[0]-a[0])*(c[1]-a[1]) < (c[0]-a[0])*(b[1]-a[1]) + 1000) and ((b[0]-a[0])*(c[1]-a[1]) > (c[0]-a[0])*(b[1]-a[1]) - 1000)
within = (p,q,r) ->
# "Return true if q is between p and r (inclusive)."
p <= q <= r or r <= q <= p
Вот другой способ сделать это с кодом на C++. Учитывая две точки, l1 и l2, легко выразить отрезок прямой между ними как
l1 + A(l2 - l1)
где 0 <= A <= 1. Это известно как векторное представление линии, если вас интересует не только его использование для этой задачи. Мы можем разделить его на компоненты x и y, получив:
x = l1.x + A(l2.x - l1.x)
y = l1.y + A(l2.y - l1.y)
Возьмите точку (x, y) и подставьте ее компоненты x и y в эти два выражения, чтобы найти A. Точка находится на линии, если решения для A в обоих выражениях равны и 0 <= A <= 1. Поскольку решение для A требует деления, есть особые случаи, когда требуется обработка, чтобы остановить деление на ноль, когда сегмент линии является горизонтальным или вертикальным. Окончательное решение выглядит следующим образом:
// Vec2 is a simple x/y struct - it could very well be named Point for this use
bool isBetween(double a, double b, double c) {
// return if c is between a and b
double larger = (a >= b) ? a : b;
double smaller = (a != larger) ? a : b;
return c <= larger && c >= smaller;
}
bool pointOnLine(Vec2<double> p, Vec2<double> l1, Vec2<double> l2) {
if (l2.x - l1.x == 0) return isBetween(l1.y, l2.y, p.y); // vertical line
if (l2.y - l1.y == 0) return isBetween(l1.x, l2.x, p.x); // horizontal line
double Ax = (p.x - l1.x) / (l2.x - l1.x);
double Ay = (p.y - l1.y) / (l2.y - l1.y);
// We want Ax == Ay, so check if the difference is very small (floating
// point comparison is fun!)
return fabs(Ax - Ay) < 0.000001 && Ax >= 0.0 && Ax <= 1.0;
}
Вот код Java, который у меня сработал:
boolean liesOnSegment(Coordinate a, Coordinate b, Coordinate c) {
double dotProduct = (c.x - a.x) * (c.x - b.x) + (c.y - a.y) * (c.y - b.y);
return (dotProduct < 0);
}
dotProduct может сказать только о выравнивании. Ваш код неполный !!! С a (0,0), b (4,0), c (1,1) у вас есть dotproduct = (1-0) * (1-4) + (1-0) * (1-0) = - 3 + 1 = -3
@ user43968 ты прав. Забыл, что тогда я работал с векторами. Ответ от Сирилла Ка правильный (полный)!
Ответ на C# с использованием класса Vector2D
public static bool IsOnSegment(this Segment2D @this, Point2D c, double tolerance)
{
var distanceSquared = tolerance*tolerance;
// Start of segment to test point vector
var v = new Vector2D( @this.P0, c ).To3D();
// Segment vector
var s = new Vector2D( @this.P0, @this.P1 ).To3D();
// Dot product of s
var ss = s*s;
// k is the scalar we multiply s by to get the projection of c onto s
// where we assume s is an infinte line
var k = v*s/ss;
// Convert our tolerance to the units of the scalar quanity k
var kd = tolerance / Math.Sqrt( ss );
// Check that the projection is within the bounds
if (k <= -kd || k >= (1+kd))
{
return false;
}
// Find the projection point
var p = k*s;
// Find the vector between test point and it's projection
var vp = (v - p);
// Check the distance is within tolerance.
return vp * vp < distanceSquared;
}
Обратите внимание, что
s * s
- скалярное произведение вектора сегмента через перегрузку оператора в C#
Ключ состоит в том, чтобы воспользоваться преимуществом проекции точки на бесконечную линию и заметить, что скалярная величина проекции тривиально говорит нам, находится ли проекция на отрезке или нет. Мы можем настроить границы скалярной величины, чтобы использовать допуск нечеткости.
Если проекция находится в пределах границ, мы просто проверяем, находится ли расстояние от точки до проекции в пределах границ.
Преимущество метода перекрестного произведения в том, что допуск имеет значимое значение.
Вы можете использовать клин и точечное произведение:
def dot(v,w): return v.x*w.x + v.y*w.y
def wedge(v,w): return v.x*w.y - v.y*w.x
def is_between(a,b,c):
v = a - b
w = b - c
return wedge(v,w) == 0 and dot(v,w) > 0
Вот мое решение с C# в Unity.
private bool _isPointOnLine( Vector2 ptLineStart, Vector2 ptLineEnd, Vector2 ptPoint )
{
bool bRes = false;
if ((Mathf.Approximately(ptPoint.x, ptLineStart.x) || Mathf.Approximately(ptPoint.x, ptLineEnd.x)))
{
if (ptPoint.y > ptLineStart.y && ptPoint.y < ptLineEnd.y)
{
bRes = true;
}
}
else if ((Mathf.Approximately(ptPoint.y, ptLineStart.y) || Mathf.Approximately(ptPoint.y, ptLineEnd.y)))
{
if (ptPoint.x > ptLineStart.x && ptPoint.x < ptLineEnd.x)
{
bRes = true;
}
}
return bRes;
}
Похоже, этот код работает только с вертикальными и горизонтальными отрезками линий. Что, если ptLineStart - это (0,0), ptLineEnd - это (2,2), а ptPoint - (1, 1)?
C# версия ответа Жюля:
public static double CalcDistanceBetween2Points(double x1, double y1, double x2, double y2)
{
return Math.Sqrt(Math.Pow (x1-x2, 2) + Math.Pow (y1-y2, 2));
}
public static bool PointLinesOnLine (double x, double y, double x1, double y1, double x2, double y2, double allowedDistanceDifference)
{
double dist1 = CalcDistanceBetween2Points(x, y, x1, y1);
double dist2 = CalcDistanceBetween2Points(x, y, x2, y2);
double dist3 = CalcDistanceBetween2Points(x1, y1, x2, y2);
return Math.Abs(dist3 - (dist1 + dist2)) <= allowedDistanceDifference;
}
Вы можете сделать это, решив линейное уравнение для этого линейного сегмента с координатами точки, вы будете знать, находится ли эта точка на линии, а затем проверив границы сегмента, чтобы узнать, находится ли он внутри или снаружи. Вы можете применить некоторый порог, потому что он где-то в пространстве, скорее всего, определяется значением с плавающей запятой, и вы не должны попадать в точное значение. Пример на php
function getLineDefinition($p1=array(0,0), $p2=array(0,0)){
$k = ($p1[1]-$p2[1])/($p1[0]-$p2[0]);
$q = $p1[1]-$k*$p1[0];
return array($k, $q);
}
function isPointOnLineSegment($line=array(array(0,0),array(0,0)), $pt=array(0,0)){
// GET THE LINE DEFINITION y = k.x + q AS array(k, q)
$def = getLineDefinition($line[0], $line[1]);
// use the line definition to find y for the x of your point
$y = $def[0]*$pt[0]+$def[1];
$yMin = min($line[0][1], $line[1][1]);
$yMax = max($line[0][1], $line[1][1]);
// exclude y values that are outside this segments bounds
if ($y>$yMax || $y<$yMin) return false;
// calculate the difference of your points y value from the reference value calculated from lines definition
// in ideal cases this would equal 0 but we are dealing with floating point values so we need some threshold value not to lose results
// this is up to you to fine tune
$diff = abs($pt[1]-$y);
$thr = 0.000001;
return $diff<=$thr;
}
Я вижу, что в этих ответах происходит ОЧЕНЬ много материала length = sqrt (x); они могут работать, но они не быстрые. Рассмотрите возможность использования квадрата длины; если вы просто сравниваете значения квадратов длины друг с другом, нет потери точности и вы сохраняете медленные вызовы sqrt ().