The API for Hull Geom states: "Assumes the vertices array is greater than three in length. If vertices is of length <= 3, returns []." (https://github.com/mbostock/d3/wiki/Hull-Geom)
I need to draw convex hulls around 2 nodes. I am using the force layout, so the convex hull needs to be dynamic in that it moves around the nodes if I click a node and drag it around. My code is currently based off of this example: http://bl.ocks.org/donaldh/2920551
For context, this is what I am trying to draw a convex hull around:
Here it works when there are 3 nodes:
Here is what I am trying to draw a convex hull around (doesn't work with the code from the example above because Hull Geom will only take arrays with 3+ vertices):
I understand the traditional use of a convex hull would never involve only two points, but I have tried drawing ellipses, rectangles, etc around the 2 nodes and it doesn't look anywhere near as good as the 3 nodes does.
I understand that Hull Geom ultimately just spits out a string that is used for pathing, so I could probably write a modified version of Hull Geom for 2 nodes.
Any suggestions on how to write a modified Hull Geom for 2 nodes or any general advice to solve my problem is really appreciated.
Basically, you need to at least one fake point very close to the line to achieve the desired result. This can be achieved in the groupPath function.
For d of length 2 you can create a temporary array and attach it to the result of the map function as follows:
var groupPath = function(d) {
var fakePoints = [];
if (d.values.length == 2)
{
//[dx, dy] is the direction vector of the line
var dx = d.values[1].x - d.values[0].x;
var dy = d.values[1].y - d.values[0].y;
//scale it to something very small
dx *= 0.00001; dy *= 0.00001;
//orthogonal directions to a 2D vector [dx, dy] are [dy, -dx] and [-dy, dx]
//take the midpoint [mx, my] of the line and translate it in both directions
var mx = (d.values[0].x + d.values[1].x) * 0.5;
var my = (d.values[0].y + d.values[1].y) * 0.5;
fakePoints = [ [mx + dy, my - dx],
[mx - dy, my + dx]];
//the two additional points will be sufficient for the convex hull algorithm
}
//do not forget to append the fakePoints to the input data
return "M" +
d3.geom.hull(d.values.map(function(i) { return [i.x, i.y]; })
.concat(fakePoints))
.join("L")
+ "Z";
}
Here a fiddle with a working example.
Isolin has a great solution, but it can be simplified. Instead of making the virtual point on the line at the midpoint, it's enough to add the fake points basically on top of an existing point...offset by an imperceptible amount. I adapted Isolin's code to also handle cases of groups with 1 or 2 nodes.
var groupPath = function(d) {
var fakePoints = [];
if (d.length == 1 || d.length == 2) {
fakePoints = [ [d[0].x + 0.001, d[0].y - 0.001],
[d[0].x - 0.001, d[0].y + 0.001],
[d[0].x - 0.001, d[0].y + 0.001]]; }
return "M" + d3.geom.hull(d.map(function(i) { return [i.x, i.y]; })
.concat(fakePoints)) //do not forget to append the fakePoints to the group data
.join("L") + "Z";
};
Related
Given a simple (non intersecting) polygon such as a floor plan (with doors between rooms missing so as to give 1 simple uninterrupted boundary). How can I find all areas within the polygon reachable from a (x, y) point (within or on the boundary of the polygon)? I’d ideally like from this to return a polygon which could be then overlaid to show all reachable areas.
I’ve considered A* search type methods where I would search the shortest path iterating over all points located on the polygon perimeter (as destination) then plotting new points at the set distance limit along the shortest path polyline to give a new polygon hull.
I’ve also thought about wave propagation as a method.
I’m wondering if I’m missing something obvious here library/method wise and if anyone has any other ideas of how I might achieve this.
Given a polygon like this:
I am creating a polygon showing internal space (excluding internal doors) like this:
This is the part my question refers to. I would like to find all reachable points within the polygon (shown in red as a new polygon) from a given point on the polygon boundary at a set maximum travel distance from that point (donated below with a red square) like this:
Triangulate the polygon.
If your chosen origin vertex is not a polygon vertex (i.e. it's a point within the polygon), include this point as a steiner point in the triangulation.
Build an undirected weighted graph from the triangulation's vertices and constrained edges (where graph edge weights are triangulation edge lengths).
Constrained edges are edges that do not lie outside the polygon.
Compute shortest paths from your origin vertex to all other vertices in the graph (using Dijkstra or Bellman-Ford algorithm). The path distance from the origin to a vertex is that vertex's Z value.
Update/create another triangulation mesh, using the same vertices with the Z values calculated from before.
Calculate distance values for every pixel by interpolating within/between triangles (interpolating based on each triangle's vertices' Z values). This is readily done by using barycentric coordinates. The interpolation output for a coordinate gives you the distance from the origin position to that coordinate.
For the illustrations below I used the NaturalNeighborInterpolator from the TinFour Java library. It eases the interpolation step by operating on a triangulation — I simply call the interpolator at each pixel coordinate and finally mask the output with the original polygon (since it effectively computes over the convex hull of the polygon).
Illustrative Code
The graph and Dijkstra implementation use the JGraphT library.
IncrementalTin tin = new IncrementalTin();
tin.add(listOfPolygonVertices); // triangulates upon insertion
Graph<Vertex, IQuadEdge> graph = new DefaultUndirectedWeightedGraph<>(IQuadEdge.class);
tin.edges().forEach(e -> {
if (e.isConstrainedRegionInterior() || e.isConstrainedRegionBorder()) {
graph.addVertex(e.getA());
graph.addVertex(e.getB());
graph.addEdge(e.getA(), e.getB(), e);
graph.setEdgeWeight(e.getA(), e.getB(), e.getLength());
}
});
DijkstraShortestPath<Vertex, IQuadEdge> shortestPaths = new DijkstraShortestPath<>(graph);
Vertex originVertex = tin.getNavigator().getNearestVertex(originX, originY);
var paths = shortestPaths.getPaths(originVertex);
IncrementalTin distanceMesh = new IncrementalTin();
for (Vertex v : graph.vertexSet()) {
var d = paths.getWeight(v);
distanceMesh.add(new Vertex(v.x, v.y, d)); // add vertices with Z to new tin
}
IInterpolatorOverTin interpolator = new NaturalNeighborInterpolator(distanceMesh);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
double z = interpolator.interpolate(x, y, null);
if (!Double.isNaN(z)) {
pixels[y * width + x] = someColour;
}
}
}
Update: Distance Boundary Vertices
If you want merely the distance boundary line, you can forgo step 5. Instead, compute (if applicable) the isoline for each triangle, based on the desired distance. If an isoline crosses a triangle (as in the illustration below), it will intersect with two of the triangle's edges — plotting a line segment between each pair of intersecting points for every such triangle give you a distance boundary.
Call a method (such as below) for each edge for every constrained triangle in the triangulation. If the distance isoline crosses the triangle, you'll get two points of intersection for that triangle; otherwise none.
/**
* Compute isoline vertex (if applicable) for a triangle side given by two vertices
*/
Vertex isoVertex(Vertex a, Vertex b, double d) {
Vertex min, max;
if (a.getZ() > b.getZ()) {
max = a;
min = b;
} else {
max = b;
min = a;
}
if (d > min.getZ() && d < max.getZ()) {
double diff = max.getZ() - min.getZ();
double numerator = d - min.getZ();
double fract = numerator / diff;
double xDiff = max.getX() - min.getX();
double yDiff = max.getY() - min.getY();
return new Vertex(min.getX() + fract * xDiff, min.getY() + fract * yDiff);
}
return null;
}
I have a Mesh created with a BufferGeometry.
I also have the coordinates of where my mouse intersects the Mesh, using the Raycaster.
I am trying to detect faces within(and touching) a radius from the intersection point.
Once I detect the "tangent" faces, I then want to color the faces. Because I am working with a BufferGeometry, I am manipulating the buffer attributes on my geometry.
Here is my code:
let vertexA;
let vertexB;
let vertexC;
let intersection;
const radius = 3;
const color = new THREE.Color('red');
const positionsAttr = mesh.geometry.attributes.position;
const colorAttr = mesh.geometry.attributes.color;
// on every mouseMove event, do below:
vertexA = new THREE.Vector3();
vertexB = new THREE.Vector3();
vertexC = new THREE.Vector3();
intersection = raycaster.intersectObject(mesh).point;
// function to detect tangent edge
function isEdgeTouched(v1, v2, point, radius) {
const line = new THREE.Line3();
const closestPoint = new THREE.Vector3();
line.set(v1, v2);
line.closestPointToPoint(point, true, closestPoint);
return point.distanceTo(closestPoint) < radius;
}
// function to color a face
function colorFace(faceIndex) {
colorAttr.setXYZ(faceIndex * 3 + 0, color.r, color.g, color.b);
colorAttr.setXYZ(faceIndex * 3 + 0, color.r, color.g, color.b);
colorAttr.setXYZ(faceIndex * 3 + 0, color.r, color.g, color.b);
colorAttr.needsUpdate = true;
}
// iterate over each face, color it if tangent
for (let i=0; i < (positionsAttr.count) /3); i++) {
vertexA.fromBufferAttribute(positionsAttr, i * 3 + 0);
vertexB.fromBufferAttribute(positionsAttr, i * 3 + 1);
vertexC.fromBufferAttribute(positionsAttr, i * 3 + 2);
if (isEdgeTouched(vertexA, vertexB, point, radius)
|| isEdgeTouched(vertexA, vertexB, point, radius)
|| isEdgeTouched(vertexA, vertexB, point, radius)) {
colorFace(i);
}
While this code works, it seems to be very poor in performance especially when I am working with a geometry with many many faces. When I checked the performance monitor on Chrome DevTools, I notices that both the isEdgeTouched and colorFace functions take up too much time on each iteration for a face.
Is there a way to improve this algorithm, or is there a better algorithm to use to detect adjacent faces?
Edit
I got some help from the THREE.js slack channel, and modified the algorithm to use Three's Sphere. I am now no longer doing "edge" detection, but instead checking whether a face is within the Sphere
Updated code below:
const sphere = new THREE.Sphere(intersection, radius);
// now checking if each vertex of a face is within sphere
// if all are, then color the face at index i
for (let i=0; i < (positionsAttr.count) /3); i++) {
vertexA.fromBufferAttribute(positionsAttr, i * 3 + 0);
vertexB.fromBufferAttribute(positionsAttr, i * 3 + 1);
vertexC.fromBufferAttribute(positionsAttr, i * 3 + 2);
if (sphere.containsPoint(vertexA)
&& sphere.containsPoint(vertexA)
&& sphere.containsPoint(vertexA)) {
colorFace(i);
}
When I tested this in my app, I noticed that the performance has definitely improved from the previous version. However, I am still wondering if I could improve this further.
This seem to be a classic Nearest Neighbors problem.
You can narrow the search by finding the nearest triangles to a given point very fast by building a Bounding Volume Hierarchy (BVH) for the mesh, such as the AABB-tree.
BVH:
https://en.m.wikipedia.org/wiki/Bounding_volume_hierarchy
AABB-Tree:
https://www.azurefromthetrenches.com/introductory-guide-to-aabb-tree-collision-detection/
Then you can query against the BVH a range query using a sphere or a box of a given radius. That amounts to traverse the BVH using a sphere/box "query" which is used to discard quickly and very early the Bounding Volume Nodes that does not clip the sphere/box "query". At the end the real distance or intersection test is made only with triangles whose BV intersect the sphere/box "query", typically a very small fraction of the triangles.
The complexity of the query against the BVH is O(log n) in contrast with your approach which is O(n).
I have to make 3d text from font glyphs. Yes, I know that I can use TextGeometry, but I need to draw this manually because I need to do offset on font splines.
At this moment I have splines with their points and I can draw letters.
From points I know: previousPoint, currentPoint and nextPoint and I need to compute bisector between previous and next points and I have no idea to do that.
Or if is another way to move spline points outer of initial position to do offset.
My idea:
Thank you!
EDIT:
With yours answers I obtained correct values for each splines from font, but only at 'o' and '0' I have a problem.
This method draw a weird tangent in bottom of the letter and I don't know to resolve this problem..
here is the result
Anybody know how to resolve this?
EDIT 2:
Finally I finished my project. And this is the final product ( .stl exporter )
final offset
Thank you for yours answers!
There are the result from: x = (prev_x + next_x) / 2 and y = (prev_y + next_y) / 2
wrong result
desired result
Here is my code where let points is all the points from the path:
getPathPoints(path) {
let points = path.getPoints();
console.log(points)
for (let i = 0; i < points.length; i++) {
let A = points.extended(i - 1); // previousPoint => where extends is a custom array prototype
let B = points.extended(i); // currentPoint
let C = points.extended(i + 1); // nextPoint
let x = (A.x + C.x) / 2;
let y = (A.y + C.y) / 2;
let bisector = new THREE.Vector2(x,y);
console.log(bisector);
}
}
What splines describe your glyphs?
I know that TTF fonts use quadratic Bezier curves. For Bezier direction vector in starting and ending points has direction onto control point. So difference
S = ControlPoint[1] - ControlPoint[0]
represents direction in the starting point, difference
E = ControlPoint[1] - ControlPoint[2]
represents direction in the ending point.
Normalize these vectors for two neighbour curves and add them - now you have bisector vector.
Bisector = E(i).Normalized + S(i+1).Normalized
I need to calculate the angle between 3 points. For this, I do the following:
Grab the 3 points (previous, current and next, it's within a loop)
Calculate the distance between the points with Pythagoras
Calculate the angle using Math.acos
This seems to work fine for shapes without angels of over 180 degrees, however if a shape has such an corner it calculates the short-side. Here's an illustration to show what I mean (the red values are wrong):
This is the code that does the calculations:
// Pythagoras for calculating distance between two points (2D)
pointDistance = function (p1x, p1y, p2x, p2y) {
return Math.sqrt((p1x - p2x)*(p1x - p2x) + (p1y - p2y)*(p1y - p2y));
};
// Get the distance between the previous, current and next points
// vprev, vcur and vnext are objects that look like this:
// { x:float, y:float, z:float }
lcn = pointDistance(vcur.x, vcur.z, vnext.x, vnext.z);
lnp = pointDistance(vnext.x, vnext.z, vprev.x, vprev.z);
lpc = pointDistance(vprev.x, vprev.z, vcur.x, vcur.z);
// Calculate and print the angle
Math.acos((lcn*lcn + lpc*lpc - lnp*lnp)/(2*lcn*lpc))*180/Math.PI
Is there something wrong in the code, did I forget to do something, or should it be done a completely different way?
HI there your math and calculations are perfect. Your running into the same problem most people do on calculators, which is orientation. What I would do is find out if the point lies to the left or right of the vector made by the first two points using this code, which I found from
Determine which side of a line a point lies
isLeft = function(ax,ay,bx,by,cx,cy){
return ((bx - ax)*(cy - ay) - (by - ay)*(cx - ax)) > 0;
}
Where ax and ay make up your first point bx by your second and cx cy your third.
if it is to the left just add 180 to your angle
I've got a working but not necessarily brief example of how this can work:
var point1x = 0, point1y = 0,
point2x = 10, point2y = 10,
point3x = 20, point3y = 10,
point4x = 10, point4y = 20;
var slope1 = Math.atan2(point2y-point1y,point2x-point1x)*180/Math.PI;
var slope2 = Math.atan2(point3y-point2y,point3x-point2x)*180/Math.PI;
var slope3 = Math.atan2(point4y-point3y,point4x-point3x)*180/Math.PI;
alert(slope1);
alert(slope2);
alert(slope3);
var Angle1 = slope1-slope2;
var Angle2 = slope2-slope3;
alert(180-Angle1);
alert(180-Angle2);
(see http://jsfiddle.net/ZUESt/1/)
To explain the multiple steps the slopeN variables are the slopes of the individual line segments. AngleN is the amount turned at each junction (ie point N+1). A positive angle is a right turn and a negative angle a left turn.
You can then subtract this angle from 180 to get the actual interior angle that you want.
It should be noted that this code can of course be compressed and that five lines are merely outputting variables to see what is going on. I'll let you worry about optimizing it for your own use with this being a proof of concept.
You need to check boundary conditions (apparently, if points are colinear) and apply the proper calculation to find the angle.
Also, a triangle can't have any (interior) angle greater than 180 degress. Sum of angle of triangle is 180 degrees.
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]);
}