Calculating angle in degrees of mouse movement - javascript

I want to calculate the angle of the mouse move in degrees. While I know you don't have to move the mouse in a straight line, I was just trying to calculate it based on starting and end points to create a nice right angle.
log("ANGLE: " + getAngle(x1, y1, x2, y2)); is giving weird results:
ANGLE: 0.24035975832980774
mouse has stopped
ANGLE: 1.334887709726425
mouse has stopped
ANGLE: 0.2722859857950508
mouse has stopped
ANGLE: 0.3715485780567732
mouse has stopped
Code:
$("canvas").mousemove(function(e) {
getDirection(e);
if (!set) {
x1 = e.pageX, //set starting mouse x
y1 = e.pageY, //set starting mouse y
set = true;
}
clearTimeout(thread);
thread = setTimeout(callback.bind(this, e), 100);
});
function getAngle (x1, y1, x2, y2) {
var distY = Math.abs(y2-y1); //opposite
var distX = Math.abs(x2-x1); //adjacent
var dist = Math.sqrt((distY*distY)+(distX*distX)); //hypotenuse,
//don't know if there is a built in JS function to do the square of a number
var val = distY/dist;
var aSine = Math.asin(val);
return aSine; //return angle in degrees
}
function callback(e) {
x2 = e.pageX; //new X
y2 = e.pageY; //new Y
log("ANGLE: " + getAngle(x1, y1, x2, y2));
log("mouse has stopped");
set = false;
}

Your calculations seem to be right, but the problem is that Math.asin(val) returns values in radians. Use the following function to convert to degrees:
Math.degrees = function(radians) {
return radians*(180/Math.PI);
}
And then call Math.degrees(Math.asin(val)) and it should work!

Use the atan2 function to get an angle in the full circle,
radians = Math.atan2(y2-y1,x2-x1);
Additionally, you may want to log the values of x1,x2,y1,y2 at the time of computation, it would be better to just have one place where the mouse position is traced. At the moment, you update the position (x2,y2,t2) every 10ms, but (x1,y1,t1) every time the mouse is moved. This can result in very unpredictable sample combinations.

The Math.asin() function returns the angle in radians. If you multiply by 180/pi, you'll get the answer in degrees.
Additionally, since you want more than 90 degrees, you should drop the Math.abs call so that you can have a negative value for aSin.

Related

Show data labels inside donut pie chart p5js

