UNPKG

@numericelements/knot-sequence

Version:

A library for generating and manipulating knot sequences for b-spline curves and surfaces

266 lines (263 loc) 16.7 kB
import { Knot } from './Knot.js'; import { AbstractStrictlyIncreasingOpenKnotSequence } from './AbstractStrictlyIncreasingOpenKnotSequence.js'; import { STRICTLYINCREASINGOPENKNOTSEQUENCECLOSEDCURVE, STRICTLYINCREASINGPERIODICKNOTSEQUENCE, STRICTLYINCREASINGOPENKNOTSEQUENCE_UPTOC0DISCONTINUITY_CLOSEDCURVEALLKNOTS, STRICTLYINCREASINGOPENKNOTSEQUENCECLOSEDCURVEALLKNOTS } from './KnotSequenceConstructorInterface.js'; import { StrictlyIncreasingPeriodicKnotSequenceClosedCurve } from './StrictlyIncreasingPeriodicKnotSequenceClosedCurve.js'; import { KnotIndexStrictlyIncreasingSequence } from './KnotIndexStrictlyIncreasingSequence.js'; import { KnotIndexIncreasingSequence } from './KnotIndexIncreasingSequence.js'; import { prepareStrictlyIncreasingOpenKnotSequenceCC } from './KnotSequenceAndUtilities/prepareStrictlyIncreasingOpenKnotSequenceCC.js'; import { KNOT_SEQUENCE_ORIGIN, KNOT_COINCIDENCE_TOLERANCE, UPPER_BOUND_NORMALIZED_BASIS_DEFAULT_ABSCISSA } from './namedConstants/KnotSequences.js'; import { 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_SIZENORMALIZED_BSPLINEBASIS, EM_MAXMULTIPLICITY_ORDER_KNOT, EM_ABSCISSA_OUT_OF_KNOT_SEQUENCE_RANGE, EM_U_OUTOF_KNOTSEQ_RANGE } from './ErrorMessages/KnotSequences.js'; class StrictlyIncreasingOpenKnotSequenceClosedCurve extends AbstractStrictlyIncreasingOpenKnotSequence { constructor(maxMultiplicityOrder, knotParameters) { super(maxMultiplicityOrder, knotParameters); if (knotParameters.type === STRICTLYINCREASINGOPENKNOTSEQUENCECLOSEDCURVE) { this.computeKnotSequenceFromPeriodicKnotSequence(knotParameters); } // this._isSequenceOfDerivative = false; // 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.updateNormalizedBasisOrigin(); this.checkMaxMultiplicityOrderConsistency(); this.checkKnotIntervalConsistency(); this.checkUniformityOfKnotSpacing(); this.checkUniformityOfKnotMultiplicity(); this.checkNonUniformKnotMultiplicityOrder(); } // // 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 // // here it is the abscissa only that can be regarded as free parameters, their multiplicity order is not considered (care must be taken about this point) // get freeKnots(): StrictlyIncreasingPeriodicKnotSequenceClosedCurve { // const abscissae = this.periodicKnots.allAbscissae.slice(0, this.periodicKnots.allAbscissae.length - 2); // const multiplicities: number[] = []; // return new StrictlyIncreasingPeriodicKnotSequenceClosedCurve(this._maxMultiplicityOrder - 1, {type: STRICTLYINCREASINGPERIODICKNOTSEQUENCE, periodicKnots: abscissae, multiplicities: multiplicities}); // // const freeKnots = this.periodicKnots; // // freeKnots.splice(0, 1); // // freeKnots.splice(freeKnots.length - 1, 1); // // return freeKnots; // } get periodicKnots() { const abscissae = []; const multiplicities = []; for (let i = this._indexKnotOrigin.knotIndex; i <= this.getKnotIndexNormalizedBasisAtSequenceEnd().knot.knotIndex; i++) { abscissae.push(this.knotSequence[i].abscissa); multiplicities.push(this.knotSequence[i].multiplicity); } return new StrictlyIncreasingPeriodicKnotSequenceClosedCurve(this._maxMultiplicityOrder - 1, { type: STRICTLYINCREASINGPERIODICKNOTSEQUENCE, periodicKnots: abscissae, multiplicities: multiplicities }); } get isSequenceUpToC0Discontinuity() { return this._isSequenceUpToC0Discontinuity; } checkNonUniformKnotMultiplicityOrder() { this._isKnotMultiplicityNonUniform = false; } checkKnotIntervalConsistency() { if (this.knotSequence[0].multiplicity >= this._maxMultiplicityOrder && this.knotSequence[this.knotSequence.length - 1].multiplicity >= this._maxMultiplicityOrder) return; if (this.abscissaAtIndex(this._indexKnotOrigin) !== KNOT_SEQUENCE_ORIGIN) this.throwRangeErrorMessage("checkKnotIntervalConsistency", EM_ORIGIN_NORMALIZEDKNOT_SEQUENCE); const normalizedBasis = this.getKnotIndicesBoundingNormalizedBasis(); const indexKnotOrigin = normalizedBasis.start.knot.knotIndex; const multiplicityAtOrigin = this.knotSequence[indexKnotOrigin].multiplicity; const indexEnd = normalizedBasis.end.knot.knotIndex; let i = 0; let cumulativeMultiplicity = 0; while ((indexKnotOrigin - i) !== 0) { const interval1 = this.knotSequence[indexEnd - i].abscissa - this.knotSequence[indexEnd - i - 1].abscissa; const multiplicity1 = this.knotSequence[indexEnd - (i + 1)].multiplicity; const interval2 = this.knotSequence[indexKnotOrigin - (i + 1)].abscissa - this.knotSequence[indexKnotOrigin - i].abscissa; const multiplicity2 = this.knotSequence[indexKnotOrigin - (i + 1)].multiplicity; if ((Math.abs(interval1 + interval2) > KNOT_COINCIDENCE_TOLERANCE || multiplicity1 !== multiplicity2) && (indexKnotOrigin - i) > 1) { this.throwRangeErrorMessage("checkKnotIntervalConsistency", EM_NO_PERIODICITY_KNOTINTERVALS_SEQUENCE_CLOSURE_LEFT); } else if ((Math.abs(interval1 + interval2) > KNOT_COINCIDENCE_TOLERANCE || multiplicity1 < multiplicity2) && (indexKnotOrigin - i) === 1) { this.throwRangeErrorMessage("checkKnotIntervalConsistency", EM_NO_PERIODICITY_KNOTINTERVALS_SEQUENCE_CLOSURE_LEFT); } if ((indexKnotOrigin - 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[indexEnd].multiplicity; while ((indexEnd + i) < (this.knotSequence.length - 1)) { const interval1 = this.knotSequence[indexKnotOrigin + (i + 1)].abscissa - this.knotSequence[indexKnotOrigin + i].abscissa; const multiplicity1 = this.knotSequence[indexKnotOrigin + (i + 1)].multiplicity; const interval2 = this.knotSequence[indexEnd + i].abscissa - this.knotSequence[indexEnd + (i + 1)].abscissa; const multiplicity2 = this.knotSequence[indexEnd + (i + 1)].multiplicity; if ((Math.abs(interval1 + interval2) > KNOT_COINCIDENCE_TOLERANCE || multiplicity1 !== multiplicity2) && (indexEnd + i) < (this.knotSequence.length - 2)) { this.throwRangeErrorMessage("checkKnotIntervalConsistency", EM_NO_PERIODICITY_KNOTINTERVALS_SEQUENCE_CLOSURE_RIGHT); } else if ((Math.abs(interval1 + interval2) > KNOT_COINCIDENCE_TOLERANCE || multiplicity1 < multiplicity2) && (indexEnd + i) === (this.knotSequence.length - 2)) { this.throwRangeErrorMessage("checkKnotIntervalConsistency", EM_NO_PERIODICITY_KNOTINTERVALS_SEQUENCE_CLOSURE_RIGHT); } if ((indexEnd + i) < (this.knotSequence.length - 2)) { cumulativeMultiplicity += multiplicity1; } else if (cumulativeMultiplicity + multiplicity2 + multiplicityAtRightBound !== this._maxMultiplicityOrder) { this.throwRangeErrorMessage("checkKnotIntervalConsistency", EM_INCORRECT_MULTIPLICITY_AT_LAST_KNOT); } i++; } } constructorInputBspBasisSizeAssessment(knotParameters) { if (knotParameters.BsplBasisSize < this._maxMultiplicityOrder || (this._maxMultiplicityOrder === 2 && knotParameters.BsplBasisSize < (this._maxMultiplicityOrder + 1))) this.throwRangeErrorMessage("constructor", EM_SIZENORMALIZED_BSPLINEBASIS); } computeKnotSequenceFromPeriodicKnotSequence(knotParameters) { const minValueMaxMultiplicityOrder = 2; this.constructorInputMultOrderAssessment(minValueMaxMultiplicityOrder); this.constructorInputArrayAssessment(knotParameters); this.checkKnotStrictlyIncreasingValues(knotParameters.periodicKnots); if (this._maxMultiplicityOrder === 2 && knotParameters.periodicKnots.length < 3) this.throwRangeErrorMessage("computeKnotSequenceFromPeriodicKnotSequence", EM_SIZENORMALIZED_BSPLINEBASIS); if (this._maxMultiplicityOrder > 2) { for (const multiplicity of knotParameters.multiplicities) { if (multiplicity > this._maxMultiplicityOrder) this.throwRangeErrorMessage("computeKnotSequenceFromPeriodicKnotSequence", EM_MAXMULTIPLICITY_ORDER_KNOT); } if (knotParameters.periodicKnots.length <= (1 + this._maxMultiplicityOrder - knotParameters.multiplicities[0])) { let cumulative_multiplicities = 0; for (let i = 1; i < knotParameters.periodicKnots.length - 1; i++) { cumulative_multiplicities += knotParameters.multiplicities[i]; } if (cumulative_multiplicities < (this._maxMultiplicityOrder - knotParameters.multiplicities[0])) this.throwRangeErrorMessage("computeKnotSequenceFromPeriodicKnotSequence", EM_SIZENORMALIZED_BSPLINEBASIS); } } const openSequence = prepareStrictlyIncreasingOpenKnotSequenceCC(this._maxMultiplicityOrder, knotParameters); const knots = openSequence.knots; const multiplicities = openSequence.multiplicities; for (let i = 0; i < knots.length; i++) { this.knotSequence.push(new Knot(knots[i], multiplicities[i])); } this._uMax = openSequence.uMax; this._indexKnotOrigin = openSequence.indexKnotOrigin; } 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; } getKnotMultiplicityAtCurveOrigin() { const multiplicity = this.knotSequence[this._indexKnotOrigin.knotIndex].multiplicity; return multiplicity; } // This index transformation is not unique. The convention followed here is the assignment of the first index of the increasing // sequence where the abscissa at index (strictly increasing sequence) appears toKnotIndexIncreasingSequence(index) { this.strictlyIncKnotIndexInputParamAssessment(index, "toKnotIndexIncreasingSequence"); let indexIncSeq = 0; for (let i = 0; i < index.knotIndex; i++) { indexIncSeq += this.knotSequence[i].multiplicity; } return new KnotIndexIncreasingSequence(indexIncSeq); } clone() { if (this._isSequenceUpToC0Discontinuity) { return new StrictlyIncreasingOpenKnotSequenceClosedCurve(this._maxMultiplicityOrder, { type: STRICTLYINCREASINGOPENKNOTSEQUENCE_UPTOC0DISCONTINUITY_CLOSEDCURVEALLKNOTS, knots: this.distinctAbscissae(), multiplicities: this.multiplicities() }); } else { return new StrictlyIncreasingOpenKnotSequenceClosedCurve(this._maxMultiplicityOrder, { type: STRICTLYINCREASINGOPENKNOTSEQUENCECLOSEDCURVEALLKNOTS, knots: this.distinctAbscissae(), multiplicities: this.multiplicities() }); } } 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++; if (Math.abs(u - knot.abscissa) < KNOT_COINCIDENCE_TOLERANCE) { if (knot.abscissa === this.knotSequence[this.knotSequence.length - this._indexKnotOrigin.knotIndex - 1].abscissa) { index = this.knotSequence.length - this._indexKnotOrigin.knotIndex - 1; } index -= 1; break; } } return new KnotIndexStrictlyIncreasingSequence(index); } const indexAtUmax = this.getKnotIndexNormalizedBasisAtSequenceEnd(); index = this.findSpanWithAbscissaDistinctFromKnotStrictlyIncreasingKnotSequence(u, indexAtUmax.knot.knotIndex); } return new KnotIndexStrictlyIncreasingSequence(index); } toIncreasingSeqOfAbscissae() { const incKnotAbscissae = []; for (const knot of this) { if (knot !== undefined) { for (let i = 0; i < knot.multiplicity; i++) { incKnotAbscissae.push(knot.abscissa); } } } return incKnotAbscissae; } revertKnotSequence() { const newKnotSequence = this.clone(); newKnotSequence.revertKnotSpacing(); return newKnotSequence; } decrementKnotMultiplicity(index, checkSequenceConsistency = true) { let newKnotSequence = this.clone(); newKnotSequence.decrementKnotMultiplicityMutSeq(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); } } newKnotSequence = new StrictlyIncreasingOpenKnotSequenceClosedCurve(this._maxMultiplicityOrder, { type: STRICTLYINCREASINGOPENKNOTSEQUENCECLOSEDCURVE, periodicKnots: periodicKnotAbscissae, multiplicities: periodicKnotMultiplicities }); } return newKnotSequence; } raiseKnotMultiplicity(index, multiplicity = 1, checkSequenceConsistency = true) { const newKnotSequence = this.clone(); newKnotSequence.raiseKnotMultiplicityKnotArrayMutSeq(index, multiplicity, checkSequenceConsistency); return newKnotSequence; } insertKnot(abscissae, times = 1) { const newKnotSequence = this.clone(); newKnotSequence.insertKnotAbscissaArrayMutSeq(abscissae, times); 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); } } const updatedSeq = new StrictlyIncreasingOpenKnotSequenceClosedCurve(this._maxMultiplicityOrder, { type: STRICTLYINCREASINGOPENKNOTSEQUENCECLOSEDCURVE, periodicKnots: periodicKnotAbscissae, multiplicities: periodicKnotMultiplicities }); this.knotSequence = previousKnotSequence; return updatedSeq; } } export { StrictlyIncreasingOpenKnotSequenceClosedCurve };