UNPKG

@mlightcad/geometry-engine

Version:

The geometry-engine package provides comprehensive geometric entities, mathematical operations, and transformations for 2D and 3D space. This package mimics AutoCAD ObjectARX's AcGe (Geometry) classes and provides the mathematical foundation for CAD opera

420 lines 17.5 kB
var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; import { AcCmErrors } from '@mlightcad/common'; import { AcGeBox3d, AcGePoint3d } from '../math'; import { calculateCurveLength, evaluateNurbsPoint, generateChordKnots, generateSqrtChordKnots, generateUniformKnots, interpolateControlPoints } from '../util'; import { AcGeCurve3d } from './AcGeCurve3d'; /** * Lightweight NURBS curve implementation */ var NurbsCurve = /** @class */ (function () { function NurbsCurve(degree, knots, controlPoints, weights) { this._degree = degree; this._knots = __spreadArray([], __read(knots), false); this._controlPoints = controlPoints.map(function (p) { return __spreadArray([], __read(p), false); }); this._weights = weights ? __spreadArray([], __read(weights), false) : new Array(controlPoints.length).fill(1.0); } NurbsCurve.prototype.degree = function () { return this._degree; }; NurbsCurve.prototype.knots = function () { return __spreadArray([], __read(this._knots), false); }; NurbsCurve.prototype.controlPoints = function () { return this._controlPoints.map(function (p) { return __spreadArray([], __read(p), false); }); }; NurbsCurve.prototype.weights = function () { return __spreadArray([], __read(this._weights), false); }; /** * Calculate a point on the curve at parameter u */ NurbsCurve.prototype.point = function (u) { return evaluateNurbsPoint(u, this._degree, this._knots, this._controlPoints, this._weights); }; /** * Calculate curve length using numerical integration */ NurbsCurve.prototype.length = function () { return calculateCurveLength(this._degree, this._knots, this._controlPoints, this._weights); }; /** * Create a NURBS curve from control points and knots */ NurbsCurve.byKnotsControlPointsWeights = function (degree, knots, controlPoints, weights) { return new NurbsCurve(degree, knots, controlPoints, weights); }; /** * Create a NURBS curve from fit points using interpolation */ NurbsCurve.byPoints = function (points, degree, parameterization) { if (parameterization === void 0) { parameterization = 'Uniform'; } // Generate knots based on parameterization type var knots; switch (parameterization) { case 'Chord': knots = generateChordKnots(degree, points); break; case 'SqrtChord': knots = generateSqrtChordKnots(degree, points); break; case 'Uniform': default: knots = generateUniformKnots(degree, points.length); break; } // Generate control points from fit points var controlPoints = interpolateControlPoints(points); var weights = new Array(controlPoints.length).fill(1.0); return new NurbsCurve(degree, knots, controlPoints, weights); }; return NurbsCurve; }()); var AcGeSpline3d = /** @class */ (function (_super) { __extends(AcGeSpline3d, _super); function AcGeSpline3d(a, b, c, d) { var _this = _super.call(this) || this; var argsLength = +(a !== undefined) + +(b !== undefined) + +(c !== undefined) + +(d !== undefined); if (argsLength < 2 || argsLength > 4) { throw AcCmErrors.ILLEGAL_PARAMETERS; } // For now, we support 3 degree only var degree = 3; _this._closed = d || false; if (!Array.isArray(b)) { // Constructor with fit points _this._fitPoints = a; _this._knotParameterization = b; // Handle closed parameter for fit points constructor if (argsLength >= 3) { _this._closed = c; } // Validate minimum number of fit points for degree 3 if (_this._fitPoints.length < 4) { throw AcCmErrors.ILLEGAL_PARAMETERS; } var points = _this.toNurbsPoints(_this._fitPoints); _this._nurbsCurve = NurbsCurve.byPoints(points, degree, _this._knotParameterization); _this._controlPoints = _this.toGePoints(_this._nurbsCurve.controlPoints()); // Store original data for potential reopening _this._originalControlPoints = __spreadArray([], __read(_this._controlPoints), false); _this._originalKnots = __spreadArray([], __read(_this._nurbsCurve.knots()), false); _this._originalWeights = __spreadArray([], __read(_this._nurbsCurve.weights()), false); } else { // Constructor with control points _this._controlPoints = a; // Handle closed parameter for control points constructor if (argsLength >= 4) { _this._closed = d; } // Validate minimum number of control points for degree 3 if (_this._controlPoints.length < 4) { throw AcCmErrors.ILLEGAL_PARAMETERS; } var points = _this.toNurbsPoints(_this._controlPoints); _this._nurbsCurve = NurbsCurve.byKnotsControlPointsWeights(degree, b, points, c); // Store original data for potential reopening _this._originalControlPoints = __spreadArray([], __read(_this._controlPoints), false); _this._originalKnots = __spreadArray([], __read(_this._nurbsCurve.knots()), false); _this._originalWeights = c ? __spreadArray([], __read(c), false) : new Array(_this._controlPoints.length).fill(1.0); } // Apply closed state if specified if (_this._closed) { _this.makeClosed(); } return _this; } /** * Set the closed property and rebuild the curve if necessary */ AcGeSpline3d.prototype.setClosed = function (closed) { if (this._closed === closed) { return; } this._closed = closed; this._boundingBoxNeedsUpdate = true; if (closed) { this.makeClosed(); } else { this.makeOpen(); } }; /** * Make the spline closed by adding control points and adjusting knots */ AcGeSpline3d.prototype.makeClosed = function () { var degree = this._nurbsCurve.degree(); var originalControlPoints = this._nurbsCurve.controlPoints(); var originalKnots = this._nurbsCurve.knots(); var originalWeights = this._nurbsCurve.weights(); // For a closed curve, we need to add control points at the end // that ensure the curve closes smoothly var closedControlPoints = __spreadArray([], __read(originalControlPoints), false); var closedWeights = __spreadArray([], __read(originalWeights), false); // Add control points to close the curve // For a degree 3 curve, we typically need 3 additional control points for (var i = 0; i < degree; i++) { // Use the first control point to ensure the curve closes closedControlPoints.push(__spreadArray([], __read(originalControlPoints[0]), false)); closedWeights.push(originalWeights[0]); } // Create new knot vector for closed curve var closedKnots = this.createClosedKnotVector(originalKnots, degree); // Create new NURBS curve this._nurbsCurve = NurbsCurve.byKnotsControlPointsWeights(degree, closedKnots, closedControlPoints, closedWeights); this._controlPoints = this.toGePoints(closedControlPoints); }; /** * Make the spline open by restoring the original curve */ AcGeSpline3d.prototype.makeOpen = function () { if (!this._originalControlPoints || !this._originalKnots || !this._originalWeights) { throw new Error('Original curve data not available'); } var degree = this._nurbsCurve.degree(); var originalPoints = this.toNurbsPoints(this._originalControlPoints); // Create new NURBS curve with original data this._nurbsCurve = NurbsCurve.byKnotsControlPointsWeights(degree, this._originalKnots, originalPoints, this._originalWeights); this._controlPoints = __spreadArray([], __read(this._originalControlPoints), false); }; /** * Create knot vector for closed curve */ AcGeSpline3d.prototype.createClosedKnotVector = function (originalKnots, degree) { // For a closed curve, we need to create a proper knot vector // that allows the curve to close smoothly // Start with the original knots var closedKnots = __spreadArray([], __read(originalKnots), false); // For a closed curve, we need to extend the knot vector // The key is to ensure that the curve can actually close var lastKnot = originalKnots[originalKnots.length - 1]; // Add knots for the additional control points // Use a spacing that ensures the curve closes properly var additionalKnots = degree; for (var i = 1; i <= additionalKnots; i++) { closedKnots.push(lastKnot + i); } return closedKnots; }; Object.defineProperty(AcGeSpline3d.prototype, "degree", { /** * Degree of the spline to be created. */ get: function () { return this._nurbsCurve.degree(); }, enumerable: false, configurable: true }); Object.defineProperty(AcGeSpline3d.prototype, "knotParameterization", { get: function () { return this._knotParameterization; }, enumerable: false, configurable: true }); Object.defineProperty(AcGeSpline3d.prototype, "startPoint", { /** * The start point of this spline */ get: function () { var knots = this._nurbsCurve.knots(); var degree = this._nurbsCurve.degree(); var startParam = knots[degree]; var startPoint = this._nurbsCurve.point(startParam); return new AcGePoint3d(startPoint[0], startPoint[1], startPoint[2]); }, enumerable: false, configurable: true }); Object.defineProperty(AcGeSpline3d.prototype, "endPoint", { /** * The end point of this spline */ get: function () { var knots = this._nurbsCurve.knots(); var degree = this._nurbsCurve.degree(); var endParam = knots[knots.length - degree - 1]; var endPoint = this._nurbsCurve.point(endParam); return new AcGePoint3d(endPoint[0], endPoint[1], endPoint[2]); }, enumerable: false, configurable: true }); Object.defineProperty(AcGeSpline3d.prototype, "length", { /** * @inheritdoc */ get: function () { return this._nurbsCurve.length(); }, enumerable: false, configurable: true }); /** * Return the value of the control point at position index in the list of control points. * If index is negative or more than the number of control points in the spline, then point * is set to the last control point. * @param index Input index (0 based) of point to get * @returns */ AcGeSpline3d.prototype.getFitPointAt = function (index) { if (!this._fitPoints) { throw new Error('No fit points in this spline'); } var length = this._fitPoints.length; var newIndex = index < 0 || index >= length ? length - 1 : index; var point = this._fitPoints[newIndex]; return { x: point.x, y: point.y, z: point.z || 0 }; }; /** * Return the value of the control point at position index in the list of control points. * If index is negative or more than the number of control points in the spline, then point * is set to the last control point. * @param index Input index (0 based) of point to get * @returns */ AcGeSpline3d.prototype.getControlPointAt = function (index) { var length = this._controlPoints.length; var newIndex = index < 0 || index >= length ? length - 1 : index; return this._controlPoints[newIndex]; }; /** * Divide this spline into the specified nubmer of points * those points as an array of points. * @param numPoints Input the nubmer of points returned * @returns Return an array of point */ AcGeSpline3d.prototype.getPoints = function (numPoints) { if (numPoints === void 0) { numPoints = 100; } var curve = this._nurbsCurve; var points = []; // Get the knot vector from the curve var knots = curve.knots(); // The valid parameter range is between knots[degree] and knots[knots.length - degree - 1] var degree = this._nurbsCurve.degree(); var startParam = knots[degree]; var endParam = knots[knots.length - degree - 1]; // Adjust step size for correct range var step = (endParam - startParam) / (numPoints - 1); for (var i = 0; i < numPoints; i++) { // For the last point, use endParam exactly to avoid floating-point issues var t = i === numPoints - 1 ? endParam : startParam + i * step; var point = curve.point(t); points.push(new AcGePoint3d(point[0], point[1], point[2])); } return points; }; AcGeSpline3d.prototype.getCurvePoints = function (curve, count) { var points = []; var knots = curve.knots(); // Get the knot vector from the curve // The valid parameter range is between knots[degree] and knots[knots.length - degree - 1] var startParam = knots[3]; var endParam = knots[knots.length - 4]; var step = (endParam - startParam) / (count - 1); // Adjust step size for correct range for (var i = 0; i < count; i++) { var t = startParam + i * step; // Map t to the correct parameter space points.push(curve.point(t)); // Sample the curve at the mapped parameter t } return points; }; /** * @inheritdoc */ AcGeSpline3d.prototype.calculateBoundingBox = function () { var points = this.getPoints(100); return new AcGeBox3d().setFromPoints(points); }; Object.defineProperty(AcGeSpline3d.prototype, "closed", { get: function () { return this._closed; }, set: function (value) { this.setClosed(value); }, enumerable: false, configurable: true }); /** * @inheritdoc */ AcGeSpline3d.prototype.transform = function (_matrix) { // TODO: Implement this method this._boundingBoxNeedsUpdate = true; return this; }; /** * Convert input points to points in NURBS format * @param points Input points to convert * @returns Return converted points */ AcGeSpline3d.prototype.toNurbsPoints = function (points) { var nurbsPoints = new Array(points.length); points.forEach(function (point, index) { nurbsPoints[index] = [point.x, point.y, point.z || 0]; }); return nurbsPoints; }; /** * Convert input points to points in geometry engine format * @param points Input points to convert * @returns Return converted points */ AcGeSpline3d.prototype.toGePoints = function (points) { var gePoints = new Array(points.length); points.forEach(function (point, index) { gePoints[index] = { x: point[0], y: point[1], z: point[2] }; }); return gePoints; }; return AcGeSpline3d; }(AcGeCurve3d)); export { AcGeSpline3d }; //# sourceMappingURL=AcGeSpline3d.js.map