Three JS Raycasting - Find point closest to cursor - javascript

Three.js r85
When raycasting with Three JS, a series of points is returned, and I'd like to find the point that is closest to the cursor. The first point returned seems to be the point that is closest to the camera.
Is there a way to find the distance between the cursor position and a point?
Here's the code I'm using to debug this right now:
var raygun = new THREE.Raycaster();
raygun.setFromCamera(mouse, camera);
var hits = raygun.intersectObjects([plotpoints]);
if (hits.length > 0) {
scope.remove(dotPlot);
scope.remove(dotPlot2);
// All points except the first one - Grey
dotGeo = new THREE.Geometry();
for (var i=1; i < hits.length; i++) {
dotGeo.vertices.push(plotpoints.geometry.vertices[hits[i].index]);
}
dotPlot = new THREE.Points(dotGeo, dotMat);
scope.add(dotPlot);
// First point - Orange
var geo2 = new THREE.Geometry();
geo2.vertices.push(plotpoints.geometry.vertices[hits[0].index]);
dotPlot2 = new THREE.Points(geo2, dotMat2);
scope.add(dotPlot2);
scope.render();
}
And here's what I'm seeing:

Ah, figured it out with math!
First thing to note is that hits[].points returns a point directly under the cursor, but it doesn't "snap" to points.
In order to get the actual position of the point, we need to use hits[].index first to get the index number of the point/vertex we hit. We can then access that vertex directly by using GEOMETRY.vertices[] which returns a THREE.Vector3 of the vertex point we hit with our raycast.
So by feeding in the index, we can get the exact position of each vertex hit by our raycast:
GEOMETRY.vertices[hits[i].index]
This provides rudimentary "snapping" to vertices.
Note: When using THREE.LineSegments, the result will always be the starting point, and not the ending point. To get the ending point, you can just add 1 to the index value:
GEOMETRY.vertices[hits[i+1].index]
To snap directly to the vertex closest to the cursor, we need to find the vertex that has the shortest perpendicular distance from the raycaster's ray. To do this we use a cross product of 2 vectors. This is more of a math concept than a programming concept though, so if you want to understand the why behind this, look up something like: perpendicular distance from a point to a line
I just took the code from this question and translated it: http://answers.unity3d.com/questions/568773/shortest-distance-from-a-point-to-a-vector.html
And the end result:
// Variables to record and compare
var smallestDist = 99;
var smallestPointIndex = 0;
// Declare variables outside of loop to save memory
var m_ray = raycaster.ray;
var raydir = m_ray.direction;
var origin = m_ray.origin;
var hitray = new THREE.Vector3(0,0,0);
var dist = 1;
// Loop over all points to find the closest
for (var i=0; i<hits.length; i++){
// Math is magic
hitray.subVectors(plotpoints.geometry.vertices[hits[i].index], origin);
dist = new THREE.Vector3().crossVectors(raydir, hitray).lengthSq();
// Record the closest point
if (dist < smallestDist) {
smallestDist = dist;
smallestPointIndex = i;
}
}
// Now we can use that single point
Here's the result :)

Related

Move Camera to make all objects fit exactly inside the frustum - three.js

EDIT : I have rephrased my question to help users with the same problem.
I have a three.js scene on which I have added some spheres.
I want to move the camera towards a specific direction until all the objects (which are randomly positioned inside the scene) are "fitting exactly" the user's screen.
I have found the answer to my problem!
1. I move the camera (zooming to the desired direction) inside a loop, and in every repeat I create a new frustum using the camera's matrix
2. I check if any of my spheres intersects with a plane of the frustum. If it does, that means that part of one of my objects is outside the frustum so I break the loop and move the camera to its last position.
The above might also works for any object (not only spheres) because every object has a boundingSphere that can be calculated (it might not be very precise the result though).
It also works when zooming out, you 'd just have to move the camera from the object until none of the has a negative distance from all the planes (negative distance means object is "outside" the plane of the frustum).
Code (only for zooming out - r72) :
var finished = false;
var camLookingAt = /* calc. */ ;
while( finished === false ){
var toDirection= camera.position.clone().sub(camLookingAt.clone());
toDirection.setLength(vec.length() - 1); // reduce length for zooming out
camera.position.set(toDirection.x, toDirection.y, toDirection.z);
camera.updateMatrix(); // make sure camera's local matrix is updated
camera.updateMatrixWorld(); // make sure camera's world matrix is updated
var frustum = new THREE.Frustum();
frustum.setFromMatrix( new THREE.Matrix4().multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ) );
for (var j = frustum.planes.length - 1; j >= 0; j--) {
var p = frustum.planes[j];
for (var i = myMeshSpheres.length - 1; i >= 0; i--) {
var sphere = new THREE.Sphere(myMeshSpheres[0].position.clone(), myMeshSpheres[0].radius);
if( p.distanceToSphere(sphere) < 1 ){ // if is negative means part of sphere is outside plane/frustum
finished = true;
}
}
}

