@altpsyche/maths 0.12.0 → 1.0.0
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 +126 -353
- package/dist/figure/animation.d.ts +6 -4
- package/dist/figure/animation.js +20 -4
- package/dist/figure/annotate.d.ts +3 -2
- package/dist/figure/annotate.js +3 -2
- package/dist/figure/axis.js +5 -8
- package/dist/figure/axis3.d.ts +8 -0
- package/dist/figure/axis3.js +13 -0
- package/dist/figure/boolean.js +2 -2
- package/dist/figure/equation.d.ts +1 -1
- package/dist/figure/equation.js +2 -2
- package/dist/figure/field.d.ts +6 -6
- package/dist/figure/field.js +5 -6
- package/dist/figure/field3.d.ts +50 -0
- package/dist/figure/field3.js +60 -0
- package/dist/figure/figure.d.ts +4 -4
- package/dist/figure/figure.js +4 -4
- package/dist/figure/frames.d.ts +1 -1
- package/dist/figure/frames.js +4 -4
- package/dist/figure/grid.d.ts +19 -0
- package/dist/figure/grid.js +32 -0
- package/dist/figure/inside.d.ts +2 -2
- package/dist/figure/inside.js +3 -4
- package/dist/figure/length.d.ts +1 -3
- package/dist/figure/length.js +7 -10
- package/dist/figure/mark.d.ts +7 -2
- package/dist/figure/node.js +2 -3
- package/dist/figure/path-data.js +2 -3
- package/dist/figure/path.d.ts +1 -1
- package/dist/figure/path.js +3 -3
- package/dist/figure/plot.d.ts +1 -1
- package/dist/figure/plot.js +9 -13
- package/dist/figure/scale.d.ts +2 -2
- package/dist/figure/scale.js +3 -3
- package/dist/figure/section.d.ts +5 -2
- package/dist/figure/section.js +9 -16
- package/dist/figure/space.d.ts +8 -91
- package/dist/figure/space.js +7 -110
- package/dist/figure/surface3.d.ts +62 -0
- package/dist/figure/surface3.js +58 -0
- package/dist/index.d.ts +18 -12
- package/dist/index.js +10 -7
- package/dist/paint/number.js +1 -2
- package/dist/paint/svg.d.ts +40 -4
- package/dist/paint/svg.js +57 -14
- package/dist/values/colour.d.ts +43 -0
- package/dist/values/colour.js +110 -0
- package/package.json +1 -1
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* The shapes an annotation is made of, composed from marks rather than being
|
|
3
3
|
* marks of their own.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* A builder hands back a group, so an arrow is a shaft and a head with ids of
|
|
6
6
|
* their own and an animation naming the arrow reaches both. Making an arrow a
|
|
7
7
|
* single mark instead would mean one path that is stroked along its shaft and
|
|
8
|
-
* filled at its head, and no mark can be both.
|
|
8
|
+
* filled at its head, and no mark can be both. `bracePath` is the one call here
|
|
9
|
+
* that hands back a path, for a figure that wants the outline and not the group.
|
|
9
10
|
*/
|
|
10
11
|
import { type Vec2 } from '../values/vec2.js';
|
|
11
12
|
import { type Path } from './path.js';
|
package/dist/figure/annotate.js
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* The shapes an annotation is made of, composed from marks rather than being
|
|
3
3
|
* marks of their own.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* A builder hands back a group, so an arrow is a shaft and a head with ids of
|
|
6
6
|
* their own and an animation naming the arrow reaches both. Making an arrow a
|
|
7
7
|
* single mark instead would mean one path that is stroked along its shaft and
|
|
8
|
-
* filled at its head, and no mark can be both.
|
|
8
|
+
* filled at its head, and no mark can be both. `bracePath` is the one call here
|
|
9
|
+
* that hands back a path, for a figure that wants the outline and not the group.
|
|
9
10
|
*/
|
|
10
11
|
import { vec2 } from '../values/vec2.js';
|
|
11
12
|
import { circle, line, polygon, straight } from './path.js';
|
package/dist/figure/axis.js
CHANGED
|
@@ -12,7 +12,7 @@ import { vec2 } from '../values/vec2.js';
|
|
|
12
12
|
import { line, polygon } from './path.js';
|
|
13
13
|
import { group, shape, text } from './node.js';
|
|
14
14
|
import { multiplesOn, tickStep, ticksOn } from './ticks.js';
|
|
15
|
-
import {
|
|
15
|
+
import { toUnits } from './scale.js';
|
|
16
16
|
/** A head pointing along the line, apex at the end and base back along it. */
|
|
17
17
|
function head(apex, back, spread) {
|
|
18
18
|
const along = vec2.normalize(vec2.sub(apex, back));
|
|
@@ -37,11 +37,8 @@ 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
|
-
//
|
|
41
|
-
//
|
|
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.
|
|
40
|
+
// Each head stands beyond the line's end, or the outermost tick stands under a head instead of on
|
|
41
|
+
// the line. The line stops where a head begins, so a head that is not opaque shows nothing through.
|
|
45
42
|
const parts = [shape('line', line(at(low, 0), at(high, 0)), { stroke: options.stroke })];
|
|
46
43
|
if (tip > 0 && options.fill) {
|
|
47
44
|
const spread = options.spread ?? 0.6;
|
|
@@ -91,7 +88,7 @@ export function numberLine(name, scale, options) {
|
|
|
91
88
|
*/
|
|
92
89
|
export function axes(name, coords, options) {
|
|
93
90
|
const holdsOrigin = interval.holds(coords.x.graph, 0) && interval.holds(coords.y.graph, 0);
|
|
94
|
-
const seat = (scale) =>
|
|
91
|
+
const seat = (scale) => toUnits(scale, interval.clampTo(scale.graph, 0));
|
|
95
92
|
return group(name, [
|
|
96
93
|
numberLine('x', coords.x, {
|
|
97
94
|
...options,
|
|
@@ -116,7 +113,7 @@ function gridLines(coords, step, along, skipping) {
|
|
|
116
113
|
return multiplesOn(scale.graph, step)
|
|
117
114
|
.filter((value) => !(skipping && onStep(value, skipping)))
|
|
118
115
|
.map((value) => {
|
|
119
|
-
const [from, to] = ends(
|
|
116
|
+
const [from, to] = ends(toUnits(scale, value));
|
|
120
117
|
return shape(String(value), line(from, to), {});
|
|
121
118
|
});
|
|
122
119
|
}
|
package/dist/figure/axis3.d.ts
CHANGED
|
@@ -31,6 +31,14 @@ export interface Axes3Options {
|
|
|
31
31
|
tickLength?: number;
|
|
32
32
|
/** From the projected tick to the label's own anchor, in figure units. */
|
|
33
33
|
gap?: number;
|
|
34
|
+
/** What each axis is called, written past its far end. An axis this does not
|
|
35
|
+
* name carries no name, and nothing is written at all without a `fill` and a
|
|
36
|
+
* `size`. */
|
|
37
|
+
names?: {
|
|
38
|
+
x?: string;
|
|
39
|
+
y?: string;
|
|
40
|
+
z?: string;
|
|
41
|
+
};
|
|
34
42
|
family?: string;
|
|
35
43
|
weight?: number;
|
|
36
44
|
}
|
package/dist/figure/axis3.js
CHANGED
|
@@ -42,6 +42,19 @@ function oneAxis(which, bounds, camera, options) {
|
|
|
42
42
|
const origin = camera.project(vec3.ZERO).at;
|
|
43
43
|
const parts = [polyline3('line', [at(low), at(high)], camera, { stroke: options.stroke })];
|
|
44
44
|
parts.push(group('ticks', marked.map((tick) => polyline3(tick.label, [vec3.sub(at(tick.value), vec3.scale(across, half)), vec3.add(at(tick.value), vec3.scale(across, half))], camera, { stroke: options.stroke }))));
|
|
45
|
+
const name = options.names?.[which];
|
|
46
|
+
if (options.fill && size > 0 && name !== undefined) {
|
|
47
|
+
// Set beyond the label of the last tick, which stands at the same point and
|
|
48
|
+
// leans the same way, so the two would be written over each other.
|
|
49
|
+
parts.push(text3('name', at(high), name, size, camera, {
|
|
50
|
+
fill: options.fill,
|
|
51
|
+
family: options.family,
|
|
52
|
+
weight: options.weight,
|
|
53
|
+
align: 'middle',
|
|
54
|
+
baseline: 'middle',
|
|
55
|
+
offset: leaning(origin, camera.project(at(high)).at, gap + size),
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
45
58
|
if (options.fill && size > 0) {
|
|
46
59
|
// The three lines cross at the origin, so only one of them writes the number
|
|
47
60
|
// there and the other two would write it again in the same place.
|
package/dist/figure/boolean.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* outside an overlap, and a difference keeps the first path's copy of it.
|
|
21
21
|
*/
|
|
22
22
|
import { vec2 } from '../values/vec2.js';
|
|
23
|
-
import { pointOn,
|
|
23
|
+
import { pointOn, tangentOn } 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';
|
|
@@ -160,7 +160,7 @@ function sidesAgainst(pieces, other, tolerance) {
|
|
|
160
160
|
const middle = pointOn(piece.from, piece.curve, 0.5);
|
|
161
161
|
const edge = nearestEdge(other, middle);
|
|
162
162
|
if (edge && edge.gap <= tolerance) {
|
|
163
|
-
return vec2.dot(
|
|
163
|
+
return vec2.dot(tangentOn(piece.from, piece.curve, 0.5), edge.heading) >= 0 ? 'along' : 'against';
|
|
164
164
|
}
|
|
165
165
|
return windingAt(other, middle) !== 0 ? 'inside' : 'outside';
|
|
166
166
|
});
|
|
@@ -14,7 +14,7 @@ export interface Equation {
|
|
|
14
14
|
readonly marks: readonly PathMark[];
|
|
15
15
|
readonly box: EquationBox;
|
|
16
16
|
}
|
|
17
|
-
export declare function
|
|
17
|
+
export declare function equationOf(root: EquationElement): Equation;
|
|
18
18
|
/** One expression typeset and read, which is the two halves above in the order
|
|
19
19
|
* they are always used in. */
|
|
20
20
|
export declare function equationFromTex(tex: string): Promise<Equation>;
|
package/dist/figure/equation.js
CHANGED
|
@@ -125,7 +125,7 @@ function boxOf(svg) {
|
|
|
125
125
|
const [x = 0, y = 0, width = 0, height = 0] = numbers;
|
|
126
126
|
return { x, y: -(y + height), width, height };
|
|
127
127
|
}
|
|
128
|
-
export function
|
|
128
|
+
export function equationOf(root) {
|
|
129
129
|
const svg = svgOf(root);
|
|
130
130
|
const marks = [];
|
|
131
131
|
const mark = (path, stack, suffix) => {
|
|
@@ -157,7 +157,7 @@ export function equationMarks(root) {
|
|
|
157
157
|
/** One expression typeset and read, which is the two halves above in the order
|
|
158
158
|
* they are always used in. */
|
|
159
159
|
export async function equationFromTex(tex) {
|
|
160
|
-
return
|
|
160
|
+
return equationOf(await typesetElement(tex));
|
|
161
161
|
}
|
|
162
162
|
/**
|
|
163
163
|
* A typeset expression placed in a figure: one shape per glyph, fitted inside a
|
package/dist/figure/field.d.ts
CHANGED
|
@@ -31,12 +31,12 @@ export interface VectorFieldOptions {
|
|
|
31
31
|
x: number;
|
|
32
32
|
y: number;
|
|
33
33
|
};
|
|
34
|
-
/** The
|
|
35
|
-
*
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
/** The runs sampled, each the whole of the graph that way where it is left
|
|
35
|
+
* out. */
|
|
36
|
+
over?: {
|
|
37
|
+
x?: Interval;
|
|
38
|
+
y?: Interval;
|
|
39
|
+
};
|
|
40
40
|
/** How long a head is, in figure units. Four times the shaft's width unless
|
|
41
41
|
* named, which is what an arrow takes when nothing says. */
|
|
42
42
|
head?: number;
|
package/dist/figure/field.js
CHANGED
|
@@ -16,11 +16,9 @@
|
|
|
16
16
|
import { interval } from '../values/interval.js';
|
|
17
17
|
import { vec2 } from '../values/vec2.js';
|
|
18
18
|
import { pointOf } from './scale.js';
|
|
19
|
+
import { stepsOf } from './grid.js';
|
|
19
20
|
import { group } from './node.js';
|
|
20
21
|
import { arrow } from './annotate.js';
|
|
21
|
-
function stepsOf(resolution) {
|
|
22
|
-
return typeof resolution === 'number' ? { x: resolution, y: resolution } : resolution;
|
|
23
|
-
}
|
|
24
22
|
/**
|
|
25
23
|
* The arrows of a field over a graph, one group per sample, named by its column
|
|
26
24
|
* and row so a stagger can reach them one at a time.
|
|
@@ -40,9 +38,10 @@ function stepsOf(resolution) {
|
|
|
40
38
|
* pointing the other, at the same magnitude.
|
|
41
39
|
*/
|
|
42
40
|
export function vectorField(name, coords, of, options) {
|
|
43
|
-
const steps = stepsOf(options.resolution ?? 12);
|
|
44
|
-
const
|
|
45
|
-
const
|
|
41
|
+
const steps = stepsOf(options.resolution ?? 12, 'x', 'y');
|
|
42
|
+
const over = options.over ?? {};
|
|
43
|
+
const overX = interval.ordered(over.x ?? coords.x.graph);
|
|
44
|
+
const overY = interval.ordered(over.y ?? coords.y.graph);
|
|
46
45
|
const children = [];
|
|
47
46
|
for (let column = 0; column < steps.x; column += 1) {
|
|
48
47
|
for (let row = 0; row < steps.y; row += 1) {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A field of vectors in space, drawn as an arrow at every sample.
|
|
3
|
+
*
|
|
4
|
+
* An arrow is measured in the world's own units rather than the figure's, unlike
|
|
5
|
+
* the arrows of a flat field, because a length in space is what perspective is
|
|
6
|
+
* for. Its head stays in figure units, since the head is drawn on the page.
|
|
7
|
+
*/
|
|
8
|
+
import { type Interval } from '../values/interval.js';
|
|
9
|
+
import { type Vec3 } from '../values/vec3.js';
|
|
10
|
+
import type { Colour } from './mark.js';
|
|
11
|
+
import type { ArrowOptions } from './annotate.js';
|
|
12
|
+
import type { Camera3 } from './camera.js';
|
|
13
|
+
import { type SpaceItem } from './space.js';
|
|
14
|
+
import type { GroupNode } from './node.js';
|
|
15
|
+
export type VectorField3Options = ArrowOptions & {
|
|
16
|
+
/** The box the samples are taken in, nothing to one each way unless named. */
|
|
17
|
+
over?: {
|
|
18
|
+
x?: Interval;
|
|
19
|
+
y?: Interval;
|
|
20
|
+
z?: Interval;
|
|
21
|
+
};
|
|
22
|
+
/** How many samples each way. One number is all three. */
|
|
23
|
+
resolution?: number | {
|
|
24
|
+
x: number;
|
|
25
|
+
y: number;
|
|
26
|
+
z: number;
|
|
27
|
+
};
|
|
28
|
+
/** How long an arrow is, in the world's own units, from the magnitude of the
|
|
29
|
+
* vector at its own sample. */
|
|
30
|
+
lengthOf: (magnitude: number) => number;
|
|
31
|
+
/** What colour an arrow is, from that same magnitude. */
|
|
32
|
+
colourFor: (magnitude: number) => Colour;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The arrows of a field sampled over a box in space, and the points each was
|
|
36
|
+
* drawn from, for a figure that sorts them among pieces of its own.
|
|
37
|
+
*
|
|
38
|
+
* An arrow is measured in the world's own units rather than the figure's, unlike
|
|
39
|
+
* the arrows of a flat field, because a length in space is what perspective is
|
|
40
|
+
* for: a far arrow drawing shorter than a near one of the same magnitude is what
|
|
41
|
+
* says which is far. Its head is still in figure units, since the head is drawn
|
|
42
|
+
* on the page.
|
|
43
|
+
*
|
|
44
|
+
* A sample sits at the middle of its cell and the count is fixed by the
|
|
45
|
+
* resolution, so a gate can hold it as the eye moves. A sample whose vector is
|
|
46
|
+
* nothing draws no arrow there.
|
|
47
|
+
*/
|
|
48
|
+
export declare function fieldArrows3(name: string, of: (at: Vec3) => Vec3, camera: Camera3, options: VectorField3Options): SpaceItem[];
|
|
49
|
+
/** A field of vectors in space, drawn as arrows ordered back to front. */
|
|
50
|
+
export declare function vectorField3(name: string, of: (at: Vec3) => Vec3, camera: Camera3, options: VectorField3Options): GroupNode;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A field of vectors in space, drawn as an arrow at every sample.
|
|
3
|
+
*
|
|
4
|
+
* An arrow is measured in the world's own units rather than the figure's, unlike
|
|
5
|
+
* the arrows of a flat field, because a length in space is what perspective is
|
|
6
|
+
* for. Its head stays in figure units, since the head is drawn on the page.
|
|
7
|
+
*/
|
|
8
|
+
import { interval } from '../values/interval.js';
|
|
9
|
+
import { vec3 } from '../values/vec3.js';
|
|
10
|
+
import { stepsOf } from './grid.js';
|
|
11
|
+
import { arrow3, scene3 } from './space.js';
|
|
12
|
+
/**
|
|
13
|
+
* The arrows of a field sampled over a box in space, and the points each was
|
|
14
|
+
* drawn from, for a figure that sorts them among pieces of its own.
|
|
15
|
+
*
|
|
16
|
+
* An arrow is measured in the world's own units rather than the figure's, unlike
|
|
17
|
+
* the arrows of a flat field, because a length in space is what perspective is
|
|
18
|
+
* for: a far arrow drawing shorter than a near one of the same magnitude is what
|
|
19
|
+
* says which is far. Its head is still in figure units, since the head is drawn
|
|
20
|
+
* on the page.
|
|
21
|
+
*
|
|
22
|
+
* A sample sits at the middle of its cell and the count is fixed by the
|
|
23
|
+
* resolution, so a gate can hold it as the eye moves. A sample whose vector is
|
|
24
|
+
* nothing draws no arrow there.
|
|
25
|
+
*/
|
|
26
|
+
export function fieldArrows3(name, of, camera, options) {
|
|
27
|
+
const { over = {}, resolution = 6, lengthOf, colourFor, ...rest } = options;
|
|
28
|
+
const box = {
|
|
29
|
+
x: interval.ordered(over.x ?? interval(0, 1)),
|
|
30
|
+
y: interval.ordered(over.y ?? interval(0, 1)),
|
|
31
|
+
z: interval.ordered(over.z ?? interval(0, 1)),
|
|
32
|
+
};
|
|
33
|
+
const steps = stepsOf(resolution, 'x', 'y', 'z');
|
|
34
|
+
const items = [];
|
|
35
|
+
for (let i = 0; i < steps.x; i += 1) {
|
|
36
|
+
for (let j = 0; j < steps.y; j += 1) {
|
|
37
|
+
for (let k = 0; k < steps.z; k += 1) {
|
|
38
|
+
const from = vec3(interval.at(box.x, (i + 0.5) / steps.x), interval.at(box.y, (j + 0.5) / steps.y), interval.at(box.z, (k + 0.5) / steps.z));
|
|
39
|
+
const vector = of(from);
|
|
40
|
+
const magnitude = vec3.magnitude(vector);
|
|
41
|
+
const length = lengthOf(magnitude);
|
|
42
|
+
if (!(magnitude > 0) || !Number.isFinite(length) || !(length > 0))
|
|
43
|
+
continue;
|
|
44
|
+
const to = vec3.add(from, vec3.scale(vector, length / magnitude));
|
|
45
|
+
items.push({
|
|
46
|
+
points: [from, to],
|
|
47
|
+
node: arrow3(`${name}/${i}-${j}-${k}`, from, to, camera, {
|
|
48
|
+
...rest,
|
|
49
|
+
stroke: { ...rest.stroke, colour: colourFor(magnitude) },
|
|
50
|
+
}),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return items;
|
|
56
|
+
}
|
|
57
|
+
/** A field of vectors in space, drawn as arrows ordered back to front. */
|
|
58
|
+
export function vectorField3(name, of, camera, options) {
|
|
59
|
+
return scene3(name, fieldArrows3('arrow', of, camera, options), camera);
|
|
60
|
+
}
|
package/dist/figure/figure.d.ts
CHANGED
|
@@ -19,13 +19,13 @@ import type { Mark } from './mark.js';
|
|
|
19
19
|
* is what lets geometry follow a number rather than only be moved about: a
|
|
20
20
|
* radius that is keyed makes a circle that is genuinely a different circle each
|
|
21
21
|
* frame. */
|
|
22
|
-
export type
|
|
22
|
+
export type TrackValues = Record<string, TrackValue>;
|
|
23
23
|
export interface Figure {
|
|
24
24
|
/** How much of the world the figure shows, in its own units. */
|
|
25
25
|
extent: ExtentChoice;
|
|
26
26
|
fit?: Fit;
|
|
27
27
|
/** The tree, either fixed or rebuilt from the clock and the sampled values. */
|
|
28
|
-
scene: Node | ((seconds: number, values:
|
|
28
|
+
scene: Node | ((seconds: number, values: TrackValues) => Node);
|
|
29
29
|
tracks?: Tracks;
|
|
30
30
|
timeline?: Timeline;
|
|
31
31
|
/** Overrides the timeline's own length, for a figure that should hold after
|
|
@@ -44,7 +44,7 @@ export interface Figure {
|
|
|
44
44
|
}
|
|
45
45
|
export declare function durationOf(figure: Figure): number;
|
|
46
46
|
/** The marks a figure shows at a time. */
|
|
47
|
-
export declare function
|
|
47
|
+
export declare function marksAt(figure: Figure, seconds: number): readonly Mark[];
|
|
48
48
|
/**
|
|
49
49
|
* The matrix a painter needs at a time, in one call.
|
|
50
50
|
*
|
|
@@ -58,6 +58,6 @@ export declare function viewAt(figure: Figure, seconds: number, width: number, h
|
|
|
58
58
|
* behind that flag. The comparison is by tolerance rather than exactly, because
|
|
59
59
|
* the sine and cosine a figure is built from are not specified to the last bit
|
|
60
60
|
* and differ between engines. */
|
|
61
|
-
export declare function
|
|
61
|
+
export declare function isLoop(figure: Figure, tolerance?: number): boolean;
|
|
62
62
|
/** Two lists holding the same marks in the same order, to a tolerance. */
|
|
63
63
|
export declare function sameMarks(one: readonly Mark[], two: readonly Mark[], tolerance?: number): boolean;
|
package/dist/figure/figure.js
CHANGED
|
@@ -16,7 +16,7 @@ export function durationOf(figure) {
|
|
|
16
16
|
return figure.duration ?? figure.timeline?.duration ?? 0;
|
|
17
17
|
}
|
|
18
18
|
/** The marks a figure shows at a time. */
|
|
19
|
-
export function
|
|
19
|
+
export function marksAt(figure, seconds) {
|
|
20
20
|
const values = figure.tracks ? sampleTracks(figure.tracks, seconds) : {};
|
|
21
21
|
const tree = typeof figure.scene === 'function' ? figure.scene(seconds, values) : figure.scene;
|
|
22
22
|
const marks = flatten(tree);
|
|
@@ -38,9 +38,9 @@ export function viewAt(figure, seconds, width, height) {
|
|
|
38
38
|
* behind that flag. The comparison is by tolerance rather than exactly, because
|
|
39
39
|
* the sine and cosine a figure is built from are not specified to the last bit
|
|
40
40
|
* and differ between engines. */
|
|
41
|
-
export function
|
|
42
|
-
const start =
|
|
43
|
-
const end =
|
|
41
|
+
export function isLoop(figure, tolerance = 1e-6) {
|
|
42
|
+
const start = marksAt(figure, 0);
|
|
43
|
+
const end = marksAt(figure, durationOf(figure));
|
|
44
44
|
return sameMarks(start, end, tolerance);
|
|
45
45
|
}
|
|
46
46
|
/** Two lists holding the same marks in the same order, to a tolerance. */
|
package/dist/figure/frames.d.ts
CHANGED
|
@@ -52,6 +52,6 @@ export type FramesOptions = FrameStep & {
|
|
|
52
52
|
* A figure with no duration is one frame, since a picture that never moves still
|
|
53
53
|
* has a picture.
|
|
54
54
|
*/
|
|
55
|
-
export declare function
|
|
55
|
+
export declare function frameTimesOf(figure: Figure, step: FrameStep): number[];
|
|
56
56
|
/** A figure walked at a fixed step, a frame at a time. */
|
|
57
57
|
export declare function framesOf(figure: Figure, options: FramesOptions): Generator<Frame>;
|
package/dist/figure/frames.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* sixty frames a second is six hundred frames of every mark it draws, and a
|
|
11
11
|
* recorder encodes a frame and throws it away.
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
13
|
+
import { marksAt, durationOf, viewAt } from './figure.js';
|
|
14
14
|
/**
|
|
15
15
|
* The times a walk reads, which a recorder needs before it has drawn anything to
|
|
16
16
|
* say how far along it is.
|
|
@@ -20,7 +20,7 @@ import { at, durationOf, viewAt } from './figure.js';
|
|
|
20
20
|
* A figure with no duration is one frame, since a picture that never moves still
|
|
21
21
|
* has a picture.
|
|
22
22
|
*/
|
|
23
|
-
export function
|
|
23
|
+
export function frameTimesOf(figure, step) {
|
|
24
24
|
const duration = durationOf(figure);
|
|
25
25
|
const count = step.fps === undefined
|
|
26
26
|
? Math.max(1, Math.round(step.frames))
|
|
@@ -30,13 +30,13 @@ export function frameTimes(figure, step) {
|
|
|
30
30
|
}
|
|
31
31
|
/** A figure walked at a fixed step, a frame at a time. */
|
|
32
32
|
export function* framesOf(figure, options) {
|
|
33
|
-
const times =
|
|
33
|
+
const times = frameTimesOf(figure, options);
|
|
34
34
|
for (let index = 0; index < times.length; index += 1) {
|
|
35
35
|
const seconds = times[index];
|
|
36
36
|
yield {
|
|
37
37
|
index,
|
|
38
38
|
seconds,
|
|
39
|
-
marks:
|
|
39
|
+
marks: marksAt(figure, seconds),
|
|
40
40
|
view: viewAt(figure, seconds, options.width, options.height),
|
|
41
41
|
};
|
|
42
42
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sampling a surface and a section share: how many steps a resolution asks
|
|
3
|
+
* for, and where the corners of that grid land.
|
|
4
|
+
*
|
|
5
|
+
* A cell reads its four corners out of the grid rather than working each of them
|
|
6
|
+
* out again. Four cells meet at every inside corner, so computing them per cell
|
|
7
|
+
* asks the surface for the same point four times, and the surface is the
|
|
8
|
+
* caller's own function with no promise about what it costs.
|
|
9
|
+
*/
|
|
10
|
+
import { type Interval } from '../values/interval.js';
|
|
11
|
+
import type { Vec3 } from '../values/vec3.js';
|
|
12
|
+
/** A resolution given as one number is that many steps along every named way. */
|
|
13
|
+
export declare function stepsOf<K extends string>(resolution: number | Record<K, number>, ...ways: readonly K[]): Record<K, number>;
|
|
14
|
+
/** The corners of a grid over a surface, taking both edges of each run, so a
|
|
15
|
+
* grid of n by m cells is n + 1 by m + 1 corners. */
|
|
16
|
+
export declare function cornersOf(of: (u: number, v: number) => Vec3, u: Interval, v: Interval, steps: {
|
|
17
|
+
u: number;
|
|
18
|
+
v: number;
|
|
19
|
+
}): Vec3[][];
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sampling a surface and a section share: how many steps a resolution asks
|
|
3
|
+
* for, and where the corners of that grid land.
|
|
4
|
+
*
|
|
5
|
+
* A cell reads its four corners out of the grid rather than working each of them
|
|
6
|
+
* out again. Four cells meet at every inside corner, so computing them per cell
|
|
7
|
+
* asks the surface for the same point four times, and the surface is the
|
|
8
|
+
* caller's own function with no promise about what it costs.
|
|
9
|
+
*/
|
|
10
|
+
import { interval } from '../values/interval.js';
|
|
11
|
+
/** A resolution given as one number is that many steps along every named way. */
|
|
12
|
+
export function stepsOf(resolution, ...ways) {
|
|
13
|
+
if (typeof resolution !== 'number')
|
|
14
|
+
return resolution;
|
|
15
|
+
const steps = {};
|
|
16
|
+
for (const way of ways)
|
|
17
|
+
steps[way] = resolution;
|
|
18
|
+
return steps;
|
|
19
|
+
}
|
|
20
|
+
/** The corners of a grid over a surface, taking both edges of each run, so a
|
|
21
|
+
* grid of n by m cells is n + 1 by m + 1 corners. */
|
|
22
|
+
export function cornersOf(of, u, v, steps) {
|
|
23
|
+
const grid = [];
|
|
24
|
+
for (let i = 0; i <= steps.u; i += 1) {
|
|
25
|
+
const column = [];
|
|
26
|
+
for (let j = 0; j <= steps.v; j += 1) {
|
|
27
|
+
column.push(of(interval.at(u, i / steps.u), interval.at(v, j / steps.v)));
|
|
28
|
+
}
|
|
29
|
+
grid.push(column);
|
|
30
|
+
}
|
|
31
|
+
return grid;
|
|
32
|
+
}
|
package/dist/figure/inside.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export declare function flattenPath(path: Path, options?: FlattenOptions): Vec2[
|
|
|
32
32
|
export declare function windingAt(loops: readonly (readonly Vec2[])[], point: Vec2): number;
|
|
33
33
|
/** The nearest straight run of a flattening to a point: how far off it is, and
|
|
34
34
|
* which way that run goes. */
|
|
35
|
-
export interface
|
|
35
|
+
export interface FlatEdge {
|
|
36
36
|
readonly gap: number;
|
|
37
37
|
readonly heading: Vec2;
|
|
38
38
|
}
|
|
@@ -43,7 +43,7 @@ export interface Edge {
|
|
|
43
43
|
* near it, which the winding count cannot answer because a point on the edge
|
|
44
44
|
* itself is the one place the count has no answer for.
|
|
45
45
|
*/
|
|
46
|
-
export declare function nearestEdge(loops: readonly (readonly Vec2[])[], point: Vec2):
|
|
46
|
+
export declare function nearestEdge(loops: readonly (readonly Vec2[])[], point: Vec2): FlatEdge | null;
|
|
47
47
|
/**
|
|
48
48
|
* Whether a path holds a point, under the nonzero rule.
|
|
49
49
|
*
|
package/dist/figure/inside.js
CHANGED
|
@@ -21,10 +21,9 @@ const DEPTH = 24;
|
|
|
21
21
|
/**
|
|
22
22
|
* How many points a whole flattening may hold before it is refused.
|
|
23
23
|
*
|
|
24
|
-
* Halving is the only bound the depth gives,
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* is a thousandfold.
|
|
24
|
+
* Halving is the only bound the depth gives, so with no ceiling on the count a
|
|
25
|
+
* fine tolerance is a machine out of memory rather than a fine flattening. The
|
|
26
|
+
* ceiling sits far above what the finest tolerance a figure asks for reaches.
|
|
28
27
|
*/
|
|
29
28
|
const POINTS = 1_000_000;
|
|
30
29
|
/** How far the two controls sit from the straight run between the ends.
|
package/dist/figure/length.d.ts
CHANGED
|
@@ -33,9 +33,7 @@ export declare function measurePath(path: Path): Measure;
|
|
|
33
33
|
* Reading the fraction of the length as the parameter is exact only where the
|
|
34
34
|
* curve moves at one rate, which is a straight line. Walking the table instead
|
|
35
35
|
* and interpolating inside the one sample the length lands in makes a cut by
|
|
36
|
-
* length even along a curve as well
|
|
37
|
-
* twenty even fractions of its length was 4.7e-3 of the whole out at the worst
|
|
38
|
-
* of them and is 1.4e-4 out now.
|
|
36
|
+
* length even along a curve as well.
|
|
39
37
|
*/
|
|
40
38
|
export declare function parameterAt(measured: Measured, wanted: number): number;
|
|
41
39
|
/** How long a path is, in figure units, across every subpath it holds. It reads
|
package/dist/figure/length.js
CHANGED
|
@@ -16,13 +16,12 @@ import { clamp } from '../values/scalar.js';
|
|
|
16
16
|
/**
|
|
17
17
|
* How many samples measure one piece.
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* which nothing here has asked for.
|
|
19
|
+
* A chord cuts the corner off the arc it spans, so a total read this way is
|
|
20
|
+
* short of the true one. What the count is chosen for is the evenness of a walk
|
|
21
|
+
* rather than the total, and a share of the length is a ratio the shortfall
|
|
22
|
+
* largely cancels out of. Doubling it costs twice the work in every cut and
|
|
23
|
+
* every walk and buys a total four times closer, which nothing here has asked
|
|
24
|
+
* for.
|
|
26
25
|
*/
|
|
27
26
|
const SAMPLES = 16;
|
|
28
27
|
function measureCurve(from, curve) {
|
|
@@ -58,9 +57,7 @@ export function measurePath(path) {
|
|
|
58
57
|
* Reading the fraction of the length as the parameter is exact only where the
|
|
59
58
|
* curve moves at one rate, which is a straight line. Walking the table instead
|
|
60
59
|
* and interpolating inside the one sample the length lands in makes a cut by
|
|
61
|
-
* length even along a curve as well
|
|
62
|
-
* twenty even fractions of its length was 4.7e-3 of the whole out at the worst
|
|
63
|
-
* of them and is 1.4e-4 out now.
|
|
60
|
+
* length even along a curve as well.
|
|
64
61
|
*/
|
|
65
62
|
export function parameterAt(measured, wanted) {
|
|
66
63
|
const { upTo, total } = measured;
|
package/dist/figure/mark.d.ts
CHANGED
|
@@ -6,8 +6,13 @@
|
|
|
6
6
|
* two-dimensional canvas can both do, rather than the union. A figure reaching
|
|
7
7
|
* for something only one of them has would look right on the page and lose it
|
|
8
8
|
* without a word in a recording, which is the worst way to find out. So there
|
|
9
|
-
* are no filters, no blend modes
|
|
10
|
-
*
|
|
9
|
+
* are no filters, no blend modes and no clipping here, and adding one means
|
|
10
|
+
* adding it to both painters in the same change.
|
|
11
|
+
*
|
|
12
|
+
* A gradient is refused for a different reason, since both painters draw one.
|
|
13
|
+
* SVG names a gradient with an element carrying an id and a canvas names it with
|
|
14
|
+
* an object built from the context, and a colour here is text that both take as
|
|
15
|
+
* it stands.
|
|
11
16
|
*/
|
|
12
17
|
import type { Vec2 } from '../values/vec2.js';
|
|
13
18
|
import type { Path } from './path.js';
|
package/dist/figure/node.js
CHANGED
|
@@ -57,9 +57,8 @@ function walk(node, prefix, transform, style, into) {
|
|
|
57
57
|
}
|
|
58
58
|
const settled = inherited(style, node);
|
|
59
59
|
const opacity = settled.opacity ?? 1;
|
|
60
|
-
// A group that scales makes the lines inside it thicker, the way it makes
|
|
61
|
-
//
|
|
62
|
-
// geometry did rather than staying at the number the author typed.
|
|
60
|
+
// A group that scales makes the lines inside it thicker, the way it makes everything else bigger,
|
|
61
|
+
// so the width goes through the transform the geometry did rather than staying as it was typed.
|
|
63
62
|
const scale = mat3.scaleFactor(transform);
|
|
64
63
|
if (node.kind === 'shape') {
|
|
65
64
|
if (!settled.fill && !settled.stroke)
|
package/dist/figure/path-data.js
CHANGED
|
@@ -139,9 +139,8 @@ export function pathFromData(d) {
|
|
|
139
139
|
let moved = false;
|
|
140
140
|
let command = '';
|
|
141
141
|
let index = 0;
|
|
142
|
-
// Held per kind because an S reflects a cubic's second control and a T a
|
|
143
|
-
//
|
|
144
|
-
// segment before it was neither.
|
|
142
|
+
// Held per kind because an S reflects a cubic's second control and a T a quadratic's only one, and
|
|
143
|
+
// either falls back to the current point when the segment before it was neither.
|
|
145
144
|
let lastCubic;
|
|
146
145
|
let lastQuadratic;
|
|
147
146
|
const take = (count) => {
|
package/dist/figure/path.d.ts
CHANGED
|
@@ -50,7 +50,7 @@ export declare function arc(centre: Vec2, radius: number, fromAngle: number, toA
|
|
|
50
50
|
export declare function pointOn(from: Vec2, curve: Cubic, along: number): Vec2;
|
|
51
51
|
/** Which way a piece is heading at a fraction along it, which is the derivative
|
|
52
52
|
* of a cubic and so a quadratic over the gaps between neighbouring points. */
|
|
53
|
-
export declare function
|
|
53
|
+
export declare function tangentOn(from: Vec2, curve: Cubic, along: number): Vec2;
|
|
54
54
|
/**
|
|
55
55
|
* One piece cut into two at a fraction, both pieces drawing what the whole
|
|
56
56
|
* drew, by de Casteljau's construction.
|
package/dist/figure/path.js
CHANGED
|
@@ -55,8 +55,8 @@ export function rect(corner, width, height) {
|
|
|
55
55
|
* fraction of the radius.
|
|
56
56
|
*
|
|
57
57
|
* Four cubics cannot be a circle exactly, and this is the value that makes the
|
|
58
|
-
* error smallest: the arc passes through both ends and the midpoint, and
|
|
59
|
-
*
|
|
58
|
+
* error smallest: the arc passes through both ends and the midpoint, and leaves
|
|
59
|
+
* the true radius by a few parts in ten thousand of it between them.
|
|
60
60
|
*/
|
|
61
61
|
const KAPPA = 0.5522847498307936;
|
|
62
62
|
/** A circle as four cubic quarters, anticlockwise from the positive x axis. */
|
|
@@ -123,7 +123,7 @@ export function pointOn(from, curve, along) {
|
|
|
123
123
|
}
|
|
124
124
|
/** Which way a piece is heading at a fraction along it, which is the derivative
|
|
125
125
|
* of a cubic and so a quadratic over the gaps between neighbouring points. */
|
|
126
|
-
export function
|
|
126
|
+
export function tangentOn(from, curve, along) {
|
|
127
127
|
const u = 1 - along;
|
|
128
128
|
const a = 3 * u * u;
|
|
129
129
|
const b = 6 * u * along;
|
package/dist/figure/plot.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ import type { Fill, Stroke } from './mark.js';
|
|
|
22
22
|
import { type Path } from './path.js';
|
|
23
23
|
export interface PlotOptions {
|
|
24
24
|
/** How many pieces the curve is cut into. */
|
|
25
|
-
|
|
25
|
+
resolution?: number;
|
|
26
26
|
/** The run of x the curve is drawn over, which is the whole width of the graph
|
|
27
27
|
* where it is left out. */
|
|
28
28
|
over?: Interval;
|