I'm trying to simple get the angle between two points in Node JS, I've gone though a few answers and none of them seem to work.
Here's what I've got so far as a test.js file.
function getDegree(x1,y1, x2,y2) {
var dy = y2 - y1;
var dx = x2 - x1;
var theta = Math.atan2(dy, dx); // range (-PI, PI]
theta *= 180 / Math.PI; // rads to degs, range (-180, 180]
// range [0, 360)
if (theta < 0) {
theta = 360 + theta;
}
return theta;
}
function output(expected, angle) {
console.log('\n');
var result = getDegree(angle[0], angle[1], angle[2], angle[3]);
console.log('getDegree(' + angle + ') = ' + result);
console.log('Expected: ' + expected + ', Result: ' + result + ' : ' + (expected == result));
}
var angle = [0,0, 0,0]; //(Current Pos), (Pos to Go to)
angle = [0,0,0,0];
output(0, angle);
angle = [0,0,0,9];
output(0, angle);
angle = [0,0,9,0];
output(90, angle);
angle = [0,0,0,-9];
output(180, angle);
angle = [0,0,-9,0];
output(270, angle);
// Should be 90ish
angle = [5,5,31,4];
output(90, angle);
module.exports = {
getDegree: getDegree
};
And the output by running directly from node in the same directory.
var test = require('./test');
getDegree(0,0,0,0) = 0
Expected: 0, Result: 0 : true
getDegree(0,0,0,9) = 90
Expected: 0, Result: 90 : false
getDegree(0,0,9,0) = 0
Expected: 90, Result: 0 : false
getDegree(0,0,0,-9) = 270
Expected: 180, Result: 270 : false
getDegree(0,0,-9,0) = 180
Expected: 270, Result: 180 : false
// 90ish
getDegree(5,5,31,4) = 357.7974018382342
Expected: 90, Result: 357.7974018382342 : false
The angle you are looking for is different from what math.atan2 gives you.i made this image , try to understand it.
so what you can do ? swap y and x.
you should substitute angle from 90 .
θ -> 90-θ
tan(90- θ) = cot θ // you can use this one too without interchanging x and y
cot θ = 1 / tan θ // so that's why you should swap x and y
as you can see you have to use 1/tan θ same as swapping y and x
var theta = Math.atan2(dx, dy);
Related
I need to find the midpoint of the arc USING JavaScript
.
I want to find M in terms of the following information:
A.X and A.Y, the coordinates of A
B.X and B.Y, the coordinates of B
Radius, the radius of the arc
C.X and C.Y, the center of the arc
How do I compute the coordinates of M?
I have a problem with the x sign
var a = {x:x1,y:y1}
var b = {x:x2,y:y2}
var c = {x:cx,y:cy}
var theta1 = Math.atan(a.y / a.y);
var theta2 = Math.atan(b.y / b.x)
var theta = (theta1 + theta2) / 2;
var mx = r * Math.cos(theta);
var my = r * Math.sin(theta);
var positive
if (cx > 0) {
positive = 1
} else {
positive = -1
}
var midx = positive * (Math.abs(mx) + Math.abs(cx))
var midy = my + cy
writeBlock(cx, cy);
writeBlock(mx, my, x1, y1, x2, y2);
Here's how I would do it, using a unit circle to make things simple:
const A = { x: 0, y: 1 };
const B = { x: 1, y: 0 };
const C = { x: 0, y: 0 };
// get A and B as vectors relative to C
const vA = { x: A.x - C.x, y: A.y - C.y };
const vB = { x: B.x - C.x, y: B.y - C.y };
// angle between A and B
const angle = Math.atan2(vA.y, vA.x) - Math.atan2(vB.y, vB.x);
// half of that
const half = angle / 2;
// rotate point B by half of the angle
const s = Math.sin(half);
const c = Math.cos(half);
const xnew = vB.x * c - vB.y * s;
const ynew = vB.x * s + vB.y * c;
// midpoint is new coords plus C
const midpoint = { x: xnew + C.x, y: ynew + C.y };
console.log(midpoint); // { x: sqrt2 / 2, y: sqrt2 / 2 }
Please note that this assumes that point B is always "after" A (going clockwise) and it always assumes the arc is defined clockwise.
Sum CA and CB vectors, making bisector D
Normalize bisector dividing by its length
Multiply normalized bisector by R
Add result to C to get M
Dx = A.x + B.x - 2*C.x
Dy = A.y + B.y - 2*C.y
Len = sqrt(Dx*Dx + Dy*Dy)
f = R / Len
Mx = C.x + Dx * f
My = C.y + Dy * f
(doesn't work for 180 degrees arc, for that case just rotate Dx by 90)
I wish to modify an image by moving columns of pixels up or down such that each column offset follows a curve.
I wish for the curve to intersect 6 or so points in some smooth way. I Imagine looping over image x co-ordinates and calling a curve function that returns the y co-ordinate for the curve at that offset, thus telling me how much to move each column of pixels.
I have investigated various types of curves but frankly I am a bit lost, I was hoping there would be a ready made solution that would allow me to plug in my point co-ords and spit out the data that I need. I'm not too fussed what kind of curve is used, as long as it looks "smooth".
Can anyone help me with this?
I am using HTML5 and canvas. The answer given here looks like the sort of thing I am after, but it refers to an R library (I guess) which is Greek to me!
Sigmoid curve
A very simple solution if you only want the curve in the y direction is to use a sigmoid curve to interpolate the y pos between control points
// where 0 <= x <= 1 and p > 1
// return value between 0 and 1 inclusive.
// p is power and determines the amount of curve
function sigmoidCurve(x, p){
x = x < 0 ? 0 : x > 1 ? 1 : x;
var xx = Math.pow(x, p);
return xx / (xx + Math.pow(1 - x, p))
}
If you want the y pos at x coordinate px that is between two control points x1,y1 and x2, y2
First find the normalized position of px between x1,x2
var nx = (px - x1) / (x2 - x1); // normalised dist between points
Plug the value into sigmoidCurve
var c = sigmoidCurve(nx, 2); // curve power 2
The use that value to calculate y
var py = (y2 - y1) * c + y1;
And you have a point on the curve between the points.
As a single expression
var py = (y2 - y1) *sigmoidCurve((px - x1) / (x2 - x1), 2) + y1;
If you set the power for the sigmoid curve to 1.5 then it is almost a perfect match for a cubic bezier curve
Example
This example shows the curve animated. The function getPointOnCurve will get the y pos of any point on the curve at position x
const ctx = canvas.getContext("2d");
const curve = [[10, 0], [120, 100], [200, 50], [290, 150]];
const pos = {};
function cubicInterpolation(x, p0, p1, p2, p3){
x = x < 0 ? 0 : x > 1 ? 1 : x;
return p1 + 0.5*x*(p2 - p0 + x*(2*p0 - 5*p1 + 4*p2 - p3 + x*(3*(p1 - p2) + p3 - p0)));
}
function sigmoidCurve(x, p){
x = x < 0 ? 0 : x > 1 ? 1 : x;
var xx = Math.pow(x, p);
return xx / (xx + Math.pow(1 - x, p))
}
// functional for loop
const doFor = (count, cb) => { var i = 0; while (i < count && cb(i++) !== true); };
// functional iterator
const eachOf = (array, cb) => { var i = 0; const len = array.length; while (i < len && cb(array[i], i++, len) !== true ); };
// find index for x in curve
// returns pos{ index, y }
// if x is at a control point then return the y value and index set to -1
// if not at control point the index is of the point befor x
function getPosOnCurve(x,curve, pos = {}){
var len = curve.length;
var i;
pos.index = -1;
pos.y = null;
if(x <= curve[0][0]) { return (pos.y = curve[0][1], pos) }
if(x >= curve[len - 1][0]) { return (pos.y = curve[len - 1][1], pos) }
i = 0;
var found = false;
while(!found){ // some JS optimisers will mark "Do not optimise"
// code that do not have an exit clause.
if(curve[i++][0] <x && curve[i][0] >= x) { break }
}
i -= 1;
if(x === curve[i][0]) { return (pos.y = curve[i][1], pos) }
pos.index =i
return pos;
}
// Using Cubic interpolation to create the curve
function getPointOnCubicCurve(x, curve, power){
getPosOnCurve(x, curve, pos);
if(pos.index === -1) { return pos.y };
var i = pos.index;
// get interpolation values for points around x
var p0,p1,p2,p3;
p1 = curve[i][1];
p2 = curve[i+1][1];
p0 = i === 0 ? p1 : curve[i-1][1];
p3 = i === curve.length - 2 ? p2 : curve[i+2][1];
// get unit distance of x between curve i, i+1
var ux = (x - curve[i][0]) / (curve[i + 1][0] - curve[i][0]);
return cubicInterpolation(ux, p0, p1, p2, p3);
}
// Using Sigmoid function to get curve.
// power changes curve power = 1 is line power > 1 tangents become longer
// With the power set to 1.5 this is almost a perfect match for
// cubic bezier solution.
function getPointOnCurve(x, curve, power){
getPosOnCurve(x, curve, pos);
if(pos.index === -1) { return pos.y };
var i = pos.index;
var p = sigmoidCurve((x - curve[i][0]) / (curve[i + 1][0] - curve[i][0]) ,power);
return curve[i][1] + (curve[i + 1][1] - curve[i][1]) * p;
}
const step = 2;
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center width and height
var ch = h / 2;
function update(timer){
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0,0,w,h);
eachOf(curve, (point) => {
point[1] = Math.sin(timer / (((point[0] + 10) % 71) * 100) ) * ch * 0.8 + ch;
});
ctx.strokeStyle = "black";
ctx.beginPath();
doFor(w / step, x => { ctx.lineTo(x * step, getPointOnCurve(x * step, curve, 1.5) - 10)});
ctx.stroke();
ctx.strokeStyle = "blue";
ctx.beginPath();
doFor(w / step, x => { ctx.lineTo(x * step, getPointOnCubicCurve(x * step, curve, 1.5) + 10)});
ctx.stroke();
ctx.strokeStyle = "black";
eachOf(curve,point => ctx.strokeRect(point[0] - 2,point[1] - 2 - 10, 4, 4) );
eachOf(curve,point => ctx.strokeRect(point[0] - 2,point[1] - 2 + 10, 4, 4) );
requestAnimationFrame(update);
}
requestAnimationFrame(update);
canvas { border : 2px solid black; }
<canvas id="canvas"></canvas>
Update
I have added a second curve type to the above demo as the blue curve offset from the original sigmoid curve in black.
Cubic polynomial
The above function can be adapted to a variety of interpolation methods. I have added the function
function cubicInterpolation(x, p0, p1, p2, p3){
x = x < 0 ? 0 : x > 1 ? 1 : x;
return p1 + 0.5*x*(p2 - p0 + x*(2*p0 - 5*p1 + 4*p2 - p3 + x*(3*(p1 - p2) + p3 - p0)));
}
Which produces a curve based on the slope of the line at two points either side of x. This method is intended for evenly spaced points but still works if you have uneven spacing (such as this example). If the spacing gets too uneven you can notice a bit of a kink in the curve at that point.
Also the curve over and under shoot may be an issue.
For more on the Maths of cubic interpolation.
I'm trying to draw 2 unit vectors and then draw an arc between them. I'm not looking for any solution, rather I want to know why my specific solution is not working.
First I pick 2 unit vectors at random.
function rand(min, max) {
if (max === undefined) {
max = min;
min = 0;
}
return Math.random() * (max - min) + min;
}
var points = [{},{}];
points[0].direction = normalize([rand(-1, 1), rand(-1, 1), 0]);
points[1].direction = normalize([rand(-1, 1), rand(-1, 1), 0]);
Note: the math here is in 3D but I'm using a 2d example by just keeping the vectors in the XY plane
I can draw those 2 unit vectors in a canvas
// move to center of canvas
var scale = ctx.canvas.width / 2 * 0.9;
ctx.transform(ctx.canvas.width / 2, ctx.canvas.height / 2);
ctx.scale(scale, scale); // expand the unit fill the canvas
// draw a line for each unit vector
points.forEach(function(point) {
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(point.direction[0], point.direction[1]);
ctx.strokeStyle = point.color;
ctx.stroke();
});
That works.
Next I want to make a matrix that puts the XY plane with its Y axis aligned with the first unit vector and in the same plane as the plane described by the 2 unit vectors
var zAxis = normalize(cross(points[0].direction, points[1].direction));
var xAxis = normalize(cross(zAxis, points[0].direction));
var yAxis = points[0].direction;
I then draw a unit grid using that matrix
ctx.setTransform(
xAxis[0] * scale, xAxis[1] * scale,
yAxis[0] * scale, yAxis[1] * scale,
ctx.canvas.width / 2, ctx.canvas.height / 2);
ctx.beginPath();
for (var y = 0; y < 20; ++y) {
var v0 = (y + 0) / 20;
var v1 = (y + 1) / 20;
for (var x = 0; x < 20; ++x) {
var u0 = (x + 0) / 20;
var u1 = (x + 1) / 20;
ctx.moveTo(u0, v0);
ctx.lineTo(u1, v0);
ctx.moveTo(u0, v0);
ctx.lineTo(u0, v1);
}
}
ctx.stroke();
That works too. Run the sample below and see the pink unit grid is always aligned with the green unit vector and facing in the direction of the red unit vector.
Finally using the data for the unit grid I want to bend it the correct amount to fill the space between the 2 unit vectors. Given it's a unit grid it seems like I should be able to do this
var cosineOfAngleBetween = dot(points[0].direction, points[1].direction);
var expand = (1 + -cosineOfAngleBetween) / 2 * Math.PI;
var angle = x * expand; // x goes from 0 to 1
var newX = sin(angle) * y; // y goes from 0 to 1
var newY = cos(angle) * y;
And if I plot newX and newY for every grid point it seems like I should get the correct arc between the 2 unit vectors.
Taking the dot product of the two unit vectors should give me the cosine of the angle between them which goes from 1 if they are coincident to -1 if they are opposite. In my case I need expand to go from 0 to PI so (1 + -dot(p0, p1)) / 2 * PI seems like it should work.
But it doesn't. See the blue arc which is the unit grid points as input to the code above.
Some things I checked. I checked zAxis is correct. It's always either [0,0,1] or [0,0,-1] which is correct. I checked xAxis and yAxis are unit vectors. They are. I checked manually setting expand to PI * .5, PI, PI * 2 and it does exactly what I expect. PI * .5 gets a 90 degree arc, 1/4th of the way around from the blue unit vector. PI gets a half circle exactly as I expect. PI * 2 gets a full circle.
That makes it seem like dot(p0,p1) is wrong but looking at the dot function it seems correct and if test it with various easy vectors it returns what I expect dot([1,0,0], [1,0,0]) returns 1. dot([-1,0,0],[1,0,0]) returns -1. dot([1,0,0],[0,1,0]) returns 0. dot([1,0,0],normalize([1,1,0])) returns 0.707...
What am I missing?
Here's the code live
function cross(a, b) {
var dst = []
dst[0] = a[1] * b[2] - a[2] * b[1];
dst[1] = a[2] * b[0] - a[0] * b[2];
dst[2] = a[0] * b[1] - a[1] * b[0];
return dst;
}
function normalize(a) {
var dst = [];
var lenSq = a[0] * a[0] + a[1] * a[1] + a[2] * a[2];
var len = Math.sqrt(lenSq);
if (len > 0.00001) {
dst[0] = a[0] / len;
dst[1] = a[1] / len;
dst[2] = a[2] / len;
} else {
dst[0] = 0;
dst[1] = 0;
dst[2] = 0;
}
return dst;
}
function dot(a, b) {
return (a[0] * b[0]) + (a[1] * b[1]) + (a[2] * b[2]);
}
var canvas = document.querySelector("canvas");
canvas.width = 200;
canvas.height = 200;
var ctx = canvas.getContext("2d");
function rand(min, max) {
if (max === undefined) {
max = min;
min = 0;
}
return Math.random() * (max - min) + min;
}
var points = [
{
direction: [0,0,0],
color: "green",
},
{
direction: [0,0,0],
color: "red",
},
];
var expand = 1;
var scale = ctx.canvas.width / 2 * 0.8;
function pickPoints() {
points[0].direction = normalize([rand(-1, 1), rand(-1, 1), 0]);
points[1].direction = normalize([rand(-1, 1), rand(-1, 1), 0]);
expand = (1 + -dot(points[0].direction, points[1].direction)) / 2 * Math.PI;
console.log("expand:", expand);
render();
}
pickPoints();
function render() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.save();
ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);
ctx.scale(scale, scale);
ctx.lineWidth = 3 / scale;
points.forEach(function(point) {
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(point.direction[0], point.direction[1]);
ctx.strokeStyle = point.color;
ctx.stroke();
});
var zAxis = normalize(cross(points[0].direction, points[1].direction));
var xAxis = normalize(cross(zAxis, points[0].direction));
var yAxis = points[0].direction;
ctx.setTransform(
xAxis[0] * scale, xAxis[1] * scale,
yAxis[0] * scale, yAxis[1] * scale,
ctx.canvas.width / 2, ctx.canvas.height / 2);
ctx.lineWidth = 0.5 / scale;
ctx.strokeStyle = "pink";
drawPatch(false);
ctx.strokeStyle = "blue";
drawPatch(true);
function drawPatch(curved) {
ctx.beginPath();
for (var y = 0; y < 20; ++y) {
var v0 = (y + 0) / 20;
var v1 = (y + 1) / 20;
for (var x = 0; x < 20; ++x) {
var u0 = (x + 0) / 20;
var u1 = (x + 1) / 20;
if (curved) {
var a0 = u0 * expand;
var x0 = Math.sin(a0) * v0;
var y0 = Math.cos(a0) * v0;
var a1 = u1 * expand;
var x1 = Math.sin(a1) * v0;
var y1 = Math.cos(a1) * v0;
var a2 = u0 * expand;
var x2 = Math.sin(a0) * v1;
var y2 = Math.cos(a0) * v1;
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.moveTo(x0, y0);
ctx.lineTo(x2, y2);
} else {
ctx.moveTo(u0, v0);
ctx.lineTo(u1, v0);
ctx.moveTo(u0, v0);
ctx.lineTo(u0, v1);
}
}
}
ctx.stroke();
}
ctx.restore();
}
window.addEventListener('click', pickPoints);
canvas {
border: 1px solid black;
}
div {
display: flex;
}
<div><canvas></canvas><p> Click for new points</p></div>
There's nothing wrong with your dot product function. It's the way you're using it:
expand = (1 + -dot(points[0].direction, points[1].direction)) / 2 * Math.PI;
should be:
expand = Math.acos(dot(points[0].direction, points[1].direction));
The expand variable, as you use it, is an angle (in radians). The dot product gives you the cosine of the angle, but not the angle itself. While the cosine of an angle varies between 1 and -1 for input [0,pi], that value does not map linearly back to the angle itself.
In other words, it doesn't work because the cosine of an angle cannot be transformed into the angle itself simply by scaling it. That's what arcsine is for.
Note that in general, you can often get by using your original formula (or any simple formula that maps that [-1,1] domain to a range of [0,pi]) if all you need is an approximation, but it will never give an exact angle except at the extremes.
This can be seen visually by plotting the two functions on top of each other:
I'd like to make a ball bounce angle change each time it hits a wall.
It will change based on how near the middle of the wall it hits...
Right now, I'm hard coding the change in X and Y when it hits a surface... My goal is to get the degrees from current X and Y, apply a change to the degrees (Right now I'm add a random number to the degrees), then calculate the new incrementing values for X and Y. I know how to get the newX and newY, but not how to get the incrementing values.
The green is the starting x y of (5,5)... the blue is the next frame of (4,4).
So I calculated the degrees to be 45 based on that.
Then added a random number to the degrees.
Then, I want to get the new x and y coordinates. So I followed this method...
currX (5) - wallX (0) = distX (5)
currY (5) - wallY (0) = distY (5)
Take the cosine of my angle + random increment, we'll say 55 degrees, * distX
cos(55 degrees) = .5735... .5735 x distX (5) = 2.86
And sin of my angle * distY
sin(55 degrees) = .8191... .8191 x distY (5) = 4.09
newX = cos result (2.86) + originX (5) = 7.86
newY = sin result (4.09) + originY (5) = 9.09
newX, newY = (7.86, 9.09)
Okay... so I have my new coordinates...
But those don't equate to what my new incrementing value of x and y should be based on my angle in incidence.
Code snippet: You can see that I'm hard coding the x,y increments (dragger.x += 2; )
function tick() {
var rand = Math.floor((Math.random()*10)+1);
console.log("ticking..." + rand);
if (dragger.x >= 400-20) {
dragger.xDir = "right";
}
if (dragger.x < 20) {
dragger.xDir = "left";
}
if (dragger.y >= 150-20) {
dragger.yDir = "up";
}
if (dragger.y < 20) {
dragger.yDir = "down";
}
var oldX = dragger.y;
var oldY = dragger.x;
if (dragger.xDir == "left") {
dragger.x += 2;
}
else {
dragger.x -= 2;
}
if (dragger.yDir == "up") {
dragger.y -= 2;
}
else {
dragger.y += 2;
}
//post update...
var newX = dragger.y;
var newY = dragger.x;
var angle = getAngle(newX, oldX, newY, oldY)
angle+=rand;
$('#getAngle').empty();
$('#getAngle').append("bounce angle (degrees): " + angle);
//console.log(xDir);
// update the stage:
stage.update();
}
function getAngle(x2, x1, y2, y1) {
var deltaX = Math.abs(x2-x1);
var deltaY = Math.abs(y2-y1);
var radians = Math.atan2(deltaX, deltaY);
var degrees = radians * (180/Math.PI);
return degrees;
}
This is a pretty interesting problem due to it's specificity.
Making a ball bounce in a programming language can be done quite easily. Like this example.
But clearly, your question is not about 'making it work'; you want explicit control over the coordinates and the angles such that you can alter them for whatever purpose you had in mind.
Because I am quite vulnerable to nerd sniping, I dusted off my geometric skills and came up with the following scrap of pseudocode (I made this from scratch to make sure I have total control):
Intuition
Pseudocode
theta = starting angle
a = current x-coordinate of ball
b = current y-coordinate of ball
quadrant = quadrant-direction to which ball is moving
/> Determine number between 1 and 360: theta
/> Calculate quadrant
.> 0-90 : quadrant 1: horizontal: 90-a vertical: b alpha: 90 - theta
.> 90-180: quadrant 4: horizontal: 90-a vertical: 30-b alpha: theta - 90
.> 180-270: quadrant 3: horizontal: a vertical: 30-b alpha: 270 - theta
.> 270-360: quadrant 2: horizontal: a vertical: b alpha: theta - 270
/> Calculate distance to side |
/> Calculate distance to top/bottom |
.> to side: n(alpha) = horizontal/cos(alpha)
.> to top/bottom: m(alpha) = vertical /sin(alpha)
/> Determine where ball is going to hit (n = side, m = top/bottom)
.> n >= m : bounces at top/bottom
.> m >= n : bounces at side
.> switch (quadrant)
.> 1 : n = right side m = top
.> 2 : n = left side m = top
.> 3 : n = left side m = bottom
.> 4 : n = right side m = bottom
/> Calculate coordinates of hit
/> Define new angle
// Normally, angle of impact = angle of reflection
// Let's define the angle of impact with respect to the origin (0,0)
.> switch (quadrant)
.> 1 :
.> n >= m (at top/bottom) : x = a + vertical*tan(alpha) y = 0 theta = 180-theta
.> m >= n (at side) : x = 90 y = b - horizontal*tan(alpha) theta = 270+alpha
.> 2 :
.> n >= m (at top/bottom) : x = a - vertical/tan(alpha) y = 0 theta = 270-alpha
.> m >= n (at side) : x = 0 y = b - horizontal*tan(alpha) theta = 90-alpha
.> 3 :
.> n >= m (at top/bottom) : x = a - vertical/tan(alpha) y = 30 theta = 270+alpha
.> m >= n (at side) : x = 0 y = b + horizontal*tan(alpha) theta = 90+alpha
.> 4 :
.> n >= m (at top/bottom) : x = a + vertical/tan(alpha) y = 30 theta = 90-alpha
.> m >= n (at side) : x = 90 y = b + horizontal*tan(alpha) theta = 270-alpha
/> Define new coordinates (for reusage of function)
.> a = x
.> b = y
.> (optional) if you would like the angles to differ, enter extra term here:
.> extra = ...
.> theta = theta + extra
Implementing this code will allow you to work with the easiness of degrees and still be able to determine the coordinates.
It works as follows:
First determine the initial position of the ball (a,b) and it's initial direction (theta)
Now the program will calculate:
Where the ball is going to hit
What the coordinates of the ball at impact are
What the new angle of reflection is (this is the part you want to change)
And then it starts over again to calculate the new hit.
In JavaScript, the code would look like this:
Code
var width = 500;
var height = 200;
var extra = 0;
var a;
var b;
var x;
var y;
var angle;
var n;
var m;
var quadrant;
var horizontal;
var vertical;
var alpha;
var side;
var topbottom;
var sides;
var i = 1;
var txt=document.getElementById("info");
txt.innerHTML="x: "+a+"<br>y: "+b+"<br>angle: "+angle+"<br>quadrant: "+quadrant;
function buttonClick()
{
if (i == 1)
{
a = 75;
b = 75;
//determine first angle randonmly
angle = Math.floor((Math.random()*360)+1);;
} else
{
a = xcoord();
b = ycoord();
}
var oldAngle = angle;
angle = findNewCoordinate(a, b, angle);
sides = hitWhere();
var txt=document.getElementById("info");
txt.innerHTML="x: "+a+"<br>y: "+b+"<br>horizontal: "+horizontal+"<br>vertical: "+vertical+"<br>n: "+n+"<br>m: "+m+"<br>angle: "+oldAngle+"<br>alpha: "+alpha+"<br>quadrant: "+quadrant+"<br>side: "+topbottom+side+"<br>"+sides+"<br>"+i;
i++;
}
function findNewCoordinate(a, b, angle)
{
if (angle >= 0 && angle < 90) { quadrant = 1; horizontal = width-a; vertical = b; alpha = (90 - angle); }
else if (angle >= 90 && angle < 180) { quadrant = 4; horizontal = width-a; vertical = height-b; alpha = (angle-90); }
else if (angle >= 180 && angle < 270) { quadrant = 3; horizontal = a; vertical = height-b; alpha = (270-angle); }
else if (angle >= 270 && angle <= 360) { quadrant = 2; horizontal = a; vertical = b; alpha = (angle-270); }
var cosa = Math.cos(alpha * Math.PI / 180);
var sina = Math.sin(alpha * Math.PI / 180);
var tana = Math.tan(alpha * Math.PI / 180);
var tant = Math.tan(angle * Math.PI / 180);
n = horizontal/cosa;
m = vertical/sina;
switch (quadrant)
{
case 1:
if (m >= n) //hit at side
{
y = b - horizontal*tana;
x = width;
angle = 270+alpha;
} else
{
y = 0;
x = a + vertical*tant;
angle = 180-angle;
}
side = "right side"; topbottom = "top";
break;
case 2:
if (m >= n) //hit at side
{
y = b-horizontal*tana;
x = 0;
angle = 90-alpha;
} else
{
y = 0;
x = a - vertical/tana;
angle = 270-alpha;
}
side = "left side"; topbottom = "top";
break;
case 3: side = "left side"; topbottom = "bottom";
if (m >= n) //hit at side
{
x = 0;
y = b + tana*horizontal;
angle = 90+alpha;
} else
{
y = height;
x = a - vertical/tana;
angle = 270+alpha;
} break;
case 4: side = "right side"; topbottom = "bottom";
if (m >= n) //hit at side
{
y = b+horizontal*tana;
x = width;
angle = 270-alpha;
} else
{
y = height;
x = a + vertical/tana;
angle = 90-alpha;
} break;
}
//add extra degrees to the angle (optional)
angle += extra;
context.beginPath();
context.arc(a, b, 5, 0, Math.PI*2, true);
context.stroke();
context.closePath();
context.fill();
drawLine(a,b,x,y);
return angle;
}
Important
Note that there are many more ways to make a bouncing program. But, because I tackled the question geometrically and without 'shortcuts', the unique characteristics of my program make it very easy for you to alter it to your likings:
You can give an extra angle to the bounce angle easily (use var extra).
You can change the movement of the ball at any time (at bounce, after bounce etc.)
You have explicit access to the coordinates of the ball
All units are conventional (in degrees and coordinates; hence easy to understand and intuitive).
Also note that I did not make the program very concise because this simply wasn't my goal. I wanted to create a bouncing ball program that, although lenghty, is an exact realisation of the geometric intuition behind it.
Demo
You can find a demo of my program in this JSFiddle.
Note that the beginning angle is determined randomly. Hence restarting the program will give a different angle.
Well, that's about it.
Good luck with building the rest of your program!
We know that
distance = average velocity x time //if acceleration is constant
Hence
time = distance / average velocity
Applying this knowledge to a two dimensional field (distance) means we have to do two things:
Apply Pythagoras theorem to find distance to new coordinates
Calculate the 'new' velocity
Before we apply the Pythagoras theorem, we have to know the direction of the move:
Now to find the distance to the new coordinates, we apply pythagoras theorem:
Pseudocode
//Change in coordinates
dx = Math.abs(newX - oldX);
dy = Math.abs(newY - oldY);
//Distance to travel
distance = Math.sqrt( Math.pow(dx, 2) + Math.pow(dy,2) );
//Units per increase
// time = distance / average velocity
velocity = ?;
time = distance / velocity;
//Now to find x+= .. and y+= .. we apply our knowledge of direction
//Together with our knowledge of the time it takes
case north east: x += (dx / time); y += (dy / time);
case south east: x += (dx / time); y -= (dy / time);
case north west: x -= (dx / time); y -= (dy / time);
case south west: x -= (dx / time); y += (dy / time);
Now note that the x and y represent the coordinates of the moving ball.
This means that we must repeat x += .. and y += .. value of time times to reach the new coordinate.
Hence you can do something like:
for (int i = 0; i < time; i ++)
{
switch (direction)
{
case "north east": x += (dx / time); y += (dy / time); break;
case "south east": x += (dx / time); y -= (dy / time); break;
case "north west": x -= (dx / time); y -= (dy / time); break;
case "south west": x -= (dx / time); y += (dy / time); break;
}
}
Also note that velocity = ? is yet to be specified by you. You can let it have a constant velocity (friction = 0), or you can implement some kind of model to mimick friction.
I hope this answers your question.
PS. This answer is actually a derivative of my other answer as I already specify direction and pixel distance in my other answer hence the step to x += .. and y += .. is actually pretty small/ straightforward.
depends on the angle it came in at.. so basically for making the ball bounce off the wall, just inverse the angle it came in at, e.g. if using velocity, if it was 3, then make it -3 when it collides with the wall, therefore the ball will bounce off the wall at the same angle as it was before it collided with the wall...
I hope this helps... Good luck
Ok so granted, its not a bug, but I am confounded by how to get a perfect circle arc between points via Bézier curve.
I need a shape like this:
So I've been calculating the four corner points like this from the center point, radius and angle with the following formula: (x?,y?)=(x+d cos α,y+d sin α), which in my coffeescript looks something like this:
x1 = centerPointX+outerRadius*Math.cos(currentAngle)
y1 = centerPointY+outerRadius*Math.sin(currentAngle)
x2 = centerPointX+innerRadius*Math.cos(currentAngle)
y2 = centerPointY+innerRadius*Math.sin(currentAngle)
x3 = centerPointX+outerRadius*Math.cos(currentAngle2)
y3 = centerPointY+outerRadius*Math.sin(currentAngle2)
x4 = centerPointX+innerRadius*Math.cos(currentAngle2)
y4 = centerPointY+innerRadius*Math.sin(currentAngle2)
How can I take the information I have and result in a path element with perfect circular curves?
(PS I am newish to SVG and if you want to help me out with the proper syntax for d= that would be cool, but I can always just write it myself. The challenge I would like help with is really more to do with Bézier.
UPDATE / SOLUTION
Using the answer below a guidance below is the function I actually used:
annularSector = (centerX,centerY,startAngle,endAngle,innerRadius,outerRadius) ->
startAngle = degreesToRadians startAngle+180
endAngle = degreesToRadians endAngle+180
p = [
[ centerX+innerRadius*Math.cos(startAngle), centerY+innerRadius*Math.sin(startAngle) ]
[ centerX+outerRadius*Math.cos(startAngle), centerY+outerRadius*Math.sin(startAngle) ]
[ centerX+outerRadius*Math.cos(endAngle), centerY+outerRadius*Math.sin(endAngle) ]
[ centerX+innerRadius*Math.cos(endAngle), centerY+innerRadius*Math.sin(endAngle) ]
]
angleDiff = endAngle - startAngle
largeArc = (if (angleDiff % (Math.PI * 2)) > Math.PI then 1 else 0)
commands = []
commands.push "M" + p[0].join()
commands.push "L" + p[1].join()
commands.push "A" + [ outerRadius, outerRadius ].join() + " 0 " + largeArc + " 1 " + p[2].join()
commands.push "L" + p[3].join()
commands.push "A" + [ innerRadius, innerRadius ].join() + " 0 " + largeArc + " 0 " + p[0].join()
commands.push "z"
return commands.join(" ")
Demo: http://phrogz.net/svg/procedural_annular_sector.xhtml
Usage:
annularSector( myPathElement, {
centerX:100, centerY:150,
startDegrees:190, endDegrees:230,
innerRadius:75, outerRadius:100
});
Core function:
// Options:
// - centerX, centerY: coordinates for the center of the circle
// - startDegrees, endDegrees: fill between these angles, clockwise
// - innerRadius, outerRadius: distance from the center
// - thickness: distance between innerRadius and outerRadius
// You should only specify two out of three of the radii and thickness
function annularSector(path,options){
var opts = optionsWithDefaults(options);
var p = [ // points
[opts.cx + opts.r2*Math.cos(opts.startRadians),
opts.cy + opts.r2*Math.sin(opts.startRadians)],
[opts.cx + opts.r2*Math.cos(opts.closeRadians),
opts.cy + opts.r2*Math.sin(opts.closeRadians)],
[opts.cx + opts.r1*Math.cos(opts.closeRadians),
opts.cy + opts.r1*Math.sin(opts.closeRadians)],
[opts.cx + opts.r1*Math.cos(opts.startRadians),
opts.cy + opts.r1*Math.sin(opts.startRadians)],
];
var angleDiff = opts.closeRadians - opts.startRadians;
var largeArc = (angleDiff % (Math.PI*2)) > Math.PI ? 1 : 0;
var cmds = [];
cmds.push("M"+p[0].join()); // Move to P0
cmds.push("A"+[opts.r2,opts.r2,0,largeArc,1,p[1]].join()); // Arc to P1
cmds.push("L"+p[2].join()); // Line to P2
cmds.push("A"+[opts.r1,opts.r1,0,largeArc,0,p[3]].join()); // Arc to P3
cmds.push("z"); // Close path (Line to P0)
path.setAttribute('d',cmds.join(' '));
function optionsWithDefaults(o){
// Create a new object so that we don't mutate the original
var o2 = {
cx : o.centerX || 0,
cy : o.centerY || 0,
startRadians : (o.startDegrees || 0) * Math.PI/180,
closeRadians : (o.endDegrees || 0) * Math.PI/180,
};
var t = o.thickness!==undefined ? o.thickness : 100;
if (o.innerRadius!==undefined) o2.r1 = o.innerRadius;
else if (o.outerRadius!==undefined) o2.r1 = o.outerRadius - t;
else o2.r1 = 200 - t;
if (o.outerRadius!==undefined) o2.r2 = o.outerRadius;
else o2.r2 = o2.r1 + t;
if (o2.r1<0) o2.r1 = 0;
if (o2.r2<0) o2.r2 = 0;
return o2;
}
}