@js-draw/math 1.26.0 → 1.27.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -69,6 +69,8 @@ export declare class Color4 {
69
69
  * ```
70
70
  */
71
71
  mix(other: Color4, fractionTo: number): Color4;
72
+ /** Returns a new color with a different opacity. */
73
+ withAlpha(a: number): Color4;
72
74
  /**
73
75
  * Ignoring this color's alpha component, returns a vector with components,
74
76
  * $$
@@ -183,6 +183,10 @@ class Color4 {
183
183
  const fractionOfThis = 1 - fractionTo;
184
184
  return new Color4(this.r * fractionOfThis + other.r * fractionTo, this.g * fractionOfThis + other.g * fractionTo, this.b * fractionOfThis + other.b * fractionTo, this.a * fractionOfThis + other.a * fractionTo);
185
185
  }
186
+ /** Returns a new color with a different opacity. */
187
+ withAlpha(a) {
188
+ return new Color4(this.r, this.g, this.b, a);
189
+ }
186
190
  /**
187
191
  * Ignoring this color's alpha component, returns a vector with components,
188
192
  * $$
@@ -185,9 +185,13 @@ export declare class Path {
185
185
  mapPoints(mapping: (point: Point2) => Point2): Path;
186
186
  transformedBy(affineTransfm: Mat33): Path;
187
187
  /**
188
- * @internal
188
+ * @internal -- TODO: This method may have incorrect output in some cases.
189
189
  */
190
190
  closedContainsPoint(point: Point2): boolean;
191
+ /**
192
+ * @returns `true` if this path (interpreted as a closed path) contains the given rectangle.
193
+ */
194
+ closedContainsRect(rect: Rect2): boolean;
191
195
  union(other: Path | PathCommand[] | null, options?: {
192
196
  allowReverse?: boolean;
193
197
  }): Path;
@@ -751,7 +751,7 @@ class Path {
751
751
  return this.mapPoints((point) => affineTransfm.transformVec2(point));
752
752
  }
753
753
  /**
754
- * @internal
754
+ * @internal -- TODO: This method may have incorrect output in some cases.
755
755
  */
756
756
  closedContainsPoint(point) {
757
757
  const bbox = this.getExactBBox();
@@ -761,7 +761,30 @@ class Path {
761
761
  const pointOutside = point.plus(Vec2_1.Vec2.of(bbox.width, 0));
762
762
  const asClosed = this.asClosed();
763
763
  const lineToOutside = new LineSegment2_1.default(point, pointOutside);
764
- return asClosed.intersection(lineToOutside).length % 2 === 1;
764
+ const intersections = asClosed.intersection(lineToOutside);
765
+ const filteredIntersections = intersections.filter((intersection, index) => {
766
+ if (index === 0)
767
+ return true; // No previous
768
+ const previousIntersection = intersections[index - 1];
769
+ const isRepeatedIntersection = previousIntersection.parameterValue >= 1 && intersection.parameterValue <= 0;
770
+ return !isRepeatedIntersection;
771
+ });
772
+ return filteredIntersections.length % 2 === 1;
773
+ }
774
+ /**
775
+ * @returns `true` if this path (interpreted as a closed path) contains the given rectangle.
776
+ */
777
+ closedContainsRect(rect) {
778
+ if (!this.bbox.containsRect(rect))
779
+ return false;
780
+ if (!rect.corners.every((corner) => this.closedContainsPoint(corner)))
781
+ return false;
782
+ for (const edge of rect.getEdges()) {
783
+ if (this.intersection(edge).length) {
784
+ return false;
785
+ }
786
+ }
787
+ return true;
765
788
  }
766
789
  // Creates a new path by joining [other] to the end of this path
767
790
  union(other,
@@ -69,6 +69,8 @@ export declare class Color4 {
69
69
  * ```
70
70
  */
71
71
  mix(other: Color4, fractionTo: number): Color4;
72
+ /** Returns a new color with a different opacity. */
73
+ withAlpha(a: number): Color4;
72
74
  /**
73
75
  * Ignoring this color's alpha component, returns a vector with components,
74
76
  * $$
@@ -177,6 +177,10 @@ export class Color4 {
177
177
  const fractionOfThis = 1 - fractionTo;
178
178
  return new Color4(this.r * fractionOfThis + other.r * fractionTo, this.g * fractionOfThis + other.g * fractionTo, this.b * fractionOfThis + other.b * fractionTo, this.a * fractionOfThis + other.a * fractionTo);
179
179
  }
180
+ /** Returns a new color with a different opacity. */
181
+ withAlpha(a) {
182
+ return new Color4(this.r, this.g, this.b, a);
183
+ }
180
184
  /**
181
185
  * Ignoring this color's alpha component, returns a vector with components,
182
186
  * $$
@@ -185,9 +185,13 @@ export declare class Path {
185
185
  mapPoints(mapping: (point: Point2) => Point2): Path;
186
186
  transformedBy(affineTransfm: Mat33): Path;
187
187
  /**
188
- * @internal
188
+ * @internal -- TODO: This method may have incorrect output in some cases.
189
189
  */
190
190
  closedContainsPoint(point: Point2): boolean;
191
+ /**
192
+ * @returns `true` if this path (interpreted as a closed path) contains the given rectangle.
193
+ */
194
+ closedContainsRect(rect: Rect2): boolean;
191
195
  union(other: Path | PathCommand[] | null, options?: {
192
196
  allowReverse?: boolean;
193
197
  }): Path;
@@ -743,7 +743,7 @@ export class Path {
743
743
  return this.mapPoints((point) => affineTransfm.transformVec2(point));
744
744
  }
745
745
  /**
746
- * @internal
746
+ * @internal -- TODO: This method may have incorrect output in some cases.
747
747
  */
748
748
  closedContainsPoint(point) {
749
749
  const bbox = this.getExactBBox();
@@ -753,7 +753,30 @@ export class Path {
753
753
  const pointOutside = point.plus(Vec2.of(bbox.width, 0));
754
754
  const asClosed = this.asClosed();
755
755
  const lineToOutside = new LineSegment2(point, pointOutside);
756
- return asClosed.intersection(lineToOutside).length % 2 === 1;
756
+ const intersections = asClosed.intersection(lineToOutside);
757
+ const filteredIntersections = intersections.filter((intersection, index) => {
758
+ if (index === 0)
759
+ return true; // No previous
760
+ const previousIntersection = intersections[index - 1];
761
+ const isRepeatedIntersection = previousIntersection.parameterValue >= 1 && intersection.parameterValue <= 0;
762
+ return !isRepeatedIntersection;
763
+ });
764
+ return filteredIntersections.length % 2 === 1;
765
+ }
766
+ /**
767
+ * @returns `true` if this path (interpreted as a closed path) contains the given rectangle.
768
+ */
769
+ closedContainsRect(rect) {
770
+ if (!this.bbox.containsRect(rect))
771
+ return false;
772
+ if (!rect.corners.every((corner) => this.closedContainsPoint(corner)))
773
+ return false;
774
+ for (const edge of rect.getEdges()) {
775
+ if (this.intersection(edge).length) {
776
+ return false;
777
+ }
778
+ }
779
+ return true;
757
780
  }
758
781
  // Creates a new path by joining [other] to the end of this path
759
782
  union(other,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@js-draw/math",
3
- "version": "1.26.0",
3
+ "version": "1.27.1",
4
4
  "description": "A math library for js-draw. ",
5
5
  "types": "./dist/mjs/lib.d.ts",
6
6
  "main": "./dist/cjs/lib.js",
@@ -27,7 +27,7 @@
27
27
  "bezier-js": "6.1.3"
28
28
  },
29
29
  "devDependencies": {
30
- "@js-draw/build-tool": "^1.26.0",
30
+ "@js-draw/build-tool": "^1.27.1",
31
31
  "@types/bezier-js": "4.1.0",
32
32
  "@types/jest": "29.5.5",
33
33
  "@types/jsdom": "21.1.3"
@@ -44,5 +44,5 @@
44
44
  "svg",
45
45
  "math"
46
46
  ],
47
- "gitHead": "6529cad584ca93a992f2576a43f25af48da3d707"
47
+ "gitHead": "e11574b7b329e7867358c400457a3d99b1d2c469"
48
48
  }
package/src/Color4.ts CHANGED
@@ -224,6 +224,11 @@ export class Color4 {
224
224
  );
225
225
  }
