Is an svg element in convex hull (path)? - javascript

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.

Related

Three JS Raycasting - Find point closest to cursor

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 :)

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!

Find polygon perimeter of points quickly in Javascript

I'm making a terrain editor and I need to find the perimeter polygon of a set of points. If I just needed a convex hull then the speed would be no issue. To make a concave hull, I must go through a few hoops. I've figured out that I can triangulate the points and then throw away any triangles with a side longer than the known distance between the points.
The next step is the problem: Combining the triangles (as mini polygons) into one large polygon using the JSTS geometry library (http://github.com/bjornharrtell/jsts) is really slow.
See the full code: http://codepen.io/anon/pen/oCfDh
I've got an array (polys) that gets merged to form the final polygon. The problem is that with 552 points (I want to support 15k+), it takes ~3500ms to run. Look at the console in the codepen link for your speed.
var reader = new jsts.io.WKTReader(),
merged = reader.read(polys[0]).union(reader.read(polys[1]));
console.time('jsts mergization');
for(var i = 2; i<polys.length; i++){
try{
merged = merged.union(reader.read(polys[i]));
}catch(err){
console.log('Error triangulating points!');
};
};
console.timeEnd('jsts mergization');
Does anybody know of any faster way to either merge triangles into a polygon or to go even wider and build a concave polygon from a set a points in a whole different way?
Thanks simonzack!
I've rewritten the algorithm using your suggestion and it's much faster!
Reworked codepen: http://codepen.io/anon/pen/Btdyj
The same example now runs in ~15ms!
function pointsToPolygon(points, triangles, maxEdgeLength){
console.time('homebrewed mergization');
var dist = function(a, b){
if(typeof a === "number"){
a = points[a];
};
if(typeof b === "number"){
b = points[b];
};
return Math.sqrt(Math.pow(a[0] - b[0], 2) +
Math.pow(a[1] - b[1], 2));
};
if(!points.length){
return undefined;
};
var pointFreq = [];
points.forEach(function(v){
pointFreq.push(0);
});
for(var i = triangles.length; i; i-=3){
if(dist(triangles[i-1], triangles[i-2]) < maxEdgeLength &&
dist(triangles[i-3], triangles[i-2]) < maxEdgeLength &&
dist(triangles[i-1], triangles[i-3]) < maxEdgeLength){
pointFreq[triangles[i-1]]++;
pointFreq[triangles[i-2]]++;
pointFreq[triangles[i-3]]++;
};
};
// Keep points that are used in 3 or fewer triangles
var output =[];
pointFreq.forEach(function(freq, i){
if(freq<4){
output.push(points[i]);
};
});
// Sort points by looping around by each next closest point
var sorted = [];
while(output.length>0){
sorted.push(output.pop());
output=output.sort(function(a,b){
var distA =dist(sorted[sorted.length-1], a),
distB =dist(sorted[sorted.length-1], b);
if(distA < distB){
return 1;
}else if(distA === distB){
return 0;
};
return -1;
});
};
sorted=simplifyPath(sorted,0.1);
console.timeEnd('homebrewed mergization');
return sorted;
};
I can find the boundary by filtering the points that are used in 3 or fewer triangles then sort points by looping around by each next closest point from any arbitrary point.
Maybe not 100% as accurate due to the Douglas-Peucker simplification algorithm (adapted from https://gist.github.com/adammiller/826148) but it seems good enough for me.

How to Manipulate a line in Raphael to add/edit points on the line

I have quite a few years experience coding but very minimal javascript and raphael. I've been looking online and in a book I bought on raphael. Part of what I am trying to do is to have a line / path and provide the user the capability to add or edit points to this line.
Something similar to the route on google maps.
I know that a path can be constructed using arrays. I believe those elements can have a unique id that will help accessing them.
I'm thinking something similar to one of the approaches suggested in this question, in particular Adam MoszczyƄski's approach with regards to separating the drawing from the data; especially as down the road it's likely that this path data will need to be persisted/loaded.
The SVG path syntax is fairly simple if all you want to do is draw straight lines. You really only need to know two commands: M to set the location of the first point on the path and L to draw a line to the rest of the points.
For example, to draw the following polyline:
[5,2]___________[12,2]
/ \
/ \
/ [13,5]
/
[1,10]
you would use the following SVG path:
M 1 10 L 5 2 L 12 2 L 13 5
Note that SVG accepts , as number separators and spaces between commands and arguments are optional if they're unambiguous. So the above path can also be written as:
M 1,10 L 5,2 L 12,2 L 13,5
or even:
M1,10L5,2L12,2L13,5
But the first form is the most convenient for us to use programatically because it can simply be constructed by joining array elements with the space character:
var svgpath = [
'M', 1, 10,
'L', 5, 2,
'L', 12, 2,
'L', 13, 5
].join(' ');
Given this, it's easy to write a function that draws a path in Raphael. There are two ways this can be done:
Write your own independent line drawing function/library and use Raphael just as a tool to draw to screen. The simplest is just a function that draws the polyline.
function draw_polyline(paper,coords) {
var first = coords.shift();
var path = ['M', first[0], first[1]];
for (var i=0; i<coords.length; i++) {
path.push('L',coords[i][0],coords[i][1]);
}
return paper.path(path.join[' ']);
}
draw_polyline(paper,[[1,10],[5,2],[12,2],[13,5]]);
Write it as an extension to Raphael. The function is exactly the same but behaves as if it's part of Raphael:
Raphael.fn.polyline = function (coords) {
var first = coords.shift();
var path = ['M', first[0], first[1]];
for (var i=0; i<coords.length; i++) {
path.push('L',coords[i][0],coords[i][1]);
}
return this.path(path.join[' ']);
}
var paper = Raphael('div',640,480);
paper.polyline([[1,10],[5,2],[12,2],[13,5]]);
Note that the simple function above simply draws the line from the given coordinates. To add or remove points to the line you'll have to redraw the path by calling the polyline function with the updated coordinates. This works but is not exactly ideal since you'll have to keep track of the old line and delete it when you draw the new line.
A better solution is to have the line update itself. To do this we'll need to refactor the code above to separate the svg path generation from the drawing function:
function polyline_path(coords) {
var first = coords.shift();
var path = ['M', first[0], first[1]];
for (var i=0; i<coords.length; i++) {
path.push('L',coords[i][0],coords[i][1]);
}
return path.join(' ');
}
then the Raphael implementation is simply:
Raphael.fn.polyline = function (coords) {
return this.path(polyline_path(coords));
}
Now we can add some functionality to the Raphael path object that represents our polyline:
Raphael.fn.polyline = function (coords) {
var path = this.path(polyline_path(coords));
path.coords = function(xy) {
if (!xy) {return coords} // return coordinates if no arguments given
coords = xy;
path.attr('path',polyline_path(coords)); // otherwise update svg path
}
return path;
}
Now we can use it like this:
var route = paper.polyline([[1,10],[5,2],[12,2],[13,5]]);
// Add point to route:
route.coords(route.coords().push([15,10]));
That's still a bit ungainly but you get the idea. You can use this basic concept to implement more advanced API. For example, it would be nice if the polyline object implement most of the Array methods so you can simply do:
route.push([15,10]); // add point to route
route.shift(); // remove point from beginning of route
route.splice(1,1,[5,3]); // modify the second point
// etc..
It's really up to you how far you want to take this.

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