Hace tiempo vimos como colocar objetos en un videojuego siguiendo la forma de una circunferencia. En este artículo vamos a continuar con la serie, explicando cómo colocar objetos para dibujar segmentos (líneas), polígonos y estrellas.
Colocar objetos en un segmento
Un segmento es una línea recta acotada por un punto de inicio (A) y un punto de fin (B). Para colocar objetos en un segmento lo dividiremos en un cierto número de partes iguales.
Como podemos ver en el ejemplo anterior, dividiendo el segmento en cuatro partes obtenemos cinco puntos equidistantes donde colocar nuestros objetos.
Nuestros puntos vienen dados por una posición $x$ (horizontal) y una posición $y$ (vertical). Restando la posición $x$ de B con la posición $x$ de A obtenemos el ancho del segmento, que podría ser negativo si B estuviera a la izquierda de A. De forma similar, restando las posiciones $y$ obtenemos el alto del segmento.
Dividiendo el ancho y el alto entre un número de partes (en el ejemplo, cuatro) obtendremos el ancho y el alto de cada parte.
Nuestros puntos equidistantes se obtendrán sumando a A el tamaño de una parte entre cero y cuatro veces (el número de partes). A continuación se muestra un ejemplo de cómo se podría implementar este algoritmo.
function drawSegment(ax, ay, bx, by, numPoints) {
// Calculamos el alto y ancho del segmento
var w = bx - ax;
var h = by - ay;
// Calculamos el alto y ancho de las partes
var numParts = numPoints - 1;
var partW = w / numParts;
var partH = h / numParts;
// Para i entre 0 y `numPoints` (no incluido) ...
for (var i = 0; i < numPoints; i++) {
// Dibujamos un punto que será A sumado i
// veces el ancho y alto de la parte.
drawPoint({
x: ax + partW * i,
y: ay + partH * i
});
}
}
Polígonos regulares
Un polígono es un conjunto de segmentos unidos entre sí. En general, todo segmento tiene un punto de inicio y un punto de fin. En el caso de un polígono, el punto de inicio de un segmento es el punto de fin del anterior.
Aquí vamos a centrarnos en los polígonos regulares, es decir, aquellos que todos los segmentos que los forman tienen la misma longitud, puesto que son los que utilizaremos después como base para hacer estrellas.
Un polígono regular se construye a partir de una circunferencia, dividiéndola en tantos arcos como lados queramos para nuestro polígono. Todos los arcos deben tener el mismo ángulo. La siguiente imagen muestra como ejemplo la construcción de un pentágono.
El proceso para obtener estos puntos es exactamente el mismo que veíamos para representar un círculo en el artículo anterior. Dividimos el ángulo de la circunferencia completa ($2\pi$ radianes o 360º) entre el número de lados y utilizamos seno y coseno para calcular los puntos que definen el polígono.
function calcPolygonVertices(numVertices, centerX,
centerY, radius, rotation)
{
// Dividimos el ángulo de la circunferencia completa
// (2pi=360°) entre el número de vértices del polígono.
// De esta manera obtendremos el ángulo de los lados.
var angInterval = 2 * Math.PI / numVertices;
// Inicializamos una lista de vértices
var vertices = [];
for (var i = 0; i < numVertices; i++) {
// Multiplicamos el número de vértice por el ángulo
// de los lados
var x = centerX +
Math.cos(i * angInterval + rotation) * radius;
var y = centerY +
Math.sin(i * angInterval + rotation) * radius;
vertices.push({x: x, y: y});
}
return vertices;
}
Sin embargo, en este caso no nos conformaremos con pintar los puntos, sino que los utilizaremos de entrada para la función anterior de manera que podamos dibujar segmentos.
function drawPolygon(numVertices, centerX, centerY, radius,
rotation, numPointsSegment)
{
var vertices = calcPolygonVertices(numVertices, centerX,
centerY, radius, rotation);
// Unimos cada vértice con el siguiente
for (var numVertex = 0; numVertex < numVertices;
numVertex++)
{
var vertexA = vertices[numVertex];
var vertexB = vertices[numVertex + 1];
drawPolygonSegment(vertexA.ax, vertexA.ay,
vertexB.ax, vertexB.ay,
numPointsSegment);
}
}
La función drawPolygonSegment
es igual que la función drawSegment
explicada antes, con una sutil diferencia: el último punto del segmento no es dibujado, ya que coincidirá con el primer punto del siguiente lado del polígono.
function drawPolygonSegment(ax, ay, bx, by, numPoints) {
// Calculamos el alto y ancho del segmento
var w = bx - ax;
var h = by - ay;
// Calculamos el alto y ancho de las partes
var numParts = numPoints - 1;
var partW = w / numParts;
var partH = h / numParts;
// Para i entre 0 y `numPoints - 1` (no incluido) ...
// El último punto no se dibuja porque coincide con
// el primer punto del siguiente segmento
for (var i = 0; i < numPoints - 1; i++) {
// Dibujamos un punto que será A sumado i
// veces el ancho y alto de la parte.
drawPoint({
x: ax + partW * i,
y: ay + partH * i
});
}
}
Hacer los polígonos animables
Añadiendo un desplazamiento a la posición de los puntos utilizados para dibujar los segmentos y modificando su valor a lo largo del tiempo es posible mostrar polígonos animados, donde los objetos no estén colocados en posiciones estáticas sino que se muevan siguiendo la forma del polígono.
El desplazamiento se especificará como una variable numérica entre 0 y 1. Un valor de 0 será sinónimo de no desplazamiento, mientras con valor de 1 significará que el primer punto estaría en la posición en la que habría estado el segundo si no se hubiera aplicado desplazamiento.
El siguiente bloque de código muestra la función de dibujado de segmentos de polígonos adaptada para soportar desplazamiento. Es similar a la anterior, pero añadimos un nuevo parámetro offset
y y se lo sumamos al número de punto.
function drawPolygonSegment(ax, ay, bx, by, numPoints,
offset)
{
// Calculamos el alto y ancho del segmento
var w = bx - ax;
var h = by - ay;
// Calculamos el alto y ancho de las partes
var numParts = numPoints - 1;
var partW = w / numParts;
var partH = h / numParts;
// Para i entre 0 y `numPoints - 1` (no incluido) ...
// El último punto no se dibuja porque coincide con
// el primer punto del siguiente segmento
for (var i = 0; i < numPoints - 1; i++) {
// Dibujamos un punto que será A sumado i
// veces el ancho y alto de la parte.
drawPoint({
x: ax + partW * (i + offset),
y: ay + partH * (i + offset)
});
}
}
Sólo quedaría añadir este parámetro también a la función de dibujar polígono y modificar su valor en cada frame.
Estrellas
A partir de un polígono regular, es fácil construir una estrella. Para ello se sigue el mismo procedimiento que en el caso del polígono, con una diferencia: Cada vértice del polígono será unido con el que esté a $n \geq 2$ posiciones después de él. A este $n$ lo denominaremos el paso de la estrella.
En la siguiente figura se muestra la construcción de una estrella de cinco puntas con paso 2:
El siguiente algoritmo nos permitiría construir una estrella de este tipo:
function drawStar(numVertices, centerX, centerY, radius,
rotation, numPointsSegment, starStep,
segmentOffset)
{
var vertices = calcPolygonVertices(numVertices, centerX,
centerY, radius, rotation);
// Una estrella tiene el doble de segmentos que vértices
var numSegments = vertices.length * 2;
var indexVertexA = 0; // Empezamos trazando desde el
// vértice 0
for (var i = 0; i < numSegments; i++) {
// El vértice destino del segmento es aquel que esté a
// `starStep` posiciones del vértice de origen
var indexVertexB = (indexVertexA + starStep)
% vertices.length;
// Dibujamos el segmento
var vertexA = vertices[indexVertexA];
var vertexB = vertices[indexVertexB];
drawPolygonSegment(vertexA.ax, vertexA.ay,
vertexB.ax, vertexB.ay,
numPointsSegment, segmentOffset);
// El vértice destino de este segmento será el vértice
// inicio del siguiente
indexVertexA = indexVertexB;
}
}
Estrellas no conectadas
Con el algoritmo anterior no es posible dibujar algunos tipos de estrellas. Es el caso de la estrella de seis puntas o hexagrama, mostrada en la figura siguiente.
Al dibujar la estrella de seis puntas con un paso de dos, nos encontramos con que de después de tres pasos volvemos a al vértice inicial. Los segmentos de la estrella de seis puntas, no están todos conectados.
Cuando se dé este caso, para terminar la estrella deberemos repetir el proceso desde el vértice siguiente a aquel en el que empezamos.
El siguiente fragmento de código muestra cómo dibujar una estrella de forma que se tenga en cuenta el caso de que haya grupos de segmentos desconectados.
function drawStar(numVertices, centerX, centerY, radius,
rotation, numPointsSegment, starStep,
segmentOffset)
{
var vertices = calcPolygonVertices(numVertices,
centerX, centerY, radius, rotation);
// Una estrella tiene el doble de segmentos que
// vértices
var numSegments = vertices.length * 2;
// Empezamos trazando desde el vértice 0
var indexStartVertex = 0;
var indexVertexA = indexStartVertex;
for (var i = 0; i < numSegments; i++) {
// El vértice destino del segmento es aquel que esté
// a `starStep` posiciones del vértice de origen
var indexVertexB = (indexVertexA + starStep)
% vertices.length;
// Dibujamos el segmento
var vertexA = vertices[indexVertexA];
var vertexB = vertices[indexVertexB];
drawPolygonSegment(vertexA.ax, vertexA.ay,
vertexB.ax, vertexB.ay,
numPointsSegment, segmentOffset);
// El vértice destino de este segmento será el
// vértice inicio del siguiente
indexVertexA = indexVertexB;
// Si volvemos al punto de inicio y todavía quedan
// más pasos, estamos ante una estrella desconectada.
// Deberemos repetir el proceso empezando a dibujar
// por el punto siguiente.
if (indexVertexA == indexStartVertex) {
indexStartVertex = (indexStartVertex + 1)
% vertices.length;
indexVertexA = indexStartVertex;
}
}
}
Demostración
La siguiente demo muestra la construcción de una estrella haciendo uso de los algoritmos explicados.
Animación
Podemos modificar en cada frame los valores algunos parámetros de la función drawStar
para crear animaciones:
rotation
define la rotación global de la estrella. Con un valor de cero el primero vértice de la estrella está situado en la parte derecha de la circunferencia (0°). Modificando este valor periódicamente podemos conseguir que la estrella gire.segmentOffset
define el desplazamiento de los objetos utilizados para dibujar los segmentos de la estrella. Modificando periódicamente este valor entre cero y uno conseguiremos que los objetos que utilicemos para usemos para dibujar la estrella no estén situados estáticamente sino que sigan la forma de la estrella.radius
define el radio de la circunferencia sobre la que se inscribe la estrella. Animando el valor de esta variable podemos hacer que crezca o decrezca.centerX
ycenterY
definen la posición de la estrella. Animando estos valores podemos hacer que se mueva por la pantalla.
La siguiente demostración permite animar alguno de de estos valores: