blob: b28a797209eafc46993210cca48de1d0f55cade3 [file] [log] [blame]
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.jme3.math;
import com.jme3.export.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
*
* @author Nehon
*/
public class Spline implements Savable {
public enum SplineType {
Linear,
CatmullRom,
Bezier,
Nurb
}
private List<Vector3f> controlPoints = new ArrayList<Vector3f>();
private List<Float> knots; //knots of NURBS spline
private float[] weights; //weights of NURBS spline
private int basisFunctionDegree; //degree of NURBS spline basis function (computed automatically)
private boolean cycle;
private List<Float> segmentsLength;
private float totalLength;
private List<Vector3f> CRcontrolPoints;
private float curveTension = 0.5f;
private SplineType type = SplineType.CatmullRom;
public Spline() {
}
/**
* Create a spline
* @param splineType the type of the spline @see {SplineType}
* @param controlPoints an array of vector to use as control points of the spline
* If the type of the curve is Bezier curve the control points should be provided
* in the appropriate way. Each point 'p' describing control position in the scene
* should be surrounded by two handler points. This applies to every point except
* for the border points of the curve, who should have only one handle point.
* The pattern should be as follows:
* P0 - H0 : H1 - P1 - H1 : ... : Hn - Pn
*
* n is the amount of 'P' - points.
* @param curveTension the tension of the spline
* @param cycle true if the spline cycle.
*/
public Spline(SplineType splineType, Vector3f[] controlPoints, float curveTension, boolean cycle) {
if(splineType==SplineType.Nurb) {
throw new IllegalArgumentException("To create NURBS spline use: 'public Spline(Vector3f[] controlPoints, float[] weights, float[] nurbKnots)' constructor!");
}
for (int i = 0; i < controlPoints.length; i++) {
Vector3f vector3f = controlPoints[i];
this.controlPoints.add(vector3f);
}
type = splineType;
this.curveTension = curveTension;
this.cycle = cycle;
this.computeTotalLentgh();
}
/**
* Create a spline
* @param splineType the type of the spline @see {SplineType}
* @param controlPoints a list of vector to use as control points of the spline
* If the type of the curve is Bezier curve the control points should be provided
* in the appropriate way. Each point 'p' describing control position in the scene
* should be surrounded by two handler points. This applies to every point except
* for the border points of the curve, who should have only one handle point.
* The pattern should be as follows:
* P0 - H0 : H1 - P1 - H1 : ... : Hn - Pn
*
* n is the amount of 'P' - points.
* @param curveTension the tension of the spline
* @param cycle true if the spline cycle.
*/
public Spline(SplineType splineType, List<Vector3f> controlPoints, float curveTension, boolean cycle) {
if(splineType==SplineType.Nurb) {
throw new IllegalArgumentException("To create NURBS spline use: 'public Spline(Vector3f[] controlPoints, float[] weights, float[] nurbKnots)' constructor!");
}
type = splineType;
this.controlPoints.addAll(controlPoints);
this.curveTension = curveTension;
this.cycle = cycle;
this.computeTotalLentgh();
}
/**
* Create a NURBS spline. A spline type is automatically set to SplineType.Nurb.
* The cycle is set to <b>false</b> by default.
* @param controlPoints a list of vector to use as control points of the spline
* @param nurbKnots the nurb's spline knots
*/
public Spline(List<Vector4f> controlPoints, List<Float> nurbKnots) {
//input data control
for(int i=0;i<nurbKnots.size()-1;++i) {
if(nurbKnots.get(i)>nurbKnots.get(i+1)) {
throw new IllegalArgumentException("The knots values cannot decrease!");
}
}
//storing the data
type = SplineType.Nurb;
this.weights = new float[controlPoints.size()];
this.knots = nurbKnots;
this.basisFunctionDegree = nurbKnots.size() - weights.length;
for(int i=0;i<controlPoints.size();++i) {
Vector4f controlPoint = controlPoints.get(i);
this.controlPoints.add(new Vector3f(controlPoint.x, controlPoint.y, controlPoint.z));
this.weights[i] = controlPoint.w;
}
CurveAndSurfaceMath.prepareNurbsKnots(knots, basisFunctionDegree);
this.computeTotalLentgh();
}
private void initCatmullRomWayPoints(List<Vector3f> list) {
if (CRcontrolPoints == null) {
CRcontrolPoints = new ArrayList<Vector3f>();
} else {
CRcontrolPoints.clear();
}
int nb = list.size() - 1;
if (cycle) {
CRcontrolPoints.add(list.get(list.size() - 2));
} else {
CRcontrolPoints.add(list.get(0).subtract(list.get(1).subtract(list.get(0))));
}
for (Iterator<Vector3f> it = list.iterator(); it.hasNext();) {
Vector3f vector3f = it.next();
CRcontrolPoints.add(vector3f);
}
if (cycle) {
CRcontrolPoints.add(list.get(1));
} else {
CRcontrolPoints.add(list.get(nb).add(list.get(nb).subtract(list.get(nb - 1))));
}
}
/**
* Adds a controlPoint to the spline
* @param controlPoint a position in world space
*/
public void addControlPoint(Vector3f controlPoint) {
if (controlPoints.size() > 2 && this.cycle) {
controlPoints.remove(controlPoints.size() - 1);
}
controlPoints.add(controlPoint);
if (controlPoints.size() >= 2 && this.cycle) {
controlPoints.add(controlPoints.get(0));
}
if (controlPoints.size() > 1) {
this.computeTotalLentgh();
}
}
/**
* remove the controlPoint from the spline
* @param controlPoint the controlPoint to remove
*/
public void removeControlPoint(Vector3f controlPoint) {
controlPoints.remove(controlPoint);
if (controlPoints.size() > 1) {
this.computeTotalLentgh();
}
}
public void clearControlPoints(){
controlPoints.clear();
totalLength = 0;
}
/**
* This method computes the total length of the curve.
*/
private void computeTotalLentgh() {
totalLength = 0;
float l = 0;
if (segmentsLength == null) {
segmentsLength = new ArrayList<Float>();
} else {
segmentsLength.clear();
}
if (type == SplineType.Linear) {
if (controlPoints.size() > 1) {
for (int i = 0; i < controlPoints.size() - 1; i++) {
l = controlPoints.get(i + 1).subtract(controlPoints.get(i)).length();
segmentsLength.add(l);
totalLength += l;
}
}
} else if(type == SplineType.Bezier) {
this.computeBezierLength();
} else if(type == SplineType.Nurb) {
this.computeNurbLength();
} else {
this.initCatmullRomWayPoints(controlPoints);
this.computeCatmulLength();
}
}
/**
* This method computes the Catmull Rom curve length.
*/
private void computeCatmulLength() {
float l = 0;
if (controlPoints.size() > 1) {
for (int i = 0; i < controlPoints.size() - 1; i++) {
l = FastMath.getCatmullRomP1toP2Length(CRcontrolPoints.get(i),
CRcontrolPoints.get(i + 1), CRcontrolPoints.get(i + 2), CRcontrolPoints.get(i + 3), 0, 1, curveTension);
segmentsLength.add(l);
totalLength += l;
}
}
}
/**
* This method calculates the Bezier curve length.
*/
private void computeBezierLength() {
float l = 0;
if (controlPoints.size() > 1) {
for (int i = 0; i < controlPoints.size() - 1; i+=3) {
l = FastMath.getBezierP1toP2Length(controlPoints.get(i),
controlPoints.get(i + 1), controlPoints.get(i + 2), controlPoints.get(i + 3));
segmentsLength.add(l);
totalLength += l;
}
}
}
/**
* This method calculates the NURB curve length.
*/
private void computeNurbLength() {
//TODO: implement
}
/**
* Iterpolate a position on the spline
* @param value a value from 0 to 1 that represent the postion between the curent control point and the next one
* @param currentControlPoint the current control point
* @param store a vector to store the result (use null to create a new one that will be returned by the method)
* @return the position
*/
public Vector3f interpolate(float value, int currentControlPoint, Vector3f store) {
if (store == null) {
store = new Vector3f();
}
switch (type) {
case CatmullRom:
FastMath.interpolateCatmullRom(value, curveTension, CRcontrolPoints.get(currentControlPoint), CRcontrolPoints.get(currentControlPoint + 1), CRcontrolPoints.get(currentControlPoint + 2), CRcontrolPoints.get(currentControlPoint + 3), store);
break;
case Linear:
FastMath.interpolateLinear(value, controlPoints.get(currentControlPoint), controlPoints.get(currentControlPoint + 1), store);
break;
case Bezier:
FastMath.interpolateBezier(value, controlPoints.get(currentControlPoint), controlPoints.get(currentControlPoint + 1), controlPoints.get(currentControlPoint + 2), controlPoints.get(currentControlPoint + 3), store);
break;
case Nurb:
CurveAndSurfaceMath.interpolateNurbs(value, this, store);
break;
default:
break;
}
return store;
}
/**
* returns the curve tension
*/
public float getCurveTension() {
return curveTension;
}
/**
* sets the curve tension
*
* @param curveTension the tension
*/
public void setCurveTension(float curveTension) {
this.curveTension = curveTension;
if(type==SplineType.CatmullRom) {
this.computeTotalLentgh();
}
}
/**
* returns true if the spline cycle
*/
public boolean isCycle() {
return cycle;
}
/**
* set to true to make the spline cycle
* @param cycle
*/
public void setCycle(boolean cycle) {
if(type!=SplineType.Nurb) {
if (controlPoints.size() >= 2) {
if (this.cycle && !cycle) {
controlPoints.remove(controlPoints.size() - 1);
}
if (!this.cycle && cycle) {
controlPoints.add(controlPoints.get(0));
}
this.cycle = cycle;
this.computeTotalLentgh();
} else {
this.cycle = cycle;
}
}
}
/**
* return the total lenght of the spline
*/
public float getTotalLength() {
return totalLength;
}
/**
* return the type of the spline
*/
public SplineType getType() {
return type;
}
/**
* Sets the type of the spline
* @param type
*/
public void setType(SplineType type) {
this.type = type;
this.computeTotalLentgh();
}
/**
* returns this spline control points
*/
public List<Vector3f> getControlPoints() {
return controlPoints;
}
/**
* returns a list of float representing the segments lenght
*/
public List<Float> getSegmentsLength() {
return segmentsLength;
}
//////////// NURBS getters /////////////////////
/**
* This method returns the minimum nurb curve knot value. Check the nurb type before calling this method. It the curve is not of a Nurb
* type - NPE will be thrown.
* @return the minimum nurb curve knot value
*/
public float getMinNurbKnot() {
return knots.get(basisFunctionDegree - 1);
}
/**
* This method returns the maximum nurb curve knot value. Check the nurb type before calling this method. It the curve is not of a Nurb
* type - NPE will be thrown.
* @return the maximum nurb curve knot value
*/
public float getMaxNurbKnot() {
return knots.get(weights.length);
}
/**
* This method returns NURBS' spline knots.
* @return NURBS' spline knots
*/
public List<Float> getKnots() {
return knots;
}
/**
* This method returns NURBS' spline weights.
* @return NURBS' spline weights
*/
public float[] getWeights() {
return weights;
}
/**
* This method returns NURBS' spline basis function degree.
* @return NURBS' spline basis function degree
*/
public int getBasisFunctionDegree() {
return basisFunctionDegree;
}
@Override
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
oc.writeSavableArrayList((ArrayList) controlPoints, "controlPoints", null);
oc.write(type, "type", SplineType.CatmullRom);
float list[] = new float[segmentsLength.size()];
for (int i = 0; i < segmentsLength.size(); i++) {
list[i] = segmentsLength.get(i);
}
oc.write(list, "segmentsLength", null);
oc.write(totalLength, "totalLength", 0);
oc.writeSavableArrayList((ArrayList) CRcontrolPoints, "CRControlPoints", null);
oc.write(curveTension, "curveTension", 0.5f);
oc.write(cycle, "cycle", false);
oc.writeSavableArrayList((ArrayList<Float>)knots, "knots", null);
oc.write(weights, "weights", null);
oc.write(basisFunctionDegree, "basisFunctionDegree", 0);
}
@Override
public void read(JmeImporter im) throws IOException {
InputCapsule in = im.getCapsule(this);
controlPoints = (ArrayList<Vector3f>) in.readSavableArrayList("wayPoints", null);
float list[] = in.readFloatArray("segmentsLength", null);
if (list != null) {
segmentsLength = new ArrayList<Float>();
for (int i = 0; i < list.length; i++) {
segmentsLength.add(new Float(list[i]));
}
}
type = in.readEnum("pathSplineType", SplineType.class, SplineType.CatmullRom);
totalLength = in.readFloat("totalLength", 0);
CRcontrolPoints = (ArrayList<Vector3f>) in.readSavableArrayList("CRControlPoints", null);
curveTension = in.readFloat("curveTension", 0.5f);
cycle = in.readBoolean("cycle", false);
knots = in.readSavableArrayList("knots", null);
weights = in.readFloatArray("weights", null);
basisFunctionDegree = in.readInt("basisFunctionDegree", 0);
}
}