@numericelements/knot-sequence
Version:
A library for generating and manipulating knot sequences for b-spline curves and surfaces
289 lines (286 loc) • 18.4 kB
JavaScript
import { KNOT_SEQUENCE_ORIGIN, KNOT_COINCIDENCE_TOLERANCE, UPPER_BOUND_NORMALIZED_BASIS_DEFAULT_ABSCISSA } from './namedConstants/KnotSequences.js';
import { AbstractIncreasingOpenKnotSequence } from './AbstractIncreasingOpenKnotSequence.js';
import { Knot } from './Knot.js';
import { INCREASINGOPENKNOTSEQUENCECLOSEDCURVE, INCREASINGOPENKNOTSEQUENCE_UPTOC0DISCONTINUITY_CLOSEDCURVEALLKNOTS, INCREASINGOPENKNOTSEQUENCECLOSEDCURVEALLKNOTS, STRICTLYINCREASINGOPENKNOTSEQUENCECLOSEDCURVE } from './KnotSequenceConstructorInterface.js';
import { EM_SIZENORMALIZED_BSPLINEBASIS, EM_ORIGIN_NORMALIZEDKNOT_SEQUENCE, EM_NO_PERIODICITY_KNOTINTERVALS_SEQUENCE_CLOSURE_LEFT, EM_INCORRECT_MULTIPLICITY_AT_FIRST_KNOT, EM_NO_PERIODICITY_KNOTINTERVALS_SEQUENCE_CLOSURE_RIGHT, EM_INCORRECT_MULTIPLICITY_AT_LAST_KNOT, EM_ABSCISSA_OUT_OF_KNOT_SEQUENCE_RANGE, EM_U_OUTOF_KNOTSEQ_RANGE } from './ErrorMessages/KnotSequences.js';
import { fromIncreasingToStrictlyIncreasingOpenKnotSequenceCC } from './KnotSequenceAndUtilities/fromIncreasingToStrictlyIncreasingOpenKnotSequenceCC.js';
import { KnotIndexStrictlyIncreasingSequence } from './KnotIndexStrictlyIncreasingSequence.js';
import { KnotIndexIncreasingSequence } from './KnotIndexIncreasingSequence.js';
import { StrictlyIncreasingOpenKnotSequenceClosedCurve } from './StrictlyIncreasingOpenKnotSequenceClosedCurve.js';
import { prepareIncreasingOpenKnotSequenceCC } from './KnotSequenceAndUtilities/prepareIncreasingOpenKnotSequenceCC.js';
class IncreasingOpenKnotSequenceClosedCurve extends AbstractIncreasingOpenKnotSequence {
constructor(maxMultiplicityOrder, knotParameters) {
super(maxMultiplicityOrder, knotParameters);
if (knotParameters.type === INCREASINGOPENKNOTSEQUENCECLOSEDCURVE) {
this.computeKnotSequenceFromPeriodicKnotSequence(knotParameters);
}
// The validity of the knot sequence should follow the given sequence of calls
// to make sure that the sequence origin is correctly set first since it is used
// when checking the degree consistency and knot multiplicities outside the effective curve interval
this.checkNonUniformKnotMultiplicityOrder();
this.checkUniformityOfKnotMultiplicity();
this.checkUniformityOfKnotSpacing();
if (knotParameters.type === INCREASINGOPENKNOTSEQUENCE_UPTOC0DISCONTINUITY_CLOSEDCURVEALLKNOTS || knotParameters.type === INCREASINGOPENKNOTSEQUENCECLOSEDCURVE
|| knotParameters.type === INCREASINGOPENKNOTSEQUENCECLOSEDCURVEALLKNOTS) {
this.updateNormalizedBasisOrigin();
this.checkKnotMultiplicitiesAtNormalizedBasisBoundaries();
this.checkKnotIntervalConsistency();
}
this.checkMaxMultiplicityOrderConsistency();
}
// freeKnots is an accessor available for B-Spline curves to obtain a subset of a knot sequence, associated with control points
// that can be available as a free parameter subset for some applications, e.g., optimization
get freeKnots() {
const freeKnots = this.periodicKnots;
freeKnots.splice(0, 1);
freeKnots.splice(freeKnots.length - 1, 1);
return freeKnots;
}
get periodicKnots() {
const periodicKnots = [];
for (const knot of this) {
if (knot !== undefined)
periodicKnots.push(knot);
}
periodicKnots.splice(0, (this._maxMultiplicityOrder - this.knotSequence[this._indexKnotOrigin.knotIndex].multiplicity));
periodicKnots.splice(periodicKnots.length - (this._maxMultiplicityOrder - this.knotSequence[this._indexKnotOrigin.knotIndex].multiplicity), (this._maxMultiplicityOrder - this.knotSequence[this._indexKnotOrigin.knotIndex].multiplicity));
return periodicKnots;
}
constructorInputBspBasisSizeAssessment(knotParameters) {
if (knotParameters.BsplBasisSize < this._maxMultiplicityOrder || (this._maxMultiplicityOrder === 2 && knotParameters.BsplBasisSize < (this._maxMultiplicityOrder + 1)))
this.throwRangeErrorMessage("constructor", EM_SIZENORMALIZED_BSPLINEBASIS);
}
checkKnotIntervalConsistency() {
if (this.knotSequence[0].multiplicity >= this._maxMultiplicityOrder && this.knotSequence[this.knotSequence.length - 1].multiplicity >= this._maxMultiplicityOrder)
return;
if (this.knotSequence[this._indexKnotOrigin.knotIndex].abscissa !== KNOT_SEQUENCE_ORIGIN)
this.throwRangeErrorMessage("checkKnotIntervalConsistency", EM_ORIGIN_NORMALIZEDKNOT_SEQUENCE);
const indexRightBoundBasis = this.getKnotIndexNormalizedBasisAtSequenceEnd().knot.knotIndex;
const multiplicityAtOrigin = this.knotSequence[this._indexKnotOrigin.knotIndex].multiplicity;
let i = 0;
let cumulativeMultiplicity = 0;
while ((this._indexKnotOrigin.knotIndex - i) > 0) {
const interval1 = this.knotSequence[indexRightBoundBasis - (i + 1)].abscissa - this.knotSequence[indexRightBoundBasis - i].abscissa;
const multiplicity1 = this.knotSequence[indexRightBoundBasis - (i + 1)].multiplicity;
const interval2 = this.knotSequence[this._indexKnotOrigin.knotIndex - (i + 1)].abscissa - this.knotSequence[this._indexKnotOrigin.knotIndex - i].abscissa;
const multiplicity2 = this.knotSequence[this._indexKnotOrigin.knotIndex - (i + 1)].multiplicity;
if ((Math.abs(interval1 - interval2) > KNOT_COINCIDENCE_TOLERANCE || multiplicity1 !== multiplicity2) && (this._indexKnotOrigin.knotIndex - i) > 1) {
this.throwRangeErrorMessage("checkKnotIntervalConsistency", EM_NO_PERIODICITY_KNOTINTERVALS_SEQUENCE_CLOSURE_LEFT);
}
else if ((Math.abs(interval1 - interval2) > KNOT_COINCIDENCE_TOLERANCE || multiplicity1 < multiplicity2) && (this._indexKnotOrigin.knotIndex - i) === 1) {
this.throwRangeErrorMessage("checkKnotIntervalConsistency", EM_NO_PERIODICITY_KNOTINTERVALS_SEQUENCE_CLOSURE_LEFT);
}
if ((this._indexKnotOrigin.knotIndex - i) > 1) {
cumulativeMultiplicity += multiplicity1;
}
else if (cumulativeMultiplicity + multiplicity2 + multiplicityAtOrigin !== this._maxMultiplicityOrder) {
this.throwRangeErrorMessage("checkKnotIntervalConsistency", EM_INCORRECT_MULTIPLICITY_AT_FIRST_KNOT);
}
i++;
}
i = 0;
cumulativeMultiplicity = 0;
const multiplicityAtRightBound = this.knotSequence[indexRightBoundBasis].multiplicity;
while ((indexRightBoundBasis + i + 1) < this.knotSequence.length) {
const interval1 = this.knotSequence[indexRightBoundBasis + i].abscissa - this.knotSequence[indexRightBoundBasis + i + 1].abscissa;
const multiplicity1 = this.knotSequence[indexRightBoundBasis + (i + 1)].multiplicity;
const interval2 = this.knotSequence[this._indexKnotOrigin.knotIndex + i].abscissa - this.knotSequence[this._indexKnotOrigin.knotIndex + i + 1].abscissa;
const multiplicity2 = this.knotSequence[this._indexKnotOrigin.knotIndex + (i + 1)].multiplicity;
if ((Math.abs(interval1 - interval2) > KNOT_COINCIDENCE_TOLERANCE || multiplicity1 !== multiplicity2) && (indexRightBoundBasis + (i + 1)) < this.knotSequence.length - 1) {
this.throwRangeErrorMessage("checkKnotIntervalConsistency", EM_NO_PERIODICITY_KNOTINTERVALS_SEQUENCE_CLOSURE_RIGHT);
}
else if ((Math.abs(interval1 - interval2) > KNOT_COINCIDENCE_TOLERANCE || multiplicity2 < multiplicity1) && (indexRightBoundBasis + (i + 1)) === this.knotSequence.length - 1) {
this.throwRangeErrorMessage("checkKnotIntervalConsistency", EM_NO_PERIODICITY_KNOTINTERVALS_SEQUENCE_CLOSURE_RIGHT);
}
if ((indexRightBoundBasis + (i + 1)) < this.knotSequence.length - 1) {
cumulativeMultiplicity += multiplicity1;
}
else if (cumulativeMultiplicity + multiplicity1 + multiplicityAtRightBound !== this._maxMultiplicityOrder) {
this.throwRangeErrorMessage("checkKnotIntervalConsistency", EM_INCORRECT_MULTIPLICITY_AT_LAST_KNOT);
}
i++;
}
}
checkNonUniformKnotMultiplicityOrder() {
this._isKnotMultiplicityNonUniform = false;
}
clone() {
if (this._isSequenceUpToC0Discontinuity) {
return new IncreasingOpenKnotSequenceClosedCurve(this._maxMultiplicityOrder, { type: INCREASINGOPENKNOTSEQUENCE_UPTOC0DISCONTINUITY_CLOSEDCURVEALLKNOTS, knots: this.allAbscissae });
}
else {
return new IncreasingOpenKnotSequenceClosedCurve(this._maxMultiplicityOrder, { type: INCREASINGOPENKNOTSEQUENCECLOSEDCURVEALLKNOTS, knots: this.allAbscissae });
}
}
computeKnotSequenceFromPeriodicKnotSequence(knotParameters) {
const minValueMaxMultiplicityOrder = 2;
this.constructorInputMultOrderAssessment(minValueMaxMultiplicityOrder);
this.constructorInputArrayAssessment(knotParameters);
this.checkKnotIncreasingValues(knotParameters.periodicKnots);
const openSequence = prepareIncreasingOpenKnotSequenceCC(this._maxMultiplicityOrder, knotParameters);
for (let i = 0; i < openSequence.knots.length; i++) {
this.knotSequence.push(new Knot(openSequence.knots[i], openSequence.multiplicities[i]));
}
this._uMax = knotParameters.periodicKnots[knotParameters.periodicKnots.length - 1];
this._indexKnotOrigin.knotIndex = openSequence.indexKnotOrigin.knotIndex;
}
toKnotIndexStrictlyIncreasingSequence(index) {
const strictlyIncreasingKnotSequence = fromIncreasingToStrictlyIncreasingOpenKnotSequenceCC(this);
const abscissa = this.abscissaAtIndex(index);
let i = 0;
for (const knot of strictlyIncreasingKnotSequence.allAbscissae) {
if (knot !== undefined) {
if (knot === abscissa)
break;
i++;
}
}
return new KnotIndexStrictlyIncreasingSequence(i);
}
isAbscissaCoincidingWithKnot(abscissa) {
let coincident = false;
let indexCoincidentKnot = 0;
for (const knot of this.knotSequence) {
if (Math.abs(abscissa - knot.abscissa) < KNOT_COINCIDENCE_TOLERANCE) {
coincident = true;
break;
}
indexCoincidentKnot++;
}
if (coincident) {
if (indexCoincidentKnot < this._indexKnotOrigin.knotIndex || abscissa > this._uMax) {
this.throwRangeErrorMessage("isAbscissaCoincidingWithKnot", EM_ABSCISSA_OUT_OF_KNOT_SEQUENCE_RANGE);
}
}
return coincident;
}
getKnotMultiplicityAtSequenceOrigin() {
const multiplicity = this.knotSequence[this._indexKnotOrigin.knotIndex].multiplicity;
return multiplicity;
}
findSpan(u) {
let index = UPPER_BOUND_NORMALIZED_BASIS_DEFAULT_ABSCISSA;
if (u < KNOT_SEQUENCE_ORIGIN || u > this._uMax) {
this.throwRangeErrorMessage("findSpan", EM_U_OUTOF_KNOTSEQ_RANGE);
}
else {
if (this.isAbscissaCoincidingWithKnot(u)) {
index = 0;
for (const knot of this.knotSequence) {
index += knot.multiplicity;
if (Math.abs(u - knot.abscissa) < KNOT_COINCIDENCE_TOLERANCE) {
if (knot.abscissa === this.knotSequence[this.knotSequence.length - this._indexKnotOrigin.knotIndex - 1].abscissa
&& this.knotSequence[this.knotSequence.length - this._indexKnotOrigin.knotIndex - 1].multiplicity === this._maxMultiplicityOrder) {
index -= this.knotSequence[this.knotSequence.length - this._indexKnotOrigin.knotIndex - 1].multiplicity;
}
else if (knot.abscissa === this._uMax) {
index -= knot.multiplicity;
}
index -= 1;
break;
}
}
return new KnotIndexIncreasingSequence(index);
}
const indexAtUmax = this.getKnotIndexNormalizedBasisAtSequenceEnd();
index = this.findSpanWithAbscissaDistinctFromKnotIncreasingKnotSequence(u, indexAtUmax.knot.knotIndex);
}
return new KnotIndexIncreasingSequence(index);
}
decrementMaxMultiplicityOrder() {
let strictlyIncSeq = fromIncreasingToStrictlyIncreasingOpenKnotSequenceCC(this);
let strictlyIncSeq_Mult = strictlyIncSeq.multiplicities();
const knotIdx_maxMultiplicityOrder = [];
for (let i = 0; i < strictlyIncSeq_Mult.length; i++) {
if (strictlyIncSeq_Mult[i] === this._maxMultiplicityOrder)
knotIdx_maxMultiplicityOrder.push(i);
}
for (const index of knotIdx_maxMultiplicityOrder) {
strictlyIncSeq_Mult[index]--;
}
let newKnots = [];
if (this._maxMultiplicityOrder > 2 || (this._maxMultiplicityOrder === 2 &&
(knotIdx_maxMultiplicityOrder.length > 0 && knotIdx_maxMultiplicityOrder[0] !== this._indexKnotOrigin.knotIndex ||
knotIdx_maxMultiplicityOrder.length === 0))) {
for (let i = 1; i < strictlyIncSeq.allAbscissae.length - 1; i++) {
for (let j = 0; j < strictlyIncSeq_Mult[i]; j++) {
newKnots.push(strictlyIncSeq.allAbscissae[i]);
}
}
}
else {
if (this._isSequenceUpToC0Discontinuity) {
newKnots = new IncreasingOpenKnotSequenceClosedCurve(1, { type: INCREASINGOPENKNOTSEQUENCE_UPTOC0DISCONTINUITY_CLOSEDCURVEALLKNOTS, knots: strictlyIncSeq.allAbscissae }).allAbscissae;
}
else {
newKnots = new IncreasingOpenKnotSequenceClosedCurve(1, { type: INCREASINGOPENKNOTSEQUENCECLOSEDCURVEALLKNOTS, knots: strictlyIncSeq.allAbscissae }).allAbscissae;
}
}
if (this._isSequenceUpToC0Discontinuity) {
return new IncreasingOpenKnotSequenceClosedCurve(this._maxMultiplicityOrder - 1, { type: INCREASINGOPENKNOTSEQUENCE_UPTOC0DISCONTINUITY_CLOSEDCURVEALLKNOTS, knots: newKnots });
}
else {
return new IncreasingOpenKnotSequenceClosedCurve(this._maxMultiplicityOrder - 1, { type: INCREASINGOPENKNOTSEQUENCECLOSEDCURVEALLKNOTS, knots: newKnots });
}
}
revertKnotSequence() {
const newKnotSequence = this.clone();
newKnotSequence.revertKnotSpacing();
return newKnotSequence;
}
decrementKnotMultiplicity(index, checkSequenceConsistency = true) {
let newKnotSequence = this.clone();
newKnotSequence.decrementKnotMultiplicityKnotArrayMutSeq(index, checkSequenceConsistency);
if (checkSequenceConsistency) {
const periodicKnotAbscissae = [];
const periodicKnotMultiplicities = [];
for (let i = newKnotSequence._indexKnotOrigin.knotIndex; i < newKnotSequence.knotSequence.length; i++) {
if (newKnotSequence.knotSequence[i].abscissa <= newKnotSequence._uMax) {
periodicKnotAbscissae.push(newKnotSequence.knotSequence[i].abscissa);
periodicKnotMultiplicities.push(newKnotSequence.knotSequence[i].multiplicity);
}
}
let strctIncSeq = new StrictlyIncreasingOpenKnotSequenceClosedCurve(this._maxMultiplicityOrder, { type: STRICTLYINCREASINGOPENKNOTSEQUENCECLOSEDCURVE, periodicKnots: periodicKnotAbscissae, multiplicities: periodicKnotMultiplicities });
const incSeqAbscissae = strctIncSeq.toIncreasingSeqOfAbscissae();
if (this._isSequenceUpToC0Discontinuity) {
return new IncreasingOpenKnotSequenceClosedCurve(this._maxMultiplicityOrder, { type: INCREASINGOPENKNOTSEQUENCE_UPTOC0DISCONTINUITY_CLOSEDCURVEALLKNOTS, knots: incSeqAbscissae });
}
else {
return new IncreasingOpenKnotSequenceClosedCurve(this._maxMultiplicityOrder, { type: INCREASINGOPENKNOTSEQUENCECLOSEDCURVEALLKNOTS, knots: incSeqAbscissae });
}
}
return newKnotSequence;
}
raiseKnotMultiplicity(index, multiplicity = 1, checkSequenceConsistency = true) {
const newKnotSequence = this.clone();
newKnotSequence.raiseKnotMultiplicityKnotArrayMutSeq(index, multiplicity, checkSequenceConsistency);
return newKnotSequence;
}
insertKnot(abscissae, multiplicity = 1) {
const newKnotSequence = this.clone();
newKnotSequence.insertKnotAbscissaArrayMutSeq(abscissae, multiplicity);
return newKnotSequence;
}
updateKnotSequenceThroughNormalizedBasisAnalysis() {
const previousKnotSequence = this.knotSequence.slice();
this.updateKnotSequenceThroughNormalizedBasisAnalysisMutSeq();
const periodicKnotAbscissae = [];
const periodicKnotMultiplicities = [];
for (let i = this._indexKnotOrigin.knotIndex; i < this.knotSequence.length; i++) {
if (this.knotSequence[i].abscissa <= this._uMax) {
periodicKnotAbscissae.push(this.knotSequence[i].abscissa);
periodicKnotMultiplicities.push(this.knotSequence[i].multiplicity);
}
}
this.knotSequence = previousKnotSequence;
let strctIncSeq = new StrictlyIncreasingOpenKnotSequenceClosedCurve(this._maxMultiplicityOrder, { type: STRICTLYINCREASINGOPENKNOTSEQUENCECLOSEDCURVE, periodicKnots: periodicKnotAbscissae, multiplicities: periodicKnotMultiplicities });
const incSeqAbscissae = strctIncSeq.toIncreasingSeqOfAbscissae();
if (this._isSequenceUpToC0Discontinuity) {
return new IncreasingOpenKnotSequenceClosedCurve(this._maxMultiplicityOrder, { type: INCREASINGOPENKNOTSEQUENCE_UPTOC0DISCONTINUITY_CLOSEDCURVEALLKNOTS, knots: incSeqAbscissae });
}
else {
return new IncreasingOpenKnotSequenceClosedCurve(this._maxMultiplicityOrder, { type: INCREASINGOPENKNOTSEQUENCECLOSEDCURVEALLKNOTS, knots: incSeqAbscissae });
}
}
}
export { IncreasingOpenKnotSequenceClosedCurve };