Come puoi determinare che un punto si trova tra altri due punti su un segmento di linea?


93

Supponiamo che tu abbia un piano bidimensionale con 2 punti (chiamati aeb) rappresentati da un intero x e un intero ay per ogni punto.

Come puoi determinare se un altro punto c si trova sul segmento di linea definito da aeb?

Uso principalmente Python, ma gli esempi in qualsiasi lingua sarebbero utili.


4
Vedo un sacco di cose length = sqrt (x) in queste risposte; potrebbero funzionare, ma non sono veloci. Considera l'idea di usare la lunghezza al quadrato; se stai solo confrontando i valori di lunghezza al quadrato tra loro, non c'è perdita di precisione e salvi chiamate lente a sqrt ().
ojrac

1
Anche il punto c è rappresentato da 2 numeri interi? Se è così, allora vuoi sapere se c è esattamente lungo una retta reale tra aeb o si trova sull'approssimazione raster della linea retta tra aeb? Questo è un importante chiarimento.
RobS

Una domanda simile è stata posta qui: stackoverflow.com/q/31346862/1914034 con una soluzione quando è necessaria una distanza buffer dalla linea
Below the Radar


1
Avviso ai futuri lettori: un discreto numero di risposte sono errate o incomplete. Alcuni casi limite che spesso non funzionano sono le linee orizzontali e verticali.
Stefnotch il

Risposte:


127

Controlla se il prodotto incrociato di (ba) e (ca) è 0, come dice Darius Bacon, ti dice se i punti a, bec sono allineati.

Ma, poiché vuoi sapere se c è tra aeb, devi anche controllare che il prodotto scalare di (ba) e (ca) sia positivo ed è minore del quadrato della distanza tra a e b.

In pseudocodice non ottimizzato:

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

5
-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)è sufficiente, non è vero?
jfs

9
Il valore assoluto del crossproduct è il doppio dell'area del triangolo formato dai tre punti (con il segno che indica il lato il terzo punto) quindi IMHO dovresti usare un epsilon che sia proporzionale alla distanza tra i due estremi.
Bart il

2
Puoi dirci perché non dovrebbe funzionare con i numeri interi? Non vedo il problema, a condizione che il controllo epsilon sia sostituito da "! = 0".
Cyrille Ka

2
Sì, la parentesi in più è solo un errore di battitura. Sono passati 4 anni prima che qualcuno dicesse qualcosa. :)
Cyrille Ka

4
È necessario rinominare a, b, c per rendere più chiaro quali sono i punti finali del segmento e quale è il punto della query.
Craig Gidney

48

Ecco come lo farei:

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)

7
Questa è una soluzione elegante.
Paul D.Eden,

6
L'unico problema con questo è la stabilità numerica: prendere differenze di numeri e così via può perdere precisione.
Jonathan Leffler

26
-epsilon < (distance(a, c) + distance(c, b) - distance(a, b)) < epsilon
jfs

1
@jfs cosa intendi con questo? A cosa serve il controllo con epsilon?
Neon Warge

3
@NeonWarge: sqrt () restituisce un float. ==è una cosa sbagliata per i float nella maggior parte dei casi . math.isclose()potrebbe essere usato invece. Non c'era math.isclose()nel 2008 e quindi ho fornito la disuguaglianza esplicita con epsilon( abs_tolin math.isclose()parlare).
jfs

35

Controlla se il prodotto incrociato di b-aed c-aè 0: significa che tutti i punti sono allineati. Se lo sono, controlla se cle coordinate di sono comprese tra a"se b". Usa le coordinate xo y, purché ae bsiano separate su quell'asse (o sono uguali su entrambi).

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

Questa risposta era un pasticcio di tre aggiornamenti. Le informazioni utili da loro: il capitolo di Brian Hayes in Beautiful Code copre lo spazio di progettazione per una funzione di test di collinearità - sfondo utile. La risposta di Vincent ha contribuito a migliorare questo. Ed è stato Hayes a suggerire di testare solo una delle coordinate xo y; originariamente il codice aveva andal posto di if a.x != b.x else.


Poiché il controllo della distanza è più veloce, sarebbe meglio prima controllare l'intervallo, quindi verificare la presenza di collineare se nel riquadro di delimitazione.
Grant M

1
La funzione is_on (a, b, c) è sbagliata nel caso in cui a == b! = C. In tal caso restituirà vero, anche se c non interseca un segmento di linea da a a b.
Mikko Virkkilä

@SuperFlux, ho appena provato a eseguirlo e ho ottenuto False.
Darius Bacon

2
Penso che questa risposta sia chiaramente superiore alla risposta attualmente accettata.
Rick sostiene Monica l'

1
collineare (a, b, c) sta testando i numeri in virgola mobile in base all'uguaglianza. Non dovrebbe usare un epsilon / tolleranza?
jwezorek

7

Ecco un altro approccio:

  • Supponiamo che i due punti siano A (x1, y1) e B (x2, y2)
  • L'equazione della retta che passa per quei punti è (x-x1) / (y-y1) = (x2-x1) / (y2-y1) .. (facendo solo l'uguaglianza delle pendenze)

Il punto C (x3, y3) si troverà tra A e B se:

  • x3, y3 soddisfa l'equazione precedente.
  • x3 si trova tra x1 e x2 e y3 si trova tra y1 e y2 (controllo banale)

Ciò non tiene conto degli errori di arrotondamento (inesattezza delle coordinate).
Bart il

Questa è l'idea giusta, credo, ma a corto di dettagli (come controlliamo l'equazione in pratica?) E un po 'buggy: l'ultimo y3 dovrebbe essere y2.
Darius Bacon

@Darius: corretto l'errore di battitura
Harley Holcombe

7

La lunghezza del segmento non è importante, quindi l'utilizzo di una radice quadrata non è necessario e dovrebbe essere evitato poiché potremmo perdere un po 'di precisione.

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)

Alcuni esempi casuali di utilizzo:

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)

1
Se cx o cy sono float, il primo ==in is_between()potrebbe fallire (btw è un prodotto incrociato sotto mentite spoglie).
jfs

aggiungi a is_between():a, b = self.a, self.b
jfs

Sembra che restituirà vero se tutti e tre i punti sono uguali (il che va bene, imho) ma falso se esattamente due dei punti sono uguali - un modo piuttosto incoerente per definire il betweenness. Ho pubblicato un'alternativa nella mia risposta.
Darius Bacon

risolto questo problema con un altro trucco cmp, ma questo codice inizia a puzzare ;-)
vincent

5

Ecco un modo diverso per farlo, con il codice fornito in C ++. Dati due punti, l1 e l2 è banale esprimere il segmento di linea tra di loro come

l1 + A(l2 - l1)

dove 0 <= A <= 1. Questa è conosciuta come la rappresentazione vettoriale di una linea se sei più interessato al di là del semplice utilizzo per questo problema. Possiamo dividere le componenti xey di questo, dando:

x = l1.x + A(l2.x - l1.x)
y = l1.y + A(l2.y - l1.y)

Prendi un punto (x, y) e sostituisci i suoi componenti xey in queste due espressioni per risolvere A. Il punto è sulla linea se le soluzioni per A in entrambe le espressioni sono uguali e 0 <= A <= 1. Perché la risoluzione per A richiede la divisione, ci sono casi speciali che richiedono una gestione per interrompere la divisione per zero quando il segmento di linea è orizzontale o verticale. La soluzione finale è la seguente:

// 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;
}

4

Utilizzando un approccio più geometrico, calcola le seguenti distanze:

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)

e verifica se ac + bc è uguale a ab :

is_on_segment = abs(ac + bc - ab) < EPSILON

Questo perché ci sono tre possibilità:

  • I 3 punti formano un triangolo => ac + bc> ab
  • Sono collineari ec è al di fuori del segmento ab => ac + bc> ab
  • Sono collineari ec è all'interno del segmento ab => ac + bc = ab

Come menzionato da Jonathan Leffler in un altro commento, questo ha problemi numerici che altri approcci come il prodotto incrociato evitano. Il capitolo a cui mi collego nella mia risposta spiega.
Darius Bacon il

3

Ok, molte menzioni di algebra lineare (prodotto incrociato di vettori) e questo funziona in uno spazio reale (cioè continuo o in virgola mobile) ma la domanda specificava specificamente che i due punti erano espressi come numeri interi e quindi un prodotto incrociato non è il corretto soluzione sebbene possa fornire una soluzione approssimativa.

La soluzione corretta è usare l'algoritmo di linea di Bresenham tra i due punti e vedere se il terzo punto è uno dei punti sulla linea. Se i punti sono sufficientemente distanti da rendere inefficace il calcolo dell'algoritmo (e dovrebbe essere molto grande affinché sia ​​così) sono sicuro che potresti scavare e trovare ottimizzazioni.


Risolve come disegnare una linea attraverso uno spazio intero bidimensionale tra due punti arbitrari e il suo matematicamente corretto. Se il terzo punto è uno dei punti su quella linea, allora è, per definizione, tra quei due punti.
cletus

1
No, l'algoritmo di linea di Bresenham risolve come creare un'approssimazione di un segmento di linea in uno spazio intero bidimensionale. Non vedo dal messaggio del poster originale che fosse una domanda sulla rasterizzazione.
Cyrille Ka

"Diciamo che hai un piano bidimensionale con 2 punti (chiamati aeb) su di esso rappresentati da un x INTEGER e ay INTEGER per ogni punto." (enfasi aggiunta da me).
cletus

1
Penso che l'algoritmo di linea di Bresenham fornisca punti interi a una linea, che possono quindi essere utilizzati per disegnare la linea. Potrebbero non essere in linea. Ad esempio, se da (0,0) a (11,13) l'algoritmo darà un numero di pixel da disegnare ma non ci sono punti interi eccetto i punti finali, perché 11 e 13 sono coprimi.
Grant M

Come può una soluzione corretta per lo spazio reale (ℝ × ℝ) non essere corretta per lo spazio intero (ℕ × ℕ), come ℕ∈ℝ. O vuoi dire: "non è ottimale per ..." invece di 'non è corretto?
Ideogramma

2

Il prodotto scalare tra (ca) e (ba) deve essere uguale al prodotto delle loro lunghezze (questo significa che i vettori (ca) e (ba) sono allineati e con la stessa direzione). Inoltre, la lunghezza di (ca) deve essere minore o uguale a quella di (ba). Pseudocodice:

# 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

L'ultima condizione non dovrebbe essere più simile a: ABS (prodotto - lengthca * lengthba) <epsilon?
Jonathan Leffler

Non dovresti invece confrontare le lunghezze al quadrato? Le radici quadrate devono essere evitate. Inoltre, se questo è inevitabile a causa dell'overflow, puoi usare math.hypot invece di math.sqrt (con l'appropriata modifica degli argomenti).
Darius Bacon

Mi chiedo anche di quell'epsilon. Puoi spiegarlo? Naturalmente, se dobbiamo occuparci dei galleggianti, dobbiamo stare attenti ai confronti, ma non mi è chiaro perché un epsilon renda questo particolare confronto più accurato.
Darius Bacon

Concordo. Ci sono molte buone risposte a questa domanda, e questa va bene. Ma questo codice deve essere modificato per non utilizzare sqrt e l'ultimo confronto corretto.
Cyrille Ka

@ Jonathan: infatti il ​​codice è più familiare ed elegante usando gli addominali. Grazie.
Federico A. Ramponi

2

Avevo bisogno di questo per JavaScript da utilizzare in una tela html5 per rilevare se il cursore dell'utente era sopra o vicino a una determinata riga. Quindi ho modificato la risposta data da Darius Bacon in 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

2

Puoi usare il prodotto wedge e dot:

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

1

Ecco come l'ho fatto a scuola. Ho dimenticato perché non è una buona idea.

MODIFICARE:

@ Darius Bacon: cita un libro "Beautiful Code" che contiene una spiegazione del perché il codice sottostante non è una buona idea.

#!/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()

1

Qualsiasi punto sul segmento di linea ( a , b ) (dove un e b sono vettori) può essere espresso come una combinazione lineare dei due vettori a e b :

In altre parole, se c si trova sul segmento di linea ( a , b ):

c = ma + (1 - m)b, where 0 <= m <= 1

Risolvendo per m , otteniamo:

m = (c.x - b.x)/(a.x - b.x) = (c.y - b.y)/(a.y - b.y)

Quindi, il nostro test diventa (in 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)

1

c # Da http://www.faqs.org/faqs/graphics/algorithms-faq/ -> Oggetto 1.02: Come faccio a trovare la distanza da un punto a una linea?

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;
        }

