Як визначити, чи є точка у 2D трикутнику? [зачинено]


258

Чи є простий спосіб визначити, чи знаходиться точка всередині трикутника? Це 2D, а не 3D.


15
Я написав повну статтю про тест у тесті на трикутник. Він показує методи барицентричного, параметричного та крапкового продукту. Потім він розглядає проблему точності, яка виникає, коли точка лежить точно на одному краї (з прикладами). Нарешті, він відкриває цілком новий метод, заснований на відстані від точки до краю. totologic.blogspot.fr/2014/01/… Насолоджуйтесь!
Логіка


1
Варто зазначити, що будь-які обговорювані тут методи дійсні і в 3D-просторі. Їм просто передує перетворення координат (і відповідна проекція точки на площину трикутника). Трикутник - це двовимірний об’єкт.
andreasdr

Для рішення, яке не залежить від порядку намотування. Ось робоча загадка
ibrahim tanyalcin

2
Я голосую, щоб закрити це питання, оскільки це стосується математики, а не програмування, і ґрунтується на думці (що для вас "легко"?).
TylerH

Відповіді:


264

Взагалі найпростіший (і цілком оптимальний) алгоритм - це перевірка, на якій стороні півплощини, створеної ребрами, знаходиться точка.

Ось якась високоякісна інформація в цій темі щодо GameDev , включаючи проблеми з продуктивністю.

Ось якийсь код для початку роботи:

float sign (fPoint p1, fPoint p2, fPoint p3)
{
    return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
}

bool PointInTriangle (fPoint pt, fPoint v1, fPoint v2, fPoint v3)
{
    float d1, d2, d3;
    bool has_neg, has_pos;

    d1 = sign(pt, v1, v2);
    d2 = sign(pt, v2, v3);
    d3 = sign(pt, v3, v1);

    has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
    has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0);

    return !(has_neg && has_pos);
}

12
Він зазвичай використовується в 2D. Барицентричні координати, як правило, бентежать людей. Також, враховуючи координати трикутника та точку кординату, я не впевнений у ефективності використання барицентриків.
Kornel Kisielewicz

7
@Kornel Барицентрична версія є більш ефективною і в 2D. У вашому рішенні також є проблема, що він повідомляє про різний результат для точок саме по краях трикутника, залежно від того, чи буде вказано трикутник за годинниковою стрілкою або проти годинникової стрілки.
Андреас Брінк

9
Для моїх цілей (з причини, що я знайшов цей сайт) оригінальна відповідь, запропонована Корнелем Кіселевичем, набагато ефективніша. Я працюю з РК-дисплеєм з координатами розміру BYTE і дуже типовим мікропроцесором, де множення чисел - дуже швидка інструкція, а поділ набагато, набагато, повільніше. Числові випуски також значно менші, через відсутність поділу! всі розрахунки точні. Дякую, Рік

4
Отже, функція знак () вказує вам, яка сторона напівплощини (утворена лінією між p2 та p3) p1?
Девід Дорія

1
Зауважте, що якщо ви припускаєте деякий порядок вершин (скажімо, проти годинникової стрілки), вам не потрібно постійно обчислювати всі ці визначники. Насправді в кращому випадку для визначення того, що точка не знаходиться всередині трикутника, достатньо одного визначника.
Таш

176

Розв’яжіть таку систему рівнянь:

p = p0 + (p1 - p0) * s + (p2 - p0) * t

Точка pзнаходиться всередині трикутника , якщо 0 <= s <= 1і 0 <= t <= 1і s + t <= 1.

s, tі 1 - s - tназиваються барицентричними координатами точки p.


1
Це швидше, ніж перевірка півплощини, але, можливо, трохи складніше зрозуміти, якщо ви новачок у барицентричних координатах.
Даніель Ріковський

8
Маючи банальні виходи (не реалізовані) у методі Корнеля, його фактично може бути набагато ефективніше, ніж ваш. Якщо ви насправді спробуєте обчислити s і t, ви будете знати, що я маю на увазі.

85
Я хотів перевірити це, тому я зробив jsfiddle, покладаючись на рішення @andreasdr та коментар coproc
уррака

5
Оптимізація: s + t <= 1передбачає, s <= 1і t <= 1якщо s >= 0і t >= 0.
Томас Едінг

7
Стаття totologic.blogspot.fr/2014/01/…, запропонована @Logic post, допомогла мені краще зрозуміти це рішення
Флайн

112

Я погоджуюся з Андреасом Брінком , барицентричні координати дуже зручні для цього завдання. Зауважте, що не потрібно щоразу розв’язувати систему рівнянь: просто оцініть аналітичне рішення. Використовуючи позначення Андреаса , рішення:

s = 1/(2*Area)*(p0y*p2x - p0x*p2y + (p2y - p0y)*px + (p0x - p2x)*py);
t = 1/(2*Area)*(p0x*p1y - p0y*p1x + (p0y - p1y)*px + (p1x - p0x)*py);

де Areaзнаходиться (підписана) область трикутника:

Area = 0.5 *(-p1y*p2x + p0y*(-p1x + p2x) + p0x*(p1y - p2y) + p1x*p2y);

Просто оцініть s, tі 1-s-t. Справа pвсередині трикутника тоді і лише тоді, коли всі вони позитивні.

EDIT: Зауважте, що вищенаведений вираз для області передбачає, що нумерація вузлів трикутника знаходиться проти годинникової стрілки. Якщо нумерація знаходиться за годинниковою стрілкою, цей вираз поверне негативну область (але з правильною величиною). Сам тест ( s>0 && t>0 && 1-s-t>0) не залежить від напрямку нумерації, оскільки вирази, які перемножуються на множення, 1/(2*Area)також змінюють знак, якщо орієнтація вузла трикутника змінюється.

EDIT 2: Для ще кращої обчислювальної ефективності див. Коментар coproc нижче (що означає, що якщо орієнтація вузлів трикутника (за годинниковою або проти годинникової стрілки) заздалегідь відома, поділ 2*Areaна вирази для sта tможе бути уникати). Дивіться також jsfiddle-код Перро Азула у коментарях під відповіддю Андреаса Брінка .


6
Це є рішенням системи рівнянь :)
Andreas Брінк

1
Так, моя думка полягає в тому, що будь-яка критика вашого методу, заснованого на обчислювальній вартості розв’язання системи рівнянь, є необґрунтованою, оскільки це не повинно бути зроблено як частина алгоритму.
andreasdr

13
Ефективність може бути підвищена, не розділяючи їх 2*Area, тобто шляхом обчислення s´=2*|Area|*sта t´=2*|Area|*t(якщо орієнтація точок - за годинниковою або проти годинникової стрілки - невідома, Areaзвичайно, знак перевірки повинен бути перевірений, але в іншому випадку, можливо, навіть не буде потрібно обчислити), оскільки для перевірки s>0достатньо перевірити s´>0. І замість перевірки 1-s-t>0достатньо перевірити s´+t´<2*|Area|.
копрок

1
Я можу додати , що , якщо p0->p1->p2це проти годинникової стрілки в декартовій (який, як правило , по годинниковою стрілкою , в екранних координатах ), то Areaобчислюється з допомогою цього методу буде позитивним.
rhgb

1
@ user2600366 Коли ви рухатиметесь по межі трикутника у напрямку p0 -> p1 -> p2 -> p0 тощо, ви матимете внутрішню частину трикутника або завжди праворуч, або завжди зліва. У першому випадку нумерація проводиться за годинниковою стрілкою, в другому - проти годинникової стрілки.
andreasdr

47

