import javafx.event.ActionEvent; import javafx.scene.Scene; import javafx.application.Application; import javafx.beans.property.*; import javafx.beans.value.*; import javafx.collections.*; import javafx.event.EventHandler; import javafx.scene.*; import javafx.scene.control.Button; import javafx.scene.control.TextArea; import javafx.scene.input.MouseEvent; import javafx.scene.layout.BorderPane; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.shape.*; import javafx.scene.text.Font; import javafx.scene.text.Text; import javafx.stage.Stage; import java.util.ArrayList; import java.util.List; import java.util.Random; /** Drag the anchors around to change a polygon's points. */ public class Main extends Application { private Polygon polygon; private Group canvas; private TextArea inputText; private Polygon solutionPolygon; private Group solutionGroup; private Text outputText; public static void main(String[] args) throws Exception { launch(args); } // main application layout logic. @Override public void start(final Stage stage) throws Exception { this.polygon = createStartingPolygon(); this.solutionPolygon = createSolutionPolygon(); this.solutionGroup = new Group(); this.canvas = new Group(); // this.canvas.setScaleY(-1); // this.canvas.setTranslateY(50); this.inputText = new TextArea(); this.inputText.setFont(Font.font("monospace")); this.outputText = new Text(); Button btn = new Button("Load"); btn.setOnAction(new EventHandler() { @Override public void handle(ActionEvent event) { loadFromText(); } }); BorderPane pane = new BorderPane(); // pane.getChildren().add(this.canvas); // pane.getChildren().add(inputText); VBox rpane = new VBox(this.inputText, this.outputText, btn); rpane.setMinWidth(120); rpane.setMaxWidth(120); pane.setRight(rpane); pane.setLeft(canvas); refreshTriangle(); stage.setTitle("Triangle Manipulation Sample"); stage.setScene( new Scene( pane, 400, 400, Color.ALICEBLUE ) ); stage.show(); } private void loadFromText() { String[] lines = inputText.getText().split("\n"); int n = Integer.parseInt(lines[0]); ArrayList coords = new ArrayList(); for (int i = 0; i < n; i++) { String[] s = lines[i + 1].trim().split(" "); coords.add(Double.parseDouble(s[0])); coords.add(Double.parseDouble(s[1])); } polygon.getPoints().setAll(coords); refreshTriangle(); } private void refreshTriangle() { ObservableList pts = polygon.getPoints(); canvas.getChildren().clear(); canvas.getChildren().add(new Rectangle (0, 0)); canvas.getChildren().add(polygon); canvas.getChildren().add(solutionPolygon); canvas.getChildren().add(solutionGroup); Group anchorGroup = new Group(); anchorGroup.getChildren().addAll(createControlAnchorsFor(pts)); canvas.getChildren().add(anchorGroup); refreshCoordinates(); } private void refreshCoordinates() { ObservableList pts = polygon.getPoints(); StringBuilder sb = new StringBuilder(); int size = pts.size(); sb.append(size / 2).append("\n"); for (int i = 0; i < size; i+=2) { Double x = pts.get(i); Double y = pts.get(i + 1); sb.append(String.format("%.0f %.0f\n", x, y)); } inputText.setText(sb.toString()); Solution solution = new Solution(); List solve = solution.solve(pts); solutionPolygon.getPoints().clear(); solutionPolygon.getPoints().setAll(solve); solutionGroup.getChildren().clear(); for (int i = 0; i < solve.size(); i+=2) { Circle circle = new Circle(solve.get(i), solve.get(i + 1), 2, Color.BLACK); solutionGroup.getChildren().add(circle); } this.outputText.setText("" + solve.size() / 2); } // creates a polygon. private Polygon createStartingPolygon() { Polygon poly = new Polygon(); poly.getPoints().setAll( 100d, 100d, 250d, 150d, 150d, 50d ); poly.setStroke(Color.FORESTGREEN); poly.setStrokeWidth(2); poly.setStrokeLineCap(StrokeLineCap.ROUND); poly.setFill(Color.CORNSILK.deriveColor(0, 1.2, 1, 0.6)); return poly; } private Polygon createSolutionPolygon() { Solution solution = new Solution(); List result = solution.solve(polygon.getPoints()); Polygon poly = new Polygon(); poly.getPoints().setAll(result); poly.setFill(Color.CYAN); return poly; } // @return a list of anchors which can be dragged around to modify points in the format [x1, y1, x2, y2...] private ObservableList createControlAnchorsFor(final ObservableList points) { ObservableList anchors = FXCollections.observableArrayList(); for (int i = 0; i < points.size(); i+=2) { final int idx = i; Double px = points.get(i); Double py = points.get(i + 1); Anchor anchor = createAnchor(points, idx, px, py); anchors.add(anchor); } return anchors; } private Anchor createAnchor(final ObservableList points, final int idx, Double px, Double py) { DoubleProperty xProperty = new SimpleDoubleProperty(px); DoubleProperty yProperty = new SimpleDoubleProperty(py); xProperty.addListener(new ChangeListener() { @Override public void changed(ObservableValue ov, Number oldX, Number x) { points.set(idx, x.doubleValue()); } }); yProperty.addListener(new ChangeListener() { @Override public void changed(ObservableValue ov, Number oldY, Number y) { points.set(idx + 1, y.doubleValue()); } }); return new Anchor(Color.GOLD, xProperty, yProperty, idx); } // a draggable anchor displayed around a point. class Anchor extends Circle { private final DoubleProperty x, y; private final int nodeIndex; Anchor(Color color, DoubleProperty x, DoubleProperty y, int index) { super(x.get(), y.get(), 4); this.nodeIndex = index; setFill(color.deriveColor(1, 1, 1, 0.5)); setStroke(color); setStrokeWidth(1); setStrokeType(StrokeType.OUTSIDE); this.x = x; this.y = y; x.bind(centerXProperty()); y.bind(centerYProperty()); enableDrag(); } // make a node movable by dragging it around with the mouse. private void enableDrag() { final Delta dragDelta = new Delta(); setOnMousePressed(new EventHandler() { @Override public void handle(MouseEvent mouseEvent) { // record a delta distance for the drag and drop operation. dragDelta.x = getCenterX() - mouseEvent.getX(); dragDelta.y = getCenterY() - mouseEvent.getY(); getScene().setCursor(Cursor.MOVE); } }); setOnMouseReleased(new EventHandler() { @Override public void handle(MouseEvent mouseEvent) { getScene().setCursor(Cursor.HAND); if (mouseEvent.isShortcutDown()) { ObservableList points = polygon.getPoints(); int n = points.size(); double midX = Math.round((points.get(nodeIndex) + points.get((nodeIndex + n - 2) % n)) / 2); double midY = Math.round((points.get(nodeIndex + 1) + points.get((nodeIndex + n - 1) % n)) / 2); points.add(nodeIndex, midX); points.add(nodeIndex + 1, midY); refreshTriangle(); mouseEvent.consume(); } if (mouseEvent.isAltDown()) { ObservableList points = polygon.getPoints(); if (points.size() > 6) { points.remove(nodeIndex, nodeIndex + 2); refreshTriangle(); mouseEvent.consume(); } } if (mouseEvent.isControlDown()) { addRandomPoints(nodeIndex, 10, 0.3); refreshTriangle(); mouseEvent.consume(); } } }); setOnMouseDragged(new EventHandler() { @Override public void handle(MouseEvent mouseEvent) { double newX = mouseEvent.getX() + dragDelta.x; if (newX > 0 && newX < getScene().getWidth()) { setCenterX(newX); } double newY = mouseEvent.getY() + dragDelta.y; if (newY > 0 && newY < getScene().getHeight()) { setCenterY(newY); } refreshCoordinates(); } }); setOnMouseEntered(new EventHandler() { @Override public void handle(MouseEvent mouseEvent) { if (!mouseEvent.isPrimaryButtonDown()) { getScene().setCursor(Cursor.HAND); } } }); setOnMouseExited(new EventHandler() { @Override public void handle(MouseEvent mouseEvent) { if (!mouseEvent.isPrimaryButtonDown()) { getScene().setCursor(Cursor.DEFAULT); } } }); } private Random random = new Random(); private void addRandomPoints(int nodeIndex, int numAdd, double randomness) { ObservableList points = polygon.getPoints(); int n = points.size(); Double x1 = points.get(nodeIndex); Double x2 = points.get((nodeIndex + n - 2) % n); Double y1 = points.get(nodeIndex + 1); Double y2 = points.get((nodeIndex + n - 1) % n); for (int i = 0; i < numAdd; i++) { double dx = (x2-x1) / (numAdd + 1); double dy = (y2-y1) / (numAdd + 1); double len = Math.sqrt(dx*dx+dy*dy); double midX = Math.round(dx * (i+1) + x1); double midY = Math.round(dy * (i+1) + y1); midX += len * randomness * (random.nextDouble() * 2 - 1); midY += len * randomness * (random.nextDouble() * 2 - 1); points.add(nodeIndex , midX); points.add(nodeIndex + 1, midY); } } // records relative x and y co-ordinates. private class Delta { double x, y; } } }