package de.gsi.silly.samples.plugins; import javafx.beans.property.DoubleProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.scene.paint.Paint; import javafx.scene.shape.LineTo; import javafx.scene.shape.MoveTo; import javafx.scene.shape.Path; import com.sun.javafx.scene.DirtyBits; import com.sun.javafx.scene.NodeHelper; /** * Basic implementation of a fractal Koch snowflake. * For details see: https://en.wikipedia.org/wiki/Koch_snowflake * * @author rstein */ public class SnowFlake extends Path { /** * Defines the horizontal position of the center of the circle in pixels. * * @defaultValue 0.0 */ private final DoubleProperty centerX = new SimpleDoubleProperty(this, "centerX", 0.0) { @Override public void invalidated() { NodeHelper.markDirty(SnowFlake.this, DirtyBits.NODE_GEOMETRY); NodeHelper.geomChanged(SnowFlake.this); setTranslateX(get() - radius.get()); } }; /** * Defines the vertical position of the center of the circle in pixels. * * @defaultValue 0.0 */ private final DoubleProperty centerY = new SimpleDoubleProperty(this, "centerY", 0.0) { @Override public void invalidated() { NodeHelper.markDirty(SnowFlake.this, DirtyBits.NODE_GEOMETRY); NodeHelper.geomChanged(SnowFlake.this); setTranslateX(get() - radius.get()); } }; /** * Defines the radius of the circle in pixels. * * @defaultValue 5.0 */ private final DoubleProperty radius = new SimpleDoubleProperty(this, "radius", 5.0) { @Override public void invalidated() { NodeHelper.markDirty(SnowFlake.this, DirtyBits.NODE_GEOMETRY); NodeHelper.geomChanged(SnowFlake.this); updatePath(); } }; /** * Defines the number of recursive iterations for Koch's snowflake * * @defaultValue 3 */ private final IntegerProperty nIterations = new SimpleIntegerProperty(this, "nIterations", 3) { @Override public void invalidated() { NodeHelper.markDirty(SnowFlake.this, DirtyBits.NODE_GEOMETRY); NodeHelper.geomChanged(SnowFlake.this); updatePath(); } }; private double xState; private double yState; private double angleState; public SnowFlake(final double centerX, final double centerY, final double radius, final int recursion, final Paint fill) { super(); setCenterX(centerX); setCenterY(centerY); setRadius(radius); setFill(fill); setRecursion(recursion); updatePath(); // NOPMD } public SnowFlake(final double radius, final Paint fill) { this(0.0, 0.0, radius, 3, fill); } public final DoubleProperty centerXProperty() { return centerX; } public final DoubleProperty centerYProperty() { return centerY; } public final double getCenterX() { return centerX.get(); } public final double getCenterY() { return centerY.get(); } public final double getRadius() { return radius.get(); } public final int getRecursion() { return nIterations.get(); } public final DoubleProperty radiusProperty() { return radius; } public final IntegerProperty recursionProperty() { return nIterations; } public final void setCenterX(double value) { if (value != 0.0) { centerXProperty().set(value); } } public final void setCenterY(double value) { if (value != 0.0) { centerYProperty().set(value); } } public final void setRadius(double value) { radius.set(value); } public final void setRecursion(int value) { nIterations.set(value); } private void koch(int n, double size) { if (n == 0) { xState += size * Math.cos(Math.toRadians(angleState)); yState += size * Math.sin(Math.toRadians(angleState)); this.getElements().add(new LineTo(xState, yState)); } else { koch(n - 1, size); angleState += 60.0; koch(n - 1, size); angleState -= 120.0; koch(n - 1, size); angleState += 60.0; koch(n - 1, size); } } protected void updatePath() { getElements().clear(); final int n = Math.max(getRecursion(), 1); final double flakeRadius = getRadius(); final double side = Math.abs(flakeRadius * Math.sqrt(3) / Math.pow(3, n)); xState = 0.0; yState = 0.0; angleState = 0.0; getElements().add(new MoveTo(flakeRadius, 0.0f)); for (int i = 0; i < 3; i++) { koch(n, side); angleState -= 120.0; } getElements().add(new LineTo(flakeRadius, 0.0f)); setTranslateX(this.getCenterX() - flakeRadius); setTranslateY(this.getCenterY() - flakeRadius); } }