Get heading and pitch from pixels on Street View - javascript
I think this is the best place for this question.
I am trying to get the heading and pitch of any clicked point on an embedded Google Street View.
The only pieces of information I know and can get are:
The field of view (degrees)
The center point's heading and pitch (in degrees) and x and y pixel position
The x and y pixel position of the mouse click
I've included here a screenshot with simplified measurements as an example:
I initally just thought you could divide the field of view by the pixel width to get degrees per pixel, but it's more complicated, I think it has to do with projecting onto the inside of a sphere, where the camera is at the centre of the sphere?
Bonus if you can tell me how to do the reverse too...
Clarification:
The goal is not to move the view to the clicked point, but give information about a clicked point. The degrees per pixel method doesn't work because the viewport is not linear.
THe values I have here are just examples, but the field of view can be bigger or smaller (from [0.something, 180], and the center is not fixed, it could be any value in the range [0, 360] and vertically [-90, 90]. The point [0, 0] is simply the heading (horizontal degrees) and pitch (vertical degrees) of the photogapher when the photo was taken, and doesn't really represent anything.
TL;DR: JavaScript code for a proof of concept included at the end of this answer.
The heading and pitch parameters h0 and p0 of the panorama image corresponds to a direction. By using the focal length f of the camera to scale this direction vector, one can get the 3D coordinates (x0, y0, z0) of the viewport center at (u0, v0):
x0 = f * cos( p0 ) * sin( h0 )
y0 = f * cos( p0 ) * cos( h0 )
z0 = f * sin( p0 )
The goal is now to find the 3D coordinates of the point at to some given pixel coordinates (u, v) in the image. First, map these pixel coordinates to pixel offsets (du, dv) (to the right and to the top) from the viewport center:
du = u - u0 = u - w / 2
dv = v0 - v = h / 2 - v
Then a local orthonormal 2D basis of the viewport in 3D has to be found. The unit vector (ux, uy, uz) supports the x-axis (to the right along the direction of increasing headings) and the vector (vx, vy, vz) supports the y-axis (to the top along the direction of increasing pitches) of the image. Once these two vectors are determined, the 3D coordinates of the point on the viewport matching the (du, dv) pixel offset in the viewport are simply:
x = x0 + du * ux + dv * vx
y = y0 + du * uy + dv * vy
z = z0 + du * uz + dv * vz
And the heading and pitch parameters h and p for this point are then:
R = sqrt( x * x + y * y + z * z )
h = atan2( x, y )
p = asin( z / R )
Finally to get the two unit vectors (ux, uy, uz) and (vx, vy, vz), compute the derivatives of the spherical coordinates by the heading and pitch parameters at (p0, h0), and one should get:
vx = -sin( p0 ) * sin ( h0 )
vy = -sin( p0 ) * cos ( h0 )
vz = cos( p0 )
ux = sgn( cos ( p0 ) ) * cos( h0 )
uy = -sgn( cos ( p0 ) ) * sin( h0 )
uz = 0
where sgn( a ) is +1 if a >= 0 else -1.
Complements:
The focal length is derived from the horizontal field of view and the width of the image:
f = (w / 2) / Math.tan(fov / 2)
The reverse mapping from heading and pitch parameters to pixel coordinates can be done similarly:
Find the 3D coordinates (x, y, z) of the direction of the ray corresponding to the specified heading and pitch parameters,
Find the 3D coordinates (x0, y0, z0) of the direction of the ray corresponding to the viewport center (an associated image plane is located at (x0, y0, z0) with an (x0, y0, z0) normal),
Intersect the ray for the specified heading and pitch parameters with the image plane, this gives the 3D offset from the viewport center,
Project this 3D offset on the local basis, getting the 2D offsets du and dv
Map du and dv to absolute pixel coordinates.
In practice, this approach seems to work similarly well on both square and rectangular viewports.
Proof of concept code (call the onLoad() function on a web page containing a sized canvas element with a "panorama" id)
'use strict';
var viewer;
function onClick(e) {
viewer.click(e);
}
function onLoad() {
var element = document.getElementById("panorama");
viewer = new PanoramaViewer(element);
viewer.update();
}
function PanoramaViewer(element) {
this.element = element;
this.width = element.width;
this.height = element.height;
this.pitch = 0;
this.heading = 0;
element.addEventListener("click", onClick, false);
}
PanoramaViewer.FOV = 90;
PanoramaViewer.prototype.makeUrl = function() {
var fov = PanoramaViewer.FOV;
return "https://maps.googleapis.com/maps/api/streetview?location=40.457375,-80.009353&size=" + this.width + "x" + this.height + "&fov=" + fov + "&heading=" + this.heading + "&pitch=" + this.pitch;
}
PanoramaViewer.prototype.update = function() {
var element = this.element;
element.style.backgroundImage = "url(" + this.makeUrl() + ")";
var width = this.width;
var height = this.height;
var context = element.getContext('2d');
context.strokeStyle = '#FFFF00';
context.beginPath();
context.moveTo(0, height / 2);
context.lineTo(width, height / 2);
context.stroke();
context.beginPath();
context.moveTo(width / 2, 0);
context.lineTo(width / 2, height);
context.stroke();
}
function sgn(x) {
return x >= 0 ? 1 : -1;
}
PanoramaViewer.prototype.unmap = function(heading, pitch) {
var PI = Math.PI
var cos = Math.cos;
var sin = Math.sin;
var tan = Math.tan;
var fov = PanoramaViewer.FOV * PI / 180.0;
var width = this.width;
var height = this.height;
var f = 0.5 * width / tan(0.5 * fov);
var h = heading * PI / 180.0;
var p = pitch * PI / 180.0;
var x = f * cos(p) * sin(h);
var y = f * cos(p) * cos(h);
var z = f * sin(p);
var h0 = this.heading * PI / 180.0;
var p0 = this.pitch * PI / 180.0;
var x0 = f * cos(p0) * sin(h0);
var y0 = f * cos(p0) * cos(h0);
var z0 = f * sin(p0);
//
// Intersect the ray O, v = (x, y, z)
// with the plane at M0 of normal n = (x0, y0, z0)
//
// n . (O + t v - M0) = 0
// t n . v = n . M0 = f^2
//
var t = f * f / (x0 * x + y0 * y + z0 * z);
var ux = sgn(cos(p0)) * cos(h0);
var uy = -sgn(cos(p0)) * sin(h0);
var uz = 0;
var vx = -sin(p0) * sin(h0);
var vy = -sin(p0) * cos(h0);
var vz = cos(p0);
var x1 = t * x;
var y1 = t * y;
var z1 = t * z;
var dx10 = x1 - x0;
var dy10 = y1 - y0;
var dz10 = z1 - z0;
// Project on the local basis (u, v) at M0
var du = ux * dx10 + uy * dy10 + uz * dz10;
var dv = vx * dx10 + vy * dy10 + vz * dz10;
return {
u: du + width / 2,
v: height / 2 - dv,
};
}
PanoramaViewer.prototype.map = function(u, v) {
var PI = Math.PI;
var cos = Math.cos;
var sin = Math.sin;
var tan = Math.tan;
var sqrt = Math.sqrt;
var atan2 = Math.atan2;
var asin = Math.asin;
var fov = PanoramaViewer.FOV * PI / 180.0;
var width = this.width;
var height = this.height;
var h0 = this.heading * PI / 180.0;
var p0 = this.pitch * PI / 180.0;
var f = 0.5 * width / tan(0.5 * fov);
var x0 = f * cos(p0) * sin(h0);
var y0 = f * cos(p0) * cos(h0);
var z0 = f * sin(p0);
var du = u - width / 2;
var dv = height / 2 - v;
var ux = sgn(cos(p0)) * cos(h0);
var uy = -sgn(cos(p0)) * sin(h0);
var uz = 0;
var vx = -sin(p0) * sin(h0);
var vy = -sin(p0) * cos(h0);
var vz = cos(p0);
var x = x0 + du * ux + dv * vx;
var y = y0 + du * uy + dv * vy;
var z = z0 + du * uz + dv * vz;
var R = sqrt(x * x + y * y + z * z);
var h = atan2(x, y);
var p = asin(z / R);
return {
heading: h * 180.0 / PI,
pitch: p * 180.0 / PI
};
}
PanoramaViewer.prototype.click = function(e) {
var rect = e.target.getBoundingClientRect();
var u = e.clientX - rect.left;
var v = e.clientY - rect.top;
var uvCoords = this.unmap(this.heading, this.pitch);
console.log("current viewport center");
console.log(" heading: " + this.heading);
console.log(" pitch: " + this.pitch);
console.log(" u: " + uvCoords.u)
console.log(" v: " + uvCoords.v);
var hpCoords = this.map(u, v);
uvCoords = this.unmap(hpCoords.heading, hpCoords.pitch);
console.log("click at (" + u + "," + v + ")");
console.log(" heading: " + hpCoords.heading);
console.log(" pitch: " + hpCoords.pitch);
console.log(" u: " + uvCoords.u);
console.log(" v: " + uvCoords.v);
this.heading = hpCoords.heading;
this.pitch = hpCoords.pitch;
this.update();
}
This answer is unprecise, have a look at most recent answer of user3146587.
I'm not very good at mathematical explanations. I've coded an example and tried to explain the steps in the code. As soon as you click on one point in the image, this point becomes the new center of the image. Even though you have explicitly not demanded for this, this is perfect for illustrating the effect. The new image is drawn with the previously calculated angle.
Example: JSFiddle
The important part is, that I use the radian to calculate radius of the "sphere of view". The radian in this case is the width of the image (in your example 100)
radius = radian / FOV
With the radian, radius and the relative position of the mouse position I can calculate the degree that changes from the center to the mouse position.
Center(50,50)
MousePosition(75/25)
RelativeMousePosition(25,-25)
When the relative mouse position is 25 the radian used for the calculation of the horizontal angle is 50.
radius = 50 / FOV // we've calculated the radius before, it stays the same
See this image for the further process:
I can calculate the new heading and pitch when I add/subtract the calculated angle to the actual angle (depending on left/right, above/under). See the linked JSFiddle for the correct behavior of this.
Doing the reverse is simple, just do the listed steps in the opposite direction (the radius stays the same).
As I've already mentioned, I'm not very good at mathematical explanations, but don't hesitate to ask questions in the comments.
Here is an attempt to give a mathematical derivation of the answer to your question.
Note: Unfortunately, this derivation only works in 1D and the conversion from a pair of angular deviations to heading and pitch is wrong.
Notations:
f: focal length of the camera
h: height in pixels of the viewport
w: width in pixels of the viewport
dy: vertical deviation in pixels from the center of the viewport
dx: horizontal deviation in pixels from the center of the viewport
fov_y: vertical field of view
fov_x: horizontal field of view
dtheta_y: relative vertical angle from the center of the viewport
dtheta_x: relative horizontal angle from the center of the viewport
Given dy, the vertical offset of the pixel from the center of the viewport (this pixel corresponds to the green ray on the figure), we are trying to find dtheta_y (the red angle), the relative vertical angle from the center of the viewport (the pitch of the center of the viewport is known to be theta_y0).
From the figure, we have:
tan( fov_y / 2 ) = ( h / 2 ) / f
tan( dtheta_y ) = dy / f
so:
tan( dtheta_y ) = dy / ( ( h / 2 ) / tan( fov_y / 2 ) )
= 2 * dy * tan( fov_y / 2 ) / h
and finally:
dtheta_y = atan( 2 * dy * tan( fov_y / 2 ) / h )
This is the relative pitch angle for the pixel at dy from the center of the viewport, simply add to it the pitch angle at the center of the viewport to get the absolute pitch angle (i.e. theta_y = theta_y0 + dtheta_y).
similarly:
dtheta_x = atan( 2 * dx * tan( fov_x / 2 ) / w )
This is the relative heading angle for the pixel at dx from the center of the viewport.
Complements:
Both relations can be inverted to get the mapping from relative heading / pitch angle to relative pixel coordinates, for instance:
dy = h tan( dtheta_y ) / ( 2 * tan( fov_y / 2 ) )
The vertical and horizontal fields of view fov_y and fov_x are linked by the relation:
w / h = tan( fov_x / 2 ) / tan( fov_y / 2 )
so:
fov_x = 2 * atan( w * tan( fov_y / 2 ) / h )
The vertical and horizontal deviations from the viewport center dy and dx can be mapped to absolute pixel coordinates:
x = w / 2 + dx
y = h / 2 - dy
Proof of concept fiddle
Martin Matysiak wrote a JS library that implements the inverse of this (placing a marker at a specific heading/pitch). I mention this as the various jsfiddle links in other answers are 404ing, the original requestor added a comment requesting this, and this SO page comes up near the top for related searches.
The blog post discussing it is at https://martinmatysiak.de/blog/view/panomarker.
The library itself is at https://github.com/marmat/google-maps-api-addons.
There's documentation and demos at http://marmat.github.io/google-maps-api-addons/ (look at http://marmat.github.io/google-maps-api-addons/panomarker/examples/basic.html and http://marmat.github.io/google-maps-api-addons/panomarker/examples/fancy.html for the PanoMarker examples).
Related
Javascript Full 360 rotation from deviceorientation event beta angle when device is horizontal
I'm developing a WebGL game where the user would use the orientation of the device to control the camera ie look around. From the three rotations of deviceorientation event, I converted them to Quaternions. THe problem starts here: I need to have the user to hold the device horizontally. In this position, the beta angle will be used to turn around the vertical axis y from the user's perspective. However, the beta angle only goes from -180 to 180, any number outside will cause the other axes to change, to get a valid rotation. Due to how my WebGL camera is handled (I'm using Unity Engine), I need to separate the axes to remap them to the Camera coordinate accordingly, and to offset the angles from the horizontal pose (adding 90 degrees to the Y axis for example, because the device is rolled back). I have to convert the quaternion back to 3 euler angles and control each camera axis through those. Is there a way to solve this? Here's what I use to get the device orientation from the web: var degtorad = Math.PI / 180; // Degree-to-Radian conversion function getQuaternion(alpha, beta, gamma) { var _x = beta ? beta * degtorad : 0; // beta value var _y = gamma ? gamma * degtorad : 0; // gamma value var _z = alpha ? alpha * degtorad : 0; // alpha value var cX = Math.cos(_x / 2); var cY = Math.cos(_y / 2); var cZ = Math.cos(_z / 2); var sX = Math.sin(_x / 2); var sY = Math.sin(_y / 2); var sZ = Math.sin(_z / 2); // // ZXY quaternion construction. // var w = cX * cY * cZ - sX * sY * sZ; var x = sX * cY * cZ - cX * sY * sZ; var y = cX * sY * cZ + sX * cY * sZ; var z = cX * cY * sZ + sX * sY * cZ; return [w, x, y, z]; } function GyroEvent(e) { var quat = getQuaternion(e.alpha, e.beta, e.gamma + 90); // w unityInstance.SendMessage("Main Camera", "GetGyroW", quat[0]); // x unityInstance.SendMessage("Main Camera", "GetGyroX", quat[1]); // y unityInstance.SendMessage("Main Camera", "GetGyroY", quat[2]); // z unityInstance.SendMessage("Main Camera", "GetGyroZ", quat[3]); } window.addEventListener("deviceorientation", GyroEvent);
3D Rotation where the z-axis remains vertical
This codepen has a small rotation function that accepts theta and phi values. The formula of the rotation can be found anywhere on the internet and is fairly simple: function rotate(P, C, theta, phi) { let ct = Math.cos(theta); let st = Math.sin(theta); let cp = Math.cos(phi); let sp = Math.sin(phi); let x = P.x - C.x; let y = P.y - C.y; let z = P.z - C.z; P.x = ct * x - st * cp * y + st * sp * z + C.x; P.y = st * x + ct * cp * y - ct * sp * z + C.y; P.z = sp * y + cp * z + C.z; } This rotation works as a demonstration, but It's easy to loose track of which face of the cube is the initially bottom face. openSCAD on the other hand has a very nice rotation where the z-axis of the object always appears as a vertical line: The above function would behave the same, if you'd only change the theta value. As soon as you change phi value the cube stars to fall over. How would I write such a rotation in JavaScript?
Rotate around a changing origin - Javascript
So I have an object rotating around an origin point. Once I rotate and then change the origin point. My object seems to jump positions. After the jump it rotates fine... Need help finding the pattern/why it's jumping and what I need to do to stop it. Here's the rotation code: adjustMapTransform = function (_x, _y) { var x = _x + (map.width/2); var y = _y + (map.height/2); //apply scale here var originPoint = { x:originXInt, y:originYInt }; var mapOrigin = { x:map.x + (map.width/2), y:map.y + (map.height/2) }; //at scale 1 var difference = { x:mapOrigin.x - originPoint.x, y:mapOrigin.y - originPoint.y }; x += (difference.x * scale) - difference.x; y += (difference.y * scale) - difference.y; var viewportMapCentre = { x: originXInt, y: originYInt } var rotatedPoint = {}; var angle = (rotation) * Math.PI / 180.0; var s = Math.sin(angle); var c = Math.cos(angle); // translate point back to origin: x -= viewportMapCentre.x; y -= viewportMapCentre.y; // rotate point var xnew = x * c - y * s; var ynew = x * s + y * c; // translate point back: x = xnew + viewportMapCentre.x - (map.width/2); y = ynew + viewportMapCentre.y - (map.height/2); var coords = { x:x, y:y }; return coords; } Also here is a JS Fiddle project that you can play around in to give you a better idea of what's happening. EDITED LINK - Got rid of the originY bug and scaling bug https://jsfiddle.net/fionoble/6k8sfkdL/13/ Thanks!
The direction of rotation is a consequence of the sign you pick for the elements in your rotation matrix. [This is Rodrigues formula for rotation in two dimensions]. So to rotate in the opposite direction simply subtract your y cosine term rather than your y sine term. Also you might try looking at different potential representations of your data. If you use the symmetric representation of the line between your points you can avoid shifting and instead simply transform your coordinates. Take your origin [with respect to your rotation], c_0, to be the constant offset in the symmetric form. You have for a point p relative to c_0: var A = (p.x - c_0.x); var B = (p.y - c_0.y); //This is the symmetric form. (p.x - c_0.x)/A = (p.y - c_0.y)/B which will be true under a change of coordinates and for any point on the line (which also takes care of scaling/dilation). Then after the change of coordinates for rotation you have [noting that this rotation has the opposite sense, not the same as yours]. //This is the symmetric form of the line incident on your rotated point //and on the center of its rotation ((p.x - c_0.x) * c + (p.y - c_0.y) * s)/A = ((p.x - c_0.x) * s - (p.y - c_0.y) * c)/B so, multiplying out we get (pn.x - c_0.x) * B * c + (pn.y - c_0.y) * B * s = (pn.x - c_0.x) * A * s - (pn.y - c_0.y) * A * c rearrangement gives (pn.x - c_0.x) * (B * c - A * s) = - (pn.y - c_0.y) * (B * s + A * c) pn.y = -(pn.x - c_0.x) * (B * c - A * s) / (B * s + A * c) + c_0.y; for any scaling.
Given two diagonally opposite points on a rectangle, how to calculate the other two points
I'm trying to re-size a div element while dragging from top right or bottom left corners. In order to calculate the new width and height, i need to know the other two points on the rectangle how can I get this values given only two point and the rotation degree? please view the image I've added to fully understand this issue plus, the div can be also rotated (centered origin) to clarify my question: the aim is to resize a div by dragging the cursor of the mouse from top right corner to bottom left. and then to resize the image so the width will be the distance between mouseX to left side. and the height will be from mouseY to the bottom side. for this i nedd to calculate both top left corner and bottom right corner as the mouse cursor moves along. thank you.
Knowing two opposite corner points as absolute coordinates, and the angle. The (x1,y1)-(x3,y3) is essentially a rotated line representing the diagonal of the rectangle, so we can do: Find its midpoint and length of segment (midpoint to a corner) "Unrotate" the two points around the midpoint Use abs() with the diffs to get the width and height The essential code // find center point (origin) using linear interpolation var mx = x1 + (x3 - x1) * 0.5, my = y1 + (y3 - y1) * 0.5, cos = Math.cos(-angle), sin = Math.sin(-angle); // unrotate known points (using negative of known angle) var x1u = cos * (x1-mx) - sin * (y1-my) + mx, y1u = sin * (x1-mx) + cos * (y1-my) + my, x3u = cos * (x3-mx) - sin * (y3-my) + mx, y3u = sin * (x3-mx) + cos * (y3-my) + my; // Get width and height: var width = Math.abs(x3u - x1u), height = Math.abs(y3u - y1u); To get the points for the missing corners, just rotate the new points made from a mix of the unrotated points: cos = Math.cos(angle); sin = Math.sin(angle); // Use known coordinates for the new points: var x2u = x1u, y2u = y3u, x4u = x3u, y4u = y1u; // rotate new points using angle var x2 = cos * (x2u-mx) - sin * (y2u-my) + mx, y2 = sin * (x2u-mx) + cos * (y2u-my) + my, x4 = cos * (x4u-mx) - sin * (y4u-my) + mx, y4 = sin * (x4u-mx) + cos * (y4u-my) + my; Demo with plotting The demo will calculate the "missing" points, width and height, and show the result for each step. Input angle is to verify that it works regardless. var ctx = document.querySelector("canvas").getContext("2d"); ctx.fillStyle = "#e00"; document.querySelector("input").addEventListener("change", update); function update() { // Test rect: 50,25 - 350, 175, center: 200,200, W: 300, H: 150 // generate x1,y1 - x3,y3 known points so we have something to work with: var value = typeof this.value !== "undefined" ? +this.value : 30, angle = value * Math.PI / 180, x1 = Math.cos(angle) * (50-200) - Math.sin(angle) * (275-200) + 200, y1 = Math.sin(angle) * (50-200) + Math.cos(angle) * (275-200) + 200, x3 = Math.cos(angle) * (350-200) - Math.sin(angle) * (125-200) + 200, y3 = Math.sin(angle) * (350-200) + Math.cos(angle) * (125-200) + 200; // Initial Visuals: rotated rect, known corner points ctx.clearRect(0,0,400,400); ctx.strokeStyle = "#000"; ctx.translate(200,200); ctx.rotate(angle); ctx.translate(-200,-200); ctx.strokeRect(50, 125, 300, 150); ctx.setTransform(1,0,0,1,0,0); ctx.fillStyle = "#e00"; ctx.fillRect(x1-2, y1-2, 4, 4); ctx.fillText("x1,y1", x1+5, y1); ctx.fillRect(x3-2, y3-2, 4, 4); ctx.fillText("x3,y3", x3+5, y3); // Step 1: find center point (origin) var mx = x1 + (x3 - x1) * 0.5, my = y1 + (y3 - y1) * 0.5; ctx.fillRect(mx-2, my-2, 4, 4); // draw center point // unrotate known points (negative angle) var x1u = Math.cos(-angle) * (x1-mx) - Math.sin(-angle) * (y1-my) + mx, y1u = Math.sin(-angle) * (x1-mx) + Math.cos(-angle) * (y1-my) + my, x3u = Math.cos(-angle) * (x3-mx) - Math.sin(-angle) * (y3-my) + mx, y3u = Math.sin(-angle) * (x3-mx) + Math.cos(-angle) * (y3-my) + my; ctx.fillStyle = "#00c"; ctx.fillRect(x1u-2, y1u-2, 4, 4); ctx.fillText("x1u,y1u", x1u+5, y1u-5); ctx.fillRect(x3u-2, y3u-2, 4, 4); ctx.fillText("x3u,y3u", x3u+5, y3u); // To get width and height: var width = Math.abs(x3u - x1u), height = Math.abs(y3u - y1u); ctx.fillText("Size: " + ((width+0.5)|0) + " x " + ((height+0.5)|0), 0, 10); // Mix known coordinates var x2u = x1u, y2u = y3u, x4u = x3u, y4u = y1u; // show unrotated points ctx.fillStyle = "#0c0"; ctx.fillRect(x2u-2, y2u-2, 4, 4); ctx.fillText("x2u,y2u", x2u+5, y2u-5); ctx.fillRect(x4u-2, y4u-2, 4, 4); ctx.fillText("x4u,y4u", x4u+5, y4u); // draw lines between unrotated points to show we have an actual rectangle ctx.strokeStyle = "#777"; ctx.beginPath(); ctx.moveTo(x1u, y1u); ctx.lineTo(x2u, y2u); ctx.lineTo(x3u, y3u); ctx.lineTo(x4u, y4u); ctx.closePath(); ctx.stroke(); // rotate new points using angle var x2 = Math.cos(angle) * (x2u-mx) - Math.sin(angle) * (y2u-my) + mx, y2 = Math.sin(angle) * (x2u-mx) + Math.cos(angle) * (y2u-my) + my, x4 = Math.cos(angle) * (x4u-mx) - Math.sin(angle) * (y4u-my) + mx, y4 = Math.sin(angle) * (x4u-mx) + Math.cos(angle) * (y4u-my) + my; // show new coordinates ctx.fillStyle = "#f0f"; ctx.fillRect(x2-2, y2-2, 4, 4); ctx.fillText("x2,y2", x2+5, y2); ctx.fillRect(x4-2, y4-2, 4, 4); ctx.fillText("x4,y4", x4+5, y4); } update(); <script src="https://cdn.rawgit.com/epistemex/slider-feedback/master/sliderfeedback.min.js"></script> Angle: <input type=range min=0 max=360 value=30><br><canvas width=400 height=400></canvas>
I think you should use Trigo for that, but since I'm terrible with those, here is a dumb way without any Maths, to get the absolute positioning of your points. var tl= document.querySelector('#tl').getBoundingClientRect(); var tr= document.querySelector('#tr').getBoundingClientRect(); var br= document.querySelector('#br').getBoundingClientRect(); var bl= document.querySelector('#bl').getBoundingClientRect(); var pointsList = { tl:[tl.left, tl.top], tr:[tr.left, tr.top], br:[br.left, br.top], bl:[bl.left, bl.top], }; for(var p in pointsList){ document.querySelector('#r').innerHTML+=p+' '+pointsList[p].join(' , ')+'<br>'; } #main{background-color:#CCC;height: 120px; width: 70px; position: relative; transform: rotate(30deg)} .dot{ width: 1px; height: 1px; position: absolute; background-color:#000;} #tl{top:0; left:0;} #tr{top:0; right:0;} #br{bottom:0; right:0;} #bl{bottom:0; left:0;} <div id="main"> <div id="tl" class="dot"></div> <div id="tr" class="dot"></div> <div id="br" class="dot"></div> <div id="bl" class="dot"></div> </div> <div id="r">
Ken's comments are a good starting point actually. You can take the tangent inverse of the slope of the diagonal and add the degrees rotated to find the angle between the diagonal and a side. m = (y3-y1)/(x3-x1) diag_angle = arctan(m) diag_angle_adjusted = diag_angle + rotation This will give you the angle between the diagonal and the bottom left side. Then, you can use the distance formula to get the diagonal length. diag_length = (y3 - y1)^2 + (x3-x1)^2 To find the length of the bottom left side you would use the cos formula, and for the bottom right you would use sin. bot_left = diag_length*cos(diag_angle_adjusted) This would let you get the lengths of the sides and proceed to calculate the other x and y. For example, sin(rotation) = (y2 - y4)/bot_left After solving for y4, it should be fairly simple to solve for x4 using cos. I am answering from my phone and have not formally tested this, but that approach should work. Hopefully tomorrow I will have time to diagram the answer if it's not clear. Good luck! And make sure to keep your signs correct for rotation.
Naming point (x1,x2) p1 etc., naming the rotation angle rot (minus 30deg in the example), naming the distance frop p1 to p4 d14 etc. Using the fact that the length op the projection of a vector on an axis is the absolute value of the dot-product of that vector on the ubit vector in that direction, the length of p1-p4 is the dot product of (cos(rot), sin(rot)) with (x3 - x1, y3 - y1). d14 = abs((x3 - x1)*cos(rot) + (y3 - y1)*sin(rot)) d12 = abs((x3 - x1)*cos(rot + 90) + (y3 - y1)sin(rot +90)) If you need the coordinates of p2 and p4 x4 = x1 + d14 * cos(rot) y4 = y1 + d14 * sin(rot) x2 = x1 + d12 * cos(rot + 90) y2 = y1 + d12 * sin(rot + 90) ( created on my tablet, to be reviewed when I work on my laptop)
Use X,Y coordinates to plot points inside a circle
Is there a way in javascript to plot x,y coordinates so they fall into a circle rather than a square? For example if I have the following code: circleRadius = 100; context.drawImage(img_elem, dx, dy, dw, dh); I need to figure out a combination of x,y values that would fall inside a 100 pixel circle. Thanks!
choose an x at random between -100 and 100 a circle is defined by x^2 + y^2 = r^2, which in your case equals 100^2 = 10000 From this equation you can get that y^2 = 10000 - x^2 , therefore the points with a chosen x and y = +/-sqrt(10000 - x^2) will lye on the circle. choose an y at random between the two coordinates found at point 3 You're set! EDIT: In JS: var radius = 100; x = Math.random() * 2 * radius - radius; ylim = Math.sqrt(radius * radius - x * x); y = Math.random() * 2 * ylim - ylim; Another edit: a jsFiddle Example
If you want equidistributed coordinates you better go for var radius = 100 var center_x = 0 var center_y = 0 // ensure that p(r) ~ r instead of p(r) ~ constant var r = radius*Math.sqrt(Math.random(1)) var angle = Math.sqrt(2*Math.PI) // compute desired coordinates var x = center_x + r*Math.cos(angle); var y = center_y + r*Math.sin(angle); If you want more points close to the middle then use var r = radius*Math.random(1) instead.
not sure what you mean for javascript but x = R*cos(theta) and y = R*sin(theta) are the Cartesian points for a circle. R is the radius of course and theta is the angle which goes from 0 to 2*Pi.
I'm posting this as a solution because this question was the only relevant result in google. My question/problem was how to add cartesian coordinates inside a circle where x and y would not exceed r. Examples: plot: (45,75) inside a circle with a radius of 100 (this would normally fall inside the circle, but not the correct position) plot: (100,100) inside a circle with a radius of 100 (this would normally fall outside the circle Solution // The scale of the graph to determine position of plot // I.E. If the graph visually uses 300px but the values only goto 100 var scale = 100; // The actual px radius of the circle / width of the graph var radiusGraph = 300; // Plot the values on a cartesian plane / graph image var xCart = xVal * radiusGraph; var yCart = yVal * radiusGraph; // Get the absolute values for comparison var xCartAbs = Math.abs( xCart ); var yCartAbs = Math.abs( yCart ); // Get the radius of the cartesian plot var radiusCart = Math.sqrt( xCart * xCart + yCart * yCart ); // Compare to decide which value is closer to the limit // Once we know, calculate the largest possible radius with the graphs limit. // r^2 = x^2 + y^2 if ( xCartAbs > yCartAbs ) { // Less than 45° diff = scale / xCartAbs; radiusMaximum = Math.sqrt( radiusGraph * radiusGraph + Math.pow( yCartAbs * diff, 2) ); } else if ( yCartAbs > xCartAbs ) { // Greater than 45° diff = scale / yCartAbs; radiusMaximum = Math.sqrt( radiusGraph * radiusGraph + Math.pow( xCartAbs * diff, 2) ); } else { // 45° radiusMaximum = Math.sqrt( 2 * ( radiusGraph * radiusGraph ) ); } // Get the percent of the maximum radius that the cartesian plot is at var radiusDiff = radiusCart / radiusMaximum; var radiusAdjusted = radiusGraph * radiusDiff; // Calculate the angle of the cartesian plot var theta = Math.atan2( yCart, xCart ); // Get the new x,y plot inside the circle using the adjust radius from above var xCoord = radiusAdjusted * Math.cos( theta ); var yCoord = radiusAdjusted * Math.sin( theta );
Not sure if this is correct JavaScript code, but something like this: for (x = -r; x < r; x++) { for (y = -r; x < r; y++) { if ((x * x + y * y) < (r * r)) { // This x/y coordinate is inside the circle. // Use <= if you want to count points _on_ the circle, too. } } }