I'm building a p5js donut chart, but I'm struggling to show the data labels in the middle. I think I have managed to get the boundaries right for it, but how would match the angle that I'm in? Or is there a way of matching just through the colours?
https://i.stack.imgur.com/enTBo.png
I have started by trying to match the boundaries of the chart to the pointer, which I managed to do using mouseX and mouseY. Any suggestions, please?
if(mouseX >= width / 2 - width * 0.2 && mouseY >= height / 2 - width * 0.2
&& mouseX <= width / 2 + width * 0.2 && mouseY <= height / 2 + width * 0.2)
{
//console.log("YAY!!! I'm inside the pie chart!!!");
}
else
{
textSize(14);
text('Hover over to see the labels', width / 2, height / 2);
}
};
[1]: https://i.stack.imgur.com/enTBo.png
While you could theoretically use the get() function to check the color of the pixel under the mouse cursor and correlate that with one of the entries in your dataset, I think you would be much better off doing the math to determine which segment the mouse is currently over. And conveniently p5.js provides helper functions that make it very easy.
In the example you showed you are only checking if the mouse cursor is in a rectangular region. But in reality you want to check if the mouse cursor is within a circle. To do this you can use the dist(x1, y1, x2, y2) function. Once you've established that the mouse cursor is over your pie chart, you'll want to determine which segment it is over. This can be done by finding the angle between a line draw from the center of the chart to the right (or whichever direction is where you started drawing the wedges), and a line drawn from the center of the chart to the mouse cursor. This can be accomplished using the angleBetween() function of p5.Vector.
Here's a working example:
const colors = ['red', 'green', 'blue'];
const thickness = 40;
let segments = {
foo: 34,
bar: 55,
baz: 89
};
let radius = 80, centerX, centerY;
function setup() {
createCanvas(windowWidth, windowHeight);
noFill();
strokeWeight(thickness);
strokeCap(SQUARE);
ellipseMode(RADIUS);
textAlign(CENTER, CENTER);
textSize(20);
centerX = width / 2;
centerY = height / 2;
}
function draw() {
background(200);
let keys = Object.keys(segments);
let total = keys.map(k => segments[k]).reduce((v, s) => v + s, 0);
let start = 0;
// Check the mouse distance and angle
let mouseDist = dist(centerX, centerY, mouseX, mouseY);
// Find the angle between a vector pointing to the right, and the vector
// pointing from the center of the window to the current mouse position.
let mouseAngle =
createVector(1, 0).angleBetween(
createVector(mouseX - centerX, mouseY - centerY)
);
// Counter clockwise angles will be negative 0 to PI, switch them to be from
// PI to TWO_PI
if (mouseAngle < 0) {
mouseAngle += TWO_PI;
}
for (let i = 0; i < keys.length; i++) {
stroke(colors[i]);
let angle = segments[keys[i]] / total * TWO_PI;
arc(centerX, centerY, radius, radius, start, start + angle);
// Check mouse pos
if (mouseDist > radius - thickness / 2 &&
mouseDist < radius + thickness / 2) {
if (mouseAngle > start && mouseAngle < start + angle) {
// If the mouse is the correct distance from the center to be hovering over
// our "donut" and the angle to the mouse cursor is in the range for the
// current slice, display the slice information
push();
noStroke();
fill(colors[i]);
text(`${keys[i]}: ${segments[keys[i]]}`, centerX, centerY);
pop();
}
}
start += angle;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.js"></script>
I think I know the source of the problem was that #thenewbie experienced: it is the p5 library being used. I was using the p5.min.js and experiencing the same problem. Once I started using the full p5.js library, the issue was resolved and #Paul's script worked.
Here is a link I came across while researching this which put me onto the solution:
https://github.com/processing/p5.js/issues/3973
Thanks Paul for the clear explanations and code above.

Rotating line smoothly towards vector (2D)

I'm writing a simple computer animation, which is a line that rotates around a fixed point at the center of that line. The amount of rotation is based on a gradient noise algorithm (OpenSimplex noise). The line has an origin [x,y] and a nr of the animation frame. These three values plugged into OpenSimplex noise give a rotation value. This part is working perfectly.
The problem is I want to make the line appear to follow the mouse cursor, depending on how far the mouse cursor is from the line. The cursor has coordinates [mx, my] (which change for every frame of animation). I can easily rotate the line and point straight towards the cursor. But I'm having difficulties factoring in the distance. To clarify; the line is rotation on the gradient noise and the mouse cursor alters that rotation to make the line (at [x, y]) point at [mx, my].
Also, the line has an 180 degree identity, so the closest end should point towards the mouse.
Basically what I'm doing now is taking "rotation line" plus "rotation mouse". If it is between 90 and 270 deg the back of the line is closest to the cursor, otherwise the front (for simplicity this is not included in the example code below). So I then take the difference, factor in the distance and substract or add it to the rotation of the line. And this works fairly well except for some artifacts.
let r = OpenSimplexNoise(x, y, frame); // gives current original rotation
let frame = 68; // whichever frame
let x = 60; // some fixed coordinate of line
let y = 60; // some fixed coordinate of line
let mouseX = 10; // changes when the mouse moves
let mouseY = 10; // changes when the mouse moves
let mouseRadius = 200;
let width = 100;
let height = 1;
function distance (x, y, cx, cy) {
return Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy));
}
function angle (x1, y1, x2, y2) {
let dx = x1 - x2;
let dy = y1 - y2;
return 360 + (Math.atan2(dy, dx) * 180 / Math.PI);
}
if (distance(x, y, mouseX, mouseY) <= mouseRadius) {
let dist = distance(x, y, mouseX, mouseY);
let mouseR = angle(x, y, mouseX, mouseY) % 360;
let near = (mouseRadius - dist) / mouseRadius;
let far = 1 - near;
r = (r * far + near * mouseR) % 360;
}
// r now includes mouse
A live version:
https://jsfiddle.net/Ruudt/56pk2wd1/1/
The problem lies in the cases where the mouse passes from being left to right of perpendicular to the (original rotation) line. Here the calculation will nominate the other end as "closests", then calculate the distance and apply this to the rotation. This results in the line jumping from pointing slightly left of the cursor to right of the cursor (or vice versa).
Is there a way to fix this?
I've made an image to illustrate the situation.
The red line represents the line using only the rotation of the gradient noise
The black line is the line that also includes mouse position
the blue arc is the mouse rotation value (right end is origin)
line rotation:

