Algoritmo de Weiler-Atherton

Este algoritmo se ideó originalmente para determinar la visibilidad de polígonos. Sin embargo, también sirve para determinar la intersección de dos polígonos, lo que implica que este mismo algoritmo se puede aplicar para recortar un polígono en un polígono de recorte. La ventaja de este algoritmo, comparado con otros, es que sirve para recortar polígonos convexos o cóncavos en cualquier polígono de recorte que también puede ser convexo o cóncavo; incluso acepta polígonos con agujeros. El resultado de este algoritmo es uno o varios polígonos que representan las partes visibles e interiores al polígono de recorte. Se supone, para cada polígono, una lista de vértices ordenada en el sentido de las agujas del reloj para representar el exterior del polígono. Una ordenación en el sentido contrario de las agujas del reloj sirve para describir cualesquier agujeros que contenga el polígono.

La estrategia se basa en recorrer los vértices y aristas del polígono a recortar que sean interiores al polígono de recorte. Si la arista está a punto de salirse del polígono de recorte, entonces cambiamos el recorrido al polígono de recorte, para continuar con su lista de vértices y aristas. Esto sugiere que debemos determinar todos los puntos de intersección entre ambos polígonos, teniendo en cuenta cuáles son entrantes y cuáles son salientes. Aplicamos las siguientes reglas al llegar a un punto de intersección en nuestro recorrido:

  • Entrante: Recorra el polígono a recortar.
  • Saliente: Recorra el polígono de recorte.

Ambos recorridos se hacen en el sentido de las agujas del reloj.

Por lo tanto, el algoritmo se divide en dos partes principales consecutivas: el cálculo de todas las intersecciones y posteriormente el recorrido de todos los puntos, según las reglas descritas anteriormente, para generar los polígonos resultantes.

Para formar cada polígono recortado, comenzamos con un punto de intersección entrante, agregando los demás puntos recorridos según las reglas establecidas hasta reencontrar ese mismo punto de intersección del comienzo, así cerrando el polígono.

Como ocurre en el recorte, es posible que las aristas de ambos polígonos no se crucen y por tanto no existan puntos de intersección. Si esto ocurriere, tenemos tres posibles casos a considerar según el polígono a recortar, P, y el polígono de recorte, R:

  • P es interior a R, por lo que el resultado es P.
  • R es interior a P o que es lo mismo, P engloba R, por lo que el resultado es R.
  • P y R son polígonos que no se intersectan entre sí, ni en las aristas ni en sus interiores y por tanto no existe ningún polígono recortado resultante.

Ejemplo

Descripción

[IMAGEN]: Figura 61 - Ejemplo del Algoritmo de Weiler-Atherton
Figura 61 - Ejemplo

Retomamos el mismo ejemplo del método de Sutherland-Hodgman y Liang-Barsky. Tenemos un polígono de recorte, R, cuya lista de vértices es:

R : \

Queremos uno varios polígonos, en tal polígono de recorte, a partir del polígono, P, descrito por la siguiente lista de vértices:

P : \

Solución

[IMAGEN]: Figura 62 - Calculamos todas las intersecciones
Figura 62 - Calculamos todas las intersecciones

Creamos una lista de polígonos, Q, que cada uno incluye una lista de vértices para representar los polígonos resultantes del recorte, inicialmente vacía:

Q1 : \{}

Antes de empezar el recorte de nuestro polígono, calculamos todos los puntos de intersección. Como tenemos que insertar estos puntos correctamente en las dos listas de vértices, P y R, para que ambas listas sigan el sentido de las agujas del reloj. Para realizar esta ordenación, calculamos y guardamos sus parámetros del segmento, t. Adicionalmente, necesitamos saber cuáles de estos puntos de intersección son entrantes (en rojo en la figura 62) y cuáles son salientes (en morado en la figura 62). Los puntos son:

