/** * Creates DisplacementMap effect for the bend * * @param node target node * @param path path that is used to draw the opposite side of a bend Its fill is updated with * linear gradient. * @param shadow path that is used to draw the shadow of a bend Its fill is updated with linear * gradient. * @param clip path that is used to clip the content of the page either for mouse operations or * for visuals */ public BookBend(final Node node, Path path, Path shadow, Path clip) { this.node = node; this.p = path; this.shadow = shadow; this.clip = clip; node.setEffect(new DisplacementMap(map)); node.layoutBoundsProperty() .addListener( new InvalidationListener() { @Override public void invalidated(Observable arg0) { newWidth = (int) Math.round(node.getLayoutBounds().getWidth()); newHeight = (int) Math.round(node.getLayoutBounds().getHeight()); if (newWidth != map.getWidth() || newHeight != map.getHeight()) { setUpdateNeeded(true); } } }); node.sceneProperty() .addListener( new ChangeListener<Scene>() { @Override public void changed( ObservableValue<? extends Scene> ov, Scene oldValue, Scene newValue) { if (newValue == null) { stopAnimationTimer(); } } }); newWidth = (int) Math.round(node.getLayoutBounds().getWidth()); newHeight = (int) Math.round(node.getLayoutBounds().getHeight()); }
private void init(double firingRadius, double templateWidth) { double x0 = templateWidth / 2.0; double y0 = -templateWidth / 2.0; double x5 = templateWidth / 2.0; double y5 = templateWidth / 2.0; double x1 = x0 + firingRadius * Math.cos(45.0 / 180.0 * Math.PI); double y1 = y0 - firingRadius * Math.sin(45.0 / 180.0 * Math.PI); double x2 = x0 + firingRadius; double y2 = y0; double x3 = x5 + firingRadius; double y3 = y5; double x4 = x5 + firingRadius * Math.cos(45.0 / 180.0 * Math.PI); double y4 = y5 + firingRadius * Math.sin(45.0 / 180.0 * Math.PI); this.getElements().add(new MoveTo(x0, y0)); this.getElements().add(new LineTo(x1, y1)); this.getElements().add(new ArcTo(firingRadius, firingRadius, 45, x2, y2, false, true)); this.getElements().add(new LineTo(x3, y3)); this.getElements().add(new ArcTo(firingRadius, firingRadius, 45, x4, y4, false, true)); this.getElements().add(new LineTo(x5, y5)); this.getElements().add(new LineTo(x0, y0)); this.setFill(Color.rgb(255, 0, 0, 0.35)); this.setStroke(Color.rgb(255, 0, 0, 0.5)); this.setFillRule(FillRule.NON_ZERO); }
private static double hypot(Point2D p1, Point2D p2) { return Math.hypot(p1.getX() - p2.getX(), p1.getY() - p2.getY()); }
/** Updates DisplacementMap and path for current coordinates. */ public void update() { setUpdateNeeded(false); if (newWidth == map.getWidth() && newHeight == map.getHeight() && targetX == oldTargetX && targetY == oldTargetY) { return; } oldTargetX = targetX; oldTargetY = targetY; if (newWidth != map.getWidth() || newHeight != map.getHeight()) { map.setWidth(newWidth); map.setHeight(newHeight); } final double W = node.getLayoutBounds().getWidth(); final double H = node.getLayoutBounds().getHeight(); // target point F for folded corner final double xF = Math.min(targetX, W - 1); final double yF = Math.min(targetY, H - 1); final Point2D F = new Point2D(xF, yF); // corner point O final double xO = W; final double yO = H; // distance between them final double FO = Math.hypot(xF - xO, yF - yO); final double AF = FO / 2; final double AC = Math.min(AF * 0.5, 200); // radius of the fold as seen along the l2 line final double R = AC / Math.PI * 1.5; final double BC = R; final double flat_R = AC; // Gradient for the line from target point to corner point final double K = (yO - yF) / (xO - xF); // angle of a line l1 final double ANGLE = Math.atan(1 / K); // point A (on line l1 - the mirror line of target and corner points) final double xA = (xO + xF) / 2; final double yA = (yO + yF) / 2; // end points of line l1 final double bottomX = xA - (H - yA) * K; final double bottomY = H; final double rightX = W; final double rightY = yA - (W - xA) / K; final Point2D RL1 = new Point2D(rightX, rightY); final Point2D BL1 = new Point2D(bottomX, bottomY); // point C (on line l2 - the line when distortion begins) final double kC = AC / AF; final double xC = xA - (xA - xF) * kC; final double yC = yA - (yA - yF) * kC; final Point2D C = new Point2D(xC, yC); final Point2D RL2 = new Point2D(W, yC - (W - xC) / K); final Point2D BL2 = new Point2D(xC - (H - yC) * K, H); // point B (on line l3 - the line where distortion ends) final double kB = BC / AC; final double xB = xC + (xA - xC) * kB; final double yB = yC + (yA - yC) * kB; // Bottom ellipse calculations final Point2D BP1 = calcIntersection(F, BL1, BL2, C); final Point2D BP3 = BL2; final Point2D BP2 = middle(BP1, BP3, 0.5); final Point2D BP4 = new Point2D(xB + BP2.getX() - xC, yB + BP2.getY() - yC); final double bE_x1 = hypot(BP2, BP3); final double bE_y2 = -hypot(BP2, BP4); final double bE_yc = -hypot(BP2, BL1); final double bE_y0 = bE_y2 * bE_y2 / (2 * bE_y2 - bE_yc); final double bE_b = bE_y0 - bE_y2; final double bE_a = Math.sqrt(-bE_x1 * bE_x1 / bE_y0 * bE_b * bE_b / bE_yc); // Right ellipse calculations final Point2D RP1 = calcIntersection(F, RL1, RL2, C); final Point2D RP3 = RL2; final Point2D RP2 = middle(RP1, RP3, 0.5); final Point2D RP4 = new Point2D(xB + RP2.getX() - xC, yB + RP2.getY() - yC); final double rE_x1 = hypot(RP2, RP3); final double rE_y2 = -hypot(RP2, RP4); final double rE_yc = -hypot(RP2, RL1); final double rE_y0 = rE_y2 * rE_y2 / (2 * rE_y2 - rE_yc); final double rE_b = rE_y0 - rE_y2; final double rE_a = Math.sqrt(-rE_x1 * rE_x1 / rE_y0 * rE_b * rE_b / rE_yc); p.setFill( new LinearGradient( xF, yF, xO, yO, false, CycleMethod.NO_CYCLE, new Stop(0, pathColor), new Stop((xC - xF) / (xO - xF), bendStartColor), new Stop((xB - xF) / (xO - xF), bendEndColor))); p.getElements() .setAll( new MoveTo(BP4.getX(), BP4.getY()), ArcToBuilder.create() .XAxisRotation(Math.toDegrees(-ANGLE)) .radiusX(bE_a) .radiusY(bE_b) .x(BP1.getX()) .y(BP1.getY()) .build(), new LineTo(xF, yF), new LineTo(RP1.getX(), RP1.getY()), ArcToBuilder.create() .XAxisRotation(Math.toDegrees(-ANGLE)) .radiusX(rE_a) .radiusY(rE_b) .x(RP4.getX()) .y(RP4.getY()) .build(), new ClosePath()); if (shadow != null) { double level0 = (xB - xF) / (xO - xF) - R / FO * 0.5; double level1 = (xB - xF) / (xO - xF) + (0.3 + (200 - AC) / 200) * R / FO; shadow.setFill( new LinearGradient( xF, yF, xO, yO, false, CycleMethod.NO_CYCLE, new Stop(level0, Color.rgb(0, 0, 0, 0.7)), new Stop(level0 * 0.3 + level1 * 0.7, Color.rgb(0, 0, 0, 0.25)), new Stop(level1, Color.rgb(0, 0, 0, 0.0)), new Stop(1, Color.rgb(0, 0, 0, 0)))); shadow .getElements() .setAll( new MoveTo(RP3.getX(), RP3.getY()), ArcToBuilder.create() .XAxisRotation(Math.toDegrees(-ANGLE)) .radiusX(rE_a) .radiusY(rE_b) .x(RP4.getX()) .y(RP4.getY()) .sweepFlag(true) .build(), new LineTo(BP4.getX(), BP4.getY()), ArcToBuilder.create() .XAxisRotation(Math.toDegrees(-ANGLE)) .radiusX(bE_a) .radiusY(bE_b) .x(BP3.getX()) .y(BP3.getY()) .sweepFlag(true) .build(), new LineTo(xO, yO), new ClosePath()); } if (clip != null) { final Point2D RL3 = new Point2D(W, yB - (W - xB) / K); final Point2D BL3 = new Point2D(xB - (H - yB) * K, H); clip.getElements() .setAll( new MoveTo(0, 0), RL3.getY() > 0 ? new LineTo(W, 0) : new LineTo(0, 0), RL3.getY() >= 0 ? new LineTo(RL3.getX(), RL3.getY()) : new LineTo(xB - (0 - yB) * K, 0), BL3.getX() >= 0 ? new LineTo(BL3.getX(), BL3.getY()) : new LineTo(0, yB - (0 - xB) / K), BL3.getX() > 0 ? new LineTo(0, H) : new LineTo(0, 0), new ClosePath()); } final double K2 = -K; final double C2 = BP3.getX() - K2 * H; final double K3 = -K; final double C3 = xB - K3 * yB; final double STEP = Math.max(0.1, R / (buffer.length - 1)); final double HYPOT = Math.hypot(1, K); final double yR = 1.5 * R; double x_1 = 0, y_1 = 0, cur_len = 0; for (double len = 0; len <= R; len += STEP) { final int index = (int) Math.round(len / STEP); final double angle = Math.asin(len / R); final double y = yR * Math.cos(angle); if (len > 0) { cur_len += Math.hypot(y - y_1, len - x_1); } buffer[index][0] = (float) angle; buffer[index][1] = (float) (cur_len * flat_R); x_1 = len; y_1 = y; } double total_len = cur_len; for (double len = 0; len <= R; len += STEP) { final int index = (int) Math.round(len / STEP); final double flat_len = buffer[index][1] / total_len; final double delta_len = flat_len - len; final double xs = delta_len / HYPOT; final double ys = K * delta_len / HYPOT; buffer[index][0] = (float) (xs / W); buffer[index][1] = (float) (ys / H); } for (int y = 0; y < map.getHeight(); y++) { final double lx2 = K2 * (y + 0.5) + C2; final double lx3 = K3 * (y + 0.5) + C3; for (int x = 0; x < map.getWidth(); x++) { if (x + 0.5 < lx2) { map.setSamples(x, y, 0, 0); } else if (x + 0.5 >= lx3 - 1) { map.setSamples(x, y, 1, 0); } else { final double len = Math.abs((x + 0.5) - K2 * (y + 0.5) - C2) / HYPOT; final int index = (int) Math.round(len / STEP); map.setSamples(x, y, buffer[index][0], buffer[index][1]); } } } }
public double[] rotationCoordinates(double[] old, double angle) { return new double[] { (old[0] * Math.cos(Math.toRadians(angle)) - old[1] * Math.sin(Math.toRadians(angle))), (old[0] * Math.sin(Math.toRadians(angle)) + old[1] * Math.cos(Math.toRadians(angle))) }; }