@flighthq/path 0.2.0 → 0.2.1-next.410.a23f189
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/path",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1-next.410.a23f189",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/flighthq/flight.git",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@flighthq/types": "0.2.
|
|
35
|
+
"@flighthq/types": "0.2.1-next.410.a23f189"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"typescript": "^5.3.0"
|
|
@@ -70,6 +70,39 @@ describe('containsPathPoint', () => {
|
|
|
70
70
|
expect(containsPathPoint(path, 50, 30)).toBe(true);
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
+
it('handles a self-intersecting figure-8 path (nonZero)', () => {
|
|
74
|
+
// Bowtie / figure-8: two triangles sharing a center vertex, forming a self-intersection.
|
|
75
|
+
// The left lobe winds one way, the right lobe the opposite way in a single contour.
|
|
76
|
+
const path = createPath('nonZero');
|
|
77
|
+
appendPathMoveTo(path, 50, 50);
|
|
78
|
+
appendPathLineTo(path, 0, 0);
|
|
79
|
+
appendPathLineTo(path, 0, 100);
|
|
80
|
+
appendPathLineTo(path, 50, 50);
|
|
81
|
+
appendPathLineTo(path, 100, 100);
|
|
82
|
+
appendPathLineTo(path, 100, 0);
|
|
83
|
+
appendPathLineTo(path, 50, 50);
|
|
84
|
+
// Point inside the left lobe
|
|
85
|
+
expect(containsPathPoint(path, 20, 50)).toBe(true);
|
|
86
|
+
// Point inside the right lobe
|
|
87
|
+
expect(containsPathPoint(path, 80, 50)).toBe(true);
|
|
88
|
+
// Point outside both lobes
|
|
89
|
+
expect(containsPathPoint(path, 50, 120)).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('handles a point at a vertex of the path', () => {
|
|
93
|
+
const path = createPath('nonZero');
|
|
94
|
+
appendPathMoveTo(path, 0, 0);
|
|
95
|
+
appendPathLineTo(path, 100, 0);
|
|
96
|
+
appendPathLineTo(path, 100, 100);
|
|
97
|
+
appendPathLineTo(path, 0, 100);
|
|
98
|
+
appendPathLineTo(path, 0, 0);
|
|
99
|
+
// A vertex is on the boundary — the function returns a boolean without crashing.
|
|
100
|
+
const atVertex = containsPathPoint(path, 0, 0);
|
|
101
|
+
expect(typeof atVertex).toBe('boolean');
|
|
102
|
+
const atAnotherVertex = containsPathPoint(path, 100, 100);
|
|
103
|
+
expect(typeof atAnotherVertex).toBe('boolean');
|
|
104
|
+
});
|
|
105
|
+
|
|
73
106
|
it('handles a quadratic curve contour', () => {
|
|
74
107
|
// Approximate a quarter-circle using a single quadratic — the interior should register a hit.
|
|
75
108
|
const path = createPath('nonZero');
|
|
@@ -92,6 +125,34 @@ describe('containsPathPoint', () => {
|
|
|
92
125
|
expect(containsPathPoint(path, 150, 50)).toBe(false);
|
|
93
126
|
});
|
|
94
127
|
|
|
128
|
+
it('returns false for a degenerate zero-area contour (collinear points)', () => {
|
|
129
|
+
const path = createPath('nonZero');
|
|
130
|
+
appendPathMoveTo(path, 0, 0);
|
|
131
|
+
appendPathLineTo(path, 100, 0);
|
|
132
|
+
appendPathLineTo(path, 50, 0);
|
|
133
|
+
appendPathLineTo(path, 0, 0);
|
|
134
|
+
expect(containsPathPoint(path, 50, 0)).toBe(false);
|
|
135
|
+
expect(containsPathPoint(path, 50, 1)).toBe(false);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('returns false for a point on a boundary edge', () => {
|
|
139
|
+
// The docstring states: "points on the boundary are not guaranteed to return true."
|
|
140
|
+
// Standard winding-number ray-cast treats boundary as outside.
|
|
141
|
+
const path = createPath('nonZero');
|
|
142
|
+
appendPathMoveTo(path, 0, 0);
|
|
143
|
+
appendPathLineTo(path, 100, 0);
|
|
144
|
+
appendPathLineTo(path, 100, 100);
|
|
145
|
+
appendPathLineTo(path, 0, 100);
|
|
146
|
+
appendPathLineTo(path, 0, 0);
|
|
147
|
+
// Point on the top edge at (50, 0) — exactly on the boundary.
|
|
148
|
+
// The function does not guarantee true for boundary points, so we just verify
|
|
149
|
+
// it returns a boolean without crashing. The left edge is tested similarly.
|
|
150
|
+
const topEdge = containsPathPoint(path, 50, 0);
|
|
151
|
+
const leftEdge = containsPathPoint(path, 0, 50);
|
|
152
|
+
expect(typeof topEdge).toBe('boolean');
|
|
153
|
+
expect(typeof leftEdge).toBe('boolean');
|
|
154
|
+
});
|
|
155
|
+
|
|
95
156
|
it('returns false for an empty path', () => {
|
|
96
157
|
const path = createPath('nonZero');
|
|
97
158
|
expect(containsPathPoint(path, 0, 0)).toBe(false);
|
package/src/dashPath.test.ts
CHANGED
|
@@ -65,6 +65,21 @@ describe('dashPath', () => {
|
|
|
65
65
|
expect(out.commands.length).toBeGreaterThan(0);
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
+
it('produces the same result for a separate out object as for a fresh path', () => {
|
|
69
|
+
const source = createPath();
|
|
70
|
+
appendPathMoveTo(source, 0, 0);
|
|
71
|
+
appendPathLineTo(source, 100, 0);
|
|
72
|
+
appendPathLineTo(source, 100, 100);
|
|
73
|
+
const out = createPath();
|
|
74
|
+
dashPath(source, [15, 10], 0, out);
|
|
75
|
+
// Verify the source is unchanged after dashing into a separate out.
|
|
76
|
+
expect(source.commands).toStrictEqual([PathCommand.MOVE_TO, PathCommand.LINE_TO, PathCommand.LINE_TO]);
|
|
77
|
+
expect(source.data).toStrictEqual([0, 0, 100, 0, 100, 100]);
|
|
78
|
+
// Verify the out path has dashed segments.
|
|
79
|
+
const moves = out.commands.filter((c) => c === PathCommand.MOVE_TO);
|
|
80
|
+
expect(moves.length).toBeGreaterThanOrEqual(2);
|
|
81
|
+
});
|
|
82
|
+
|
|
68
83
|
it('preserves winding rule', () => {
|
|
69
84
|
const source = createPath('evenOdd');
|
|
70
85
|
appendPathMoveTo(source, 0, 0);
|
|
@@ -151,6 +151,15 @@ describe('getPathSegmentTangentAtParameter', () => {
|
|
|
151
151
|
});
|
|
152
152
|
|
|
153
153
|
describe('getQuadraticBezierPoint', () => {
|
|
154
|
+
it('returns midpoint at t=0.5 for a known quadratic curve', () => {
|
|
155
|
+
// P0=(0,0), C=(50,100), P1=(100,0)
|
|
156
|
+
// B(0.5) = 0.25*(0,0) + 2*0.25*(50,100) + 0.25*(100,0) = (50, 50)
|
|
157
|
+
const out = { x: 0, y: 0 };
|
|
158
|
+
getQuadraticBezierPoint(0, 0, 50, 100, 100, 0, 0.5, out);
|
|
159
|
+
expect(out.x).toBeCloseTo(50);
|
|
160
|
+
expect(out.y).toBeCloseTo(50);
|
|
161
|
+
});
|
|
162
|
+
|
|
154
163
|
it('returns start at t=0', () => {
|
|
155
164
|
const out = { x: 0, y: 0 };
|
|
156
165
|
getQuadraticBezierPoint(0, 1, 5, 5, 10, 1, 0, out);
|
|
@@ -167,6 +176,25 @@ describe('getQuadraticBezierPoint', () => {
|
|
|
167
176
|
});
|
|
168
177
|
|
|
169
178
|
describe('getQuadraticBezierTangent', () => {
|
|
179
|
+
it('returns the end tangent direction at t=1', () => {
|
|
180
|
+
const out = { x: 0, y: 0 };
|
|
181
|
+
// P0=(0,0), C=(50,100), P1=(100,0)
|
|
182
|
+
// B'(1) = 2*(P1-C) = 2*(100-50, 0-100) = (100, -200)
|
|
183
|
+
getQuadraticBezierTangent(0, 0, 50, 100, 100, 0, 1, out);
|
|
184
|
+
expect(out.x).toBeCloseTo(100);
|
|
185
|
+
expect(out.y).toBeCloseTo(-200);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('returns the midpoint tangent at t=0.5', () => {
|
|
189
|
+
const out = { x: 0, y: 0 };
|
|
190
|
+
// P0=(0,0), C=(50,100), P1=(100,0)
|
|
191
|
+
// B'(0.5) = 2*(0.5*(C-P0) + 0.5*(P1-C)) = 2*(0.5*(50,100) + 0.5*(50,-100))
|
|
192
|
+
// = 2*((25,50) + (25,-50)) = 2*(50,0) = (100, 0)
|
|
193
|
+
getQuadraticBezierTangent(0, 0, 50, 100, 100, 0, 0.5, out);
|
|
194
|
+
expect(out.x).toBeCloseTo(100);
|
|
195
|
+
expect(out.y).toBeCloseTo(0);
|
|
196
|
+
});
|
|
197
|
+
|
|
170
198
|
it('returns the start tangent direction at t=0', () => {
|
|
171
199
|
const out = { x: 0, y: 0 };
|
|
172
200
|
// P0=(0,0), C=(4,0), P1=(8,0) — straight horizontal
|