package com.jme3.scene.plugins.blender.curves; import java.util.ArrayList; import java.util.List; import com.jme3.math.Vector3f; import com.jme3.scene.plugins.blender.file.DynamicArray; import com.jme3.scene.plugins.blender.file.Structure; /** * A class that helps to calculate the bezier curves calues. It uses doubles for performing calculations to minimize * floating point operations errors. * @author Marcin Roguski (Kaelthas) */ public class BezierCurve { private static final int IPO_CONSTANT = 0; private static final int IPO_LINEAR = 1; private static final int IPO_BEZIER = 2; public static final int X_VALUE = 0; public static final int Y_VALUE = 1; public static final int Z_VALUE = 2; /** * The type of the curve. Describes the data it modifies. * Used in ipos calculations. */ private int type; /** The dimension of the curve. */ private int dimension; /** A table of the bezier points. */ private double[][][] bezierPoints; /** Array that stores a radius for each bezier triple. */ private double[] radiuses; /** Interpolation types of the bezier triples. */ private int[] interpolations; public BezierCurve(final int type, final List<Structure> bezTriples, final int dimension) { this(type, bezTriples, dimension, false); } @SuppressWarnings("unchecked") public BezierCurve(final int type, final List<Structure> bezTriples, final int dimension, boolean fixUpAxis) { if (dimension != 2 && dimension != 3) { throw new IllegalArgumentException("The dimension of the curve should be 2 or 3!"); } this.type = type; this.dimension = dimension; // first index of the bezierPoints table has the length of triples amount // the second index points to a table od three points of a bezier triple (handle, point, handle) // the third index specifies the coordinates of the specific point in a bezier triple bezierPoints = new double[bezTriples.size()][3][dimension]; radiuses = new double[bezTriples.size()]; interpolations = new int[bezTriples.size()]; int i = 0, j, k; for (Structure bezTriple : bezTriples) { DynamicArray<Number> vec = (DynamicArray<Number>) bezTriple.getFieldValue("vec"); for (j = 0; j < 3; ++j) { for (k = 0; k < dimension; ++k) { bezierPoints[i][j][k] = vec.get(j, k).doubleValue(); } if (fixUpAxis && dimension == 3) { double temp = bezierPoints[i][j][2]; bezierPoints[i][j][2] = -bezierPoints[i][j][1]; bezierPoints[i][j][1] = temp; } } radiuses[i] = ((Number) bezTriple.getFieldValue("radius")).floatValue(); interpolations[i++] = ((Number) bezTriple.getFieldValue("ipo", IPO_BEZIER)).intValue(); } } /** * This method evaluates the data for the specified frame. The Y value is returned. * @param frame * the frame for which the value is being calculated * @param valuePart * this param specifies wheather we should return the X, Y or Z part of the result value; it should have * one of the following values: X_VALUE - the X factor of the result Y_VALUE - the Y factor of the result * Z_VALUE - the Z factor of the result * @return the value of the curve */ public double evaluate(int frame, int valuePart) { for (int i = 0; i < bezierPoints.length - 1; ++i) { if (frame >= bezierPoints[i][1][0] && frame <= bezierPoints[i + 1][1][0]) { double t = (frame - bezierPoints[i][1][0]) / (bezierPoints[i + 1][1][0] - bezierPoints[i][1][0]); switch (interpolations[i]) { case IPO_BEZIER: double oneMinusT = 1.0f - t; double oneMinusT2 = oneMinusT * oneMinusT; double t2 = t * t; return bezierPoints[i][1][valuePart] * oneMinusT2 * oneMinusT + 3.0f * bezierPoints[i][2][valuePart] * t * oneMinusT2 + 3.0f * bezierPoints[i + 1][0][valuePart] * t2 * oneMinusT + bezierPoints[i + 1][1][valuePart] * t2 * t; case IPO_LINEAR: return (1f - t) * bezierPoints[i][1][valuePart] + t * bezierPoints[i + 1][1][valuePart]; case IPO_CONSTANT: return bezierPoints[i][1][valuePart]; default: throw new IllegalStateException("Unknown interpolation type for curve: " + interpolations[i]); } } } if (frame < bezierPoints[0][1][0]) { return bezierPoints[0][1][1]; } else { // frame>bezierPoints[bezierPoints.length-1][1][0] return bezierPoints[bezierPoints.length - 1][1][1]; } } /** * This method returns the frame where last bezier triple center point of the bezier curve is located. * @return the frame number of the last defined bezier triple point for the curve */ public int getLastFrame() { return (int) bezierPoints[bezierPoints.length - 1][1][0]; } /** * This method returns the type of the bezier curve. The type describes the parameter that this curve modifies * (ie. LocationX or rotationW of the feature). * @return the type of the bezier curve */ public int getType() { return type; } /** * The method returns the radius for the required bezier triple. * * @param bezierTripleIndex * index of the bezier triple * @return radius of the required bezier triple */ public double getRadius(int bezierTripleIndex) { return radiuses[bezierTripleIndex]; } /** * This method returns a list of control points for this curve. * @return a list of control points for this curve. */ public List<Vector3f> getControlPoints() { List<Vector3f> controlPoints = new ArrayList<Vector3f>(bezierPoints.length * 3); for (int i = 0; i < bezierPoints.length; ++i) { controlPoints.add(new Vector3f((float) bezierPoints[i][0][0], (float) bezierPoints[i][0][1], (float) bezierPoints[i][0][2])); controlPoints.add(new Vector3f((float) bezierPoints[i][1][0], (float) bezierPoints[i][1][1], (float) bezierPoints[i][1][2])); controlPoints.add(new Vector3f((float) bezierPoints[i][2][0], (float) bezierPoints[i][2][1], (float) bezierPoints[i][2][2])); } return controlPoints; } @Override public String toString() { StringBuilder sb = new StringBuilder("Bezier curve: ").append(type).append('\n'); for (int i = 0; i < bezierPoints.length; ++i) { sb.append(this.toStringBezTriple(i)).append('\n'); } return sb.toString(); } /** * This method converts the bezier triple of a specified index into text. * @param tripleIndex * index of the triple * @return text representation of the triple */ private String toStringBezTriple(int tripleIndex) { if (dimension == 2) { return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ") (" + bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ") (" + bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ")]"; } else { return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ", " + bezierPoints[tripleIndex][0][2] + ") (" + bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ", " + bezierPoints[tripleIndex][1][2] + ") (" + bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ", " + bezierPoints[tripleIndex][2][2] + ")]"; } } }