/*
JWildfire - an image and animation processor written in Java
Copyright (C) 1995-2011 Andreas Maschke
Variation created by Nicolaus Anderson, 2013
Under the same license.
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.M_PI;
import static org.jwildfire.base.mathlib.MathLib.cos;
import static org.jwildfire.base.mathlib.MathLib.exp;
import static org.jwildfire.base.mathlib.MathLib.sin;
import static org.jwildfire.base.mathlib.MathLib.tan;
import org.jwildfire.create.tina.base.XForm;
import org.jwildfire.create.tina.base.XYZPoint;
/**
* @author Nicolaus Anderson
*/
public class Onion2Func extends VariationFunc {
private static final long serialVersionUID = 1L;
private static final String PN_MEETING_PT = "meeting_pt";
private static final String PN_CIRCLE_A = "circle_a";
private static final String PN_CIRCLE_B = "circle_b";
private static final String PN_SHIFT_X = "shift_x";
private static final String PN_SHIFT_Y = "shift_y";
private static final String PN_SHIFT_Z = "shift_z";
private static final String PN_TOP_CROP = "top_crop";
private static final String PN_STRETCH = "stretch";
private static final String[] params = {
PN_MEETING_PT,
PN_CIRCLE_A, PN_CIRCLE_B,
PN_SHIFT_X, PN_SHIFT_Y, PN_SHIFT_Z,
PN_TOP_CROP,
PN_STRETCH
};
private double meetingPt = 0.5;
private double circle_a = 1.0;
private double circle_b = 1.0;
private double shift_x = 0.0;
private double shift_y = 0.0;
private double shift_z = 0.0;
private double top_crop = 0.0;
private double stretch = 1.0;
@Override
public void transform(FlameTransformationContext pContext, XForm pXForm, XYZPoint pAffineTP, XYZPoint pVarTP, double pAmount) {
/* Description:
The transform creates a shape similar to an onion by starting with a circle
and smoothly transitioning to a (negative) exponential function. In this case,
they meet where exp(x) has a slope of 1.
To make the function spherical, the point's x and y values are treated as a
radius and become "t". The circle and exponential function are then formed
from parametric equations, where "t" is the same "t" from the original point's
x and y values.
These equations are as follows:
circle: xp=cos(t), yp=sin(t) (or r=cos(t), z=sin(t))
exponential: xp=cos(t), yp=exp(s) (or r=cos(t), z=exp(s))
where "s" represents a shifted "t" value so as to aline the exponential
function with the circle.
The slope of the circle:
dyp/dxp = cos(t)dt/-sin(t)dt = -cot(t)
Thus, cot(t) becomes the coefficient on exp(x) so as to make the slopes the
same. However, this requires a y-axis realignment of cot(t) instead of 1
in addition to a shift in the y-axis to bring exp(t) to meet with the circle.
y-axis-shift = sin(t) - cot(t)
This value of "t" - where the circle and exponential function meet - is
called "m", and it is a user-set variable.
Thus, the shifts for exp(s) are:
x-axis shift = cos(m)
y-axis shift = sin(m) - cot(m)
Final exponential equation:
xp = cos(t)
yp = cot(t)*exp( -xp + cos(m) ) + sin(m) - cot(m)
NOTE: xp is made negative instead of cot(t) so as to perform the desired
mirror in parametric space.
*/
// Initial coordinates
XYZPoint ini = new XYZPoint();
ini.x = pAffineTP.x;
ini.y = pAffineTP.y;
ini.z = pAffineTP.z;
ini.x -= shift_x;
ini.y -= shift_y;
//ini.z -= shift_z;
ini.invalidate();
// Final coordinates in parametric space
double r;
double z;
/* Convert x and y of point to parametric space,
noting they are the radius outward in real space. */
double t = ini.getPrecalcSqrt() / stretch - M_PI / 2;
/* NOTE: "t" and "meetingPt" are in radians */
if (t > meetingPt) {
// exponential curve
r = cos(t);
if (tan(meetingPt) != 0.0) {
z = exp(cos(meetingPt) - r) / tan(meetingPt)
+ sin(meetingPt)
- 1 / tan(meetingPt);
if (z > top_crop && top_crop > 0) { /* FIX ADDED. top_crop could start at -1 for cropping below middle. */
z = top_crop;
r = 0;
}
}
else {
z = top_crop;
}
}
else {
// circular curve
r = cos(t);
z = sin(t);
}
// Expand radius of onion
r *= circle_a * pAmount;
z *= circle_b * pAmount;
// Convert parametric space (2D) back to real space (3D)
/* A new coordinate equals the new factor times a unit vector in the
direction of the coordinate of interest. */
pVarTP.x += r * (ini.x / ini.getPrecalcSqrt());
pVarTP.y += r * (ini.y / ini.getPrecalcSqrt());
pVarTP.z += z;
if (pContext.isPreserveZCoordinate()) {
// (this should cause positive z values to curl inside the onion)
/* Treating z as a vector that is split into two components, one being
an x-component of parametric space which becomes a new radius for
real space, taking into account conversion through unit vectors */
pVarTP.x += r * ini.z * (ini.x / ini.getPrecalcSqrt());
pVarTP.y += r * ini.z * (ini.y / ini.getPrecalcSqrt());
/* The z-component is a normalized value from the parametric space y
(which is real space z), taking into account the amount that went into
the x component. */
pVarTP.z += ini.z * z / (r * r + z * z);
}
pVarTP.x += shift_x;
pVarTP.y += shift_y;
//pVarTP.z += shift_z;
}
@Override
public String getName() {
return "onion2";
}
@Override
public String[] getParameterNames() {
return params;
}
@Override
public Object[] getParameterValues() {
return new Object[] {
meetingPt,
circle_a,
circle_b,
shift_x,
shift_y,
shift_z,
top_crop,
stretch
};
}
@Override
public void setParameter(String pName, double pValue) {
if (PN_MEETING_PT.equalsIgnoreCase(pName)) {
meetingPt = pValue;
}
else if (PN_CIRCLE_A.equalsIgnoreCase(pName)) {
circle_a = pValue;
}
else if (PN_CIRCLE_B.equalsIgnoreCase(pName)) {
circle_b = pValue;
}
else if (PN_SHIFT_X.equalsIgnoreCase(pName)) {
shift_x = pValue;
}
else if (PN_SHIFT_Y.equalsIgnoreCase(pName)) {
shift_y = pValue;
}
else if (PN_SHIFT_Z.equalsIgnoreCase(pName)) {
shift_z = pValue;
}
else if (PN_TOP_CROP.equalsIgnoreCase(pName)) {
top_crop = pValue;
}
else if (PN_STRETCH.equalsIgnoreCase(pName)) {
if (pValue > 0.0)
stretch = pValue;
}
else
throw new IllegalArgumentException(pName);
}
}