Dragging point along vector with mouse

I've been experimenting with trigonometry for the past few days, and came up with one of those neat stat pentagons you find in some games. (fiddle!)
I'd really like to allow the vertices of the inner polygon to be draggable to change the stat values. I have mouse functionality working well, but what's the best way to drag a point on the line with the mouse?
I've created a picture to visualize my problem; the red polygon is the "current" polygon, the pink lines represent the new polygon, the pink circle emphasizes the new point for the vertex, the blue line is the vector tangent, and the green circle is the cursor.
I've written a program which deals with vectors before, but I'm not sure how to apply it to this situation.
Here's some code (in the loop function):
for(var i = 0; i < innerPolygonKnobs.length; i ++){
var knob = innerPolygonKnobs[i];
distX = knob.x-mouse.x;
distY = knob.y-mouse.y;
distTotal = Math.sqrt(distX*distX + distY*distY);
if(distTotal < 8){
if(!knob.over)change = true;
knob.over = true;
if(mouse.down){
// What goes here?
}
} else {
if(knob.over)change = true;
knob.over = false;
}
}
if(change)redraw();
Thanks so much in advance! :D
This function will give you the closest point to the mouse on any given line:
// given a line defined like this
var line={x0:50,y0:50,x1:150,y1:150};
// calculate the closest point on the line to [x,y]
function getClosestPointOnLine(line,x,y) {
//
lerp=function(a,b,x){ return(a+x*(b-a)); };
var dx=line.x1-line.x0;
var dy=line.y1-line.y0;
var t=((x-line.x0)*dx+(y-line.y0)*dy)/(dx*dx+dy*dy);
t=Math.min(1,Math.max(0,t));
var lineX=lerp(line.x0, line.x1, t);
var lineY=lerp(line.y0, line.y1, t);
return({x:lineX,y:lineY});
};
Then just redraw your inner polygon to connect to the point found above.
Interesting app...good luck with it!

Is an svg element in convex hull (path)?

I have an svg path which I can draw. With d3js I calculate a convex hull around the path with d3.geom.hull(...). Now I have some svg objects like nodes (svg:circle) and I want to find out whether the node is in the hull or not. How can I realize that? Here is a picture from my svg view:
EDIT:
My goal is to the node elements in the hull (which are within the path), not only at the edge of the path.
Here's an easy way to do that:
Calculate your hull geometry, get back the coordinates array that d3.geom.hull gives you.
Add your new point to your original data array and calculate d3.geom.hull again on this array.
Compare the array of points returned from step 1 with the array of points returned from step 2 to see if the calculated hull has changed. If it has, then the point is outside the convex hull. If there is no change, then it's inside the convex hull.
This might be performance-intensive if you have a really large dataset.
Here's some simple code to demonstrate:
// Some Random points
var coords = d3.range(50).map(function() {return [Math.random(),Math.random()]})
yourHull = d3.geom.hull(coords)
// Random new point
newCoord = [Math.random(),Math.random()]
coords.push(newCoord)
newHull = d3.geom.hull(coords)
//The easy case to spot
if (newHull.length != yourHull.length) {
console.log("Outside")
}
//If the array lengths are the same, the point values may have changed
else {
var outside = false;
for (var x = 0; x < yourHull.length;x++) {
for (var y = 0; y < 2;y++) {
if (yourHull[x][y] != newHull[x][y]) {
outside = true;
break;
}
}
}
if (outside) {
console.log("outside")
}
else {
console.log("on the hull")
}
}
The fastest way of doing this is to have the browser do all the actual work. In particular, use the method document.getElementFromPoint() to have the rendering engine determine the overlap.
The idea is simple -- add the point you're interested in behind the hull, then check whether the above method gives you the point or the hull. The code looks like this.
function isInside(point) {
var c = svg.insert("circle", "path.hull")
.attr("r", 1)
.attr("cx", point[0])
.attr("cy", point[1]);
var bounds = c.node().getBoundingClientRect();
var atPoint = document.elementFromPoint(bounds.left, bounds.top);
var inside = atPoint == c.node() ? false : true;
c.remove();
return inside;
}
The only slightly tricky bit is to convert the relative coordinates of the point to absolute coordinates -- the above code assumes that the SVG is a top-level element on the page itself or not translated by the containing elements. If this is not the case, adjust the code as appropriate.
The big advantage over the other answer is that the runtime does not depend on the size of the hull (as in the number of points defining it). It only depends on the number of points you want to check.
Complete demo here.

