/*
* Copyright 2017 Laszlo Balazs-Csiki
*
* This file is part of Pixelitor. Pixelitor is free software: you
* can redistribute it and/or modify it under the terms of the GNU
* General Public License, version 3 as published by the Free
* Software Foundation.
*
* Pixelitor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Pixelitor. If not, see <http://www.gnu.org/licenses/>.
*/
package pixelitor.filters;
import pixelitor.filters.gui.IntChoiceParam;
import pixelitor.filters.gui.IntChoiceParam.Value;
import pixelitor.filters.gui.RangeParam;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
* "Flower of Life" filter
*/
public class FlowerOfLife extends ShapeFilter {
private final static int GRID_TYPE_TRIANGULAR = 1;
private final static int GRID_TYPE_SQUARE = 2;
private final static int GRID_TYPE_SQUARE_2 = 3;
public static final double SQRT_2 = 1.4142135623730950488016887242097;
private final RangeParam radius = new RangeParam("Radius", 1, 50, 100);
private final RangeParam iterations = new RangeParam("Iterations", 1, 3, 10);
private final IntChoiceParam grid = new IntChoiceParam("Grid Type", new Value[]{
new Value("Triangular", GRID_TYPE_TRIANGULAR),
new Value("Square", GRID_TYPE_SQUARE),
new Value("Square 2", GRID_TYPE_SQUARE_2)
});
public FlowerOfLife() {
addParamsToFront(
radius.withAdjustedRange(0.2),
iterations,
grid);
}
@Override
protected Shape createShape(int width, int height) {
Path2D shape = new Path2D.Double();
double r = radius.getValueAsDouble();
double cx = width * center.getRelativeX();
double cy = height * center.getRelativeY();
Circle firstCircle = new Circle(cx, cy, r);
Set<Circle> circles = new HashSet<>();
circles.add(firstCircle);
int gridType = grid.getValue();
int numIterations = iterations.getValue();
for (int it = 2; it <= numIterations; it++) {
List<Circle> circlesSoFar = new ArrayList<>(circles);
for (Circle circle : circlesSoFar) {
List<Circle> neighbors;
if (gridType == GRID_TYPE_TRIANGULAR) {
neighbors = circle.genTriangleGridNeighbors();
} else if (gridType == GRID_TYPE_SQUARE) {
neighbors = circle.genSquareGridNeighbors();
} else if (gridType == GRID_TYPE_SQUARE_2) {
neighbors = circle.genSquare2GridNeighbors();
} else {
throw new IllegalStateException("gridType = " + gridType);
}
circles.addAll(neighbors);
}
}
// int numCircles = circles.size();
// System.out.println("FlowerOfLife::createShape: numIterations = " + numIterations + ", numCircles = " + numCircles);
for (Circle circle : circles) {
shape.append(circle.toShape(), false);
}
return shape;
}
private static class Circle {
final double cx;
final double cy;
final double r;
public Circle(double cx, double cy, double r) {
this.cx = cx;
this.cy = cy;
this.r = r;
}
List<Circle> genTriangleGridNeighbors() {
List<Circle> n = new ArrayList<>(6);
double rowHeight = r * 0.86602540378443864676372317075294; // sqrt(3)/2
double halfRadius = r / 2;
n.add(new Circle(cx + r, cy, r)); // right
n.add(new Circle(cx - r, cy, r)); // left
n.add(new Circle(cx - halfRadius, cy - rowHeight, r)); // top left
n.add(new Circle(cx + halfRadius, cy - rowHeight, r)); // top right
n.add(new Circle(cx - halfRadius, cy + rowHeight, r)); // bottom left
n.add(new Circle(cx + halfRadius, cy + rowHeight, r)); // bottom right
return n;
}
List<Circle> genSquareGridNeighbors() {
List<Circle> n = new ArrayList<>(6);
double distance = r * SQRT_2;
n.add(new Circle(cx - distance, cy, r)); // left
n.add(new Circle(cx + distance, cy, r)); // right
n.add(new Circle(cx, cy - distance, r)); // top
n.add(new Circle(cx, cy + distance, r)); // bottom
return n;
}
List<Circle> genSquare2GridNeighbors() {
List<Circle> n = new ArrayList<>(6);
double distance = r * SQRT_2;
n.add(new Circle(cx - distance, cy - distance, r)); // top left
n.add(new Circle(cx + distance, cy - distance, r)); // top right
n.add(new Circle(cx - distance, cy + distance, r)); // bottom left
n.add(new Circle(cx + distance, cy + distance, r)); // bottom right
return n;
}
Shape toShape() {
double d = 2 * r;
return new Ellipse2D.Double(cx - r, cy - r, d, d);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Circle circle = (Circle) o;
return Double.compare(circle.cx, cx) == 0 &&
Double.compare(circle.cy, cy) == 0;
}
@Override
public int hashCode() {
return Objects.hash(cx, cy);
}
}
@Override
protected float getGradientRadius(float cx, float cy) {
int gridType = grid.getValue();
float r = radius.getValueAsFloat();
int it = iterations.getValue();
if (gridType == GRID_TYPE_TRIANGULAR) {
return it * r;
} else if (gridType == GRID_TYPE_SQUARE) {
return (float) (r + (it - 1) * SQRT_2 * r);
} else if (gridType == GRID_TYPE_SQUARE_2) {
return r + (it - 1) * 2 * r;
} else {
throw new IllegalStateException("gridType = " + gridType);
}
}
}