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

com.graphbuilder.curve.BSpline Maven / Gradle / Ivy

Go to download

This OSGi bundle wraps poi, poi-ooxml, and poi-scratchpad ${pkgVersion} jar files.

There is a newer version: 5.2.3_1
Show newest version
/*
* Copyright (c) 2005, Graph Builder
* 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 Graph Builder 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.graphbuilder.curve;


/**

General non-rational B-Spline implementation where the degree can be specified.

For the B-Spline, there are 3 types of knot-vectors, uniform clamped, uniform unclamped, and non-uniform. A uniform knot-vector means that the knots are equally spaced. A clamped knot-vector means that the first k-knots and last k-knots are repeated, where k is the degree + 1. Non-uniform means that the knot-values have no specific properties. For all 3 types, the knot-values must be non-decreasing.

Here are some examples of uniform clamped knot vectors for degree 3:

number of control points = 4: [0, 0, 0, 0, 1, 1, 1, 1]
number of control points = 7: [0, 0, 0, 0, 0.25, 0.5, 0.75, 1, 1, 1, 1]

The following is a figure of a B-Spline generated using a uniform clamped knot vector:

Here are some examples of uniform unclamped knot vectors for degree 3:

number of control points = 4: [0, 0.14, 0.29, 0.43, 0.57, 0.71, 0.86, 1] (about)
number of control points = 7: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]

The following is a figure of a B-Spline generated using a uniform unclamped knot vector:

Note: Although the knot-values in the examples are between 0 and 1, this is not a requirement.

When the knot-vector is uniform clamped, the default interval is [0, 1]. When the knot-vector is uniform unclamped, the default interval is [grad * degree, 1 - grad * degree], where grad is the gradient or the knot-span. Specifying the knotVectorType as UNIFORM_CLAMPED or UNIFORM_UNCLAMPED means that the internal knot-vector will not be used.

Note: The computation required is O(2^degree) or exponential. Increasing the degree by 1 means that twice as many computations are done. */ public class BSpline extends ParametricCurve { public static final int UNIFORM_CLAMPED = 0; public static final int UNIFORM_UNCLAMPED = 1; public static final int NON_UNIFORM = 2; private static final ThreadLocal SHARED_DATA = new ThreadLocal(){ protected SharedData initialValue() { return new SharedData(); } }; private final SharedData sharedData = SHARED_DATA.get(); private static class SharedData { private int[] a = new int[0]; // counter used for the a-function values (required length >= degree) private int[] c = new int[0]; // counter used for bit patterns (required length >= degree) private double[] knot = new double[0]; // (required length >= numPts + degree) } private ValueVector knotVector = new ValueVector(new double[] { 0, 0, 0, 0, 1, 1, 1, 1 }, 8); private double t_min = 0.0; private double t_max = 1.0; private int sampleLimit = 1; private int degree = 4; // the internal degree variable is always 1 plus the specified degree private int knotVectorType = UNIFORM_CLAMPED; private boolean useDefaultInterval = true; public BSpline(ControlPath cp, GroupIterator gi) { super(cp, gi); } protected void eval(double[] p) { int dim = p.length - 1; double t = p[dim]; int numPts = gi.getGroupSize(); gi.set(0,0); for (int i = 0; i < numPts; i++) { double w = N(t, i); //double w = N(t, i, degree); double[] loc = cp.getPoint(gi.next()).getLocation(); for (int j = 0; j < dim; j++) p[j] += (loc[j] * w); //pt[i][j] * w); } } /** Specifies the interval that the curve should define itself on. The default interval is [0.0, 1.0]. When the knot-vector type is one of UNIFORM_CLAMPED or UNIFORM_UNCLAMPED and the useDefaultInterval flag is true, then these values will not be used. @throws IllegalArgumentException If t_min > t_max. @see #t_min() @see #t_max() */ public void setInterval(double t_min, double t_max) { if (t_min > t_max) throw new IllegalArgumentException("t_min <= t_max required."); this.t_min = t_min; this.t_max = t_max; } /** Returns the starting interval value. @see #setInterval(double, double) @see #t_max() */ public double t_min() { return t_min; } /** Returns the finishing interval value. @see #setInterval(double, double) @see #t_min() */ public double t_max() { return t_max; } public int getSampleLimit() { return sampleLimit; } /** Sets the sample-limit. For more information on the sample-limit, see the BinaryCurveApproximationAlgorithm class. The default sample-limit is 1. @throws IllegalArgumentException If sample-limit < 0. @see com.graphbuilder.curve.BinaryCurveApproximationAlgorithm @see #getSampleLimit() */ public void setSampleLimit(int limit) { if (limit < 0) throw new IllegalArgumentException("Sample-limit >= 0 required."); sampleLimit = limit; } /** Returns the degree of the curve. @see #setDegree(int) */ public int getDegree() { return degree - 1; } /** Sets the degree of the curve. The degree specifies how many controls points have influence when computing a single point on the curve. Specifically, degree + 1 control points are used. The degree must be greater than 0. A degree of 1 is linear, 2 is quadratic, 3 is cubic, etc. Warning: Increasing the degree by 1 doubles the number of computations required. The default degree is 3 (cubic). @see #getDegree() @throws IllegalArgumentException If degree <= 0. */ public void setDegree(int d) { if (d <= 0) throw new IllegalArgumentException("Degree > 0 required."); degree = d + 1; } /** Returns the knot-vector for this curve. @see #setKnotVector(ValueVector) */ public ValueVector getKnotVector() { return knotVector; } /** Sets the knot-vector for this curve. When the knot-vector type is one of UNIFORM_CLAMPED or UNIFORM_UNCLAMPED then the values in the knot-vector will not be used. @see #getKnotVector() @throws IllegalArgumentException If the value-vector is null. */ public void setKnotVector(ValueVector v) { if (v == null) throw new IllegalArgumentException("Knot-vector cannot be null."); knotVector = v; } /** Returns the value of the useDefaultInterval flag. @see #setUseDefaultInterval(boolean) */ public boolean getUseDefaultInterval() { return useDefaultInterval; } /** Sets the value of the useDefaultInterval flag. When the knot-vector type is one of UNIFORM_CLAMPED or UNIFORM_UNCLAMPED and the useDefaultInterval flag is true, then default values will be computed for t_min and t_max. Otherwise t_min and t_max are used as the interval. @see #getUseDefaultInterval() */ public void setUseDefaultInterval(boolean b) { useDefaultInterval = b; } /** Returns the type of knot-vector to use. @see #setKnotVectorType(int) */ public int getKnotVectorType() { return knotVectorType; } /** Sets the type of knot-vector to use. There are 3 types, UNIFORM_CLAMPED, UNIFORM_UNCLAMPED and NON_UNIFORM. NON_UNIFORM can be thought of as user specified. UNIFORM_CLAMPED and UNIFORM_UNCLAMPED are standard knot-vectors for the B-Spline. @see #getKnotVectorType() @throws IllegalArgumentException If the knot-vector type is unknown. */ public void setKnotVectorType(int type) { if (type < 0 || type > 2) throw new IllegalArgumentException("Unknown knot-vector type."); knotVectorType = type; } /** There are two types of requirements for this curve, common requirements and requirements that depend on the knotVectorType. The common requirements are that the group-iterator must be in range and the number of points (group size) must be greater than the degree. If the knot-vector type is NON_UNIFORM (user specified) then there are additional requirements, otherwise there are no additional requirements. The additional requirements when the knotVectorType is NON_UNIFORM are that the internal-knot vector must have an exact size of degree + numPts + 1, where degree is specified by the setDegree method and numPts is the group size. Also, the knot-vector values must be non-decreasing. If any of these requirements are not met, then IllegalArgumentException is thrown */ public void appendTo(MultiPath mp) { if (!gi.isInRange(0, cp.numPoints())) throw new IllegalArgumentException("Group iterator not in range"); int numPts = gi.getGroupSize(); int f = numPts - degree; if (f < 0) throw new IllegalArgumentException("group iterator size - degree < 0"); int x = numPts + degree; if (sharedData.knot.length < x) sharedData.knot = new double[2 * x]; double t1 = t_min; double t2 = t_max; if (knotVectorType == NON_UNIFORM) { if (knotVector.size() != x) throw new IllegalArgumentException("knotVector.size(" + knotVector.size() + ") != " + x); sharedData.knot[0] = knotVector.get(0); for (int i = 1; i < x; i++) { sharedData.knot[i] = knotVector.get(i); if (sharedData.knot[i] < sharedData.knot[i-1]) throw new IllegalArgumentException("Knot not in sorted order! (knot[" + i + "] < knot[" + i + "-1])"); } } else if (knotVectorType == UNIFORM_UNCLAMPED) { double grad = 1.0 / (x - 1); for (int i = 0; i < x; i++) sharedData.knot[i] = i * grad; if (useDefaultInterval) { t1 = (degree - 1) * grad; t2 = 1.0 - (degree - 1) * grad; } } else if (knotVectorType == UNIFORM_CLAMPED) { double grad = 1.0 / (f + 1); for (int i = 0; i < degree; i++) sharedData.knot[i] = 0; int j = degree; for (int i = 1; i <= f; i++) sharedData.knot[j++] = i * grad; for (int i = j; i < x; i++) sharedData.knot[i] = 1.0; if (useDefaultInterval) { t1 = 0.0; t2 = 1.0; } } if (sharedData.a.length < degree) { sharedData.a = new int[2 * degree]; sharedData.c = new int[2 * degree]; } double[] p = new double[mp.getDimension() + 1]; p[mp.getDimension()] = t1; eval(p); if (connect) mp.lineTo(p); else mp.moveTo(p); BinaryCurveApproximationAlgorithm.genPts(this, t1, t2, mp); } /** Non-recursive implementation of the N-function. */ protected double N(double t, int i) { double d = 0; for (int j = 0; j < degree; j++) { double t1 = sharedData.knot[i+j]; double t2 = sharedData.knot[i+j+1]; if (t >= t1 && t <= t2 && t1 != t2) { int dm2 = degree - 2; for (int k = degree - j - 1; k >= 0; k--) sharedData.a[k] = 0; if (j > 0) { for (int k = 0; k < j; k++) sharedData.c[k] = k; sharedData.c[j] = Integer.MAX_VALUE; } else { sharedData.c[0] = dm2; sharedData.c[1] = degree; } int z = 0; while (true) { if (sharedData.c[z] < sharedData.c[z+1] - 1) { double e = 1.0; int bc = 0; int y = dm2 - j; int p = j - 1; for (int m = dm2, n = degree; m >= 0; m--, n--) { if (p >= 0 && sharedData.c[p] == m) { int w = i + bc; double kd = sharedData.knot[w+n]; e *= (kd - t) / (kd - sharedData.knot[w+1]); bc++; p--; } else { int w = i + sharedData.a[y]; double kw = sharedData.knot[w]; e *= (t - kw) / (sharedData.knot[w+n-1] - kw); y--; } } // this code updates the a-counter if (j > 0) { int g = 0; boolean reset = false; while (true) { sharedData.a[g]++; if (sharedData.a[g] > j) { g++; reset = true; } else { if (reset) { for (int h = g - 1; h >= 0; h--) sharedData.a[h] = sharedData.a[g]; } break; } } } d += e; // this code updates the bit-counter sharedData.c[z]++; if (sharedData.c[z] > dm2) break; for (int k = 0; k < z; k++) sharedData.c[k] = k; z = 0; } else { z++; } } break; // required to prevent spikes } } return d; } /* The recursive implementation of the N-function (not used) is below. In addition to being slower, the recursive implementation of the N-function has another problem which relates to the base case. Note: the reason the recursive implementation is slower is because there are a lot of repetitive calculations. Some definitions of the N-function give the base case as t >= knot[i] && t < knot[i+1] or t > knot[i] && t <= knot[i+1]. To see why this is a problem, consider evaluating t on the range [0, 1] with the Bezier Curve knot vector [0,0,0,...,1,1,1]. Then, trying to evaluate t == 1 or t == 0 won't work. Changing the base case to the one below (with equal signs on both comparisons) leads to a problem known as spikes. A curve will have spikes at places where t falls on a knot because when equality is used on both comparisons, the value of t will have 2 regions of influence. */ /*private double N(double t, int i, int k) { if (k == 1) { if (t >= knot[i] && t <= knot[i+1] && knot[i] != knot[i+1]) return 1.0; return 0.0; } double n1 = N(t, i, k-1); double n2 = N(t, i+1, k-1); double a = 0.0; double b = 0.0; if (n1 != 0) a = (t - knot[i]) / (knot[i+k-1] - knot[i]); if (n2 != 0) b = (knot[i+k] - t) / (knot[i+k] - knot[i+1]); return a * n1 + b * n2; }*/ public void resetMemory() { if (sharedData.a.length > 0) { sharedData.a = new int[0]; sharedData.c = new int[0]; } if (sharedData.knot.length > 0) sharedData.knot = new double[0]; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy