Interpolación Lineal
Árticulo original escrito por: Matt DesLauriers
La Interpolación Lineal, a veces también llamada lerp o mezcla (mix
), es una función muy útil dentro de la programación creativa, desarrollo de videojuegos, visualización de datos y arte generativo. Esta función intercala dentro de un rango dado (inicio, fin)
basandose en un parámetro t
, donde t
normalmente es un valor que está dentro del intervalo que va de 0
a 1
.
// Interpolacion Lineal. A veces también conocida como "lerp" o "mix"
function lerp (inicio, fin, t) {
return inicio * (1 - t) + fin * t;
}
// Ejemplos:
lerp(0, 100, 0.5); // 50
lerp(20, 80, 0); // 20
lerp(30, 5, 1); // 5
lerp(-1, 1, 0.5); // 0
lerp(0.5, 1, 0.5); // 0.75
Ejemplos
Por ejemplo, si dividimos el tiempo en el bucle por la duración de este, obtendremos un valor t
de entre 0.0
y 1.0
y entonces, podremos asignar este valor t
a un nuevo intervalo como hacemos en lerp(150, 300, t);
en caso de querer aumentar gradualmente el radio, o como ocurre con lerp (100, 10, t);
para disminuir progresivamente el grosor del borde de la circunferencia.
Reasignando un valor de un intervalo a otro
// Círculo
const circle_stroke = lerp(100, 10, t);
const circle_radius = lerp(width/8, width/4, t)
ctx.lineWidth = circle_stroke;
ctx.beginPath();
ctx.ellipse(width/2, (height/2) - statusbar_height, circle_radius, circle_radius, Math.PI / 4, 0, 2 * Math.PI);
ctx.stroke();
Otro ejemplo, podemos usar la interpolación lineal para animar sutilmente desde unas coordenadas a otras. Si definimos un punto de inicio (x1, y1)
y un punto final (x2, y2)
, al interpolar los ejes x
e y
por separado encontraremos el punto intermedio entre estas.
Animación entre dos puntos
const point_radius = width/16;
const points = [
{ x : (width/2) - (point_radius*3), y : (height/2) - (point_radius*3) },
{ x : (width/2) + (point_radius*3), y : (height/2) + (point_radius*3) },
];
// Puntos
ctx.beginPath();
ctx.ellipse(points[0].x, points[0].y, point_radius, point_radius, Math.PI/4, 0, 2*Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.ellipse(points[1].x, points[1].y, point_radius, point_radius, Math.PI/4, 0, 2*Math.PI);
ctx.stroke();
// Círculo
const dot_x = lerp(points[0].x, points[1].x, t);
const dot_y = lerp(points[0].y, points[1].y, t);
ctx.beginPath();
ctx.arc(dot_x, dot_y, point_radius - 10, 0, 2*Math.PI);
ctx.fill();
O también se puede usar la interpolación lineal para saltar hacia un objetivo que se encuentre en movimiento. En cada frame de la animación se puede interpolar desde las coordenadas actuales hasta el valor de las del objetivo estableciendo un valor pequeño en el parámetro t
, uno tal como 0.05
por ejemplo. Esto seria como decir: — Camina un 5% hacia el objetivo en cada fotograma (frame). —
Saltando hacia un objetivo en movimiento
const _point_radius = width/16;
const _spacing = (width/8);
const _x = padding + _spacing;
const _y = padding + _spacing;
const _w = width - ((padding*2) + (_spacing*2));
const _h = height - ((padding*2) + (_spacing*2) + statusbar_height);
const _points = [
{ 'x' : _x + (_w/2), 'y' : _y + (_h/2) },
{ 'x' : _x, 'y' : _y + (_h/2) },
{ 'x' : _x, 'y' : _y },
{ 'x' : _x + _w, 'y' : _y + _h },
]
// Establecer las coordinadas del círculo por defecto si no han sido definidas aún.
_chasing_dot = (typeof _chasing_dot === 'undefined') ? {'x' : _points[3].x, 'y': _points[3].y} : _chasing_dot;
// Establecer valor del nuevo t y del destino
let _current_point = Math.round(t);
_current_point += (even) ? 0 : 2;
let _t = ((t>.5) ? t-.5 : t) * 2;
_t = parseFloat(_t.toFixed(2));
// Círculo Objetivo
ctx.beginPath();
ctx.ellipse(_points[_current_point].x, _points[_current_point].y, _point_radius, _point_radius, Math.PI/4, 0, 2*Math.PI);
ctx.stroke();
// Punto perseguidor
const _dot_x = lerp(_chasing_dot.x, _points[_current_point].x, 0.05 );
const _dot_y = lerp(_chasing_dot.y, _points[_current_point].y, 0.05 );
ctx.beginPath();
ctx.arc(_dot_x, _dot_y, _point_radius-20, 0, 2*Math.PI);
ctx.fill();
_chasing_dot.x = _dot_x;
_chasing_dot.y = _dot_y;
Un ejemplo más avanzado, pero basado en el mismo concepto sería la interpolación desde un color (azúl) hasta otro (grís oscuro). Para realizar esto, tendríamos que interpolar los canales rgb
o hsl
de cada color por separado tal y como lo hariamos si se tratara de coordenadas de 2D o 3D.
Animación entre dos colores
const RGBs = [
{'r': 96, 'g': 172, 'b': 212},
{'r': 35, 'g': 35, 'b': 35},
];
const r = lerp(RGBs[0].r, RGBs[1].r, t);
const g = lerp(RGBs[0].g, RGBs[1].g, t);
const b = lerp(RGBs[0].b, RGBs[1].b, t);
ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
ctx.beginPath();
ctx.arc(width/2, (height/2) - statusbar_height, width/4, 0, 2*Math.PI);
ctx.fill();
// Volver al color de relleno previo.
ctx.fillStyle = '#999';
Hay muchas formas de usar la interpolación lineal y muchos más tipos de interpolación (cúbica, bilineal, etc.). Estos conceptos nos serán tambien utiles en otras áreas como: curvas, splines y ecuaciones paramétricas.
Puedes ver el código que se ha utilizado para realizar los ejemplos aquí.
Esta es una traducción libre basada en el artículo Linear Interpolation escrito originalmente por Matt DesLauriers.