Я написав цей код перед останньою спробою в Google та пошуку цієї сторінки, тому думав, що поділюсь ним. Це в основному оптимізована версія відповіді Кисілевич. Я також заглянув у метод баріцентрика, але, судячи зі статті Вікіпедії, мені важко зрозуміти, наскільки це ефективніше (я здогадуюсь, що існує деяка глибша еквівалентність). У всякому разі, цей алгоритм має перевагу в тому, що він не використовує поділ; потенційною проблемою є поведінка виявлення краю в залежності від орієнтації.

bool intpoint_inside_trigon(intPoint s, intPoint a, intPoint b, intPoint c)
{
    int as_x = s.x-a.x;
    int as_y = s.y-a.y;

    bool s_ab = (b.x-a.x)*as_y-(b.y-a.y)*as_x > 0;

    if((c.x-a.x)*as_y-(c.y-a.y)*as_x > 0 == s_ab) return false;

    if((c.x-b.x)*(s.y-b.y)-(c.y-b.y)*(s.x-b.x) > 0 != s_ab) return false;

    return true;
}

Словом, ідея така: чи точка s ліворуч або праворуч від обох прямих AB та AC? Якщо це правда, вона не може бути всередині. Якщо помилково, це, принаймні, всередині «шишок», які задовольняють умові. Тепер, оскільки ми знаємо, що точка всередині тригона (трикутника) повинна знаходитися на тій же стороні AB, що і BC (а також CA), ми перевіряємо, чи вони різняться. Якщо вони є, s не може бути всередині, інакше вони повинні бути всередині.

Деякі ключові слова в обчисленнях - це півплощини рядка та визначальник (2х2 поперечний добуток). Можливо, більш педагогічним способом є, мабуть, думка про це як точку, що знаходиться всередині, якщо вона знаходиться в одній стороні (зліва або справа) до кожного з ліній AB, BC та CA. Наведений вище спосіб здавався кращим для оптимізації.


2
Цей тест приблизно на 140-180% швидший, ніж перший, що надається (завдяки вам обом BTW :). Я запустив код тут: paste.ubuntu.com/p/k5w7ywH4p8, використовуючи двигун nodejs v8 з відключеними оптимізаціями, і отримав такі результати:: w! Node -p - мінімальний тест1: 114,852ms test2: 64,330ms test1: 115,650ms test2: 63,491ms test1: 117,671ms test2: 65,353ms test1: 119,146ms test2: 63,871ms test1: 118,271ms test1: 118,670ms test2: 63,352ms
surgemcgee

@surgemcgee, чому б ти запустив його без оптимізацій? Хіба це більше не віддалено від реальності?
xuiqzy

@xuiqzy Ну, моя програма містить два різних рішення. Мені ще належить застосувати найшвидший спосіб зробити це. Можливо, цей коментар слід видалити та замінити моїми закінченими роботами щодо цього ..
surgemcgee

33

C # версія барицентричного методу, опублікована andreasdr та Perro Azul. Зауважте, що обчислення площі можна уникнути, якщо sта tмають протилежні знаки. Я перевірив правильність поведінки досить ретельним одиничним тестом.

public static bool PointInTriangle(Point p, Point p0, Point p1, Point p2)
{
    var s = p0.Y * p2.X - p0.X * p2.Y + (p2.Y - p0.Y) * p.X + (p0.X - p2.X) * p.Y;
    var t = p0.X * p1.Y - p0.Y * p1.X + (p0.Y - p1.Y) * p.X + (p1.X - p0.X) * p.Y;

    if ((s < 0) != (t < 0))
        return false;

    var A = -p1.Y * p2.X + p0.Y * (p2.X - p1.X) + p0.X * (p1.Y - p2.Y) + p1.X * p2.Y;

    return A < 0 ?
            (s <= 0 && s + t >= A) :
            (s >= 0 && s + t <= A);
}

[ редагувати ]
прийняті запропоновані зміни від @Pierre; дивитись коментарі


Рішення із закінченням, якщо оператор працює за годинниковою стрілкою та проти годинникової стрілки.
Люк Дупін

@LukeDupin Не впевнений, що я розумію ваш коментар. Ця відповідь працює як розміщена для будь-якого поставленого замовлення з 3 балів.
Гленн Слейден

12

Версія Java баріцентричного методу:

class Triangle {
    Triangle(double x1, double y1, double x2, double y2, double x3,
            double y3) {
        this.x3 = x3;
        this.y3 = y3;
        y23 = y2 - y3;
        x32 = x3 - x2;
        y31 = y3 - y1;
        x13 = x1 - x3;
        det = y23 * x13 - x32 * y31;
        minD = Math.min(det, 0);
        maxD = Math.max(det, 0);
    }

    boolean contains(double x, double y) {
        double dx = x - x3;
        double dy = y - y3;
        double a = y23 * dx + x32 * dy;
        if (a < minD || a > maxD)
            return false;
        double b = y31 * dx + x13 * dy;
        if (b < minD || b > maxD)
            return false;
        double c = det - a - b;
        if (c < minD || c > maxD)
            return false;
        return true;
    }

    private final double x3, y3;
    private final double y23, x32, y31, x13;
    private final double det, minD, maxD;
}

Вищевказаний код буде точно працювати з цілими числами, не допускаючи переповнення. Він також буде працювати з трикутниками за годинниковою стрілкою та проти годинникової стрілки. Він не працюватиме з колінеарними трикутниками (але ви можете перевірити це, перевіривши det == 0).

Барицентрична версія найшвидша, якщо ви збираєтеся перевірити різні точки з одним і тим же трикутником.

Барицентрична версія не симетрична в трьох точках трикутника, тому, ймовірно, буде менш послідовною, ніж крайова напівплощинна версія Корнеля Кіселевича, через помилки округлення плаваючої точки.

Кредит: Я вказав вищезгаданий код із статті Вікіпедії про барицентричні координати.


Приємно! Можна навіть вдосконалити використання кортезів Point3f / Point2f javax.vecmath, щоб краще обробляти введення даних.
Алекс Беррт

10

Простий спосіб - це:

знайти вектори, що з'єднують точку з трьома вершинами трикутника, і підсумовуємо кути між цими векторами. Якщо сума кутів дорівнює 2 * пі, то точка знаходиться всередині трикутника.

Два хороших сайти, які пояснюють альтернативи:

blackpawn і вольфрам


3
Гм, цей метод не зовсім ефективний, і він дуже схильний до числових помилок ...
Корнель Кіселевич

Це зовсім навпаки, це дуже неефективно :-) Це просто один простий спосіб, який легко здійснити. Чи можете ви навести приклад числової помилки, яку це може спричинити?
Саймон П Стівенс

Хоча мені це здається найкращим з усіх відповідей у ​​цій темі, я гадаю, що точки на краях трикутника обчислюються таким чином, щоб вони були включені до трикутника, і ви не маєте міцного контролю над цим.
Скоротити

перевірити, чи це точно 2пі чисельно неможливо з огляду на ірраціональність pi. Однак вам просто потрібно перевірити, чи кути складають щось більше, ніж пі.
lonewarrior556

10

Використовуючи аналітичне рішення для барицентричних координат (вказав Андреас Брінк ) і:

  • не розподіляючи множення на круглі дужки
  • уникаючи обчислення декількох разів однакових термінів, зберігаючи їх
  • скорочення порівнянь (на що вказували копрок та Томас Едінг )

Можна мінімізувати кількість "дорогих" операцій:

function ptInTriangle(p, p0, p1, p2) {
    var dX = p.x-p2.x;
    var dY = p.y-p2.y;
    var dX21 = p2.x-p1.x;
    var dY12 = p1.y-p2.y;
    var D = dY12*(p0.x-p2.x) + dX21*(p0.y-p2.y);
    var s = dY12*dX + dX21*dY;
    var t = (p2.y-p0.y)*dX + (p0.x-p2.x)*dY;
    if (D<0) return s<=0 && t<=0 && s+t>=D;
    return s>=0 && t>=0 && s+t<=D;
}

