/*
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_PI;
import static org.jwildfire.base.mathlib.MathLib.cos;
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 PrimitivesWFFunc extends VariationFunc {
private static final long serialVersionUID = 1L;
private static final String PARAM_SHAPE = "shape";
private static final String PARAM_A = "a";
private static final String PARAM_B = "b";
private static final String PARAM_C = "c";
private static final String PARAM_FILLED = "filled";
private static final String[] paramNames = { PARAM_SHAPE, PARAM_A, PARAM_B, PARAM_C, PARAM_FILLED };
private static final int SHAPE_DISC = 0;
private static final int SHAPE_SPHERE = 1;
private static final int SHAPE_CUBE = 2;
private static final int SHAPE_SQUARE = 3;
private static final int SHAPE_TRIANGLE = 4;
private static final int SHAPE_PYRAMID = 5;
private static final int SHAPE_TORUS = 6;
private int shape = SHAPE_SPHERE;
private double a = 1.0;
private double b = 1.0;
private double c = 1.0;
private int filled = 0;
@Override
public void transform(FlameTransformationContext pContext, XForm pXForm, XYZPoint pAffineTP, XYZPoint pVarTP, double pAmount) {
switch (shape) {
case SHAPE_DISC:
createDisc(pContext, pXForm, pAffineTP, pVarTP, pAmount);
break;
case SHAPE_CUBE:
createCube(pContext, pXForm, pAffineTP, pVarTP, pAmount);
break;
case SHAPE_SQUARE:
createSquare(pContext, pXForm, pAffineTP, pVarTP, pAmount);
break;
case SHAPE_TRIANGLE:
createTriangle(pContext, pXForm, pAffineTP, pVarTP, pAmount);
break;
case SHAPE_PYRAMID:
createPyramid(pContext, pXForm, pAffineTP, pVarTP, pAmount);
break;
case SHAPE_TORUS:
createTorus(pContext, pXForm, pAffineTP, pVarTP, pAmount);
break;
case SHAPE_SPHERE:
default:
createSphere(pContext, pXForm, pAffineTP, pVarTP, pAmount);
}
}
private void createSquare(FlameTransformationContext pContext, XForm pXForm, XYZPoint pAffineTP, XYZPoint pVarTP, double pAmount) {
double size = pAmount;
double dx = 0.0, dy = 0.0;
if (filled == 1 && pContext.random() < 0.5) {
dx = size * (pContext.random() - 0.5);
dy = size * (pContext.random() - 0.5);
}
else {
switch (pContext.random(2)) {
case 0:
boolean left = pContext.random() < 0.5;
if (filled == 0 || pContext.random() < 0.05) {
dx = size * (left ? -0.5 : 0.5);
}
else {
dx = size * (pContext.random() - 0.5);
}
dy = size * (pContext.random() - 0.5);
break;
case 1:
boolean top = pContext.random() < 0.5;
dx = size * (pContext.random() - 0.5);
if (filled == 0 || pContext.random() < 0.05) {
dy = size * (top ? -0.5 : 0.5);
}
else {
dy = size * (pContext.random() - 0.5);
}
break;
default: // nothing to do
break;
}
}
pVarTP.x += dx;
pVarTP.y += dy;
if (pContext.isPreserveZCoordinate()) {
pVarTP.z += pAmount * pAffineTP.z;
}
}
private boolean insideUnitTriangle(double pX, double pY) {
// Compute vectors
// v0 = C - A
// v1 = B - A
// v2 = P - A
double v0x = 0;
double v0y = 1;
double v1x = 1;
double v1y = 0;
double v2x = pX;
double v2y = pY;
// Compute dot products
double dot00 = v0x * v0x + v0y * v0y;
double dot01 = v0x * v1x + v0y * v1y;
double dot02 = v0x * v2x + v0y * v2y;
double dot11 = v1x * v1x + v1y * v1y;
double dot12 = v1x * v2x + v1y * v2y;
// Compute barycentric coordinates
double invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
double u = (dot11 * dot02 - dot01 * dot12) * invDenom;
double v = (dot00 * dot12 - dot01 * dot02) * invDenom;
// Check if point is in triangle
return (u >= 0) && (v >= 0) && (u + v < 1);
}
private void createTriangle(FlameTransformationContext pContext, XForm pXForm, XYZPoint pAffineTP, XYZPoint pVarTP, double pAmount) {
double dx = 0.0, dy = 0.0;
while (true) {
dx = pContext.random();
dy = pContext.random();
if (insideUnitTriangle(dx, dy)) {
break;
}
}
pVarTP.x += pContext.random() < 0.5 ? dx : -dx;
pVarTP.y += -dy;
if (pContext.isPreserveZCoordinate()) {
pVarTP.z += pAmount * pAffineTP.z;
}
}
private void createCube(FlameTransformationContext pContext, XForm pXForm, XYZPoint pAffineTP, XYZPoint pVarTP, double pAmount) {
double size = pAmount;
double dx = 0.0, dy = 0.0, dz = 0.0;
switch (pContext.random(3)) {
case 0:
boolean left = pContext.random() < 0.5;
if (filled == 0 || pContext.random() < 0.05) {
dx = size * (left ? -0.5 : 0.5);
}
else {
dx = size * (pContext.random() - 0.5);
}
dy = size * (pContext.random() - 0.5);
dz = size * (pContext.random() - 0.5);
break;
case 1:
boolean top = pContext.random() < 0.5;
dx = size * (pContext.random() - 0.5);
if (filled == 0 || pContext.random() < 0.05) {
dy = size * (top ? -0.5 : 0.5);
}
else {
dy = size * (pContext.random() - 0.5);
}
dz = size * (pContext.random() - 0.5);
break;
case 2:
boolean front = pContext.random() < 0.5;
dx = size * (pContext.random() - 0.5);
dy = size * (pContext.random() - 0.5);
if (filled == 0 || pContext.random() < 0.05) {
dz = size * (front ? -0.5 : 0.5);
}
else {
dz = size * (pContext.random() - 0.5);
}
break;
default: // nothing to do
break;
}
pVarTP.x += dx;
pVarTP.y += dy;
pVarTP.z += dz;
}
private static final double P_SINA = sqrt(3.0) / 2.0;
private static final double P_COSA = 0.5;
private void createPyramid(FlameTransformationContext pContext, XForm pXForm, XYZPoint pAffineTP, XYZPoint pVarTP, double pAmount) {
double size = pAmount;
double dx = 0.0, dy = 0.0, dz = 0.0;
int side = pContext.random(5);
switch (side) {
case 0:
dx = size * (pContext.random() - 0.5);
dy = size * 0.5;
dz = size * (pContext.random() - 0.5);
break;
default:
double tx = 0.0,
ty = 0.0;
while (true) {
tx = pContext.random();
ty = pContext.random();
if (insideUnitTriangle(tx, ty)) {
break;
}
}
tx *= size;
ty *= size;
switch (side) {
case 1:
dx = -size * 0.5 + P_COSA * ty;
dy = size * 0.5 - P_SINA * ty;
dz = (pContext.random() < 0.5 ? tx : -tx) * 0.5;
break;
case 2:
dx = size * 0.5 - P_COSA * ty;
dy = 0.5 * size - P_SINA * ty;
dz = (pContext.random() < 0.5 ? tx : -tx) * 0.5;
break;
case 3:
dx = (pContext.random() < 0.5 ? tx : -tx) * 0.5;
dy = 0.5 * size - P_SINA * ty;
dz = -size * 0.5 + P_COSA * ty;
break;
case 4:
dx = (pContext.random() < 0.5 ? tx : -tx) * 0.5;
dy = 0.5 * size - P_SINA * ty;
dz = size * 0.5 - P_COSA * ty;
break;
default: // nothing to do
break;
}
}
pVarTP.x += dx;
pVarTP.y += dy;
pVarTP.z += dz;
}
private void createSphere(FlameTransformationContext pContext, XForm pXForm, XYZPoint pAffineTP, XYZPoint pVarTP, double pAmount) {
double radius = filled == 0 ? pAmount : pContext.random() < 0.05 ? pAmount : pAmount * pContext.random();
double a = pContext.random() * 2 * M_PI;
double sina = sin(a);
double cosa = cos(a);
double b = pContext.random() * 2 * M_PI;
double sinb = sin(b);
double cosb = cos(b);
pVarTP.x += radius * sina * cosb;
pVarTP.y += radius * sina * sinb;
pVarTP.z += radius * cosa;
}
private void createDisc(FlameTransformationContext pContext, XForm pXForm, XYZPoint pAffineTP, XYZPoint pVarTP, double pAmount) {
double radius = filled == 0 ? pAmount : pContext.random() < 0.05 ? pAmount : pAmount * pContext.random();
double a = pContext.random() * 2 * M_PI;
double sina = sin(a);
double cosa = cos(a);
pVarTP.x += radius * cosa;
pVarTP.y += radius * sina;
if (pContext.isPreserveZCoordinate()) {
pVarTP.z += pAmount * pAffineTP.z;
}
}
private void createTorus(FlameTransformationContext pContext, XForm pXForm, XYZPoint pAffineTP, XYZPoint pVarTP, double pAmount) {
double radius = filled == 0 ? a : pContext.random() < 0.05 ? a : a * pContext.random();
double alpha = pContext.random() * 2 * M_PI;
double x = radius * cos(alpha);
double y = radius * sin(alpha);
double beta = pContext.random() * 2 * M_PI;
pVarTP.y += x * pAmount;
pVarTP.x += (y + b + b) * sin(beta) * pAmount;
pVarTP.z += (y + b + b) * cos(beta) * pAmount;
}
@Override
public String[] getParameterNames() {
return paramNames;
}
@Override
public Object[] getParameterValues() {
return new Object[] { shape, a, b, c, filled };
}
@Override
public void setParameter(String pName, double pValue) {
if (PARAM_SHAPE.equalsIgnoreCase(pName))
shape = Tools.FTOI(pValue);
else if (PARAM_A.equalsIgnoreCase(pName))
a = pValue;
else if (PARAM_B.equalsIgnoreCase(pName))
b = pValue;
else if (PARAM_C.equalsIgnoreCase(pName))
c = pValue;
else if (PARAM_FILLED.equalsIgnoreCase(pName))
filled = limitIntVal(Tools.FTOI(pValue), 0, 1);
else
throw new IllegalArgumentException(pName);
}
@Override
public String getName() {
return "primitives_wf";
}
}