Using circle math for solid collision

function distance(x1, y1, x2, y2) {
var x = x1 - x2;
var y = y1 - y2;
return(Math.sqrt((x*x) + (y*y)))
};
function collisionCirc(circ1, circ2) {
var d = distance(circ1.x, circ1.y, circ2.x, circ2.y);
var r = circ1.radius + circ2.radius;
return(r > d);
};
function collisionCircPoint(circ1, circ2) {
var cx = ((circ1.x * circ2.radius) + (circ2.x * circ1.radius)) / (circ1.radius + circ2.radius);
var cy = ((circ1.y * circ2.radius) + (circ2.y * circ1.radius)) / (circ1.radius + circ2.radius);
var p = [cx, cy];
return p;
};
function angleDegrees(x1, y1, x2, y2) {
return (Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI) + 180;
};
function updateCollisions() {
var a;
var p;
Player.hitArea = new PIXI.Circle(Player.sprite.x, Player.sprite.y, 20);
MapObjects.chest.hitArea = new PIXI.Circle(MapObjects.chest.x, MapObjects.chest.y, 20);
if (collisionCirc(Player.hitArea, MapObjects.chest.hitArea)) {
a = angleDegrees(Player.sprite.x, Player.sprite.y, MapObjects.chest.x, MapObjects.chest.y);
p = collisionCircPoint(Player.hitArea, MapObjects.chest.hitArea);
Player.sprite.x = p[0];
Player.sprite.y = p[1];
};
};
I have 2 sprites on the map and each has a circle hitArea defined. I am trying to make a smooth circular collision that the player cannot pass through. I thought I could just set the Player.sprite's coordinates to the point of collision but it just warps him to the MapObjects.chest's coordinates, even though the point of collision is correct and is 20 pixels from the MapObject.chest's center. What am I doing wrong or what more information is needed to create a collision much like the JavaScript physics libraries where I can circle around a circle object?
The collision point is between the player and the obstacle. If you move the player towards the collision point, you are actually moving the player closer. For example, if there's exactly 40 px (r1+r2) between the player and the obstacle, the collision point is between them, at only 20 px from the obstacle!
When you have multiple objects, getting it right when the collision has already happened is difficult. If there is only one obstacle nearby, you can simply move the player directly away from the obstacle. However, this way the player might actually end up inside another obstacle.
Another solution is to go back to the start and try smaller movements, until there is no collision. This way you would eventually get it right, but this might also be slow.
The mathematically correct solution is to calculate the maximum distance to move before the collision happens. This is done by solving the following vector equation:
# p = player position before moving
# o = obstacle position
# u = player direction (unit vector)
# d = distance to move
distance(o, p + d * u) = o.radius + p.radius
That's mathematics, you may solve it by yourself or using a tool like Wolfram Alpha.
Solving this equation will give you zero, one or two possible values for the distance. Negative values you can dismiss, as they mean that the player is already past the obstacle. If you get only one value, it means that the player would merely brush the obstacle, which you can also dismiss. Two values mean that the collision happens between these distances; the smaller value is where the collision starts, and the larger value is where the player would already be through the obstacle. Also, if one value is positive and the other is negative, it means that the player is already inside the obstacle, which should never happen.
You should run this check for all nearby obstacles and then move the player according to the smallest non-negative result (that is, zero or positive), or less, if the player can't move that fast.
Finally, to circle around a round object, you can move the player a little bit in a perpendicular direction (either left or right, depending on which side of the obstacle the player will be passing) after a collision, if this doesn't cause any new collisions.
There are many other possible implementations.
Player.hitArea = new PIXI.Circle(Player.sprite.x, Player.sprite.y, 20);
MapObjects.chest.hitArea = new PIXI.Circle(MapObjects.chest.x, MapObjects.chest.y, 20);
if (collisionCirc(Player.hitArea, MapObjects.chest.hitArea)) {
p = collisionCircPoint(Player.hitArea, MapObjects.chest.hitArea);
a = angleDegrees(Player.sprite.x, Player.sprite.y, MapObjects.chest.x, MapObjects.chest.y);
if (Player.sprite.x - MapObjects.chest.x > 0) {
Player.sprite.x += 1;
} else if (Player.sprite.x + MapObjects.chest.x > 0) {
Player.sprite.x -= 1;
};
if (Player.sprite.y - MapObjects.chest.y > 0) {
Player.sprite.y += 1;
} else if (Player.sprite.y + MapObjects.chest.y > 0) {
Player.sprite.y -= 1;
};
};
};
I added that and it actually works well enough minus the player speed being slightly too fast when running into the MapObjects.chest's hitArea at certain angles. Work on that later.