Il modo corretto per evitare problemi di precisione nella maggior parte degli altri approcci. Anche significativamente più efficiente della maggior parte degli altri apprendisti.
Robin Davies

1

Ecco del codice Java che ha funzionato per me:

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);
    if (dotProduct < 0) return true;
    return false;
}

1
dotProduct può solo parlare di allineamento. Il tuo codice è incompleto !!! Con a (0,0), b (4,0), c (1,1) hai dotproduct = (1-0) * (1-4) + (1-0) * (1-0) = - 3 + 1 = -3
user43968

0

che ne dici di assicurarti che la pendenza sia la stessa e che il punto sia tra gli altri?

dati punti (x1, y1) e (x2, y2) (con x2> x1) e punto candidato (a, b)

se (b-y1) / (a-x1) = (y2-y2) / (x2-x1) E x1 <a <x2

Quindi (a, b) deve essere sulla linea tra (x1, y1) e (x2, y2)


Che ne dici di pazzi problemi di precisione in virgola mobile quando alcune delle coordinate sono vicine o identiche?
Robin Davies

I computer non funzionano bene in virgola mobile. In un computer non esistono valori regolabili all'infinito. Quindi, se stai usando i punti mobili, devi stabilire la definizione e usare un piccolo valore epsilon come determinante, e qualsiasi due punti più vicini di quell'epsilon dovrebbero essere considerati lo stesso punto. Determina il punto che è sulla stessa linea e alla stessa distanza dai punti finali. Se il tuo punto candidato è all'interno del tuo epsilon di quel punto calcolato, chiamalo identico.
Charles Bretana

Il punto era che questa risposta è inutilizzabile a causa di problemi di precisione quando la si implementa effettivamente nel codice. Quindi nessuno dovrebbe usarlo. Una bella risposta a un test di matematica. Ma un fallimento competitivo in un corso di comp-sci. Sono venuto qui cercando il metodo del prodotto puntato (che è corretto); quindi ho pensato di dedicare alcuni istanti a contrassegnare le molte risposte in questo thread che non sono corrette in modo che altri che hanno familiarità con la soluzione corretta non siano tentati di usarle.
Robin Davies

Hai ragione sui problemi che sorgono a causa dell'incapacità dei computer di rappresentare ogni possibile numero reale su una linea. Non sei corretto sul fatto che qualsiasi soluzione (incluso il metodo del prodotto a punti) possa essere immune a questi problemi. Qualsiasi soluzione può soffrire di questi problemi. A meno che non si conceda un margine per epsilon accettabile, un punto che si trova esattamente sulla linea (ma le cui coordinate non sono rappresentabili nella rappresentazione binaria in virgola mobile ieee), fallirà anche il test del prodotto puntuale, poiché il computer rappresenterà le coordinate del punto in modo impreciso di una certa quantità.
Charles Bretana

0

Una risposta in C # utilizzando una classe 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;
}

Nota che

s * s

è il prodotto scalare del vettore del segmento tramite l'overloading dell'operatore in C #

La chiave è sfruttare la proiezione del punto sulla linea infinita e osservare che la quantità scalare della proiezione ci dice banalmente se la proiezione è sul segmento oppure no. Possiamo regolare i limiti della quantità scalare per utilizzare una tolleranza fuzzy.

Se la proiezione è entro i limiti, verifichiamo solo se la distanza dal punto alla proiezione è entro i limiti.

Il vantaggio rispetto all'approccio cross-product è che la tolleranza ha un valore significativo.


0

Ecco la mia soluzione con C # in 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;
}

Sembra che questo codice funzioni solo con segmenti di linea verticali e orizzontali. Cosa succede se ptLineStart è (0,0), ptLineEnd è (2,2) e ptPoint è (1, 1)?
vac

0

Versione C # della risposta di Jules:

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;
}

0

Puoi farlo risolvendo l'equazione di linea per quel segmento di linea con le coordinate del punto, saprai se quel punto è sulla linea e quindi controlla i limiti del segmento per sapere se è all'interno o all'esterno di esso. Puoi applicare una soglia perché è da qualche parte nello spazio molto probabilmente definita da un valore in virgola mobile e non devi colpire quella esatta. Esempio in 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;
    
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.