All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jme3.math.Spline Maven / Gradle / Ivy

There is a newer version: 3.7.0-stable
Show newest version
/*
 * Copyright (c) 2009-2021 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
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 controlPoints = new ArrayList<>();
    private List 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 segmentsLength;
    private float totalLength;
    private List 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 Bézier 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.computeTotalLength();
    }

    /**
     * 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 curve is a Bézier 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 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.computeTotalLength();
    }

    /**
     * Create a NURBS spline. A spline type is automatically set to SplineType.Nurb.
     * The cycle is set to false 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 controlPoints, List 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.computeTotalLength();
    }

    private void initCatmullRomWayPoints(List list) {
        if (CRcontrolPoints == null) {
            CRcontrolPoints = new ArrayList();
        } 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 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.clone());
        if (controlPoints.size() >= 2 && this.cycle) {
            controlPoints.add(controlPoints.get(0).clone());
        }
        if (controlPoints.size() > 1) {
            this.computeTotalLength();
        }
    }

    /**
     * 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.computeTotalLength();
        }
    }

    public void clearControlPoints() {
        controlPoints.clear();
        totalLength = 0;
    }

    /**
     * This method computes the total length of the curve.
     */
    private void computeTotalLength() {
        totalLength = 0;
        float l = 0;
        if (segmentsLength == null) {
            segmentsLength = new ArrayList();
        } 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 Bézier 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
    }

    /**
     * Interpolate a position on the spline
     *
     * @param value a value from 0 to 1 that represent the position between the
     * current 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
     *
     * @return the value of the tension parameter
     */
    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 && !getControlPoints().isEmpty()) {
            this.computeTotalLength();
        }
    }

    /**
     * @return true if the spline cycles
     */
    public boolean isCycle() {
        return cycle;
    }

    /**
     * set to true to make the spline cycle
     *
     * @param cycle true for cyclic, false for acyclic
     */
    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.computeTotalLength();
            } else {
                this.cycle = cycle;
            }
        }
    }

    /**
     * @return the total length of the spline
     */
    public float getTotalLength() {
        return totalLength;
    }

    /**
     * return the type of the spline
     *
     * @return the enum value
     */
    public SplineType getType() {
        return type;
    }

    /**
     * Sets the type of the spline
     *
     * @param type Linear/CatmullRom/Bezier/Nurb
     */
    public void setType(SplineType type) {
        this.type = type;
        this.computeTotalLength();
    }

    /**
     * returns this spline control points
     *
     * @return the pre-existing list
     */
    public List getControlPoints() {
        return controlPoints;
    }

    /**
     * returns a list of float representing the segments length
     *
     * @return the pre-existing list
     */
    public List getSegmentsLength() {
        return segmentsLength;
    }

    //////////// NURBS getters /////////////////////
    /**
     * This method returns the minimum nurb curve knot value. Check the nurb
     * type before calling this method. If the curve is not of a Nurb type, an 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. If the curve is not of a Nurb type, an 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 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;
    }

    /**
     * Serialize this spline to the specified exporter, for example when
     * saving to a J3O file.
     *
     * @param ex (not null)
     * @throws IOException from the exporter
     */
    @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[] = null;
        if (segmentsLength != null) {
            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) knots, "knots", null);
        oc.write(weights, "weights", null);
        oc.write(basisFunctionDegree, "basisFunctionDegree", 0);
    }

    /**
     * De-serialize this spline from the specified importer, for example
     * when loading from a J3O file.
     *
     * @param im (not null)
     * @throws IOException from the importer
     */
    @Override
    @SuppressWarnings("unchecked")
    public void read(JmeImporter im) throws IOException {
        InputCapsule in = im.getCapsule(this);

        controlPoints = in.readSavableArrayList("controlPoints", new ArrayList<>());
        /* Empty List as default, prevents null pointers */
        float list[] = in.readFloatArray("segmentsLength", null);
        if (list != null) {
            segmentsLength = new ArrayList();
            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 = 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);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy