2010-03-19 6 views
7

Ho una freccia disegnata tra due oggetti su un Winform..NET Il mouse di accertamento è in linea tracciato tra due punti arbitrari

Quale sarebbe il modo più semplice per determinare che il mio mouse stia attualmente sorvolando o vicino a questa linea.

Ho considerato di verificare se il punto del mouse interseca un quadrato definito ed estrapolato dai due punti, tuttavia ciò sarebbe possibile solo se i due punti avessero valori x o y molto simili.

Sto pensando, inoltre, che questo problema sia probabilmente più nei regni dell'algebra lineare piuttosto che nella semplice trigonometria, e mentre ricordo gli aspetti più semplici delle matrici, questo problema va oltre la mia conoscenza dell'algebra lineare.

D'altra parte, se una libreria .NET può far fronte alla funzione, ancora meglio.

EDIT Grazie per le risposte, ci sono stati alcuni molto buoni tutti meritevoli di essere contrassegnati come risposta.

Ho scelto la risposta di Coincoin come accettata, poiché mi piace che possa essere applicata a qualsiasi forma disegnata, tuttavia ha finito per implementare l'equazione di Tim Robinson, poiché sembrava molto più efficiente con una semplice equazione piuttosto che la creazione di nuovi percorsi grafici e penne, come nel mio caso ho bisogno di farlo su MouseMove per 1-n diverse relazioni (ovviamente ci sarebbe un po 'di cache e ottimizzazioni, ma il punto rimane ancora)

Il problema principale con l'equazione era che sembrava trattare la linea è infinita, quindi ho aggiunto anche un test sui limiti.

Il codice (taglio iniziale, io probabilmente Neaten un po '), per chi fosse interessato, è al di sotto

if (Math.Sqrt(Math.Pow(_end.X - _start.X, 2) + 
      Math.Pow(_end.Y - _start.Y, 2)) == 0) 
    { 
     _isHovering = 
      new RectangleF(e.X, e.Y, 1, 1).IntersectsWith(_bounds); 
    } 
    else 
    { 
     float threshold = 10.0f; 

     float distance = (float)Math.Abs( 
      (((_end.X - _start.X) * (_start.Y - e.Y)) - 
      ((_start.X - e.X) * (_end.Y - _start.Y)))/
      Math.Sqrt(Math.Pow(_end.X - _start.X, 2) + 
      Math.Pow(_end.Y - _start.Y, 2))); 

     _isHovering = (
      distance <= threshold && 
       new RectangleF(e.X, e.Y, 1, 1).IntersectsWith(_bounds) 
      ); 
    } 

e _bounds è definito come:

_bounds = new Rectangle(
    Math.Min(_start.X, _end.X), 
    Math.Min(_start.Y, _end.Y), 
    Math.Abs(_start.X - _end.X), Math.Abs(_start.Y - _end.Y)); 
+0

ho bisogno, anche, di prendere in considerazione la soglia quando faccio il div da 0 caso speciale di controllo – johnc

risposta

7

Se si desidera facilmente effettuare prove di colpire su forme disegnate arbitrarie, è possibile creare un percorso che contiene il disegno, poi Widden il percorso e fare un test di visibilità utilizzando solo funzioni quadro.

Ad esempio, qui si crea un percorso con una linea:

GraphicsPath path = new GraphicsPath(); 

path.AddLine(x1, y1, x2, y2); 
path.CloseFigure(); 

Poi, allargare il percorso e creare una regione per la prova hit:

path.Widen(new Pen(Color.Black, 3)); 
region = new Region(path); 

Infine, l'hit test:

region.IsVisible(point); 

Il vantaggio di questo metodo è che può facilmente estendere a spline, frecce, arco, torte o praticamente nulla disegnabile con GDI +. Lo stesso percorso può essere utilizzato nella logica HitTest e Draw estraendolo.

Ecco il codice combinandolo tutto:

public GraphicsPath Path 
{ 
    get { 
     GraphicsPath path = new GraphicsPath(); 
     path.AddLine(x1, y1, x2, y2); 
     path.CloseFigure(); 

     return path; 
    } 
} 

bool HitTest(Point point) 
{ 
    using(Pen new pen = Pen(Color.Black, 3)) 
    using(GraphicsPaht path = Path) 
    { 
     path.Widen(pen); 

     using(Region region = new Region(path)) 
      return region.IsVisible(point); 
    } 
} 


void Draw(Graphics graphics) 
{ 
    using(Pen pen = new Pen(Color.Blue, 0)) 
    using(GraphicsPaht path = Path) 
     graphics.DrawPath(pen, path); 
} 
+0

Molto buono. ... anzi ... –

+0

Molto carino, grazie – johnc

0

Partenza MouseEnter (oggetto mittente, EventArgs e). Trappola quando "entra" nell'area di controllo.

+0

Controllare MouseEnter su cosa? Questa è una linea disegnata dall'oggetto grafico GDI? – johnc

+0

Ah. Non sapevo che lo stavi disegnando direttamente. Se è un controllo winforms, viene cablato con gli eventi del mouse. È possibile ereditare da una base di controllo e sovrascrivere il disegno per prelevarli. –

+0

Grazie, ma è troppo inefficiente usare una base di controllo in questa circostanza, temo. – johnc

4

Per rispondere "Il passaggio del mouse su questa linea?", È necessario verificare l'intersezione della linea di punti. Tuttavia, dal momento che stai chiedendo "il mouse è vicino alla linea?", Sembra che tu voglia calcolare la distanza tra il punto del mouse e la linea.

Ecco una spiegazione ragionevolmente accurata della distanza punto-line: http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html

direi che è necessario implementare questa formula nel codice: (rubato da Wolfram.com)

http://mathworld.wolfram.com/images/equations/Point-LineDistance2-Dimensional/NumberedEquation8.gif

Dove:

  • (x0, x0) è la posizione del puntatore del mouse
  • (x1, y1) è una delle estremità della linea
  • (x2 , y2) è l'altra estremità della linea
  • |n| è Math.Abs(n)
  • La metà inferiore è Math.Sqrt
  • si può ignorare il |v.r| se si desidera
+0

Farò un tentativo e ti faccio sapere. Grazie. – johnc

+0

@Tim Robinson, ho implementato l'equazione, vedere la domanda – johnc

1

È necessario costruire due (nozionale) linee di confine parallele al percorso ideale. Quindi devi solo calcolare, per ogni posizione del mouse, se il mouse si trova all'esterno o all'interno del canale formato da tali linee.

È non necessario calcolare la distanza dal mouse alla linea principale.

+0

Bella idea [testo arbitrario per compensare la lunghezza del commento richiesta] – johnc

2

Vorrei calcolare l'equazione di Pendenza-Intercept (y = mx + b) per la mia linea e quindi usarla per testare le coordinate del mouse. Si potrebbe facilmente mettere un intervallo intorno y per vedere se sei "vicino".

Modifica per campione.

penso che qualcosa di simile a questo funziona:

PointF currentPoint; 
PointF p1, p2; 
float threshold = 2.0f; 
float m = (p1.Y - p2.Y)/(p1.X - p2.X); 
float b = p1.Y - (m * p1.X); 

if (Math.Abs(((m * currentPoint.X) + b) - currentPoint.Y) <= threshold) 
{ 
    //On it. 
} 
+0

Mi piace l'efficienza di questo – johnc

+0

+1 Questo è quello che stavo suggerendo, ma ha preso il tempo di scrivere la matematica. – egrunin

+1

Questo non funzionerà per le linee verticali dove p1.X == p2.X, dato che dividerete per zero. Le linee verticali non hanno una pendenza definita. –

Problemi correlati