Код можна вставити у roефрідла Perro Azul або спробувати його, натиснувши «Запустити фрагмент коду» нижче

var ctx = $("canvas")[0].getContext("2d");
var W = 500;
var H = 500;

var point = { x: W / 2, y: H / 2 };
var triangle = randomTriangle();

$("canvas").click(function(evt) {
    point.x = evt.pageX - $(this).offset().left;
    point.y = evt.pageY - $(this).offset().top;
    test();
});

$("canvas").dblclick(function(evt) {
    triangle = randomTriangle();
    test();
});

test();

function test() {
    var result = ptInTriangle(point, triangle.a, triangle.b, triangle.c);
    
    var info = "point = (" + point.x + "," + point.y + ")\n";
    info += "triangle.a = (" + triangle.a.x + "," + triangle.a.y + ")\n";
    info += "triangle.b = (" + triangle.b.x + "," + triangle.b.y + ")\n";
    info += "triangle.c = (" + triangle.c.x + "," + triangle.c.y + ")\n";
    info += "result = " + (result ? "true" : "false");

    $("#result").text(info);
    render();
}

function ptInTriangle(p, p0, p1, p2) {
    var A = 1/2 * (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
    var sign = A < 0 ? -1 : 1;
    var s = (p0.y * p2.x - p0.x * p2.y + (p2.y - p0.y) * p.x + (p0.x - p2.x) * p.y) * sign;
    var t = (p0.x * p1.y - p0.y * p1.x + (p0.y - p1.y) * p.x + (p1.x - p0.x) * p.y) * sign;
    
    return s > 0 && t > 0 && (s + t) < 2 * A * sign;
}

function render() {
    ctx.fillStyle = "#CCC";
    ctx.fillRect(0, 0, 500, 500);
    drawTriangle(triangle.a, triangle.b, triangle.c);
    drawPoint(point);
}

function drawTriangle(p0, p1, p2) {
    ctx.fillStyle = "#999";
    ctx.beginPath();
    ctx.moveTo(p0.x, p0.y);
    ctx.lineTo(p1.x, p1.y);
    ctx.lineTo(p2.x, p2.y);
    ctx.closePath();
    ctx.fill();
    ctx.fillStyle = "#000";
    ctx.font = "12px monospace";
    ctx.fillText("1", p0.x, p0.y);
    ctx.fillText("2", p1.x, p1.y);
    ctx.fillText("3", p2.x, p2.y);
}

function drawPoint(p) {
    ctx.fillStyle = "#F00";
    ctx.beginPath();
    ctx.arc(p.x, p.y, 5, 0, 2 * Math.PI);
    ctx.fill();
}

function rand(min, max) {
	return Math.floor(Math.random() * (max - min + 1)) + min;
}

function randomTriangle() {
    return {
        a: { x: rand(0, W), y: rand(0, H) },
        b: { x: rand(0, W), y: rand(0, H) },
        c: { x: rand(0, W), y: rand(0, H) }
    };
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<pre>Click: place the point.
Double click: random triangle.</pre>
<pre id="result"></pre>
<canvas width="500" height="500"></canvas>

Що веде до:

  • змінна "нагадує": 30
  • змінне сховище: 7
  • доповнення: 4
  • віднімання: 8
  • множення: 6
  • підрозділи: немає
  • порівняння: 4

Це досить добре порівнюється з рішенням Корнеля Кіселевича (25 відкликань, 1 сховище, 15 віднімань, 6 множень, 5 порівнянь), і може бути навіть краще, якщо потрібно виявлення за годинниковою / проти годинникової стрілки (для цього потрібно 6 відкликань, 1 додавання, 2 віднімання , 2 множення і 1 порівняння саме по собі, використовуючи визначник аналітичного рішення, як вказував rhgb ).


Приємне рішення. Я думаю, це цілком еквівалентно моєму останньому підходу тут на MSE: math.stackexchange.com/questions/51326/…
Джек Д'Ауріціо

Я щойно перевірив код таким, який є, і він не працює для мене (наприклад, p -4.69317198, -6.99191951 p0 -7.05846786 0.596718192 p1 -6.8703599 -2.36565161 p2 -4.69317198, -6.99191951)
Giovanni Funchal

@GiovanniFunchal Дивно, ваш приклад працює для мене, як у jsfiddle (замініть початкові визначення "точка" та "трикутник"), так і в моїй локальній реалізації Python. Проблеми з числовою точністю (спробуйте зняти кілька десятків)?
Седрик Дюфур

1
Ви, мабуть, найшвидші в моєму тесті: jsfiddle.net/eyal/gxw3632c/27 . Однак різниця між усіма методами досить мала.
Eyal

Спробуйте трикутник (-1, -1), (1, -1), (0,1) і точка (0, -1). Повертає помилку, коли вона повинна повернути істину, оскільки s (2) + t (2)> d (2). Щось не так з математикою на ребрах трикутника, здається, як точка p знаходиться прямо на межі між p0 та p1, і це не проста проблема перетворення a <в a <= чи щось подібне.
devnullicus

5

Те, що я роблю, - це перерахувати три норми обличчя,

  • у 3D шляхом поперечного добутку бічного вектора та нормального вектора грані.

  • у 2D просто замінивши компоненти та відкинувши один,

то всередині / назовні для будь-якої однієї сторони - це коли точковий добуток сторони звичайної і вектор вершини на точку змінить знак. Повторіть для інших двох (або більше) сторін.

Переваги:

  • багато підраховується настільки чудово для багаторазового тестування на одному трикутнику.

  • раннє відхилення звичайного випадку більше зовні, ніж всередині точок. (також якщо розподіл точок зважено на одну сторону, спочатку може перевірити цю сторону.)


5

Ось ефективна реалізація Python :

def PointInsideTriangle2(pt,tri):
    '''checks if point pt(2) is inside triangle tri(3x2). @Developer'''
    a = 1/(-tri[1,1]*tri[2,0]+tri[0,1]*(-tri[1,0]+tri[2,0])+ \
        tri[0,0]*(tri[1,1]-tri[2,1])+tri[1,0]*tri[2,1])
    s = a*(tri[2,0]*tri[0,1]-tri[0,0]*tri[2,1]+(tri[2,1]-tri[0,1])*pt[0]+ \
        (tri[0,0]-tri[2,0])*pt[1])
    if s<0: return False
    else: t = a*(tri[0,0]*tri[1,1]-tri[1,0]*tri[0,1]+(tri[0,1]-tri[1,1])*pt[0]+ \
              (tri[1,0]-tri[0,0])*pt[1])
    return ((t>0) and (1-s-t>0))

та приклад виводу:

введіть тут опис зображення


Я не зміг зробити цю роботу, наприклад, для точки в трикутнику [(0,0), (3,0), (3,4)], ні пунктів (1,1) або (0 , 0) тест позитивний. Я спробував як з точки зору трикутника, так і проти годинникової стрілки.
ThorSummoner

3

Якщо ви шукаєте швидкості, ось процедура, яка може вам допомогти.

Сортуйте вершини трикутника за їх ординатами. Це займає в гіршому випадку три порівняння. Нехай Y0, Y1, Y2 - три відсортовані значення. Намалювавши через них три горизонталі, ви розділите площину на дві півплощини та дві плити. Нехай Y - ордината точки запиту.

if Y < Y1
    if Y <= Y0 -> the point lies in the upper half plane, outside the triangle; you are done
    else Y > Y0 -> the point lies in the upper slab
else
    if Y >= Y2 -> the point lies in the lower half plane, outside the triangle; you are done
    else Y < Y2 -> the point lies in the lower slab

Коштує ще два порівняння. Як бачите, швидке відхилення досягається для точок поза «обмежувальною плитою».

За бажанням, ви можете надати тест на абсцисах для швидкого відхилення зліва та справа ( X <= X0' or X >= X2'). Це дозволить одночасно здійснити тест на швидке обмеження, але вам також потрібно буде сортувати по абсцисах.

Врешті-решт вам потрібно буде обчислити знак даної точки відносно двох сторін трикутника, які обмежують відповідну плиту (верхню або нижню). Тест має форму:

((X - Xi) * (Y - Yj) > (X - Xi) * (Y - Yj)) == ((X - Xi) * (Y - Yk) > (X - Xi) * (Y - Yk))

Повне обговорення i, j, kкомбінацій (їх шість, виходячи з результатів сортування), виходить за рамки цієї відповіді і "залишається читачеві як вправа"; для ефективності вони повинні бути жорстко кодованими.

Якщо ви вважаєте, що це рішення є складним, зауважте, що воно, головним чином, включає прості порівняння (деякі з яких можна попередньо обчислити), плюс 6 віднімань та 4 множення у випадку, якщо тест з обмежувальним вікном не вдається. Останню вартість важко перемогти, оскільки в гіршому випадку ви не можете уникнути порівняння точки тесту проти двох сторін (жоден метод в інших відповідях не має меншої вартості, деякі погіршують, як 15 віднімання та 6 множень, іноді поділ).

ОНОВЛЕННЯ: Швидше з перетворенням зсуву

Як було пояснено трохи вище, ви можете швидко знайти точку всередині однієї з чотирьох горизонтальних смуг, розділених трьома вершинами ординат, використовуючи два порівняння.

Ви можете необов'язково виконати один або два додаткових тесту Х, щоб перевірити наближеність до обмежувального поля (пунктирними лініями).

Потім розглянемо перетворення "зсуву", яке задається X'= X - m Y, Y' = Y, де mнахил DX/DYдля найвищого краю. Це перетворення зробить цю сторону трикутника вертикальною. А оскільки ви знаєте, на якій стороні середньої горизонталі ви знаходитесь, достатньо перевірити знак відносно однієї сторони трикутника.

введіть тут опис зображення

Якщо припустити, що ви попередньо обчислили нахил m, а також X'для стрижених вершин трикутника та коефіцієнти рівнянь сторін як X = m Y + p, вам знадобиться в гіршому випадку

  • два ординатні порівняння для вертикальної класифікації;
  • необов'язково одне чи два порівняння абсцис для відхилення обмежувальної коробки;
  • обчислення X' = X - m Y;
  • одне-два порівняння з абсцисами зрізаного трикутника;
  • тест X >< m' Y + p'на один знак відповідної сторони зрізаного трикутника.

3

Якщо ви знаєте координати трьох вершин і координати конкретної точки, то ви можете отримати площу повного трикутника. Потім обчисліть площу трьох відрізків трикутника (одна точка - дана точка, а дві інші - будь-які дві вершини трикутника). Таким чином, ви отримаєте площу трьох відрізків трикутника. Якщо сума цих областей дорівнює загальній площі (яку ви отримали раніше), то точка повинна бути всередині трикутника. Інакше точка не знаходиться всередині трикутника. Це має спрацювати. Якщо є якісь проблеми, повідомте мене. Дякую.


3

Інша функція в python , швидша, ніж метод Developer (принаймні для мене) та натхненна рішенням Седрика Дюфура :

def ptInTriang(p_test, p0, p1, p2):       
     dX = p_test[0] - p0[0]
     dY = p_test[1] - p0[1]
     dX20 = p2[0] - p0[0]
     dY20 = p2[1] - p0[1]
     dX10 = p1[0] - p0[0]
     dY10 = p1[1] - p0[1]

     s_p = (dY20*dX) - (dX20*dY)
     t_p = (dX10*dY) - (dY10*dX)
     D = (dX10*dY20) - (dY10*dX20)

     if D > 0:
         return (  (s_p >= 0) and (t_p >= 0) and (s_p + t_p) <= D  )
     else:
         return (  (s_p <= 0) and (t_p <= 0) and (s_p + t_p) >= D  )

Ви можете перевірити це за допомогою:

X_size = 64
Y_size = 64
ax_x = np.arange(X_size).astype(np.float32)
ax_y = np.arange(Y_size).astype(np.float32)
coords=np.meshgrid(ax_x,ax_y)
points_unif = (coords[0].reshape(X_size*Y_size,),coords[1].reshape(X_size*Y_size,))
p_test = np.array([0 , 0])
p0 = np.array([22 , 8]) 
p1 = np.array([12 , 55]) 
p2 = np.array([7 , 19]) 
fig = plt.figure(dpi=300)
for i in range(0,X_size*Y_size):
    p_test[0] = points_unif[0][i]
    p_test[1] = points_unif[1][i]
    if ptInTriang(p_test, p0, p1, p2):
        plt.plot(p_test[0], p_test[1], '.g')
    else:
        plt.plot(p_test[0], p_test[1], '.r')

Структура потребує багато, але ця сітка тестується за 0,0195319652557 секунд проти 0,0844349861145 секунд коду розробника .

Нарешті коментар до коду:

# Using barycentric coordintes, any point inside can be described as:
# X = p0.x * r + p1.x * s + p2.x * t
# Y = p0.y * r + p1.y * s + p2.y * t
# with:
# r + s + t = 1  and 0 < r,s,t < 1
# then: r = 1 - s - t
# and then:
# X = p0.x * (1 - s - t) + p1.x * s + p2.x * t
# Y = p0.y * (1 - s - t) + p1.y * s + p2.y * t
#
# X = p0.x + (p1.x-p0.x) * s + (p2.x-p0.x) * t
# Y = p0.y + (p1.y-p0.y) * s + (p2.y-p0.y) * t
#
# X - p0.x = (p1.x-p0.x) * s + (p2.x-p0.x) * t
# Y - p0.y = (p1.y-p0.y) * s + (p2.y-p0.y) * t
#
# we have to solve:
#
# [ X - p0.x ] = [(p1.x-p0.x)   (p2.x-p0.x)] * [ s ]
# [ Y - p0.Y ]   [(p1.y-p0.y)   (p2.y-p0.y)]   [ t ]
#
# ---> b = A*x ; ---> x = A^-1 * b
# 
# [ s ] =   A^-1  * [ X - p0.x ]
# [ t ]             [ Y - p0.Y ]
#
# A^-1 = 1/D * adj(A)
#
# The adjugate of A:
#
# adj(A)   =   [(p2.y-p0.y)   -(p2.x-p0.x)]
#              [-(p1.y-p0.y)   (p1.x-p0.x)]
#
# The determinant of A:
#
# D = (p1.x-p0.x)*(p2.y-p0.y) - (p1.y-p0.y)*(p2.x-p0.x)
#
# Then:
#
# s_p = { (p2.y-p0.y)*(X - p0.x) - (p2.x-p0.x)*(Y - p0.Y) }
# t_p = { (p1.x-p0.x)*(Y - p0.Y) - (p1.y-p0.y)*(X - p0.x) }
#
# s = s_p / D
# t = t_p / D
#
# Recovering r:
#
# r = 1 - (s_p + t_p)/D
#
# Since we only want to know if it is insidem not the barycentric coordinate:
#
# 0 < 1 - (s_p + t_p)/D < 1
# 0 < (s_p + t_p)/D < 1
# 0 < (s_p + t_p) < D
#
# The condition is:
# if D > 0:
#     s_p > 0 and t_p > 0 and (s_p + t_p) < D
# else:
#     s_p < 0 and t_p < 0 and (s_p + t_p) > D
#
# s_p = { dY20*dX - dX20*dY }
# t_p = { dX10*dY - dY10*dX }
# D = dX10*dY20 - dY10*dX20

Ця функція не працює. Дайте, ptInTriang([11,45],[45, 45],[45, 45] ,[44, 45])і воно повернеться, trueхоча це помилково
Код Папи

3

Оскільки відповіді на JS немає, рішення за
годинниковою стрілкою та проти годинникової стрілки:

function triangleContains(ax, ay, bx, by, cx, cy, x, y) {

    let det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)

    return  det * ((bx - ax) * (y - ay) - (by - ay) * (x - ax)) > 0 &&
            det * ((cx - bx) * (y - by) - (cy - by) * (x - bx)) > 0 &&
            det * ((ax - cx) * (y - cy) - (ay - cy) * (x - cx)) > 0 

}

EDIT: була помилка друку ( cy - ayзамість cx - ax), це виправлено.

https://jsfiddle.net/jniac/rctb3gfL/

function triangleContains(ax, ay, bx, by, cx, cy, x, y) {

    let det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)
	
    return  det * ((bx - ax) * (y - ay) - (by - ay) * (x - ax)) > 0 &&
            det * ((cx - bx) * (y - by) - (cy - by) * (x - bx)) > 0 &&
            det * ((ax - cx) * (y - cy) - (ay - cy) * (x - cx)) > 0 

}






