-1

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(mindeg, maxdeg, t); // l = lerphannah(mindeg, maxdeg, 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);

//---------------Various Lerp from google and asking 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;
  /* border-radius: 5px; */
}

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

resulting image:

enter image description here

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?

user29244
  • 327
  • 2
  • 11
  • 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. – DMGregory Apr 04 '22 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! – user29244 Apr 05 '22 at 00: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! – user29244 Apr 07 '22 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. – DMGregory Apr 07 '22 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 – user29244 Apr 08 '22 at 00:51
  • It looks to me like you've gotten snared up in radian-degree conversions. You generate your angles in degrees, but then convert them to radians when passing them to your lerp function, but then work with them as though they're in degrees inside the normalize function, but then use them as though they're in radians inside the create direction function. I'd recommend picking one standard and sticking to it. And deleting and streamlining much of this code - this is not a problem that needs 125 lines to solve. – DMGregory Apr 08 '22 at 20:48
  • Thanks! It's basically an artefact of going around with many lerp implementation, I lost track of the finer details it seems.

    There is also some bug in the for loop, it interpolate correctly ccw (ie counting 10 by 10 angles), but when it shift to cw it has too many sample. Also max = 360 crash the bowser.

    Now I fixed the code part that prevented the javascript from executing (Math.pi -> Math.PI).

    I'm not sure how to compress the drawing part which is why it's a bit too long

    – user29244 Apr 08 '22 at 21:51
  • Also max = 360 crash the bowser -> (index%360 = 0, therefore teh condition "< 360" can never be reach). – user29244 Apr 08 '22 at 21:59

2 Answers2

0

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 :

enter image description here

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

Mangata
  • 2,516
  • 1
  • 2
  • 9
0

I had to shift this

l = lerpAngle(mindeg, maxdeg, t)

createDirection(l)

into

l = lerpAngle(min, max, t)

createDirection(l*deg)

user29244
  • 327
  • 2
  • 11