# How to interpolate angular data properly

I have been trying to interpolate raw angular data in degree (for a flow map grid) but any attempt fails because there is always two path, and I can't figure out how to interpolate on the shorter one only!

I have made a code sandbox to try different strategy in javascript:

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext("2d");
ctx.translate(0, canvas.height);
ctx.scale(1, -1); //flip canvas

var tau = Math.PI * 2;
var deg = tau / 360;

var min = 20; //start angle
var max = 270; //end angle
var step = 10; //space between angle

//interpolated angles visual
color('green');
for (let index = min; index % 360 < max; index += step) {
let t = remap01(min, max, index);

// l = lerpwrap(min*deg, max*deg, t);
// l = lerphannah(min*deg, max*deg, t);
let l = lerpAngle(min * deg, max * deg, t);

createDirection(l);
}

//start angle visual
color('red');
createDirection(min * deg);

//end angle visual
color('blue');
createDirection(max * deg);

function lerpwrap(start, end, amount) {
let short = (((((end - start) % 360) + 540) % 360) - 180);
return start + (short * amount) % 360;
}

function lerphannah(b, a, t) {
if (Math.abs(a - 360 - b) < (Math.abs(a) - b)) {
a = a - 360;
};
return b = b + (a - b) * t;
}

function lerp(value1, value2, t) {
return value1 + (value2 - value1) * t;
}

// Remaps angles into [0, 360) range on degrees.
function normalizeAngle(angle) {
return angle - Math.floor(angle / 360) * 360;
}
// Returns the shortest signed angular delta
// from angle from to angle to, in degrees.
function angleDifference(from, to) {
// Wrap difference into [0, 360) range.
let difference = normalizeAngle(to - from);
// Remap to range (-180, 180]
// so that angles more than a half turn away
// go via the shorter route.
if (difference > 180) {
difference -= 360
};
return difference;
}

// Linearly interpolates between two angles,
// using interpolation weight blend in [0, 1].
// Return is normalized into [0, 360) range.
function lerpAngle(from, to, blend) {
let difference = angleDifference(from, to);
return normalizeAngle(from + blend * difference);
}
// --------------utils
function createDirection(angle) {
let angularline = createPoint(0, 1);
angularline = rotateAngle(angularline.x, angularline.y, angle);
angularline = scalePoint(angularline, 45);

let midscreen = createPoint(canvas.width / 2, canvas.height / 2);
let target = createPoint(midscreen.x + angularline.x, midscreen.y + angularline.y);
line(midscreen.x, midscreen.y, target.x, target.y);
}

function remap01(t1, t2, x) {
return (x - t1) / (t2 - t1);
}

function color(colour) {
ctx.fillStyle = colour;
ctx.strokeStyle = colour;
}

function createPoint(xp, yp) {
return {
x: xp,
y: yp
};
}

function rotateAngle(x, y, angle) {
let x1 = x * Math.cos(angle) - y * Math.sin(angle);
let y1 = x * Math.sin(angle) + y * Math.cos(angle);
let result = {
x: x1,
y: y1
};
return result;
}

function scalePoint(point, scale) {
return {
x: point.x * scale,
y: point.y * scale
};
}

function line(startX, startY, endX, endY) {
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(endX, endY);
ctx.closePath();
ctx.stroke();
}
* {
box-sizing: border-box;
}

body {
background-color: #333;
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: Arial, Helvetica, sans-serif;
min-height: 100vh;
margin: 0;
}

#canvas {
background: #f0f0f0;
}

#source {
display: none;
}
<canvas id="canvas" width="400" height="100"></canvas>

resulting image:

0° is up

I have been trying many implementations but none work so far

How do I interpolate angular data such as it goes by the shorter distance in the circle?

• Remember to search for past Q&A — you're certainly not the first game developer in history to ask about interpolating an angle. 😉 You can find out existing answers here and in similar past questions. Apr 4, 2022 at 23:49
• I obviously didn't use the right keyword, angular interpolation is web stuff, modulo is anything but that, and whatever I found (in stack answer no less) was in the code snippet and doing nothing. Googling isn't what it used to be :( thanks, i'll try that, that was surprisingly the hardest part of a pcg stateless agent circulation system, now I can have path that aren't 90° only! Apr 5, 2022 at 0:21
• @DMGregory Plot twist, it's not working, I have the exact same result as above, instead of the shortest path, it takes the long path, I spend time verifying the code those last 2 days, so I don't see where the error is! Apr 7, 2022 at 21:26
• Edit your question to show the code you're using. Aim to give us a Minimal Complete Verifiable Example: everything a user would need to reproduce the problem in a new project. Apr 7, 2022 at 21:56
• I have edited as much as possible, though I can't figure out why the edit don't work directly within the question, but the code is teh one I use, and I put all the helper function for drawing Apr 8, 2022 at 0:51

Think of two angles as two unit vectors(a,b), Calculate their Cross product. For example:

let min = 0;
let max = 45;
let a = (1,0);     // 0 degree Calculated by trigonometry
let b = (0.7,0.7); // 45 degree Calculated by trigonometry
let cross = a[0]*b[1] – b[0]*a[1]; //two-dimensional vector cross product formula
// when cross>0 ,a in the counterclockwise direction of b
if (cross > 0){
//make sure degree b is greater
min,max = max,min;
}
if (min> max){
max += 360;
}
let step = 10;
for (let index = min; index % 360 < max; index += step) {
let t = remap01(min, max, index);
let l = lerpAngle(min * deg, max * deg, t);
createDirection(l);
}


The principle: The magnitude of the cross product can be interpreted as the positive area of the parallelogram having a and b as sides :

If the result (area) is positive, This angle < 180° ,otherwise it's > 180°.

• That's clever! I was trying to avoid vector. Apr 9, 2022 at 1:50