/* 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_PI; import static org.jwildfire.base.mathlib.MathLib.asin; import static org.jwildfire.base.mathlib.MathLib.atan2; 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.base.mathlib.MathLib.sqrt; import org.jwildfire.base.Tools; import org.jwildfire.create.tina.base.XForm; import org.jwildfire.create.tina.base.XYZPoint; public class SynthFunc extends VariationFunc { private static final long serialVersionUID = 1L; private static final String PARAM_A = "a"; private static final String PARAM_MODE = "mode"; private static final String PARAM_POWER = "power"; private static final String PARAM_MIX = "mix"; private static final String PARAM_SMOOTH = "smooth"; private static final String PARAM_B = "b"; private static final String PARAM_B_TYPE = "b_type"; private static final String PARAM_B_SKEW = "b_skew"; private static final String PARAM_B_FRQ = "b_frq"; private static final String PARAM_B_PHS = "b_phs"; private static final String PARAM_B_LAYER = "b_layer"; private static final String PARAM_C = "c"; private static final String PARAM_C_TYPE = "c_type"; private static final String PARAM_C_SKEW = "c_skew"; private static final String PARAM_C_FRQ = "c_frq"; private static final String PARAM_C_PHS = "c_phs"; private static final String PARAM_C_LAYER = "c_layer"; private static final String PARAM_D = "d"; private static final String PARAM_D_TYPE = "d_type"; private static final String PARAM_D_SKEW = "d_skew"; private static final String PARAM_D_FRQ = "d_frq"; private static final String PARAM_D_PHS = "d_phs"; private static final String PARAM_D_LAYER = "d_layer"; private static final String PARAM_E = "e"; private static final String PARAM_E_TYPE = "e_type"; private static final String PARAM_E_SKEW = "e_skew"; private static final String PARAM_E_FRQ = "e_frq"; private static final String PARAM_E_PHS = "e_phs"; private static final String PARAM_E_LAYER = "e_layer"; private static final String PARAM_F = "f"; private static final String PARAM_F_TYPE = "f_type"; private static final String PARAM_F_SKEW = "f_skew"; private static final String PARAM_F_FRQ = "f_frq"; private static final String PARAM_F_PHS = "f_phs"; private static final String PARAM_F_LAYER = "f_layer"; private static final String[] paramNames = { PARAM_A, PARAM_MODE, PARAM_POWER, PARAM_MIX, PARAM_SMOOTH, PARAM_B, PARAM_B_TYPE, PARAM_B_SKEW, PARAM_B_FRQ, PARAM_B_PHS, PARAM_B_LAYER, PARAM_C, PARAM_C_TYPE, PARAM_C_SKEW, PARAM_C_FRQ, PARAM_C_PHS, PARAM_C_LAYER, PARAM_D, PARAM_D_TYPE, PARAM_D_SKEW, PARAM_D_FRQ, PARAM_D_PHS, PARAM_D_LAYER, PARAM_E, PARAM_E_TYPE, PARAM_E_SKEW, PARAM_E_FRQ, PARAM_E_PHS, PARAM_E_LAYER, PARAM_F, PARAM_F_TYPE, PARAM_F_SKEW, PARAM_F_FRQ, PARAM_F_PHS, PARAM_F_LAYER }; private double a = 1.0; private int mode = 3; private double power = -2.0; private double mix = 1.0; private int smooth = 0; private double b = 0.0; private int b_type = 0; private double b_skew = 0.0; private double b_frq = 1.0; private double b_phs = 0.0; private int b_layer = 0; private double c = 0.0; private int c_type = 0; private double c_skew = 0.0; private double c_frq = 1.0; private double c_phs = 0.0; private int c_layer = 0; private double d = 0.0; private int d_type = 0; private double d_skew = 0.0; private double d_frq = 1.0; private double d_phs = 0.0; private int d_layer = 0; private double e = 0.0; private int e_type = 0; private double e_skew = 0.0; private double e_frq = 1.0; private double e_phs = 0.0; private int e_layer = 0; private double f = 0.0; private int f_type = 0; private double f_skew = 0.0; private double f_frq = 1.0; private double f_phs = 0.0; private int f_layer = 0; @Override public void transform(FlameTransformationContext pContext, XForm pXForm, XYZPoint pAffineTP, XYZPoint pVarTP, double pAmount) { /* synth by slobo777, http://slobo777.deviantart.com/art/Synth-V2-128594088 */ double Vx, Vy, radius, theta; // Position vector in cartesian and polar co-ords double theta_factor; // Evaluation of synth() function for current point double s, c, mu; // Handy temp variables, s & c => sine & cosine, mu = generic temp param SinCosPair pair = new SinCosPair(); switch (mode) { case MODE_RAWCIRCLE: // Power NO, Smooth YES // Get current radius and angle Vx = pAffineTP.x; Vy = pAffineTP.y; radius = sqrt(Vx * Vx + Vy * Vy); theta = atan2(Vx, Vy); // Calculate new radius theta_factor = synth_value(theta); radius = interpolate(radius, theta_factor, smooth); s = sin(theta); c = cos(theta); // Write to running totals for transform pVarTP.x += pAmount * radius * s; pVarTP.y += pAmount * radius * c; break; case MODE_RAWY: // Power NO, Smooth YES // Use x and y values directly Vx = pAffineTP.x; Vy = pAffineTP.y; // y value will be mapped according to synth(x) value theta_factor = synth_value(Vx); // Write to running totals for transform pVarTP.x += pAmount * Vx; pVarTP.y += pAmount * interpolate(Vy, theta_factor, smooth); break; case MODE_RAWX: // Power NO, Smooth YES // Use x and y values directly Vx = pAffineTP.x; Vy = pAffineTP.y; // x value will be mapped according to synth(y) value theta_factor = synth_value(Vy); // Write to running totals for transform pVarTP.x += pAmount * interpolate(Vx, theta_factor, smooth); pVarTP.y += pAmount * Vy; break; case MODE_RAWXY: // Power NO, Smooth YES // Use x and y values directly Vx = pAffineTP.x; Vy = pAffineTP.y; // x value will be mapped according to synth(y) value theta_factor = synth_value(Vy); pVarTP.x += pAmount * interpolate(Vx, theta_factor, smooth); // y value will be mapped according to synth(x) value theta_factor = synth_value(Vx); pVarTP.y += pAmount * interpolate(Vy, theta_factor, smooth); break; case MODE_SPHERICAL: // Power YES, Smooth YES // Re-write of spherical with synth tweak Vx = pAffineTP.x; Vy = pAffineTP.y; radius = pow(Vx * Vx + Vy * Vy + EPS, (power + 1.0) / 2.0); // Get angle and angular factor theta = atan2(Vx, Vy); theta_factor = synth_value(theta); radius = interpolate(radius, theta_factor, smooth); s = sin(theta); c = cos(theta); // Write to running totals for transform pVarTP.x += pAmount * radius * s; pVarTP.y += pAmount * radius * c; break; case MODE_BUBBLE: // Power NO, Smooth YES // Re-write of bubble with synth tweak Vx = pAffineTP.x; Vy = pAffineTP.y; radius = sqrt(Vx * Vx + Vy * Vy) / ((Vx * Vx + Vy * Vy) / 4 + 1); // Get angle and angular factor theta = atan2(Vx, Vy); theta_factor = synth_value(theta); radius = interpolate(radius, theta_factor, smooth); s = sin(theta); c = cos(theta); // Write to running totals for transform pVarTP.x += pAmount * radius * s; pVarTP.y += pAmount * radius * c; break; case MODE_BLUR_LEGACY: // Power YES, Smooth YES // "old" blur style, has some problems with moire-style artefacts radius = (pContext.random() + pContext.random() + 0.002 * pContext.random()) / 2.002; theta = 2.0 * M_PI * pContext.random() - M_PI; Vx = radius * sin(theta); Vy = radius * cos(theta); radius = pow(radius * radius + EPS, power / 2.0); // Get angle and angular factor theta_factor = synth_value(theta); radius = pAmount * interpolate(radius, theta_factor, smooth); // Write back to running totals for new vector pVarTP.x += Vx * radius; pVarTP.y += Vy * radius; break; case MODE_BLUR_NEW: // Power YES, Smooth YES // Blur style, with normal smoothing function // Choose radius randomly, then adjust distribution using pow radius = 0.5 * (pContext.random() + pContext.random()); theta = 2 * M_PI * pContext.random() - M_PI; radius = pow(radius * radius + EPS, -power / 2.0); // Get angular factor defining the shape theta_factor = synth_value(theta); // Get final radius after synth applied radius = interpolate(radius, theta_factor, smooth); s = sin(theta); c = cos(theta); // Write to running totals for transform pVarTP.x += pAmount * radius * s; pVarTP.y += pAmount * radius * c; break; case MODE_BLUR_RING: // Power YES, Smooth YES // Blur style, with normal smoothing function radius = 1.0 + 0.1 * (pContext.random() + pContext.random() - 1.0) * power; theta = 2 * M_PI * pContext.random() - M_PI; // Get angular factor defining the shape theta_factor = synth_value(theta); // Get final radius after synth applied radius = interpolate(radius, theta_factor, smooth); s = sin(theta); c = cos(theta); // Write to running totals for transform pVarTP.x += pAmount * radius * s; pVarTP.y += pAmount * radius * c; break; case MODE_BLUR_RING2: // Power YES, Smooth NO // Simple, same-thickness ring // Choose radius randomly, then adjust distribution using pow theta = 2 * M_PI * pContext.random() - M_PI; radius = pow(pContext.random() + EPS, power); // Get final radius after synth applied radius = synth_value(theta) + 0.1 * radius; s = sin(theta); c = cos(theta); // Write to running totals for transform pVarTP.x += pAmount * radius * s; pVarTP.y += pAmount * radius * c; break; case MODE_SHIFTNSTRETCH: // Power YES, Smooth NO // Use (adjusted) radius to move point around circle Vx = pAffineTP.x; Vy = pAffineTP.y; radius = pow(Vx * Vx + Vy * Vy + EPS, power / 2.0); theta = atan2(Vx, Vy) - 1.0 + synth_value(radius); s = sin(theta); c = cos(theta); // Write to running totals for transform pVarTP.x += pAmount * radius * s; pVarTP.y += pAmount * radius * c; break; case MODE_SHIFTTANGENT: // Power YES, Smooth NO // Use (adjusted) radius to move point tangentially to circle Vx = pAffineTP.x; Vy = pAffineTP.y; radius = pow(Vx * Vx + Vy * Vy + EPS, power / 2.0); theta = atan2(Vx, Vy); s = sin(theta); c = cos(theta); // Adjust Vx and Vy directly mu = synth_value(radius) - 1.0; Vx += mu * c; Vy -= mu * s; // Write to running totals for transform pVarTP.x += pAmount * Vx; pVarTP.y += pAmount * Vy; break; case MODE_SHIFTTHETA: // Power YES, Smooth NO // Use (adjusted) radius to move point around circle Vx = pAffineTP.x; Vy = pAffineTP.y; radius = pow(Vx * Vx + Vy * Vy + EPS, power / 2.0); theta = atan2(Vx, Vy) - 1.0 + synth_value(radius); s = sin(theta); c = cos(theta); radius = sqrt(Vx * Vx + Vy * Vy); // Write to running totals for transform pVarTP.x += pAmount * radius * s; pVarTP.y += pAmount * radius * c; break; case MODE_BLUR_ZIGZAG: // Power YES, Smooth YES // Blur effect based on line segment // theta is used as x value // Vy is y value Vy = 1.0 + 0.1 * (pContext.random() + pContext.random() - 1.0) * power; theta = 2.0 * asin((pContext.random() - 0.5) * 2.0); // Get angular factor defining the shape theta_factor = synth_value(theta); // Get new location Vy = interpolate(Vy, theta_factor, smooth); // Write to running totals for transform pVarTP.x += pAmount * (theta / M_PI); pVarTP.y += pAmount * (Vy - 1.0); break; case MODE_SHIFTX: // Power NO, Smooth YES // Use x and y values directly Vx = pAffineTP.x; Vy = pAffineTP.y; // Write to running totals for transform pVarTP.x += pAmount * (Vx + synth_value(Vy) - 1.0); pVarTP.y += pAmount * Vy; break; case MODE_SHIFTY: // Power NO, Smooth NO // Use x and y values directly Vx = pAffineTP.x; Vy = pAffineTP.y; // Write to running totals for transform pVarTP.x += pAmount * Vx; pVarTP.y += pAmount * (Vy + synth_value(Vx) - 1.0); break; case MODE_SHIFTXY: // Power NO, Smooth NO // Use x and y values directly Vx = pAffineTP.x; Vy = pAffineTP.y; // Write to running totals for transform pVarTP.x += pAmount * (Vx + synth_value(Vy) - 1.0); pVarTP.y += pAmount * (Vy + synth_value(Vx) - 1.0); break; case MODE_SINUSOIDAL: // Power NO, Smooth NO Vx = pAffineTP.x; Vy = pAffineTP.y; // The default mix=0 is same as normal sin pVarTP.x += pAmount * (synth_value(Vx) - 1.0 + (1.0 - mix) * sin(Vx)); pVarTP.y += pAmount * (synth_value(Vy) - 1.0 + (1.0 - mix) * sin(Vy)); break; case MODE_SWIRL: // Power YES, Smooth WAVE Vx = pAffineTP.x; Vy = pAffineTP.y; radius = pow(Vx * Vx + Vy * Vy + EPS, power / 2.0); // Synth-modified sine & cosine synthsincos(radius, pair, smooth); s = pair.s; c = pair.c; pVarTP.x += pAmount * (s * Vx - c * Vy); pVarTP.y += pAmount * (c * Vx + s * Vy); break; case MODE_HYPERBOLIC: // Power YES, Smooth WAVE Vx = pAffineTP.x; Vy = pAffineTP.y; radius = pow(Vx * Vx + Vy * Vy + EPS, power / 2.0); theta = atan2(Vx, Vy); // Synth-modified sine & cosine synthsincos(theta, pair, smooth); s = pair.s; c = pair.c; pVarTP.x += pAmount * s / radius; pVarTP.y += pAmount * c * radius; break; case MODE_JULIA: // Power YES, Smooth WAVE Vx = pAffineTP.x; Vy = pAffineTP.y; radius = pow(Vx * Vx + Vy * Vy + EPS, power / 4.0); theta = atan2(Vx, Vy) / 2.0; if (pContext.random() < 0.5) theta += M_PI; // Synth-modified sine & cosine synthsincos(theta, pair, smooth); s = pair.s; c = pair.c; pVarTP.x += pAmount * radius * c; pVarTP.y += pAmount * radius * s; break; case MODE_DISC: // Power YES, Smooth WAVE Vx = pAffineTP.x; Vy = pAffineTP.y; theta = atan2(Vx, Vy) / M_PI; radius = M_PI * pow(Vx * Vx + Vy * Vy + EPS, power / 2.0); // Synth-modified sine & cosine synthsincos(radius, pair, smooth); s = pair.s; c = pair.c; pVarTP.x = pAmount * s * theta; pVarTP.y = pAmount * c * theta; break; case MODE_RINGS: // Power PARAM, Smooth WAVE Vx = pAffineTP.x; Vy = pAffineTP.y; radius = sqrt(Vx * Vx + Vy * Vy); theta = atan2(Vx, Vy); mu = power * power + EPS; radius += -2.0 * mu * (int) ((radius + mu) / (2.0 * mu)) + radius * (1.0 - mu); synthsincos(theta, pair, smooth); s = pair.s; c = pair.c; pVarTP.x += pAmount * s * radius; pVarTP.y += pAmount * c * radius; break; case MODE_CYLINDER: // Power YES, Smooth WAVE Vx = pAffineTP.x; Vy = pAffineTP.y; radius = pow(Vx * Vx + Vy * Vy + EPS, power / 2.0); // Modified sine only used here synthsincos(Vx, pair, smooth); s = pair.s; c = pair.c; pVarTP.x += pAmount * radius * s; pVarTP.y += pAmount * radius * Vy; break; case MODE_XMIRROR: // Power NO, Smooth NO Vx = pAffineTP.x; Vy = pAffineTP.y; // Modified sine only used here mu = synth_value(Vx) - 1.0; Vy = 2.0 * mu - Vy; pVarTP.x += pAmount * Vx; pVarTP.y += pAmount * Vy; break; case MODE_XYMIRROR: // Power NO, Smooth NO Vx = pAffineTP.x; Vy = pAffineTP.y; // radius sneakily being used to represent something completely different, sorry! mu = synth_value(Vx) - 1.0; radius = synth_value(Vy) - 1.0; Vy = 2.0 * mu - Vy; Vx = 2.0 * radius - Vx; pVarTP.x += pAmount * Vx; pVarTP.y += pAmount * Vy; break; case MODE_SPHERICAL2: // Power YES, Smooth YES Vx = pAffineTP.x; Vy = pAffineTP.y; radius = sqrt(Vx * Vx + Vy * Vy); // Get angle and angular factor theta = atan2(Vx, Vy); theta_factor = synth_value(theta); radius = interpolate(radius, theta_factor, smooth); radius = pow(radius, power); s = sin(theta); c = cos(theta); // Write to running totals for transform pVarTP.x += pAmount * radius * s; pVarTP.y += pAmount * radius * c; break; default: // nothing to do break; } if (pContext.isPreserveZCoordinate()) { pVarTP.z += pAmount * pAffineTP.z; } } @Override public String[] getParameterNames() { return paramNames; } @Override public Object[] getParameterValues() { return new Object[] { a, mode, power, mix, smooth, b, b_type, b_skew, b_frq, b_phs, b_layer, c, c_type, c_skew, c_frq, c_phs, c_layer, d, d_type, d_skew, d_frq, d_phs, d_layer, e, e_type, e_skew, e_frq, e_phs, e_layer, f, f_type, f_skew, f_frq, f_phs, f_layer }; } @Override public void setParameter(String pName, double pValue) { if (PARAM_A.equalsIgnoreCase(pName)) a = pValue; else if (PARAM_MODE.equalsIgnoreCase(pName)) mode = Tools.FTOI(pValue); else if (PARAM_POWER.equalsIgnoreCase(pName)) power = pValue; else if (PARAM_MIX.equalsIgnoreCase(pName)) mix = pValue; else if (PARAM_SMOOTH.equalsIgnoreCase(pName)) smooth = Tools.FTOI(pValue); else if (PARAM_B.equalsIgnoreCase(pName)) b = pValue; else if (PARAM_B_TYPE.equalsIgnoreCase(pName)) b_type = Tools.FTOI(pValue); else if (PARAM_B_SKEW.equalsIgnoreCase(pName)) b_skew = pValue; else if (PARAM_B_FRQ.equalsIgnoreCase(pName)) b_frq = pValue; else if (PARAM_B_PHS.equalsIgnoreCase(pName)) b_phs = pValue; else if (PARAM_B_LAYER.equalsIgnoreCase(pName)) b_layer = Tools.FTOI(pValue); else if (PARAM_C.equalsIgnoreCase(pName)) c = pValue; else if (PARAM_C_TYPE.equalsIgnoreCase(pName)) c_type = Tools.FTOI(pValue); else if (PARAM_C_SKEW.equalsIgnoreCase(pName)) c_skew = pValue; else if (PARAM_C_FRQ.equalsIgnoreCase(pName)) c_frq = pValue; else if (PARAM_C_PHS.equalsIgnoreCase(pName)) c_phs = pValue; else if (PARAM_C_LAYER.equalsIgnoreCase(pName)) c_layer = Tools.FTOI(pValue); else if (PARAM_D.equalsIgnoreCase(pName)) d = pValue; else if (PARAM_D_TYPE.equalsIgnoreCase(pName)) d_type = Tools.FTOI(pValue); else if (PARAM_D_SKEW.equalsIgnoreCase(pName)) d_skew = pValue; else if (PARAM_D_FRQ.equalsIgnoreCase(pName)) d_frq = pValue; else if (PARAM_D_PHS.equalsIgnoreCase(pName)) d_phs = pValue; else if (PARAM_D_LAYER.equalsIgnoreCase(pName)) d_layer = Tools.FTOI(pValue); else if (PARAM_E.equalsIgnoreCase(pName)) e = pValue; else if (PARAM_E_TYPE.equalsIgnoreCase(pName)) e_type = Tools.FTOI(pValue); else if (PARAM_E_SKEW.equalsIgnoreCase(pName)) e_skew = pValue; else if (PARAM_E_FRQ.equalsIgnoreCase(pName)) e_frq = pValue; else if (PARAM_E_PHS.equalsIgnoreCase(pName)) e_phs = pValue; else if (PARAM_E_LAYER.equalsIgnoreCase(pName)) e_layer = Tools.FTOI(pValue); else if (PARAM_F.equalsIgnoreCase(pName)) f = pValue; else if (PARAM_F_TYPE.equalsIgnoreCase(pName)) f_type = Tools.FTOI(pValue); else if (PARAM_F_SKEW.equalsIgnoreCase(pName)) f_skew = pValue; else if (PARAM_F_FRQ.equalsIgnoreCase(pName)) f_frq = pValue; else if (PARAM_F_PHS.equalsIgnoreCase(pName)) f_phs = pValue; else if (PARAM_F_LAYER.equalsIgnoreCase(pName)) f_layer = Tools.FTOI(pValue); else throw new IllegalArgumentException(pName); } @Override public String getName() { return "synth"; } // ------------------------------------------------------------- // Modes // "Lagacy" modes from v1 private static final int MODE_SPHERICAL = 0; private static final int MODE_BUBBLE = 1; private static final int MODE_BLUR_LEGACY = 2; // New modes in v2 private static final int MODE_BLUR_NEW = 3; private static final int MODE_BLUR_ZIGZAG = 4; private static final int MODE_RAWCIRCLE = 5; private static final int MODE_RAWX = 6; private static final int MODE_RAWY = 7; private static final int MODE_RAWXY = 8; private static final int MODE_SHIFTX = 9; private static final int MODE_SHIFTY = 10; private static final int MODE_SHIFTXY = 11; private static final int MODE_BLUR_RING = 12; private static final int MODE_BLUR_RING2 = 13; private static final int MODE_SHIFTNSTRETCH = 14; private static final int MODE_SHIFTTANGENT = 15; private static final int MODE_SHIFTTHETA = 16; private static final int MODE_XMIRROR = 17; private static final int MODE_XYMIRROR = 18; private static final int MODE_SPHERICAL2 = 19; // Ideas: // Rectangle // Grid // Spiral grid // Ortho? // Failed experiments (were 12-18) private static final int MODE_SINUSOIDAL = 1001; private static final int MODE_SWIRL = 1002; private static final int MODE_HYPERBOLIC = 1003; private static final int MODE_JULIA = 1004; private static final int MODE_DISC = 1005; private static final int MODE_RINGS = 1006; private static final int MODE_CYLINDER = 1007; // ------------------------------------------------------------- // Wave types private static final int WAVE_SIN = 0; private static final int WAVE_COS = 1; private static final int WAVE_SQUARE = 2; private static final int WAVE_SAW = 3; private static final int WAVE_TRIANGLE = 4; private static final int WAVE_CONCAVE = 5; private static final int WAVE_CONVEX = 6; private static final int WAVE_NGON = 7; // New wave types in v2 private static final int WAVE_INGON = 8; // ------------------------------------------------------------- // Layer types private static final int LAYER_ADD = 0; private static final int LAYER_MULT = 1; private static final int LAYER_MAX = 2; private static final int LAYER_MIN = 3; // ------------------------------------------------------------- // Interpolation types private static final int LERP_LINEAR = 0; private static final int LERP_BEZIER = 1; // ------------------------------------------------------------- // Sine/Cosine interpretation types private static final int SINCOS_MULTIPLY = 0; private static final int SINCOS_MIXIN = 1; private static final double EPS = EPSILON; // ------------------------------------------------------------- // synth_value calculates the wave height y from theta, which is an abstract // angle that could come from any other calculation - for circular modes // it will be the angle between the positive y axis and the vector from // the origin to the pont i.e. atan2(x,y) // You must call the argument "vp". private double synth_value(double theta) { double theta_factor = a; double x = 0, y, z; if (b != 0.0) { z = b_phs + theta * b_frq; y = z / (2 * M_PI); y -= floor(y); // y is in range 0 - 1. Now skew according to synth_b_skew if (b_skew != 0.0) { z = 0.5 + 0.5 * b_skew; if (y > z) { // y is 0.5 if equals z, up to 1.0 y = 0.5 + 0.5 * (y - z) / (1.0 - z + EPS); } else { // y is 0.5 if equals z, down to 0.0 y = 0.5 - 0.5 * (z - y) / (z + EPS); } } switch (b_type) { case WAVE_SIN: x = sin(y * 2 * M_PI); break; case WAVE_COS: x = cos(y * 2 * M_PI); break; case WAVE_SQUARE: x = y > 0.5 ? 1.0 : -1.0; break; case WAVE_SAW: x = 1.0 - 2.0 * y; break; case WAVE_TRIANGLE: x = y > 0.5 ? 3.0 - 4.0 * y : 2.0 * y - 1.0; break; case WAVE_CONCAVE: x = 8.0 * (y - 0.5) * (y - 0.5) - 1.0; break; case WAVE_CONVEX: x = 2.0 * sqrt(y) - 1.0; break; case WAVE_NGON: y -= 0.5; y *= (2.0 * M_PI / b_frq); x = (1.0 / (cos(y) + EPS) - 1.0); break; case WAVE_INGON: y -= 0.5; y *= (2.0 * M_PI / b_frq); z = cos(y); x = z / (1.0 + EPS - z); break; default: // nothing to do break; } switch (b_layer) { case LAYER_ADD: theta_factor += b * x; break; case LAYER_MULT: theta_factor *= (1.0 + b * x); break; case LAYER_MAX: z = a + b * x; theta_factor = (theta_factor > z ? theta_factor : z); break; case LAYER_MIN: z = a + b * x; theta_factor = (theta_factor < z ? theta_factor : z); break; default: // nothing to do break; } } if (c != 0.0) { z = c_phs + theta * c_frq; y = z / (2 * M_PI); y -= floor(y); // y is in range 0 - 1. Now skew according to synth_c_skew if (c_skew != 0.0) { z = 0.5 + 0.5 * c_skew; if (y > z) { // y is 0.5 if equals z, up to 1.0 y = 0.5 + 0.5 * (y - z) / (1.0 - z + EPS); } else { // y is 0.5 if equals z, down to 0.0 y = 0.5 - 0.5 * (z - y) / (z + EPS); } } switch (c_type) { case WAVE_SIN: x = sin(y * 2 * M_PI); break; case WAVE_COS: x = cos(y * 2 * M_PI); break; case WAVE_SQUARE: x = y > 0.5 ? 1.0 : -1.0; break; case WAVE_SAW: x = 1.0 - 2.0 * y; break; case WAVE_TRIANGLE: x = y > 0.5 ? 3.0 - 4.0 * y : 2.0 * y - 1.0; break; case WAVE_CONCAVE: x = 8.0 * (y - 0.5) * (y - 0.5) - 1.0; break; case WAVE_CONVEX: x = 2.0 * sqrt(y) - 1.0; break; case WAVE_NGON: y -= 0.5; y *= (2.0 * M_PI / c_frq); x = (1.0 / (cos(y) + EPS) - 1.0); break; case WAVE_INGON: y -= 0.5; y *= (2.0 * M_PI / c_frq); z = cos(y); x = z / (1.0 + EPS - z); break; default: // nothing to do break; } switch (c_layer) { case LAYER_ADD: theta_factor += c * x; break; case LAYER_MULT: theta_factor *= (1.0 + c * x); break; case LAYER_MAX: z = a + c * x; theta_factor = (theta_factor > z ? theta_factor : z); break; case LAYER_MIN: z = a + c * x; theta_factor = (theta_factor < z ? theta_factor : z); break; default: // nothing to do break; } } if (d != 0.0) { z = d_phs + theta * d_frq; y = z / (2 * M_PI); y -= floor(y); // y is in range 0 - 1. Now skew according to synth_d_skew if (d_skew != 0.0) { z = 0.5 + 0.5 * d_skew; if (y > z) { // y is 0.5 if equals z, up to 1.0 y = 0.5 + 0.5 * (y - z) / (1.0 - z + EPS); } else { // y is 0.5 if equals z, down to 0.0 y = 0.5 - 0.5 * (z - y) / (z + EPS); } } switch (d_type) { case WAVE_SIN: x = sin(y * 2 * M_PI); break; case WAVE_COS: x = cos(y * 2 * M_PI); break; case WAVE_SQUARE: x = y > 0.5 ? 1.0 : -1.0; break; case WAVE_SAW: x = 1.0 - 2.0 * y; break; case WAVE_TRIANGLE: x = y > 0.5 ? 3.0 - 4.0 * y : 2.0 * y - 1.0; break; case WAVE_CONCAVE: x = 8.0 * (y - 0.5) * (y - 0.5) - 1.0; break; case WAVE_CONVEX: x = 2.0 * sqrt(y) - 1.0; break; case WAVE_NGON: y -= 0.5; y *= (2.0 * M_PI / d_frq); x = (1.0 / (cos(y) + EPS) - 1.0); break; case WAVE_INGON: y -= 0.5; y *= (2.0 * M_PI / d_frq); z = cos(y); x = z / (1.0 + EPS - z); break; default: // nothing to do break; } switch (d_layer) { case LAYER_ADD: theta_factor += d * x; break; case LAYER_MULT: theta_factor *= (1.0 + d * x); break; case LAYER_MAX: z = a + d * x; theta_factor = (theta_factor > z ? theta_factor : z); break; case LAYER_MIN: z = a + d * x; theta_factor = (theta_factor < z ? theta_factor : z); break; default: // nothing to do break; } } if (e != 0.0) { z = e_phs + theta * e_frq; y = z / (2 * M_PI); y -= floor(y); // y is in range 0 - 1. Now skew according to synth_e_skew if (e_skew != 0.0) { z = 0.5 + 0.5 * e_skew; if (y > z) { // y is 0.5 if equals z, up to 1.0 y = 0.5 + 0.5 * (y - z) / (1.0 - z + EPS); } else { // y is 0.5 if equals z, down to 0.0 y = 0.5 - 0.5 * (z - y) / (z + EPS); } } switch (e_type) { case WAVE_SIN: x = sin(y * 2 * M_PI); break; case WAVE_COS: x = cos(y * 2 * M_PI); break; case WAVE_SQUARE: x = y > 0.5 ? 1.0 : -1.0; break; case WAVE_SAW: x = 1.0 - 2.0 * y; break; case WAVE_TRIANGLE: x = y > 0.5 ? 3.0 - 4.0 * y : 2.0 * y - 1.0; break; case WAVE_CONCAVE: x = 8.0 * (y - 0.5) * (y - 0.5) - 1.0; break; case WAVE_CONVEX: x = 2.0 * sqrt(y) - 1.0; break; case WAVE_NGON: y -= 0.5; y *= (2.0 * M_PI / e_frq); x = (1.0 / (cos(y) + EPS) - 1.0); break; case WAVE_INGON: y -= 0.5; y *= (2.0 * M_PI / e_frq); z = cos(y); x = z / (1.0 + EPS - z); break; default: // nothing to do break; } switch (e_layer) { case LAYER_ADD: theta_factor += e * x; break; case LAYER_MULT: theta_factor *= (1.0 + e * x); break; case LAYER_MAX: z = a + e * x; theta_factor = (theta_factor > z ? theta_factor : z); break; case LAYER_MIN: z = a + e * x; theta_factor = (theta_factor < z ? theta_factor : z); break; default: // nothing to do break; } } if (f != 0.0) { z = f_phs + theta * f_frq; y = z / (2 * M_PI); y -= floor(y); // y is in range 0 - 1. Now skew according to synth_f_skew if (f_skew != 0.0) { z = 0.5 + 0.5 * f_skew; if (y > z) { // y is 0.5 if equals z, up to 1.0 y = 0.5 + 0.5 * (y - z) / (1.0 - z + EPS); } else { // y is 0.5 if equals z, down to 0.0 y = 0.5 - 0.5 * (z - y) / (z + EPS); } } switch (f_type) { case WAVE_SIN: x = sin(y * 2 * M_PI); break; case WAVE_COS: x = cos(y * 2 * M_PI); break; case WAVE_SQUARE: x = y > 0.5 ? 1.0 : -1.0; break; case WAVE_SAW: x = 1.0 - 2.0 * y; break; case WAVE_TRIANGLE: x = y > 0.5 ? 3.0 - 4.0 * y : 2.0 * y - 1.0; break; case WAVE_CONCAVE: x = 8.0 * (y - 0.5) * (y - 0.5) - 1.0; break; case WAVE_CONVEX: x = 2.0 * sqrt(y) - 1.0; break; case WAVE_NGON: y -= 0.5; y *= (2.0 * M_PI / f_frq); x = (1.0 / (cos(y) + EPS) - 1.0); break; case WAVE_INGON: y -= 0.5; y *= (2.0 * M_PI / f_frq); z = cos(y); x = z / (1.0 + EPS - z); break; default: // nothing to do break; } switch (f_layer) { case LAYER_ADD: theta_factor += f * x; break; case LAYER_MULT: theta_factor *= (1.0 + f * x); break; case LAYER_MAX: z = a + f * x; theta_factor = (theta_factor > z ? theta_factor : z); break; case LAYER_MIN: z = a + f * x; theta_factor = (theta_factor < z ? theta_factor : z); break; default: // nothing to do break; } } // Mix is applied here, assuming 1.0 to be the "flat" line for legacy support return theta_factor * mix + (1.0 - mix); } // ------------------------------------------------------------- // Mapping function y = fn(x) based on quadratic Bezier curves for smooth type 1 // Returns close to y = x for high/low values of x, y = m when x = 1.0, and // something in-between y = m*x and y = x lines when x is close-ish to 1.0 // Function always has slope of 0.0 or greater, so no x' values "overlap" private double bezier_quad_map(double x, double m) { double a = 1.0; // a is used to control sign of result double t = 0.0; // t is the Bezier curve parameter // Simply reflect in the y axis for negative values if (m < 0.0) { m = -m; a = -1.0; } if (x < 0.0) { x = -x; a = -a; } // iM is "inverse m" used in a few places below double iM = 1e10; if (m > 1.0e-10) { iM = 1.0 / m; } double L = (2.0 - m) > 2.0 * m ? (2.0 - m) : 2.0 * m; // "Non Curved" // Covers x >= L, or always true if m == 1.0 // y = x i.e. not distorted if ((x > L) || (m == 1.0)) { return a * x; } if ((m < 1.0) && (x <= 1.0)) { // Bezier Curve #1 // Covers 0 <= $m <= 1.0, 0 <= $x <= 1.0 // Control points are (0,0), (m,m) and (1,m) t = x; // Special case when m == 0.5 if ((m - 0.5) * (m - 0.5) > 1e-10) { t = (-1.0 * m + sqrt(m * m + (1.0 - 2.0 * m) * x)) / (1.0 - 2.0 * m); } return a * (x + (m - 1.0) * t * t); } if ((1.0 < m) && (x <= 1.0)) { // Bezier Curve #2 // Covers m >= 1.0, 0 <= x <= 1.0 // Control points are (0,0), (iM,iM) and (1,m) t = x; // Special case when m == 2 if ((m - 2.0) * (m - 2.0) > 1e-10) { t = (-1.0 * iM + sqrt(iM * iM + (1.0 - 2.0 * iM) * x)) / (1 - 2 * iM); } return a * (x + (m - 1.0) * t * t); } // Deliberate divide by zero to rule out code causing a bug if (m < 1.0) { // Bezier Curve #3 // Covers 0 <= m <= 1.0, 1 <= x <= L // Control points are (1,m), (1,1) and (L,L) // (L is x value (>1) where we re-join y = x line, and is maximum( iM, 2 * m ) t = sqrt((x - 1.0) / (L - 1.0)); return a * (x + (m - 1.0) * t * t + 2 * (1.0 - m) * t + (m - 1.0)); } // Curve #4 // Covers 1.0 <= m, 1 <= x <= L // Control points are (1,m), (m,m) and (L,L) // (L is x value (>1) where we re-join y = x line, and is maximum( iM, 2 * m ) t = (1.0 - m) + sqrt((m - 1.0) * (m - 1.0) + (x - 1.0)); return a * (x + (m - 1.0) * t * t - 2.0 * (m - 1.0) * t + (m - 1.0)); } // Handle potentially many types of interpolation routine in future . . . private double interpolate(double x, double m, int lerp_type) { switch (lerp_type) { case LERP_LINEAR: return x * m; case LERP_BEZIER: return bezier_quad_map(x, m); } return x * m; } private static class SinCosPair { public double s; public double c; } private void synthsincos(double theta, SinCosPair pair, int sine_type) { pair.s = sin(theta); pair.c = cos(theta); switch (sine_type) { case SINCOS_MULTIPLY: pair.s = pair.s * synth_value(theta); pair.c = pair.c * synth_value(theta + M_PI / 2.0); break; case SINCOS_MIXIN: pair.s = (1.0 - mix) * pair.s + (synth_value(theta) - 1.0); pair.c = (1.0 - mix) * pair.c + (synth_value(theta + M_PI / 2.0) - 1.0); break; default: // nothing to do break; } return; } }