let width = 500, height = 500

// clockwise
let triangle1 = {

	A : { x: 10, y: -10 },
	C : { x: 20, y: 100 },
	B : { x: -90, y: 10 },
	
	color: '#f00',

}

// counter clockwise
let triangle2 = {

	A : { x: 20, y: -60 },
	B : { x: 90, y: 20 },
	C : { x: 20, y: 60 },

	color: '#00f',
	
}


let scale = 2
let mouse = { x: 0, y: 0 }






// DRAW >

let wrapper = document.querySelector('div.wrapper')

wrapper.onmousemove = ({ layerX:x, layerY:y }) => {
	
	x -= width / 2
	y -= height / 2
	x /= scale
	y /= scale
	
	mouse.x = x
	mouse.y = y
	
	drawInteractive()

}

function drawArrow(ctx, A, B) {

	let v = normalize(sub(B, A), 3)
	let I = center(A, B)
	
	let p
	
	p = add(I, rotate(v, 90), v)
	ctx.moveTo(p.x, p.y)
	ctx.lineTo(I.x, I .y)
	p = add(I, rotate(v, -90), v)
	ctx.lineTo(p.x, p.y)

}

function drawTriangle(ctx, { A, B, C, color }) {

	ctx.beginPath()
	ctx.moveTo(A.x, A.y)
	ctx.lineTo(B.x, B.y)
	ctx.lineTo(C.x, C.y)
	ctx.closePath()
	
	ctx.fillStyle = color + '6'
	ctx.strokeStyle = color
	ctx.fill()
	
	drawArrow(ctx, A, B)
	drawArrow(ctx, B, C)
	drawArrow(ctx, C, A)
	
	ctx.stroke()

}

