/*
JWildfire - an image and animation processor written in Java
Copyright (C) 1995-2012 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.animate;
import static org.jwildfire.base.mathlib.MathLib.EPSILON;
import static org.jwildfire.base.mathlib.MathLib.fabs;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jwildfire.base.Prefs;
import org.jwildfire.base.Tools;
import org.jwildfire.base.mathlib.MathLib;
import org.jwildfire.create.tina.base.Flame;
import org.jwildfire.create.tina.base.Layer;
import org.jwildfire.create.tina.base.XForm;
import org.jwildfire.create.tina.base.motion.MotionCurve;
import org.jwildfire.create.tina.palette.RGBPalette;
import org.jwildfire.create.tina.render.FlameRenderer;
import org.jwildfire.create.tina.render.RenderInfo;
import org.jwildfire.create.tina.render.RenderMode;
import org.jwildfire.create.tina.render.RenderedFlame;
import org.jwildfire.create.tina.variation.Variation;
import org.jwildfire.create.tina.variation.VariationFunc;
import org.jwildfire.envelope.Envelope;
import org.jwildfire.envelope.Envelope.Interpolation;
import org.jwildfire.image.SimpleImage;
public class AnimationService {
public static SimpleImage renderFrame(Flame flame, int pWidth, int pHeight, Prefs pPrefs) throws Exception {
RenderInfo info = new RenderInfo(pWidth, pHeight, RenderMode.PRODUCTION);
double wScl = (double) info.getImageWidth() / (double) flame.getWidth();
double hScl = (double) info.getImageHeight() / (double) flame.getHeight();
flame.setPixelsPerUnit((wScl + hScl) * 0.5 * flame.getPixelsPerUnit());
flame.setWidth(info.getImageWidth());
flame.setHeight(info.getImageHeight());
FlameRenderer renderer = new FlameRenderer(flame, pPrefs, flame.isBGTransparency(), false);
RenderedFlame res = renderer.renderFlame(info);
return res.getImage();
}
public static Flame createFrameFlame(int pFrame, int pFrameCount, double pFPS, Flame pFlame, GlobalScript pGlobalScripts[], XFormScript[] pXFormScripts, int pMotionBlurLength, double pMotionBlurTimeStep, int pWidth, int pHeight, Prefs pPrefs) {
Flame res = pFlame.makeCopy();
for (GlobalScript script : pGlobalScripts) {
AnimationService.addMotionCurve(res, script, pFrame, pFrameCount, pFPS);
}
for (XFormScript script : pXFormScripts) {
AnimationService.addMotionCurve(res, script, pFrame, pFrameCount, pFPS);
}
res.setFrame(pFrame);
res.setMotionBlurLength(pMotionBlurLength);
res.setMotionBlurTimeStep(pMotionBlurTimeStep);
return res;
}
public static <T> double getPropertyValue(T pSource, String pName) {
@SuppressWarnings("unchecked")
Class<T> cls = (Class<T>) pSource.getClass();
Field field;
try {
field = cls.getDeclaredField(pName);
field.setAccessible(true);
Class<?> fieldCls = field.getType();
if (fieldCls == double.class || fieldCls == Double.class) {
Double res = field.getDouble(pSource);
return res != null ? res.doubleValue() : 0.0;
}
else if (fieldCls == int.class || fieldCls == Integer.class) {
Integer res = field.getInt(pSource);
return res != null ? Double.valueOf(res.intValue()) : 0.0;
}
else if (fieldCls == boolean.class || fieldCls == Boolean.class) {
Boolean res = field.getBoolean(pSource);
return res ? 1.0 : 0.0;
}
else {
throw new IllegalStateException(fieldCls.getName());
}
}
catch (Throwable ex) {
throw new RuntimeException(ex);
}
}
public static <T> void setPropertyValue(T pDest, String pName, double pValue) {
@SuppressWarnings("unchecked")
Class<T> cls = (Class<T>) pDest.getClass();
Field field;
try {
field = cls.getDeclaredField(pName);
field.setAccessible(true);
Class<?> fieldCls = field.getType();
if (fieldCls == double.class || fieldCls == Double.class) {
field.setDouble(pDest, pValue);
}
else if (fieldCls == int.class || fieldCls == Integer.class) {
field.setInt(pDest, (int) MathLib.round(pValue));
}
else if (fieldCls == boolean.class || fieldCls == Boolean.class) {
field.setBoolean(pDest, (int) MathLib.round(pValue) != 0);
}
else {
throw new IllegalStateException(fieldCls.getName());
}
}
catch (Throwable ex) {
throw new RuntimeException(ex);
}
}
public static <T> MotionCurve getPropertyCurve(T pSource, String pName) {
@SuppressWarnings("unchecked")
Class<T> cls = (Class<T>) pSource.getClass();
Field field;
try {
field = cls.getDeclaredField(pName + Tools.CURVE_POSTFIX);
field.setAccessible(true);
Class<?> fieldCls = field.getType();
if (fieldCls == MotionCurve.class) {
return (MotionCurve) field.get(pSource);
}
else {
throw new IllegalStateException(fieldCls.getName());
}
}
catch (Throwable ex) {
throw new RuntimeException(ex);
}
}
public static Flame evalMotionCurves(Flame pFlame, double pFrame) {
try {
_evalMotionCurves(pFlame, pFrame);
return pFlame;
}
catch (Throwable ex) {
throw new RuntimeException(ex);
}
}
private static void _evalMotionCurves(Object pObject, double pFrame) throws IllegalAccessException {
Class<?> cls = pObject.getClass();
for (Field field : cls.getDeclaredFields()) {
field.setAccessible(true);
if (field.getType() == MotionCurve.class && field.getName().endsWith(Tools.CURVE_POSTFIX)) {
MotionCurve curve = (MotionCurve) field.get(pObject);
if (curve.isEnabled()) {
double value = evalCurve(pFrame, curve);
String propName = field.getName().substring(0, field.getName().length() - Tools.CURVE_POSTFIX.length());
curve.getChangeHandler().processValueChange(pObject, propName, value);
if (pObject instanceof RGBPalette) {
curve.getChangeHandler().processValueChange(pObject, "modified", 1.0);
}
//setPropertyValue(pObject, propName, value);
// System.out.println(propName + " " + value);
}
}
else if (field.getType().isAssignableFrom(ArrayList.class)) {
List<?> childs = (List<?>) field.get(pObject);
for (Object child : childs) {
_evalMotionCurves(child, pFrame);
}
}
else if (field.getType().isAssignableFrom(RGBPalette.class)) {
RGBPalette gradient = (RGBPalette) field.get(pObject);
_evalMotionCurves(gradient, pFrame);
}
}
if (pObject instanceof Variation) {
Variation var = (Variation) pObject;
VariationFunc func = var.getFunc();
for (String name : func.getParameterNames()) {
MotionCurve curve = var.getMotionCurve(name);
if (curve != null && curve.isEnabled()) {
double value = evalCurve(pFrame, curve);
try {
func.setParameter(name, value);
}
catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
public static Flame disableMotionCurves(Flame pFlame) {
try {
_disableMotionCurves(pFlame);
return pFlame;
}
catch (Throwable ex) {
throw new RuntimeException(ex);
}
}
private static void _disableMotionCurves(Object pObject) throws IllegalAccessException {
Class<?> cls = pObject.getClass();
for (Field field : cls.getDeclaredFields()) {
field.setAccessible(true);
if (field.getType() == MotionCurve.class && field.getName().endsWith(Tools.CURVE_POSTFIX)) {
MotionCurve curve = (MotionCurve) field.get(pObject);
if (curve.isEnabled()) {
curve.setEnabled(false);
}
}
else if (field.getType().isAssignableFrom(ArrayList.class)) {
List<?> childs = (List<?>) field.get(pObject);
for (Object child : childs) {
_disableMotionCurves(child);
}
}
else if (field.getType().isAssignableFrom(RGBPalette.class)) {
RGBPalette gradient = (RGBPalette) field.get(pObject);
_disableMotionCurves(gradient);
}
}
if (pObject instanceof Variation) {
Variation var = (Variation) pObject;
VariationFunc func = var.getFunc();
for (String name : func.getParameterNames()) {
MotionCurve curve = var.getMotionCurve(name);
if (curve != null && curve.isEnabled()) {
curve.setEnabled(false);
}
}
}
}
private static double evalCurve(double pFrame, MotionCurve curve) {
MotionCurve currCurve = curve;
double value = 0.0;
while (currCurve != null) {
Envelope envelope = currCurve.toEnvelope();
value += envelope.evaluate(pFrame);
currCurve = currCurve.getParent();
}
return value;
}
public static class MotionCurveAttribute {
private final String name;
private final MotionCurve motionCurve;
public MotionCurveAttribute(String pName, MotionCurve pMotionCurve) {
name = pName;
motionCurve = pMotionCurve;
}
public String getName() {
return name;
}
public MotionCurve getMotionCurve() {
return motionCurve;
}
}
private static Map<Class<?>, List<Field>> motionCurvePropertyCache = new HashMap<Class<?>, List<Field>>();
private static List<Field> getMotionCurveProperties(Object pObject) {
Class<?> cls = pObject.getClass();
List<Field> res = motionCurvePropertyCache.get(cls);
if (res == null) {
res = new ArrayList<Field>();
motionCurvePropertyCache.put(cls, res);
for (Field field : cls.getDeclaredFields()) {
field.setAccessible(true);
if (field.getType() == MotionCurve.class && field.getName().endsWith(Tools.CURVE_POSTFIX)) {
res.add(field);
}
}
}
return res;
}
public static List<MotionCurveAttribute> getAllMotionCurves(Object pObject) {
try {
List<MotionCurveAttribute> res = new ArrayList<MotionCurveAttribute>();
for (Field field : getMotionCurveProperties(pObject)) {
MotionCurve curve = (MotionCurve) field.get(pObject);
res.add(new MotionCurveAttribute(field.getName(), curve));
}
return res;
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
public static enum EnvelopePointsShape {
RAMP, SINE
}
public static class EnvelopePoints {
private int envX[];
private double envY[];
public EnvelopePoints(SimpleScript pScript, int pFrameCount, double pFPS, EnvelopePointsShape pDefaultShape, double pAmplitude) {
if (pScript.getAmplitudeCurve().isEnabled()) {
int[] srcX = pScript.getAmplitudeCurve().getX();
double[] srcY = pScript.getAmplitudeCurve().getY();
if (srcX.length > 1) {
envX = new int[srcX.length + 2];
envY = new double[srcY.length + 2];
for (int i = 0; i < srcX.length; i++) {
envX[i + 1] = srcX[i];
envY[i + 1] = (double) pFrameCount / (DFLT_DURATION * pFPS) * srcY[i];
}
final int DX = 50;
double DY;
DY = (envY[envY.length - 2] - envY[envY.length - 3]) / (double) (envX[envX.length - 2] - envX[envX.length - 3]) * DX;
if (Double.isInfinite(DY) || Double.isNaN(DY))
DY = 0.0;
envX[envX.length - 1] = envX[envX.length - 2] + DX;
envY[envY.length - 1] = envY[envY.length - 2] + DY;
DY = (envY[2] - envY[1]) / (double) (envX[2] - envX[1]) * DX;
if (Double.isInfinite(DY) || Double.isNaN(DY))
DY = 0.0;
envX[0] = envX[1] - DX;
envY[0] = envY[1] - DY;
}
else {
envX = new int[1];
envY = new double[1];
envX[0] = -10;
envY[0] = (double) pFrameCount / (DFLT_DURATION * pFPS) * srcY[0];
}
}
else {
if (fabs(pScript.getAmplitude()) < EPSILON || fabs(pFPS) < EPSILON) {
envX = new int[1];
envY = new double[1];
envX[0] = 0;
envY[0] = 0.0;
}
else {
switch (pDefaultShape) {
case RAMP: {
double amp = pAmplitude * (double) pFrameCount / (DFLT_DURATION * pFPS) * pScript.getAmplitude();
envX = new int[2];
envY = new double[2];
final int DX = 10;
final double DY = amp / (double) pFrameCount * (double) DX;
envX[0] = -DX;
envY[0] = -DY;
envX[1] = pFrameCount + DX;
envY[1] = amp + DY;
}
break;
case SINE: {
int phase = Tools.FTOI(DFLT_DURATION * pFPS / 2.0);
int start = -phase;
int cnt = 1;
while (start <= pFrameCount + phase) {
start += phase;
cnt++;
}
envX = new int[cnt];
envY = new double[cnt];
start = -phase;
int state = 1;
for (int i = 0; i < cnt; i++) {
envX[i] = start;
switch (state++ % 4) {
case 1:
envY[i] = -pScript.getAmplitude();
break;
case 3:
envY[i] = pScript.getAmplitude();
break;
default:
envY[i] = 0.0;
break;
}
start += phase;
}
}
break;
default:
throw new IllegalArgumentException(pDefaultShape.toString());
}
}
}
}
public int[] getEnvX() {
return envX;
}
public double[] getEnvY() {
return envY;
}
}
public static void addMotionCurve(Flame pFlame, GlobalScript pScript, int pFrame, int pFrameCount, double pFPS) {
if (pScript != null && pScript.getScriptType() != null && !GlobalScriptType.NONE.equals(pScript.getScriptType()) && fabs(pScript.getAmplitude()) > EPSILON) {
EnvelopePoints points;
MotionCurve curve = null;
switch (pScript.getScriptType()) {
case ROTATE_PITCH:
curve = pFlame.getCamPitchCurve();
points = new EnvelopePoints(pScript, pFrameCount, pFPS, EnvelopePointsShape.RAMP, 360.0);
break;
case ROTATE_ROLL:
curve = pFlame.getCamRollCurve();
points = new EnvelopePoints(pScript, pFrameCount, pFPS, EnvelopePointsShape.RAMP, 360.0);
break;
case ROTATE_YAW:
curve = pFlame.getCamYawCurve();
points = new EnvelopePoints(pScript, pFrameCount, pFPS, EnvelopePointsShape.RAMP, 360.0);
break;
case MOVE_CAM_X:
curve = pFlame.getCamPosXCurve();
points = new EnvelopePoints(pScript, pFrameCount, pFPS, EnvelopePointsShape.SINE, 1.0);
break;
case MOVE_CAM_Y:
curve = pFlame.getCamPosYCurve();
points = new EnvelopePoints(pScript, pFrameCount, pFPS, EnvelopePointsShape.SINE, 1.0);
break;
case MOVE_CAM_Z:
curve = pFlame.getCamPosZCurve();
points = new EnvelopePoints(pScript, pFrameCount, pFPS, EnvelopePointsShape.SINE, 1.0);
break;
default:
throw new IllegalArgumentException(pScript.toString());
}
addEnvelope(pFrameCount, curve, points.getEnvX(), points.getEnvY());
}
}
private static void addEnvelope(int pFrameCount, MotionCurve curve, int[] envX, double[] envY) {
Envelope envelope = new Envelope(envX, envY);
envelope.setViewXMin(-10);
envelope.setViewXMax(10 + pFrameCount);
envelope.setViewYMin(-10000.0);
envelope.setViewYMax(10000.0);
envelope.setInterpolation(Interpolation.SPLINE);
envelope.setSelectedIdx(0);
if (!curve.isEnabled()) {
curve.assignFromEnvelope(envelope);
curve.setEnabled(true);
}
else {
MotionCurve newParentCurve = new MotionCurve();
newParentCurve.setEnabled(true);
newParentCurve.assignFromEnvelope(envelope);
while (curve.getParent() != null) {
curve = curve.getParent();
}
curve.setParent(newParentCurve);
}
}
public static double DFLT_DURATION = 6.0;
public static void addMotionCurve(Flame pFlame, XFormScript pScript, int pFrame, int pFrameCount, double pFPS) {
if (pScript != null && pScript.getScriptType() != null && !XFormScriptType.NONE.equals(pScript.getScriptType()) && fabs(pScript.getAmplitude()) > EPSILON) {
for (Layer layer : pFlame.getLayers()) {
switch (pScript.getScriptType()) {
case ROTATE_FULL: {
int idx = 0;
for (XForm xForm : layer.getXForms()) {
MotionCurve curve = xForm.getRotateCurve();
EnvelopePoints points = new EnvelopePoints(pScript, pFrameCount, pFPS, EnvelopePointsShape.RAMP, idx++ % 2 == 0 ? 360.0 : -360.0);
addEnvelope(pFrameCount, curve, points.getEnvX(), points.getEnvY());
}
}
break;
case ROTATE_POST_FULL: {
int idx = 0;
for (XForm xForm : layer.getXForms()) {
MotionCurve curve = xForm.getPostRotateCurve();
EnvelopePoints points = new EnvelopePoints(pScript, pFrameCount, pFPS, EnvelopePointsShape.RAMP, idx++ % 2 == 0 ? 360.0 : -360.0);
addEnvelope(pFrameCount, curve, points.getEnvX(), points.getEnvY());
}
}
break;
case ROTATE_FIRST_XFORM:
case ROTATE_2ND_XFORM:
case ROTATE_3RD_XFORM:
case ROTATE_4TH_XFORM:
case ROTATE_5TH_XFORM:
case ROTATE_LAST_XFORM:
case ROTATE_FINAL_XFORM: {
XForm xForm = null;
xForm = getXForm(pScript.getScriptType(), layer, xForm);
if (xForm != null) {
EnvelopePoints points = new EnvelopePoints(pScript, pFrameCount, pFPS, EnvelopePointsShape.RAMP, 360.0);
MotionCurve curve = xForm.getRotateCurve();
addEnvelope(pFrameCount, curve, points.getEnvX(), points.getEnvY());
}
}
break;
case ROTATE_POST_FIRST_XFORM:
case ROTATE_POST_2ND_XFORM:
case ROTATE_POST_3RD_XFORM:
case ROTATE_POST_4TH_XFORM:
case ROTATE_POST_5TH_XFORM:
case ROTATE_POST_LAST_XFORM:
case ROTATE_POST_FINAL_XFORM: {
XForm xForm = null;
xForm = getXForm(pScript.getScriptType(), layer, xForm);
if (xForm != null) {
EnvelopePoints points = new EnvelopePoints(pScript, pFrameCount, pFPS, EnvelopePointsShape.RAMP, 360.0);
MotionCurve curve = xForm.getPostRotateCurve();
addEnvelope(pFrameCount, curve, points.getEnvX(), points.getEnvY());
}
}
break;
default:
throw new IllegalArgumentException(pScript.toString());
}
break;
}
}
}
private static XForm getXForm(XFormScriptType pScript, Layer layer, XForm xForm) {
switch (pScript) {
case ROTATE_FIRST_XFORM:
case ROTATE_POST_FIRST_XFORM:
return layer.getXForms().size() > 0 ? layer.getXForms().get(0) : null;
case ROTATE_2ND_XFORM:
case ROTATE_POST_2ND_XFORM:
return layer.getXForms().size() > 1 ? layer.getXForms().get(1) : null;
case ROTATE_3RD_XFORM:
case ROTATE_POST_3RD_XFORM:
return layer.getXForms().size() > 2 ? layer.getXForms().get(2) : null;
case ROTATE_4TH_XFORM:
case ROTATE_POST_4TH_XFORM:
return layer.getXForms().size() > 3 ? layer.getXForms().get(3) : null;
case ROTATE_5TH_XFORM:
case ROTATE_POST_5TH_XFORM:
return layer.getXForms().size() > 4 ? layer.getXForms().get(4) : null;
case ROTATE_LAST_XFORM:
case ROTATE_POST_LAST_XFORM:
return layer.getXForms().size() > 0 ? layer.getXForms().get(layer.getXForms().size() - 1) : null;
case ROTATE_FINAL_XFORM:
case ROTATE_POST_FINAL_XFORM:
return layer.getFinalXForms().size() > 0 ? layer.getFinalXForms().get(0) : null;
default:
throw new IllegalArgumentException(pScript.toString());
}
}
}