Is there any function that will give me the intersection point of a Polygon and Line2D ?
I have a Polygon and a line segment that I know intersect I want the actual value of the intersection point not a boolean answer.
Here you are. The interesting methods are getIntersections and getIntersection. The former parses over all polygon segments and checks for intersections, the latter does the actual calculation. Do keep in mind that the calculation can be seriously optimized and doesn't check for division by 0. This will also work only for polygons. It could be adapted to work with other shapes if you introduce calculations for cubic and quadratic curves. It is assumed that Line2D.Double is used instead of Line2D.Float. A Set is used to avoid duplicate points (might occur on polygon corner intersections).
Please don't use this without extensive testing, since I've just hacked it together quickly and am not sure it's completely sound.
package quickpolygontest;
import java.awt.Polygon;
import java.awt.geom.Line2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Main {
public static void main(String[] args) throws Exception {
final Polygon poly = new Polygon(new int[]{1,2,2,1}, new int[]{1,1,2,2}, 4);
final Line2D.Double line = new Line2D.Double(2.5, 1.3, 1.3, 2.5);
final Set<Point2D> intersections = getIntersections(poly, line);
for(Iterator<Point2D> it = intersections.iterator(); it.hasNext();) {
final Point2D point = it.next();
System.out.println("Intersection: " + point.toString());
}
}
public static Set<Point2D> getIntersections(final Polygon poly, final Line2D.Double line) throws Exception {
final PathIterator polyIt = poly.getPathIterator(null); //Getting an iterator along the polygon path
final double[] coords = new double[6]; //Double array with length 6 needed by iterator
final double[] firstCoords = new double[2]; //First point (needed for closing polygon path)
final double[] lastCoords = new double[2]; //Previously visited point
final Set<Point2D> intersections = new HashSet<Point2D>(); //List to hold found intersections
polyIt.currentSegment(firstCoords); //Getting the first coordinate pair
lastCoords[0] = firstCoords[0]; //Priming the previous coordinate pair
lastCoords[1] = firstCoords[1];
polyIt.next();
while(!polyIt.isDone()) {
final int type = polyIt.currentSegment(coords);
switch(type) {
case PathIterator.SEG_LINETO : {
final Line2D.Double currentLine = new Line2D.Double(lastCoords[0], lastCoords[1], coords[0], coords[1]);
if(currentLine.intersectsLine(line))
intersections.add(getIntersection(currentLine, line));
lastCoords[0] = coords[0];
lastCoords[1] = coords[1];
break;
}
case PathIterator.SEG_CLOSE : {
final Line2D.Double currentLine = new Line2D.Double(coords[0], coords[1], firstCoords[0], firstCoords[1]);
if(currentLine.intersectsLine(line))
intersections.add(getIntersection(currentLine, line));
break;
}
default : {
throw new Exception("Unsupported PathIterator segment type.");
}
}
polyIt.next();
}
return intersections;
}
public static Point2D getIntersection(final Line2D.Double line1, final Line2D.Double line2) {
final double x1,y1, x2,y2, x3,y3, x4,y4;
x1 = line1.x1; y1 = line1.y1; x2 = line1.x2; y2 = line1.y2;
x3 = line2.x1; y3 = line2.y1; x4 = line2.x2; y4 = line2.y2;
final double x = (
(x2 - x1)*(x3*y4 - x4*y3) - (x4 - x3)*(x1*y2 - x2*y1)
) /
(
(x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4)
);
final double y = (
(y3 - y4)*(x1*y2 - x2*y1) - (y1 - y2)*(x3*y4 - x4*y3)
) /
(
(x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4)
);
return new Point2D.Double(x, y);
}
}
There is java.awt.geom.Area.intersect(Area) using the constructor Area(Shape) with your Polygon and passing your Line2D as an Area to intersect will give you the Area which is intersected.
With great success, i used this approach:
Area a = new Area(shape1);
Area b = new Area(shape2);
b.intersect(a);
if (!b.isEmpty()) {
//Shapes have non-empty intersection, so do you actions.
//In case of need, actual intersection is in Area b. (its destructive operation)
}
You need to bear in mind that it might intersect at multiple places.
Let's call the line segment of the polygon P and the real line segment L.
We find the slope of each line (slope is m)
ml = (ly1-ly2) / (lx1-lx2);
mp = (ply-pl2) / (px1-px2);
Find the y intercept of each line
// y = mx+b where b is y-intercept
bl = ly1 - (ml*lx1);
bp = py1 - (pl*px1);
You can solve for the x value with:
x = (bp - bl) / (ml - mp)
Then plug that X into one of the equations to get the Y
y = ml * x + bl
Here's an implemented version of the algorithm
class pointtest {
static float[] intersect(float lx1, float ly1, float lx2, float ly2,
float px1, float py1, float px2, float py2) {
// calc slope
float ml = (ly1-ly2) / (lx1-lx2);
float mp = (py1-py2) / (px1-px2);
// calc intercept
float bl = ly1 - (ml*lx1);
float bp = py1 - (mp*px1);
float x = (bp - bl) / (ml - mp);
float y = ml * x + bl;
return new float[]{x,y};
}
public static void main(String[] args) {
float[] coords = intersect(1,1,5,5,1,4,5,3);
System.out.println(coords[0] + " " + coords[1]);
}
}
and results:
3.4 3.4
If you are not restricted to use the Polygon and Line2D Objects I would recommend to use JTS.
Create LinearRing geometry (your polygon).
Create LineString geometry.
Create intersection Point(s) using the intersection method.
Simple code example:
// create ring: P1(0,0) - P2(0,10) - P3(10,10) - P4(0,10)
LinearRing lr = new GeometryFactory().createLinearRing(new Coordinate[]{new Coordinate(0,0), new Coordinate(0,10), new Coordinate(10,10), new Coordinate(10,0), new Coordinate(0,0)});
// create line: P5(5, -1) - P6(5, 11) -> crossing the ring vertically in the middle
LineString ls = new GeometryFactory().createLineString(new Coordinate[]{new Coordinate(5,-1), new Coordinate(5,11)});
// calculate intersection points
Geometry intersectionPoints = lr.intersection(ls);
// simple output of points
for(Coordinate c : intersectionPoints.getCoordinates()){
System.out.println(c.toString());
}
Result is:
(5.0, 0.0, NaN)
(5.0, 10.0, NaN)
Related
I need to implement sort of a pathfinding algorithm, the context is the following:
I have a starting Point2D, and and objective (a Circle).
I draw a line between the starting point and the circle center.
I try to calculate a path that does not cross any other circles.
(The blue square is my object I want to move (at starting point)) and the red circle is my objective).
What I wanted to do first was to do something like this:
But the code I have seems to be buggy as sometimes, I've got negatives intersection coordonates (black points).
Is there any other way to solve this problem ? Am I seeing the problem from a correct point of view ? There is also a problem as I'm iterating over the circles to determines which intersects or not, but if the line intersect 2 or more circles, the order of which it intersect planets is different from the order I see the points on screen.
My goal is to create a PathTransition between starting point and objective following the correct path (no intersection).
I've not mentioned it, but the container is a Pane.
EDIT:
public static Point2D getMidPoint(Point2D p1, Point2D p2) {
return new Point2D((p1.getX() + p2.getX()) / 2, (p1.getY() + p2.getY()) / 2);
}
public static Circle createCircleFromPoint2D(Point2D p) {
return new Circle(p.getX(), p.getY(), 5);
}
public static Point2D createPoint2D(double x, double y) {
return new Point2D(x, y);
}
public static Pair<Point2D, Point2D> translate(int distance, Point2D p1, Point2D p2, double reference, double startX) {
double pente = (p2.getY() - p1.getY()) / (p2.getX() - p1.getX());
double newX1 = p1.getX() + (startX < reference ? -1 : 1) * (Math.sqrt(((distance*distance) / (1 + (pente*pente)))));
double newX2 = p2.getX() + (startX > reference ? -1 : 1) * (Math.sqrt(((distance*distance) / (1 + (pente*pente)))));
double newY1 = pente * (newX1 - p1.getX()) + p1.getY();
double newY2 = pente * (newX2 - p2.getX()) + p2.getY();
return new Pair<>(new Point2D(newX1, newY1), new Point2D(newX2, newY2));
}
public void start(Stage primaryStage) throws Exception{
Pane pane = new Pane();
Circle objective = new Circle(800, 250, 25);
Circle circle2 = new Circle(500, 250, 125);
Circle circle3 = new Circle(240, 400, 75);
Circle circle4 = new Circle(700, 500, 150, Color.VIOLET);
Circle circle5 = new Circle(1150, 300, 115, Color.ORANGE);
Rectangle myObject = new Rectangle(175, 175, 15, 15);
objective.setFill(Color.RED);
circle2.setFill(Color.BLUE);
circle3.setFill(Color.GREEN);
myObject.setFill(Color.BLUE);
ArrayList<Circle> circles = new ArrayList<>();
circles.add(objective);
circles.add(circle2);
circles.add(circle3);
circles.add(circle4);
circles.add(circle5);
Line straightLine = new Line();
pane.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
myObject.setX(event.getX());
myObject.setY(event.getY());
// My starting coordinates (at mouse position)
double fromX = myObject.getX();
double fromY = myObject.getY();
// Where I want to go
double toX = objective.getCenterX();
double toY = objective.getCenterY();
// Line style
straightLine.setStartX(event.getX());
straightLine.setStartY(event.getY());
straightLine.setEndX(toX);
straightLine.setEndY(toY);
straightLine.setStrokeWidth(2);
straightLine.setStroke(Color.GRAY.deriveColor(0, 1, 1, 0.5));
straightLine.setStrokeLineCap(StrokeLineCap.BUTT);
straightLine.getStrokeDashArray().setAll(10.0, 5.0);
straightLine.setMouseTransparent(true);
// Coordinates to Point2D
Point2D from = new Point2D(fromX, fromY);
Point2D to = new Point2D(toX, toY);
Path path = new Path();
path.getElements().add(new MoveTo(fromX, fromY));
for (Circle c : circles) {
if (straightLine.intersects(c.getLayoutBounds())) {
// I don't want to do anything if I'm intersecting the objective (for now)
if (c == objective)
continue;
Shape s = Shape.intersect(straightLine, c);
double xmin = s.getBoundsInLocal().getMinX();
double ymin = s.getBoundsInLocal().getMinY();
double xmax = s.getBoundsInLocal().getMaxX();
double ymax = s.getBoundsInLocal().getMaxY();
Point2D intersectionPt1 = createPoint2D((fromX < objective.getCenterX()) ? xmin : xmax , (fromY < objective.getCenterY()) ? ymin : ymax);
Point2D intersectionPt2 = createPoint2D((fromX > objective.getCenterX()) ? xmin : xmax , (fromY < objective.getCenterY()) ? ymax : ymin);
Point2D middlePt = getMidPoint(intersectionPt1, intersectionPt2);
Circle circlePt1 = new Circle(intersectionPt1.getX(), intersectionPt1.getY(), 5);
Circle circlePt2 = new Circle(intersectionPt2.getX(), intersectionPt2.getY(), 5);
Circle circleMiddle = new Circle(middlePt.getX(), middlePt.getY(), 5, Color.RED);
if (c != objective) {
// To calculate the points just before/after the first/second points (green points)
Pair<Point2D, Point2D> pts = translate(50, intersectionPt1, intersectionPt2, objective.getCenterX(), fromX);
Point2D beforePt1 = pts.getKey();
Point2D beforePt2 = pts.getValue();
Circle circleBeforePt1 = createCircleFromPoint2D(beforePt1);
Circle circleBeforePt2 = createCircleFromPoint2D(beforePt2);
circleBeforePt1.setFill(Color.GREEN);
circleBeforePt2.setFill(Color.GREEN);
pane.getChildren().addAll(circleBeforePt1, circleBeforePt2);
}
pane.getChildren().addAll(s, circlePt1, circlePt2, circleMiddle);
}
}
PathTransition pathTransition = new PathTransition();
pathTransition.setDuration(Duration.seconds(2));
pathTransition.setNode(myObject);
pathTransition.setPath(path);
pathTransition.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
pathTransition.play();
}
});
pane.getChildren().addAll(circles);
pane.getChildren().addAll(myObject, straightLine);
Scene scene = new Scene(pane, 1600, 900);
primaryStage.setScene(scene);
primaryStage.show();
}
I want to calculate a path (not necessarily a shortest path) from Point A to Point B, but can't figure it out how. Now I have the points where I would like to pass, I don't know how to link them togethers.
Solution strategy and implementation
I built a solution with the following strategy: On a given line from(X,Y) to to(X,Y) I compute the closest intersection with one of the obstacle shapes. From that shape I take the length of the intersection as a measure of how large the obstacle is, and take a look at the points left and right by 1/2 of that length from some point shortly before the intersection. The first of the left and right points that is not inside an obstacle is then used to sub-divide the task of finding a path around the obstacles.
protected void computeIntersections(double fromX, double fromY, double toX, double toY) {
// recursively test for obstacles and try moving around them by
// calling this same procedure on the segments to and from
// a suitable new point away from the line
Line testLine = new Line(fromX, fromY, toX, toY);
//compute the unit direction of the line
double dX = toX-fromX, dY = toY-fromY;
double ds = Math.hypot(dX,dY);
dX /= ds; dY /= ds;
// get the length from the initial point of the minimal intersection point
// and the opposite point of the same obstacle, remember also the closest obstacle
double t1=-1, t2=-1;
Shape obst = null;
for (Shape c : lstObstacles) {
if (testLine.intersects(c.getLayoutBounds())) {
Shape s = Shape.intersect(testLine, c);
if( s.getLayoutBounds().isEmpty() ) continue;
// intersection bounds of the current shape
double s1, s2;
if(Math.abs(dX) < Math.abs(dY) ) {
s1 = ( s.getBoundsInLocal().getMinY()-fromY ) / dY;
s2 = ( s.getBoundsInLocal().getMaxY()-fromY ) / dY;
} else {
s1 = ( s.getBoundsInLocal().getMinX()-fromX ) / dX;
s2 = ( s.getBoundsInLocal().getMaxX()-fromX ) / dX;
}
// ensure s1 < s2
if ( s2 < s1 ) { double h=s2; s2=s1; s1=h; }
// remember the closest intersection
if ( ( t1 < 0 ) || ( s1 < t1 ) ) { t1 = s1; t2 = s2; obst = c; }
}
}
// at least one intersection found
if( ( obst != null ) && ( t1 > 0 ) ) {
intersectionDecorations.getChildren().add(Shape.intersect(testLine, obst));
// coordinates for the vertex point of the path
double midX, midY;
// go to slightly before the intersection set
double intersectX = fromX + 0.8*t1*dX, intersectY = fromY + 0.8*t1*dY;
// orthogonal segment of half the length of the intersection, go left and right
double perpX = 0.5*(t2-t1)*dY, perpY = 0.5*(t1-t2)*dX;
Rectangle testRect = new Rectangle( 10, 10);
// go away from the line to hopefully have less obstacle from the new point
while( true ) {
// go "left", test if free
midX = intersectX + perpX; midY = intersectY + perpY;
testRect.setX(midX-5); testRect.setY(midY-5);
if( Shape.intersect(testRect, obst).getLayoutBounds().isEmpty() ) break;
// go "right"
midX = intersectX - perpX; midY = intersectY - perpY;
testRect.setX(midX-5); testRect.setY(midY-5);
if( Shape.intersect(testRect, obst).getLayoutBounds().isEmpty() ) break;
// if obstacles left and right, try closer points next
perpX *= 0.5; perpY *= 0.5;
}
intersectionDecorations.getChildren().add(new Line(intersectX, intersectY, midX, midY));
// test the first segment for intersections with obstacles
computeIntersections(fromX, fromY, midX, midY);
// add the middle vertex to the solution path
connectingPath.getElements().add(new LineTo(midX, midY));
// test the second segment for intersections with obstacles
computeIntersections(midX, midY, toX, toY);
}
}
This first chosen point might not be the most optimal one, as one can see, but it does the job. To do better one would have to construct some kind of decision tree of the left-right decisions and then chose the shortest path among the variants. All the usual strategies then apply, like starting a second tree from the target location, depth-first search etc.
The auxillary lines are the intersections that were used and the perpendicular lines to the new midpoints.
PathfinderApp.java
I used this problem to familiarize myself with the use of FXML, thus the main application has the usual boilerplate code.
package pathfinder;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class PathfinderApp extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("pathfinder.fxml"));
primaryStage.setTitle("Finding a path around obstacles");
primaryStage.setScene(new Scene(root, 1600, 900));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
pathfinder.fxml
The FXML file contains the "most" static (in the sense of always present for the given type of task) elements of the user interface. These are the cursor rectangle, the target circle and a line between them. Then groups for the obstacles and "decorations" from the path construction, and the path itself. This separation allows to clear and populate these groupings independent from each other with no other organizational effort.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.Group?>
<?import javafx.scene.text.Text?>
<?import javafx.scene.shape.Line?>
<?import javafx.scene.shape.Path?>
<?import javafx.scene.shape.Circle?>
<?import javafx.scene.shape.Rectangle?>
<?import javafx.scene.paint.Color?>
<Pane xmlns:fx="http://javafx.com/fxml"
fx:controller="pathfinder.PathfinderController" onMouseClicked="#setCursor">
<Circle fx:id="target" centerX="800" centerY="250" radius="25" fill="red"/>
<Rectangle fx:id="cursor" x="175" y="175" width="15" height="15" fill="lightblue"/>
<Line fx:id="straightLine" startX="${cursor.X}" startY="${cursor.Y}" endX="${target.centerX}" endY="${target.centerY}"
strokeWidth="2" stroke="gray" strokeLineCap="butt" strokeDashArray="10.0, 5.0" mouseTransparent="true" />
<Group fx:id="obstacles" />
<Group fx:id="intersectionDecorations" />
<Path fx:id="connectingPath" strokeWidth="2" stroke="blue" />
</Pane>
PathfinderController.java
The main work is done in the controller. Some minimal initialization binding the target and cursor to their connecting line and the mouse event handler (with code that prevents the cursor to be placed inside some obstacle) and then the path finding procedures. One framing procedure and the recursive workhorse from above.
package pathfinder;
import javafx.fxml.FXML;
import javafx.geometry.Bounds;
import javafx.scene.layout.Pane;
import javafx.scene.Group;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.scene.shape.Shape;
import javafx.scene.shape.Line;
import javafx.scene.shape.Path;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.paint.Color;
import javafx.scene.input.MouseEvent;
import java.util.*;
public class PathfinderController {
#FXML
private Circle target;
#FXML
private Rectangle cursor;
#FXML
private Line straightLine;
#FXML
private Path connectingPath;
#FXML
private Group obstacles, intersectionDecorations;
private static List<Shape> lstObstacles = Arrays.asList(
new Circle( 500, 250, 125, Color.BLUE ),
new Circle( 240, 400, 75, Color.GREEN ),
new Circle( 700, 500, 150, Color.VIOLET),
new Circle(1150, 300, 115, Color.ORANGE)
);
#FXML
public void initialize() {
straightLine.startXProperty().bind(cursor.xProperty());
straightLine.startYProperty().bind(cursor.yProperty());
obstacles.getChildren().addAll(lstObstacles);
findPath();
}
#FXML
protected void setCursor(MouseEvent e) {
Shape test = new Rectangle(e.getX()-5, e.getY()-5, 10, 10);
for (Shape c : lstObstacles) {
if( !Shape.intersect(c, test).getLayoutBounds().isEmpty() ) return;
}
cursor.setX(e.getX());
cursor.setY(e.getY());
findPath();
}
protected void findPath() {
double fromX = cursor.getX();
double fromY = cursor.getY();
double toX = target.getCenterX();
double toY = target.getCenterY();
intersectionDecorations.getChildren().clear();
connectingPath.getElements().clear();
// first point of path
connectingPath.getElements().add(new MoveTo(fromX, fromY));
// check path for intersections, move around if necessary
computeIntersections(fromX, fromY, toX, toY);
// last point of the path
connectingPath.getElements().add(new LineTo(toX, toY));
}
protected void computeIntersections(double fromX, double fromY, double toX, double toY) {
...
}
// end class
}
It may not be the desired answer, but did you think about unit testing your math code? It is easy to do for math code and then you can be sure the low level functions work correct.
If you still have the bug afterwards, you can write a unit test for easier reproducing it and post it here.
On Topic:
Your algorithm with the lines can get quite complex or even find no solution with more and/or overlapping circles.
Why not use the standard A* algorithm, where all non-white pixels are obstacles. Is that overkill?
Is it somehow possible to retrieve font (ttf/otf) outline as a curve/series of points on Android? For example, if I wanted to convert a word with specific font into a vector format?
Since I never developed for an Android device, I will give you the way to do that, but in Java.
This is a couple of good libraries, but I don't know if it would be possible for you to use it (C/C++) So I will explain you how to do it yourself.
You should in first convert your word in a shape using a TextLayout (an immutable graphical representation of styled character data that you can draw) in a FontRenderContext.
According to the John J Smith answer here: https://stackoverflow.com/a/6864113/837765, It should be possible to use something similar to a TextLayout on Android. But, there's no quivalent of FontRenderContext. As I said, I never developed for an Android device, but there is probably (I hope so) a workaround to convert characters in a shape.
In Java Something like this should work (to convert text in a Shape):
public Shape getShape(String text, Font font, Point from) {
FontRenderContext context = new FontRenderContext(null, false, false);
GeneralPath shape = new GeneralPath();
TextLayout layout = new TextLayout(text, font, context);
Shape outline = layout.getOutline(null);
shape.append(outline, true);
return shape;
}
Then, you should find the shape boundary. It's not pretty difficult here, because your shape can give you directly the path iterator with shape.getPathIterator(null)
On each iteration, you can get the current segment, its type and the coordinates.:
SEG_QUADTO: a quadratic parametric curve;
SEG_CUBICTO : a cubic parametric curve;
SEG_LINETO : specifies the end point of a line;
SEG_MOVETO : a point that specifies the starting location for a new subpath.
At this point, you should read about Bézier curve here and here.
You will learn that:
Any quadratic spline can be expressed as a cubic (where the cubic term
is zero). The end points of the cubic will be the same as the
quadratic's.
CP0 = QP0 CP3 = QP2
The two control points for the cubic are:
CP1 = QP0 + 2/3 *(QP1-QP0) CP2 = CP1 + 1/3 *(QP2-QP0)
So converting from TrueType to PostScript is trivial.
In Java Something like this should work:
public List<Point> getPoints(Shape shape) {
List<Point> out = new ArrayList<Point>();
PathIterator iterator = shape.getPathIterator(null);
double[] coordinates = new double[6];
double x = 0, y = 0;
while (!iterator.isDone()) {
double x1 = coordinates[0];
double y1 = coordinates[1];
double x2 = coordinates[2];
double y2 = coordinates[3];
double x3 = coordinates[4];
double y3 = coordinates[5];
switch (iterator.currentSegment(coordinates)) {
case PathIterator.SEG_QUADTO:
x3 = x2;
y3 = y2;
x2 = x1 + 1 / 3f * (x2 - x1);
y2 = y1 + 1 / 3f * (y2 - y1);
x1 = x + 2 / 3f * (x1 - x);
y1 = y + 2 / 3f * (y1 - y);
out.add(new Point(x3, y3));
x = x3;
y = y3;
break;
case PathIterator.SEG_CUBICTO:
out.add(new Point(x3, y3));
x = x3;
y = y3;
break;
case PathIterator.SEG_LINETO:
out.add(new Point(x1, y1));
x = x1;
y = y1;
break;
case PathIterator.SEG_MOVETO:
out.add(new Point(x1, y1));
x = x1;
y = y1;
break;
}
iterator.next();
}
return out;
}
I created a demo project on Bitbucket, maybe it could help you.
https://bitbucket.org/pieralexandre/fontshape
Initial shape text (after outline transformation):
The points on the shape:
Only the points:
And the output of all points:
(0.0,0.0)
(9.326171875,200.0)
(9.326171875,127.734375)
(0.0,0.0)
(50.7080078125,130.126953125)
(62.158203125,138.232421875)
(69.82421875,162.158203125)
(60.302734375,190.087890625)
(50.78125,200.0)
//...
I know you cannot use Graphics2D (I used it for the UI) in Android, but you should be able to use my solution for an Android Project (I hope).
After that, you could be able (with the help of Bézier curve) to recreate your curves.
Also, there's a couple of other good tools here. take a look at this one:
http://nodebox.github.io/opentype.js/
It's in Javascript, but maybe it could help you even more.
My final goal is to have a method, lets say:
Rectangle snapRects(Rectangle rec1, Rectangle rec2);
Imagine a Rectangle having info on position, size and angle.
Dragging and dropping the ABDE rectangle close to the BCGF rectangle would call the method with ABDE as first argument and BCGF as second argument, and the resulting rectangle is a rectangle lined up with BCGF's edge.
The vertices do not have to match (and preferrably won't so the snapping isn't so restrictive).
I can only understand easily how to give the same angle, but the position change is quite confusing to me. Also, i believe even if i reached a solution it would be quite badly optimized (excessive resource cost), so I would appreciate guidance on this.
(This has already been asked but no satisfatory answer was given and the question forgotten.)
------------------------------------------------------------------
Edit: It seems my explanation was insufficient so I will try to clarify my wishes:
The following image shows the goal of the method in a nutshell:
Forget about "closest rectangle", imagine there are just two rectangles. The lines inside the rectangles represent the direction they are facing (visual aid for the angle).
There is a static rectangle, which is not to be moved and has an angle (0->360), and a rectangle (also with an angle) which I want to Snap to the closest edge of the static rectangle. By this, i mean, i want the least transformations possible for the "snap to edge" to happen.
This brings many possible cases, depending on the rotation of the rectangles and their position relative to each other.
The next image shows the static rectangle and how the position of the "To Snap" rectangle changes the snapping result:
The final rotations might not be perfect since it was done by eye, but you get the point, it matters the relative position and also both angles.
Now, in my point of view, which may be completely naive, I see this problem solved on two important and distinct steps on transforming the "To Snap" rectangle: Positioning and Rotation
Position: The objective of the new position is to stick to the closest edge, but since we want it to stick paralell to the static rectangle, the angle of the static rectangle matters. The next image shows examples of positioning:
In this case, the static rectangle has no angle, so its easy to determine up, down, left and right. But with angle, there are alot more possibilities:
As for the rotation, the goal is for the "to snap" rectangle to rotate the minimum needed to become paralell with the static rectangle:
As a final note, in regard of implementation input, the goal is to actually drag the "to snap" rectangle to wherever position i wish around the static rectangle and by pressing a keyboard key, the snap happens.
Also, it appears i have exagerated a little when i asked for optimization, to be honest i do not need or require optimization, I do prefer an easy to read, step by step clear code (if its the case), rather than any optimization at all.
I hope i was clear this time, sorry for the lack of clarity in the first place, if you have any more doubts please do ask.
The problem is obviously underspecified: What does "line up" for the edges mean? A common start point (but not necessarily a common end point)? A common center point for both edges? (That's what I assumed now). Should ALL edges match? What is the criterion for deciding WHICH edge of the first rectangle should be "matched" with WHICH edge of the second rectangle? That is, imagine one square consists exactly of the center points of the edges of the other square - how should it be aligned then?
(Secondary question: In how far is optimization (or "low resource cost") important?)
However, I wrote a few first lines, maybe this can be used to point out more clearly what the intended behavior should be - namely by saying in how far the intended behavior differs from the actual behavior:
EDIT: Old code omitted, update based on the clarification:
The conditions for the "snapping" are still not unambiguous. For example, it is not clear whether the change in position or the change in the angle should be preferred. But admittedly, I did not figure out in detail all possible cases where this question could arise. In any case, based on the updated question, this might be closer to what you are looking for.
NOTE: This code is neither "clean" nor particularly elegant or efficient. The goal until now was to find a method that delivers "satisfactory" results. Optimizations and beautifications are possible.
The basic idea:
Given are the static rectangle r1, and the rectangle to be snapped, r0
Compute the edges that should be snapped together. This is divided in two steps:
The method computeCandidateEdgeIndices1 computes the "candidate edges" (resp. their indices) of the static rectangle that the moving rectangle may be snapped to. This is based on the folowing criterion: It checks how many vertices (corners) of the moving rectangle are right of the particular edge. For example, if all 4 vertices of the moving rectangle are right of edge 2, then edge 2 will be a candidate for snapping the rectangle to.
Since there may be multiple edges for which the same number of vertices may be "right", the method computeBestEdgeIndices computes the candidate edge whose center has the least distance to the center of any edge of the moving rectangle. The indices of the respective edges are returned
Given the indices of the edges to be snapped, the angle between these edges is computed. The resulting rectangle will be the original rectangle, rotated by this angle.
The rotated rectangle will be moved so that the centers of the snapped edges are at the same point
I tested this with several configurations, and the results at least seem "feasible" for me. Of course, this does not mean that it works satisfactory in all cases, but maybe it can serve as a starting point.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class RectangleSnap
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
RectangleSnapPanel panel = new RectangleSnapPanel();
f.getContentPane().add(panel);
f.setSize(1000,1000);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class SnapRectangle
{
private Point2D position;
private double sizeX;
private double sizeY;
private double angleRad;
private AffineTransform at;
SnapRectangle(
double x, double y,
double sizeX, double sizeY, double angleRad)
{
this.position = new Point2D.Double(x,y);
this.sizeX = sizeX;
this.sizeY = sizeY;
this.angleRad = angleRad;
at = AffineTransform.getRotateInstance(
angleRad, position.getX(), position.getY());
}
double getAngleRad()
{
return angleRad;
}
double getSizeX()
{
return sizeX;
}
double getSizeY()
{
return sizeY;
}
Point2D getPosition()
{
return position;
}
void draw(Graphics2D g)
{
Color oldColor = g.getColor();
Rectangle2D r = new Rectangle2D.Double(
position.getX(), position.getY(), sizeX, sizeY);
AffineTransform at = AffineTransform.getRotateInstance(
angleRad, position.getX(), position.getY());
g.draw(at.createTransformedShape(r));
g.setColor(Color.RED);
for (int i=0; i<4; i++)
{
Point2D c = getCorner(i);
Ellipse2D e = new Ellipse2D.Double(c.getX()-3, c.getY()-3, 6, 6);
g.fill(e);
g.drawString(""+i, (int)c.getX(), (int)c.getY()+15);
}
g.setColor(Color.GREEN);
for (int i=0; i<4; i++)
{
Point2D c = getEdgeCenter(i);
Ellipse2D e = new Ellipse2D.Double(c.getX()-3, c.getY()-3, 6, 6);
g.fill(e);
g.drawString(""+i, (int)c.getX(), (int)c.getY()+15);
}
g.setColor(oldColor);
}
Point2D getCorner(int i)
{
switch (i)
{
case 0:
return new Point2D.Double(position.getX(), position.getY());
case 1:
{
Point2D.Double result = new Point2D.Double(
position.getX(), position.getY()+sizeY);
return at.transform(result, null);
}
case 2:
{
Point2D.Double result = new Point2D.Double
(position.getX()+sizeX, position.getY()+sizeY);
return at.transform(result, null);
}
case 3:
{
Point2D.Double result = new Point2D.Double(
position.getX()+sizeX, position.getY());
return at.transform(result, null);
}
}
return null;
}
Line2D getEdge(int i)
{
Point2D p0 = getCorner(i);
Point2D p1 = getCorner((i+1)%4);
return new Line2D.Double(p0, p1);
}
Point2D getEdgeCenter(int i)
{
Point2D p0 = getCorner(i);
Point2D p1 = getCorner((i+1)%4);
Point2D c = new Point2D.Double(
p0.getX() + 0.5 * (p1.getX() - p0.getX()),
p0.getY() + 0.5 * (p1.getY() - p0.getY()));
return c;
}
void setPosition(double x, double y)
{
this.position.setLocation(x, y);
at = AffineTransform.getRotateInstance(
angleRad, position.getX(), position.getY());
}
}
class RectangleSnapPanel extends JPanel implements MouseMotionListener
{
private final SnapRectangle rectangle0;
private final SnapRectangle rectangle1;
private SnapRectangle snappedRectangle0;
RectangleSnapPanel()
{
this.rectangle0 = new SnapRectangle(
200, 300, 250, 200, Math.toRadians(-21));
this.rectangle1 = new SnapRectangle(
500, 300, 200, 150, Math.toRadians(36));
addMouseMotionListener(this);
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setColor(Color.BLACK);
rectangle0.draw(g);
rectangle1.draw(g);
if (snappedRectangle0 != null)
{
g.setColor(Color.BLUE);
snappedRectangle0.draw(g);
}
}
#Override
public void mouseDragged(MouseEvent e)
{
rectangle0.setPosition(e.getX(), e.getY());
snappedRectangle0 = snapRects(rectangle0, rectangle1);
repaint();
}
#Override
public void mouseMoved(MouseEvent e)
{
}
private static SnapRectangle snapRects(
SnapRectangle r0, SnapRectangle r1)
{
List<Integer> candidateEdgeIndices1 =
computeCandidateEdgeIndices1(r0, r1);
int bestEdgeIndices[] = computeBestEdgeIndices(
r0, r1, candidateEdgeIndices1);
int bestEdgeIndex0 = bestEdgeIndices[0];
int bestEdgeIndex1 = bestEdgeIndices[1];
System.out.println("Best to snap "+bestEdgeIndex0+" to "+bestEdgeIndex1);
Line2D bestEdge0 = r0.getEdge(bestEdgeIndex0);
Line2D bestEdge1 = r1.getEdge(bestEdgeIndex1);
double edgeAngle = angleRad(bestEdge0, bestEdge1);
double rotationAngle = edgeAngle;
if (rotationAngle <= Math.PI)
{
rotationAngle = Math.PI + rotationAngle;
}
else if (rotationAngle <= -Math.PI / 2)
{
rotationAngle = Math.PI + rotationAngle;
}
else if (rotationAngle >= Math.PI)
{
rotationAngle = -Math.PI + rotationAngle;
}
SnapRectangle result = new SnapRectangle(
r0.getPosition().getX(), r0.getPosition().getY(),
r0.getSizeX(), r0.getSizeY(), r0.getAngleRad()-rotationAngle);
Point2D edgeCenter0 = result.getEdgeCenter(bestEdgeIndex0);
Point2D edgeCenter1 = r1.getEdgeCenter(bestEdgeIndex1);
double dx = edgeCenter1.getX() - edgeCenter0.getX();
double dy = edgeCenter1.getY() - edgeCenter0.getY();
result.setPosition(
r0.getPosition().getX()+dx,
r0.getPosition().getY()+dy);
return result;
}
// Compute for the edge indices for r1 in the given list
// the one that has the smallest distance to any edge
// of r0, and return this pair of indices
private static int[] computeBestEdgeIndices(
SnapRectangle r0, SnapRectangle r1,
List<Integer> candidateEdgeIndices1)
{
int bestEdgeIndex0 = -1;
int bestEdgeIndex1 = -1;
double minCenterDistance = Double.MAX_VALUE;
for (int i=0; i<candidateEdgeIndices1.size(); i++)
{
int edgeIndex1 = candidateEdgeIndices1.get(i);
for (int edgeIndex0=0; edgeIndex0<4; edgeIndex0++)
{
Point2D p0 = r0.getEdgeCenter(edgeIndex0);
Point2D p1 = r1.getEdgeCenter(edgeIndex1);
double distance = p0.distance(p1);
if (distance < minCenterDistance)
{
minCenterDistance = distance;
bestEdgeIndex0 = edgeIndex0;
bestEdgeIndex1 = edgeIndex1;
}
}
}
return new int[]{ bestEdgeIndex0, bestEdgeIndex1 };
}
// Compute the angle, in radians, between the given lines,
// in the range (-2*PI, 2*PI)
private static double angleRad(Line2D line0, Line2D line1)
{
double dx0 = line0.getX2() - line0.getX1();
double dy0 = line0.getY2() - line0.getY1();
double dx1 = line1.getX2() - line1.getX1();
double dy1 = line1.getY2() - line1.getY1();
double a0 = Math.atan2(dy0, dx0);
double a1 = Math.atan2(dy1, dx1);
return (a0 - a1) % (2 * Math.PI);
}
// In these methods, "right" refers to screen coordinates, which
// unfortunately are upside down in Swing. Mathematically,
// these relation is "left"
// Compute the "candidate" edges of r1 to which r0 may
// be snapped. These are the edges to which the maximum
// number of corners of r0 are right of
private static List<Integer> computeCandidateEdgeIndices1(
SnapRectangle r0, SnapRectangle r1)
{
List<Integer> bestEdgeIndices = new ArrayList<Integer>();
int maxRight = 0;
for (int i=0; i<4; i++)
{
Line2D e1 = r1.getEdge(i);
int right = countRightOf(e1, r0);
if (right > maxRight)
{
maxRight = right;
bestEdgeIndices.clear();
bestEdgeIndices.add(i);
}
else if (right == maxRight)
{
bestEdgeIndices.add(i);
}
}
//System.out.println("Candidate edges "+bestEdgeIndices);
return bestEdgeIndices;
}
// Count the number of corners of the given rectangle
// that are right of the given line
private static int countRightOf(Line2D line, SnapRectangle r)
{
int count = 0;
for (int i=0; i<4; i++)
{
if (isRightOf(line, r.getCorner(i)))
{
count++;
}
}
return count;
}
// Returns whether the given point is right of the given line
// (referring to the actual line *direction* - not in terms
// of coordinates in 2D!)
private static boolean isRightOf(Line2D line, Point2D point)
{
double d00 = line.getX1() - point.getX();
double d01 = line.getY1() - point.getY();
double d10 = line.getX2() - point.getX();
double d11 = line.getY2() - point.getY();
return d00 * d11 - d10 * d01 > 0;
}
}
I have a problem that I can't seem to get a working algorithm for, I've been trying to days and get so close but yet so far.
I want to draw a triangle defined by 3 points (p0, p1, p2). This triangle can be any shape, size, and orientation. The triangle must also be filled inside.
Here's a few things I've tried and why they've failed:
1
Drawing lines along the triangle from side to side
Failed because the triangle would have holes and would not be flat due to the awkwardness of drawing lines across the angled surface with changing locations
2
Iterate for an area and test if the point falls past the plane parallel to the triangle and 3 other planes projected onto the XY, ZY, and XZ plane that cover the area of the triangle
Failed because for certain triangles (that have very close sides) there would be unpredictable results, e.g. voxels floating around not connected to anything
3
Iterate for an area along the sides of the triangle (line algorithm) and test to see if a point goes past a parallel plane
Failed because drawing a line from p0 to p1 is not the same as a line from p1 to p0 and any attempt to rearrange either doesn't help, or causes more problems. Asymmetry is the problem with this one.
This is all with the intent of making polygons and flat surfaces. 3 has given me the most success and makes accurate triangles, but when I try to connect these together everything falls apart and I get issues with things not connecting, asymmetry, etc. I believe 3 will work with some tweaking but I'm just worn out from trying to make this work for so long and need help.
There's a lot of small details in my algorithms that aren't really relevant so I left them out. For number 3 it might be a problem with my implementation and not the algorithm itself. If you want code I'll try and clean it up enough to be understandable, it will take me a few minutes though. But I'm looking for algorithms that are known to work. I can't seem to find any voxel shape making algorithms anywhere, I've been doing everything from scratch.
EDIT:
Here's the third attempt. It's a mess, but I tried to clean it up.
// Point3i is a class I made, however the Vector3fs you'll see are from lwjgl
public void drawTriangle (Point3i r0, Point3i r1, Point3i r2)
{
// Util is a class I made with some useful stuff inside
// Starting values for iteration
int sx = (int) Util.min(r0.x, r1.x, r2.x);
int sy = (int) Util.min(r0.y, r1.y, r2.y);
int sz = (int) Util.min(r0.z, r1.z, r2.z);
// Ending values for iteration
int ex = (int) Util.max(r0.x, r1.x, r2.x);
int ey = (int) Util.max(r0.y, r1.y, r2.y);
int ez = (int) Util.max(r0.z, r1.z, r2.z);
// Side lengths
float l0 = Util.distance(r0.x, r1.x, r0.y, r1.y, r0.z, r1.z);
float l1 = Util.distance(r2.x, r1.x, r2.y, r1.y, r2.z, r1.z);
float l2 = Util.distance(r0.x, r2.x, r0.y, r2.y, r0.z, r2.z);
// Calculate the normal vector
Vector3f nn = new Vector3f(r1.x - r0.x, r1.y - r0.y, r1.z - r0.z);
Vector3f n = new Vector3f(r2.x - r0.x, r2.y - r0.y, r2.z - r0.z);
Vector3f.cross(nn, n, n);
// Determines which direction we increment for
int iz = n.z >= 0 ? 1 : -1;
int iy = n.y >= 0 ? 1 : -1;
int ix = n.x >= 0 ? 1 : -1;
// Reorganize for the direction of iteration
if (iz < 0) {
int tmp = sz;
sz = ez;
ez = tmp;
}
if (iy < 0) {
int tmp = sy;
sy = ey;
ey = tmp;
}
if (ix < 0) {
int tmp = sx;
sx = ex;
ex = tmp;
}
// We're we want to iterate over the end vars so we change the value
// by their incrementors/decrementors
ex += ix;
ey += iy;
ez += iz;
// Maximum length
float lmax = Util.max(l0, l1, l2);
// This is a class I made which manually iterates over a line, I already
// know that this class is working
GeneratorLine3d g0, g1, g2;
// This is a vector for the longest side
Vector3f v = new Vector3f();
// make the generators
if (lmax == l0) {
v.x = r1.x - r0.x;
v.y = r1.y - r0.y;
v.z = r1.z - r0.z;
g0 = new GeneratorLine3d(r0, r1);
g1 = new GeneratorLine3d(r0, r2);
g2 = new GeneratorLine3d(r2, r1);
}
else if (lmax == l1) {
v.x = r1.x - r2.x;
v.y = r1.y - r2.y;
v.z = r1.z - r2.z;
g0 = new GeneratorLine3d(r2, r1);
g1 = new GeneratorLine3d(r2, r0);
g2 = new GeneratorLine3d(r0, r1);
}
else {
v.x = r2.x - r0.x;
v.y = r2.y - r0.y;
v.z = r2.z - r0.z;
g0 = new GeneratorLine3d(r0, r2);
g1 = new GeneratorLine3d(r0, r1);
g2 = new GeneratorLine3d(r1, r2);
}
// Absolute values for the normal
float anx = Math.abs(n.x);
float any = Math.abs(n.y);
float anz = Math.abs(n.z);
int i, o;
int si, so;
int ii, io;
int ei, eo;
boolean maxx, maxy, maxz,
midy, midz, midx,
minx, miny, minz;
maxx = maxy = maxz =
midy = midz = midx =
minx = miny = minz = false;
// Absolute values for the longest side vector
float rnx = Math.abs(v.x);
float rny = Math.abs(v.y);
float rnz = Math.abs(v.z);
int rmid = Util.max(rnx, rny, rnz);
if (rmid == rnz) midz = true;
else if (rmid == rny) midy = true;
midx = !midz && !midy;
// Determine the inner and outer loop directions
if (midz) {
if (any > anx)
{
maxy = true;
si = sy;
ii = iy;
ei = ey;
}
else {
maxx = true;
si = sx;
ii = ix;
ei = ex;
}
}
else {
if (anz > anx) {
maxz = true;
si = sz;
ii = iz;
ei = ez;
}
else {
maxx = true;
si = sx;
ii = ix;
ei = ex;
}
}
if (!midz && !maxz) {
minz = true;
so = sz;
eo = ez;
}
else if (!midy && !maxy) {
miny = true;
so = sy;
eo = ey;
}
else {
minx = true;
so = sx;
eo = ex;
}
// GeneratorLine3d is iterable
Point3i p1;
for (Point3i p0 : g0) {
// Make sure the two 'mid' coordinate correspond for the area inside the triangle
if (midz)
do p1 = g1.hasNext() ? g1.next() : g2.next();
while (p1.z != p0.z);
else if (midy)
do p1 = g1.hasNext() ? g1.next() : g2.next();
while (p1.y != p0.y);
else
do p1 = g1.hasNext() ? g1.next() : g2.next();
while (p1.x != p0.x);
eo = (minx ? p0.x : miny ? p0.y : p0.z);
so = (minx ? p1.x : miny ? p1.y : p1.z);
io = eo - so >= 0 ? 1 : -1;
for (o = so; o != eo; o += io) {
for (i = si; i != ei; i += ii) {
int x = maxx ? i : midx ? p0.x : o;
int y = maxy ? i : midy ? p0.y : o;
int z = maxz ? i : midz ? p0.z : o;
// isPassing tests to see if a point goes past a plane
// I know it's working, so no code
// voxels is a member that is an arraylist of Point3i
if (isPassing(x, y, z, r0, n.x, n.y, n.z)) {
voxels.add(new Point3i(x, y, z));
break;
}
}
}
}
}
You could use something like Besenham's line algorithm, but extended into three dimensions. The two main ideas we want to take from it are:
rotate the initial line so its slope isn't too steep.
for any given x value, find an integer value that is closest to the ideal y value.
Just as Bresenham's algorithm prevents gaps by performing an initial rotation, we'll avoid holes by performing two initial rotations.
Get the normal vector and point that represent the plane your triangle lies on. Hint: use the cross product of (line from p0 to p1) and (line from p0 to p2) for the vector, and use any of your corner points for the point.
You want the plane to be sufficiently not-steep, to avoid holes. You must satisfy these conditions:
-1 >= norm.x / norm.y >= 1
-1 >= norm.z / norm.y >= 1
Rotate your normal vector and initial points 90 degrees about the x axis and 90 degrees about the z axis until these conditions are satisfied. I'm not sure how to do this in the fewest number of rotations, but I'm fairly sure you can satisfy these conditions for any plane.
Create a function f(x,z) which represents the plane your rotated triangle now lies on. It should return the Y value of any pair of X and Z values.
Project your triangle onto the XZ plane (i.e., set all the y values to 0), and use your favorite 2d triangle drawing algorithm to get a collection of x-and-z coordinates.
For each pixel value from step 4, pass the x and z values into your function f(x,z) from step 3. Round the result to the nearest integer, and store the x, y, and z values as a voxel somewhere.
If you performed any rotations in step 2, perform the opposite of those rotations in reverse order on your voxel collection.
Start with a function that checks for triangle/voxel intersection. Now you can scan a volume and find the voxels that intersect the triangle - these are the ones you're interested in. This is a lousy algorithm but is also a regression test for anything else you try. This test is easy to implement using SAT (separating axis theorem) and considering the triangle a degenerate volume (1 face, 3 edges) and considering the voxels symmetry (only 3 face normals).
I use octtrees, so my preferred method is to test a triangle against a large voxel and figure out which of the 8 child octants it intersects. Then use recursion on the intersected children until the desired level of subdivision is attained. Hint: at most 6 of the children can be intersected by the triangle and often fewer than that. This is tricky but will produce the same results as the first method but much quicker.
Rasterization in 3d is probably fastest, but IMHO is even harder to guarantee no holes in all cases. Again, use the first method for comparison.
I have two GPS coordinates which link together to make a line. I also have a GPS point which is near to, but never exactly on, the line. My question is, how do I find the nearest point along the line to the given point?
Game Dev has an answer to this, it is in C++ but it should be easy to port over. Which CarlG has kindly done (hopefully he does not mind me reposting):
public static Point2D nearestPointOnLine(double ax, double ay, double bx, double by, double px, double py,
boolean clampToSegment, Point2D dest) {
// Thanks StackOverflow!
// https://stackoverflow.com/questions/1459368/snap-point-to-a-line-java
if (dest == null) {
dest = new Point2D.Double();
}
double apx = px - ax;
double apy = py - ay;
double abx = bx - ax;
double aby = by - ay;
double ab2 = abx * abx + aby * aby;
double ap_ab = apx * abx + apy * aby;
double t = ap_ab / ab2;
if (clampToSegment) {
if (t < 0) {
t = 0;
} else if (t > 1) {
t = 1;
}
}
dest.setLocation(ax + abx * t, ay + aby * t);
return dest;
}
Try this:
ratio = (((x1-x0)^2+(y1-y0)^2)*((x2-x1)^2 + (y2-y1)^2) - ((x2-x1)(y1-y0) - (x1-x0)(y2-y1))^2)^0.5
-----------------------------------------------------------------------------------------
((x2-x1)^2 + (y2-y1)^2)
xc = x1 + (x2-x1)*ratio;
yc = y1 + (y2-y1)*ratio;
Where:
x1,y1 = point#1 on the line
x2,y2 = point#2 on the line
x0,y0 = Another point near the line
xc,yx = The nearest point of x0,y0 on the line
ratio = is the ratio of distance of x1,y1 to xc,yc and distance of x1,y1 to x2,y2
^2 = square
^0.5 = square root
The formular is derived after we find the distant from point x0,y0 to line (x1,y1 -> x2,y3).
See here
I've test this code here (this particular one I gave you above) but I've used it similar method years ago and it work so you may try.
You can use JTS for that.
Create a LineSegment (your line)
Create a Coordinate (the point you want to snap to the line)
Get Point on the line by using the closestPoint method
Very simple code example:
// create Line: P1(0,0) - P2(0,10)
LineSegment ls = new LineSegment(0, 0, 0, 10);
// create Point: P3(5,5)
Coordinate c = new Coordinate(5, 5);
// create snapped Point: P4(0,5)
Coordinate snappedPoint = ls.closestPoint(c);