function contains({ A, B, C }, P) {

	return triangleContains(A.x, A.y, B.x, B.y, C.x, C.y, P.x, P.y)

}

function resetCanvas(canvas) {

	canvas.width = width
	canvas.height = height
	
	let ctx = canvas.getContext('2d')

	ctx.resetTransform()
	ctx.clearRect(0, 0, width, height)
	ctx.setTransform(scale, 0, 0, scale, width/2, height/2)
	
}

function drawDots() {

	let canvas = document.querySelector('canvas#dots')
	let ctx = canvas.getContext('2d')

	resetCanvas(canvas)
	
	let count = 1000

	for (let i = 0; i < count; i++) {

		let x = width * (Math.random() - .5)
		let y = width * (Math.random() - .5)
		
		ctx.beginPath()
		ctx.ellipse(x, y, 1, 1, 0, 0, 2 * Math.PI)
		
		if (contains(triangle1, { x, y })) {
		
			ctx.fillStyle = '#f00'
		
		} else if (contains(triangle2, { x, y })) {
		
			ctx.fillStyle = '#00f'
		
		} else {
		
			ctx.fillStyle = '#0003'
		
		}

		
		ctx.fill()
		
	}
	
}

function drawInteractive() {

	let canvas = document.querySelector('canvas#interactive')
	let ctx = canvas.getContext('2d')

	resetCanvas(canvas)
	
	ctx.beginPath()
	ctx.moveTo(0, -height/2)
	ctx.lineTo(0, height/2)
	ctx.moveTo(-width/2, 0)
	ctx.lineTo(width/2, 0)
	ctx.strokeStyle = '#0003'
	ctx.stroke()
	
	drawTriangle(ctx, triangle1)
	drawTriangle(ctx, triangle2)
	
	ctx.beginPath()
	ctx.ellipse(mouse.x, mouse.y, 4, 4, 0, 0, 2 * Math.PI)
	
	if (contains(triangle1, mouse)) {
	
		ctx.fillStyle = triangle1.color + 'a'
		ctx.fill()
		
	} else if (contains(triangle2, mouse)) {
	
		ctx.fillStyle = triangle2.color + 'a'
		ctx.fill()
		
	} else {
	
		ctx.strokeStyle = 'black'
		ctx.stroke()
		
	}
	
}

drawDots()
drawInteractive()










// trigo

function add(...points) {
	
	let x = 0, y = 0
	
	for (let point of points) {
	
		x += point.x
		y += point.y
	
	}
	
	return { x, y }

}

function center(...points) {
	
	let x = 0, y = 0
	
	for (let point of points) {
	
		x += point.x
		y += point.y
	
	}
	
	x /= points.length
	y /= points.length
	
	return { x, y }

}

function sub(A, B) {

	let x = A.x - B.x
	let y = A.y - B.y
	
	return { x, y }

}

function normalize({ x, y }, length = 10) {

	let r = length / Math.sqrt(x * x + y * y)
	
	x *= r
	y *= r
	
	return { x, y }

}

function rotate({ x, y }, angle = 90) {

	let length = Math.sqrt(x * x + y * y)
	
	angle *= Math.PI / 180
	angle += Math.atan2(y, x)
	
	x = length * Math.cos(angle)
	y = length * Math.sin(angle)
	
	return { x, y }

}
* {
	margin: 0;
}

html {
	font-family: monospace;
}

body {
	padding: 32px;
}

span.red {
	color: #f00;
}

span.blue {
	color: #00f;
}

canvas {
	position: absolute;
	border: solid 1px #ddd;
}
<p><span class="red">red triangle</span> is clockwise</p>
<p><span class="blue">blue triangle</span> is couter clockwise</p>
<br>
<div class="wrapper">
	<canvas id="dots"></canvas>
	<canvas id="interactive"></canvas>
</div>

введіть тут опис зображення

Я використовую тут той самий метод, як описано вище: точка знаходиться всередині ABC, якщо вона знаходиться відповідно на "одній" стороні кожного прямого AB, BC, CA.

Приклад включення трикутника


Я втомився від цього коду, і він не працює. Це завжди повертає Хибність.
xApple

хммм ... ти, мабуть, помилився. Ось загадка
Джозеф Мердріньяк,

Я бачив вашу відповідь Python, ми використовуємо той самий метод, якщо я використовую ще один рядок ( let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)), це визначає порядок намотування трикутника, тому метод буде працювати з трикутниками CW & CCW (див. jsFiddle).
Джозеф Мердріньяк

1
хм, я помилився, я написав: let det = (bx - ax) * (cy - ay) - (by - ay) * (cy - ay)замість let det = (bx - ax) * (cy - ay) - (by - ay) * (cx - ax)цього це виправлено, дякую за повідомлення
Джозеф Мердріньяк

2

Я просто хочу використовувати просту векторну математику для пояснення рішення барицентричних координат, яке дав Андреас, це буде легше зрозуміти.

  1. Область A визначається як будь-який вектор, заданий s * v02 + t * v01, з умовою s> = 0 і t> = 0. Якщо будь-яка точка всередині трикутника v0, v1, v2, вона повинна бути всередині області A.