Custom THREE.Curve.create displays incorrectly

As suggested in this answer, I've created a linearly interpolated curve like this:
THREE.Linear3 = THREE.Curve.create(
function ( points, label /* array of Vector3 */) {
this.points = (points == undefined) ? [] : points;
this.label = label;
},
function ( t ) {
var v = new THREE.Vector3();
var c = [];
var points = this.points, point, intPoint, weight;
point = ( points.length - 1 ) * t;
intPoint = Math.floor( point );
weight = point - intPoint;
c[ 1 ] = intPoint;
c[ 2 ] = intPoint > points.length - 2 ? points.length - 1 : intPoint + 1;
var pt1 = points[ c[1] ],
pt2 = points[ c[2] ];
v.copy( pt1 ).lerp( pt2, weight );
return v;
}
);
However, when I'm trying to display a trajectory at different lengths (in an animated kinda-way) I get the following behavior i.e. instead of the curve going through the points, it kinda cuts through the space, note that in the example below each trajectory is supposed to go through the coordinates of each of the spheres (animated gif below):
I am not sure I understand the getPoint function or what is it supposed to return. Any Help is greatly appreciated.
JSFiddle
This is a minimal example but you can see how the right corner has a jerky motion as the tube expands.
http://jsfiddle.net/ElDeveloper/3uyf3sq3/1/
Cleaning some code
That helped me investigate the issue.
You are leaking geometries, you need to dispose the geometry after removing the mesh from the scene
scene.remove(c_mesh)
c_tube && c_tube.dispose();
Use WebGLRenderer. The CanvasRenderer leaks removed objects, and you're creating new objects on each frame. (If you're stuck with CanvasRenderer for some reason, sorry for you)
(For the fiddle) slow the motion, requestAnimationFrame isn't required for a test, setTimeout(animate, 500); allows the user to see what's happening.
What's the point of a 0-segment tube ?
if (index >= points.length - 1){
index = 1; //start with 2 points
}
Works as expected
The TubeGeometry does a tube of N (second argument in constructor, 16 in fiddle) segments. (I'll come back to that later, but I don't think you always want 16 segments)
The default behaviour of Curve.getPoinAt (the method used by TubeGeometry and probably lots of other geometries) is to return equidistant points. You can expect: distance(getPointAt(0),getPointAt(0.1)) == distance(getPointAt(0.1),getPointAt(0.2)) to be true.
Because of these points, no matter how many points you put in your path, the TubeGeometry will build a 16-segments tube with all segment of the same length, going from the first to the last point of your path. There is little chance that one of the 15 intermediate points will be exactly at the position of an edge. That should explain what you see.
Trying to fix the stuff
First get rid of that equidistant way-to-be of the TubeGeometry+Path. Overloading getUtoTmapping should be enough (I found that reading the source):
THREE.Linear3.prototype.getUtoTmapping = function(u) {
return u;
};
I changed your getPoint. It probably does the same thing, but I was more comfortable with my code the investigate
function ( t ) {
var points = this.points;
var index = ( points.length - 1 ) * t;
var floorIndex = Math.floor(index);
if(floorIndex == points.length-1)
return points[floorIndex];
var floorPoint = points[floorIndex];
var ceilPoint = points[floorIndex+1];
return floorPoint.clone().lerp(ceilPoint, index - floorIndex);
}
Give the correct number of segments to the TubeGeometry constructor:
var pathPoints = points.slice(0, index);
c_path = new THREE.Linear3(pathPoints, 'Test');
c_tube = new THREE.TubeGeometry(c_path, pathPoints.length-1, 10, 16, false, true);
At this point, you should have roughly what you were expecting
You should see the tube always going through the edges. You should also see that the TubeGeometry isn't meant to have angles. You could improve this angle issue either by looking at how TubeGeometry and Curve handles tangents, or (if you don't care about slowness) by increasing the number of segments to a very large number:
c_tube = new THREE.TubeGeometry(c_path, 200/*pathPoints.length-1*/, 10, 16, false, true);
That's all for the answer. You can find the last version of my experiments here: http://jsfiddle.net/dqn73m98/5/. You can also ask the three.js developers if such a feature exists, or request an implementation in a github issue (if someone as time to do it), here

