/* JWildfire - an image and animation processor written in Java Copyright (C) 1995-2015 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.M_2PI; import static org.jwildfire.base.mathlib.MathLib.M_PI; import static org.jwildfire.base.mathlib.MathLib.SMALL_EPSILON; 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.floor; import static org.jwildfire.base.mathlib.MathLib.iabs; 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 BWRandsFunc extends VariationFunc { private static final long serialVersionUID = 1L; private static final String PARAM_CELLSIZE = "cellsize"; private static final String PARAM_SPACE = "space"; private static final String PARAM_GAIN = "gain"; private static final String PARAM_INNER_TWIST = "inner_twist"; private static final String PARAM_OUTER_TWIST = "outer_twist"; private static final String PARAM_SEED = "seed"; private static final String PARAM_RROT = "rrot"; private static final String PARAM_RMIN = "rmin"; private static final String PARAM_LOONIE_CHANCE = "loonie_chance"; private static final String PARAM_PETALS_CHANCE = "petals_chance"; private static final String PARAM_MIN_PETALS = "minpetals"; private static final String PARAM_MAX_PETALS = "maxpetals"; private static final String[] paramNames = { PARAM_CELLSIZE, PARAM_SPACE, PARAM_GAIN, PARAM_INNER_TWIST, PARAM_OUTER_TWIST, PARAM_SEED, PARAM_RROT, PARAM_RMIN, PARAM_LOONIE_CHANCE, PARAM_PETALS_CHANCE, PARAM_MIN_PETALS, PARAM_MAX_PETALS }; private double cellsize = 1.0; private double space = 0.0; private double gain = 1.25; private double inner_twist = 0.0; private double outer_twist = 0.0; private int seed = 3210; private double rrot = 1.0; private double rmin = 0.25; private double loonie_chance = 0.50; private double petals_chance = 0.50; private int minpetals = 3; private int maxpetals = 20; // precalculated private double _g2; private double _r2; private double _rfactor; private int _petx, _pety; @Override public void transform(FlameTransformationContext pContext, XForm pXForm, XYZPoint pAffineTP, XYZPoint pVarTP, double pAmount) { // bwrands, by dark-beam double Vx, Vy; // V is "global" vector, double Cx, Cy; // C is "cell centre" vector double Lx, Ly; // L is "local" bubble vector double r, theta, s, c; double Vv2, flwr; Vx = pAffineTP.x; Vy = pAffineTP.y; if (fabs(cellsize) < SMALL_EPSILON) { // Linear if cells are too small pVarTP.x += pAmount * Vx; pVarTP.y += pAmount * Vy; if (pContext.isPreserveZCoordinate()) { pVarTP.z += pAmount * pAffineTP.z; } return; } int Ix = (int) floor(Vx / cellsize); int Iy = (int) floor(Vy / cellsize); Cx = (Ix + 0.5) * cellsize; Cy = (Iy + 0.5) * cellsize; // mess with Ix & Iy to get random int xx = Ix ^ 0xB641; int yy = Iy ^ 0x9D81; int xy = xx * yy + seed; xx &= 0xFFFF; yy &= 0xFFFF; xy &= 0xFFFF; // no less than 3 remixes are needed to get good randomness // use always identical mixes or you lose some data (you don't want this!) int tt = bytemix(xx, yy); yy = bytemix(yy, xx); xx = tt; tt = byteshf(xx, xy); yy = byteshf(xy, yy); xx = tt; tt = bytexim(xx, yy); yy = bytexim(yy, xx); xx = tt; double Ssz = ((double) xx) / 65535.0; // to get a range not 0-1 for circles size edit here Ssz = rmin + Ssz * (1.0 - rmin); double Aan = rrot * M_2PI * ((double) yy) / 65535.0; // to get a range not 0-2pi for rotations size edit here tt = byteprimes(xx, yy); yy = byteprimes(yy, xx); xx = tt; double LoonieChance = -((double) xx) / 65535.0 + loonie_chance; // 0.5 for a chance 50% ! double PetalsChance = 0.0; if (LoonieChance < 0) PetalsChance = LoonieChance + petals_chance; // user choice but don't upset modulus... double NPetals; if (_pety == 0) NPetals = (double) _petx; // ... when min = max else NPetals = (double) (_petx + (yy >> 3) % (1 + _pety)); // yy last byte is not enough random. But yy >> 3 is good if (LoonieChance <= PetalsChance) LoonieChance = -1.0; // the bigger probability must win. // if more random values are needed remix again :D Lx = Vx - Cx; Ly = Vy - Cy; Vv2 = Ssz * _r2; if ((Lx * Lx + Ly * Ly) > Vv2) { // Linear if outside the bubble pVarTP.x += pAmount * Vx; pVarTP.y += pAmount * Vy; if (pContext.isPreserveZCoordinate()) { pVarTP.z += pAmount * pAffineTP.z; } return; } if (LoonieChance > 0.0) { // We're in the loonie! // r = Vv2 / (Lx * Lx + Ly * Ly) - MAX(MIN(VAR(bwrands_gain),1.0),0.0); LOOKING BAD!!! r = Vv2 / (Lx * Lx + Ly * Ly) - 1.0; r = sqrt(r); Lx *= r; Ly *= r; Vv2 = 1.0; // recycled var } else if (PetalsChance > 0.0) { // We're in the petals! flwr = NPetals / M_2PI * (M_PI + atan2(Ly, Lx)); flwr = flwr - (int) (flwr); flwr = fabs(flwr - 0.5) * 2.0; r = sqrt(Lx * Lx + Ly * Ly); // We need a little chaos game to fill the empty space outside the flower. PetalsChance = pContext.random(); if (PetalsChance < .5 * (flwr + .5)) { // petals Lx *= (1.0 - r) * (flwr * 1.1); // 1.1 remove the ugly border Ly *= (1.0 - r) * (flwr * 1.1); // 1.1 remove the ugly border } else { Vv2 = sqrt(Vv2); // dist to circle border // filling the rest of the circle Lx *= (Vv2 - r * (1.0 - flwr)) / (r + SMALL_EPSILON); Ly *= (Vv2 - r * (1.0 - flwr)) / (r + SMALL_EPSILON); } Vv2 = 1.0; // recycled var } else { // We're in the bubble! // Bubble distortion on local co-ordinates: Lx *= _g2; Ly *= _g2; r = _rfactor / ((Lx * Lx + Ly * Ly) / (4.0 * Ssz) + 1.0); Lx *= r; Ly *= r; Vv2 = sqrt(Ssz); // recycled var } // Spin around the centre: r = Vv2 * (Lx * Lx + Ly * Ly) / _r2; // r should be 0.0 - 1.0 theta = inner_twist * (1.0 - r) + outer_twist * r; s = sin(theta + Aan); c = cos(theta + Aan); // Add rotated local vectors direct to centre (avoids use of temp storage) Vx = Cx + c * Lx + s * Ly; Vy = Cy - s * Lx + c * Ly; // Finally add values in pVarTP.x += pAmount * Vx; pVarTP.y += pAmount * Vy; if (pContext.isPreserveZCoordinate()) { pVarTP.z += pAmount * pAffineTP.z; } } @Override public String[] getParameterNames() { return paramNames; } @Override public Object[] getParameterValues() { return new Object[] { cellsize, space, gain, inner_twist, outer_twist, seed, rrot, rmin, loonie_chance, petals_chance, minpetals, maxpetals }; } @Override public void setParameter(String pName, double pValue) { if (PARAM_CELLSIZE.equalsIgnoreCase(pName)) cellsize = pValue; else if (PARAM_SPACE.equalsIgnoreCase(pName)) space = pValue; else if (PARAM_GAIN.equalsIgnoreCase(pName)) gain = pValue; else if (PARAM_INNER_TWIST.equalsIgnoreCase(pName)) inner_twist = pValue; else if (PARAM_OUTER_TWIST.equalsIgnoreCase(pName)) outer_twist = pValue; else if (PARAM_SEED.equalsIgnoreCase(pName)) seed = Tools.FTOI(pValue); else if (PARAM_RROT.equalsIgnoreCase(pName)) rrot = limitVal(pValue, 0, 1.0); else if (PARAM_RMIN.equalsIgnoreCase(pName)) rmin = limitVal(pValue, 0, 1.0); else if (PARAM_LOONIE_CHANCE.equalsIgnoreCase(pName)) loonie_chance = limitVal(pValue, 0, 1.0); else if (PARAM_PETALS_CHANCE.equalsIgnoreCase(pName)) petals_chance = limitVal(pValue, 0, 1.0); else if (PARAM_MIN_PETALS.equalsIgnoreCase(pName)) minpetals = limitIntVal(Tools.FTOI(pValue), 1, 1000); else if (PARAM_MAX_PETALS.equalsIgnoreCase(pName)) maxpetals = limitIntVal(Tools.FTOI(pValue), 1, 1000); else throw new IllegalArgumentException(pName); } @Override public String getName() { return "bwrands"; } @Override public void init(FlameTransformationContext pContext, Layer pLayer, XForm pXForm, double pAmount) { double radius = 0.5 * (cellsize / (1.0 + space * space)); // g2 is multiplier for radius _g2 = sqr(gain) / radius + 1.0e-6; // Start max_bubble as maximum x or y value before applying bubble double max_bubble = _g2 * radius; if (max_bubble > 2.0) { // Values greater than 2.0 "recurve" round the back of the bubble max_bubble = 1.0; } else { // Expand smaller bubble to fill the space max_bubble *= 1.0 / ((max_bubble * max_bubble) / 4.0 + 1.0); } _r2 = radius * radius; _rfactor = radius / max_bubble; _petx = Math.min(minpetals, maxpetals); _pety = iabs(maxpetals - minpetals); // if (VAR(pety) >= EPS) VAR(pety) += 1; } private int bytemix(int a, int b) { return ((a & 0x5A5A) ^ (b & 0xA5A5)); } private int bytexim(int a, int b) { // variation... return ((a & 0xAAAA) ^ (b & 0x5555)); } private int byteshf(int a, int b) { return ((a << 8) ^ (b >> 8)) & 0xFFFF; } private int byteprimes(int a, int b) { // variation... return ((a * 857 - 4) & 0xFF00 ^ (-b * 977 + 8) & 0x00FF); } }