i1 = (  0,  3 )  ⇐  t = 0,6666  (saliente)
i2 = ( -1,  2 )  ⇐  t = 0,3333  (entrante)
i3 = (  3,  0 )  ⇐  t = 0       (entrante)
i4 = (0,75, 3 )  ⇐  t = 0,75    (saliente)

Insertamos estos puntos de intersección correctamente en cada lista de vértices. i1 e i2 deben insertarse correctamente entre los vértices P.v1 y P.v2. Comparando los parámetros de i1 e i2, determinamos que el orden correcto es:

  v1       i2     i1     v2
(-2,1), (-1,2), (0,3), (1,4)
 t=0    t=0,33  t=0,66  t=1

El resultando de todas las inserciones correctas en las listas de vértices de ambos polígonos es:

P : \
R : \

Seguimos el recorrido, pero llegamos al punto i1 que es saliente. Según las reglas, debemos cambiar de lista de vértices para continuar el recorrido en R. Agregando este punto i1, por el momento, Q1 contiene la siguiente lista:

Q1 : \
[IMAGEN]: Figura 64 - Seguimos con el recorrido de R
Figura 64 - Recorremos R

Ahora, seguimos con el recorrido del polígono, R. Para ello, buscamos el punto de intersección saliente, i1, en la lista de R. Recorriendo R, nos encontramos con el vértice, R.r2, el cual agregamos a Q1. El siguiente punto es una intersección entrante, i3, por lo que debemos cambiar a la lista de vértices del polígono, P. Ahora la lista de vértices, Q1, es:

Q1 : \
[IMAGEN]: Figura 65 - Seguimos con el recorrido de P
Figura 65 - Recorremos P

Al saltar al polígono, P, buscamos el punto de intersección, i3. Siguiendo la lista de P, a partir de i3, nos encontramos con el punto de intersección saliente, i4, el cual agregamos a Q1. Como se trata de un punto de intersección saliente, debemos cambiar a la lista de vértices del polígono, R. Por ahora la lista de vértices, Q1, es:

Q1 : \
[IMAGEN]: Figura 66 - Seguimos con el recorrido de R
Figura 66 - Recorremos R

Con la lista de vértices, R, buscamos el punto de intersección, i4. Siguiendo la lista de R, a partir de i4, nos encontramos con el vértice original, R.r3, el cual agregamos a Q1. El siguiente punto en R es de intersección entrante, i1, que también se debería agregar a Q1, pero ya lo visitamos previamente; y por tanto, se agregó en una etapa anterior del algoritmo. Como se trata de un punto de intersección entrante, debemos cambiar a la lista de vértices del polígono, P. Terminamos la lista de vértices, Q1, que queda así:

Q1 : \
[IMAGEN]: Figura 67 - Polígono recortado, Q
Figura 67 - Solución Final

Al terminar un polígono recortado, volvemos a comenzar el algoritmo con la lista de vértices, P. Buscamos el siguiente punto de intersección entrante que no hayamos visitado, ni por lo tanto hayamos agregado al polígono de recorte, previamente. Como todos los puntos entrantes han sido visitados y usados anteriormente, finalizamos el recorte, dando lugar al polígono resultante, Q, que queda así:

Q : \

Algoritmo

El algoritmo hace uso de cierta información que agruparemos en la siguiente estructura especial, definida así:

PuntoInfo
\{
  real t;
  Punto vértice;
  booleano entrante;
}

Esta información se asociará a los puntos de intersección que calcularemos. Como el algoritmo debe distinguir entre un punto original de un polígono dado y un punto de intersección calculado, agregaremos una propiedad a Punto llamada original; si es verdadero, entonces se trata de un punto original, y falso indicará que es una intersección. También agregamos otra propiedad a Punto llamada visitado; si es verdadero, entonces hemos procesado este punto en el algoritmo de la intersección, y falso indicará que aún está por procesar.

Presentamos el algoritmo para Recortar() que acepta cualquier tipo de polígono convexo o cóncavo cerrado:

ListaPolígonos Recortar( Polígono P, Polígono R )
  1. Crear ListaPolígonos: Resultado ← vacía
  2. P.Agregar( P.v[1] ) // Agregamos el primer vértice al final
  3. R.Agregar( R.v[1] )
  4. ExistenIntersecciones ← Calcular_Intersecciones( P, R )
  5. Si ExistenIntersecciones = verdadero, entonces
  6. Resultado ← Intersectar( P, R, Resultado )
  7. Si no, entonces
  8. ContienePunto ← Acepta( P, R.v[1] )
  9. Si ContienePunto = verdadero, entonces
  10. Resultado ← R
  11. Si no, entonces
  12. ContienePunto ← Acepta( R, P.v[1] )
  13. Si ContienePunto = verdadero, entonces
  14. Resultado ← P
  15. Si no, entonces
  16. Resultado ← vacía
  17. Terminar( Resultado )

Exponemos el algoritmo de Calcular_Intersecciones() para calcular los puntos de intersección de las aristas entre los dos polígonos. Simultáneamente, agregamos estos puntos de intersección a los dos polígonos. Adicionalmente indicamos cuáles puntos son entrantes y cuáles son salientes.

booleano Calcular_Intersecciones( ref Polígono P, ref Polígono R )
  1. ExistenIntersecciones ← falso
  2. A ← P.v1
  3. B ← Siguiente_Original( P, A )
  4. Mientras que, B ≠ P.v1
  5. C ← R.v1
  6. D ← Siguiente_Original( R, C )
  7. Mientras que, D ≠ R.v1, repetir
  8. tp ← Calcular_Parámetro( A, B, C, D )
  9. Si 0 ≤ tp ≤ 1, entonces
  10. tr ← Calcular_Parámetro( C, D, A, B )
  11. Si 0 ≤ tr ≤ 1, entonces
  12. ExistenIntersecciones ← verdadero
  13. nuevo.vértice ← A + tp*(B-A)
  14. nuevo.vértice.original ← falso
  15. nuevo.t ← tp
  16. nuevo.entrante ← Es_Entrante( A, B, C, D )
  17. Insertar( P, A, B, nuevo )
  18. nuevo.t ← tr
  19. Insertar( Q, C, D, nuevo )
  20. C ← D
  21. D ← Siguiente_Original( R, C )
  22. A ← B
  23. B ← Siguiente_Original( P, A )
  24. Terminar( ExistenIntersecciones )

El algoritmo de Siguiente_Original() requiere un polígono y un punto que pertenece a tal polígono. Recorrerá todos los puntos en busca del siguiente que es un vértice - un punto original del polígono.

Punto Siguiente_Original( Polígono P, Punto A )
  1. Resultado ← P.Siguiente( A )
  2. Mientras que, Resultado.original = falso, repetir
  3. Resultado ← P.Siguiente( Resultado )
  4. Terminar( Resultado )

Hacemos uso del algoritmo básico Siguiente() que obtendrá el siguiente vértice en la lista que forma el polígono.

El algoritmo para Calcular_Parámetro() requiere los puntos extremos de cada segmento para calcular el valor del parámetro del primer segmento del punto de intersección de ambos segmentos.

real Calcular_Parámetro( Punto P0, Punto P1, Punto Q0, Punto Q1 )
  1. N ← ( Q0y - Q1y, Q1x - Q0x )
  2. numerador ← N⋅(P0 - Q0)
  3. denominador ← -N⋅(P1 - P0)
  4. Si denominador ≠ 0, entonces
  5. t ← numerador / denominador
  6. Si no, entonces
  7. t ← ∞
  8. Terminar( t )

He aquí el algoritmo de Es_Entrante(), que determina si el segmento, descrito por el vector U, es entrante (verdadero) al cruzar la arista descrita por el vector V. Esto implica que su punto de intersección, tambié es entrante.

