简体   繁体   中英

Calculate rotation of needle

I got a movement vector that looks like one of them:

let movement = { x: 0, y: 0 } // no movement
let movement = { x: 1, y: 0 } // move right
let movement = { x: -1, y: 0 } // move left
let movement = { x: 0, y: 1 } // move down
let movement = { x: 0, y: -1 } // move up
let movement = { x: 1, y: -1 } // move right & up
let movement = { x: 1, y: 1 } // move right & down
let movement = { x: -1, y: -1 } // move left & up
let movement = { x: -1, y: 1 } // move left & down

See this snippet:

 let movement = {x: 0, y: 0} $(window).on("keydown", function(e) { if (e.key === "ArrowDown") movement.y = 1 else if (e.key === "ArrowUp") movement.y = -1 else if (e.key === "ArrowRight") movement.x = 1 else if (e.key === "ArrowLeft") movement.x = -1 }) $(window).on("keyup", function(e) { if (e.key === "ArrowDown") movement.y = 0 else if (e.key === "ArrowUp") movement.y = 0 else if (e.key === "ArrowRight") movement.x = 0 else if (e.key === "ArrowLeft") movement.x = 0 }) setInterval(function() { console.log(movement) }, 100)
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> Focus in console window & press arrow keys to see the result!


No I'd like to create a arrow/needle that points into the direction of the current movement vector using javascript + canvas.

图片

And this code is what I've got so far:

 let movement = { x: 0, y: 0 } $(window).on("keydown", function(e) { if (e.key === "ArrowDown") movement.y = 1 else if (e.key === "ArrowUp") movement.y = -1 else if (e.key === "ArrowRight") movement.x = 1 else if (e.key === "ArrowLeft") movement.x = -1 update() }).on("keyup", function(e) { if (e.key === "ArrowDown") movement.y = 0 else if (e.key === "ArrowUp") movement.y = 0 else if (e.key === "ArrowRight") movement.x = 0 else if (e.key === "ArrowLeft") movement.x = 0 update() }) let canvas = $("#canvas"); let ctx = canvas[0].getContext("2d") let update = function() { let size = 40; let pixelPosition = { x: canvas.width() / 2 - size/2, y: canvas.height() / 2 - size/2 } ctx.clearRect(0, 0, canvas.width(), canvas.height()) let rotation = Math.atan(movement.y/movement.x) ctx.save(); ctx.translate(pixelPosition.x + size / 2, pixelPosition.y + size / 2); ctx.rotate(rotation); ctx.translate(-pixelPosition.x - size / 2, -pixelPosition.y - size / 2); let img = new Image() img.src = "https://png.icons8.com/windows/1600/long-arrow-right.png" ctx.drawImage(img, pixelPosition.x, pixelPosition.y, size, size); ctx.restore(); }
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <canvas id="canvas" width="200" height="200"></canvas>

But I'm clueless how to calculate the rotation of the needle using the movement vector. Any help would be really appreciated.

You should use Math.atan2(y,x) to account for quadrants.

Note that the arguments are y,x rather than y/x .

EDIT

If you want (0,0) to point upwards, then you can use an if statement when deciding your rotation to check that and set the rotation manually to -Math.PI/2 .

Although an if statement is probably more readable, below is a ternary that works as well.

let rotation = (movement.y == 0 && movement.x == 0) ? -Math.PI/2 : Math.atan2(movement.y, movement.x)

 let movement = { x: 0, y: 0 } $(window).on("keydown", function(e) { if (e.key === "ArrowDown") movement.y = 1 else if (e.key === "ArrowUp") movement.y = -1 else if (e.key === "ArrowRight") movement.x = 1 else if (e.key === "ArrowLeft") movement.x = -1 update() }).on("keyup", function(e) { if (e.key === "ArrowDown") movement.y = 0 else if (e.key === "ArrowUp") movement.y = 0 else if (e.key === "ArrowRight") movement.x = 0 else if (e.key === "ArrowLeft") movement.x = 0 update() }) let canvas = $("#canvas"); let ctx = canvas[0].getContext("2d") let update = function() { let size = 40; let pixelPosition = { x: canvas.width() / 2 - size/2, y: canvas.height() / 2 - size/2 } ctx.clearRect(0, 0, canvas.width(), canvas.height()) let rotation = (movement.y == 0 && movement.x == 0) ? -Math.PI/2 : Math.atan2(movement.y, movement.x) ctx.save(); ctx.translate(pixelPosition.x + size / 2, pixelPosition.y + size / 2); ctx.rotate(rotation); ctx.translate(-pixelPosition.x - size / 2, -pixelPosition.y - size / 2); let img = new Image() img.src = "https://png.icons8.com/windows/1600/long-arrow-right.png" ctx.drawImage(img, pixelPosition.x, pixelPosition.y, size, size); ctx.restore(); }
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <canvas id="canvas" width="200" height="200"></canvas>

As you state yourself in comments, atan is not suitable for this as the quotient of the coordinates loses information about the coordinates. Instead of suggesting atan2 , I would not advise using a trigonometric function for this: it is overkill.

As you only have 9 states (with "no movement" included), you could just map your movement to a number between 0 and 8 and use that:

moveIndex = movement.y*3+movement.x+4; // 0...8

This is more efficient.

As a side note, I also feel that using canvas would only be useful if you need it for other purposes as well. Otherwise you could just display the appropriate character available in the Unicode character set:

NB: make sure to call preventDefault when you process the arrow key events.

 const movement = { x: 0, y: 0 } $(window).on("keydown", function(e) { if (["ArrowDown","ArrowUp","ArrowRight","ArrowLeft"].includes(e.key)) e.preventDefault(); if (e.key === "ArrowDown") movement.y = 1 else if (e.key === "ArrowUp") movement.y = -1 else if (e.key === "ArrowRight") movement.x = 1 else if (e.key === "ArrowLeft") movement.x = -1 update() }).on("keyup", function(e) { if (["ArrowDown","ArrowUp","ArrowRight","ArrowLeft"].includes(e.key)) e.preventDefault(); if (["ArrowDown", "ArrowUp"].includes(e.key)) movement.y = 0 else if (["ArrowRight", "ArrowLeft"].includes(e.key)) movement.x = 0 update() }) function update() { const moveIndex = movement.y*3+movement.x+4; // 0...8 $("#arrow").text("↖↑↗← →↙↓↘"[moveIndex]); }
 #arrow { font-size: 80px }
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> Press arrow keys to show arrow... <div id="arrow"></div>

If you still need the canvas version, then you can of course use the index to get the appropriate image from an array of images, or to get the angle from an array of angles. The middle one of that array should represent the "no movement" case, so should clear the display.

I just played with something last night that may be just what you need. Forgoing trig except for vecLen calcs (where its still unnecessary), I draw an arrow head whose direction is specified with a vector.

You normalize the vector that extends from the back to the front of the arrow. You can then use this to step back towards the back of the arrow. Once done, you can step a small amount perpendicular to this vector in each direction, and you have the endpoints of each side of the arrow. endPoint1 --> lineEndPoint -->endPoint2

The 10 and 3 figures set the length and 1/2 width of the arrow-head.

 "use strict"; function byId(id){return document.getElementById(id)} function newEl(tag){return document.createElement(tag)} window.addEventListener('load', onDocLoaded, false); function onDocLoaded(evt) { var can = byId('output'); var ctx = can.getContext('2d'); drawArrowLine(0,0, can.width/2,can.height/2, ctx); } class vec2d { constructor(x,y) { this.mX = x; this.mY = y; } dotProd(other){return this.mX * other.mX + this.mY*other.mY;} add(other) { return new vec2d(this.mX+other.mX, this.mY+other.mY); } sub(other) { return new vec2d(this.mX-other.mX, this.mY-other.mY); } perp() { var tmp = this.mX; this.mX = -this.mY; this.mY = tmp; return this; } length() { return Math.hypot( this.mX, this.mY ); } scale(k) { this.mX *= k; this.mY *= k; return this; } normalize() { var len = this.length(); this.mX /= len; this.mY /= len; return this; } static clone(other) { return new vec2d(other.mX, other.mY); } clone() { return vec2d.clone(this); } } function drawArrowLine(fromX,fromY, toX,toY, ctx) { drawLine(fromX,fromY,toX,toY,ctx); drawArrow( new vec2d(toX,toY), new vec2d(toX-fromX,toY-fromY).normalize(), ctx ); } function drawLine(x1,y1,x2,y2,ctx) { ctx.beginPath(); ctx.moveTo(x1,y1); ctx.lineTo(x2,y2); ctx.stroke(); } function drawArrow(posVec, dirVec, ctx) { // points in direction of the arrow var tmp = vec2d.clone(dirVec); tmp.normalize(); tmp.scale(10); var tangent = vec2d.clone(tmp).perp(); tangent.normalize(); tangent.scale(3); var p1 = posVec.sub(tmp); var arHead1 = p1.sub(tangent); var arHead2 = p1.add(tangent); ctx.beginPath(); ctx.moveTo(arHead1.mX, arHead1.mY); ctx.lineTo(posVec.mX, posVec.mY); ctx.lineTo(arHead2.mX, arHead2.mY); ctx.stroke(); }
 <canvas id='output' width='200' height='200'></canvas>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM