@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
586 lines • 22.5 kB
JavaScript
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 __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
import { AcCmErrors } from '@mlightcad/common';
import { AcGeBox3d, AcGePlane, AcGePoint3d, AcGeVector3d } from '../math';
import { AcGeMathUtil, ORIGIN_POINT_3D, TAU } from '../util';
import { AcGeCurve3d } from './AcGeCurve3d';
import { AcGeLine3d } from './AcGeLine3d';
/**
* The class represeting both full circles and circular arcs in 3d space. The ellipse arc is
* defined by a center point, radius, start angle, end angle, a normal vector, and a reference
* vector. If start angle is equal to 0 and end angle is equal to 2 * Math.PI, it represents
* a full circle.
*/
var AcGeCircArc3d = /** @class */ (function (_super) {
__extends(AcGeCircArc3d, _super);
/**
* Create a 3d circular arc.
* @param center The center point of the arc.
* @param radius The radius of the arc.
* @param startAngle The start angle of the arc.
* @param endAngle The end angle of the arc.
* @param normal The normal vector of the plane in which the arc lies.
* @param refVec The reference vector from which angles are measured. Default value is x axis.
*/
function AcGeCircArc3d(center, radius, startAngle, endAngle, normal, refVec) {
if (refVec === void 0) { refVec = AcGeVector3d.X_AXIS; }
var _this = _super.call(this) || this;
_this.center = center;
_this.radius = radius;
_this.startAngle = startAngle;
_this.endAngle = endAngle;
_this.normal = normal;
_this.refVec = refVec;
// Check whether it is a full ellipse
if ((endAngle - startAngle) % TAU == 0) {
_this.startAngle = 0;
_this.endAngle = TAU;
}
else {
_this.startAngle = startAngle;
_this.endAngle = endAngle;
}
return _this;
}
/**
* Compute center point of the arc given three points
* @param startPoint Input start point of the arc
* @param endPoint Input end point of the arc
* @param pointOnArc Input one point on the arc (P3)
* @returns Return center point of the arc
*/
AcGeCircArc3d.computeCenterPoint = function (startPoint, endPoint, pointOnArc) {
// Midpoints of the edges
var mid1 = new AcGeVector3d()
.addVectors(startPoint, endPoint)
.multiplyScalar(0.5);
var mid2 = new AcGeVector3d()
.addVectors(startPoint, pointOnArc)
.multiplyScalar(0.5);
// Vectors perpendicular to the edges
var vec1 = new AcGeVector3d().subVectors(endPoint, startPoint);
var vec2 = new AcGeVector3d().subVectors(pointOnArc, startPoint);
// Normal vector to the plane formed by the triangle
var normal = new AcGeVector3d().crossVectors(vec1, vec2).normalize();
if (normal.lengthSq() === 0) {
// If the points are collinear, the normal vector will have zero length.
console.error('Points are collinear and cannot form a valid arc.');
return null;
}
// Compute perpendicular vectors on the plane of the triangle
var perpendicular1 = new AcGeVector3d()
.crossVectors(vec1, normal)
.normalize();
var perpendicular2 = new AcGeVector3d()
.crossVectors(vec2, normal)
.normalize();
// Solve the system of equations to find the intersection point (center of the circle)
var direction1 = perpendicular1
.clone()
.multiplyScalar(Number.MAX_SAFE_INTEGER);
var direction2 = perpendicular2
.clone()
.multiplyScalar(Number.MAX_SAFE_INTEGER);
var line1 = new AcGeLine3d(mid1, mid1.clone().add(direction1));
var line2 = new AcGeLine3d(mid2, mid2.clone().add(direction2));
var center = new AcGeVector3d();
var result = line1.closestPointToPoint(line2.startPoint, true, center);
if (!result) {
console.error('Cannot find a valid center for the arc.');
return null;
}
return center;
};
/**
* Create arc by three points
* @param startPoint Input the start point
* @param endPoint Input the end point
* @param pointOnArc Input one point between the start point and the end point
*/
AcGeCircArc3d.createByThreePoints = function (startPoint, endPoint, pointOnArc) {
var center = AcGeCircArc3d.computeCenterPoint(startPoint, endPoint, pointOnArc);
if (center) {
var radius = center.distanceTo(startPoint);
// Compute the vectors from the center to the start and end points
var centerToStart = new AcGeVector3d().subVectors(startPoint, center);
var centerToEnd = new AcGeVector3d().subVectors(endPoint, center);
// Compute the start angle and end angle relative to the x-axis
var startAngle = Math.atan2(centerToStart.y, centerToStart.x);
var endAngle = Math.atan2(centerToEnd.y, centerToEnd.x);
return new AcGeCircArc3d(center, radius, startAngle, endAngle, AcGeVector3d.Z_AXIS);
}
};
Object.defineProperty(AcGeCircArc3d.prototype, "center", {
/**
* Center of circular arc
*/
get: function () {
return this._center;
},
set: function (value) {
this._center = new AcGePoint3d(value.x, value.y, value.z || 0);
this._boundingBoxNeedsUpdate = true;
},
enumerable: false,
configurable: true
});
Object.defineProperty(AcGeCircArc3d.prototype, "radius", {
/**
* Radius of circular arc
*/
get: function () {
return this._radius;
},
set: function (value) {
if (value < 0)
throw AcCmErrors.ILLEGAL_PARAMETERS;
this._radius = value;
this._boundingBoxNeedsUpdate = true;
},
enumerable: false,
configurable: true
});
Object.defineProperty(AcGeCircArc3d.prototype, "startAngle", {
/**
* Start angle in radians of circular arc in the range 0 to 2 * PI.
*/
get: function () {
return this._startAngle;
},
set: function (value) {
this._startAngle = AcGeMathUtil.normalizeAngle(value);
this._boundingBoxNeedsUpdate = true;
},
enumerable: false,
configurable: true
});
Object.defineProperty(AcGeCircArc3d.prototype, "endAngle", {
/**
* End angle in radians of circular arc in the range 0 to 2 * PI.
*/
get: function () {
return this._endAngle;
},
set: function (value) {
this._endAngle =
this.startAngle == 0 && value == TAU
? value
: AcGeMathUtil.normalizeAngle(value);
this._boundingBoxNeedsUpdate = true;
},
enumerable: false,
configurable: true
});
Object.defineProperty(AcGeCircArc3d.prototype, "deltaAngle", {
/**
* Return angle between endAngle and startAngle in range 0 to 2*PI
*/
get: function () {
return AcGeMathUtil.normalizeAngle(this.endAngle - this.startAngle);
},
enumerable: false,
configurable: true
});
Object.defineProperty(AcGeCircArc3d.prototype, "isLargeArc", {
/**
* Return true if the arc is a large arc whose delta angle value is greater than PI.
*/
get: function () {
return Math.abs(this.deltaAngle) > Math.PI ? 1 : 0;
},
enumerable: false,
configurable: true
});
Object.defineProperty(AcGeCircArc3d.prototype, "clockwise", {
/**
* Return true if the arc is clockwise from startAngle to endAngle
*/
get: function () {
return this.deltaAngle <= 0;
},
enumerable: false,
configurable: true
});
Object.defineProperty(AcGeCircArc3d.prototype, "normal", {
/**
* Normal vector defining the plane of the circular arc
*/
get: function () {
return this._normal;
},
set: function (value) {
this._normal = new AcGeVector3d(value.x, value.y, value.z);
// Normalize the normal vector to ensure it's a unit vector
this._normal.normalize();
this._boundingBoxNeedsUpdate = true;
},
enumerable: false,
configurable: true
});
Object.defineProperty(AcGeCircArc3d.prototype, "refVec", {
/**
* The unit reference vector of circular arc
*/
get: function () {
return this._refVec;
},
set: function (value) {
this._refVec = new AcGeVector3d(value.x, value.y, value.z);
// Normalize the normal vector to ensure it's a unit vector
this._refVec.normalize();
this._boundingBoxNeedsUpdate = true;
},
enumerable: false,
configurable: true
});
Object.defineProperty(AcGeCircArc3d.prototype, "startPoint", {
/**
* The start point of circular arc
*/
get: function () {
return this.getPointAtAngle(this._startAngle);
},
enumerable: false,
configurable: true
});
Object.defineProperty(AcGeCircArc3d.prototype, "endPoint", {
/**
* The end point of circular arc
*/
get: function () {
return this.getPointAtAngle(this._endAngle);
},
enumerable: false,
configurable: true
});
Object.defineProperty(AcGeCircArc3d.prototype, "midPoint", {
/**
* The middle point of the circular arc
*/
get: function () {
var startAngle = this.startAngle;
var deltaAngle = this.deltaAngle;
// For a full circle, define midpoint at PI from start
if (this.closed) {
startAngle = 0;
deltaAngle = TAU;
}
var midAngle = startAngle + deltaAngle * 0.5;
return this.getPointAtAngle(midAngle);
},
enumerable: false,
configurable: true
});
Object.defineProperty(AcGeCircArc3d.prototype, "length", {
/**
* @inheritdoc
*/
get: function () {
return this.closed
? 2 * Math.PI * this.radius
: Math.abs(this.deltaAngle * this.radius);
},
enumerable: false,
configurable: true
});
Object.defineProperty(AcGeCircArc3d.prototype, "area", {
/**
* The area of this arc
*/
get: function () {
return this.closed
? Math.PI * this.radius * this.radius
: Math.abs(this.deltaAngle * this.radius * this.radius);
},
enumerable: false,
configurable: true
});
/**
* Returns the nerest point on this arc to the given point.
* @param point Input point
*/
AcGeCircArc3d.prototype.nearestPoint = function (point) {
var p = new AcGeVector3d(point.x, point.y, point.z || 0);
var c = this.center;
var n = this.normal;
// 1. Project point onto arc plane
var v = p.clone().sub(c);
var distToPlane = v.dot(n);
var projected = p.clone().sub(n.clone().multiplyScalar(distToPlane));
// 2. Direction from center to projected point
var dir = projected.clone().sub(c);
if (dir.lengthSq() === 0) {
// Degenerate: point is at center
return this.startPoint.clone();
}
dir.normalize().multiplyScalar(this.radius);
// 3. Candidate point on full circle
var circlePoint = c.clone().add(dir);
// 4. Angle of candidate
var angle = this.getAngle(circlePoint.clone());
// Normalize angle relative to startAngle
var start = this.startAngle;
var delta = this.deltaAngle;
var t = AcGeMathUtil.normalizeAngle(angle - start);
// 5. Clamp to arc range
if (t < 0)
t = 0;
if (t > delta)
t = delta;
var arcPoint = this.getPointAtAngle(start + t);
// 6. Compare with endpoints (important!)
var dArc = arcPoint.distanceTo(p);
var dStart = this.startPoint.distanceTo(p);
var dEnd = this.endPoint.distanceTo(p);
if (dStart < dArc && dStart <= dEnd)
return this.startPoint.clone();
if (dEnd < dArc && dEnd < dStart)
return this.endPoint.clone();
return arcPoint;
};
/**
* Returns tangent snap point(s) from a given point to this arc.
* @param point Input point
* @returns Array of tangent points on the arc
*/
AcGeCircArc3d.prototype.tangentPoints = function (point) {
var e_1, _a;
var result = [];
var P = new AcGeVector3d(point.x, point.y, point.z || 0);
var C = this.center;
var n = this.normal;
var r = this.radius;
// Vector CP
var v = P.clone().sub(C);
// Distance from point to arc plane
var distToPlane = v.dot(n);
// Project point onto arc plane
var Pp = P.clone().sub(n.clone().multiplyScalar(distToPlane));
var Cp = C.clone();
var dVec = Pp.clone().sub(Cp);
var d = dVec.length();
// No tangent if point is inside the circle
if (d < r)
return result;
// Angle between CP and tangent line
var alpha = Math.acos(r / d);
// Base angle from refVec
var baseAngle = this.getAngle(Pp.clone());
// Two tangent angles on the full circle
var angles = [baseAngle + alpha, baseAngle - alpha];
try {
for (var angles_1 = __values(angles), angles_1_1 = angles_1.next(); !angles_1_1.done; angles_1_1 = angles_1.next()) {
var angle = angles_1_1.value;
// Normalize angle into arc parameter space
var t = AcGeMathUtil.normalizeAngle(angle - this.startAngle);
// Check if tangent lies on the arc
if (t >= 0 && t <= this.deltaAngle) {
result.push(this.getPointAtAngle(this.startAngle + t));
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (angles_1_1 && !angles_1_1.done && (_a = angles_1.return)) _a.call(angles_1);
}
finally { if (e_1) throw e_1.error; }
}
return result;
};
/**
* Returns the nearest tangent snap point, or null if none exists.
*/
AcGeCircArc3d.prototype.nearestTangentPoint = function (point) {
var tangents = this.tangentPoints(point);
if (tangents.length === 0)
return null;
var P = new AcGePoint3d(point.x, point.y, point.z || 0);
if (tangents.length === 1)
return tangents[0];
return tangents[0].distanceTo(P) < tangents[1].distanceTo(P)
? tangents[0]
: tangents[1];
};
/**
* @inheritdoc
*/
AcGeCircArc3d.prototype.calculateBoundingBox = function () {
var e_2, _a;
var angles = [this.startAngle, this.endAngle];
for (var i = 0; i < 2 * Math.PI; i += Math.PI / 2) {
if (AcGeMathUtil.isBetweenAngle(i, this.startAngle, this.endAngle)) {
angles.push(i);
}
}
var minX = Infinity, minY = Infinity, minZ = Infinity;
var maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
try {
for (var angles_2 = __values(angles), angles_2_1 = angles_2.next(); !angles_2_1.done; angles_2_1 = angles_2.next()) {
var angle = angles_2_1.value;
var point = this.getPointAtAngle(angle);
if (point.x < minX)
minX = point.x;
if (point.y < minY)
minY = point.y;
if (point.z < minZ)
minZ = point.z;
if (point.x > maxX)
maxX = point.x;
if (point.y > maxY)
maxY = point.y;
if (point.z > maxZ)
maxZ = point.z;
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (angles_2_1 && !angles_2_1.done && (_a = angles_2.return)) _a.call(angles_2);
}
finally { if (e_2) throw e_2.error; }
}
return new AcGeBox3d({ x: minX, y: minY, z: minZ }, { x: maxX, y: maxY, z: maxZ });
};
Object.defineProperty(AcGeCircArc3d.prototype, "closed", {
/**
* Return true if its start point is identical to its end point. Otherwise, return false.
*/
get: function () {
return (Math.abs(this.endAngle - this.startAngle) / Math.PI) % 2 == 0;
},
enumerable: false,
configurable: true
});
/**
* Divide this arc 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
*/
AcGeCircArc3d.prototype.getPoints = function (numPoints) {
var points = [];
var deltaAngle = this.deltaAngle;
var startAngle = this.startAngle;
if (this.closed) {
deltaAngle = TAU;
startAngle = 0;
}
for (var i = 0; i <= numPoints; i++) {
var angle = startAngle + deltaAngle * (i / numPoints);
var point = this.getPointAtAngle(angle);
points.push(point);
}
return points;
};
/**
* @inheritdoc
*/
AcGeCircArc3d.prototype.transform = function (matrix) {
var startVec = _vector3
.copy(this.refVec)
.applyAxisAngle(this.normal, this.startAngle)
.multiplyScalar(this.radius);
var endVec = _vector3
.copy(this.refVec)
.applyAxisAngle(this.normal, this.endAngle)
.multiplyScalar(this.radius);
this.center.applyMatrix4(matrix);
startVec.applyMatrix4(matrix);
endVec.applyMatrix4(matrix);
this.normal.applyMatrix4(matrix).normalize();
this.refVec.applyMatrix4(matrix).normalize();
this.startAngle = this.getAngle(startVec);
this.endAngle = this.getAngle(endVec);
this._boundingBoxNeedsUpdate = true;
return this;
};
/**
* @inheritdoc
*/
AcGeCircArc3d.prototype.copy = function (value) {
this.center = value.center;
this.radius = value.radius;
this.startAngle = value.startAngle;
this.endAngle = value.endAngle;
this.normal = value.normal;
this.refVec = value.refVec;
this._boundingBoxNeedsUpdate = true;
return this;
};
/**
* @inheritdoc
*/
AcGeCircArc3d.prototype.clone = function () {
return new AcGeCircArc3d(this.center.clone(), this.radius, this.startAngle, this.endAngle, this.normal, this.refVec);
};
/**
* Calculate angle between the specified vec and the reference vector of this arc.
* @param vec Input one vector
* @returns Return angle between the specified vec and the reference vector of this arc.
*/
AcGeCircArc3d.prototype.getAngle = function (vec) {
vec.sub(this.center); // 转换到以圆心为中心的坐标系
return Math.atan2(vec.dot(_vector3.crossVectors(this.refVec, this.normal)), vec.dot(this.refVec));
};
/**
* Returns the point on the arc at a specific angle.
* @param angle The angle at which to get the point.
*/
AcGeCircArc3d.prototype.getPointAtAngle = function (angle) {
// Calculate orthogonal vector to refVec in the plane of the arc
var n = this.normal;
var r = this.refVec;
var orthogonalVec = {
x: n.y * r.z - n.z * r.y,
y: n.z * r.x - n.x * r.z,
z: n.x * r.y - n.y * r.x
};
// Point on the arc at the given angle
var center = this.center;
var radius = this.radius;
return new AcGePoint3d(center.x +
radius * (r.x * Math.cos(angle) + orthogonalVec.x * Math.sin(angle)), center.y +
radius * (r.y * Math.cos(angle) + orthogonalVec.y * Math.sin(angle)), center.z +
radius * (r.z * Math.cos(angle) + orthogonalVec.z * Math.sin(angle)));
};
Object.defineProperty(AcGeCircArc3d.prototype, "plane", {
/**
* Return the plane in which the circular arc lies.
*/
get: function () {
var distance = new AcGeVector3d(this.center).distanceTo(ORIGIN_POINT_3D);
return new AcGePlane(this.normal, distance);
},
enumerable: false,
configurable: true
});
return AcGeCircArc3d;
}(AcGeCurve3d));
export { AcGeCircArc3d };
var _vector3 = /*@__PURE__*/ new AcGeVector3d();
//# sourceMappingURL=AcGeCircArc3d.js.map