JS Canvas - draw line at a specified angle

I'd like to make an app where a ball moves at the angle your mouse hits it. So if you swipe your mouse down from top left quadrant at 30 degrees (I guess that would be 180-30 = angle of 150 degrees), it will knock the ball that way. I've been drawing my lines as such:
function drawAngles () {
var d = 50; //start line at (10, 20), move 50px away at angle of 30 degrees
var angle = 80 * Math.PI/180;
ctx.beginPath();
ctx.moveTo(300,0);
ctx.lineTo(300,600); //x, y
ctx.moveTo(0,300);
ctx.lineTo(600,300);
ctx.moveTo(300,300);
ctx.lineTo(600,100);
ctx.arc(300,300,300,0,2*Math.PI);
ctx.stroke();
}
But this doesn't give me an idea of what the angles are.
Then I move the ball at that angle (for now, I'm animating it without mouse interaction)
function getAngleX (x) {
return x = x + (50 * Math.cos(Math.PI/6));
}
function getAngleY(y) {
return y = y + (50 * Math.sin(Math.PI/6));
}
//just animate this box to move at an angle from center down at 30 degrees
$(".anotherBox").mouseenter(function(e) {
pos = $(this).position();
box2X = pos.left;
box2Y = pos.top;
$(this).animate({
//top : $(window).outerHeight(),
top : getAngleY(box2Y)+"px",
left: getAngleX(box2X)+"px",
}, "slow");
});
So how can I draw a line at a specified angle? I'd like to make sure my ball is following along that path.
You can use different approaches to achieve this but if you want to use the same basis to move and draw then this approach may suit well.
First we use a function to get step values for x and y based on the angle (in radians):
function getSteps(angle) {
var cos = Math.cos(angle),
sin = Math.sin(angle);
return {
x: cos -sin,
y: sin + cos
}
}
Then using these steps values we can scale them to get an end point, or scale them gradually to animate an object along the line. A simple loop could look like this (just for example):
function loop() {
var x = i * step.x, // scale using i
y = i * step.y;
ctx.fillRect(200 + x, 200 + y, 2, 2); // add to origin start point 200, 200
i += 1; // increase i
if (i < length) requestAnimationFrame(loop);
}
Live demo
If you just want to draw a line at a certain angle you can do the following instead:
function lineAtAngle(x1, y1, length, angle) {
ctx.moveTo(x1, y1);
ctx.lineTo(x1 + length * Math.cos(angle), y1 + length * Math.sin(angle));
}
then stroke it.
Hope this helps!
If i guess right, i think you want the mouse act like a baseball bat, and you need to measure the current mouse angle, that is to store previous mouse position and do some math.
You have also to keep track if you allready handled current collision, to avoid the ball being 'sticky' and follow the mouse.
http://jsfiddle.net/gamealchemist/z3U8g/
var ctx = cv.getContext('2d');
var ball = {
x:200, y:200,
r : 30,
vx : 0.4, vy:0.4
}
// when mouse moved that distance, ball speed norm will be 1
var speedNorm = 10;
var collisionOnGoing = false;
function collide() {
var dist = sq(ball.x - mx) + sq (ball.y-my);
// too far from ball ?
if (dist > sq(ball.r)) {
collisionOnGoing = false;
return;
}
// return if collision allready handled
if (collisionOnGoing) return;
var mouseDist =Math.sqrt( sq(mx-lastmx) + sq(my-lastmy) );
// no collision if mouse too slow
if (mouseDist<speedNorm/5) return;
// launch the ball in current direction
// with a speed relative to the mouse speed.
var mouseAngle = Math.atan2(my-lastmy, mx-lastmx);
ball.vx= (mouseDist / speedNorm ) * Math.cos(mouseAngle);
ball.vy= (mouseDist / speedNorm ) * Math.sin(mouseAngle);
collisionOnGoing = true;
}
function animate() {
requestAnimationFrame(animate);
ctx.clearRect(0,0,400,400);
// collide ball with mouse
collide();
// draw ball
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, 6.3);
ctx.fill();
ctx.closePath();
// move
ball.x+=ball.vx;
ball.y+=ball.vy;
// collide with screen
if (ball.x>400) ball.vx=-Math.abs(ball.vx);
if (ball.x<0) ball.vx=Math.abs(ball.vx);
if (ball.y>400) ball.vy=-Math.abs(ball.vy);
if (ball.y<0) ball.vy=Math.abs(ball.vy);
}
animate();
// --- Mouse handling ---
addEventListener('mousemove', mouseMove);
var mx=-1, my=-1, lastmx=-1, lastmy=-1;
var cvRect = cv.getBoundingClientRect();
var cvLeft = cvRect.left;
var cvTop = cvRect.top;
function mouseMove(e) {
lastmx = mx; lastmy=my;
mx=e.clientX - cvLeft;
my=e.clientY - cvTop;
}
function sq(x) { return x*x; }