booleano Es_Entrante( Vector U, Vector V )
  1. N ← ( -Vy, Vx )
  2. denominador ← N⋅U
  3. Si denominador < 0, entonces
  4. Terminar( verdadero )
  5. Si no, entonces
  6. Terminar( falso )

El siguiente algoritmo describe Insertar(), que acepta un polígono, dos puntos extremos de una arista, y el nuevo punto a insertar correctamente.

Insertar( ref Polígono P, Punto A, Punto B, PuntoInfo nuevo )
  1. Actual ← A
  2. Sig ← P.Siguiente( A )
  3. terminar ← falso
  4. Mientras que, terminar = falso y Sig.original = falso, repetir
  5. Si nuevo.t < Sig.t, entonces
  6. P.Insertar_Nuevo( Actual, nuevo )
  7. terminar ← verdadero
  8. Si no, entonces
  9. Actual ← Sig
  10. Sig ← P.Siguiente( Actual )
  11. Resultado ← P.Siguiente( Resultado )
  12. P.Insertar_Nuevo( Actual, nuevo )
  13. Terminar

El algoritmo básico del polígono, Insertar_Nuevo(), sirve para agregar un nuevo punto a ello, justo después del punto indicado.

Exponemos el algoritmo de Intersectar() para determinar los polígonos de intersección entre dos polígonos dados.

ListaPolígonos Intersectar( Polígono P, Polígono R )
  1. Crear ListaPolígonos: Actual ← \ // Actual[1] = P, Actual[2] = R
  2. Crear ListaPolígonos: Resultado ← vacía
  3. Crear Polígono: Auxiliar ← vacío
  4. índice ← Buscar_Punto_Entrante_No_Visitado( P )
  5. Mientras que, índice > 0, repetir
  6. n ← 1
  7. Mientras que, Actual[n].v[índice].visitado = falso, repetir
  8. Actual[n].v[índice].visitado = verdadero
  9. Auxiliar.Agregar( Actual[n].v[índice].vértice )
  10. Si Actual[n].v[índice].original = falso, entonces
  11. Si Actual[n].v[índice].entrante = falso, entonces // Cambio de polígono
  12. n_ant ← n
  13. Si n = 1, entonces
  14. n ← 2
  15. Si no, entonces
  16. n ← 1
  17. índice ← Buscar_Punto( Actual[n], Actual[n_ant].v[índice].vértice )
  18. índice ← índice + 1
  19. Si índice > Actual[n].Cantidad_Vértices(), entonces
  20. índice ← 1
  21. Resultado.AgregarPolígono( Auxiliar )
  22. Auxiliar.Vaciar()
  23. índice ← Buscar_Punto_Entrante_No_Visitado( P )
  24. Terminar( Resultado )

He aquí el algoritmo de Buscar_Punto_Entrante_No_Visitado() para encontrar el punto entrante sin marcar como visitado del polígono dado. El algoritmo retornará el índice al punto encontrado o 0 (cero), para indicar que no existe ninguno.

entero Buscar_Punto_Entrante_No_Visitado( Polígono P )
  1. Para: índice ← 1 hasta P.Cantidad_Vértices(), repetir
  2. Si P.v[índice].original = falso y Si P.v[índice].entrante = verdadero y Si P.v[índice].visitado = falso, entonces
  3. Terminar( índice )
  4. Terminar( 0 )

Presentamos el algoritmo de Buscar_Punto() para encontrar el punto indicado en el polígono dado. El algoritmo retornará el índice al punto encontrado o 0 (cero), para indicar que no se encontró.

entero Buscar_Punto( Polígono P, Punto I )
  1. Para: índice ← 1 hasta P.Cantidad_Vértices(), repetir
  2. Si P.v[índice].vértice = I, entonces
  3. Terminar( índice )
  4. Terminar( 0 )