/*
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.
*/
package org.jwildfire.create.tina.variation;
import static java.lang.Math.abs;
import static org.jwildfire.base.mathlib.MathLib.M_PI;
import static org.jwildfire.base.mathlib.MathLib.atan2;
import static org.jwildfire.base.mathlib.MathLib.cos;
import static org.jwildfire.base.mathlib.MathLib.exp;
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.base.mathlib.MathLib.sqrt;
import org.jwildfire.create.tina.base.Layer;
import org.jwildfire.create.tina.base.XForm;
import org.jwildfire.create.tina.base.XYZPoint;
/**
Butterfly2, a variation based on the Butterfly curve, discovered ~1988 by Temple H. Fay
Implemented by CozyG, March 2015
For references, see http://en.wikipedia.org/wiki/Butterfly_curve_%28transcendental%29
*/
public class ButterflyFayFunc extends VariationFunc {
private static final long serialVersionUID = 1L;
private static final String PARAM_OFFSET = "offset";
private static final String PARAM_UNIFIED_INNER_OUTER = "unified_inner_outer";
private static final String PARAM_OUTER_MODE = "outer_mode";
private static final String PARAM_INNER_MODE = "inner_mode";
private static final String PARAM_OUTER_SPREAD = "outer_spread";
private static final String PARAM_INNER_SPREAD = "inner_spread";
private static final String PARAM_OUTER_SPREAD_RATIO = "outer_spread_ratio";
private static final String PARAM_INNER_SPREAD_RATIO = "inner_spread_ratio";
private static final String PARAM_SPREAD_SPLIT = "spread_split";
private static final String PARAM_FILL = "fill";
// my standard approach if there is a cycles variable is that if cycles is set to 0,
// that means function should decide cycle value automatically
// for curves, function will make best effort to calculate minimum number of cycles needed
// to close curve, or a somewhat arbitrary number if cannot
private static final String PARAM_CYCLES = "cycles";
private static final String[] paramNames = { PARAM_OFFSET, PARAM_UNIFIED_INNER_OUTER, PARAM_OUTER_MODE, PARAM_INNER_MODE, PARAM_OUTER_SPREAD, PARAM_INNER_SPREAD, PARAM_OUTER_SPREAD_RATIO, PARAM_INNER_SPREAD_RATIO, PARAM_SPREAD_SPLIT, PARAM_CYCLES, PARAM_FILL };
private double cycles = 0; // number of cycles (2*PI radians, circle circumference), if set to 0 then number of cycles is calculated automatically
private double offset = 0; // offset c from equations
private int unified_inner_outer = 1;
private int outer_mode = 1;
private int inner_mode = 1;
private double outer_spread = 0; // deform based on original x/y
private double inner_spread = 0; // deform based on original x/y
private double outer_spread_ratio = 1; // how much outer_spread applies to x relative to y
private double inner_spread_ratio = 1; // how much outer_spread applies to x relative to y
private double spread_split = 1;
private double fill = 0;
// GAH 3/13/2015:
// I don't have a mathematical proof yet,
// but observationally the butterfly curve is closed at 2*(PI)^3 radians (or cycles * (PI)^2 )
// can't find this reported anywhere -- did I just make a discovery??
// side note:
// at 2*PI (1 cycle) appears to be closed, but really isn't, this is just a matter of resolution
// 2nd cycle almost exactly follows path of 1rst cycle, then with start of 3rd cycle starts to diverge more obviously
private double number_of_cycles; // 1 cycle = 2*PI
private double cycle_length = 2 * M_PI; // 2(PI)
private double radians_to_close = 2 * M_PI * M_PI * M_PI; // 2(PI)^3
private double cycles_to_close = radians_to_close / cycle_length; // = PI^2
@Override
public void init(FlameTransformationContext pContext, Layer pLayer, XForm pXForm, double pAmount) {
// setting cyclesParam to 0 is meant to indicate the function should determine how many cycles
// (actually setting cycles = 0 will just yield a single point)
// for the butterfly curve I am taking that to mean closing the curve, which as noted above
// I have observationally determined is at exactly 2(PI)^3 radians, which is same as (PI)^2 cycles
if (cycles == 0) {
number_of_cycles = cycles_to_close;
}
else {
number_of_cycles = cycles;
}
}
@Override
public void transform(FlameTransformationContext pContext, XForm pXForm, XYZPoint pAffineTP, XYZPoint pVarTP, double pAmount) {
double theta = atan2(pAffineTP.y, pAffineTP.x); // atan2 range is [-PI, PI], so covers 2PI, or 1 cycle
double t = number_of_cycles * theta;
// r = e^cos(t) - 2cos(4t) - sin^5(t/12)
// y = sin(t)*r
// x = cos(t)*r
double rin = spread_split * sqrt((pAffineTP.x * pAffineTP.x) + (pAffineTP.y * pAffineTP.y));
double r = 0.5 * (exp(cos(t)) - (2 * cos(4 * t)) - pow(sin(t / 12), 5) + offset);
// a "fully" parameterized version:
// double r = 0.5 * ( (m1 * exp(m4 * cos(t + b1) + b4)) - (m2 * 2 * (cos(m5*4*t + b2) + b5)) - (m3 * pow(m6 * (sin(m7 * t/12 + b3) + b6), (m8 * 5 + b7))) + offset);
if (fill != 0) {
r = r + (fill * (pContext.random() - 0.5));
}
double x = r * sin(t);
double y = -1 * r * cos(t); // adding -1 to flip butterfly to point up
double xin, yin;
double rinx, riny;
if ((abs(rin) > abs(r)) || (unified_inner_outer == 1)) { // incoming point lies "outside" of curve OR ignoring inner_outer distinction
switch (outer_mode) {
case 0: // no spread
pVarTP.x += pAmount * x;
pVarTP.y += pAmount * y;
break;
case 1:
rinx = (rin * outer_spread * outer_spread_ratio) - (outer_spread * outer_spread_ratio) + 1;
riny = (rin * outer_spread) - outer_spread + 1;
pVarTP.x += pAmount * rinx * x;
pVarTP.y += pAmount * riny * y;
break;
case 2:
xin = Math.abs(pAffineTP.x);
yin = Math.abs(pAffineTP.y);
if (x < 0) {
xin = xin * -1;
}
if (y < 0) {
yin = yin * -1;
}
pVarTP.x += pAmount * (x + (outer_spread * outer_spread_ratio * (xin - x)));
pVarTP.y += pAmount * (y + (outer_spread * (yin - y)));
break;
case 3:
xin = Math.abs(pAffineTP.x);
yin = Math.abs(pAffineTP.y);
if (x < 0) {
xin = xin * -1;
}
if (y < 0) {
yin = yin * -1;
}
pVarTP.x += pAmount * (x + (outer_spread * outer_spread_ratio * xin));
pVarTP.y += pAmount * (y + (outer_spread * yin));
break;
case 4:
rinx = (0.5 * rin) + (outer_spread * outer_spread_ratio);
riny = (0.5 * rin) + outer_spread;
pVarTP.x += pAmount * rinx * x;
pVarTP.y += pAmount * riny * y;
break;
case 5: // original butterfly2 implementation (same as outer_mode 3, but without the sign modifications)
pVarTP.x += pAmount * (x + (outer_spread * outer_spread_ratio * pAffineTP.x));
pVarTP.y += pAmount * (y + (outer_spread * pAffineTP.y));
break;
default:
pVarTP.x += pAmount * x;
pVarTP.y += pAmount * y;
break;
}
}
else { // incoming point lies "inside" or "on" curve
switch (inner_mode) {
case 0: // no spread
pVarTP.x += pAmount * x;
pVarTP.y += pAmount * y;
break;
case 1:
rinx = (rin * inner_spread * inner_spread_ratio) - (inner_spread * inner_spread_ratio) + 1;
riny = (rin * inner_spread) - inner_spread + 1;
pVarTP.x += pAmount * rinx * x;
pVarTP.y += pAmount * riny * y;
break;
case 2:
xin = Math.abs(pAffineTP.x);
yin = Math.abs(pAffineTP.y);
if (x < 0) {
xin = xin * -1;
}
if (y < 0) {
yin = yin * -1;
}
pVarTP.x += pAmount * (x - (inner_spread * inner_spread_ratio * (x - xin)));
pVarTP.y += pAmount * (y - (inner_spread * (y - yin)));
break;
case 3:
xin = Math.abs(pAffineTP.x);
yin = Math.abs(pAffineTP.y);
if (x < 0) {
xin = xin * -1;
}
if (y < 0) {
yin = yin * -1;
}
pVarTP.x += pAmount * (x - (inner_spread * inner_spread_ratio * xin));
pVarTP.y += pAmount * (y - (inner_spread * yin));
break;
case 4:
rinx = (0.5 * rin) + (inner_spread * inner_spread_ratio);
riny = (0.5 * rin) + inner_spread;
pVarTP.x += pAmount * rinx * x;
pVarTP.y += pAmount * riny * y;
break;
case 5: // original butterfly2 implementation (same as inner_mode 3, but without the sign modifications)
pVarTP.x += pAmount * (x + (inner_spread * inner_spread_ratio * pAffineTP.x));
pVarTP.y += pAmount * (y + (inner_spread * pAffineTP.y));
break;
default:
pVarTP.x += pAmount * x;
pVarTP.y += pAmount * y;
break;
}
}
if (pContext.isPreserveZCoordinate()) {
pVarTP.z += pAmount * pAffineTP.z;
}
}
@Override
public String[] getParameterNames() {
return paramNames;
}
@Override
public Object[] getParameterValues() {
return new Object[] { offset, unified_inner_outer, outer_mode, inner_mode, outer_spread, inner_spread, outer_spread_ratio, inner_spread_ratio, spread_split, cycles, fill };
}
@Override
public void setParameter(String pName, double pValue) {
if (PARAM_OFFSET.equalsIgnoreCase(pName))
offset = pValue;
else if (PARAM_UNIFIED_INNER_OUTER.equalsIgnoreCase(pName)) {
unified_inner_outer = (pValue == 0 ? 0 : 1);
}
else if (PARAM_OUTER_MODE.equalsIgnoreCase(pName)) {
outer_mode = (int) floor(pValue);
if (outer_mode > 5 || outer_mode < 0) {
outer_mode = 0;
}
}
else if (PARAM_INNER_MODE.equalsIgnoreCase(pName)) {
inner_mode = (int) floor(pValue);
if (inner_mode > 5 || inner_mode < 0) {
inner_mode = 0;
}
}
else if (PARAM_OUTER_SPREAD.equalsIgnoreCase(pName))
outer_spread = pValue;
else if (PARAM_INNER_SPREAD.equalsIgnoreCase(pName))
inner_spread = pValue;
else if (PARAM_OUTER_SPREAD_RATIO.equalsIgnoreCase(pName))
outer_spread_ratio = pValue;
else if (PARAM_INNER_SPREAD_RATIO.equalsIgnoreCase(pName))
inner_spread_ratio = pValue;
else if (PARAM_SPREAD_SPLIT.equalsIgnoreCase(pName))
spread_split = pValue;
else if (PARAM_CYCLES.equalsIgnoreCase(pName))
cycles = pValue;
else if (PARAM_FILL.equalsIgnoreCase(pName))
fill = pValue;
else
throw new IllegalArgumentException(pName);
}
@Override
public String getName() {
return "butterfly_fay";
}
}