const angleSteps = 16;
const angleMoveSteps = 90;
var currentDir = 0;
var shipDir = 0;
var targetAngle = 0;
function getNextAngle(newDirection, currentDirection) {
const step = 360 / angleMoveSteps
newDirection %= 360;
currentDirection %= 360;
newDirection = newDirection < currentDirection ? newDirection + 360 : newDirection;
const dif = newDirection - currentDirection;
if (dif > 360 / 2 && dif < 360 - step) { return currentDirection - step }
if (dif > step && dif < 360 / 2) { return currentDirection + step }
return currentDirection
}
function getStepAngle(dir) {
return Math.floor(((dir % 360 + 360) % 360) / (360 / angleSteps) + 0.5) % angleSteps;
}
/* Demo code from here down */
Math.TAU = Math.PI * 2;
const ctx = canvas.getContext("2d")
const w = canvas.width;
const h = canvas.height;
var seeking = false;
const speed = 100; // milliseconds
update();
canvas.addEventListener("click", event => {
const bounds = canvas.getBoundingClientRect();
const x = event.pageX - bounds.left - scrollX;
const y = event.pageY - bounds.top - scrollY;
targetAngle = Math.atan2(y - w / 2, x - h / 2) * 180 / Math.PI;
if(!seeking){ render() }
});
function render() {
requestAnimationFrame(update);
var newDir = getNextAngle(targetAngle, currentDir);
if(newDir !== currentDir) {
currentDir = newDir;
seeking = true;
setTimeout(render, speed);
} else {
currentDir = targetAngle;
setTimeout(()=>requestAnimationFrame(update), speed);
seeking = false;
}
}
function update() {
shipDir = getStepAngle(currentDir);
clear();
drawCompase();
drawTargetAngle(targetAngle);
drawCurrentAngle(currentDir);
drawStepAngle(shipDir);
}
function clear() { ctx.clearRect(0,0,w,h) }
function angleText(text,x,y,angle,size = 12, col = "#000") {
const xAX = Math.cos(angle);
const xAY = Math.sin(angle);
ctx.fillStyle = col;
ctx.font = size + "px arial";
ctx.textAlign = "right";
ctx.textBaseline = "middle";
if(xAX < 0) {
ctx.setTransform(-xAX, -xAY, xAY, -xAX, x, y);
ctx.textAlign = "left";
} else {
ctx.setTransform(xAX, xAY, -xAY, xAX, x, y);
ctx.textAlign = "right";
}
ctx.fillText(text,0,0);
}
function drawCompase() {
var i;
const rad = h * 0.4, rad1 = h * 0.395, rad2 = h * 0.41;
ctx.lineWidth = 1;
ctx.strokeStyle = "#000";
ctx.beginPath();
ctx.arc(w / 2, h / 2, rad, 0, Math.TAU);
ctx.stroke();
ctx.lineWidth = 2;
ctx.beginPath();
for (i = 0; i < 1; i += 1 / angleSteps) {
const ang = i * Math.TAU;
ctx.moveTo(Math.cos(ang) * rad1 + w / 2, Math.sin(ang) * rad1 + h / 2);
ctx.lineTo(Math.cos(ang) * rad2 + w / 2, Math.sin(ang) * rad2 + h / 2);
}
ctx.stroke();
for (i = 0; i < 1; i += 1 / angleSteps) {
const ang = i * Math.TAU;
angleText(
(ang * 180 / Math.PI).toFixed(1).replace(".0",""),
Math.cos(ang) * (rad1 - 2) + w / 2,
Math.sin(ang) * (rad1 - 2) + h / 2,
ang
);
}
ctx.setTransform(1,0,0,1,0,0);
}
function drawTargetAngle(angle) { // angle in deg
const rad = h * 0.30, rad1 = h * 0.1, rad2 = h * 0.34;
const ang = angle * Math.PI / 180;
const fromA = ang - Math.PI / (angleSteps * 4);
const toA = ang + Math.PI / (angleSteps * 4);
ctx.linewidth = 2;
ctx.strokeStyle = "#F00";
ctx.beginPath();
ctx.moveTo(Math.cos(fromA) * rad + w / 2, Math.sin(fromA) * rad + h / 2);
ctx.lineTo(Math.cos(ang) * rad2 + w / 2, Math.sin(ang) * rad2 + h / 2);
ctx.lineTo(Math.cos(toA) * rad + w / 2, Math.sin(toA) * rad + h / 2);
ctx.stroke();
angleText(
angle.toFixed(1).replace(".0",""),
Math.cos(ang) * (rad - 4) + w / 2,
Math.sin(ang) * (rad - 4) + h / 2,
ang,
12, "#F00"
);
ctx.setTransform(1,0,0,1,0,0);
}
function drawCurrentAngle(angle) { // angle in deg
const rad = h * 0.14, rad2 = h * 0.17;
const ang = angle * Math.PI / 180;
const fromA = ang - Math.PI / (angleSteps * 2);
const toA = ang + Math.PI / (angleSteps * 2);
ctx.linewidth = 2;
ctx.strokeStyle = "#0A0";
ctx.beginPath();
ctx.moveTo(Math.cos(fromA) * rad + w / 2, Math.sin(fromA) * rad + h / 2);
ctx.lineTo(Math.cos(ang) * rad2 + w / 2, Math.sin(ang) * rad2 + h / 2);
ctx.lineTo(Math.cos(toA) * rad + w / 2, Math.sin(toA) * rad + h / 2);
ctx.stroke();
angleText(
angle.toFixed(1).replace(".0",""),
Math.cos(ang) * (rad - 4) + w / 2,
Math.sin(ang) * (rad - 4) + h / 2,
ang,
12, "#0A0"
);
ctx.setTransform(1,0,0,1,0,0);
}
function drawStepAngle(angle) { // ang 0 to angleSteps cyclic
var ang = angle % angleSteps;
ang *= Math.PI / angleSteps*2;
const fromA = ang - Math.PI / angleSteps;
const toA = ang + Math.PI / angleSteps;
const rad = h * 0.4, rad1 = h * 0.35, rad2 = h * 0.44;
const rad3 = h * 0.34, rad4 = h * 0.45;
ctx.linewidth = 1;
ctx.strokeStyle = "#08F";
ctx.beginPath();
ctx.arc(w / 2, h / 2, rad1, fromA, toA);
ctx.moveTo(w / 2 + Math.cos(fromA) * rad2, h / 2 + Math.sin(fromA) * rad2, 0, Math.TAU);
ctx.arc(w / 2, h / 2, rad2, fromA, toA);
ctx.stroke();
ctx.linewidth = 2;
ctx.beginPath();
ctx.moveTo(Math.cos(fromA) * rad3 + w / 2, Math.sin(fromA) * rad3 + h / 2);
ctx.lineTo(Math.cos(fromA) * rad4 + w / 2, Math.sin(fromA) * rad4 + h / 2);
ctx.moveTo(Math.cos(toA) * rad3 + w / 2, Math.sin(toA) * rad3 + h / 2);
ctx.lineTo(Math.cos(toA) * rad4 + w / 2, Math.sin(toA) * rad4 + h / 2);
ctx.stroke();
angleText(
angle,
Math.cos(ang + 0.1) * (rad - 2) + w / 2,
Math.sin(ang + 0.1) * (rad - 2) + h / 2,
ang,
16, "#08F"
);
ctx.setTransform(1,0,0,1,0,0);
}
body { font-family: arial }
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<span> Click to set new target direction</span>
<canvas id="canvas" width="400" height="400"></canvas>