Google Maps API V3 - Showing progress along a route

I want to show a custom route on a route along with the current progress. I've overlayed the a Polyline to show the route and I am able to find the LatLng of the current postion and place a marker. What I want to do now is to highlight the traveled part of the Polyline using a different colour. I'm pretty sure it's not possible to have multiple colours for one Polyline so I plan to overlay a second Polyline over the first the first to give this effect. Here's my question:
Knowing the LatLng of the current position and armed with the array of LatLng points used to create the orginal Polyline how best can I create the second 'Progess' route?
Thanks ahead.
With a few assumptions:
Your path is quite dense (if not, you could interpolate intermediate points)
Your route doesn't overlap with itself (wouldn't work with a repeating circular path, say)
..one crude way would be as follows:
Using Python'y pseudo-code, say you have a route like this:
points = [LatLng(1, 1), LatLng(2, 2), LatLng(3, 3), LatLng(4, 4)]
You draw this as a PolyLine as usual.
Then, given your current position, you would find the nearest point on the route:
cur_pos = LatLng(3.1, 3.0123)
nearest_latlng = closest_point(points, to = cur_pos)
Then nearest_latlng would contain LatLng(3, 3)
Find nearest_latlng in the list, then simply draw a second polyline up to this point. In other words, you truncate the points list at the current LatLng:
progress_points = [LatLng(1, 1), LatLng(2, 2), LatLng(3, 3)]
..then draw that on the map
As mentioned, this will break if the path loops back on itself (closest_point would only ever find the first or last point)
If you know how far has been travelled, there is an epoly extension which gives a few methods that could be used, mainly:
.GetIndexAtDistance(metres)
Returns the vertex number at or after the specified distance along the path.
That index could be used instead of the closest_point calculated one above
Typescript example:
getRouteProgressPoints(directionResult: google.maps.DirectionsResult | undefined, targetDistance: number) {
if (!directionResult || targetDistance <= 0) return [];
const route = directionResult.routes[0];
const leg: google.maps.DirectionsLeg = route.legs[0];
const routePoints: google.maps.LatLng[] = [];
// collect all points
leg.steps.forEach((step) => {
step.path.forEach((stepPath) => {
routePoints.push(stepPath);
});
});
let dist = 0;
let olddist = 0;
const points = [];
// go throw all points until target
for (let i = 0; i < routePoints.length && dist < targetDistance; i++) {
const currentPoint = routePoints[i];
// add first point
if (!i) {
points.push(currentPoint);
continue;
}
const prevPoint = routePoints[i - 1];
olddist = dist;
// add distance between points
dist += google.maps.geometry.spherical.computeDistanceBetween(prevPoint, currentPoint);
if (dist > targetDistance) {
const m = (targetDistance - olddist) / (dist - olddist);
const targetPoint = new google.maps.LatLng(
prevPoint.lat() + (currentPoint.lat() - prevPoint.lat()) * m,
prevPoint.lng() + (currentPoint.lng() - prevPoint.lng()) * m,
);
points.push(targetPoint);
} else {
points.push(currentPoint);
}
}
return points;
}
That is going to be really difficult unless you already have a way of determining that the vehicle has passed certain points already. If you are aware that it has passed certain points you just create the second polyline with the passed points and the current marker as the end of the polyline. You will probably want to change the width of the polyline in order to make it viewable when it is overlaying the initial route.
All of that being said if you don't know if the points have been passed I am not sure what you can do other than write some sort of code that determines if your current position is past a certain set of points, but I don't know how you do that if a route zigzags all over the place possibly..... Now if your points go from straight west to straight east etc, than coding something will be easy, but I doubt that is the case.
Hope this helps somewhat....

Categories