that's problem just a question of basic math but i can't figure out where my mistake is.
So i got a logarithmic x axis and a linear y axis.
I have two points A(centerX, centerY) and B(x2, y2) in my plot, and I want to calculate the angle between the x-axis and vector AB.
I know the x,y value of both points, but if I compare my solution with a calculator i get the wrong value.
I'm using atan2 in Javascript.
Do I have to normalize my vectors first ?
function calcAngle(centerX, centerY, x2, y2) {
var distY = y2 - centerY;
var distX = x2 - centerX;
var theta = Math.atan2(distY, distX);
return theta*180/Math.PI;
}
If you want to get the angle as seen on your plot you will have to take into account the scaling factors for x and y and - yes - you will also have to calculate the logarithm of the x values.
Something like the following might work:
function calcAngle(centerX, centerY, x2, y2, xscale, yscale) {
var distX = Math.log(x2) - Math.log(centerX);
var distY = y2 - centerY;
var theta = Math.atan2(yscale*distY, xscale*distX);
return theta*180/Math.PI;
}
Related
I'm trying to make a project where the user can draw arrows in a canvas and i need a curved line for that.
As you know one quadratic curve is represented by something like that:
M 65 100 Q 300, 100, 300, 20
Where the first two numbers(65, 100) represents the starting point coordinates, the last two (300,20) represents the ending point coordinates(arrow end).
I need to calculate the middle two numbers based on the first and second point, to make a nice looking curved line.
The first point will have the coordinates from mousedown and the second point from mouseup.
For now i'm using like this.
function addCurve(Ax, Ay, Bx, By){
canvas.add(new fabric.Path('M '+ Ax +' '+ Ay +' Q 100, 100, '+ Bx +', '+ By +'', { fill: '', stroke: 'red' }));
}
addCurve(100,0,200,0);
So, how to calculate the middle point coordinates to get an uniform curve?
I'm also using fabric.js in this project.
First start with the two end points
x1 = ? // start point
y1 = ?
x2 = ? // end point
y2 = ?
To get the mid point
mx = (x1 + x2) / 2;
my = (y1 + y2) / 2;
You will need the vector from first to second point
vx = x2 - x1;
vy = y2 - y1;
The line at 90deg (clockwise or right) from the start and end points is
px = -vy; // perpendicular
py = vx;
The line is the same length as the distance between the two points. The quadratic curve will extend out half the distance that the control point is from the line. So if we want the curve to be 1/4 out by length then half the p vector and add to mid point
cx = mx + px / 2; // get control point
cy = my + py / 2;
If you want the curve to bend the other way
cx = my - px / 2;
cy = my - py / 2;
Or you can write it with the curve amount as a var
var curveAmount = 0.25; // How far out the curve is compared to the line length
cx = my - px * (curveAmount * 2);
cy = my - py * (curveAmount * 2);
Make curveAmount bigger for more curve, smaller for less. Zero for no curve at all and negative to bend the other way.
I want to calculate the rotation of a specific point (top and left). It's a bit complicated. I know the original top and left. Then a scaling is added and then the rotation is calculated.
At the moment i do this. (orginal left:-350, orginal top: -10, f1_scale: 0.544444, rotation angle:-30deg)
function sin(x) {
return Math.sin(x / 180 * Math.PI);
}
function cos(x) {
return Math.cos(x / 180 * Math.PI);
}
function rotate(x, y, a) {
var x2 = cos(a) * x - sin(a) * y;
var y2 = sin(a) * x - cos(a) * y;
return [x2, y2];
}
var scaledLeft = -350 * f1_scale;
var scaledTop = -10 * f1_scale;
var rotateOut = rotate(scaledLeft, scaledTop,-30);
This works for the left (x) coordinate, but the y coordinate is way off.
Can someone see what i did wrong or did someone already tried this?
Thank you.
You need to understand math behind it. First, look at this image http://prntscr.com/amd2it where:
(x0, y0) are coordinates of starting point
(x1, y1) are coordinates after rotation
(p, q) are coordinates of point of rotation
In order to find (x1, y1), you need to know value of (p, q), as well as (x0, y0) and angle a. If we apply elementary geometry, we get this:
sin(a)( q - y0 ) = q - y1
y1 = q - sin(a)( q - y0 )
and
cos(a)( p - x0 ) = p - x1
x1 = p - cos(a)( p - x0 )
or you can use Pythagoras' theorem for second value.
When we understand this, I don't think it will be problem to translate it to code.
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/
I have a drawing pad, and I use quadraticCurveTo to make all of the lines and curves, and I want the line to get thinner when you move the mouse faster. However if I just do something like calculate the velocity of the pen and have that alter the thickness of the curves, then each curve is a different thickness, and the thickness doesn't change smoothly. Is there another way to do this?
In other words - can quadraticCurveTo only draw a curve at a set thickness, and not change the thickness throughout the curve?
If yes, can you think of another way I can change the thickness of the line?
Here is a jsfiddle:
http://jsfiddle.net/y24su/
Here is the main part of the code that draws the curves -
if (isMouseDown){
++i;
X[i] = e.pageX;
Y[i] = e.pageY;
var x1 = X[i-2];
var y1 = Y[i-2];
var x2 = X[i-1];
var y2 = Y[i-1];
var x3 = X[i];
var y3 = Y[i];
var mid1x = (x1 + x2)/2;
var mid1y = (y1 + y2)/2;
var mid2x = (x2 + x3)/2;
var mid2y = (y2 + y3)/2;
var distance = Math.pow((Math.pow(mid2x-x2,2) + Math.pow(mid2y-y2,2)),2) + Math.pow((Math.pow(x2-mid1x,2) + Math.pow(y2-mid1y,2)),2);
var velocity = distance/2;
if(i>1)
{
if (velocity<1)
{ drawQuadraticThreePoints("black", 10, mid1x, mid1y, x2, y2, mid2x, mid2y);
}
else
{ drawQuadraticThreePoints("black", 10/velocity, mid1x, mid1y, x2, y2, mid2x, mid2y);
}
}
}
function drawQuadraticThreePoints (color, thickness, x1,y1,x2,y2,x3,y3) {
context.strokeStyle = color;
context.lineWidth = thickness;
context.beginPath();
context.moveTo(x1,y1);
context.quadraticCurveTo(x2,y2,x3,y3);
context.stroke();
}
You only get 1 context.lineWidth setting for each context.beginPath.
That means your context.quadraticCurveTo can only have 1 linewidth.
To get a variable width line without the "chunky" width changes, you must break your quadratic curves into many smaller line-segments.
With many line-segements you can gradually change the line width between each segment.
The following function calculates xy points along a quadratic curve.
function getQuadraticBezierXYatT(startPt,controlPt,endPt,T) {
var x = Math.pow(1-T,2) * startPt.x + 2 * (1-T) * T * controlPt.x + Math.pow(T,2) * endPt.x;
var y = Math.pow(1-T,2) * startPt.y + 2 * (1-T) * T * controlPt.y + Math.pow(T,2) * endPt.y;
return( {x:x,y:y} );
}
These are the inputs to the function:
startPt, controlPt, endPt are objects: eg. { x:10, y:20 }
T is an interval along the curve from 0.00 to 1.00
T==0.00 at the start of the line
T==1.00 at the end of the line
The rule of 1 lineWidth per beginPath still applies, but drawing multiple line segments per curve gives you more beginPaths with which to gradually adjust your lineWidth.
How can I detect when the user clicks inside the red bubble?
It should not be like a square field. The mouse must be really inside the circle:
Here's the code:
<canvas id="canvas" width="1000" height="500"></canvas>
<script>
var canvas = document.getElementById("canvas")
var ctx = canvas.getContext("2d")
var w = canvas.width
var h = canvas.height
var bubble = {
x: w / 2,
y: h / 2,
r: 30,
}
window.onmousedown = function(e) {
x = e.pageX - canvas.getBoundingClientRect().left
y = e.pageY - canvas.getBoundingClientRect().top
if (MOUSE IS INSIDE BUBBLE) {
alert("HELLO!")
}
}
ctx.beginPath()
ctx.fillStyle = "red"
ctx.arc(bubble.x, bubble.y, bubble.r, 0, Math.PI*2, false)
ctx.fill()
ctx.closePath()
</script>
A circle, is the geometric position of all the points whose distance from a central point is equal to some number "R".
You want to find the points whose distance is less than or equal to that "R", our radius.
The distance equation in 2d euclidean space is d(p1,p2) = root((p1.x-p2.x)^2 + (p1.y-p2.y)^2).
Check if the distance between your p and the center of the circle is less than the radius.
Let's say I have a circle with radius r and center at position (x0,y0) and a point (x1,y1) and I want to check if that point is in the circle or not.
I'd need to check if d((x0,y0),(x1,y1)) < r which translates to:
Math.sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)) < r
In JavaScript.
Now you know all these values (x0,y0) being bubble.x and bubble.y and (x1,y1) being x and y.
To test if a point is within a circle, you want to determine if the distance between the given point and the center of the circle is smaller than the radius of the circle.
Instead of using the point-distance formula, which involves the use of a (slow) square root, you can compare the non-square-rooted (or still-squared) distance between the points. If that distance is less than the radius squared, then you're in!
// x,y is the point to test
// cx, cy is circle center, and radius is circle radius
function pointInCircle(x, y, cx, cy, radius) {
var distancesquared = (x - cx) * (x - cx) + (y - cy) * (y - cy);
return distancesquared <= radius * radius;
}
(Not using your code because I want to keep the function general for onlookers who come to this question later)
This is slightly more complicated to comprehend, but its also faster, and if you intend on ever checking point-in-circle in a drawing/animation/object moving loop, then you'll want to do it the fastest way possible.
Related JS perf test:
http://jsperf.com/no-square-root
Just calculate the distance between the mouse pointer and the center of your circle, then decide whether it's inside:
var dx = x - bubble.x,
dy = y - bubble.y,
dist = Math.sqrt(dx * dx + dy * dy);
if (dist < bubble.r) {
alert('hello');
}
Demo
As mentioned in the comments, to eliminate Math.sqrt() you can use:
var distsq = dx * dx + dy * dy,
rsq = bubble.r * bubble.r;
if (distsq < rsq) {
alert('HELLO');
}
An alternative (not always useful meaning it will only work for the last path (re)defined, but I bring it up as an option):
x = e.pageX - canvas.getBoundingClientRect().left
y = e.pageY - canvas.getBoundingClientRect().top
if (ctx.isPointInPath(x, y)) {
alert("HELLO!")
}
Path can btw. be any shape.
For more details:
http://www.w3.org/TR/2dcontext/#dom-context-2d-ispointinpath