I'am trying to rotate Leaflet Rectangle using code from this question.
rotatePoints (center, points, yaw) {
const res = []
const angle = yaw * (Math.PI / 180)
for (let i = 0; i < points.length; i++) {
const p = points[i]
// translate to center
const p2 = new LatLng(p.lat - center.lat, p.lng - center.lng)
// rotate using matrix rotation
const p3 = new LatLng(Math.cos(angle) * p2.lat - Math.sin(angle) * p2.lng, Math.sin(angle) * p2.lat + Math.cos(angle) * p2.lng)
// translate back to center
const p4 = new LatLng(p3.lat + center.lat, p3.lng + center.lng)
// done with that point
res.push(p4)
}
return res
}
The problem is that the rectangle is skewed while rotating.
Any ideas how to optimize this function?
Fixed
Final code:
rotatePoints (center, points, yaw) {
const res = []
const centerPoint = map.latLngToLayerPoint(center)
const angle = yaw * (Math.PI / 180)
for (let i = 0; i < points.length; i++) {
const p = map.latLngToLayerPoint(points[i])
// translate to center
const p2 = new Point(p.x - centerPoint.x, p.y - centerPoint.y)
// rotate using matrix rotation
const p3 = new Point(Math.cos(angle) * p2.x - Math.sin(angle) * p2.y, Math.sin(angle) * p2.x + Math.cos(angle) * p2.y)
// translate back to center
let p4 = new Point(p3.x + centerPoint.x, p3.y + centerPoint.y)
// done with that point
p4 = map.layerPointToLatLng(p4)
res.push(p4)
}
return res
}
What is "rectangle" on sphere? It is result of applying of current projection to such coordinates that their image on the map/screen form rectangle. Note that lat-long coordinates are not ought to be equal for every rectangle side (for example, longitude for equator-aligned rectangle will differ for top and bottom point in the most of usual projections)
So to to get good-looking rectangle in map, you need to rotate vertices in screen coordinates, and make back-projection into lat-long space.
Related
Note: This question has NOTHING to do with Three.js, it's only Tensorflow.js and Trigonometry.
I am trying to rotate a 3D object in Three.js by rotating my face. I have used this code by akhirai560 for rotating in X and Y axis.:
function normal(vec) {
let norm = 0;
for (const v of vec) {
norm += v * v;
}
return Math.sqrt(norm);
}
function getHeadAnglesCos(keypoints) {
// Vertical (Y-Axis) Rotation
const faceVerticalCentralPoint = [
0,
(keypoints[10][1] + keypoints[152][1]) * 0.5,
(keypoints[10][2] + keypoints[152][2]) * 0.5,
];
const verticalAdjacent = keypoints[10][2] - faceVerticalCentralPoint[2];
const verticalOpposite = keypoints[10][1] - faceVerticalCentralPoint[1];
const verticalHypotenuse = normal([verticalAdjacent, verticalOpposite]);
const verticalCos = verticalAdjacent / verticalHypotenuse;
// Horizontal (X-Axis) Rotation
const faceHorizontalCentralPoint = [
(keypoints[226][0] + keypoints[446][0]) * 0.5,
0,
(keypoints[226][2] + keypoints[446][2]) * 0.5,
];
const horizontalAdjacent = keypoints[226][2] - faceHorizontalCentralPoint[2];
const horizontalOpposite = keypoints[226][0] - faceHorizontalCentralPoint[0];
const horizontalHypotenuse = normal([horizontalAdjacent, horizontalOpposite]);
const horizontalCos = horizontalAdjacent / horizontalHypotenuse;
return [horizontalCos, verticalCos];
}
It calculates the rotation by finding the cos of these points (original image source):
I also want to calculate the cos of Z axis rotation. Thanks!
Am not fully clear of the end goal of the question, but if simply looking to rotate about the Z axis, the following snippet will assist.
center = { x: 50, y: 50 };
points = [
{ x: 60, y: 60 },
{ x: 50, y: 100 }
]
function rotateAboutZ( xyRotationPoint, points, zRotation ) {
let result = points.map( p => {
// Adjust the point based on the center of rotation.
let x = p.x - xyRotationPoint.x;
let y = p.y - xyRotationPoint.y;
// Calculate the radius and angle in preparation for rotation
// about the Z axis.
let radius = Math.sqrt( x * x + y * y );
let angle = Math.atan2( y, x );
// Adjust the angle by the requested rotation.
let angleRotated = angle + zRotation;
// Finally, calculate the new XY coordinates, and re-adjust
// based on the center of rotation.
let xRotated = radius * Math.cos( angleRotated ) + xyRotationPoint.x;
let yRotated = radius * Math.sin( angleRotated ) + xyRotationPoint.y;
return { x: xRotated, y: yRotated };
} );
return result;
}
// Let's rotate the points by 180 degrees around the
// center point of (50,50).
let result = rotateAboutZ( center, points, Math.PI );
console.log( result );
I'm working on an HTML Canvas demo to learn more about circle to circle collision detection and response. I believe that the detection code is correct but the response math is not quite there.
The demo has been implemented using TypeScript, which is a typed superset of JavaScript that is transpiled to plain JavaScript.
I believe that the problem exists within the checkCollision method of the Circle class, specifically the math for calculating the new velocity.
The blue circle position is controlled by the mouse (using an event listener). If the red circle collides from the right side of the blue circle, the collision response seems to work correctly, but if it approaches from the left it does not respond correctly.
I am looking for some guidance on how I can revise the checkCollision math to correctly handle the collision from any angle.
Here is a CodePen for a live demo and dev environment:
CodePen
class DemoCanvas {
canvasWidth: number = 500;
canvasHeight: number = 500;
canvas: HTMLCanvasElement = document.createElement('canvas');
constructor() {
this.canvas.width = this.canvasWidth;
this.canvas.height = this.canvasHeight;
this.canvas.style.border = '1px solid black';
this.canvas.style.position = 'absolute';
this.canvas.style.left = '50%';
this.canvas.style.top = '50%';
this.canvas.style.transform = 'translate(-50%, -50%)';
document.body.appendChild(this.canvas);
}
clear() {
this.canvas.getContext('2d').clearRect(0, 0, this.canvas.width, this.canvas.height);
}
getContext(): CanvasRenderingContext2D {
return this.canvas.getContext('2d');
}
getWidth(): number {
return this.canvasWidth;
}
getHeight(): number {
return this.canvasHeight;
}
getTop(): number {
return this.canvas.getBoundingClientRect().top;
}
getRight(): number {
return this.canvas.getBoundingClientRect().right;
}
getBottom(): number {
return this.canvas.getBoundingClientRect().bottom;
}
getLeft(): number {
return this.canvas.getBoundingClientRect().left;
}
}
class Circle {
x: number;
y: number;
xVelocity: number;
yVelocity: number;
radius: number;
color: string;
canvas: DemoCanvas;
context: CanvasRenderingContext2D;
constructor(x: number, y: number, xVelocity: number, yVelocity: number, color: string, gameCanvas: DemoCanvas) {
this.radius = 20;
this.x = x;
this.y = y;
this.xVelocity = xVelocity;
this.yVelocity = yVelocity;
this.color = color;
this.canvas = gameCanvas;
this.context = this.canvas.getContext();
}
public draw(): void {
this.context.fillStyle = this.color;
this.context.beginPath();
this.context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
this.context.fill();
}
public move(): void {
this.x += this.xVelocity;
this.y += this.yVelocity;
}
checkWallCollision(gameCanvas: DemoCanvas): void {
let top = 0;
let right = 500;
let bottom = 500;
let left = 0;
if(this.y < top + this.radius) {
this.y = top + this.radius;
this.yVelocity *= -1;
}
if(this.x > right - this.radius) {
this.x = right - this.radius;
this.xVelocity *= -1;
}
if(this.y > bottom - this.radius) {
this.y = bottom - this.radius;
this.yVelocity *= -1;
}
if(this.x < left + this.radius) {
this.x = left + this.radius;
this.xVelocity *= -1;
}
}
checkCollision(x1: number, y1: number, r1: number, x2: number, y2: number, r2: number) {
let distance: number = Math.abs((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
// Detect collision
if(distance < (r1 + r2) * (r1 + r2)) {
// Respond to collision
let newVelocityX1 = (circle1.xVelocity + circle2.xVelocity) / 2;
let newVelocityY1 = (circle1.yVelocity + circle1.yVelocity) / 2;
circle1.x = circle1.x + newVelocityX1;
circle1.y = circle1.y + newVelocityY1;
circle1.xVelocity = newVelocityX1;
circle1.yVelocity = newVelocityY1;
}
}
}
let demoCanvas = new DemoCanvas();
let circle1: Circle = new Circle(250, 250, 5, 5, "#F77", demoCanvas);
let circle2: Circle = new Circle(250, 540, 5, 5, "#7FF", demoCanvas);
addEventListener('mousemove', function(e) {
let mouseX = e.clientX - demoCanvas.getLeft();
let mouseY = e.clientY - demoCanvas.getTop();
circle2.x = mouseX;
circle2.y = mouseY;
});
function loop() {
demoCanvas.clear();
circle1.draw();
circle2.draw();
circle1.move();
circle1.checkWallCollision(demoCanvas);
circle2.checkWallCollision(demoCanvas);
circle1.checkCollision(circle1.x, circle1.y, circle1.radius, circle2.x, circle2.y, circle2.radius);
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
Elasic 2D collision
The problem is likely because the balls do not move away from each other and then in the next frame they are still overlapping and it gets worse. My guess from just looking at the code.
A simple solution.
Before you can have the two balls change direction you must ensure that they are positioned correctly. They must be just touching, (no overlay) or they can get caught up in each other.
Detect collision, and fix position.
// note I am using javascript.
// b1,b2 are the two balls or circles
// b1.dx,b1.dy are velocity (deltas) to save space same for b2
// get dist between them
// first vect from one to the next
const dx = b2.x - b1.x;
const dy = b2.y - b1.y;
// then distance
const dist = Math.sqrt(dx*dx + dy*dy);
// then check overlap
if(b1.radius + b2.radius >= dist){ // the balls overlap
// normalise the vector between them
const nx = dx / dist;
const ny = dy / dist;
// now move each ball away from each other
// along the same line as the line between them
// Use the ratio of the radius to work out where they touch
const touchDistFromB1 = (dist * (b1.radius / (b1.radius + b2.radius)))
const contactX = b1.x + nx * touchDistFromB1;
const contactY = b1.y + ny * touchDistFromB1;
// now move each ball so that they just touch
// move b1 back
b1.x = contactX - nx * b1.radius;
b1.y = contactY - ny * b1.radius;
// and b2 in the other direction
b2.x = contactX + nx * b2.radius;
b2.y = contactY + ny * b2.radius;
If one is static
If one of the balls is static then you can keep its position and move the other ball.
// from contact test for b1 is immovable
if(b1.radius + b2.radius >= dist){ // the balls overlap
// normalise the vector between them
const nx = dx / dist;
const ny = dy / dist;
// move b2 away from b1 along the contact line the distance of the radius summed
b2.x = b1.x + nx * (b1.radius + b2.radius);
b2.y = b1.y + ny * (b1.radius + b2.radius);
Now you have the balls correctly separated a you can calculate the new trajectories
Changing the trajectories.
There are a wide variety of ways to do this, but the one I like best is the elastic collision. I created a function from the Elastic collision in Two dimensional space wiki source and have been using it in games for some time.
The function and information is in the snippet at the bottom.
Next I will show how to call the function continuing on from the code above
// get the direction and velocity of each ball
const v1 = Math.sqrt(b1.dx * b1.dx + b1.dy * b1.dy);
const v2 = Math.sqrt(b2.dx * b2.dx + b2.dy * b2.dy);
// get the direction of travel of each ball
const dir1 = Math.atan2(b1.dy, b1.dx);
const dir2 = Math.atan2(b2.dy, b2.dx);
// get the direction from ball1 center to ball2 cenet
const directOfContact = Math.atan2(ny, nx);
// You will also need a mass. You could use the area of a circle, or the
// volume of a sphere to get the mass of each ball with its radius
// this will make them react more realistically
// An approximation is good as it is the ratio not the mass that is important
// Thus ball are spheres. Volume is the cubed radius
const mass1 = Math.pow(b1.radius,3);
const mass1 = Math.pow(b2.radius,3);
And finally you can call the function
ellastic2DCollistionD(b1, b2, v1, v2, d1, d2, directOfContact, mass1, mass2);
And it will correctly set the deltas of both balls.
Moving the ball position along their deltas is done after the collision function
b1.x += b1.dx;
b1.y += b1.dy;
b2.x += b1.dx;
b2.y += b1.dy;
If one of the balls is static you just ignore the deltas.
Elasic 2D collision function
Derived from information at Elastic collision in Two dimensional space wiki
// obj1, obj2 are the object that will have their deltas change
// velocity1, velocity2 is the velocity of each
// dir1, dir2 is the direction of travel
// contactDir is the direction from the center of the first object to the center of the second.
// mass1, mass2 is the mass of the first and second objects.
//
// function ellastic2DCollistionD(obj1, obj2, velocity1, velocity2, dir1, dir2, contactDir, mass1, mass2){
// The function applies the formula below twice, once fro each object, allowing for a little optimisation.
// The formula of each object's new velocity is
//
// For 2D moving objects
// v1,v2 is velocity
// m1, m2 is the mass
// d1 , d2 us the direction of moment
// p is the angle of contact;
//
// v1* cos(d1-p) * (m1 - m2) + 2 * m2 * v2 * cos(d2- p)
// vx = ----------------------------------------------------- * cos(p) + v1 * sin(d1-p) * cos(p + PI/2)
// m1 + m2
// v1* cos(d1-p) * (m1 - m2) + 2 * m2 * v2 * cos(d2- p)
// vy = ----------------------------------------------------- * sin(p) + v1 * sin(d1-p) * sin(p + PI/2)
// m1 + m2
// More info can be found at https://en.wikipedia.org/wiki/Elastic_collision#Two-dimensional
// to keep the code readable I use abbreviated names
function ellastic2DCollistionD(obj1, obj2, v1, v2, d1, d2, cDir, m1, m2){
const mm = m1 - m2;
const mmt = m1 + m2;
const v1s = v1 * Math.sin(d1 - cDir);
const cp = Math.cos(cDir);
const sp = Math.sin(cDir);
var cdp1 = v1 * Math.cos(d1 - cDir);
var cdp2 = v2 * Math.cos(d2 - cDir);
const cpp = Math.cos(cDir + Math.PI / 2)
const spp = Math.sin(cDir + Math.PI / 2)
var t = (cdp1 * mm + 2 * m2 * cdp2) / mmt;
obj1.dx = t * cp + v1s * cpp;
obj1.dy = t * sp + v1s * spp;
cDir += Math.PI;
const v2s = v2 * Math.sin(d2 - cDir);
cdp1 = v1 * Math.cos(d1 - cDir);
cdp2 = v2 * Math.cos(d2 - cDir);
t = (cdp2 * -mm + 2 * m1 * cdp1) / mmt;
obj2.dx = t * -cp + v2s * -cpp;
obj2.dy = t * -sp + v2s * -spp;
}
Note just realized that you are using a typeScript and the function above is specifically type agnostic. Does not care about obj1, obj2 type, and will add the deltas to any object that you pass.
You will have to change the function for typeScript.
The velocity vector should change by a multiple of the normal vector at the collision point, which is also the normalized vector between the circle mid points.
There are several posts here and elsewhere on elastic circle collisions and the computation of the impulse exchange (for instance Collision of circular objects, with jsfiddle for planet billiard https://stackoverflow.com/a/23671054/3088138).
If circle2 is bound to the mouse, then the event listener should also update the velocity using the difference to the previous point and the difference of time stamps, or better some kind of moving average thereof. The mass of this circle in the collision formulas is to be considered infinite.
As you are using requestAnimationFrame, the spacing of the times it is called is to be considered random. It would be better to use actual time stamps and some effort at implementing the Euler method (or whatever the resulting order 1 integration method amounts to) using the actual time increments. The collision procedure should not contain a position update, as that is the domain of the integration step, which in turn makes it necessary to add a test that the disks are actually moving together.
I want to distribute n points evenly on a circle circumference in quadrants I and IV only.
As parameters, I have the numbers of point n, the center of circle coordiantes cx and cy and the radius r.
I can distribute the points over the whole circumference like using this formula below, but I am looking for the formula to spread them only in quadrants I and IV
var n = 5;
var cx = 1;
var cy = 1;
var r = 2;
//I store each point's coordinates in this array below
var coordinates = [];
for (var i=0; i < n; i++) {
//defining point's angle with the center of the circle in radiant
//this angle distribute the points evenly over all 4 quadrants
var angle = ((360/n) * i) * (Math.PI/180);
//calculating the point's coordinates on the circle circumference
var pointX = cx + r * Math.cos(angle);
var pointY = cx + r * Math.sin(angle);
//storing the point's coordinates
coordinates.push([pointX, pointY]);
}
Here would be the steps I'd take to solve this:
find the angle betw. each point var incrementBy = 180 / n
start angle will be 270º and end angle will be 90º
iterate through via
code
var increment = 180 / n
var startAngle = 270
for (var i = 0; i < n; i++)
{
var angle = startAngle + increment * i
var rads = angle * Math.pi / 180
var tx = cx + r * Math.cos(rads)
var ty = cy + r * Math.sin(rads)
coords.push([tx, ty])
}
note
I didn't bother to convert for traditional quadrants (vs JS's y-axis moving downwards). If that is needed then, after your calculations, just invert the ty value. I also didn't bother to reduce the angle value when it exceeds 360º when you're incrementing back into Quad I.
like this?
var n = 5;
var r = 2;
var cx = 1;
var cy = 1;
var coordinates = [];
for(var i=0; i<n; ++i){
var a = (i+.5) / n * Math.PI;
coordinates.push([
cx + Math.sin(a) * r,
cy - Math.cos(a) * r
]);
}
Here is the way to equally distribute objects
var boxes = []
let count = 10;
for (let i = 0; i < count; i++) {
let size = 0.8;
let radius = 3;
let box = new THREE.Mesh(new THREE.BoxGeometry( size, size, size ),new THREE.MeshPhysicalMaterial(0x333333))
let gap = 0.5;
let angle = i * ((Math.PI * 2) / count);
let x = radius * Math.cos(angle);
let y = 0;
let z = radius * Math.sin(angle);
box.position.set(x,y,z);
boxes.push(box);`enter code here`
scene.add(box)
}
here is how it looks for 10 blocks
here is how it looks for 5 blocks
var n = 5;
var cx = 1;
var cy = 1;
var r = 2;
//I store each point's coordinates in this array below
var coordinates = [];
for (var i=0; i < n; i++) {
//defining the angle of the point with the center of the circle in radiant
var angle = ((360/n) * i) * (Math.PI/180);
//calculating the coordinates of the point on the circle circumference
var pointX = cx + r * Math.cos(angle);
var pointY = cx + r * Math.sin(angle);
// Here, we are going to use a boolean expression to determine if
// [pointX, pointY] is within quadrant 1 or 4.
// We can start with this boolean equation:
// (pointX >= cx && pointY >= cy) || (pointX >= cx && pointY <= cy)
// But this problem can be simplified to only pointX >= cx
if(pointX >= cx){
//storing the point's coordinates
coordinates.push([pointX, pointY]);
}
}
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).
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.
}
}
}