I'm currently creating a custom 3D paraboloid mesh by warping a 2D plane with the following algorithm applied to each vertex:
function transformVertex(v) {
var lat = toRad(v.y - height/2),
lon = toRad(v.x - width/2)/cos(lat);
lat = lat*cos(lon/1.5);
v.x = radius * cos(lat) * cos(lon);
v.y = radius * cos(lat) * sin(lon);
v.z = radius * sin(lat);
}
My question is, how can I reverse this process? I need to take a 3D point on the paraboloid and convert it to 2D coordinates relative to that plane PRE warping.
There are some changes in variables during calculation, so let's version the variables and determine their functional relationships:
1- lat_0 = f(v_0.y)
2- lon_0 = f(v_0.x, lat_0)
3- lat_1 = f(lat_0, lon_0)
4- v_1.x = f(lat_1, lon_0)
5- v_1.y = f(lat_1, lon_0)
6- v_1.z = f(lat_1)
Now going from 6 back to 1,
lat_1 = asin(v_1.z / radius)
lon_0 = asin(v_1.y / radius / cos(lat_1)) OR lon_0 = acos(v_1.x / radius / cos(lat_1))
At this point, lon_0 depends on v_0.x and lat_0, and lat_0 depends on v_0.y, which locks up the whole thing. It looks irreversible.
Related
So I am creating a simulation of a bouncing ball, and the user can place lines the ball can collide with on the canvas by dragging from one point to another. There are essentially four lines that can be created:
So the object that stores a line is defined as such:
export interface pathSection {
xfrom: number;
yfrom: number;
xto: number;
yto: number;
length: number;
}
The first and third lines in the image for example dont give the same value from
Math.atan2(yto - yfrom, xto - from);
So given the (relative) complexity of the surfaces, I need to find the angle between a moving object and that surface at the point of collision:
The ball strikes the surface at an angle a, which is what I want!
However I am having trouble finding the angle between the two vectors. This is what I understood would work:
var dx = this.path[index_for_path_section].xfrom - this.path[index_for_path_section].xto;
var dy = this.path[index_for_path_section].yfrom - this.path[index_for_path_section].yto;
var posX = this.particle.pos.x;
var posY = this.particle.pos.y;
var posNextX = posX + this.particle.v.x;
var posNextY = posY + this.particle.v.y;
var angleOfRamp = Math.atan2(dy, dx);
var angleOfvelocity = Math.atan2(posNextY - posY, posNextX - posX);
var angleBetween = angleOfRamp - angleOfvelocity;
This is then used to calculate the speed of the object after the collision:
var spd = Math.sqrt(this.particle.v.x * this.particle.v.x + this.particle.v.y * this.particle.v.y);
var restitution = this.elasticity / 100;
this.particle.v.x = restitution * spd * Math.cos(angleBetween);
this.particle.v.y = restitution * spd * Math.sin(angleBetween);
However the angle calculated is around -4.5 Pi, about -90 degrees for the object directly down and the surface at what looks to be around 45-60 degrees…
The red arrow shows the path of the object moving through the surface - the white dots show where a collision has been detected between the surface and the object.
Any help on how to get the correct and usable angle between the two velocity and the line would be appreciated!
Note I have tried utilizing this solution, but have struggled to adapt it to my own work.
So it took me some time, and I am not 100% sure still of why it works because I think im finding the JavaScript angles system a bit tricky, but:
var dx = this.path[collided].xfrom - this.path[collided].xto;
var dy = this.path[collided].yfrom - this.path[collided].yto;
var spd = Math.sqrt(this.particle.v.x * this.particle.v.x + this.particle.v.y * this.particle.v.y);
var angleOfRamp = Math.atan2(dy, dx);
var angleOfvelocity = Math.atan2(this.particle.v.y, this.particle.v.x);
var angleBetween = angleOfRamp * 2 - angleOfvelocity; // not sure why :)
if (angleBetween < 0) { angleBetween += 2*Math.PI; } // not sure why :)
const restitution = this.elasticity / 100;
this.particle.v.x = restitution * spd * Math.cos(angleBetween);
this.particle.v.y = restitution * spd * Math.sin(angleBetween);
Thanks to all who looked :)
I'm building a simple app that places a marker on your screen where at the top of certain landmarks in the real world, going to overlay the markers over the camera's view.
I have the latitude/longitude/altitude for both the viewing device and the world landmarks, and I convert those to ECEF coordinates. But I am having trouble with the 3D projection math. The point always seems to get placed in the middle of the screen... maybe my scaling is wrong somewhere so it looks like it's hardly moving from the center?
Viewing device GPS coordinates:
GPS:
lat: 45.492132
lon: -122.721062
alt: 124 (meters)
ECEF:
x: -2421034.078421273
y: -3768100.560012433
z: 4525944.676268726
Landmark GPS coordinates:
GPS:
lat: 45.499278
lon: -122.708417
alt: 479 (meters)
ECEF:
x: -2420030.781624382
y: -3768367.5284123267
z: 4526754.604333807
I tried following the math from here to build a function to get me screen coordinates from 3D point coordinates.
When I put those ECEF points into my projection function, with a viewport of 1440x335 I get: x: 721, y: 167
Here is my function:
function projectionCoordinates(origin, destination) {
const relativeX = destination.x - origin.x;
const relativeY = destination.y - origin.y;
const relativeZ = destination.z - origin.z;
const xPerspective = relativeX / relativeZ;
const yPerspective = relativeY / relativeZ;
const xNormalized = (xPerspective + viewPort.width / 2) / viewPort.width;
const yNormalized = (yPerspective + viewPort.height / 2) / viewPort.height;
const xRaster = Math.floor(xNormalized * viewPort.width);
const yRaster = Math.floor((1 - yNormalized) * viewPort.height);
return { x: xRaster, y: yRaster };
}
I believe the point should be placed much higher on the screen. That article I linked mentions 3x4 matrices which I couldn't follow along with (not sure how to build the 3x4 matrices from the 3D points). Maybe those are important, especially since I will eventually have to take the device's tilt into consideration (looking up or down with phone).
If it's needed, here is my function to convert latitude/longitude/altitude coordinates to ECEF (copy/pasted from another SO answer):
function llaToCartesion({ lat, lon, alt }) {
const cosLat = Math.cos((lat * Math.PI) / 180.0);
const sinLat = Math.sin((lat * Math.PI) / 180.0);
const cosLon = Math.cos((lon * Math.PI) / 180.0);
const sinLon = Math.sin((lon * Math.PI) / 180.0);
const rad = 6378137.0;
const f = 1.0 / 298.257224;
const C =
1.0 / Math.sqrt(cosLat * cosLat + (1 - f) * (1 - f) * sinLat * sinLat);
const S = (1.0 - f) * (1.0 - f) * C;
const h = alt;
const x = (rad * C + h) * cosLat * cosLon;
const y = (rad * C + h) * cosLat * sinLon;
const z = (rad * S + h) * sinLat;
return { x, y, z };
}
Your normalise and raster steps are cancelling out the view port scaling you need. Multiplying out this:
const xNormalized = (xPerspective + viewPort.width / 2) / viewPort.width;
gives you:
const xNormalized = xPerspective / viewPort.width + 0.5;
And applying this line:
const xRaster = Math.floor(xNormalized * viewPort.width);
gives you:
const xRaster = Math.floor(xPerspective + viewPort.width * 0.5);
Your calculation of xPerspective is correct (but see comment below) - however the value is going to be around 1 looking at your numbers. Which is why the point is near the centre of the screen.
The correct way to do this is:
const xRaster = Math.floor(xPerspective * viewPort.width /2 + viewPort.width /2);
You can simplify that. The idea is that xPerspective is the tan of the angle that xRelative subtends at the eye. Multiplying the tan by half the width of the screen gives you the x distance from the centre of the screen. You then add the x position of the centre of the screen to get the screen coordinate.
Your maths uses an implicit camera view which is aligned with the x, y, z axes. To move the view around you need to calculate xRelative etc relative to the camera before doing the perspective divide step (division by zRelative). An easy way to do this is to represent your camera as 3 vectors which are the X,Y,Z of the camera view. You then calculate the projection of the your 3D point on your camera by taking the dot product of the vector [xRelative, yRelative, zRelative] with each of X,Y and Z. This gives you a new [xCamera, yCamera, zCamera] which will change as you move your camera. You can also do this with matrices.
I'm trying to develop a small application using html5 and canvas/KineticJS. I'd like to trace a number of rays that start from a 2d point to infinite, just setting a custom angle degree. For example, if I set 90° the app should render four rays (two straight lines, one vertical and one horizontal that meet in my 2d point). If I set 60° I should see 3 straight lines, like an asterisk *
The longest line you'll ever have to draw is the size of the canvas's diagonal:
var r = Math.sqrt(Math.pow(canvas.width, 2) + Math.pow(canvas.height, 2));
Use sin and cos to calculate each of your end points at that radius:
var theta = delta * Math.PI / 180.0;
var dx = r * Math.cos(n * theta);
var dy = r * Math.sin(n * theta);
Then, just draw lines from (x, y) to (x + dx, y + dy). Simples.
I'm trying to write a small 'perspective' javascript app that allows me to fly through a set of x,y,z points that inhabit a 3d space.
I have the concept of a camera which changes its rotation and xyz position, while each point maintains a constant xyz point.
I then have a set of equations that works out how the camera's x,y,z coordinates should be adjusted for flying directly forwards. The x,y,z adjustments obviously depend upon the rotation of the camera.
It almost works, but at certain 'attitudes' the camera position adjustment goes wrong and the flightpath doesn't go straight ahead but goes off at an angle, or even reverses. The equations for working out the projection are as follows:
var directionFactor = 1;
if (direction == 'backward') directionFactor = -1;
sx = Math.sin(cameraView.rotX);
cx = Math.cos(cameraView.rotX);
sy = Math.sin(cameraView.rotY);
cy = Math.cos(cameraView.rotY);
sz = Math.sin(cameraView.rotZ);
cz = Math.cos(cameraView.rotZ);
// Z-Axis
ztrig = Math.sqrt((cx * cx) + (cy * cy)) * (cx * cy);
cameraView.z = cameraView.z + directionFactor *
(Math.abs(airspeed / 15) * ztrig);
// Y-Axis
ytrig = Math.sqrt((sx * sx) + (cz * cz)) * (sx * cz);
cameraView.y = cameraView.y + directionFactor *
(Math.abs(airspeed / 15) *ytrig);
// X-Axis
xtrig = Math.sqrt((cz * cz) + (sy * sy)) * (cz * sy);
cameraView.x = cameraView.x - directionFactor *
(Math.abs(airspeed / 15) * xtrig);
Obviously my equations aren't quite right. Can anyone tell me where I'm going wrong? Much appreciated and thanks.
You have some errors in your equations. (They are valid in the 2d case but not in 3d)
when you calculate
sx = Math.sin(cameraView.rotX);
It does make sense in 2d since :
sx = Math.sin(cameraView.rotX) = x/SQRT(y*y + x*x)
where (x, y) is the position of the camera.
But in 3d it's more complicated :
In 3d :
Thus to obtain the cartesian coordinate :
You may also use 3d matrix to perform rotation.
Is there a way to translate into javascript a piece of code that will allow me to show map pins around a point taking in consideration a radius ?
var data=[
{long:3,lat:2},
{long:5,lat:2},
{long:2,lat:3}
];
aCoord={long:1,lat:2};
for(var i=0;i<data.length;i++){
if (data[i] is 30 kms far from aCoord)
myMap.addPin(data[i]);
}
myMap.autozoom();
Thank you,
Regards
I came up with this example so you have an idea on how to calculate the points. You'll need to figure out how to do any necessary conversions for lat/lon.
/**
* Returns coordinates for N points around a circle with a given radius from
* the center.
*
* center: array [x, y]
* radius: int
* num_points: int
*/
function get_points_on_circle(center, radius, num_points) {
if (!num_points) num_points = 10;
var interval = Math.PI * 2 / num_points;
points = [];
i = -1;
while (++i < num_points) {
var theta = interval * i,
point = [Math.cos(theta) * radius + center[0], Math.sin(theta) * radius + center[1]];
points.push(point);
}
return points;
}
// Sample usage
var center = [250, 250],
radius = 100,
num_points = 10;
var points = get_points_on_circle(center, radius, num_points);
Test it out (uses Raphael for plotting)
If you are interested in learning a little about the logic:
A radian is a unit of measure for angles. There are a total of 2*PI radians in a circle. Using that fact, you can calculate the angle interval of any number of points on a circle by performing 2*PI/num_points.
When you know the angle interval, you can calculate the angle (theta) of a point on a circle. Once you have theta (the angle), you have polar coordinates (radius,angle). For that to be of any use to us in this problem, you need to convert the polar coordinates into Cartesian coordinates (x,y). You can do that by using the following formulas:
x = cos(theta) * radius
y = sin(theta) * radius
That's pretty much it in a nutshell.