/*
JWildfire - an image and animation processor written in Java
Copyright (C) 1995-2011 Andreas Maschke
This is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This software 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with this software;
if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
// Port of Hexes and Crackle plugin by slobo777, see http://slobo777.deviantart.com/art/Apo-Plugins-Hexes-And-Crackle-99243824
// All credits for this wonderful plugin to him!
// "Hexes" variation breaks plane into hexagonal cells and applies same
// power, scaling, rotation.
package org.jwildfire.create.tina.variation;
import static org.jwildfire.base.mathlib.MathLib.cos;
import static org.jwildfire.base.mathlib.MathLib.floor;
import static org.jwildfire.base.mathlib.MathLib.pow;
import static org.jwildfire.base.mathlib.MathLib.sin;
import static org.jwildfire.create.tina.variation.VoronoiTools.VORONOI_MAXPOINTS;
import static org.jwildfire.create.tina.variation.VoronoiTools._x_;
import static org.jwildfire.create.tina.variation.VoronoiTools._y_;
import static org.jwildfire.create.tina.variation.VoronoiTools.closest;
import static org.jwildfire.create.tina.variation.VoronoiTools.voronoi;
import org.jwildfire.base.mathlib.MathLib;
import org.jwildfire.create.tina.base.Layer;
import org.jwildfire.create.tina.base.XForm;
import org.jwildfire.create.tina.base.XYZPoint;
public class HexesFunc extends VariationFunc {
private static final long serialVersionUID = 1L;
private static final String PARAM_CELLSIZE = "cellsize";
private static final String PARAM_POWER = "power";
private static final String PARAM_ROTATE = "rotate";
private static final String PARAM_SCALE = "scale";
protected static final String[] paramNames = { PARAM_CELLSIZE, PARAM_POWER, PARAM_ROTATE, PARAM_SCALE };
private double cellsize = 1.0;
private double power = 1.0;
private double rotate = 0.166;
private double scale = 1.0;
//Following are pre-calculated fixed multipliers for converting
//between "Hex" co-ordinates and "Original" co-ordinates.
//This is, in fact, an affine transform. So we'll use same notation
//as for Apophysis transforms:
//x_hex = a_hex * x_cartesian + b_hex * y_cartesian
//y_hex = c_hex * x_cartesian + d_hex * y_cartesian
// . . . and the reverse:
//x_cartesian = a_cart * x_hex + b_cart * y_hex
//y_cartesian = c_cart * x_hex + d_cart * y_hex
//Values for e and f are 0.0 in both cases, so not required.
//Xh = (Xo + sqrt(3) * Yo) / (3 * l)
private static final double a_hex = 1.0 / 3.0;
private static final double b_hex = 1.7320508075688772935 / 3.0;
//Now: Xh = ( a_hex * Xo + b_hex * Yo ) / l;
//Yh = (-Xo + sqrt(3) * Yo) / (3 * l)
private static final double c_hex = -1.0 / 3.0;
private static final double d_hex = 1.7320508075688772935 / 3.0;
//Now: Yh = ( c_hex * Xo + d_hex * Yo ) / l;
//Xo = 3/2 * l * (Xh - Yh)
private static final double a_cart = 1.5;
private static final double b_cart = -1.5;
//Now: Xo = ( a_cart * Xh + b_cart * Yh ) * l;
//Yo = sqrt(3)/2 * l * (Xh + Yh)
private static final double c_cart = 1.7320508075688772935 / 2.0;
private static final double d_cart = 1.7320508075688772935 / 2.0;
//Now: Yo = ( c_cart * Xh + d_cart * Yh ) * l;
private static final int cell_choice[][] = new int[][] { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 }, { 0, 0 }, { 0, 1 }, { 1, -1 }, { 1, 0 }, { 1, 1 } };
//centre gives centre co-ordinates either from cache,
//or calculated from scratch if needed
private void cell_centre(int x, int y, double s, double V[]) {
V[_x_] = (a_cart * x + b_cart * y) * s;
V[_y_] = (c_cart * x + d_cart * y) * s;
}
@Override
public void transform(FlameTransformationContext pContext, XForm pXForm, XYZPoint pAffineTP, XYZPoint pVarTP, double pAmount) {
double P[][] = new double[VORONOI_MAXPOINTS][2];
double DXo, DYo, L, L1, L2, R, s, trgL, Vx, Vy;
double U[] = new double[2];
int Hx, Hy;
// For speed/convenience
s = this.cellsize;
// Infinite number of small cells? No effect . . .
if (0.0 == s) {
return;
}
// Get cartesian co-ordinates, and convert to hex co-ordinates
U[_x_] = pAffineTP.x;
U[_y_] = pAffineTP.y;
Hx = (int) floor((a_hex * U[_x_] + b_hex * U[_y_]) / s);
Hy = (int) floor((c_hex * U[_x_] + d_hex * U[_y_]) / s);
// Get a set of 9 hex centre points, based around the one above
int i = 0;
int di, dj;
for (di = -1; di < 2; di++) {
for (dj = -1; dj < 2; dj++) {
cell_centre(Hx + di, Hy + dj, s, P[i]);
i++;
}
}
int q = closest(P, 9, U);
// Remake list starting from chosen hex, ensure it is completely surrounded (total 7 points)
// First adjust centres according to which one was found to be closest
Hx += cell_choice[q][_x_];
Hy += cell_choice[q][_y_];
// First point is central/closest
cell_centre(Hx, Hy, cellsize, P[0]);
// In hex co-ords, offsets are: (0,1) (1,1) (1,0) (0,-1) (-1,-1) (-1, 0)
cell_centre(Hx, Hy + 1, s, P[1]);
cell_centre(Hx + 1, Hy + 1, s, P[2]);
cell_centre(Hx + 1, Hy, s, P[3]);
cell_centre(Hx, Hy - 1, s, P[4]);
cell_centre(Hx - 1, Hy - 1, s, P[5]);
cell_centre(Hx - 1, Hy, s, P[6]);
L1 = voronoi(P, 7, 0, U);
// Delta vector from centre of hex
DXo = U[_x_] - P[0][_x_];
DYo = U[_y_] - P[0][_y_];
/////////////////////////////////////////////////////////////////
// Apply "interesting bit" to cell's DXo and DYo co-ordinates
// trgL is the defined value of l, independent of any rotation
trgL = pow(L1 + 1e-100, power) * scale;
// Rotate
Vx = DXo * rotCos + DYo * rotSin;
Vy = -DXo * rotSin + DYo * rotCos;
//////////////////////////////////////////////////////////////////
// Measure voronoi distance again
U[_x_] = Vx + P[0][_x_];
U[_y_] = Vy + P[0][_y_];
L2 = voronoi(P, 7, 0, U);
//////////////////////////////////////////////////////////////////
// Scale to meet target size . . . adjust according to how close
// we are to the edge
// Code here attempts to remove the "rosette" effect caused by
// scaling difference between corners and closer edges
// L is maximum of L1 or L2 . . .
// When L = 0.8 or higher . . . match trgL/L2 exactly
// When L = 0.5 or less . . . match trgL/L1 exactly
L = (L1 > L2) ? L1 : L2;
if (L < 0.5) {
R = trgL / L1;
}
else {
if (L > 0.8) {
R = trgL / L2;
}
else {
R = ((trgL / L1) * (0.8 - L) + (trgL / L2) * (L - 0.5)) / 0.3;
}
}
Vx *= R;
Vy *= R;
// Add cell centre co-ordinates back in
Vx += P[0][_x_];
Vy += P[0][_y_];
// Finally add values in
applyCellCalculation(pVarTP, pAmount, L, Vx, Vy);
}
protected void applyCellCalculation(XYZPoint pVarTP, double pAmount, double L, double Vx, double Vy) {
pVarTP.x += pAmount * Vx;
pVarTP.y += pAmount * Vy;
}
@Override
public String[] getParameterNames() {
return paramNames;
}
@Override
public Object[] getParameterValues() {
return new Object[] { cellsize, power, rotate, scale };
}
@Override
public void setParameter(String pName, double pValue) {
if (PARAM_CELLSIZE.equalsIgnoreCase(pName))
cellsize = pValue;
else if (PARAM_POWER.equalsIgnoreCase(pName))
power = pValue;
else if (PARAM_ROTATE.equalsIgnoreCase(pName))
rotate = pValue;
else if (PARAM_SCALE.equalsIgnoreCase(pName))
scale = pValue;
else
throw new IllegalArgumentException(pName);
}
@Override
public String getName() {
return "hexes";
}
private double rotSin, rotCos;
@Override
public void init(FlameTransformationContext pContext, Layer pLayer, XForm pXForm, double pAmount) {
rotSin = sin(rotate * 2.0 * MathLib.M_PI);
rotCos = cos(rotate * 2.0 * MathLib.M_PI);
}
}