введіть тут опис зображення

  1. Якщо далі обмежити s, і t належить [0, 1]. Отримаємо область B, яка містить усі вектори s * v02 + t * v01, при цьому умова s, t належить [0, 1]. Варто зазначити, що низька частина області В є дзеркалом трикутника v0, v1, v2. Проблема виникає, якщо ми можемо дати певну умову s та t, щоб у подальшому виключити низьку частину B.

введіть тут опис зображення

  1. Припустимо, ми даємо значення s, а t змінюється в [0, 1]. На наступному малюнку точка p знаходиться на межі v1v2. Усі вектори s * v02 + t * v01, які знаходяться по лінії тире, прості векторні суми. У точці v1v2 та перетину лінії тире p маємо:

(1-с) | v0v2 | / | v0v2 | = tp | v0v1 | / | v0v1 |

отримуємо 1 - s = tp, тоді 1 = s + tp. Якщо будь-який t> tp, який 1 <s + t, де знаходиться на подвійній лінії тире, вектор знаходиться поза трикутником, будь-який t <= tp, який 1> = s + t, де знаходиться на одній тире лінії, вектор всередині трикутника.

Тоді, якщо ми задали будь-який s в [0, 1], для вектора всередині трикутника відповідне t повинно відповідати 1> = s + t.

введіть тут опис зображення

Отже, нарешті, отримуємо v = s * v02 + t * v01, v - це всередині трикутника з умовою s, t, s + t, що належить [0, 1]. Потім перекладіть на точку, у нас є

p - p0 = s * (p1 - p0) + t * (p2 - p0), з s, t, s + t в [0, 1]

що таке саме рішення Андреаса для розв’язання системи рівнянь p = p0 + s * (p1 - p0) + t * (p2 - p0), при цьому s, t, s + t належать [0, 1].


Можна просто сказати, що ви використовуєте локальний кадр, визначений трьома вершинами, так що сторони стають s = 0, t = 0 і s + t = 1. Африканне перетворення координат - це добре відома операція лінійної алгебри.
Ів Дауст

2

Ось рішення в python, яке є ефективним, задокументованим і містить три одиничних тести. Це професійна якість і готовий бути включеним у ваш проект у вигляді модуля, як є.

import unittest

###############################################################################
def point_in_triangle(point, triangle):
    """Returns True if the point is inside the triangle
    and returns False if it falls outside.
    - The argument *point* is a tuple with two elements
    containing the X,Y coordinates respectively.
    - The argument *triangle* is a tuple with three elements each
    element consisting of a tuple of X,Y coordinates.

    It works like this:
    Walk clockwise or counterclockwise around the triangle
    and project the point onto the segment we are crossing
    by using the dot product.
    Finally, check that the vector created is on the same side
    for each of the triangle's segments.
    """
    # Unpack arguments
    x, y = point
    ax, ay = triangle[0]
    bx, by = triangle[1]
    cx, cy = triangle[2]
    # Segment A to B
    side_1 = (x - bx) * (ay - by) - (ax - bx) * (y - by)
    # Segment B to C
    side_2 = (x - cx) * (by - cy) - (bx - cx) * (y - cy)
    # Segment C to A
    side_3 = (x - ax) * (cy - ay) - (cx - ax) * (y - ay)
    # All the signs must be positive or all negative
    return (side_1 < 0.0) == (side_2 < 0.0) == (side_3 < 0.0)

###############################################################################
class TestPointInTriangle(unittest.TestCase):

    triangle = ((22 , 8),
                (12 , 55),
                (7 , 19))

    def test_inside(self):
        point = (15, 20)
        self.assertTrue(point_in_triangle(point, self.triangle))

    def test_outside(self):
        point = (1, 7)
        self.assertFalse(point_in_triangle(point, self.triangle))

    def test_border_case(self):
        """If the point is exactly on one of the triangle's edges,
        we consider it is inside."""
        point = (7, 19)
        self.assertTrue(point_in_triangle(point, self.triangle))

###############################################################################
if __name__ == "__main__":
    suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestPointInTriangle)
    unittest.TextTestRunner().run(suite)

Для додаткового алгоритму існує додатковий необов'язковий графічний тест для підтвердження його дійсності:

import random
from matplotlib import pyplot
from triangle_test import point_in_triangle

###############################################################################
# The area #
size_x = 64
size_y = 64

# The triangle #
triangle = ((22 , 8),
            (12 , 55),
            (7 , 19))

# Number of random points #
count_points = 10000

# Prepare the figure #
figure = pyplot.figure()
axes = figure.add_subplot(111, aspect='equal')
axes.set_title("Test the 'point_in_triangle' function")
axes.set_xlim(0, size_x)
axes.set_ylim(0, size_y)

# Plot the triangle #
from matplotlib.patches import Polygon
axes.add_patch(Polygon(triangle, linewidth=1, edgecolor='k', facecolor='none'))

# Plot the points #
for i in range(count_points):
    x = random.uniform(0, size_x)
    y = random.uniform(0, size_y)
    if point_in_triangle((x,y), triangle): pyplot.plot(x, y, '.g')
    else:                                  pyplot.plot(x, y, '.b')

# Save it #
figure.savefig("point_in_triangle.pdf")

Створення такої графіки:

Перевірте функцію point_in_triangle


1

Існують примхливі крайові умови, коли точка знаходиться саме на спільному краї двох сусідніх трикутників. Точка не може бути в обох або в жодному з трикутників. Вам потрібен довільний, але послідовний спосіб призначення точки. Наприклад, провести горизонтальну лінію через точку. Якщо лінія перетинається з іншою стороною трикутника праворуч, точка трактується так, ніби вона знаходиться всередині трикутника. Якщо перехрестя ліворуч, точка знаходиться зовні.

Якщо лінія, на якій лежить точка, горизонтальна, скористайтеся вгорі / внизу.

Якщо точка знаходиться на загальній вершині декількох трикутників, використовуйте трикутник, у центрі якого точка утворює найменший кут.

Веселіше: три бали можуть бути по прямій (нульові градуси), наприклад (0,0) - (0,10) - (0,5). У алгоритмі тріангуляції "вухо" (0,10) повинно бути відключене, при цьому "трикутник" утворюється як вироджений випадок прямої лінії.


1

Це найпростіша концепція, щоб визначити, чи знаходиться точка всередині або поза трикутником або на руці трикутника.

Визначення точки знаходиться всередині трикутника за детермінантами:

Визначення точки знаходиться всередині трикутника за детермінантами

Найпростіший робочий код:

#-*- coding: utf-8 -*-

import numpy as np

tri_points = [(1,1),(2,3),(3,1)]

def pisinTri(point,tri_points):
    Dx , Dy = point

    A,B,C = tri_points
    Ax, Ay = A
    Bx, By = B
    Cx, Cy = C

    M1 = np.array([ [Dx - Bx, Dy - By, 0],
                    [Ax - Bx, Ay - By, 0],
                    [1      , 1      , 1]
                  ])

    M2 = np.array([ [Dx - Ax, Dy - Ay, 0],
                    [Cx - Ax, Cy - Ay, 0],
                    [1      , 1      , 1]
                  ])

    M3 = np.array([ [Dx - Cx, Dy - Cy, 0],
                    [Bx - Cx, By - Cy, 0],
                    [1      , 1      , 1]
                  ])

    M1 = np.linalg.det(M1)
    M2 = np.linalg.det(M2)
    M3 = np.linalg.det(M3)
    print(M1,M2,M3)

    if(M1 == 0 or M2 == 0 or M3 ==0):
            print("Point: ",point," lies on the arms of Triangle")
    elif((M1 > 0 and M2 > 0 and M3 > 0)or(M1 < 0 and M2 < 0 and M3 < 0)):
            #if products is non 0 check if all of their sign is same
            print("Point: ",point," lies inside the Triangle")
    else:
            print("Point: ",point," lies outside the Triangle")

