/**
*
*/
package wblut.geom;
import java.util.Collections;
import wblut.WB_Epsilon;
import javolution.util.FastMap;
import javolution.util.FastTable;
// TODO: Auto-generated Javadoc
/**
* Normalized BSpline knot vector.
*
* @author Frederik Vanhoutte, W:Blut
*/
public class WB_NurbsKnot {
/** The values. */
protected final double[] values;
/** The degree. */
protected final int degree;
/** The n. */
protected final int n;
/** The m. */
protected final int m;
/**
* Instantiates a uniform, clamped knot vector for the given
* number of control points and curve degree.
*
* @param ncp number of control points
* @param degree degree of curve
*/
public WB_NurbsKnot(final int ncp, final int degree) {
if ((degree > ncp - 1) || (degree < 1)) {
throw new IllegalArgumentException(
"Degree too high for number of control points or smaller than 1.");
}
this.degree = degree;
n = ncp - 1;
m = n + this.degree + 1;
values = new double[m + 1];
for (int i = 0; i <= this.degree; i++) {
values[i] = 0;
values[m - i] = 1;
}
for (int i = 0; i < n - this.degree; i++) {
values[this.degree + 1 + i] = (i + 1.0) / (n - this.degree + 1);
}
}
/**
* Instantiates a non-uniform, clamped knot vector for the given
* number of control points and curve degree.
*
* @param ncp number of control points
* @param degree degree of curve
* @param val non-clamped values of knot, should be non-decreasing and of size ncp-degree-1
*/
public WB_NurbsKnot(final int ncp, final int degree, final double[] val) {
this(ncp, degree);
if (val.length != ncp - 1 - degree) {
throw new IllegalArgumentException(
"Number of provided values doesn't match knot size and/or degree.");
}
if (val[ncp - 2 - degree] > 1.0) {
throw new IllegalArgumentException(
"Highest provided value is higher than 1.");
}
for (int i = 0; i <= degree; i++) {
values[i] = 0;
values[m - i] = 1;
}
for (int i = 0; i < n - degree; i++) {
if (val[i] < values[degree + i]) {
throw new IllegalArgumentException(
"Provided values are not non-decreasing or first value is smaller than 0.");
}
values[degree + 1 + i] = val[i];
}
}
/**
* Instantiates a new w b_ nurbs knot.
*
* @param degree the degree
* @param val the val
*/
public WB_NurbsKnot(final int degree, final double[] val) {
m = val.length - 1;
values = new double[m + 1];
this.degree = degree;
n = m - this.degree - 1;
for (int i = 0; i <= m; i++) {
values[i] = val[i];
}
}
/**
* Instantiates a copy of the given knot.
*
* @param knot the knot
*/
public WB_NurbsKnot(final WB_NurbsKnot knot) {
degree = knot.degree;
n = knot.n;
m = n + degree + 1;
values = new double[m + 1];
for (int i = 0; i <= m; i++) {
values[i] = knot.value(i);
}
}
/**
* P.
*
* @return degree of knot
*/
public int p() {
return degree;
}
/**
* N.
*
* @return number of control points-1
*/
public int n() {
return n;
}
/**
* M.
*
* @return size of knot -1
*/
public int m() {
return m;
}
/**
* S.
*
* @return distinct knot values, start and end value not included
*/
public int s() {
double cv, ov;
ov = Double.NaN;
int s = 0;
for (int i = 0; i <= m; i++) {
cv = values[i];
if (cv != ov) {
s++;
}
ov = cv;
}
return s - 2;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
final StringBuffer str = new StringBuffer();
str.append("Knot {");
for (int i = 0; i < m; i++) {
str.append(values[i] + ", ");
}
str.append(values[m] + "}");
return str.toString();
}
/**
* Get knot values.
*
* @return knot values
*/
public double[] values() {
return values;
}
/**
* Get ith knot value.
*
* @param i index of knot value (0<=i<=m)
* @return ith value
*/
public double value(final int i) {
if ((i < 0) || (i > m)) {
throw new IllegalArgumentException("Index " + i
+ " doesn't exist in knot.");
}
return values[i];
}
/**
* Set ith knot value.
*
* @param i index of knot value (0<=i<=m)
* @param k knot value
*/
public void setValue(final int i, final double k) {
if ((i < 0) || (i > m)) {
throw new IllegalArgumentException("Index " + i
+ " doesn't exist in knot.");
}
values[i] = k;
}
/**
* Find knot span for given curve parameter.
*
* @param u parameter (0<=u<=1)
* @return span
*/
public int span(final double u) {
if ((u < values[0]) || (u > values[n + 1])) {
throw new IllegalArgumentException("Value outside of knot range.");
}
if (u == values[n + 1]) {
return n;
}
int low = degree;
int high = n + 1;
int mid = (low + high) / 2;
while ((u < values[mid]) || (u >= values[mid + 1])) {
if (u < values[mid]) {
high = mid;
} else {
low = mid;
}
mid = (low + high) / 2;
}
return mid;
}
/**
* Find multiplicity of knot value corresponding to parameter value.
*
* @param u parameter (0<=u<=1)
* @return multiplicity
*/
public int multiplicity(final double u) {
if ((u < values[0]) || (u > values[n + 1])) {
throw new IllegalArgumentException("Value outside of knot range.");
}
int mult = 0;
final int i = span(u);
int li = i;
int ui = i + 1;
while ((li >= 0) && (WB_Epsilon.isEqualAbs(values[li], u))) {
mult++;
li--;
}
while ((ui < m + 1) && (WB_Epsilon.isEqualAbs(values[ui], u))) {
mult++;
ui++;
}
return mult;
}
/**
* Find multiplicity of knot value corresponding to parameter value, optimized when span is already known.
*
* @param u parameter (0<=u<=1)
* @param span span of u
* @return multiplicity
*/
public int multiplicity(final double u, final int span) {
if ((u < values[0]) || (u > values[n + 1])) {
throw new IllegalArgumentException("Value outside of knot range.");
}
int mult = 0;
int li = span;
int ui = span + 1;
while ((li >= 0) && (values[li] == u)) {
mult++;
li--;
}
while ((ui < m + 1) && (values[ui] == u)) {
mult++;
ui++;
}
return mult;
}
/**
* Basis functions.
*
* @param span the span
* @param u the u
* @return the double[]
*/
protected double[] basisFunctions(final int span, final double u) {
final double[] N = new double[degree + 1];
final double[] left = new double[degree + 1];
final double[] right = new double[degree + 1];
double saved, temp;
N[0] = 1.0;
for (int j = 1; j <= degree; j++) {
left[j] = u - values[span + 1 - j];
right[j] = values[span + j] - u;
saved = 0.0;
for (int r = 0; r < j; r++) {
temp = N[r] / (right[r + 1] + left[j - r]);
N[r] = saved + right[r + 1] * temp;
saved = left[j - r] * temp;
}
N[j] = saved;
}
return N;
}
/**
* All basis functions.
*
* @param span the span
* @param u the u
* @param p the p
* @return the double[][]
*/
protected double[][] allBasisFunctions(final int span, final double u,
final int p) {
final double[][] N = new double[p + 1][p + 1];
for (int i = 0; i <= p; i++) {
final double[] left = new double[i + 1];
final double[] right = new double[i + 1];
double saved, temp;
N[0][i] = 1.0;
for (int j = 1; j <= i; j++) {
left[j] = u - values[span + 1 - j];
right[j] = values[span + j] - u;
saved = 0.0;
for (int r = 0; r < j; r++) {
temp = N[r][i] / (right[r + 1] + left[j - r]);
N[r][i] = saved + right[r + 1] * temp;
saved = left[j - r] * temp;
}
N[j][i] = saved;
}
}
return N;
}
/**
* Normalize knot range to [0,1].
*/
public void normalize() {
final double low = values[0];
final double high = values[m];
double denom = (high - low);
if (WB_Epsilon.isZero(denom)) {
throw new IllegalArgumentException(
"Knot value range too small to normalize.");
}
denom = 1.0 / denom;
for (int i = 0; i <= m; i++) {
values[i] -= low;
values[i] *= denom;
if (WB_Epsilon.isZero(values[i])) {
values[i] = 0.0;
}
if (WB_Epsilon.isZero(values[i] - 1.0)) {
values[i] = 1.0;
}
}
}
/**
* Merge.
*
* @param UA the ua
* @param UB the ub
* @return the w b_ nurbs knot
*/
public static WB_NurbsKnot merge(final WB_NurbsKnot UA, final WB_NurbsKnot UB) {
if (UA.p() != UB.p()) {
throw new IllegalArgumentException(
"Cannot merge knots of different degree.");
}
final FastMap<Double, Integer> knotvalues = new FastMap<Double, Integer>();
int total = 0;
for (int i = 0; i <= UA.m;) {
final double ua = UA.value(i);
final int mul = UA.multiplicity(ua);
final Integer mulex = knotvalues.get(ua);
if (mulex == null) {
knotvalues.put(ua, mul);
total += mul;
} else if (mulex < mul) {
knotvalues.put(ua, mul);
total += mul - mulex;
}
i += mul;
}
for (int i = 0; i <= UB.m;) {
final double ub = UB.value(i);
final int mul = UB.multiplicity(ub);
final Integer mulex = knotvalues.get(ub);
if (mulex == null) {
knotvalues.put(ub, mul);
total += mul;
} else if (mulex < mul) {
knotvalues.put(ub, mul);
total += mul - mulex;
}
i += mul;
}
final FastTable<Double> distinctValues = new FastTable<Double>();
distinctValues.addAll(knotvalues.keySet());
Collections.sort(distinctValues);
final double[] allValues = new double[total];
int offset = 0;
for (int i = 0; i < distinctValues.size(); i++) {
final int mul = knotvalues.get(distinctValues.get(i));
for (int j = 0; j < mul; j++) {
allValues[offset + j] = distinctValues.get(i);
}
offset += mul;
}
return new WB_NurbsKnot(UA.degree, allValues);
}
}