226
226
 
227
+ /** Returns a new color with a different opacity. */
228
+ public withAlpha(a: number) {
229
+ return new Color4(this.r, this.g, this.b, a);
230
+ }
231
+
227
232
  /**
228
233
  * Ignoring this color's alpha component, returns a vector with components,
229
234
  * $$
@@ -536,4 +536,21 @@ describe('Path', () => {
536
536
  expect(path.tangentAt(at)).objEq(expected);
537
537
  },
538
538
  );
539
+
540
+ it.each([
541
+ // A rectangle completely contained
542
+ ['m0,0 l10,0 l0,10 l-10,0', new Rect2(3, 3, 3, 3), true],
543
+ // A rectangle partially contained
544
+ ['m0,0 l10,0 l0,10 l-10,0', new Rect2(3, 3, 33, 3), false],
545
+ // A rectangle not contained
546
+ ['m0,0 l10,0 l0,10 l-10,0', new Rect2(13, 3, 1, 1), false],
547
+ // More complicated path containing a rectangle
548
+ ['M0,0 Q10,15 10,5', new Rect2(5, 5, 1, 1), true],
549
+ ['M0,0 Q10,15 10,5', new Rect2(15, 5, 1, 1), false],
550
+ ])(
551
+ '.closedContainsRect should return whether a rectangle is contained within a path (case %#: path(%s), rect(%s))',
552
+ (pathString, rect, expected) => {
553
+ expect(Path.fromString(pathString).closedContainsRect(rect)).toBe(expected);
554
+ },
555
+ );
539
556
  });
@@ -969,7 +969,7 @@ export class Path {
969
969
  }
970
970
 
971
971
  /**
972
- * @internal
972
+ * @internal -- TODO: This method may have incorrect output in some cases.
973
973
  */
