@bitbybit-dev/base 0.20.2 → 0.20.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/lib/api/inputs/base-inputs.d.ts +8 -0
- package/lib/api/inputs/index.d.ts +3 -0
- package/lib/api/inputs/index.js +3 -0
- package/lib/api/inputs/inputs.d.ts +3 -0
- package/lib/api/inputs/inputs.js +3 -0
- package/lib/api/inputs/line-inputs.d.ts +240 -0
- package/lib/api/inputs/line-inputs.js +247 -0
- package/lib/api/inputs/mesh-inputs.d.ts +82 -0
- package/lib/api/inputs/mesh-inputs.js +83 -0
- package/lib/api/inputs/point-inputs.d.ts +153 -0
- package/lib/api/inputs/point-inputs.js +188 -0
- package/lib/api/inputs/polyline-inputs.d.ts +206 -0
- package/lib/api/inputs/polyline-inputs.js +229 -0
- package/lib/api/inputs/text-inputs.d.ts +1 -1
- package/lib/api/inputs/transforms-inputs.d.ts +18 -0
- package/lib/api/inputs/transforms-inputs.js +29 -0
- package/lib/api/inputs/vector-inputs.d.ts +8 -0
- package/lib/api/inputs/vector-inputs.js +8 -0
- package/lib/api/models/index.d.ts +1 -0
- package/lib/api/models/index.js +1 -0
- package/lib/api/models/point/bucket.d.ts +1 -0
- package/lib/api/models/point/bucket.js +1 -0
- package/lib/api/models/point/hex-grid-data.d.ts +8 -0
- package/lib/api/models/point/hex-grid-data.js +2 -0
- package/lib/api/models/point/index.d.ts +1 -0
- package/lib/api/models/point/index.js +1 -0
- package/lib/api/services/dates.js +45 -15
- package/lib/api/services/index.d.ts +3 -0
- package/lib/api/services/index.js +3 -0
- package/lib/api/services/line.d.ts +158 -0
- package/lib/api/services/line.js +334 -0
- package/lib/api/services/lists.d.ts +1 -1
- package/lib/api/services/lists.js +1 -2
- package/lib/api/services/mesh.d.ts +66 -0
- package/lib/api/services/mesh.js +235 -0
- package/lib/api/services/point.d.ts +96 -1
- package/lib/api/services/point.js +540 -1
- package/lib/api/services/polyline.d.ts +149 -0
- package/lib/api/services/polyline.js +446 -0
- package/lib/api/services/transforms.d.ts +26 -1
- package/lib/api/services/transforms.js +66 -3
- package/lib/api/services/vector.d.ts +18 -0
- package/lib/api/services/vector.js +27 -0
- package/lib/api/unit-test-helper.d.ts +20 -0
- package/lib/api/unit-test-helper.js +130 -0
- package/package.json +2 -2
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { GeometryHelper } from "./geometry-helper";
|
|
2
|
+
import * as Inputs from "../inputs";
|
|
3
|
+
import { Point } from "./point";
|
|
4
|
+
import { Vector } from "./vector";
|
|
5
|
+
import { Line } from "./line";
|
|
6
|
+
/**
|
|
7
|
+
* Contains various methods for polyline. Polyline in bitbybit is a simple object that has points property containing an array of points.
|
|
8
|
+
* { points: number[][] }
|
|
9
|
+
*/
|
|
10
|
+
export declare class Polyline {
|
|
11
|
+
private readonly vector;
|
|
12
|
+
private readonly point;
|
|
13
|
+
private readonly line;
|
|
14
|
+
private readonly geometryHelper;
|
|
15
|
+
constructor(vector: Vector, point: Point, line: Line, geometryHelper: GeometryHelper);
|
|
16
|
+
/**
|
|
17
|
+
* Gets the length of the polyline
|
|
18
|
+
* @param inputs a polyline
|
|
19
|
+
* @returns length
|
|
20
|
+
* @group get
|
|
21
|
+
* @shortname polyline length
|
|
22
|
+
* @drawable false
|
|
23
|
+
*/
|
|
24
|
+
length(inputs: Inputs.Polyline.PolylineDto): number;
|
|
25
|
+
/**
|
|
26
|
+
* Gets the number of points in the polyline
|
|
27
|
+
* @param inputs a polyline
|
|
28
|
+
* @returns nr of points
|
|
29
|
+
* @group get
|
|
30
|
+
* @shortname nr polyline points
|
|
31
|
+
* @drawable false
|
|
32
|
+
*/
|
|
33
|
+
countPoints(inputs: Inputs.Polyline.PolylineDto): number;
|
|
34
|
+
/**
|
|
35
|
+
* Gets the points of the polyline
|
|
36
|
+
* @param inputs a polyline
|
|
37
|
+
* @returns points
|
|
38
|
+
* @group get
|
|
39
|
+
* @shortname points
|
|
40
|
+
* @drawable true
|
|
41
|
+
*/
|
|
42
|
+
getPoints(inputs: Inputs.Polyline.PolylineDto): Inputs.Base.Point3[];
|
|
43
|
+
/**
|
|
44
|
+
* Reverse the points of the polyline
|
|
45
|
+
* @param inputs a polyline
|
|
46
|
+
* @returns reversed polyline
|
|
47
|
+
* @group convert
|
|
48
|
+
* @shortname reverse polyline
|
|
49
|
+
* @drawable true
|
|
50
|
+
*/
|
|
51
|
+
reverse(inputs: Inputs.Polyline.PolylineDto): Inputs.Polyline.PolylinePropertiesDto;
|
|
52
|
+
/**
|
|
53
|
+
* Transform the polyline
|
|
54
|
+
* @param inputs a polyline
|
|
55
|
+
* @returns transformed polyline
|
|
56
|
+
* @group transforms
|
|
57
|
+
* @shortname transform polyline
|
|
58
|
+
* @drawable true
|
|
59
|
+
*/
|
|
60
|
+
transformPolyline(inputs: Inputs.Polyline.TransformPolylineDto): Inputs.Polyline.PolylinePropertiesDto;
|
|
61
|
+
/**
|
|
62
|
+
* Create the polyline
|
|
63
|
+
* @param inputs points and info if its closed
|
|
64
|
+
* @returns polyline
|
|
65
|
+
* @group create
|
|
66
|
+
* @shortname polyline
|
|
67
|
+
* @drawable true
|
|
68
|
+
*/
|
|
69
|
+
create(inputs: Inputs.Polyline.PolylineCreateDto): Inputs.Polyline.PolylinePropertiesDto;
|
|
70
|
+
/**
|
|
71
|
+
* Create the lines from the polyline
|
|
72
|
+
* @param inputs polyline
|
|
73
|
+
* @returns lines
|
|
74
|
+
* @group convert
|
|
75
|
+
* @shortname polyline to lines
|
|
76
|
+
* @drawable true
|
|
77
|
+
*/
|
|
78
|
+
polylineToLines(inputs: Inputs.Polyline.PolylineDto): Inputs.Base.Line3[];
|
|
79
|
+
/**
|
|
80
|
+
* Create the segments from the polyline
|
|
81
|
+
* @param inputs polyline
|
|
82
|
+
* @returns segments
|
|
83
|
+
* @group convert
|
|
84
|
+
* @shortname polyline to segments
|
|
85
|
+
* @drawable false
|
|
86
|
+
*/
|
|
87
|
+
polylineToSegments(inputs: Inputs.Polyline.PolylineDto): Inputs.Base.Segment3[];
|
|
88
|
+
/**
|
|
89
|
+
* Finds the points of self intersection of the polyline
|
|
90
|
+
* @param inputs points of self intersection
|
|
91
|
+
* @returns polyline
|
|
92
|
+
* @group intersections
|
|
93
|
+
* @shortname polyline self intersections
|
|
94
|
+
* @drawable true
|
|
95
|
+
*/
|
|
96
|
+
polylineSelfIntersection(inputs: Inputs.Polyline.PolylineToleranceDto): Inputs.Base.Point3[];
|
|
97
|
+
/**
|
|
98
|
+
* Finds the intersection points between two polylines
|
|
99
|
+
* @param inputs two polylines and tolerance
|
|
100
|
+
* @returns points
|
|
101
|
+
* @group intersection
|
|
102
|
+
* @shortname two polyline intersection
|
|
103
|
+
* @drawable true
|
|
104
|
+
*/
|
|
105
|
+
twoPolylineIntersection(inputs: Inputs.Polyline.TwoPolylinesToleranceDto): Inputs.Base.Point3[];
|
|
106
|
+
/**
|
|
107
|
+
* Create the polylines from segments that are potentially connected but scrambled randomly
|
|
108
|
+
* @param inputs segments
|
|
109
|
+
* @returns polylines
|
|
110
|
+
* @group sort
|
|
111
|
+
* @shortname segments to polylines
|
|
112
|
+
* @drawable true
|
|
113
|
+
*/
|
|
114
|
+
sortSegmentsIntoPolylines(inputs: Inputs.Polyline.SegmentsToleranceDto): Inputs.Base.Polyline3[];
|
|
115
|
+
/**
|
|
116
|
+
* Calculates the maximum possible half-line fillet radius for each corner
|
|
117
|
+
* of a given polyline. For a closed polyline, it includes the corners
|
|
118
|
+
* connecting the last segment back to the first.
|
|
119
|
+
*
|
|
120
|
+
* The calculation uses the 'half-line' constraint, meaning the fillet's
|
|
121
|
+
* tangent points must lie within the first half of each segment connected
|
|
122
|
+
* to the corner.
|
|
123
|
+
*
|
|
124
|
+
* @param inputs Defines the polyline points, whether it's closed, and an optional tolerance.
|
|
125
|
+
* @returns An array containing the maximum fillet radius calculated for each corner.
|
|
126
|
+
* The order corresponds to corners P[1]...P[n-2] for open polylines,
|
|
127
|
+
* and P[1]...P[n-2], P[0], P[n-1] for closed polylines.
|
|
128
|
+
* Returns an empty array if the polyline has fewer than 3 points.
|
|
129
|
+
* @group fillet
|
|
130
|
+
* @shortname polyline max fillet radii
|
|
131
|
+
* @drawable false
|
|
132
|
+
*/
|
|
133
|
+
maxFilletsHalfLine(inputs: Inputs.Polyline.PolylineToleranceDto): number[];
|
|
134
|
+
/**
|
|
135
|
+
* Calculates the single safest maximum fillet radius that can be applied
|
|
136
|
+
* uniformly to all corners of a polyline, based on the 'half-line' constraint.
|
|
137
|
+
* This is determined by finding the minimum of the maximum possible fillet
|
|
138
|
+
* radii calculated for each individual corner.
|
|
139
|
+
*
|
|
140
|
+
* @param inputs Defines the polyline points, whether it's closed, and an optional tolerance.
|
|
141
|
+
* @returns The smallest value from the results of calculatePolylineMaxFillets.
|
|
142
|
+
* Returns 0 if the polyline has fewer than 3 points or if any
|
|
143
|
+
* calculated maximum radius is 0.
|
|
144
|
+
* @group fillet
|
|
145
|
+
* @shortname polyline safest fillet radius
|
|
146
|
+
* @drawable false
|
|
147
|
+
*/
|
|
148
|
+
safestFilletRadius(inputs: Inputs.Polyline.PolylineToleranceDto): number;
|
|
149
|
+
}
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contains various methods for polyline. Polyline in bitbybit is a simple object that has points property containing an array of points.
|
|
3
|
+
* { points: number[][] }
|
|
4
|
+
*/
|
|
5
|
+
export class Polyline {
|
|
6
|
+
constructor(vector, point, line, geometryHelper) {
|
|
7
|
+
this.vector = vector;
|
|
8
|
+
this.point = point;
|
|
9
|
+
this.line = line;
|
|
10
|
+
this.geometryHelper = geometryHelper;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Gets the length of the polyline
|
|
14
|
+
* @param inputs a polyline
|
|
15
|
+
* @returns length
|
|
16
|
+
* @group get
|
|
17
|
+
* @shortname polyline length
|
|
18
|
+
* @drawable false
|
|
19
|
+
*/
|
|
20
|
+
length(inputs) {
|
|
21
|
+
let distanceOfPolyline = 0;
|
|
22
|
+
for (let i = 1; i < inputs.polyline.points.length; i++) {
|
|
23
|
+
const previousPoint = inputs.polyline.points[i - 1];
|
|
24
|
+
const currentPoint = inputs.polyline.points[i];
|
|
25
|
+
distanceOfPolyline += this.point.distance({ startPoint: previousPoint, endPoint: currentPoint });
|
|
26
|
+
}
|
|
27
|
+
return distanceOfPolyline;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Gets the number of points in the polyline
|
|
31
|
+
* @param inputs a polyline
|
|
32
|
+
* @returns nr of points
|
|
33
|
+
* @group get
|
|
34
|
+
* @shortname nr polyline points
|
|
35
|
+
* @drawable false
|
|
36
|
+
*/
|
|
37
|
+
countPoints(inputs) {
|
|
38
|
+
return inputs.polyline.points.length;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Gets the points of the polyline
|
|
42
|
+
* @param inputs a polyline
|
|
43
|
+
* @returns points
|
|
44
|
+
* @group get
|
|
45
|
+
* @shortname points
|
|
46
|
+
* @drawable true
|
|
47
|
+
*/
|
|
48
|
+
getPoints(inputs) {
|
|
49
|
+
return inputs.polyline.points;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Reverse the points of the polyline
|
|
53
|
+
* @param inputs a polyline
|
|
54
|
+
* @returns reversed polyline
|
|
55
|
+
* @group convert
|
|
56
|
+
* @shortname reverse polyline
|
|
57
|
+
* @drawable true
|
|
58
|
+
*/
|
|
59
|
+
reverse(inputs) {
|
|
60
|
+
return { points: inputs.polyline.points.reverse() };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Transform the polyline
|
|
64
|
+
* @param inputs a polyline
|
|
65
|
+
* @returns transformed polyline
|
|
66
|
+
* @group transforms
|
|
67
|
+
* @shortname transform polyline
|
|
68
|
+
* @drawable true
|
|
69
|
+
*/
|
|
70
|
+
transformPolyline(inputs) {
|
|
71
|
+
const transformation = inputs.transformation;
|
|
72
|
+
let transformedControlPoints = inputs.polyline.points;
|
|
73
|
+
transformedControlPoints = this.geometryHelper.transformControlPoints(transformation, transformedControlPoints);
|
|
74
|
+
return { points: transformedControlPoints };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Create the polyline
|
|
78
|
+
* @param inputs points and info if its closed
|
|
79
|
+
* @returns polyline
|
|
80
|
+
* @group create
|
|
81
|
+
* @shortname polyline
|
|
82
|
+
* @drawable true
|
|
83
|
+
*/
|
|
84
|
+
create(inputs) {
|
|
85
|
+
var _a;
|
|
86
|
+
return {
|
|
87
|
+
points: inputs.points,
|
|
88
|
+
isClosed: (_a = inputs.isClosed) !== null && _a !== void 0 ? _a : false,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Create the lines from the polyline
|
|
93
|
+
* @param inputs polyline
|
|
94
|
+
* @returns lines
|
|
95
|
+
* @group convert
|
|
96
|
+
* @shortname polyline to lines
|
|
97
|
+
* @drawable true
|
|
98
|
+
*/
|
|
99
|
+
polylineToLines(inputs) {
|
|
100
|
+
const segments = this.polylineToSegments(inputs);
|
|
101
|
+
return segments.map((segment) => ({
|
|
102
|
+
start: segment[0],
|
|
103
|
+
end: segment[1],
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Create the segments from the polyline
|
|
108
|
+
* @param inputs polyline
|
|
109
|
+
* @returns segments
|
|
110
|
+
* @group convert
|
|
111
|
+
* @shortname polyline to segments
|
|
112
|
+
* @drawable false
|
|
113
|
+
*/
|
|
114
|
+
polylineToSegments(inputs) {
|
|
115
|
+
const polyline = inputs.polyline;
|
|
116
|
+
const segments = [];
|
|
117
|
+
const points = polyline.points;
|
|
118
|
+
const numPoints = points.length;
|
|
119
|
+
if (numPoints < 2) {
|
|
120
|
+
return segments;
|
|
121
|
+
}
|
|
122
|
+
// Create segments between consecutive points
|
|
123
|
+
for (let i = 0; i < numPoints - 1; i++) {
|
|
124
|
+
segments.push([points[i], points[i + 1]]);
|
|
125
|
+
}
|
|
126
|
+
// Add closing segment if the polyline is closed and has enough points
|
|
127
|
+
if (polyline.isClosed && numPoints >= 2) {
|
|
128
|
+
if (!this.point.twoPointsAlmostEqual({ point1: points[numPoints - 1], point2: points[0], tolerance: 1e-9 })) {
|
|
129
|
+
segments.push([points[numPoints - 1], points[0]]);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return segments;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Finds the points of self intersection of the polyline
|
|
136
|
+
* @param inputs points of self intersection
|
|
137
|
+
* @returns polyline
|
|
138
|
+
* @group intersections
|
|
139
|
+
* @shortname polyline self intersections
|
|
140
|
+
* @drawable true
|
|
141
|
+
*/
|
|
142
|
+
polylineSelfIntersection(inputs) {
|
|
143
|
+
const { polyline, tolerance } = inputs;
|
|
144
|
+
const lines = this.polylineToLines({ polyline });
|
|
145
|
+
const numSegments = lines.length;
|
|
146
|
+
if (numSegments < 3) {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
const selfIntersectionPoints = [];
|
|
150
|
+
const defaultTolerance = tolerance !== null && tolerance !== void 0 ? tolerance : 1e-6;
|
|
151
|
+
for (let i = 0; i < numSegments; i++) {
|
|
152
|
+
for (let j = i + 1; j < numSegments; j++) {
|
|
153
|
+
let areAdjacent = (j === i + 1);
|
|
154
|
+
if (!areAdjacent && polyline.isClosed && i === 0 && j === numSegments - 1) {
|
|
155
|
+
areAdjacent = true;
|
|
156
|
+
}
|
|
157
|
+
if (areAdjacent) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const intersection = this.line.lineLineIntersection({
|
|
161
|
+
line1: lines[i],
|
|
162
|
+
line2: lines[j],
|
|
163
|
+
checkSegmentsOnly: true,
|
|
164
|
+
tolerance: defaultTolerance,
|
|
165
|
+
});
|
|
166
|
+
if (intersection) {
|
|
167
|
+
let foundClose = false;
|
|
168
|
+
for (const existingPoint of selfIntersectionPoints) {
|
|
169
|
+
if (this.point.twoPointsAlmostEqual({
|
|
170
|
+
point1: intersection,
|
|
171
|
+
point2: existingPoint,
|
|
172
|
+
tolerance: defaultTolerance
|
|
173
|
+
})) {
|
|
174
|
+
foundClose = true;
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (!foundClose) {
|
|
179
|
+
selfIntersectionPoints.push(intersection);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return selfIntersectionPoints;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Finds the intersection points between two polylines
|
|
188
|
+
* @param inputs two polylines and tolerance
|
|
189
|
+
* @returns points
|
|
190
|
+
* @group intersection
|
|
191
|
+
* @shortname two polyline intersection
|
|
192
|
+
* @drawable true
|
|
193
|
+
*/
|
|
194
|
+
twoPolylineIntersection(inputs) {
|
|
195
|
+
const { polyline1, polyline2, tolerance } = inputs;
|
|
196
|
+
const lines1 = this.polylineToLines({ polyline: polyline1 });
|
|
197
|
+
const lines2 = this.polylineToLines({ polyline: polyline2 });
|
|
198
|
+
const intersectionPoints = [];
|
|
199
|
+
const defaultTolerance = tolerance !== null && tolerance !== void 0 ? tolerance : 1e-6;
|
|
200
|
+
for (const seg1 of lines1) {
|
|
201
|
+
for (const seg2 of lines2) {
|
|
202
|
+
const intersection = this.line.lineLineIntersection({
|
|
203
|
+
line1: seg1,
|
|
204
|
+
line2: seg2,
|
|
205
|
+
checkSegmentsOnly: true,
|
|
206
|
+
tolerance: defaultTolerance,
|
|
207
|
+
});
|
|
208
|
+
if (intersection) {
|
|
209
|
+
let foundClose = false;
|
|
210
|
+
for (const existingPoint of intersectionPoints) {
|
|
211
|
+
if (this.point.twoPointsAlmostEqual({
|
|
212
|
+
point1: intersection,
|
|
213
|
+
point2: existingPoint,
|
|
214
|
+
tolerance: defaultTolerance
|
|
215
|
+
})) {
|
|
216
|
+
foundClose = true;
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (!foundClose) {
|
|
221
|
+
intersectionPoints.push(intersection);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return intersectionPoints;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Create the polylines from segments that are potentially connected but scrambled randomly
|
|
230
|
+
* @param inputs segments
|
|
231
|
+
* @returns polylines
|
|
232
|
+
* @group sort
|
|
233
|
+
* @shortname segments to polylines
|
|
234
|
+
* @drawable true
|
|
235
|
+
*/
|
|
236
|
+
sortSegmentsIntoPolylines(inputs) {
|
|
237
|
+
var _a;
|
|
238
|
+
const tolerance = (_a = inputs.tolerance) !== null && _a !== void 0 ? _a : 1e-5; // Default tolerance
|
|
239
|
+
const segments = inputs.segments;
|
|
240
|
+
if (!segments || segments.length === 0) {
|
|
241
|
+
return [];
|
|
242
|
+
}
|
|
243
|
+
const toleranceSq = tolerance * tolerance;
|
|
244
|
+
const numSegments = segments.length;
|
|
245
|
+
const used = new Array(numSegments).fill(false);
|
|
246
|
+
const results = [];
|
|
247
|
+
const endpointMap = new Map();
|
|
248
|
+
const invTolerance = 1.0 / tolerance;
|
|
249
|
+
const getGridKey = (p) => {
|
|
250
|
+
const ix = Math.round(p[0] * invTolerance);
|
|
251
|
+
const iy = Math.round(p[1] * invTolerance);
|
|
252
|
+
const iz = Math.round(p[2] * invTolerance);
|
|
253
|
+
return `${ix},${iy},${iz}`;
|
|
254
|
+
};
|
|
255
|
+
// 1. Build the spatial map
|
|
256
|
+
for (let i = 0; i < numSegments; i++) {
|
|
257
|
+
const segment = segments[i];
|
|
258
|
+
if (this.point.twoPointsAlmostEqual({ point1: segment[0], point2: segment[1], tolerance: tolerance })) {
|
|
259
|
+
used[i] = true; // Mark degenerate as used
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const key0 = getGridKey(segment[0]);
|
|
263
|
+
const key1 = getGridKey(segment[1]);
|
|
264
|
+
const info0 = { segmentIndex: i, endpointIndex: 0, coords: segment[0] };
|
|
265
|
+
const info1 = { segmentIndex: i, endpointIndex: 1, coords: segment[1] };
|
|
266
|
+
if (!endpointMap.has(key0))
|
|
267
|
+
endpointMap.set(key0, []);
|
|
268
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
269
|
+
endpointMap.get(key0).push(info0);
|
|
270
|
+
if (key1 !== key0) {
|
|
271
|
+
if (!endpointMap.has(key1))
|
|
272
|
+
endpointMap.set(key1, []);
|
|
273
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
274
|
+
endpointMap.get(key1).push(info1);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
278
|
+
endpointMap.get(key0).push(info1); // Add both endpoints if same key
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// --- Helper to find connecting segment ---
|
|
282
|
+
const findConnection = (pointToMatch) => {
|
|
283
|
+
const searchKeys = [];
|
|
284
|
+
const px = Math.round(pointToMatch[0] * invTolerance);
|
|
285
|
+
const py = Math.round(pointToMatch[1] * invTolerance);
|
|
286
|
+
const pz = Math.round(pointToMatch[2] * invTolerance);
|
|
287
|
+
for (let dx = -1; dx <= 1; dx++) {
|
|
288
|
+
for (let dy = -1; dy <= 1; dy++) {
|
|
289
|
+
for (let dz = -1; dz <= 1; dz++) {
|
|
290
|
+
searchKeys.push(`${px + dx},${py + dy},${pz + dz}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
let bestMatch = undefined;
|
|
295
|
+
let minDistanceSq = toleranceSq;
|
|
296
|
+
for (const searchKey of searchKeys) {
|
|
297
|
+
const candidates = endpointMap.get(searchKey);
|
|
298
|
+
if (!candidates)
|
|
299
|
+
continue;
|
|
300
|
+
for (const candidate of candidates) {
|
|
301
|
+
// Only consider segments not already used in *any* polyline
|
|
302
|
+
if (!used[candidate.segmentIndex]) {
|
|
303
|
+
const diffVector = this.vector.sub({ first: candidate.coords, second: pointToMatch });
|
|
304
|
+
const distSq = this.vector.lengthSq({ vector: diffVector });
|
|
305
|
+
if (distSq < minDistanceSq) {
|
|
306
|
+
// Check with precise method if it's a potential best match
|
|
307
|
+
if (this.point.twoPointsAlmostEqual({ point1: candidate.coords, point2: pointToMatch, tolerance: tolerance })) {
|
|
308
|
+
bestMatch = candidate;
|
|
309
|
+
minDistanceSq = distSq; // Update min distance found
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// No need for final check here, already done inside the loop
|
|
316
|
+
if (bestMatch && !used[bestMatch.segmentIndex]) { // Double check used status
|
|
317
|
+
return bestMatch;
|
|
318
|
+
}
|
|
319
|
+
return undefined;
|
|
320
|
+
};
|
|
321
|
+
// 2. Iterate and chain segments
|
|
322
|
+
for (let i = 0; i < numSegments; i++) {
|
|
323
|
+
if (used[i])
|
|
324
|
+
continue; // Skip if already part of a polyline
|
|
325
|
+
// Start a new polyline
|
|
326
|
+
used[i] = true; // Mark the starting segment as used
|
|
327
|
+
const startSegment = segments[i];
|
|
328
|
+
const currentPoints = [startSegment[0], startSegment[1]];
|
|
329
|
+
let currentHead = startSegment[0];
|
|
330
|
+
let currentTail = startSegment[1];
|
|
331
|
+
let isClosed = false;
|
|
332
|
+
let iterations = 0;
|
|
333
|
+
// Extend forward (tail)
|
|
334
|
+
while (iterations++ < numSegments) {
|
|
335
|
+
const nextMatch = findConnection(currentTail);
|
|
336
|
+
if (!nextMatch)
|
|
337
|
+
break; // No unused segment connects to the tail
|
|
338
|
+
// We found a potential next segment
|
|
339
|
+
const nextSegment = segments[nextMatch.segmentIndex];
|
|
340
|
+
const pointToAdd = (nextMatch.endpointIndex === 0) ? nextSegment[1] : nextSegment[0];
|
|
341
|
+
// Check for closure *before* adding the point
|
|
342
|
+
if (this.point.twoPointsAlmostEqual({ point1: pointToAdd, point2: currentHead, tolerance: tolerance })) {
|
|
343
|
+
isClosed = true;
|
|
344
|
+
// Mark the closing segment as used
|
|
345
|
+
used[nextMatch.segmentIndex] = true;
|
|
346
|
+
break; // Closed loop found
|
|
347
|
+
}
|
|
348
|
+
// Not closing, so add the point and mark the segment used
|
|
349
|
+
used[nextMatch.segmentIndex] = true;
|
|
350
|
+
currentPoints.push(pointToAdd);
|
|
351
|
+
currentTail = pointToAdd;
|
|
352
|
+
}
|
|
353
|
+
// Extend backward (head) - only if not already closed
|
|
354
|
+
iterations = 0;
|
|
355
|
+
if (!isClosed) {
|
|
356
|
+
while (iterations++ < numSegments) {
|
|
357
|
+
const prevMatch = findConnection(currentHead);
|
|
358
|
+
if (!prevMatch)
|
|
359
|
+
break; // No unused segment connects to the head
|
|
360
|
+
const prevSegment = segments[prevMatch.segmentIndex];
|
|
361
|
+
const pointToAdd = (prevMatch.endpointIndex === 0) ? prevSegment[1] : prevSegment[0];
|
|
362
|
+
// Check for closure against the current tail *before* adding
|
|
363
|
+
if (this.point.twoPointsAlmostEqual({ point1: pointToAdd, point2: currentTail, tolerance: tolerance })) {
|
|
364
|
+
isClosed = true;
|
|
365
|
+
// Mark the closing segment as used
|
|
366
|
+
used[prevMatch.segmentIndex] = true;
|
|
367
|
+
break; // Closed loop found
|
|
368
|
+
}
|
|
369
|
+
// Not closing, add point to beginning and mark segment used
|
|
370
|
+
used[prevMatch.segmentIndex] = true;
|
|
371
|
+
currentPoints.unshift(pointToAdd);
|
|
372
|
+
currentHead = pointToAdd;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// Final closure check (might be redundant now, but harmless)
|
|
376
|
+
// This catches cases like A->B, B->A which form a 2-point closed loop
|
|
377
|
+
if (!isClosed && currentPoints.length >= 2) {
|
|
378
|
+
isClosed = this.point.twoPointsAlmostEqual({ point1: currentHead, point2: currentTail, tolerance: tolerance });
|
|
379
|
+
}
|
|
380
|
+
// Remove duplicate point for closed loops with more than 2 points
|
|
381
|
+
if (isClosed && currentPoints.length > 2) {
|
|
382
|
+
// Check if the first and last points are indeed the ones needing merging
|
|
383
|
+
if (this.point.twoPointsAlmostEqual({ point1: currentPoints[currentPoints.length - 1], point2: currentPoints[0], tolerance: tolerance })) {
|
|
384
|
+
currentPoints.pop();
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
// Add the completed polyline (even if it's just the starting segment)
|
|
388
|
+
results.push({
|
|
389
|
+
points: currentPoints,
|
|
390
|
+
isClosed: isClosed,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
return results;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Calculates the maximum possible half-line fillet radius for each corner
|
|
397
|
+
* of a given polyline. For a closed polyline, it includes the corners
|
|
398
|
+
* connecting the last segment back to the first.
|
|
399
|
+
*
|
|
400
|
+
* The calculation uses the 'half-line' constraint, meaning the fillet's
|
|
401
|
+
* tangent points must lie within the first half of each segment connected
|
|
402
|
+
* to the corner.
|
|
403
|
+
*
|
|
404
|
+
* @param inputs Defines the polyline points, whether it's closed, and an optional tolerance.
|
|
405
|
+
* @returns An array containing the maximum fillet radius calculated for each corner.
|
|
406
|
+
* The order corresponds to corners P[1]...P[n-2] for open polylines,
|
|
407
|
+
* and P[1]...P[n-2], P[0], P[n-1] for closed polylines.
|
|
408
|
+
* Returns an empty array if the polyline has fewer than 3 points.
|
|
409
|
+
* @group fillet
|
|
410
|
+
* @shortname polyline max fillet radii
|
|
411
|
+
* @drawable false
|
|
412
|
+
*/
|
|
413
|
+
maxFilletsHalfLine(inputs) {
|
|
414
|
+
return this.point.maxFilletsHalfLine({
|
|
415
|
+
points: inputs.polyline.points,
|
|
416
|
+
checkLastWithFirst: inputs.polyline.isClosed,
|
|
417
|
+
tolerance: inputs.tolerance,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Calculates the single safest maximum fillet radius that can be applied
|
|
422
|
+
* uniformly to all corners of a polyline, based on the 'half-line' constraint.
|
|
423
|
+
* This is determined by finding the minimum of the maximum possible fillet
|
|
424
|
+
* radii calculated for each individual corner.
|
|
425
|
+
*
|
|
426
|
+
* @param inputs Defines the polyline points, whether it's closed, and an optional tolerance.
|
|
427
|
+
* @returns The smallest value from the results of calculatePolylineMaxFillets.
|
|
428
|
+
* Returns 0 if the polyline has fewer than 3 points or if any
|
|
429
|
+
* calculated maximum radius is 0.
|
|
430
|
+
* @group fillet
|
|
431
|
+
* @shortname polyline safest fillet radius
|
|
432
|
+
* @drawable false
|
|
433
|
+
*/
|
|
434
|
+
safestFilletRadius(inputs) {
|
|
435
|
+
const allMaxRadii = this.maxFilletsHalfLine(inputs);
|
|
436
|
+
if (allMaxRadii.length === 0) {
|
|
437
|
+
// No corners, or fewer than 3 points. No fillet possible.
|
|
438
|
+
return 0;
|
|
439
|
+
}
|
|
440
|
+
// Find the minimum radius among all calculated maximums.
|
|
441
|
+
// If any corner calculation resulted in 0, the safest radius is 0.
|
|
442
|
+
const safestRadius = Math.min(...allMaxRadii);
|
|
443
|
+
// Ensure we don't return a negative radius if Math.min had weird input (shouldn't happen here)
|
|
444
|
+
return Math.max(0, safestRadius);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
@@ -74,6 +74,17 @@ export declare class Transforms {
|
|
|
74
74
|
* @drawable false
|
|
75
75
|
*/
|
|
76
76
|
scaleXYZ(inputs: Inputs.Transforms.ScaleXYZDto): Base.TransformMatrixes;
|
|
77
|
+
/**
|
|
78
|
+
* Creates a stretch transformation along a specific direction, relative to a center point.
|
|
79
|
+
* This scales points along the given direction vector while leaving points in the
|
|
80
|
+
* plane perpendicular to the direction (passing through the center) unchanged.
|
|
81
|
+
* @param inputs Defines the center, direction, and scale factor for the stretch.
|
|
82
|
+
* @returns Array of transformations: [Translate To Origin, Stretch, Translate Back].
|
|
83
|
+
* @group scale
|
|
84
|
+
* @shortname stretch dir center
|
|
85
|
+
* @drawable false
|
|
86
|
+
*/
|
|
87
|
+
stretchDirFromCenter(inputs: Inputs.Transforms.StretchDirCenterDto): Base.TransformMatrixes;
|
|
77
88
|
/**
|
|
78
89
|
* Creates uniform scale transformation
|
|
79
90
|
* @param inputs Scale Dto
|
|
@@ -110,13 +121,27 @@ export declare class Transforms {
|
|
|
110
121
|
* @drawable false
|
|
111
122
|
*/
|
|
112
123
|
translationsXYZ(inputs: Inputs.Transforms.TranslationsXYZDto): Base.TransformMatrixes[];
|
|
124
|
+
/**
|
|
125
|
+
* Creates the identity transformation
|
|
126
|
+
* @returns transformation
|
|
127
|
+
* @group identity
|
|
128
|
+
* @shortname identity
|
|
129
|
+
* @drawable false
|
|
130
|
+
*/
|
|
131
|
+
identity(): Base.TransformMatrix;
|
|
113
132
|
private translation;
|
|
114
133
|
private scaling;
|
|
115
|
-
private identity;
|
|
116
134
|
private rotationAxis;
|
|
117
135
|
private rotationX;
|
|
118
136
|
private rotationY;
|
|
119
137
|
private rotationZ;
|
|
120
138
|
private rotationYawPitchRoll;
|
|
121
139
|
private rotationMatrixFromQuat;
|
|
140
|
+
/**
|
|
141
|
+
* Creates a 4x4 matrix that scales along a given direction vector.
|
|
142
|
+
* @param direction The direction vector (will be normalized).
|
|
143
|
+
* @param scale The scale factor along the direction.
|
|
144
|
+
* @returns A 4x4 column-major transformation matrix.
|
|
145
|
+
*/
|
|
146
|
+
private stretchDirection;
|
|
122
147
|
}
|