Editar: El comentario de los OPs ha sido escéptico sobre la eficiencia de la verificación de límite circular negativa sugerida para mejorar el algoritmo para verificar si un punto 2D arbitrario se encuentra dentro de un rectángulo girado y / o en movimiento. Jugueteando un poco en mi motor de juego 2D (OpenGL / C ++), complemente mi respuesta al proporcionar un punto de referencia de rendimiento de mi algoritmo contra los algoritmos actuales de verificación de punto en rectángulo (y variaciones) de los OP.
Originalmente sugerí dejar el algoritmo en su lugar (ya que es casi óptimo), pero simplificar mediante la simple lógica del juego: (1) usar un círculo preprocesado alrededor del rectángulo original; (2) hacer una verificación de distancia y si el punto se encuentra dentro del círculo dado; (3) use los OP u otro algoritmo sencillo (recomiendo el algoritmo isLeft como se proporciona en otra respuesta). La lógica detrás de mi sugerencia es que verificar si un punto está dentro de un círculo es considerablemente más eficiente que una verificación de límites de un rectángulo girado o cualquier otro polígono.
Mi escenario inicial para una prueba de referencia es ejecutar una gran cantidad de puntos que aparecen y desaparecen (cuya posición cambia en cada bucle de juego) en un espacio restringido que se llenará con alrededor de 20 cuadrados giratorios / en movimiento. He publicado un video ( enlace de youtube ) con fines ilustrativos. Observe los parámetros: número de puntos, números o rectángulos que aparecen al azar. Voy a comparar con los siguientes parámetros:
APAGADO : Algoritmo directo proporcionado por el OP sin verificaciones negativas de límites circulares
EN : uso de círculos procesados (límite) alrededor de los rectángulos como primera verificación de exclusión
ENCENDIDO + Pila : creación de límites de círculo en tiempo de ejecución dentro del bucle en la pila
ON + Distancia cuadrada : Usar distancias cuadradas como una optimización adicional para evitar tomar el algoritmo de raíz cuadrada más costoso (Pieter Geerkens).
Aquí hay un resumen de las diversas actuaciones de diferentes algoritmos mostrando el tiempo que lleva iterar a través del ciclo.
El eje x muestra una mayor complejidad al agregar más puntos (y así desacelerar el ciclo). (Por ejemplo, en 1000 verificaciones de puntos que aparecen aleatoriamente en un espacio confiado con 20 rectángulos, el bucle itera y llama al algoritmo 20000 veces). El eje y muestra el tiempo que tarda (ms) en completar todo el bucle con una alta resolución temporizador de rendimiento. Más de 20 ms sería problemático para un juego decente, ya que no aprovecharía los altos fps para interpolar una animación fluida y el juego puede parecer así 'resistente' a veces.
Resultado 1 : un algoritmo de enlace circular preprocesado con una comprobación negativa rápida dentro del bucle mejora el rendimiento en un 1900% en comparación con el algoritmo normal (5% del tiempo del bucle original sin una comprobación). El resultado es aproximadamente proporcional al número de iteraciones dentro de un ciclo, por lo tanto, no importa si verificamos 10 o 10000 puntos que aparecen al azar. Por lo tanto, en esta ilustración se puede aumentar la cantidad de objetos de forma segura a 10k sin sentir una pérdida de rendimiento.
Resultado 2 : un comentario anterior sugirió que el algoritmo puede ser más rápido pero requiere mucha memoria. Sin embargo, tenga en cuenta que almacenar un flotante para el tamaño del círculo preprocesado solo requiere 4 bytes. Esto no debería plantear ningún problema real a menos que el OP planee ejecutar simultáneamente más de 100000 objetos. Un enfoque alternativo y eficiente en la memoria es calcular el tamaño máximo del círculo en la pila dentro del bucle y dejarlo fuera de alcance con cada iteración y, por lo tanto, prácticamente no usar memoria por un precio desconocido de velocidad. De hecho, el resultado muestra que este enfoque es realmente más lento que el uso de un tamaño de círculo preprocesado, pero aún muestra una mejora considerable del rendimiento de alrededor del 1150% (es decir, el 8% del tiempo de procesamiento original).
Resultado 3 : mejoré aún más el algoritmo del resultado 1 al usar distancias cuadradas en lugar de distancias reales y, por lo tanto, tomar una operación de raíz cuadrada computacionalmente costosa. Esto solo aumenta ligeramente el rendimiento (2400%). (Nota: también pruebo tablas hash para matrices preprocesadas para aproximaciones de raíces cuadradas con un resultado similar pero ligeramente peor)
Resultado 4 : compruebo además mover / colisionar los rectángulos; sin embargo, esto no cambia los resultados básicos (como se esperaba) ya que la verificación lógica sigue siendo esencialmente la misma.
Resultado 5 : Varío el número de rectángulos y encuentro que el algoritmo se vuelve aún más eficiente cuanto menos concurrido esté el espacio (no se muestra en la demostración). El resultado también es algo esperado, ya que la probabilidad disminuye para que aparezca un punto dentro de un espacio pequeño entre un círculo y los límites del objeto. En el otro extremo, trato de aumentar el número de rectángulos también 100 dentro del mismo pequeño espacio confinado Y variarlos dinámicamente en tamaño en el tiempo de ejecución dentro del bucle (sin (iterador)). Esto todavía funciona extremadamente bien con un aumento en el rendimiento del 570% (o 15% del tiempo de bucle original).
Resultado 6 : pruebo algoritmos alternativos sugeridos aquí y encuentro una diferencia muy leve pero no significativa en el rendimiento (2%). El algoritmo IsLeft, interesante y más simple, funciona muy bien con un aumento del rendimiento del 17% (85% del tiempo de cálculo original), pero ni mucho menos la eficiencia de un algoritmo de verificación negativa rápida.
Mi punto es considerar primero el diseño eficiente y la lógica del juego, especialmente cuando se trata de límites y eventos de colisión. El algoritmo actual de los OP ya es bastante eficiente y una optimización adicional no es tan crítica como la optimización del concepto subyacente en sí. Además, es bueno comunicar el alcance y el propósito del juego, ya que la eficiencia de un algoritmo depende de manera crítica de ellos.
Sugiero que siempre intente comparar cualquier algoritmo complejo durante la etapa de diseño del juego, ya que simplemente mirar el código simple puede no revelar la verdad sobre el rendimiento real en tiempo de ejecución. El algoritmo sugerido puede no ser necesario aquí, si, por ejemplo, se desea simplemente probar si el cursor del mouse se encuentra dentro de un rectángulo o no, o cuando la mayoría de los objetos ya se están tocando. Si la mayoría de los puntos de verificación están dentro del rectángulo, el algoritmo será menos eficiente. (Sin embargo, entonces sería posible establecer un límite de 'círculo interno' como una verificación negativa secundaria). Las verificaciones de límite de círculo / esfera son muy útiles para cualquier detección de colisión decente de una gran cantidad de objetos que naturalmente tienen algo de espacio entre ellos. .
Rec Points Iter OFF ON ON_Stack ON_SqrDist Ileft Algorithm (Wondra)
(ms) (ms) (ms) (ms) (ms) (ms)
20 10 200 0.29 0.02 0.04 0.02 0.17
20 100 2000 2.23 0.10 0.20 0.09 1.69
20 1000 20000 24.48 1.25 1.99 1.05 16.95
20 10000 200000 243.85 12.54 19.61 10.85 160.58