print("Vertices of Triangle: ",tri_points)
points = [(0,0),(1,1),(2,3),(3,1),(2,2),(4,4),(1,0),(0,4)]
for c in points:
    pisinTri(c,tri_points)

0

Найпростіший спосіб, і він працює з усіма типами трикутників, - це просто визначити кути кутів точки P, A, B, C. Якщо будь-який з кутів більший за 180,0 градусів, то він знаходиться зовні, якщо 180,0, то він знаходиться на окружності, і якщо acos обманює вас і менше 180.0, то він знаходиться всередині. Погляньте на розуміння http: // math-Physics -psychology.blogspot.hu/2015/01/earlish-determina-that-point-is.html


0

Чесно кажучи, це так просто, як відповідь Саймона С Стівена, проте при такому підході ви не маєте надійного контролю над тим, чи хочете ви, щоб точки на краях трикутника були включені чи ні.

Мій підхід трохи інший, але дуже базовий. Розглянемо наступний трикутник;

введіть тут опис зображення

Щоб мати точку в трикутнику, ми повинні задовольнити 3 умови

  1. Кут ACE (зелений) повинен бути меншим, ніж кут ACB (червоний)
  2. Кут ECB (синій) повинен бути меншим, ніж кут ACB (червоний)
  3. Точка E і точка C повинні мати однаковий знак, коли їх значення x і y застосовуються до рівняння | AB | рядок.

У цьому методі ви маєте повний контроль, щоб включити або виключити точку на ребрах окремо. Тож ви можете перевірити, чи є точка у трикутнику, що включає лише | AC | край, наприклад.

Тому моє рішення в JavaScript було б таким;

function isInTriangle(t,p){

  function isInBorder(a,b,c,p){
    var m = (a.y - b.y) / (a.x - b.x);                     // calculate the slope
    return Math.sign(p.y - m*p.x + m*a.x - a.y) === Math.sign(c.y - m*c.x + m*a.x - a.y);
  }
  
  function findAngle(a,b,c){                               // calculate the C angle from 3 points.
    var ca = Math.hypot(c.x-a.x, c.y-a.y),                 // ca edge length
        cb = Math.hypot(c.x-b.x, c.y-b.y),                 // cb edge length
        ab = Math.hypot(a.x-b.x, a.y-b.y);                 // ab edge length
    return Math.acos((ca*ca + cb*cb - ab*ab) / (2*ca*cb)); // return the C angle
  }

  var pas = t.slice(1)
             .map(tp => findAngle(p,tp,t[0])),             // find the angle between (p,t[0]) with (t[1],t[0]) & (t[2],t[0])
       ta = findAngle(t[1],t[2],t[0]);
  return pas[0] < ta && pas[1] < ta && isInBorder(t[1],t[2],t[0],p);
}

var triangle = [{x:3, y:4},{x:10, y:8},{x:6, y:10}],
      point1 = {x:3, y:9},
      point2 = {x:7, y:9};

console.log(isInTriangle(triangle,point1));
console.log(isInTriangle(triangle,point2));


0
bool isInside( float x, float y, float x1, float y1, float x2, float y2, float x3, float y3 ) {
  float l1 = (x-x1)*(y3-y1) - (x3-x1)*(y-y1), 
    l2 = (x-x2)*(y1-y2) - (x1-x2)*(y-y2), 
    l3 = (x-x3)*(y2-y3) - (x2-x3)*(y-y3);
  return (l1>0 && l2>0  && l3>0) || (l1<0 && l2<0 && l3<0);
}

Це не може бути ефективнішим за це! Кожна сторона трикутника може мати незалежне положення та орієнтацію, отже, три обчислення: l1, l2 та l3, безумовно, потрібні, включаючи 2 множення. Як тільки l1, l2 та l3 відомі, результат - це лише кілька основних порівнянь та булевих операцій.


0

Нібито високоефективний код, який я адаптував у JavaScript (стаття нижче):

function pointInTriangle (p, p0, p1, p2) {
  return (((p1.y - p0.y) * (p.x - p0.x) - (p1.x - p0.x) * (p.y - p0.y)) | ((p2.y - p1.y) * (p.x - p1.x) - (p2.x - p1.x) * (p.y - p1.y)) | ((p0.y - p2.y) * (p.x - p2.x) - (p0.x - p2.x) * (p.y - p2.y))) >= 0;
}
  • pointInTriangle(p, p0, p1, p2) - для трикутників проти годинникової стрілки
  • pointInTriangle(p, p0, p1, p2) - для трикутників за годинниковою стрілкою

Подивіться у jsFiddle (включений тест на працездатність), там також є окрема функція перевірки намотування. Або натисніть "Запустити фрагмент коду" нижче

var ctx = $("canvas")[0].getContext("2d");
var W = 500;
var H = 500;

var point = { x: W / 2, y: H / 2 };
var triangle = randomTriangle();

$("canvas").click(function(evt) {
    point.x = evt.pageX - $(this).offset().left;
    point.y = evt.pageY - $(this).offset().top;
    test();
});

$("canvas").dblclick(function(evt) {
    triangle = randomTriangle();
    test();
});

document.querySelector('#performance').addEventListener('click', _testPerformance);

test();

function test() {
    var result = checkClockwise(triangle.a, triangle.b, triangle.c) ? pointInTriangle(point, triangle.a, triangle.c, triangle.b) : pointInTriangle(point, triangle.a, triangle.b, triangle.c);
    
    var info = "point = (" + point.x + "," + point.y + ")\n";
    info += "triangle.a = (" + triangle.a.x + "," + triangle.a.y + ")\n";
    info += "triangle.b = (" + triangle.b.x + "," + triangle.b.y + ")\n";
    info += "triangle.c = (" + triangle.c.x + "," + triangle.c.y + ")\n";
    info += "result = " + (result ? "true" : "false");

    $("#result").text(info);
    render();
}

function _testPerformance () {
	var px = [], py = [], p0x = [], p0y = [], p1x = [], p1y = [], p2x = [], p2y = [], p = [], p0 = [], p1 = [], p2 = [];
    
	for(var i = 0; i < 1000000; i++) {
    p[i] = {x: Math.random() * 100, y: Math.random() * 100};
    p0[i] = {x: Math.random() * 100, y: Math.random() * 100};
    p1[i] = {x: Math.random() * 100, y: Math.random() * 100};
    p2[i] = {x: Math.random() * 100, y: Math.random() * 100};
  }
  console.time('optimal: pointInTriangle');
  for(var i = 0; i < 1000000; i++) {
    pointInTriangle(p[i], p0[i], p1[i], p2[i]);
  }
  console.timeEnd('optimal: pointInTriangle');

  console.time('original: ptInTriangle');
  for(var i = 0; i < 1000000; i++) {
  	ptInTriangle(p[i], p0[i], p1[i], p2[i]);
  }
  console.timeEnd('original: ptInTriangle');
}

function pointInTriangle (p, p0, p1, p2) {
	return (((p1.y - p0.y) * (p.x - p0.x) - (p1.x - p0.x) * (p.y - p0.y)) | ((p2.y - p1.y) * (p.x - p1.x) - (p2.x - p1.x) * (p.y - p1.y)) | ((p0.y - p2.y) * (p.x - p2.x) - (p0.x - p2.x) * (p.y - p2.y))) >= 0;
}

function ptInTriangle(p, p0, p1, p2) {
    var s = (p0.y * p2.x - p0.x * p2.y + (p2.y - p0.y) * p.x + (p0.x - p2.x) * p.y);
    var t = (p0.x * p1.y - p0.y * p1.x + (p0.y - p1.y) * p.x + (p1.x - p0.x) * p.y);

    if (s <= 0 || t <= 0) return false;

    var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
    return (s + t) < A;
}

function render() {
    ctx.fillStyle = "#CCC";
    ctx.fillRect(0, 0, 500, 500);
    drawTriangle(triangle.a, triangle.b, triangle.c);
    drawPoint(point);
}

function checkClockwise(p0, p1, p2) {
    var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
    return A > 0;
}

function drawTriangle(p0, p1, p2) {
    ctx.fillStyle = "#999";
    ctx.beginPath();
    ctx.moveTo(p0.x, p0.y);
    ctx.lineTo(p1.x, p1.y);
    ctx.lineTo(p2.x, p2.y);
    ctx.closePath();
    ctx.fill();
    ctx.fillStyle = "#000";
    ctx.font = "12px monospace";
    ctx.fillText("1", p0.x, p0.y);
    ctx.fillText("2", p1.x, p1.y);
    ctx.fillText("3", p2.x, p2.y);
}

function drawPoint(p) {
    ctx.fillStyle = "#F00";
    ctx.beginPath();
    ctx.arc(p.x, p.y, 5, 0, 2 * Math.PI);
    ctx.fill();
}

function rand(min, max) {
	return Math.floor(Math.random() * (max - min + 1)) + min;
}

function randomTriangle() {
    return {
        a: { x: rand(0, W), y: rand(0, H) },
        b: { x: rand(0, W), y: rand(0, H) },
        c: { x: rand(0, W), y: rand(0, H) }
    };
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id="performance">Run performance test (open console)</button>
<pre>Click: place the point.
Double click: random triangle.</pre>
<pre id="result"></pre>
<canvas width="500" height="500"></canvas>

Натхненно цим: http://www.phatcode.net/articles.php?id=459


-1
bool point2Dtriangle(double e,double f, double a,double b,double c, double g,double h,double i, double v, double w){
    /* inputs: e=point.x, f=point.y
               a=triangle.Ax, b=triangle.Bx, c=triangle.Cx 
               g=triangle.Ay, h=triangle.By, i=triangle.Cy */
    v = 1 - (f * (b - c) + h * (c - e) + i * (e - b)) / (g * (b - c) + h * (c - a) + i * (a - b));
    w = (f * (a - b) + g * (b - e) + h * (e - a)) / (g * (b - c) + h * (c - a) + i * (a - b));
    if (*v > -0.0 && *v < 1.0000001 && *w > -0.0 && *w < *v) return true;//is inside
    else return false;//is outside
    return 0;
} 

майже досконалі декартові координати, перетворені з барицентричних, експортуються в подвійних * v (x) і * w (y). У обох експортних дублях має бути знак * раніше, у кожному випадку, ймовірно: * v та * w Код можна використовувати і для іншого трикутника чотирикутника. Цим підписом написано лише трикутник abc від годинникової стрілки abcd quad.

A---B
|..\\.o|  
|....\\.| 
D---C 

точка o знаходиться всередині трикутника ABC для тестування за допомогою другого трикутника викликайте цю функцію напрямок CDA, і результати повинні бути правильними після *v=1-*v;і *w=1-*w;для чотирикутника


-1

Мені потрібна була точка в трикутнику в "контрольованому середовищі", коли ви абсолютно впевнені, що трикутники будуть за годинниковою стрілкою. Отже, я взяв jsfiddle Перро Азула і змінив його, як пропонував копрок для таких випадків; також видалено зайві 0,5 та 2 множення, оскільки вони просто скасовують одне одного.

http://jsfiddle.net/dog_funtom/H7D7g/

var ctx = $("canvas")[0].getContext("2d");
var W = 500;
var H = 500;

var point = {
    x: W / 2,
    y: H / 2
};
var triangle = randomTriangle();

$("canvas").click(function (evt) {
    point.x = evt.pageX - $(this).offset().left;
    point.y = evt.pageY - $(this).offset().top;
    test();
});

$("canvas").dblclick(function (evt) {
    triangle = randomTriangle();
    test();
});

test();

function test() {
    var result = ptInTriangle(point, triangle.a, triangle.b, triangle.c);

    var info = "point = (" + point.x + "," + point.y + ")\n";
    info += "triangle.a = (" + triangle.a.x + "," + triangle.a.y + ")\n";
    info += "triangle.b = (" + triangle.b.x + "," + triangle.b.y + ")\n";
    info += "triangle.c = (" + triangle.c.x + "," + triangle.c.y + ")\n";
    info += "result = " + (result ? "true" : "false");

    $("#result").text(info);
    render();
}

function ptInTriangle(p, p0, p1, p2) {
    var s = (p0.y * p2.x - p0.x * p2.y + (p2.y - p0.y) * p.x + (p0.x - p2.x) * p.y);
    var t = (p0.x * p1.y - p0.y * p1.x + (p0.y - p1.y) * p.x + (p1.x - p0.x) * p.y);

    if (s <= 0 || t <= 0) return false;

    var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);

    return (s + t) < A;
}

function checkClockwise(p0, p1, p2) {
    var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);
    return A > 0;
}

function render() {
    ctx.fillStyle = "#CCC";
    ctx.fillRect(0, 0, 500, 500);
    drawTriangle(triangle.a, triangle.b, triangle.c);
    drawPoint(point);
}

function drawTriangle(p0, p1, p2) {
    ctx.fillStyle = "#999";
    ctx.beginPath();
    ctx.moveTo(p0.x, p0.y);
    ctx.lineTo(p1.x, p1.y);
    ctx.lineTo(p2.x, p2.y);
    ctx.closePath();
    ctx.fill();
    ctx.fillStyle = "#000";
    ctx.font = "12px monospace";
    ctx.fillText("1", p0.x, p0.y);
    ctx.fillText("2", p1.x, p1.y);
    ctx.fillText("3", p2.x, p2.y);
}

function drawPoint(p) {
    ctx.fillStyle = "#F00";
    ctx.beginPath();
    ctx.arc(p.x, p.y, 5, 0, 2 * Math.PI);
    ctx.fill();
}

function rand(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

function randomTriangle() {
    while (true) {
        var result = {
            a: {
                x: rand(0, W),
                y: rand(0, H)
            },
            b: {
                x: rand(0, W),
                y: rand(0, H)
            },
            c: {
                x: rand(0, W),
                y: rand(0, H)
            }
        };
        if (checkClockwise(result.a, result.b, result.c)) return result;
    }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<pre>Click: place the point.
Double click: random triangle.</pre>

<pre id="result"></pre>

<canvas width="500" height="500"></canvas>

Ось еквівалентний код C # для Unity:

public static bool IsPointInClockwiseTriangle(Vector2 p, Vector2 p0, Vector2 p1, Vector2 p2)
{
    var s = (p0.y * p2.x - p0.x * p2.y + (p2.y - p0.y) * p.x + (p0.x - p2.x) * p.y);
    var t = (p0.x * p1.y - p0.y * p1.x + (p0.y - p1.y) * p.x + (p1.x - p0.x) * p.y);

    if (s <= 0 || t <= 0)
        return false;

    var A = (-p1.y * p2.x + p0.y * (-p1.x + p2.x) + p0.x * (p1.y - p2.y) + p1.x * p2.y);

    return (s + t) < A;
}

-3

Один з найпростіших способів перевірити, чи поверхня, утворена вершинами трикутника (x1, y1), (x2, y2), (x3, y3), є позитивною чи ні.

Площа може бути обчислена за формулою:

1/2 [x1 (y2 – y3) + x2 (y3 – y1) + x3 (y1 – y2)]

або python-код можна записати так:

def triangleornot(p1,p2,p3):
    return (1/ 2) [p1[0](p2[1]–p3[1]) + p2[0] (p3[1]–p1[1]) + p3[0] (p1[0]–p2[0])]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.