find position of a point with origin, angle and radius - javascript

im stuck with a trigonometry problem in a javascript game im trying to make.
with a origin point(xa,ya) a radius and destination point (ya,yb) I need to find the position of a new point.
//calculate a angle in degree
function angle(xa, ya, xb, yb)
{
var a= Math.atan2(yb - ya, xb - xa);
a*= 180 / Math.PI;
return a;
}
function FindNewPointPosition()
{
//radius origine(xa,xb) destination(ya,yb)
var radius=30;
var a = angle(xa, xb, ya, yb);
newpoint.x = xa + radius * Math.cos(a);
newpoint.y = ya + radius * Math.sin(a);
return newpoint;
}
Imagine a image because I dont have enough reputation to post one :
blue square is the map (5000x5000), black square (500x500) what players see (hud).
Cross(400,400) is the origin and sun(4200,4200) the destination.
The red dot (?,?) indicate to player which direction take to find the sun ..
But sun and cross position can be reverse or in different corner or anywhere !
At the moment the red dot do not do that at all ..
Tks for your help.

Why did you use ATAN2? Change to Math.atan() - you will get angle in var A
Where you have to place your red dot? inside hud?
Corrected code
https://jsfiddle.net/ka9xr07j/embedded/result/
var obj = FindNewPointPosition(400,4200,400,4200); - new position 417. 425

Finally I find a solution without using angle.
function newpointposition(origin, destination)
{
// radius distance between cross and red dot
var r=30;
// calculate a vector
var xDistance = destination.x - origin.x;
var yDistance = destination.y - origin.y;
// normalize vector
var length = Math.sqrt(xDistance * xDistance + yDistance * yDistance);
xDistance /= length;
yDistance /= length;
// add the radius
xDistance = xDistance * r;
yDistance = yDistance * r;
var newpoint = { x: 0, y: 0 };
newpoint.x = origin.x + xDistance;
newpoint.y = origin.y + yDistance;
return newpoint;
}
var radar = newpointposition({
x: 500,
y: 800
}, {
x: 3600,
y: 2850
});
alert(radar.x + ' ' + radar.y);
ty Trike, using jsfiddle really help me.

Related

Converting 360 degree view to equirectangular in node js?

I have been trying to convert the 360 degree camera, single fish eye image, to equirectangular viewer in node js for the past two days. In stackoverflow, the same question is asked and answered in pseudo code. I have been trying to convert pseudo code to node js and cleared some errors. Now the project runs without error but the output image is blank.
From that pseudo, I dont know the polar_w, polar_h and geo_w, geo_h, geo and polar value, so, it gave static value to show the output. Here is a link which i followed to convert pseudo code to node js.
How to convert spherical coordinates to equirectangular projection coordinates?.
Here is the code I tried for converting spherical image to equirectangular viewer:
exports.sphereImage=(request, response)=>{
var Jimp = require('jimp');
// Photo resolution
var img_w_px = 1280;
var img_h_px = 720;
var polar_w = 1280;
var polar_h = 720;
var geo_w = 1280;
var geo_h = 720;
var img_h_deg = 70;
var img_w_deg = 30;
// Camera field-of-view angles
var img_ha_deg = 70;
var img_va_deg = 40;
// Camera rotation angles
var hcam_deg = 230;
var vcam_deg = 60;
// Camera rotation angles in radians
var hcam_rad = hcam_deg/180.0*Math.PI;
var vcam_rad = vcam_rad/180.0*Math.PI;
// Rotation around y-axis for vertical rotation of camera
var rot_y = [
[Math.cos(vcam_rad), 0, Math.sin(vcam_rad)],
[0, 1, 0],
[-Math.sin(vcam_rad), 0, Math.cos(vcam_rad)]
];
// Rotation around z-axis for horizontal rotation of camera
var rot_z = [
[Math.cos(hcam_rad), -Math.sin(hcam_rad), 0],
[Math.sin(hcam_rad), Math.cos(hcam_rad), 0],
[0, 0, 1]
];
Jimp.read('./public/images/4-18-2-42.jpg', (err, lenna) => {
polar = new Jimp(img_w_px, img_h_px);
geo = new Jimp(img_w_px, img_h_px);
for(var i=0; i<img_h_px; ++i)
{
for(var j=0; j<img_w_px; ++j)
{
// var p = img.getPixelAt(i, j);
var p = lenna.getPixelColor(i, j)
// var p = getPixels(img, { x: i, y: j })
// Calculate relative position to center in degrees
var p_theta = (j - img_w_px / 2.0) / img_w_px * img_w_deg / 180.0 * Math.PI;
var p_phi = -(i - img_h_px / 2.0) / img_h_px * img_h_deg / 180.0 *Math. PI;
// Transform into cartesian coordinates
var p_x = Math.cos(p_phi) * Math.cos(p_theta);
var p_y = Math.cos(p_phi) * Math.sin(p_theta);
var p_z = Math.sin(p_phi);
var p0 = {p_x, p_y, p_z};
// Apply rotation matrices (note, z-axis is the vertical one)
// First vertically
var p1 = rot_y[1][2][3] * p0;
var p2 = rot_z[1][2][3] * p1;
// Transform back into spherical coordinates
var theta = Math.atan2(p2[1], p2[0]);
var phi = Math.asin(p2[2]);
// Retrieve longitude,latitude
var longitude = theta / Math.PI * 180.0;
var latitude = phi / Math.PI * 180.0;
// Now we can use longitude,latitude coordinates in many different
projections, such as:
// Polar projection
{
var polar_x_px = (0.5*Math.PI + phi)*0.5 * Math.cos(theta)
/Math.PI*180.0 * polar_w;
var polar_y_px = (0.5*Math.PI + phi)*0.5 * Math.sin(theta)
/Math.PI*180.0 * polar_h;
polar.setPixelColor(p, polar_x_px, polar_y_px);
}
// Geographical (=equirectangular) projection
{
var geo_x_px = (longitude + 180) * geo_w;
var geo_y_px = (latitude + 90) * geo_h;
// geo.setPixel(geo_x_px, geo_y_px, p.getRGB());
geo.setPixelColor(p, geo_x_px, geo_y_px);
}
// ...
}
}
geo.write('./public/images/4-18-2-42-00001.jpg');
polar.write('./public/images/4-18-2-42-00002.jpg');
});
}
And tried another method by slicing image into four parts to detect car. Sliced image into four parts using image-slice module and to read and write jimp module is used. But unfortunately cars not detected properly.
Here is the code i used for slicing image:
exports.sliceImage=(request, response)=>{
var imageToSlices = require('image-to-slices');
var lineXArray = [540, 540];
var lineYArray = [960, 960];
var source = './public/images/4-18-2-42.jpg'; // width: 300, height: 300
imageToSlices(source, lineXArray, lineYArray, {
saveToDir: './public/images/',
clipperOptions: {
canvas: require('canvas')
}
}, function() {
console.log('the source image has been sliced into 9 sections!');
});
}//sliceImage
And for detect car from image i used opencv4nodejs. Cars are not detected properly. here is the code i used for detect car:
function runDetectCarExample(img=null){
if(img==null){
img = cv.imread('./public/images/section-1.jpg');
}else
{
img=cv.imread(img);
}
const minConfidence = 0.06;
const predictions = classifyImg(img).filter(res => res.confidence > minConfidence && res.className=='car');
const drawClassDetections = makeDrawClassDetections(predictions);
const getRandomColor = () => new cv.Vec(Math.random() * 255, Math.random() * 255, 255);
drawClassDetections(img, 'car', getRandomColor);
cv.imwrite('./public/images/section-'+Math.random()+'.jpg', img);
var name="distanceFromCamera";
var focalLen= 1.6 ;//Focal length in mm
var realObjHeight=254 ;//Real Height of Object in mm
var cameraFrameHeight=960;//Height of Image in pxl
var imgHeight=960;//Image Height in pxl
var sensorHeight=10;//Sensor height in mm
var R = 6378.1 //#Radius of the Earth
var brng = 1.57 //#Bearing is 90 degrees converted to radians.
var hc=(200/100);//Camera height in m
predictions
.forEach((data)=> {
// imgHeight=img.rows;//Image Height in pxl
// realObjHeight=data.rect.height;
// data.rect[name]=((focalLen)*(realObjHeight)*
(cameraFrameHeight))/((imgHeight)*(sensorHeight));
var dc=(((data.rect.width * focalLen) / img.cols)*2.54)*100; // meters
console.log(Math.floor(parseInt(data.rect.width)));
// var dc=((Math.floor(parseInt(data.rect.width)* 0.264583) * focalLen) / img.cols); // mm
var lat1=13.0002855;//13.000356;
var lon1=80.2046441;//80.204632;
// Gate 13.0002855,80.2046441
// Brazil Polsec : -19.860566, -43.969436
// var d=Math.sqrt((dc*dc)+(hc*hc));
// d=(data.rect[name])/1000;
data.rect[name]=d=dc/1000;
lat1 =toRadians(lat1);
lon1 = toRadians(lon1);
brng =toRadians(90);
// lat2 = Math.asin( Math.sin(lat1)*Math.cos(d/R) +
// Math.cos(lat1)*Math.sin(d/R)*Math.cos(brng));
// lon2 = lon1 +
Math.atan2(Math.sin(brng)*Math.sin(d/R)*Math.cos(lat1),
// Math.cos(d/R)-Math.sin(lat1)*Math.sin(lat2));
var lat2 = Math.asin(Math.sin(lat1) * Math.cos(d/6371) +
Math.cos(lat1) * Math.sin(d/6371) * Math.cos(brng));
var lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(d/6371) * Math.cos(lat1),
Math.cos(d/6371) - Math.sin(lat1) * Math.sin(lat2));
lat2 = toDegrees(lat2);
lon2 = toDegrees(lon2);
data.rect['latLong']=lat2+','+lon2;
// console.log(brng);
});
response.send(predictions);
cv.imshowWait('img', img);
};
here is the fish eye image which need to be converted to equirectangular.
Any help much appreciated pls....
You are asking how to convert a 360deg fish-eye projection to an equirectangular projection.
In order to do this, for every pixel on the fish-eye image you need to know where to place in onto the output image.
Your input image is 1920x1080, let us assume you want to output it to an equirectangular projection of the same size.
The input circle mapping is defined as:
cx = 960; // center of circle on X-axis
cy = 540; // center of circle on Y-axis
radius = 540; // radius of circle
If you have a pixel at (x,y) in the input image, then we can calculate the spherical coordinates using:
dx = (x - cx) * 1.0 / radius;
dy = (y - cy) * 1.0 / radius;
theta_deg = atan2(dy, dx) / MATH_PI * 180;
phi_deg = acos(sqrt(dx*dx + dy*dy)) / MATH_PI * 180;
outputx = (theta_deg + 180) / 360.0 * outputwidth_px;
outputy = (phi_deg + 90) / 180.0 * outputheight_px;
So there we translated (x,y) from the fish-eye image to the (outputx,outputy) in the equirectangular image. In order to not leave the implementation as the dreaded "exercise to the reader", here is some sample Javascript-code using the Jimp-library as used by the OP:
var jimp = require('jimp');
var inputfile = 'input.png';
jimp.read(inputfile, function(err, inputimage)
{
var cx = 960;
var cy = 540;
var radius = 540;
var inputwidth = 1920;
var inputheight = 1080;
var outputwidth = 1920;
var outputheight = 1080;
new jimp(outputwidth, outputheight, 0x000000ff, function(err, outputimage)
{
for(var y=0;y<inputheight;++y)
{
for(var x=0;x<inputwidth;++x)
{
var color = inputimage.getPixelColor(x, y);
var dx = (x - cx) * 1.0 / radius;
var dy = (y - cy) * 1.0 / radius;
var theta_deg = Math.atan2(dy, dx) / Math.PI * 180;
var phi_deg = Math.acos(Math.sqrt(dx*dx + dy*dy)) / Math.PI * 180;
var outputx = Math.round((theta_deg + 180) / 360.0 * outputwidth);
var outputy = Math.round((phi_deg + 90) / 180.0 * outputheight);
outputimage.setPixelColor(color, outputx, outputy);
}
}
outputimage.write('output.png');
});
});
Note that you will still need to do blending of the pixel with neighbouring pixels (for the same reason as when you're resizing the image).
Additionally, in your case, you only have half of the sphere (you can't see the sun in the sky). So you would need to use var outputy = Math.round(phi_deg / 90.0 * outputheight). In order to keep the right aspect ratio, you might want to change the height to 540.
Also note that the given implementation may not be efficient at all, it's better to use the buffer directly.
Anyway, without blending I came up with the result as demonstrated here:
So in order to do blending, you could use the simplest method which is the nearest neighbour approach. In that case, you should invert the formulas in the above example. Instead of moving the pixels from the input image to the right place in the output image, you can go through every pixel in the output image and ask which input pixel we can use for that. This will avoid the black pixels, but may still show artifacts:
var jimp = require('jimp');
var inputfile = 'input.png';
jimp.read(inputfile, function(err, inputimage)
{
var cx = 960;
var cy = 540;
var radius = 540;
var inputwidth = 1920;
var inputheight = 1080;
var outputwidth = 1920;
var outputheight = 1080/2;
var blendmap = {};
new jimp(outputwidth, outputheight, 0x000000ff, function(err, outputimage)
{
for(var y=0;y<outputheight;++y)
{
for(var x=0;x<outputwidth;++x)
{
var theta_deg = 360 - x * 360.0 / outputwidth - 180;
var phi_deg = 90 - y * 90.0 / outputheight;
var r = Math.sin(phi_deg * Math.PI / 180)
var dx = Math.cos(theta_deg * Math.PI / 180) * r;
var dy = Math.sin(theta_deg * Math.PI / 180) * r;
var inputx = Math.round(dx * radius + cx);
var inputy = Math.round(dy * radius + cy);
outputimage.setPixelColor(inputimage.getPixelColor(inputx, inputy), x, y);
}
}
outputimage.write('output.png');
});
});
For reference, in order to convert between Cartesian and Spherical coordinate systems. These are the formulas (taken from here). Note that the z is in your case just 1, a so-called "unit" sphere, so you can just leave it out of the equations. You should also understand that since the camera is actually taking a picture in three dimensions, you also need formulas to work in three dimensions.
Here is the generated output image:
Since I don't see your original input image in your question anymore, in order for anyone to test the code from this answer, you can use the following image:
Run the code with:
mkdir /tmp/test
cd /tmp/test
npm install --permanent jimp
cat <<EOF >/tmp/test/main.js
... paste the javascript code from above ...
EOF
curl https://i.stack.imgur.com/0zWt6.png > input.png
node main.js
Note: In order to further improve the blending, you should remove the Math.round. So for instance, if you need to grab a pixel at x is 0.75, and the pixel on the left at x = 0 is white, and the pixel on the right at x = 1 is black. Then you want to mix both colors into a dark grey color (using ratio 0.75). You would have to do this for both dimensions simultaneously, if you want a nice result. But this should really be in a new question imho.

Got two circles and a point. How to get get circle along it

I got a mathematical problem for designing icons, and I am looking for a JavaScript solution.
I take 2 circles, each a different radius and position, and a point. I want to know the smallest circle that contains the two circles and goes along the point.
The red cirle is the circle I want to know
In want a function like this:
function calculate(c1x, c1y, c1r, c2x, c2y, c2r, px, py){
//Calculation
return {
x: outX,
y: outY,
r: outR
};
}
There is a lot of degenerate cases: one circle is inside another and so on.
For the most general case - big circle touches both small ones and goes through the point - we need to solve a system of three equations for three unknowns cx, cy, R:
(px - cx)^2 + (py - cy)^2 = R^2
(cx1 - cx)^2 + (cy1 - cy)^2 = (R-r1)^2
(cx2 - cx)^2 + (cy2 - cy)^2 = (R-r2)^2
You can try to solve it with paper and pencil or use some program for symbolic calculations like Maple, Mathematica, Matlab, MathCad etc.
P.S. to slightly simplify calculations, subtract point coordinates from all values, solve system
cx^2 + cy^2 = R^2
(cx1' - cx)^2 + (cy1' - cy)^2 = (R-r1)^2
(cx2' - cx)^2 + (cy2' - cy)^2 = (R-r2)^2
and add px, py to the result
function distance(x1, y1, x2, y2){
return Math.sqrt(Math.pow(x1-x2, 2) + Math.pow(y1-y2, 2));
}
function calculate(c1x, c1y, c1r, c2x, c2y, c2r, px, py){
var dpc1 = distance(c1x, c1y, px, py) + c1r;
var dpc2 = distance(c2x, c2y, px, py) + c2r;
var dc1c2 = distance(c1x, c1y, c2x, c2y);
var theta = Math.acos((dpc1*dpc1 + dpc2*dpc2 - dc1c2*dc1c2)/(2*dpc1*dpc2))/2;
var outR = Math.max(dpc1, dpc2);
var outX = px + outR * Math.cos(theta);
var outY = py + outR * Math.sin(theta);
return {
x: outX,
y: outY,
r: outR
};
}

Detect mouse is near circle edge

I have a function which gets the mouse position in world space, then checks to see if the mouse is over or near to the circle's line.
The added complication how ever is the circle is transformed at an angle so it's more of an ellipse. I can't see to get the code to detect that the mouse is near the border of circle and am unsure where I am going wrong.
This is my code:
function check(evt){
var x = (evt.offsetX - element.width/2) + camera.x; // world space
var y = (evt.offsetY - element.height/2) + camera.y; // world space
var threshold = 20/scale; //margin to edge of circle
for(var i = 0; i < obj.length;i++){
// var mainAngle is related to the transform
var x1 = Math.pow((x - obj[i].originX), 2) / Math.pow((obj[i].radius + threshold) * 1,2);
var y1 = Math.pow((y - obj[i].originY),2) / Math.pow((obj[i].radius + threshold) * mainAngle,2);
var x0 = Math.pow((x - obj[i].originX),2) / Math.pow((obj[i].radius - threshold) * 1, 2);
var y0 = Math.pow((y - obj[i].originY),2) / Math.pow((obj[i].radius - threshold) * mainAngle, 2);
if(x1 + y1 <= 1 && x0 + y0 >= 1){
output.innerHTML += '<br/>Over';
return false;
}
}
output.innerHTML += '<br/>out';
}
To understand it better, I have a fiddle here: http://jsfiddle.net/nczbmbxm/ you can move the mouse over the circle, it should say "Over" when you are within the threshold of being near the circle's perimeter. Currently it does not seem to work. And I can't work out what the maths needs to be check for this.
There is a typo on line 34 with orignX
var x1 = Math.pow((x - obj[i].orignX), 2) / Math.pow((obj[i].radius + threshold) * 1,2);
should be
var x1 = Math.pow((x - obj[i].originX), 2) / Math.pow((obj[i].radius + threshold) * 1,2);
now you're good to go!
EDIT: In regards to the scaling of the image and further rotation of the circle, I would set up variables for rotation about the x-axis and y-axis, such as
var xAngle;
var yAngle;
then as an ellipse can be written in the form
x^2 / a^2 + y^2 / b^2 = 1
such as in Euclidean Geometry,
then the semi-major and semi-minor axes would be determined by the rotation angles. If radius is the circles actual radius. then
var semiMajor = radius * cos( xAngle );
var semiMinor = radius;
or
var semiMajor = radius;
var semiMinor = radius * cos( yAngle );
you would still need to do some more transformations if you wanted an x and y angle.
so if (xMouseC, yMouseC) are the mouse coordinates relative to the circles centre, all you must do is check if that point satisfies the equation of the ellipse to within a certain tolerance, i.e. plug in
a = semiMajor;
b = semiMinor;
x = xMouseC;
y = yMouseC;
and see if it is sufficiently close to 1.
Hope that helps!

Positioning image on Google Maps with rotate / scale / translate

I'm developing a user-interface for positioning an image on a google map.
I started from : http://overlay-tiler.googlecode.com/svn/trunk/upload.html which is pretty close to what I want.
But instead of 3 contact points I want a rotate tool, a scale tool and a translate tool (the later exists).
I tried to add a rotate tool but it doesn't work as I expected :
I put a dot on the left bottom corner that control the rotation (around the center of the image). The mouse drag the control dot and I calculate the 3 others points.
My code is based on the mover object but I changed the onMouseMove function :
overlaytiler.Rotater.prototype.rotateDot_ = function(dot, theta, origin) {
dot.x = ((dot.x - origin.x) * Math.cos(theta) - (dot.y - origin.y) * Math.sin(theta)) + origin.x;
dot.y = ((dot.x - origin.x) * Math.sin(theta) + (dot.y - origin.y) * Math.cos(theta)) + origin.y;
dot.render();
};
overlaytiler.Rotater.prototype.onMouseMove_ = function(e) {
var dots = this.controlDots_;
var center = overlaytiler.Rotater.prototype.getCenter_(dots);
// Diagonal length
var r = Math.sqrt(Math.pow(this.x - center.x, 2) + Math.pow(this.y - center.y, 2));
var old = {
x: this.x,
y: this.y
};
// Real position
var newPos = {
x: this.x + e.clientX - this.cx,
y: this.y + e.clientY - this.cy
}
var newR = Math.sqrt(Math.pow(newPos.x - center.x, 2) + Math.pow(newPos.y - center.y, 2));
var theta = - Math.acos((2 * r * r - (Math.pow(newPos.x - old.x, 2) + Math.pow(newPos.y - old.y, 2))) / (2 * r * r));
// Fixed distance position
this.x = (newPos.x - center.x) * (r / newR) + center.x;
this.y = (newPos.y - center.y) * (r / newR) + center.y;
dots[1].x = center.x + (center.x - this.x);
dots[1].y = center.y + (center.y - this.y);
dots[1].render();
overlaytiler.Rotater.prototype.rotateDot_(dots[2], theta, center);
overlaytiler.Rotater.prototype.rotateDot_(dots[0], theta, center);
// Render
this.render();
this.cx = e.clientX;
this.cy = e.clientY;
};
Unfortunately there is a problem with precision and angle sign.
http://jsbin.com/iQEbIzo/4/
After a few rotations the image is highly distorted and rotation is supported only in one direction.
I wonder how I can achieve a great precision and without any distortion.
Maybe my approach is useless here (try to move the corners at the right coordinates), I tried to rotate the image with the canvas but my attempts were unsuccessful.
Edit : Full working version : http://jsbin.com/iQEbIzo/7/
Here is my version of it. #efux and #Ben answers are far more complete and well designed however the maps don't scale in/out when you zoom in/out. Overlays very likely need to do this since they are used to put a "second map" or photograph over the existing map.
Here is the JSFiddle: http://jsfiddle.net/adelriosantiago/3tzzwmsx/4/
The code that does the drawing is the following:
DebugOverlay.prototype.draw = function() {
var overlayProjection = this.getProjection();
var sw = overlayProjection.fromLatLngToDivPixel(this.bounds_.getSouthWest());
var ne = overlayProjection.fromLatLngToDivPixel(this.bounds_.getNorthEast());
var div = this.div_;
div.style.left = sw.x + 'px';
div.style.top = ne.y + 'px';
div.style.width = (ne.x - sw.x) + 'px';
div.style.height = (sw.y - ne.y) + 'px';
div.style.transform = 'rotate(' + rot + 'deg)';
};
For sure this code could be implemented on efux and Ben code if needed but I haven't tried yet.
Note that the box marker does not updates its position when the rotation marker moves...
rotation is supported only in one direction
This is due to how you calculate the angle between two vectors.
It always gives you the same vector no matter if the mouse is right of the dot or not. I've found a solution in a german math board (unfortunately I cant access the site without using the cache of Google : cached version).
Note that in this example the angle α is on both sides the same and not as you would expect -α in the second one. To find out if the vector a is always on "the same side" of vector b you can use this formula.
ax*by - ay*bx
This is either positive or negative. You you simply can change the sign of the angle to α * -1.
I modified some parts of your code.
overlaytiler.Rotater.prototype.rotateDot_ = function(dot, theta, origin) {
// translate to origin
dot.x -= origin.x ;
dot.y -= origin.y ;
// perform rotation
newPos = {
x: dot.x*Math.cos(theta) - dot.y*Math.sin(theta),
y: dot.x*Math.sin(theta) + dot.y*Math.cos(theta)
} ;
dot.x = newPos.x ;
dot.y = newPos.y ;
// translate back to center
dot.x += origin.x ;
dot.y += origin.y ;
dot.render();
};
If you want to know, how I rotate the points please reference to this site and this one.
overlaytiler.Rotater.prototype.onMouseMove_ = function(e) {
var dots = this.controlDots_;
var center = overlaytiler.Rotater.prototype.getCenter_(dots);
// get the location of the canvas relative to the screen
var rect = new Array() ;
rect[0] = dots[0].canvas_.getBoundingClientRect() ;
rect[1] = dots[1].canvas_.getBoundingClientRect() ;
rect[2] = dots[2].canvas_.getBoundingClientRect() ;
// calculate the relative center of the image
var relCenter = {
x: (rect[0].left + rect[2].left) / 2,
y: (rect[0].top + rect[2].top) / 2
} ;
// calculate a vector from the center to the bottom left of the image
dotCorner = {
x: rect[1].left - (rect[1].left - relCenter.x) * 2 - relCenter.x,
y: rect[1].top - (rect[1].top - relCenter.y) * 2 - relCenter.y
} ;
// calculate a vector from the center to the mouse position
mousePos = {
x: e.clientX - relCenter.x,
y: e.clientY - relCenter.y
} ;
// calculate the angle between the two vector
theta = calculateAngle(dotCorner, mousePos) ;
// is the mouse-vector left of the dot-vector -> refer to the german math board
if(dotCorner.y*mousePos.x - dotCorner.x*mousePos.y > 0) {
theta *= -1 ;
}
// calculate new position of the dots and render them
overlaytiler.Rotater.prototype.rotateDot_(dots[2], theta, center);
overlaytiler.Rotater.prototype.rotateDot_(dots[1], theta, center);
overlaytiler.Rotater.prototype.rotateDot_(dots[0], theta, center);
// Render
this.render();
this.cx = e.clientX;
this.cy = e.clientY;
};
You can see that I wrote some function for vector calculations (just to make the code more readable):
function calculateScalarProduct(v1,v2)
{
return (v1.x * v2.x + v1.y * v2.y) ;
}
function calculateLength(v1)
{
return (Math.sqrt(v1.x*v1.x + v1.y*v1.y)) ;
}
function calculateAngle(v1, v2)
{
return (Math.acos(calculateScalarProduct(v1,v2) / (calculateLength(v1)*calculateLength(v2)))) ;
}
This is my working solution. Comment if you don't understand something, so I can make my answer more comprehensive.
Working example: JSBin
Wow, this was a tough one.

Calculate x, y coordinates of rotated line segments to draw on a Canvas

This is less an HTML, CANVAS question and more of a general math question. I posted it here because it's prototyped using CANVAS and is still kind of a general programming question that I thought someone could answer. Here is the basic idea: I want to draw a line 10 pixels thick, but I don't want to use the standard lineTo and set a stroke width. I want to actually draw the border of the line using beginPath and lineTo. The reason being is this is actually for AS3 project and using this method allows us to have a line stroke and fill. So rotating the canvas and things of that nature are out of the question. I just need to figure out how to calculate the proper x, y coordinates for the line.
In the code below is the coordinates for the top of a line. I basically want to take this coordinates, add 10 to the y axis for each one and that will give me the return coordinates for the bottom of the line. Of course, each segment of the line is rotated, so calculating the coordinates for the bottom of the line has proved tricky. I'm hoping someone can help.
Once you run the example code, the issue should be obvious. The line isn't drawn properly. For relatively mild rotations of line segments it seems to work, but as the angle of rotation gets more severe the x, y coordinates are calculated incorrectly.
<!doctype html>
<html>
<body>
<canvas id="canvas" width="800" height="600">
</canvas>
<script type="text/javascript">
var coords = [
{x:78,y:183},
{x:130,y:183},
{x:237,y:212},
{x:450,y:213},
{x:517,y:25},
{x:664,y:212},
{x:716,y:212}
];
var coordsBck = [];
for( i = 0; i < coords.length; i++ ) {
var c1, c2, r;
c1 = coords[i];
if( i < coords.length - 1 ) {
c2 = coords[i + 1];
r = Math.atan2((c2.y - c1.y),(c2.x - c1.x));
console.log( c1.x, c1.y, c2.x, c2.y, ( r * 180/Math.PI ));
}
else
{
r = 00;
}
var d = r * 180/Math.PI;
var cos = Math.cos( r );
var sin = Math.sin( r );
var x = cos * 0 - sin * 10;
var y = sin * 0 + cos * 10;
coordsBck.push({x: c1.x + x, y: c1.y + y});
}
while(coordsBck.length > 0 )
{
coords.push( coordsBck.pop() );
}
var ctx = document.getElementById("canvas").getContext("2d");
ctx.beginPath();
for( i = 0; i < coords.length; i++ ) {
var line = coords[i];
console.log( i, line.x, line.y );
if( i == 0 )
{
ctx.moveTo( line.x, line.y );
}
else
{
ctx.lineTo( line.x, line.y );
}
}
ctx.fill();
function t(o) {
return "x: " + o.x + ", y: " + o.y;
}
</script>
</body>
</html>
If you don't need end caps. http://jsfiddle.net/xA6kB/1/
<doctype !html>
<html>
<body>
<canvas id="canvas" width="800" height="600">
</canvas>
<script type="text/javascript">
var points =
[
{x: 78, y: 183},
{x: 130, y: 183},
{x: 237, y: 212},
{x: 450, y: 213},
{x: 517, y: 25},
{x: 664, y: 212},
{x: 716, y: 212}
];
var quads = [];
var lineThickness = 10;
// Remove the -1 to create a loop
for (var i = 0; i < points.length - 1; ++i)
{
var point = points[i];
var nextPoint = points[(i + 1) % points.length];
// Right hand normal with x positive to the right and y positive down
var normal = {x: -(nextPoint.y - point.y), y: nextPoint.x - point.x};
// Normalize normal
var normalLength = Math.sqrt(normal.x * normal.x + normal.y * normal.y);
normal.x /= normalLength;
normal.y /= normalLength;
// A quad has 4 points
quads.push({x: point.x - lineThickness / 2 * normal.x, y: point.y - lineThickness / 2 * normal.y});
quads.push({x: nextPoint.x - lineThickness / 2 * normal.x, y: nextPoint.y - lineThickness / 2 * normal.y});
quads.push({x: nextPoint.x + lineThickness / 2 * normal.x, y: nextPoint.y + lineThickness / 2 * normal.y});
quads.push({x: point.x + lineThickness / 2 * normal.x, y: point.y + lineThickness / 2 * normal.y});
}
var context = document.getElementById("canvas").getContext("2d");
context.beginPath();
for(var i = 0; i < quads.length; i += 4)
{
context.moveTo(quads[i].x, quads[i].y);
context.lineTo(quads[i + 1].x, quads[i + 1].y);
context.lineTo(quads[i + 2].x, quads[i + 2].y);
context.lineTo(quads[i + 3].x, quads[i + 3].y);
}
context.fill();
</script>
</body>
</html>
When i have such issues, i usually compute normalized vectors, and 'play' with them.
Say you draw a line from A to B, compute AB vector (ABx=Bx-Ax ; ABy=By-Ay) then i normalize it (...) to get ABN.
Then i compute ABNR, the 90 degree rotation of ABN ( ABNR.x = -ABN.y ; ABNR.y = ABN.x )
Then in your example, say A' and A'' are the points surrounding A, we have the simple A'=A+5*ABNR and A''= A-5*ABNR , and as well B'=B+5*ABNR and B''=B-5*ABNR.
The rectangle you want to draw is the A'A''B''B' rectangle.
There must be much more optimized way to do this (after all, one can draw a line with only additions), this one is simple and works, it depends on your speed rquirements. You may also optimze the code once you have your formulas working.
I ended up sorting this out after Vincent and Sirisian's answers gave me some ideas. I really appreciate the input guys! Basically, both of those answers made me realized I should be treating the segments like rectangles and that I needed some additional coordinates. I put together a jsfiddle if anyone was in interested.
http://jsfiddle.net/WesleyJohnson/sAaM9/1/

Categories

Resources