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.
Related
Each line segment has 2 xy coordinates given,
Input are below,
[3,4],[5,4]
[8,4],[20,4]
[10,4],[15,4]
In the above picture, if the lines are overlapping, it can be considered as a line segment. May I know the logic or mathematics behind solving it which outputs the 2 non-overlapping line segments which are [3,4],[5,4] and [8,4],[20,4]?
The input I gave is just 3 line segments that we have to filter out to non-overlapping line segments, so this gets complicated fast with more line segments if we don't have the proper mathematics. I am doing this because I faced this bug in my programming :).
I will appreciate any help that I can obtain :)
My solution is I have tried to find whether 2 lines are overlapping from by using some of the logic which is A.start <= B.end && B.start <= A.end as stated in here. The current code which I am stuck on this problem is published in codepen live demo here.
I reviewed the code and found there was nothing wrong with the logic. The problem was with the dimensions and input.
Here's the revised code.
function resolveOverlaps(lines) {
if (lines.length <= 1) return lines;
// Sort the lines ascending by start value
lines.sort((a, b) => a[0][0] - b[0][0]);
let outLines = [lines[0]];
let last = outLines[0];
// Iterate over the lines, skipping the first one
lines.slice(1).forEach((line) => {
// There's an overlap, so extend the current segment's end
if (line[0][0] <= last[1][0]) {
last[1][0] = Math.max(last[1][0], line[1][0]);
} else {
// No overlap, start a new segment
outLines.push(line);
last = outLines[outLines.length - 1];
}
});
return outLines;
}
Your drawing indicates that you're using two-dimensional data points, with x and y coordinates. Finding overlapping lines in 2D requires more sophisticated logic than just checking start and end points.
However, as your sandbox example is 1D, I'll address that.
In this solution, you iterate over each of the line segments, sorted by increasing start coordinate. As you encounter each subsequent segment, you choose to either combine the two or add a new segment, depending on whether they overlap or not.
function resolveOverlaps(lines) {
if (lines.length <= 1) return lines;
if (lines[0].length !== 2 || typeof lines[0][0] !== "number") {
throw new Error(
"Invalid input shape. resolveOverlaps requires a list of N tuples like `[[start0, end0], ..., [startN, endN]]`"
);
}
for (const line of lines) {
if (line[0] > line[1]) {
throw new Error(
`Invalid segment [${line[0]}, ${line[1]}]. Ensure startend.`
);
}
}
// Sort the lines ascending by start value
lines.sort((a, b) => a[0][0] - b[0][0]);
let outLines = [lines[0]];
let last = outLines[0];
// Iterate over the lines, skipping the first one
lines.slice(1).forEach((line) => {
// There's an overlap, so extend the current segment's end
if (line[0][0] <= last[1][0]) {
last[1][0] = Math.max(last[1][0], line[1][0]);
} else {
// No overlap, start a new segment
outLines.push(line);
last = outLines[outLines.length - 1];
}
});
return outLines;
}
Edit: Here's an implementation for 2D segments which assumes that all points lie on the same line, and thus only needs to check the x coordinate of each point. A general 2D solution requires more sophisticated logic than this.
function resolveOverlaps2d(lines) {
if (lines.length <= 1) return lines;
if (lines[0].length !== 2 || lines[0][0].length !== 2) {
throw new Error(
"Invalid input shape. resolveOverlaps requires a list of N tuples like `[[[x0_0, y0_0], [x0_1, y0_1]], ..., [[xN_0, yN_0], [xN_1, yN_1]]]"
);
}
for (const line of lines) {
if (line[0][0] > line[1][0]) {
throw new Error(
`Invalid segment [${line[0]}, ${line[1]}]. Ensure start <= end.`
);
}
}
// Sort the lines ascending by start value
lines.sort((a, b) => a[0][0] - b[0][0]);
let outLines = [lines[0]];
let last = outLines[0];
// Iterate over the lines, skipping the first one
lines.slice(1).forEach((line) => {
// There's an overlap, so extend the current segment's end
if (line[0][0] <= last[1][0]) {
last[1][0] = Math.max(last[1][0], line[1][0]);
} else {
// No overlap, start a new segment
outLines.push(line);
last = outLines[outLines.length - 1];
}
});
return outLines;
}
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 :)
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.
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
How can I prevent this map generator from creating touching corners like this:
-X
X-
Or
X-
-X
Here is a simplified example of the generator: http://jsfiddle.net/fDv9C/2/
Your question answers itself, almost.
Here's the fiddle: http://jsfiddle.net/qBJVY/
if (!!grid[y][x] && !!grid[y+1][x+1] && !grid[y+1][x] && !grid[y][x+1]) {
good=false;
grid[y+1][x]=2;
}
It simply checks for the combinations you do not want and patches them up. It always adds a grid point so as not to disconnect any parts of the map.
This in turn may lead to another situation where the issue may occur, but if it changed anything (that is, if it found a problem), it will simply check again. This can be optimized, for instance by recursively adjusting whatever was changed, but usually it only needs 1 or 2 passes. There's a limiter on there to not allow more than 100 passes, just in case there is some unforeseen circumstance in which it cannot fix it (I can't think of such a situation, though :) ).
Because of the way that you are creating board it's very difficulty to do this checking during generation. I create simple function that check board after. It's using flood algorithm. Here is the fiddle http://jsfiddle.net/jzTEX/8/ (blue background is original map, red background is map after checking)
Basically we create second array grid2. After filling grid we run recursively floodV function
function floodV(x,y) {
var shiftArray = [[0,1],[0,-1],[1,0],[-1,0]];
grid2[y][x]=1;
for(var k=0;k<4;k++) {
var x1=x+shiftArray[k][0];
var y1=y+shiftArray[k][1];
if(grid[y1][x1] == 1 && grid2[y1][x1] == 0 && checkV(x1,y1)) {
grid2[y1][x1] = 1;
floodV(x1,y1);
}
}
}
with the check function
function checkV(x,y) {
var checkVarr = [[-1,-1], [-1,1], [1,1], [1,-1]];
for(var k=0;k<4;k++) {
if(grid[y+checkVarr[k][0]][x+checkVarr[k][1]] == 1 && grid[y+checkVarr[k][0]][x] == 0 && grid[y][x+checkVarr[k][1]] == 0 && grid2[y+checkVarr[k][0]][x+checkVarr[k][1]] == 1)
return false;
}
return true;
}
This isn't perfect because we can sometimes throw away big parts of the map but if we try to start adding new elements we have to check whole map again (in worths case).
This is what I did: http://jsfiddle.net/fDv9C/13/
Where's the magic happening? Scroll down to lines 53 through 58:
var bottom = y_next + 1;
var left = x_next - 1;
var right = x_next + 1;
var top = y_next - 1;
if (grid[top][left] || grid[top][right] ||
grid[bottom][left] || grid[bottom][right]) continue;
In short your touching corner points can only occur at the computed next position. Hence if any one of the four corner neighbors of the next position exists, you must compute another next position.
You may even decrement the counter i when this happens to get as many paths as possible (although it doesn't really make a big difference):
var bottom = y_next + 1;
var left = x_next - 1;
var right = x_next + 1;
var top = y_next - 1;
if (grid[top][left] || grid[top][right] ||
grid[bottom][left] || grid[bottom][right]) {
i--;
continue;
}
See the demo here: http://jsfiddle.net/fDv9C/12/
Edit: I couldn't resist. I had to create an automatic map generator so that I needn't keep clicking run: http://jsfiddle.net/fDv9C/14/