2010-03-19 10 views
7

Tengo una flecha dibujada entre dos objetos en un formulario de Win..NET Determinación del mouse está en línea dibujado entre dos puntos arbitrarios

¿Cuál sería la forma más sencilla de determinar que mi mouse se encuentra actualmente sobre esta línea o cerca de ella?

He considerado probar si el punto del ratón se cruza con un cuadrado definido y extrapolado por los dos puntos, sin embargo, esto solo sería factible si los dos puntos tienen valores x o y muy similares.

Estoy pensando, también, que este problema probablemente esté más en el dominio del álgebra lineal que en la trigonometría simple, y si bien recuerdo los aspectos más simples de las matrices, este problema escapa a mi conocimiento del álgebra lineal.

Por otro lado, si una biblioteca .NET puede hacer frente a la función, incluso mejor.

EDIT Gracias por las respuestas, hubo algunos muy buenos que merecen ser etiquetados como respondidos.

Elegí la respuesta de Coincoin como aceptada, ya que me gusta que podría aplicarse a cualquier forma dibujada, sin embargo terminé implementando la ecuación de Tim Robinson, ya que parecía mucho más eficiente con una ecuación simple en lugar de nuevas rutas de gráficos y bolígrafos, como en mi caso, necesito hacerlo en MooMove para relaciones 1-n diferentes (obviamente, habría almacenamiento en caché y optimizaciones, pero el punto aún persiste)

El problema principal con la ecuación era que parecía tratar la línea es infinita, así que agregué una prueba de límites también.

El código (corte inicial, probablemente Neaten un poco), para los interesados, está por debajo

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

y _bounds se define como:

_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

que necesito, también, tener en cuenta el umbral cuando lo haga el div por 0 caso especial de verificación Comprobar – johnc

Respuesta

7

Si desea fácilemte realizar pruebas golpeado en formas dibujadas arbitrarias, se puede crear una ruta que contiene el dibujo, entonces Widden el camino y hacer una prueba de visibilidad utilizando solo funciones de marco.

Por ejemplo, aquí se crea un trazado con una línea:

GraphicsPath path = new GraphicsPath(); 

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

Entonces, ensanchar el camino y crear una región de la prueba de posicionamiento:

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

Por último, la prueba de posicionamiento:

region.IsVisible(point); 

La ventaja de este método es que se puede extender fácilmente a las estrías, flechas, arco, pasteles o casi cualquier cosa dibujable con GDI +. La misma ruta se puede usar en la lógica HitTest y Draw extrayéndola.

Aquí está el código combinándolo todo:

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

Muy bien. ... de hecho ... –

+0

Muy bien, gracias – johnc

0

Salida MouseEnter (remitente del objeto, EventArgs e). Atrapa cuando "entra" en el área de control.

+0

MouseEnter en qué? Esta es una línea dibujada por el objeto gráfico GDI? – johnc

+0

Ah. No sabía que lo estabas dibujando directamente. Si se trata de un control de winforms, viene conectado con eventos de mouse. Posiblemente podría heredar de una base de control y anular el dibujo para recogerlos. –

+0

Gracias, pero me temo que es demasiado ineficiente usar una base de control en esta circunstancia. – johnc

4

Para responder "¿Pasa el mouse sobre esta línea?", Debe verificar la intersección de los puntos. Sin embargo, dado que estás preguntando "¿está el mouse cerca de la línea?", Parece que quieres calcular la distancia entre el punto del mouse y la línea.

Aquí hay una explicación razonable a fondo de la distancia punto-line: http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html

yo diría que necesita para poner en práctica esta fórmula en el código: (robado de wolframio.com)

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

Donde:

  • (x0, x0) es la ubicación del puntero del ratón
  • (x1, y1) es un extremo de la línea de
  • (x2 , y2) es el otro extremo de la línea
  • |n| es Math.Abs(n)
  • La mitad inferior es Math.Sqrt
  • puede ignorar el |v.r| si quieres
+0

Le daré una oportunidad y le haré saber. Gracias. – johnc

+0

@Tim Robinson, he implementado la ecuación, vea la pregunta – johnc

1

Es necesario construir dos (teórica) líneas de límite paralelas a la trayectoria ideal. Entonces solo necesita calcular, para cada posición del mouse, si el mouse está fuera o dentro del canal formado por esas líneas.

Usted no necesita calcular la distancia desde el mouse hasta la línea principal.

+0

Buena idea [texto arbitrario para compensar la longitud de comentario requerida] – johnc

2

Me gustaría calcular la ecuación Slope-Intercept (y = mx + b) para mi línea y luego usar eso para probar las coordenadas del mouse. Podrías poner fácilmente un rango alrededor de y para ver si estás "cerca".

Editar para muestra.

creo que algo como esto funciona:

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

Me gusta la eficiencia de este – johnc

+0

+1 Esto es lo que sugerí, pero él tomó el es hora de escribir las matemáticas. – egrunin

+1

Esto no funcionará para líneas verticales donde p1.X == p2.X, ya que se dividirá por cero. Las líneas verticales no tienen una pendiente definida. –

Cuestiones relacionadas