/*
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 org.jwildfire.base.mathlib.MathLib.EPSILON;
import static org.jwildfire.base.mathlib.MathLib.M_2PI;
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.fabs;
import static org.jwildfire.base.mathlib.MathLib.sin;
import static org.jwildfire.base.mathlib.MathLib.sqr;
import static org.jwildfire.base.mathlib.MathLib.sqrt;
import org.jwildfire.base.Tools;
import org.jwildfire.create.tina.base.Layer;
import org.jwildfire.create.tina.base.XForm;
import org.jwildfire.create.tina.base.XYZPoint;
public class PRose3DFunc extends VariationFunc {
private static final long serialVersionUID = 1L;
private static final String PARAM_L = "l";
private static final String PARAM_K = "k";
private static final String PARAM_C = "c";
private static final String PARAM_Z1 = "z1";
private static final String PARAM_Z2 = "z2";
private static final String PARAM_REF_SC = "refSc";
private static final String PARAM_OPT = "opt";
private static final String PARAM_OPT_SC = "optSc";
private static final String PARAM_OPT3 = "opt3";
private static final String PARAM_TRANSP = "transp";
private static final String PARAM_DIST = "dist";
private static final String PARAM_WAGSC = "wagsc";
private static final String PARAM_CRVSC = "srvsc";
private static final String PARAM_F = "f";
private static final String PARAM_WIGSC = "wigsc";
private static final String PARAM_OFFSET = "offset";
private static final String[] paramNames = { PARAM_L, PARAM_K, PARAM_C, PARAM_Z1, PARAM_Z2, PARAM_REF_SC, PARAM_OPT, PARAM_OPT_SC, PARAM_OPT3, PARAM_TRANSP, PARAM_DIST, PARAM_WAGSC, PARAM_CRVSC, PARAM_F, PARAM_WIGSC, PARAM_OFFSET };
private double l = 1.0;
private double k = 3.0 + (Math.random() < 0.5 ? Math.random() * 10.0 : Tools.randomInt(15));
private double c = 0.0;
private double z1 = 1.0;
private double z2 = 1.0;
private double refSc = 1.0;
private double opt = 1.0;
private double optSc = 1.0;
private double opt3 = 0.0;
private double transp = 0.5;
private double dist = 1.0;
private double wagsc = 0.0;
private double crvsc = 0.0;
private double f = 3.0;
private double wigsc = 0.0;
private double offset = 0;
@Override
public void transform(FlameTransformationContext pContext, XForm pXForm, XYZPoint pAffineTP, XYZPoint pVarTP, double pAmount) {
// pRose3D by Larry Berlin, http://aporev.deviantart.com/gallery/#/d2blmhg
int posNeg = 1;
double th = 0.0;
double sth, cth, pang, wig, wag, wag2, wag3, wag12 = 0.0, waggle = 0.0, ghostPrep; // ?? offset as variable? or preset calculated value?
double length = this.l;
double numPetals = this.k;
double constant = this.c;
double scaleZ1 = this.z1;
double scaleZ2 = this.z2;
double smooth12 = fabs(this.opt);
double smooth3 = fabs(this.opt3);
double opScale = fabs(this.optSc);
double ghost = sqr(this.transp); // Sets the testing limit for randoms to determine transparency
double wagScale = this.wagsc;
double curveScale = this.crvsc;
double frequency = this.f * M_2PI;
double wigScale = this.wigsc * 0.5;
double rad = sqrt(sqr(pAffineTP.x) + sqr(pAffineTP.y));
double curve1 = rad / length;
double curve2 = sqr(rad / length);
double curve3 = (rad - length * 0.5) / length;
double curve4 = sqr(sqr(rad / length));
if (smooth12 > 2.0)
{
smooth12 = 2.0;
}
double antiOpt1 = 2.0 - smooth12;
ghostPrep = pContext.getRandGen().random();
th = atan2(pAffineTP.y, pAffineTP.x); // returns the angle this point has with the axis
sth = sin(th);
cth = cos(th);
this._optDir = Math.copySign(1.0, this.opt);
this._petalsSign = Math.copySign(1.0, this.k);
if (this.opt3 < 0.0)
{
this._optDir = -1.0;
}
if (smooth3 > 1.0)
{
smooth3 = 1.0;
}
if (length == 0.0)
{
length = 0.000001;
}
if (fabs(numPetals) < 0.0001)
{
numPetals = 0.0001 * this._petalsSign; // need a non-zero minimum
}
if (pContext.getRandGen().random() < 0.5)
{
posNeg = -1;
}
if (this._cycle == 0.0)
{
pang = th / this._cycle + EPSILON; // point's angle location relative to which petal it belongs to
}
else
{
pang = th / this._cycle; // point's angle location relative to which petal it belongs to
}
// circumference relative offset, with high frequency values, this causes the ripples to progress along the perimeter
wig = pang * frequency * 0.5 + this.offset * this._cycle;
// Develop means to structure Z
if (this._optDir < 0.0)
{
wag = sin(curve1 * M_PI * opScale) + wagScale * 0.4 * rad + curveScale * 0.5 * (sin(curve2 * M_PI)); // length anchored
wag3 = sin(curve4 * M_PI * opScale) + wagScale * sqr(rad) * 0.4 + curveScale * 0.5 * (cos(curve3 * M_PI)); // length anchored
}
else
{
wag = sin(curve1 * M_PI * opScale) + wagScale * 0.4 * rad + curveScale * 0.5 * (cos(curve3 * M_PI)); // two curveScale methods
wag3 = sin(curve4 * M_PI * opScale) + wagScale * sqr(rad) * 0.4 + curveScale * 0.5 * (sin(curve2 * M_PI)); // length anchored
}
wag2 = sin(curve2 * M_PI * opScale) + wagScale * 0.4 * rad + curveScale * 0.5 * (cos(curve3 * M_PI)); // length anchored
/*
Animation Method
Start with option 1
smoothly change to option 2
Allow option 3 to replace either of the first two
*/
if (smooth12 <= 1.0)
{
wag12 = wag;
}
else if (smooth12 <= 2.0 && smooth12 > 1.0)
{
wag12 = wag2 * (1.0 - antiOpt1) + wag * antiOpt1;
}
else if (smooth12 > 2.0)
{
wag12 = wag2;
}
if (smooth3 == 0.0)
{
waggle = wag12;
}
else if (smooth3 > 0.0)
{
waggle = wag12 * (1.0 - smooth3) + wag3 * smooth3;
}
// Basic Polar Rose drawing method
//pVarTP.x += pAmount*0.5*(length*cos(numPetals*th+constant))*cth;
//pVarTP.y += pAmount*0.5*(length*cos(numPetals*th+constant))*sth;
if (this._optDir > 0.0)
{
ghost = 0.0;
}
if (ghostPrep < ghost) // occasion to draw reflection... but only when posNeg is negative
{
if (posNeg < 0) // ghost starts with the reflection transparent
{
pVarTP.x += pAmount * 0.5 * this.refSc * (length * cos(numPetals * th + constant)) * cth;
pVarTP.y += pAmount * 0.5 * this.refSc * (length * cos(numPetals * th + constant)) * sth;
pVarTP.z += pAmount * -0.5 * ((scaleZ2 * waggle + sqr(rad * 0.5) * sin(wig) * wigScale) + (this.dist)); // adjustable height
}
else
{
pVarTP.x += pAmount * 0.5 * (length * cos(numPetals * th + constant)) * cth;
pVarTP.y += pAmount * 0.5 * (length * cos(numPetals * th + constant)) * sth;
pVarTP.z += pAmount * 0.5 * ((scaleZ1 * waggle + sqr(rad * 0.5) * sin(wig) * wigScale) + (this.dist));
}
}
else // this is the option when optDir > 0.0
{
pVarTP.x += pAmount * 0.5 * (length * cos(numPetals * th + constant)) * cth;
pVarTP.y += pAmount * 0.5 * (length * cos(numPetals * th + constant)) * sth;
pVarTP.z += pAmount * 0.5 * ((scaleZ1 * waggle + sqr(rad * 0.5) * sin(wig) * wigScale) + (this.dist));
}
}
@Override
public String[] getParameterNames() {
return paramNames;
}
@Override
public Object[] getParameterValues() {
return new Object[] { l, k, c, z1, z2, refSc, opt, optSc, opt3, transp, dist, wagsc, crvsc, f, wigsc, offset };
}
@Override
public void setParameter(String pName, double pValue) {
if (PARAM_L.equalsIgnoreCase(pName))
l = pValue;
else if (PARAM_K.equalsIgnoreCase(pName))
k = pValue;
else if (PARAM_C.equalsIgnoreCase(pName))
c = pValue;
else if (PARAM_Z1.equalsIgnoreCase(pName))
z1 = pValue;
else if (PARAM_Z2.equalsIgnoreCase(pName))
z2 = pValue;
else if (PARAM_REF_SC.equalsIgnoreCase(pName))
refSc = pValue;
else if (PARAM_OPT.equalsIgnoreCase(pName))
opt = pValue;
else if (PARAM_OPT_SC.equalsIgnoreCase(pName))
optSc = pValue;
else if (PARAM_OPT3.equalsIgnoreCase(pName))
opt3 = pValue;
else if (PARAM_TRANSP.equalsIgnoreCase(pName))
transp = pValue;
else if (PARAM_DIST.equalsIgnoreCase(pName))
dist = pValue;
else if (PARAM_WAGSC.equalsIgnoreCase(pName))
wagsc = pValue;
else if (PARAM_CRVSC.equalsIgnoreCase(pName))
crvsc = pValue;
else if (PARAM_F.equalsIgnoreCase(pName))
f = pValue;
else if (PARAM_WIGSC.equalsIgnoreCase(pName))
wigsc = pValue;
else if (PARAM_OFFSET.equalsIgnoreCase(pName))
offset = pValue;
else
throw new IllegalArgumentException(pName);
}
@Override
public String getName() {
return "pRose3D";
}
private double _cycle, _optDir, _petalsSign;
@Override
public void init(FlameTransformationContext pContext, Layer pLayer, XForm pXForm, double pAmount) {
// divide in pre-calc, multiply in calc,
if (k == 0.0)
{
_cycle = M_2PI / (k + 0.000001); // describes the segment of a full circle used for each petal
}
else
{
_cycle = M_2PI / (k); // describes the segment of a full circle used for each petal
}
_optDir = 0.0;
_petalsSign = 0.0;
}
}