JS - push ball at an angle

I want to push a ball at an angle using the mouse.
To do this, so far I have:
Calculated mouse movement angle
Calculated original and new positions of ball
But the ball isn't moving when I hit the ball. It seems to trail behind.
I think this is due to animating it in my callback. But I need to run the animation there in order to pass in the newX and newY after calculation.
And sometimes it goes off on weird angles.
I think this is because when I set the newX, newY, it's adding to the new location, instead of just positioning it where it should be?
Or, I noticed my angles don't follow all the way around the circle (meaning, moving to upper right quadrant gives angle range of 0 - 90, moving to lower right gives range of 0 - 90 still.. but it should give range of 270 - 360). Not sure how to fix this.
Lastly, sometimes the angle gives NaN. Not sure why it's not a number
Any thoughts?
FIDDLE: http://jsfiddle.net/edH59/
Code:
//Get angle of mouse movement
function getAngle (x1, y1, x2, y2) {
var dY = Math.abs(y2-y1); //opposite
var dX = Math.abs(x2-x1); //adjacent
var dist = Math.sqrt((dY*dY)+(dX*dX)); //hypotenuse
var sin = dY/dist; //opposite over hypotenuse
var radians = Math.asin(sin);
var degrees = radians*(180/Math.PI); //convert from radians to degrees
angle = degrees;
return degrees; //return angle in degrees
}
$("canvas").mousemove(function(e) {
getDirection(e);
if (!set) {
x1 = e.pageX,
y1 = e.pageY,
set = true;
}
clearTimeout(thread);
thread = setTimeout(callback.bind(this, e), 100);
});
$(".anotherBox").mouseenter(function(e) {
pos = $(this).position();
box2X = pos.left;
box2Y = pos.top;
if(animate) {
$(this).animate({
top : newY+"px",
left: newX+"px",
}, "slow");
}
animate = false;
});
}
function calcNewLoc (x, y, xDist, yDist) {
newX = x + (xDist * Math.cos(angle));
newY = y + (yDist * Math.sin(angle));
}
function callback(e) {
x2 = e.pageX;
y2 = e.pageY;
t2 = new Date().getTime();
var xDist = x2 - x1,
yDist = y2 - y1,
time = t2 - t1;
//to calc angle... need to get starting position and ending position
$(".angle").html(getAngle(x1, y1, x2, y2));
calcNewLoc(x1, y1, xDist, yDist);
animate = true; //only allow animation of ball once new locations are calculated
log("mouse has stopped");
set = false;
}
To have an angle, you have to store the previous location of the mouse and consider the current angle as the angle between last measure and current. You could do some kind of averageing on this to smooth the result. In my demo the current direction is drawn in red if the direction vector is long enough.
The computation of the angle is done with a classical atan2.
var mouseAngle = Math.atan2(my-lastmy, mx-lastmx);
You can have a look at the result here :
http://jsfiddle.net/gamealchemist/z3U8g/4/embedded/result/
code is here :
http://jsfiddle.net/gamealchemist/z3U8g/4/

Categories

Resources