/* 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.M_2PI; import static org.jwildfire.base.mathlib.MathLib.M_PI; import static org.jwildfire.base.mathlib.MathLib.sin; import static org.jwildfire.base.mathlib.MathLib.cos; import org.jwildfire.create.tina.base.XForm; import org.jwildfire.create.tina.base.XYZPoint; import org.jwildfire.create.tina.base.Layer; /* * MobiusStrip variation * derived from Apophysis mobius (or avMobius) plugin by slobo777: * http://slobo777.deviantart.com/art/Mobius-Apo-Variation-60525279 * Initial translation into Java by chronologicaldot -- August 2015 * Extensively revised by CozyG -- September 2015 * (including adding of modify_z, twists, width_mode, radial_mode parameters) -- September 2015 * Note that the Apophysis "mobius" plugin that "mobius_strip" is derived from is * different from the other "mobius" Apophysis plugin that existing JWildfire "mobius" * variation is derived from, and the underlying math for the two variations is completely different * this "mobius_strip" variation is based on equations for the Mobius strip, * see https://en.wikipedia.org/wiki/M%C3%B6bius_strip for details * whereas the other "mobius" variation is based on equations for Mobius transformations, * see https://en.wikipedia.org/wiki/M%C3%B6bius_transformation for details */ public class MobiusStripFunc extends VariationFunc { private static final long serialVersionUID = 1L; private static int WRAP = 0; private static int EDGE = 1; private static int HIDE = 2; private static int LEAVE = 3; double radius = 1; // if width >> radius then will have self-intersections? double width = 1; // twists are really "half-twists" of 180 degrees each // when set via parameter, rounded down to nearest integer // single twist is classical Mobius strip // odd number of twists preserve "single-side" property of Mobius strip // even number of twists has two sides double twists = 1; // x range of [-rangex/2 -> range_x/2] mapped along the strip (to angle range [0 -> 2*PI]) // (strip should probably have explicit xmin and xmax range instead...) double range_x = 1; // y range of [-range_y/2 -> range_y/2] mapped across the strip (to width range [-width/2 -> width/2]) // (strip should probably have explicit ymin and ymax range instead...) double range_y = 1; double rotate_x = 0; double rotate_y = 0; double modify_z = 1; // default to full 3D int width_mode = WRAP; // default to wrapping all x coords around the strip int radial_mode = WRAP; // default to wrapping all y coords across the strip double xmin, xmax, ymin, ymax; double rotxSin, rotxCos, rotySin, rotyCos; private static final String PARAM_RADIUS = "radius"; private static final String PARAM_WIDTH = "width"; private static final String PARAM_TWISTS = "twists"; private static final String PARAM_RANGE_X = "range x"; private static final String PARAM_RANGE_Y = "range y"; private static final String PARAM_ROTATE_X = "rotate x"; private static final String PARAM_ROTATE_Y = "rotate y"; private static final String PARAM_MODIFY_Z = "modify z"; private static final String PARAM_WIDTH_MODE = "width mode"; private static final String PARAM_RADIAL_MODE = "radial mode"; private static final String[] paramNames = { PARAM_RADIUS, PARAM_WIDTH, PARAM_TWISTS, PARAM_RANGE_X, PARAM_RANGE_Y, PARAM_ROTATE_X, PARAM_ROTATE_Y, PARAM_MODIFY_Z, PARAM_WIDTH_MODE, PARAM_RADIAL_MODE }; @Override public void init(FlameTransformationContext pContext, Layer pLayer, XForm pXForm, double pAmount) { rotxSin = sin( rotate_x * M_2PI ); rotxCos = cos( rotate_x * M_2PI ); rotySin = sin( rotate_y * M_2PI ); rotyCos = cos( rotate_y * M_2PI ); xmin = -range_x/2; xmax = range_x/2; ymin = -range_y/2; ymax = range_y/2; } @Override public void transform(FlameTransformationContext pContext, XForm pXForm, XYZPoint pAffineTP, XYZPoint pVarTP, double pAmount) { // t ranges from 0 to 2*PI radians around the strip double t; // s ranges from -width/2 to width/2 across the strip double s; double Mx, My, Mz, Rx, Ry, Rz; double x = pAffineTP.x; double y = pAffineTP.y; // HIDE mode takes precedence if (radial_mode == HIDE && (x > xmax || x < xmin)) { pVarTP.doHide = true; return; } if (width_mode == HIDE && (y > ymax || y < ymin)) { pVarTP.doHide = true; return; } // then LEAVE mode if (radial_mode == LEAVE && (x > xmax || x < xmin)) { pVarTP.x += pAffineTP.x; pVarTP.y += pAffineTP.y; return; } if (width_mode == LEAVE && (y > ymax || y < ymin)) { pVarTP.x += pAffineTP.x; pVarTP.y += pAffineTP.y; return; } if (range_x == 0) { t = 0; } else { if (radial_mode == WRAP) { x = x % (range_x/2); // wrap to [-range_x/2, range_x/2] } else if (radial_mode == EDGE) { if (x > xmax) { x = xmax; } else if (x < xmin) { x = xmin; } } // also does this calc for points that pass prior checks when in HIDE or LEAVE mode t = (x * (M_PI/(range_x/2))) + M_PI; } if (range_y == 0) { s = 0; } else { if (width_mode == WRAP) { y = y % (range_y/2); // wrap to [-range_y/2, range_y/2] } else if (width_mode == EDGE) { if (y > ymax) { y = ymax; } else if (y < ymin) { y = ymin; } } // also does this calc for points that pass prior checks when in HIDE or LEAVE mode s = y * ((width/2)/(range_y/2)); } // Initial "object" co-ordinates Mx = ( radius + s * cos( twists * t / 2 ) ) * cos( t ); My = ( radius + s * cos( twists * t / 2 ) ) * sin( t ); Mz = s * sin( twists * t / 2 ); // Rotate around X axis (change y and z) // Store temporarily in R variables Rx = Mx; Ry = My * rotyCos + Mz * rotySin; Rz = Mz * rotyCos - My * rotySin; // Rotate around Y axis (change x and z) // Store back in M variables Mx = Rx * rotxCos - Rz * rotxSin; My = Ry; Mz = Rz * rotxCos + Rx * rotxSin; // Add final values in to variations totals pVarTP.x += pAmount * Mx; pVarTP.y += pAmount * My; if (modify_z != 0) { pVarTP.z += pAmount * Mz * modify_z; } else { pVarTP.z += pAmount * pVarTP.z; } } @Override public String[] getParameterNames() { return paramNames; } @Override public Object[] getParameterValues() { return new Object[] { radius, width, twists, range_x, range_y, rotate_x, rotate_y, modify_z, width_mode, radial_mode }; } @Override public void setParameter(String pName, double pValue) { if (PARAM_RADIUS.equalsIgnoreCase(pName)) radius = pValue; else if (PARAM_WIDTH.equalsIgnoreCase(pName)) width = pValue; else if (PARAM_TWISTS.equalsIgnoreCase(pName)) twists = (int)pValue; else if (PARAM_RANGE_X.equalsIgnoreCase(pName)) range_x = pValue; else if (PARAM_RANGE_Y.equalsIgnoreCase(pName)) range_y = pValue; else if (PARAM_ROTATE_X.equalsIgnoreCase(pName)) rotate_x = pValue; else if (PARAM_ROTATE_Y.equalsIgnoreCase(pName)) rotate_y = pValue; else if (PARAM_MODIFY_Z.equalsIgnoreCase(pName)) modify_z = pValue; else if (PARAM_WIDTH_MODE.equalsIgnoreCase(pName)) { width_mode = (int)pValue; if (width_mode < 0 || width_mode > 3) { width_mode = 0; } } else if (PARAM_RADIAL_MODE.equalsIgnoreCase(pName)) { radial_mode = (int)pValue; if (radial_mode < 0 || radial_mode > 3) { radial_mode = 0; } } else throw new IllegalArgumentException(pName); } @Override public String getName() { return "mobius_strip"; } @Override public int getPriority() { return 0; } }