Hi i'm starting to work with webGL using Three.js and I need to detect if a click on a sphere is within a certain section of it's surface.
Currently i can detect if the sphere was clicked and get the coords of the point clicked. Now what i need is to detect if that click was in a certain region of that sphere, based on a array of 3D points from that sphere (another suggestion is ok).
The sphere is in the center point, and the point is garrantied to be on the surface of the sphere. Now i need to calculate if it's just within a section section. Any suggestions? My problem seams to be more mathematical.
Also i prefere a generic way to do this because the sections may be just a triangle or may be more complex figures.
My first idea was to project your 3D points on to the screen coordinates (ie from world coordinates to view coordinates, exactly as in drawing the shape on the screen). This gives you the visual region that corresponds to the surface-of-interest. This would be a simple 3D-to-2D projection using your view, and then you can see if the click location lies in the 2D polygon.
Then I realised a problem with this approach, which is that it won't work if your region-of-interest goes around onto the back-surface of the sphere.
If this is a problem, you will need to construct the projection of your mouse click along the camera direction. If you are using an isometric camera, this should be possible...
Draw a (great-circle) ray from the point. Find the nearest intersection with a segment of the curve. The point is inside the curve if and only if the segment crosses the ray from right to left.
One solution is to render a pseudocolor image where your areas each have a color of their own to a texture. Then sample the image, use the pesudocolor as array index. * for parctical reason the encoding should spread values around a bit.
I've ended using a different method then the suggestions.
I'm using matrix determinants where: (T1, T2, T3) are points that form a triangle and X is the point i want to know if it's inside this triangle, then i simple calculate 3 determinants where:
d1 = det([T1 T2 X])
d2 = det([T1 X T3])
d3 = det([T1 T2 X])
If all determinants are the same sign, then the point is inside the triangle.
Now i form a list of triangles based on the selection area and check if the point is inside one of those triangles.
this.Detector.triangleDetector = function(position, triangleArray){
for(var idxString in triangleArray){
var index = parseInt(idxString);
if(this.pointInTriangle(position, triangleArray[index].coords1, triangleArray[index].coords2, triangleArray[index].coords3))
return true;
}
return false;
}
The function pointInTriangle(x,t1,t2,t3) does the determinant verification.
this.Detector.pointInTriangle = function(x,T1,T2,T3){
var array1 = [coord1.x ,coord1.y ,coord1.z];
var array2 = [coord2.x ,coord2.y ,coord2.z];
var array3 = [coord3.x ,coord3.y ,coord3.z];
var zero = 0;
var A = [[zero,zero,zero],[zero,zero,zero],[zero,zero,zero]];
var d1,d2,d3;
A[0][0] = position.x;
A[0][1] = position.y;
A[0][2] = position.z;
A[1][0] = array2[0];
A[1][1] = array2[1];
A[1][2] = array2[2];
A[2][0] = array3[0];
A[2][1] = array3[1];
A[2][2] = array3[2];
d1 = MyMath.determinant(A,3);
A[0][0] = array1[0];
A[0][1] = array1[1];
A[0][2] = array1[2];
A[1][0] = position.x;
A[1][1] = position.y;
A[1][2] = position.z;
d2 = MyMath.determinant(A,3);
A[1][0] = array2[0];
A[1][1] = array2[1];
A[1][2] = array2[2];
A[2][0] = position.x;
A[2][1] = position.y;
A[2][2] = position.z;
d3 = MyMath.determinant(A,3);
if((d1>=0 && d2 >=0 && d3>=0) || (d1<=0 && d2 <=0 && d3<=0)){
return true;
}
return false;
};
Related
I want to develop a game "Cut the Shape" in JavaScript. Several components with convex polygons that will need to be clipped depending on the number of convex freedom faces. Lines and polygons are displayed on the Canvas. The Paper.js graphics load to use.
But then the question arose, which concerns the correct separation of the polygons from each other relative to the drawn line, I just can’t think of a way to do this.
Here is an example on simple polygons (the lines can be absolutely any, the user draws them himself):
I got to the points of dividing the polygon into other polygons using small dots with a line:
var polygon = new Path.RegularPolygon(new Point(200, 300), 4, 100);
polygon.strokeColor = 'blue';
polygon.fullySelected = true;
var shapesArray = [];
shapesArray.push(polygon);
function splitShape(path1, path2){
var shapesArrayCopy = path1.slice(0);
shapesArray = [];
for(var i = 0; i < shapesArrayCopy.length; i++){
var intersections = shapesArrayCopy[i].getIntersections(path2);
if(intersections.length >= 2){
var p1 = shapesArrayCopy[i].split(shapesArrayCopy[i].getNearestLocation(intersections[0].point));
var p2 = shapesArrayCopy[i].split(shapesArrayCopy[i].getNearestLocation(intersections[1].point));
p1.closed = true;
p2.closed = true;
shapesArray.push(Object.assign(p1));
shapesArray.push(Object.assign(p2));
path2.visible = false;
}
else{
shapesArray.push(shapesArrayCopy[i])
}
}
var myPath;
function onMouseDown(event) {
myPath = new Path();
myPath.strokeColor = 'black';
myPath.add(event.point);
myPath.add(event.point);
}
function onMouseDrag(event) {
myPath.segments.pop();
myPath.add(event.point);
}
function onMouseUp(event) {
splitShape(shapesArray, myPath)
myPath.visible = false;
}
In 2D You just displace the cuts in perpendicular direction to the cut line. If your cut line endpoints are: p0(x0,y0),p1(x1,y1) then the line direction is:
dp = p1-p0 = (x1-x0,y1-y0)
make it unit:
dp /= sqrt((dp.x*dp.x)+(dp.y*dp.y)
make it equal to half of the gap between cuts:
dp *= 0.5*gap
now tere are two perpendicular directions:
d0 = (-dp.y,+dp.x)
d1 = (+dp.y,-dp.x)
so now just add d0 to all vertexes of one cut, and d1 to the other one. Which use for which is simply you take point that does not lie on the cutting line (for example avg point of your cut) p and compute (only once for polygon cut):
t = dot(p-p0,d0) = ((p.x-x0)*d0.x)+((p.y-y0)*d0.y)
if (t>0) use d0, if (t<0) use d1 and if (t==0) you chose wrong point p as it lies on cutting line.
About 6 months ago i started making a 3d graphics engine.
Its already looking very good. I already implemented rotation, translation, scaling, Z-buffer(painter's algoritm),... Im now working on a specular shader. For that i need some way to get the angle of he individual faces
My question is, how do i get the angle of a plane by only knowing the position of the four corners?
Here is what i got so far:
function faceAngle(verts,faces){
var arr = [];
for(var i=0;i<faces.length;i++){
var posA = verts[faces[i][0]];//the four corners
var posB = verts[faces[i][1]];// A B
var posC = verts[faces[i][2]];// -----
var posD = verts[faces[i][3]];// | |
// | |
var ar = []; // -----
ar.push(/*some Maths*/);//x // D C
ar.push(/*some Maths*/);//y
ar.push(/*some Maths*/);//z
arr.push(ar);
}
return arr;
}
Orientation of plane in the space is defined by normal vector. To get this vector, calculate cross product of two edges (belonging to the plane). So you need only three non-collinear points in the plane.
n = (posB - posA) x (posC - posA) //cross product of two vectors
Note that components of normalized (unit) normal vector are direction cosines
Like this site http://www.gsmlondon.ac.uk/global-oil-map/
I would like to click on this marker, let this marker turn to the center of the screen.
Now know the mark latitude and longitude, how to turn after the click? I do not understand.
Interesting question, I tried Matrix to get right orientation, but the result is a little bit strange. I choose another way using spherical coordinates system, and it works now.
we need to get two points coordinates, one is the point on the sphere surface which is closest to the camera we note it as P(the line from camera to center of sphere intersect with the sphere). another point is where we click on the sphere surface, we note it as Q .
we use raycaster to get P and Q Cartesian Coordinates. and convert the Cartesian Coordinates to Spherical Coordinates(always described like (r,θ,φ)).
then, we calculate the angular displacement from Q to P. and make the displacement as an addition to sphere rotation.
Here is my snippet:
//get the point coordinates which a line from camera to sphere center intersect with the sphere
var vector = new THREE.Vector3().copy(sphere.position);
vector = vector.unproject(camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects([sphere],true);
var intersected_point = new THREE.Vector3().copy(intersects[0].point);
//calculate the intersected point spherical coordinates
var radius = sphere.children[0].geometry.parameters.radius;
var heading = Math.atan2(intersects[0].point.x,intersects[0].point.z);
var pitch = Math.asin(-(intersects[0].point.y)/radius);
document.addEventListener("click",OnDocumentClick,false);
function OnDocumentClick(event)
{
//get the point coordinates which you click on the sphere surface
var vector = new THREE.Vector3(( event.clientX / window.innerWidth ) * 2 - 1, -( event.clientY / window.innerHeight ) * 2 + 1, 0.5);
vector = vector.unproject(camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects([sphere],true);
if(intersects.length > 0)
{
//get click point spherical coordinates
var heading1 = Math.atan2(intersects[0].point.x,intersects[0].point.z);
var pitch1 = Math.asin(-(intersects[0].point.y)/radius);
//calculate displacement of click point to intersected point
var delta_heading = heading - heading1;
var delta_pitch = pitch - pitch1;
var target_pitch = parseFloat(sphere.rotation.x) +delta_pitch;
var target_heading = parseFloat(sphere.rotation.y) + delta_heading;
//using an animation to rotate the sphere
anime({
targets:sphere.rotation,
x:target_pitch,
y:target_heading,
elasticity: 0
});
}
}
At the end, I use an animation lib to make the rotation smooth.
Here is my demo:rotate the earth.
I made a little progress, the previous version has a little bit off. when I turn up and down the earth, I got a bad result. I think the code sphere.rotation.x += delta_pitch make sphere rotate on object axises. but what we need is making the sphere rotate on the world space axises. we know world axises coordinates are always x_axis = (1,0,0) ; y_axis = (0,1,0) ; z_axis = (0,0,1); then, I convert the world coordinates to object coordinates, Sphere matrix interpret sphere rotate from indentity rotation to current rotation. and the inverse matrix interpret the backword. so we can apply the inverse matrix to basic axises to get object space coordinates. make sphere rotate on new axises. I just make a little change in OnDcoumentClick function:
var heading1 = Math.atan2(intersects[0].point.x,intersects[0].point.z);
var pitch1 = Math.asin(-(intersects[0].point.y)/radius);
//get the sphere inverse matrix;
var sphere_matrix = new THREE.Matrix4().copy(sphere.matrix);
var inverse_sphere_matrix = new THREE.Matrix4();
inverse_sphere_matrix.getInverse(sphere_matrix);
//convert world space x and y axises to sphere object space coordinates.
var x_axis = new THREE.Vector3(1,0,0);
var y_axis = new THREE.Vector3(0,1,0);
x_axis.applyMatrix4(inverse_sphere_matrix);
y_axis.applyMatrix4(inverse_sphere_matrix);
//calculate displacement of click point to intersected point
var delta_heading = heading - heading1;
var delta_pitch = pitch - pitch1;
//make sphere rotate around whith world x and y axises.
sphere.rotateOnAxis(x_axis,delta_pitch);
sphere.rotateOnAxis(y_axis,delta_heading);
Here is my new demo: rotate earth new version.
I am trying to determine a way to calculate the number of meters represented by 1 pixel at a given zoom level and geo centerpoint in Leaflet. Could anyone direct me to the math involved or if there is a way to do this out of the box in leaflet? I am not finding much out there.
You can use the containerPointToLatLng conversion function of L.Map to get the latLngcoordinates for a given pixelcoordinate. If you take one of the first pixel, and one of the next, you can use the distanceTo utility method of L.LatLng to calculate the distance in meters between them. See the following code (assuming map is an instance of L.Map):
var centerLatLng = map.getCenter(); // get map center
var pointC = map.latLngToContainerPoint(centerLatLng); // convert to containerpoint (pixels)
var pointX = [pointC.x + 1, pointC.y]; // add one pixel to x
var pointY = [pointC.x, pointC.y + 1]; // add one pixel to y
// convert containerpoints to latlng's
var latLngC = map.containerPointToLatLng(pointC);
var latLngX = map.containerPointToLatLng(pointX);
var latLngY = map.containerPointToLatLng(pointY);
var distanceX = latLngC.distanceTo(latLngX); // calculate distance between c and x (latitude)
var distanceY = latLngC.distanceTo(latLngY); // calculate distance between c and y (longitude)
That should work, thanks to Jarek Piórkowski for pointing my mistake before the edit.
You can use this to work out the metres per pixel:
metresPerPixel = 40075016.686 * Math.abs(Math.cos(map.getCenter().lat * Math.PI/180)) / Math.pow(2, map.getZoom()+8);
Take a look at openstreetmap.org page on zoom levels. It gives this formula for calculating the meters per pixel:
The distance represented by one pixel (S) is given by
S=C*cos(y)/2^(z+8) where...
C is the (equatorial) circumference of the Earth
z is the zoom level
y is the latitude of where you're interested in the scale.
Correct me if I am wrong, IMHO, the number of meters per pixel = map height in meters / map height in pixels
function metresPerPixel() {
const southEastPoint = map.getBounds().getSouthEast();
const northEastPoint = map.getBounds().getNorthEast();
const mapHeightInMetres = southEastPoint.distanceTo(northEastPoint);
const mapHeightInPixels = map.getSize().y;
return mapHeightInMetres / mapHeightInPixels;
}
I am trying to create a Javascript web application where a user clicks on a canvas to drop an infinite amount of dots. There is solve button, that when clicked draws lines between the dots so that all dots are connected by exactly 2 other dots, and no lines can cross. With my code so far, there are certain instances where the lines still cross, and I can't programmatically figure out logic that will connect all the dots without any lines ever crossing.
So far, I collect all the points (X-Y coordinates) and put them in a JavaScript array of objects. I then need to sort the array so that it is in the correct order to be drawn. Everything works at this point except the order does not always satisfy the requirements.
My Question: Does anyone have any ideas on a set of rules that will order these points (X-Y coordinates) so that they all connect but never cross, that will work in every scenario?
Thanks for your help.
var centroid = get_polygon_centroid($points);
$points = _.sortBy($points, function(p){
var dx = p.coords.x-centroid.x;
var dy = p.coords.y-centroid.y;
return dx*dx + dy*dy;
});
$points = _.sortBy($points, function(p){
var dx = p.coords.x-centroid.x;
var dy = p.coords.y-centroid.y;
return Math.atan2(dy, dx);
});
$points.push($points[0]);
Here's an algorithm:
Find the center of mass ( O(n) time, where n is the number of points)
For each point, compute the angle from the center to that point ( O(n) time). This can be done with Math.atan2(p.y-c.y, p.x-c.x) in JS where p is the current point and c is the center.
Sort by angle ( O(n log n) time). For any angles that are exactly the same, sort by radius next, smallest to largest.
Connect pairs of points ai to ai+1 for every i from 0 to n-1 and also an-1 to a0
This should result in a connected graph where no two lines intersect in O(n log n) time.
Update:
This code should work.
//iterate through all points and calculate the center, c
var c = {x:0, y:0}, p;
for (p : points) {
c.x+=p.coords.x;
c.y+=p.coords.y;
}
c.x/=points.length;
c.y/=points.length;
points.sort(function(p1, p2){
var dx1 = p1.coords.x-c.x;
var dy1 = p1.coords.y-c.y;
var a1 = Math.atan2(dy1, dx1);
var dx2 = p2.coords.x-c.x;
var dy2 = p2.coords.y-c.y;
var a2 = Math.atan2(dy2, dx2);
//If angles are the same, sort by length
if (a1===a2){
var d1 = dx1*dx1 + dy1*dy1;
var d2 = dx2*dx2 + dy2*dy2;
return d1-d2;
}
//otherwise sort by angle
return a1-a2;
}
//Iterate through all Points and draw lines between them
var i;
for (i=0;i<n;i++){
//This is not real code \/
line(p[i], p[(i+1)%n]);
}