@altpsyche/maths 0.9.2 → 0.9.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/README.md +16 -8
- package/dist/figure/animation.js +3 -3
- package/dist/figure/axis.d.ts +8 -0
- package/dist/figure/axis.js +30 -12
- package/dist/figure/boolean.js +1 -1
- package/dist/figure/cut.js +1 -1
- package/dist/figure/equation.js +2 -1
- package/dist/figure/inside.d.ts +4 -0
- package/dist/figure/inside.js +5 -1
- package/dist/figure/intersect.d.ts +10 -3
- package/dist/figure/intersect.js +1 -4
- package/dist/figure/tolerance.d.ts +15 -0
- package/dist/figure/tolerance.js +15 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -195,14 +195,22 @@ decides which of those a piece is, by counting how many times the other path win
|
|
|
195
195
|
`areaOf` says how much a path encloses, in closed form rather than by sampling, which is what every
|
|
196
196
|
claim above is checked against.
|
|
197
197
|
|
|
198
|
-
<img src="docs/boolean-strip.svg" width="
|
|
199
|
-
|
|
200
|
-
Four times of one figure. A small disc walks across a larger one: clear of it, touching
|
|
201
|
-
point, crossing it at two, and wholly inside it. Those are the four cases this kind of code
|
|
202
|
-
silently wrong, which is why the demo walks through all of them rather than drawing one.
|
|
203
|
-
|
|
204
|
-
Two
|
|
205
|
-
|
|
198
|
+
<img src="docs/boolean-strip.svg" width="820" alt="Four moments in two rows, each showing the three panels, as the small disc walks from clear of the large one, through touching it at one point, through overlapping it, to sitting wholly inside it.">
|
|
199
|
+
|
|
200
|
+
Four times of one figure, in two rows. A small disc walks across a larger one: clear of it, touching
|
|
201
|
+
it at one point, crossing it at two, and wholly inside it. Those are the four cases this kind of code
|
|
202
|
+
gets silently wrong, which is why the demo walks through all of them rather than drawing one.
|
|
203
|
+
|
|
204
|
+
Two shapes that share an edge are combined by which way each of them runs over it. Two paths walking
|
|
205
|
+
a shared stretch the same way have their solid on the same side of it, so the stretch is on the edge
|
|
206
|
+
of a union and of an overlap and is kept once. Walking it opposite ways puts their solids on opposite
|
|
207
|
+
sides, so the stretch is inside a union and outside an overlap, and a difference keeps the first
|
|
208
|
+
path's copy of it. Two rectangles sharing an edge unite into one rectangle, and a shape combined with
|
|
209
|
+
itself gives itself back.
|
|
210
|
+
|
|
211
|
+
When the pieces kept will not join into a loop, the operation stops and says how far apart the two
|
|
212
|
+
ends of the run it had are. That happens when an input crosses itself, which these do not take. A
|
|
213
|
+
shape drawn with a gap in it and nothing said about it is the one failure a caller cannot see.
|
|
206
214
|
|
|
207
215
|
## The animations
|
|
208
216
|
|
package/dist/figure/animation.js
CHANGED
|
@@ -141,9 +141,9 @@ export function fadeTo(target, opacity) {
|
|
|
141
141
|
* A mark carried through a transform, geometry and weight together.
|
|
142
142
|
*
|
|
143
143
|
* A transform that scales makes the lines inside it thicker and the words
|
|
144
|
-
* bigger, the way it makes everything else bigger, which is what
|
|
145
|
-
* already does
|
|
146
|
-
* mark with the stroke it started at.
|
|
144
|
+
* bigger, the way it makes everything else bigger, which is what a group that
|
|
145
|
+
* scales already does to the marks under it. Doing less here would leave a
|
|
146
|
+
* shrinking mark with the stroke it started at.
|
|
147
147
|
*/
|
|
148
148
|
function carried(mark, through) {
|
|
149
149
|
const scale = mat3.scaleFactor(through);
|
package/dist/figure/axis.d.ts
CHANGED
|
@@ -28,6 +28,10 @@ export interface NumberLineOptions {
|
|
|
28
28
|
/** Leaves the label at zero out, which is what a second axis crossing here
|
|
29
29
|
* wants, since both would otherwise write the same number in the same place. */
|
|
30
30
|
skipZero?: boolean;
|
|
31
|
+
/** The number on this line another line crosses it at. The label there is
|
|
32
|
+
* written below and to the left of the crossing rather than under it, since
|
|
33
|
+
* under it is where the other line and its head already are. */
|
|
34
|
+
crossedAt?: number;
|
|
31
35
|
}
|
|
32
36
|
/**
|
|
33
37
|
* One axis as a group: the line, the ticks under `ticks`, the labels under
|
|
@@ -61,6 +65,10 @@ export interface NumberPlaneOptions {
|
|
|
61
65
|
/** How much of the stroke a minor line is drawn with, since a grid a reader
|
|
62
66
|
* notices is a grid competing with the curve on top of it. */
|
|
63
67
|
minorOpacity?: number;
|
|
68
|
+
/** How wide a minor line is against a major one. A minor line that differs
|
|
69
|
+
* only in how strong its ink is reads as the same line, so the grid comes out
|
|
70
|
+
* flat and busy. */
|
|
71
|
+
minorWidth?: number;
|
|
64
72
|
ticks?: number;
|
|
65
73
|
}
|
|
66
74
|
/**
|
package/dist/figure/axis.js
CHANGED
|
@@ -37,14 +37,17 @@ export function numberLine(name, scale, options) {
|
|
|
37
37
|
const gap = options.gap ?? size * 0.35;
|
|
38
38
|
const { from: low, to: high } = interval.ordered(scale.units);
|
|
39
39
|
const at = (along, off) => (across ? vec2(along, seat + off) : vec2(seat + off, along));
|
|
40
|
-
// The line
|
|
41
|
-
//
|
|
42
|
-
|
|
40
|
+
// The line runs the whole of the axis and each head stands beyond its end,
|
|
41
|
+
// rather than the heads eating into the line. Eating in leaves the outermost
|
|
42
|
+
// tick standing under a head instead of on the line, and the line still stops
|
|
43
|
+
// where a head begins, so a head that is not fully opaque has nothing showing
|
|
44
|
+
// through it.
|
|
45
|
+
const parts = [shape('line', line(at(low, 0), at(high, 0)), { stroke: options.stroke })];
|
|
43
46
|
if (tip > 0 && options.fill) {
|
|
44
47
|
const spread = options.spread ?? 0.6;
|
|
45
48
|
parts.push(group('tips', [
|
|
46
|
-
shape('low', head(at(low, 0), at(low
|
|
47
|
-
shape('high', head(at(high, 0), at(high
|
|
49
|
+
shape('low', head(at(low - tip, 0), at(low, 0), spread), { fill: options.fill }),
|
|
50
|
+
shape('high', head(at(high + tip, 0), at(high, 0), spread), { fill: options.fill }),
|
|
48
51
|
]));
|
|
49
52
|
}
|
|
50
53
|
const step = tickStep(scale.graph, options.ticks);
|
|
@@ -57,17 +60,22 @@ export function numberLine(name, scale, options) {
|
|
|
57
60
|
if (options.fill && size > 0) {
|
|
58
61
|
// Placed by an anchor and an alignment and never by how wide the text is,
|
|
59
62
|
// so a long label moves nothing else in the figure.
|
|
60
|
-
const align = across ? 'middle' : 'end';
|
|
61
|
-
const baseline = across ? 'hanging' : 'middle';
|
|
62
63
|
const written = marked.filter((tick) => !(options.skipZero && tick.value === 0));
|
|
64
|
+
const off = half + gap;
|
|
63
65
|
parts.push(group('labels', written.map((tick) => {
|
|
64
66
|
const along = interval.remap(tick.value, scale.graph, scale.units);
|
|
65
|
-
|
|
67
|
+
const crossed = options.crossedAt !== undefined && tick.value === options.crossedAt;
|
|
68
|
+
const anchor = crossed
|
|
69
|
+
? across
|
|
70
|
+
? vec2(along - off, seat - off)
|
|
71
|
+
: vec2(seat - off, along - off)
|
|
72
|
+
: at(along, -off);
|
|
73
|
+
return text(tick.label, anchor, tick.label, size, {
|
|
66
74
|
fill: options.fill,
|
|
67
75
|
family: options.family,
|
|
68
76
|
weight: options.weight,
|
|
69
|
-
align,
|
|
70
|
-
baseline,
|
|
77
|
+
align: crossed || !across ? 'end' : 'middle',
|
|
78
|
+
baseline: crossed || across ? 'hanging' : 'middle',
|
|
71
79
|
});
|
|
72
80
|
})));
|
|
73
81
|
}
|
|
@@ -85,7 +93,12 @@ export function axes(name, coords, options) {
|
|
|
85
93
|
const holdsOrigin = interval.holds(coords.x.graph, 0) && interval.holds(coords.y.graph, 0);
|
|
86
94
|
const seat = (scale) => scaled(scale, interval.clampTo(scale.graph, 0));
|
|
87
95
|
return group(name, [
|
|
88
|
-
numberLine('x', coords.x, {
|
|
96
|
+
numberLine('x', coords.x, {
|
|
97
|
+
...options,
|
|
98
|
+
at: seat(coords.y),
|
|
99
|
+
direction: 'across',
|
|
100
|
+
crossedAt: holdsOrigin ? 0 : undefined,
|
|
101
|
+
}),
|
|
89
102
|
numberLine('y', coords.y, { ...options, at: seat(coords.x), direction: 'up', skipZero: holdsOrigin }),
|
|
90
103
|
]);
|
|
91
104
|
}
|
|
@@ -124,7 +137,12 @@ export function numberPlane(name, coords, options) {
|
|
|
124
137
|
parts.push(group('minors', [
|
|
125
138
|
group('x', gridLines(coords, step.x / minors, 'x', step.x)),
|
|
126
139
|
group('y', gridLines(coords, step.y / minors, 'y', step.y)),
|
|
127
|
-
], {
|
|
140
|
+
], {
|
|
141
|
+
style: {
|
|
142
|
+
stroke: { ...options.stroke, width: options.stroke.width * (options.minorWidth ?? 0.6) },
|
|
143
|
+
opacity: options.minorOpacity ?? 0.4,
|
|
144
|
+
},
|
|
145
|
+
}));
|
|
128
146
|
}
|
|
129
147
|
parts.push(group('majors', [group('x', gridLines(coords, step.x, 'x')), group('y', gridLines(coords, step.y, 'y'))], {
|
|
130
148
|
style: { stroke: options.stroke },
|
package/dist/figure/boolean.js
CHANGED
|
@@ -24,7 +24,7 @@ import { pointOn, slopeOn } from './path.js';
|
|
|
24
24
|
import { cutPath } from './cut.js';
|
|
25
25
|
import { curveCrossings } from './intersect.js';
|
|
26
26
|
import { flattenPath, nearestEdge, windingAt } from './inside.js';
|
|
27
|
-
|
|
27
|
+
import { TOLERANCE } from './tolerance.js';
|
|
28
28
|
/** A subpath left open closed by the straight run back to where it started,
|
|
29
29
|
* since a path with an open loop has no inside for any of this to ask about. */
|
|
30
30
|
function closedLoops(path) {
|
package/dist/figure/cut.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { splitCurve } from './path.js';
|
|
11
11
|
import { measurePath } from './length.js';
|
|
12
|
-
|
|
12
|
+
import { TOLERANCE } from './tolerance.js';
|
|
13
13
|
/** The fractions for one piece, in order, with the ones that would leave a
|
|
14
14
|
* piece of nothing dropped. */
|
|
15
15
|
function wanted(fractions, tolerance) {
|
package/dist/figure/equation.js
CHANGED
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
* expressions has to be made on.
|
|
23
23
|
*
|
|
24
24
|
* Three things stop the walk instead of being drawn, and each names what it
|
|
25
|
-
* found
|
|
25
|
+
* found: a TeX error, a character the font has no outline for, and a macro the
|
|
26
|
+
* typesetter does not know.
|
|
26
27
|
*/
|
|
27
28
|
import { mat3 } from '../values/mat3.js';
|
|
28
29
|
import { vec2 } from '../values/vec2.js';
|
package/dist/figure/inside.d.ts
CHANGED
|
@@ -49,5 +49,9 @@ export declare function nearestEdge(loops: readonly (readonly Vec2[])[], point:
|
|
|
49
49
|
*
|
|
50
50
|
* A point sitting on the edge itself has no answer this can be right about, and
|
|
51
51
|
* what comes back for one is whichever side the tolerance put it on.
|
|
52
|
+
*
|
|
53
|
+
* The path is flattened again on every call, which is a millisecond every three
|
|
54
|
+
* points against a path of a hundred pieces. Asking about many points wants the
|
|
55
|
+
* flattening made once and the count taken against it.
|
|
52
56
|
*/
|
|
53
57
|
export declare function containsPoint(path: Path, point: Vec2, options?: FlattenOptions): boolean;
|
package/dist/figure/inside.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* ray answers.
|
|
15
15
|
*/
|
|
16
16
|
import { vec2 } from '../values/vec2.js';
|
|
17
|
-
|
|
17
|
+
import { TOLERANCE } from './tolerance.js';
|
|
18
18
|
/** How many times a piece may be halved, which a tolerance of zero would
|
|
19
19
|
* otherwise leave unbounded. */
|
|
20
20
|
const DEPTH = 24;
|
|
@@ -136,6 +136,10 @@ export function nearestEdge(loops, point) {
|
|
|
136
136
|
*
|
|
137
137
|
* A point sitting on the edge itself has no answer this can be right about, and
|
|
138
138
|
* what comes back for one is whichever side the tolerance put it on.
|
|
139
|
+
*
|
|
140
|
+
* The path is flattened again on every call, which is a millisecond every three
|
|
141
|
+
* points against a path of a hundred pieces. Asking about many points wants the
|
|
142
|
+
* flattening made once and the count taken against it.
|
|
139
143
|
*/
|
|
140
144
|
export function containsPoint(path, point, options = {}) {
|
|
141
145
|
return windingAt(flattenPath(path, options), point) !== 0;
|
|
@@ -13,8 +13,14 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { type Vec2 } from '../values/vec2.js';
|
|
15
15
|
import type { Cubic } from './path.js';
|
|
16
|
-
/**
|
|
17
|
-
*
|
|
16
|
+
/**
|
|
17
|
+
* One place two curves meet, as a fraction along each of them and the point.
|
|
18
|
+
*
|
|
19
|
+
* The point is read off the first curve. Where the two cross cleanly the second
|
|
20
|
+
* curve's own fraction lands on the same point to the last few bits, and where
|
|
21
|
+
* they meet without crossing it can land as far off as the tolerance, since
|
|
22
|
+
* Newton has no step to take when the two are heading the same way.
|
|
23
|
+
*/
|
|
18
24
|
export interface Crossing {
|
|
19
25
|
readonly alongFirst: number;
|
|
20
26
|
readonly alongSecond: number;
|
|
@@ -22,7 +28,8 @@ export interface Crossing {
|
|
|
22
28
|
}
|
|
23
29
|
export interface CrossingOptions {
|
|
24
30
|
/** How close two pieces come before they count as meeting, in the picture's
|
|
25
|
-
* own units.
|
|
31
|
+
* own units. It decides which meetings are told apart rather than how sharp
|
|
32
|
+
* one is, since Newton's method supplies the sharpness afterwards. */
|
|
26
33
|
readonly tolerance?: number;
|
|
27
34
|
}
|
|
28
35
|
/**
|
package/dist/figure/intersect.js
CHANGED
|
@@ -12,10 +12,7 @@
|
|
|
12
12
|
* tolerance to the last few bits.
|
|
13
13
|
*/
|
|
14
14
|
import { vec2 } from '../values/vec2.js';
|
|
15
|
-
|
|
16
|
-
* meetings are told apart rather than how sharp one is, since Newton's method
|
|
17
|
-
* supplies the sharpness afterwards. */
|
|
18
|
-
const TOLERANCE = 1e-6;
|
|
15
|
+
import { TOLERANCE } from './tolerance.js';
|
|
19
16
|
/**
|
|
20
17
|
* How many pairs the search may make before it answers with where it had got to.
|
|
21
18
|
*
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How close two things come before this package reads them as the same place.
|
|
3
|
+
*
|
|
4
|
+
* It is a distance in the picture's own units rather than a share of anything,
|
|
5
|
+
* so a figure drawn in units of thousands wants its own number passed in. Every
|
|
6
|
+
* call that takes a tolerance takes it as a distance and defaults to this one,
|
|
7
|
+
* so a crossing, a cut, a flattening and a stitch all agree about what counts as
|
|
8
|
+
* one place without a caller having to line four numbers up.
|
|
9
|
+
*
|
|
10
|
+
* A millionth of a figure unit is a ten thousandth of a pixel at the hundred
|
|
11
|
+
* pixels to the unit the demos draw at, so nothing this decides is visible. What
|
|
12
|
+
* it decides is which meetings are told apart, since two crossings closer than
|
|
13
|
+
* this come back as one.
|
|
14
|
+
*/
|
|
15
|
+
export declare const TOLERANCE = 0.000001;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How close two things come before this package reads them as the same place.
|
|
3
|
+
*
|
|
4
|
+
* It is a distance in the picture's own units rather than a share of anything,
|
|
5
|
+
* so a figure drawn in units of thousands wants its own number passed in. Every
|
|
6
|
+
* call that takes a tolerance takes it as a distance and defaults to this one,
|
|
7
|
+
* so a crossing, a cut, a flattening and a stitch all agree about what counts as
|
|
8
|
+
* one place without a caller having to line four numbers up.
|
|
9
|
+
*
|
|
10
|
+
* A millionth of a figure unit is a ten thousandth of a pixel at the hundred
|
|
11
|
+
* pixels to the unit the demos draw at, so nothing this decides is visible. What
|
|
12
|
+
* it decides is which meetings are told apart, since two crossings closer than
|
|
13
|
+
* this come back as one.
|
|
14
|
+
*/
|
|
15
|
+
export const TOLERANCE = 1e-6;
|
package/dist/index.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export type { Key, Track, TrackValue, Tracks } from './timing/track.js';
|
|
|
23
23
|
export { arc, circle, line, polygon, polyline, pointCount, pointOn, rect, slopeOn, splitCurve, straight, transformPath } from './figure/path.js';
|
|
24
24
|
export type { Cubic, Path, Subpath } from './figure/path.js';
|
|
25
25
|
export { pathFromData } from './figure/path-data.js';
|
|
26
|
+
export { TOLERANCE } from './figure/tolerance.js';
|
|
26
27
|
export { areaOf } from './figure/area.js';
|
|
27
28
|
export { differenceOf, intersectionOf, unionOf } from './figure/boolean.js';
|
|
28
29
|
export type { BooleanOptions } from './figure/boolean.js';
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ export { mat3 } from './values/mat3.js';
|
|
|
16
16
|
export { SAME_TIME, keyAt, sampleTrack, sampleTracks, withKey, withoutKey } from './timing/track.js';
|
|
17
17
|
export { arc, circle, line, polygon, polyline, pointCount, pointOn, rect, slopeOn, splitCurve, straight, transformPath } from './figure/path.js';
|
|
18
18
|
export { pathFromData } from './figure/path-data.js';
|
|
19
|
+
export { TOLERANCE } from './figure/tolerance.js';
|
|
19
20
|
export { areaOf } from './figure/area.js';
|
|
20
21
|
export { differenceOf, intersectionOf, unionOf } from './figure/boolean.js';
|
|
21
22
|
export { containsPoint, flattenPath, nearestEdge, windingAt } from './figure/inside.js';
|
package/package.json
CHANGED