974
974
  public closedContainsPoint(point: Point2) {
975
975
  const bbox = this.getExactBBox();
@@ -981,7 +981,32 @@ export class Path {
981
981
  const asClosed = this.asClosed();
982
982
 
983
983
  const lineToOutside = new LineSegment2(point, pointOutside);
984
- return asClosed.intersection(lineToOutside).length % 2 === 1;
984
+
985
+ const intersections = asClosed.intersection(lineToOutside);
986
+ const filteredIntersections = intersections.filter((intersection, index) => {
987
+ if (index === 0) return true; // No previous
988
+ const previousIntersection = intersections[index - 1];
989
+ const isRepeatedIntersection =
990
+ previousIntersection.parameterValue >= 1 && intersection.parameterValue <= 0;
991
+ return !isRepeatedIntersection;
992
+ });
993
+ return filteredIntersections.length % 2 === 1;
994
+ }
995
+
996
+ /**
997
+ * @returns `true` if this path (interpreted as a closed path) contains the given rectangle.
998
+ */
999
+ public closedContainsRect(rect: Rect2) {
1000
+ if (!this.bbox.containsRect(rect)) return false;
1001
+ if (!rect.corners.every((corner) => this.closedContainsPoint(corner))) return false;
1002
+
1003
+ for (const edge of rect.getEdges()) {
1004
+ if (this.intersection(edge).length) {
1005
+ return false;
1006
+ }
1007
+ }
1008
+
1009
+ return true;
985
1010
  }
986
1011
 
987
1012
  // Creates a new path by joining [other] to the end of this path