@altpsyche/maths 0.11.0 → 0.13.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 +124 -326
- 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 +57 -0
- package/dist/figure/frames.js +43 -0
- 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 +19 -11
- package/dist/index.js +10 -6
- package/dist/paint/number.js +1 -2
- package/dist/paint/svg.d.ts +29 -4
- package/dist/paint/svg.js +49 -14
- package/dist/values/colour.d.ts +43 -0
- package/dist/values/colour.js +110 -0
- package/package.json +1 -1
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;
|
package/dist/figure/plot.js
CHANGED
|
@@ -17,15 +17,14 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { interval } from '../values/interval.js';
|
|
19
19
|
import { vec2 } from '../values/vec2.js';
|
|
20
|
-
import { pointOf,
|
|
20
|
+
import { pointOf, toUnits } from './scale.js';
|
|
21
21
|
import { group, shape } from './node.js';
|
|
22
22
|
import { line, polygon, straight } from './path.js';
|
|
23
23
|
/**
|
|
24
24
|
* How many pieces a curve is cut into when a figure does not say.
|
|
25
25
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* surface anything here is drawn at.
|
|
26
|
+
* The count is what draws a sine over two turns under a tenth of a pixel from
|
|
27
|
+
* the true one on the largest surface anything here is drawn at.
|
|
29
28
|
*/
|
|
30
29
|
const SAMPLES = 96;
|
|
31
30
|
/**
|
|
@@ -34,10 +33,8 @@ const SAMPLES = 96;
|
|
|
34
33
|
*
|
|
35
34
|
* An end with evenly spaced neighbours takes the three-point one-sided
|
|
36
35
|
* difference, which is second order like the middle and exact for a quadratic.
|
|
37
|
-
* An end that was cut at the edge of the graph is not evenly spaced, so
|
|
38
|
-
*
|
|
39
|
-
* three-point ends leave an uncut curve exact where the two-point ends leave it
|
|
40
|
-
* 3.7e-4 figure units out.
|
|
36
|
+
* An end that was cut at the edge of the graph is not evenly spaced, so the
|
|
37
|
+
* three-point form does not hold there and it takes the two-point difference.
|
|
41
38
|
*/
|
|
42
39
|
function slopes(xs, ys) {
|
|
43
40
|
const last = xs.length - 1;
|
|
@@ -95,7 +92,7 @@ function crossing(of, drawable, bounds, inside, outside) {
|
|
|
95
92
|
* that makes the cubic pass through both samples at both slopes.
|
|
96
93
|
*/
|
|
97
94
|
export function plot(coords, of, options = {}) {
|
|
98
|
-
const samples = Math.max(1, Math.round(options.
|
|
95
|
+
const samples = Math.max(1, Math.round(options.resolution ?? SAMPLES));
|
|
99
96
|
const { from, to } = interval.ordered(options.over ?? coords.x.graph);
|
|
100
97
|
if (!(to > from))
|
|
101
98
|
return [];
|
|
@@ -165,7 +162,7 @@ export function plot(coords, of, options = {}) {
|
|
|
165
162
|
* samplings that agree to within a sample.
|
|
166
163
|
*/
|
|
167
164
|
export function areaUnder(coords, of, over, options = {}) {
|
|
168
|
-
const foot =
|
|
165
|
+
const foot = toUnits(coords.y, interval.clampTo(coords.y.graph, options.baseline ?? 0));
|
|
169
166
|
return plot(coords, of, { ...options, over }).map((top) => {
|
|
170
167
|
const last = top.curves.length > 0 ? top.curves[top.curves.length - 1].to : top.start;
|
|
171
168
|
const under = vec2(last.x, foot);
|
|
@@ -199,9 +196,8 @@ export function riemannBars(name, coords, of, options = {}) {
|
|
|
199
196
|
if (!Number.isFinite(y))
|
|
200
197
|
continue;
|
|
201
198
|
const top = interval.clampTo(coords.y.graph, y);
|
|
202
|
-
// Built from
|
|
203
|
-
//
|
|
204
|
-
// number, and adding a height back on to the near corner overshoots it.
|
|
199
|
+
// Built from four corners rather than a corner and a size: a bar whose top is held on the
|
|
200
|
+
// graph's own edge has that edge exactly, and adding a height back on overshoots it.
|
|
205
201
|
const corner = pointOf(coords, left, foot);
|
|
206
202
|
const far = pointOf(coords, right, top);
|
|
207
203
|
children.push(shape(String(bar), polygon([corner, vec2(far.x, corner.y), far, vec2(corner.x, far.y)]), {}));
|
package/dist/figure/scale.d.ts
CHANGED
|
@@ -19,10 +19,10 @@ export interface Scale {
|
|
|
19
19
|
}
|
|
20
20
|
export declare function scaleOf(graph: Interval, units: Interval): Scale;
|
|
21
21
|
/** A number on the axis, as a place in the figure's own units. */
|
|
22
|
-
export declare function
|
|
22
|
+
export declare function toUnits(scale: Scale, value: number): number;
|
|
23
23
|
/** A place in the figure's own units, as a number on the axis, which is what a
|
|
24
24
|
* reader pointing at the picture is asking for. */
|
|
25
|
-
export declare function
|
|
25
|
+
export declare function toGraph(scale: Scale, place: number): number;
|
|
26
26
|
export interface Coords {
|
|
27
27
|
readonly x: Scale;
|
|
28
28
|
readonly y: Scale;
|
package/dist/figure/scale.js
CHANGED
|
@@ -15,12 +15,12 @@ export function scaleOf(graph, units) {
|
|
|
15
15
|
return { graph, units };
|
|
16
16
|
}
|
|
17
17
|
/** A number on the axis, as a place in the figure's own units. */
|
|
18
|
-
export function
|
|
18
|
+
export function toUnits(scale, value) {
|
|
19
19
|
return interval.remap(value, scale.graph, scale.units);
|
|
20
20
|
}
|
|
21
21
|
/** A place in the figure's own units, as a number on the axis, which is what a
|
|
22
22
|
* reader pointing at the picture is asking for. */
|
|
23
|
-
export function
|
|
23
|
+
export function toGraph(scale, place) {
|
|
24
24
|
return interval.remap(place, scale.units, scale.graph);
|
|
25
25
|
}
|
|
26
26
|
export function coordsOf(x, y) {
|
|
@@ -28,5 +28,5 @@ export function coordsOf(x, y) {
|
|
|
28
28
|
}
|
|
29
29
|
/** A pair of graph numbers as a point in the figure's own units. */
|
|
30
30
|
export function pointOf(coords, x, y) {
|
|
31
|
-
return vec2(
|
|
31
|
+
return vec2(toUnits(coords.x, x), toUnits(coords.y, y));
|
|
32
32
|
}
|
package/dist/figure/section.d.ts
CHANGED
|
@@ -19,8 +19,11 @@ export interface Plane {
|
|
|
19
19
|
normal: Vec3;
|
|
20
20
|
}
|
|
21
21
|
export interface SectionOptions {
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
/** The runs of the two parameters, nothing to one each unless named. */
|
|
23
|
+
over?: {
|
|
24
|
+
u?: Interval;
|
|
25
|
+
v?: Interval;
|
|
26
|
+
};
|
|
24
27
|
resolution?: number | {
|
|
25
28
|
u: number;
|
|
26
29
|
v: number;
|
package/dist/figure/section.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { interval } from '../values/interval.js';
|
|
14
14
|
import { vec3 } from '../values/vec3.js';
|
|
15
15
|
import { TOLERANCE } from './tolerance.js';
|
|
16
|
+
import { cornersOf, stepsOf } from './grid.js';
|
|
16
17
|
/** Which edge of a cell each pair of corners is, going round from the corner at
|
|
17
18
|
* the low end of both parameters. */
|
|
18
19
|
const EDGES = [
|
|
@@ -128,20 +129,13 @@ function joinEnds(runs, tolerance) {
|
|
|
128
129
|
* so drawing the points as they are given draws the loop closed.
|
|
129
130
|
*/
|
|
130
131
|
export function sectionOf(of, plane, options = {}) {
|
|
131
|
-
const {
|
|
132
|
-
const
|
|
132
|
+
const { over = {}, resolution = 24, tolerance = TOLERANCE } = options;
|
|
133
|
+
const u = over.u ?? interval(0, 1);
|
|
134
|
+
const v = over.v ?? interval(0, 1);
|
|
135
|
+
const steps = stepsOf(resolution, 'u', 'v');
|
|
133
136
|
const facing = vec3.normalize(plane.normal);
|
|
134
|
-
const sample =
|
|
135
|
-
const gap =
|
|
136
|
-
for (let i = 0; i <= steps.u; i += 1) {
|
|
137
|
-
sample.push([]);
|
|
138
|
-
gap.push([]);
|
|
139
|
-
for (let j = 0; j <= steps.v; j += 1) {
|
|
140
|
-
const point = of(interval.at(u, i / steps.u), interval.at(v, j / steps.v));
|
|
141
|
-
sample[i].push(point);
|
|
142
|
-
gap[i].push(vec3.dot(facing, vec3.sub(point, plane.point)));
|
|
143
|
-
}
|
|
144
|
-
}
|
|
137
|
+
const sample = cornersOf(of, u, v, steps);
|
|
138
|
+
const gap = sample.map((column) => column.map((point) => vec3.dot(facing, vec3.sub(point, plane.point))));
|
|
145
139
|
const points = [];
|
|
146
140
|
const found = new Map();
|
|
147
141
|
const segments = [];
|
|
@@ -169,9 +163,8 @@ export function sectionOf(of, plane, options = {}) {
|
|
|
169
163
|
}
|
|
170
164
|
if (crossed.length !== 4)
|
|
171
165
|
continue;
|
|
172
|
-
// A cell whose corners alternate in sign has two ways to be joined
|
|
173
|
-
//
|
|
174
|
-
// corners it agrees with is the pair the curve runs around.
|
|
166
|
+
// A cell whose corners alternate in sign has two ways to be joined, so the middle of the cell
|
|
167
|
+
// decides: the pair of corners it agrees with is the pair the curve runs around.
|
|
175
168
|
const middle = (gaps[0] + gaps[1] + gaps[2] + gaps[3]) / 4;
|
|
176
169
|
const pairs = middle >= 0 === gaps[0] >= 0 ? [[0, 1], [2, 3]] : [[3, 0], [1, 2]];
|
|
177
170
|
for (const [first, second] of pairs) {
|
package/dist/figure/space.d.ts
CHANGED
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
* Marks placed at points in space, which a camera turns into the flat nodes the
|
|
3
3
|
* rest of this package already draws.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* one shape on the page: a line running past the eye comes back as
|
|
7
|
-
* it the eye can see, and a shape wholly behind the eye comes back
|
|
8
|
-
* with no children, which flattens to no marks rather than to a mark
|
|
5
|
+
* A builder that draws one thing hands back a group, because a shape in space is
|
|
6
|
+
* not always one shape on the page: a line running past the eye comes back as
|
|
7
|
+
* the pieces of it the eye can see, and a shape wholly behind the eye comes back
|
|
8
|
+
* as a group with no children, which flattens to no marks rather than to a mark
|
|
9
|
+
* of nothing. A builder that draws pieces for a scene to sort hands back those
|
|
10
|
+
* pieces with the points they came from, which is what says how far off each is.
|
|
9
11
|
*/
|
|
10
12
|
import { type Vec3 } from '../values/vec3.js';
|
|
11
13
|
import type { Vec2 } from '../values/vec2.js';
|
|
14
|
+
import type { Fill } from './mark.js';
|
|
12
15
|
import { type GroupNode, type Node, type Style, type TextOptions } from './node.js';
|
|
13
|
-
import { type Interval } from '../values/interval.js';
|
|
14
|
-
import type { Colour, Fill, Stroke } from './mark.js';
|
|
15
16
|
import { type ArrowOptions } from './annotate.js';
|
|
16
17
|
import type { Camera3 } from './camera.js';
|
|
17
18
|
export type Polyline3Options = Style & {
|
|
@@ -53,7 +54,7 @@ export type SpaceItem = {
|
|
|
53
54
|
* sort is stable, and a picture that changed which of two touching faces was on
|
|
54
55
|
* top between frames would flicker.
|
|
55
56
|
*/
|
|
56
|
-
export declare function
|
|
57
|
+
export declare function scene3(name: string, items: readonly SpaceItem[], camera: Camera3): GroupNode;
|
|
57
58
|
export type Arrow3Options = ArrowOptions;
|
|
58
59
|
/**
|
|
59
60
|
* A line between two points in space with a head at the far end.
|
|
@@ -67,87 +68,3 @@ export type Arrow3Options = ArrowOptions;
|
|
|
67
68
|
* with no head, since the place the head belongs is not on the page.
|
|
68
69
|
*/
|
|
69
70
|
export declare function arrow3(name: string, from: Vec3, to: Vec3, camera: Camera3, options: Arrow3Options): GroupNode;
|
|
70
|
-
export type VectorField3Options = ArrowOptions & {
|
|
71
|
-
/** The box the samples are taken in, nothing to one each way unless named. */
|
|
72
|
-
over?: {
|
|
73
|
-
x?: Interval;
|
|
74
|
-
y?: Interval;
|
|
75
|
-
z?: Interval;
|
|
76
|
-
};
|
|
77
|
-
/** How many samples each way. One number is all three. */
|
|
78
|
-
resolution?: number | {
|
|
79
|
-
x: number;
|
|
80
|
-
y: number;
|
|
81
|
-
z: number;
|
|
82
|
-
};
|
|
83
|
-
/** How long an arrow is, in the world's own units, from the magnitude of the
|
|
84
|
-
* vector at its own sample. */
|
|
85
|
-
lengthOf: (magnitude: number) => number;
|
|
86
|
-
/** What colour an arrow is, from that same magnitude. */
|
|
87
|
-
colourFor: (magnitude: number) => Colour;
|
|
88
|
-
};
|
|
89
|
-
/**
|
|
90
|
-
* The arrows of a field sampled over a box in space, and the points each was
|
|
91
|
-
* drawn from, for a figure that sorts them among pieces of its own.
|
|
92
|
-
*
|
|
93
|
-
* An arrow is measured in the world's own units rather than the figure's, unlike
|
|
94
|
-
* the arrows of a flat field, because a length in space is what perspective is
|
|
95
|
-
* for: a far arrow drawing shorter than a near one of the same magnitude is what
|
|
96
|
-
* says which is far. Its head is still in figure units, since the head is drawn
|
|
97
|
-
* on the page.
|
|
98
|
-
*
|
|
99
|
-
* A sample sits at the middle of its cell and the count is fixed by the
|
|
100
|
-
* resolution, so a gate can hold it as the eye moves. A sample whose vector is
|
|
101
|
-
* nothing draws no arrow there.
|
|
102
|
-
*/
|
|
103
|
-
export declare function fieldArrows3(name: string, of: (at: Vec3) => Vec3, camera: Camera3, options: VectorField3Options): SpaceItem[];
|
|
104
|
-
/** A field of vectors in space, drawn as arrows ordered back to front. */
|
|
105
|
-
export declare function vectorField3(name: string, of: (at: Vec3) => Vec3, camera: Camera3, options: VectorField3Options): GroupNode;
|
|
106
|
-
export type Surface3Options = {
|
|
107
|
-
/** The run of the first parameter, nothing to one unless named. */
|
|
108
|
-
u?: Interval;
|
|
109
|
-
/** The run of the second parameter, nothing to one unless named. */
|
|
110
|
-
v?: Interval;
|
|
111
|
-
/** How many cells each way. */
|
|
112
|
-
resolution?: number | {
|
|
113
|
-
u: number;
|
|
114
|
-
v: number;
|
|
115
|
-
};
|
|
116
|
-
/**
|
|
117
|
-
* The colour a cell is filled with, given how squarely it faces the light: one
|
|
118
|
-
* where it faces the light head on, a half where it is edge on, and nothing
|
|
119
|
-
* where it faces straight away.
|
|
120
|
-
*
|
|
121
|
-
* The author supplies this rather than naming two colours to mix, because
|
|
122
|
-
* mixing two colours means reading them, and a colour here is any CSS colour
|
|
123
|
-
* written as text with nothing that parses one.
|
|
124
|
-
*/
|
|
125
|
-
shade: (amount: number) => Fill;
|
|
126
|
-
/** Which way the light comes from, over the shoulder of an eye on the positive
|
|
127
|
-
* z axis unless named. */
|
|
128
|
-
light?: Vec3;
|
|
129
|
-
/** Whether a cell facing away from the eye is left out. Off by default, because
|
|
130
|
-
* a count that changes as the camera turns is a count no gate can hold. */
|
|
131
|
-
cull?: boolean;
|
|
132
|
-
stroke?: Stroke;
|
|
133
|
-
};
|
|
134
|
-
/**
|
|
135
|
-
* The cells a surface is made of, before they are put in an order.
|
|
136
|
-
*
|
|
137
|
-
* Cells rather than one shape is what makes the depth sort work at all: a surface
|
|
138
|
-
* that folds over itself has no one place in a painting order, and pieces small
|
|
139
|
-
* enough to be flat do.
|
|
140
|
-
*
|
|
141
|
-
* A scene holding a surface and a plane that cuts through it has to sort all of
|
|
142
|
-
* their cells together, since two surfaces sorted apart are two groups and the
|
|
143
|
-
* second is painted over the first whichever way round they stand. Each cell
|
|
144
|
-
* carries the name it was given ahead of its own place in the grid, so an
|
|
145
|
-
* animation can still name a whole surface once its cells are mixed with
|
|
146
|
-
* another's.
|
|
147
|
-
*/
|
|
148
|
-
export declare function surfaceCells(name: string, of: (u: number, v: number) => Vec3, camera: Camera3, options: Surface3Options): SpaceItem[];
|
|
149
|
-
/**
|
|
150
|
-
* A surface given by a function of two parameters, drawn as a grid of
|
|
151
|
-
* four-cornered cells ordered back to front.
|
|
152
|
-
*/
|
|
153
|
-
export declare function surface3(name: string, of: (u: number, v: number) => Vec3, camera: Camera3, options: Surface3Options): GroupNode;
|
package/dist/figure/space.js
CHANGED
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
* Marks placed at points in space, which a camera turns into the flat nodes the
|
|
3
3
|
* rest of this package already draws.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* one shape on the page: a line running past the eye comes back as
|
|
7
|
-
* it the eye can see, and a shape wholly behind the eye comes back
|
|
8
|
-
* with no children, which flattens to no marks rather than to a mark
|
|
5
|
+
* A builder that draws one thing hands back a group, because a shape in space is
|
|
6
|
+
* not always one shape on the page: a line running past the eye comes back as
|
|
7
|
+
* the pieces of it the eye can see, and a shape wholly behind the eye comes back
|
|
8
|
+
* as a group with no children, which flattens to no marks rather than to a mark
|
|
9
|
+
* of nothing. A builder that draws pieces for a scene to sort hands back those
|
|
10
|
+
* pieces with the points they came from, which is what says how far off each is.
|
|
9
11
|
*/
|
|
10
12
|
import { vec3 } from '../values/vec3.js';
|
|
11
13
|
import { circle, line, polygon, polyline } from './path.js';
|
|
12
14
|
import { group, shape, text } from './node.js';
|
|
13
|
-
import { interval } from '../values/interval.js';
|
|
14
15
|
import { arrow } from './annotate.js';
|
|
15
16
|
/**
|
|
16
17
|
* Where along a segment the near plane is crossed.
|
|
@@ -104,7 +105,7 @@ function middleDepth(points, camera) {
|
|
|
104
105
|
* sort is stable, and a picture that changed which of two touching faces was on
|
|
105
106
|
* top between frames would flicker.
|
|
106
107
|
*/
|
|
107
|
-
export function
|
|
108
|
+
export function scene3(name, items, camera) {
|
|
108
109
|
const measured = items.map((item) => ({ node: item.node, depth: middleDepth(item.points, camera) }));
|
|
109
110
|
measured.sort((a, b) => b.depth - a.depth);
|
|
110
111
|
return group(name, measured.map((item) => item.node));
|
|
@@ -136,107 +137,3 @@ export function arrow3(name, from, to, camera, options) {
|
|
|
136
137
|
return group(name, []);
|
|
137
138
|
return arrow(name, tail, end.at, options);
|
|
138
139
|
}
|
|
139
|
-
function gridOf(resolution) {
|
|
140
|
-
return typeof resolution === 'number' ? { x: resolution, y: resolution, z: resolution } : resolution;
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* The arrows of a field sampled over a box in space, and the points each was
|
|
144
|
-
* drawn from, for a figure that sorts them among pieces of its own.
|
|
145
|
-
*
|
|
146
|
-
* An arrow is measured in the world's own units rather than the figure's, unlike
|
|
147
|
-
* the arrows of a flat field, because a length in space is what perspective is
|
|
148
|
-
* for: a far arrow drawing shorter than a near one of the same magnitude is what
|
|
149
|
-
* says which is far. Its head is still in figure units, since the head is drawn
|
|
150
|
-
* on the page.
|
|
151
|
-
*
|
|
152
|
-
* A sample sits at the middle of its cell and the count is fixed by the
|
|
153
|
-
* resolution, so a gate can hold it as the eye moves. A sample whose vector is
|
|
154
|
-
* nothing draws no arrow there.
|
|
155
|
-
*/
|
|
156
|
-
export function fieldArrows3(name, of, camera, options) {
|
|
157
|
-
const { over = {}, resolution = 6, lengthOf, colourFor, ...rest } = options;
|
|
158
|
-
const box = {
|
|
159
|
-
x: interval.ordered(over.x ?? interval(0, 1)),
|
|
160
|
-
y: interval.ordered(over.y ?? interval(0, 1)),
|
|
161
|
-
z: interval.ordered(over.z ?? interval(0, 1)),
|
|
162
|
-
};
|
|
163
|
-
const steps = gridOf(resolution);
|
|
164
|
-
const items = [];
|
|
165
|
-
for (let i = 0; i < steps.x; i += 1) {
|
|
166
|
-
for (let j = 0; j < steps.y; j += 1) {
|
|
167
|
-
for (let k = 0; k < steps.z; k += 1) {
|
|
168
|
-
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));
|
|
169
|
-
const vector = of(from);
|
|
170
|
-
const magnitude = vec3.magnitude(vector);
|
|
171
|
-
const length = lengthOf(magnitude);
|
|
172
|
-
if (!(magnitude > 0) || !Number.isFinite(length) || !(length > 0))
|
|
173
|
-
continue;
|
|
174
|
-
const to = vec3.add(from, vec3.scale(vector, length / magnitude));
|
|
175
|
-
items.push({
|
|
176
|
-
points: [from, to],
|
|
177
|
-
node: arrow3(`${name}/${i}-${j}-${k}`, from, to, camera, {
|
|
178
|
-
...rest,
|
|
179
|
-
stroke: { ...rest.stroke, colour: colourFor(magnitude) },
|
|
180
|
-
}),
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
return items;
|
|
186
|
-
}
|
|
187
|
-
/** A field of vectors in space, drawn as arrows ordered back to front. */
|
|
188
|
-
export function vectorField3(name, of, camera, options) {
|
|
189
|
-
return space(name, fieldArrows3('arrow', of, camera, options), camera);
|
|
190
|
-
}
|
|
191
|
-
function resolutionOf(resolution) {
|
|
192
|
-
return typeof resolution === 'number' ? { u: resolution, v: resolution } : resolution;
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* The cells a surface is made of, before they are put in an order.
|
|
196
|
-
*
|
|
197
|
-
* Cells rather than one shape is what makes the depth sort work at all: a surface
|
|
198
|
-
* that folds over itself has no one place in a painting order, and pieces small
|
|
199
|
-
* enough to be flat do.
|
|
200
|
-
*
|
|
201
|
-
* A scene holding a surface and a plane that cuts through it has to sort all of
|
|
202
|
-
* their cells together, since two surfaces sorted apart are two groups and the
|
|
203
|
-
* second is painted over the first whichever way round they stand. Each cell
|
|
204
|
-
* carries the name it was given ahead of its own place in the grid, so an
|
|
205
|
-
* animation can still name a whole surface once its cells are mixed with
|
|
206
|
-
* another's.
|
|
207
|
-
*/
|
|
208
|
-
export function surfaceCells(name, of, camera, options) {
|
|
209
|
-
const { u = interval(0, 1), v = interval(0, 1), resolution = 24, shade, light = vec3(0, 0, 1), cull = false, stroke } = options;
|
|
210
|
-
const steps = resolutionOf(resolution);
|
|
211
|
-
const toLight = vec3.normalize(light);
|
|
212
|
-
const items = [];
|
|
213
|
-
for (let i = 0; i < steps.u; i += 1) {
|
|
214
|
-
for (let j = 0; j < steps.v; j += 1) {
|
|
215
|
-
const corners = [
|
|
216
|
-
of(interval.at(u, i / steps.u), interval.at(v, j / steps.v)),
|
|
217
|
-
of(interval.at(u, (i + 1) / steps.u), interval.at(v, j / steps.v)),
|
|
218
|
-
of(interval.at(u, (i + 1) / steps.u), interval.at(v, (j + 1) / steps.v)),
|
|
219
|
-
of(interval.at(u, i / steps.u), interval.at(v, (j + 1) / steps.v)),
|
|
220
|
-
];
|
|
221
|
-
const normal = vec3.normalize(vec3.cross(vec3.sub(corners[1], corners[0]), vec3.sub(corners[3], corners[0])));
|
|
222
|
-
if (cull) {
|
|
223
|
-
const middle = corners.reduce((sum, corner) => vec3.add(sum, vec3.scale(corner, 1 / 4)), vec3.ZERO);
|
|
224
|
-
if (vec3.dot(normal, vec3.sub(camera.eye, middle)) <= 0)
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
const fill = shade((vec3.dot(normal, toLight) + 1) / 2);
|
|
228
|
-
items.push({
|
|
229
|
-
points: corners,
|
|
230
|
-
node: polyline3(`${name}/${i}-${j}`, corners, camera, { close: true, fill, stroke }),
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
return items;
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* A surface given by a function of two parameters, drawn as a grid of
|
|
238
|
-
* four-cornered cells ordered back to front.
|
|
239
|
-
*/
|
|
240
|
-
export function surface3(name, of, camera, options) {
|
|
241
|
-
return space(name, surfaceCells('cell', of, camera, options), camera);
|
|
242
|
-
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A surface given by a function of two parameters, drawn as a grid of cells.
|
|
3
|
+
*
|
|
4
|
+
* Cells rather than one shape is what makes the depth sort work at all: a
|
|
5
|
+
* surface that folds over itself has no one place in a painting order, and
|
|
6
|
+
* pieces small enough to be flat do.
|
|
7
|
+
*/
|
|
8
|
+
import { type Interval } from '../values/interval.js';
|
|
9
|
+
import { type Vec3 } from '../values/vec3.js';
|
|
10
|
+
import type { Fill, Stroke } from './mark.js';
|
|
11
|
+
import type { Camera3 } from './camera.js';
|
|
12
|
+
import { type SpaceItem } from './space.js';
|
|
13
|
+
import type { GroupNode } from './node.js';
|
|
14
|
+
export type Surface3Options = {
|
|
15
|
+
/** The runs of the two parameters, nothing to one each unless named. */
|
|
16
|
+
over?: {
|
|
17
|
+
u?: Interval;
|
|
18
|
+
v?: Interval;
|
|
19
|
+
};
|
|
20
|
+
/** How many cells each way. */
|
|
21
|
+
resolution?: number | {
|
|
22
|
+
u: number;
|
|
23
|
+
v: number;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* The colour a cell is filled with, given how squarely it faces the light: one
|
|
27
|
+
* where it faces the light head on, a half where it is edge on, and nothing
|
|
28
|
+
* where it faces straight away.
|
|
29
|
+
*
|
|
30
|
+
* The author supplies this rather than naming two colours to mix, because
|
|
31
|
+
* mixing two colours means reading them, and a colour here is any CSS colour
|
|
32
|
+
* written as text with nothing that parses one.
|
|
33
|
+
*/
|
|
34
|
+
shade: (amount: number) => Fill;
|
|
35
|
+
/** Which way the light comes from, over the shoulder of an eye on the positive
|
|
36
|
+
* z axis unless named. */
|
|
37
|
+
light?: Vec3;
|
|
38
|
+
/** Whether a cell facing away from the eye is left out. Off by default, because
|
|
39
|
+
* a count that changes as the camera turns is a count no gate can hold. */
|
|
40
|
+
cull?: boolean;
|
|
41
|
+
stroke?: Stroke;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The cells a surface is made of, before they are put in an order.
|
|
45
|
+
*
|
|
46
|
+
* Cells rather than one shape is what makes the depth sort work at all: a surface
|
|
47
|
+
* that folds over itself has no one place in a painting order, and pieces small
|
|
48
|
+
* enough to be flat do.
|
|
49
|
+
*
|
|
50
|
+
* A scene holding a surface and a plane that cuts through it has to sort all of
|
|
51
|
+
* their cells together, since two surfaces sorted apart are two groups and the
|
|
52
|
+
* second is painted over the first whichever way round they stand. Each cell
|
|
53
|
+
* carries the name it was given ahead of its own place in the grid, so an
|
|
54
|
+
* animation can still name a whole surface once its cells are mixed with
|
|
55
|
+
* another's.
|
|
56
|
+
*/
|
|
57
|
+
export declare function surfaceCells(name: string, of: (u: number, v: number) => Vec3, camera: Camera3, options: Surface3Options): SpaceItem[];
|
|
58
|
+
/**
|
|
59
|
+
* A surface given by a function of two parameters, drawn as a grid of
|
|
60
|
+
* four-cornered cells ordered back to front.
|
|
61
|
+
*/
|
|
62
|
+
export declare function surface3(name: string, of: (u: number, v: number) => Vec3, camera: Camera3, options: Surface3Options): GroupNode;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A surface given by a function of two parameters, drawn as a grid of cells.
|
|
3
|
+
*
|
|
4
|
+
* Cells rather than one shape is what makes the depth sort work at all: a
|
|
5
|
+
* surface that folds over itself has no one place in a painting order, and
|
|
6
|
+
* pieces small enough to be flat do.
|
|
7
|
+
*/
|
|
8
|
+
import { interval } from '../values/interval.js';
|
|
9
|
+
import { vec3 } from '../values/vec3.js';
|
|
10
|
+
import { cornersOf, stepsOf } from './grid.js';
|
|
11
|
+
import { polyline3, scene3 } from './space.js';
|
|
12
|
+
/**
|
|
13
|
+
* The cells a surface is made of, before they are put in an order.
|
|
14
|
+
*
|
|
15
|
+
* Cells rather than one shape is what makes the depth sort work at all: a surface
|
|
16
|
+
* that folds over itself has no one place in a painting order, and pieces small
|
|
17
|
+
* enough to be flat do.
|
|
18
|
+
*
|
|
19
|
+
* A scene holding a surface and a plane that cuts through it has to sort all of
|
|
20
|
+
* their cells together, since two surfaces sorted apart are two groups and the
|
|
21
|
+
* second is painted over the first whichever way round they stand. Each cell
|
|
22
|
+
* carries the name it was given ahead of its own place in the grid, so an
|
|
23
|
+
* animation can still name a whole surface once its cells are mixed with
|
|
24
|
+
* another's.
|
|
25
|
+
*/
|
|
26
|
+
export function surfaceCells(name, of, camera, options) {
|
|
27
|
+
const { over = {}, resolution = 24, shade, light = vec3(0, 0, 1), cull = false, stroke } = options;
|
|
28
|
+
const u = over.u ?? interval(0, 1);
|
|
29
|
+
const v = over.v ?? interval(0, 1);
|
|
30
|
+
const steps = stepsOf(resolution, 'u', 'v');
|
|
31
|
+
const grid = cornersOf(of, u, v, steps);
|
|
32
|
+
const toLight = vec3.normalize(light);
|
|
33
|
+
const items = [];
|
|
34
|
+
for (let i = 0; i < steps.u; i += 1) {
|
|
35
|
+
for (let j = 0; j < steps.v; j += 1) {
|
|
36
|
+
const corners = [grid[i][j], grid[i + 1][j], grid[i + 1][j + 1], grid[i][j + 1]];
|
|
37
|
+
const normal = vec3.normalize(vec3.cross(vec3.sub(corners[1], corners[0]), vec3.sub(corners[3], corners[0])));
|
|
38
|
+
if (cull) {
|
|
39
|
+
const middle = corners.reduce((sum, corner) => vec3.add(sum, vec3.scale(corner, 1 / 4)), vec3.ZERO);
|
|
40
|
+
if (vec3.dot(normal, vec3.sub(camera.eye, middle)) <= 0)
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const fill = shade((vec3.dot(normal, toLight) + 1) / 2);
|
|
44
|
+
items.push({
|
|
45
|
+
points: corners,
|
|
46
|
+
node: polyline3(`${name}/${i}-${j}`, corners, camera, { close: true, fill, stroke }),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return items;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A surface given by a function of two parameters, drawn as a grid of
|
|
54
|
+
* four-cornered cells ordered back to front.
|
|
55
|
+
*/
|
|
56
|
+
export function surface3(name, of, camera, options) {
|
|
57
|
+
return scene3(name, surfaceCells('cell', of, camera, options), camera);
|
|
58
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* Nothing below the line may import anything above it.
|
|
9
9
|
*/
|
|
10
10
|
export { clamp, inverseLerp, lerp, remap } from './values/scalar.js';
|
|
11
|
+
export { colourOf, colourText, lerpColour } from './values/colour.js';
|
|
12
|
+
export type { Rgba } from './values/colour.js';
|
|
11
13
|
export { curveFor, easeIn, easeOut, linear, smoothstep } from './values/ease.js';
|
|
12
14
|
export type { Curve } from './values/ease.js';
|
|
13
15
|
export { vec2 } from './values/vec2.js';
|
|
@@ -19,10 +21,10 @@ export type { Interval } from './values/interval.js';
|
|
|
19
21
|
export { mat3 } from './values/mat3.js';
|
|
20
22
|
export type { Mat3 } from './values/mat3.js';
|
|
21
23
|
export { mat4 } from './values/mat4.js';
|
|
22
|
-
export type { Mat4
|
|
24
|
+
export type { Mat4 } from './values/mat4.js';
|
|
23
25
|
export { SAME_TIME, keyAt, sampleTrack, sampleTracks, withKey, withoutKey } from './timing/track.js';
|
|
24
26
|
export type { Key, Track, TrackValue, Tracks } from './timing/track.js';
|
|
25
|
-
export { arc, circle, line, polygon, polyline, pointCount, pointOn, rect,
|
|
27
|
+
export { arc, circle, line, polygon, polyline, pointCount, pointOn, rect, splitCurve, straight, tangentOn, transformPath } from './figure/path.js';
|
|
26
28
|
export type { Cubic, Path, Subpath } from './figure/path.js';
|
|
27
29
|
export { pathFromData } from './figure/path-data.js';
|
|
28
30
|
export { TOLERANCE } from './figure/tolerance.js';
|
|
@@ -30,7 +32,7 @@ export { areaOf } from './figure/area.js';
|
|
|
30
32
|
export { differenceOf, intersectionOf, unionOf } from './figure/boolean.js';
|
|
31
33
|
export type { BooleanOptions } from './figure/boolean.js';
|
|
32
34
|
export { containsPoint, flattenPath, nearestEdge, windingAt } from './figure/inside.js';
|
|
33
|
-
export type {
|
|
35
|
+
export type { FlatEdge, FlattenOptions } from './figure/inside.js';
|
|
34
36
|
export { cutPath } from './figure/cut.js';
|
|
35
37
|
export type { Cut, CutOptions } from './figure/cut.js';
|
|
36
38
|
export { curveCrossings } from './figure/intersect.js';
|
|
@@ -50,13 +52,17 @@ export { axes, numberLine, numberPlane } from './figure/axis.js';
|
|
|
50
52
|
export type { AxesOptions, NumberLineOptions, NumberPlaneOptions } from './figure/axis.js';
|
|
51
53
|
export { camera3, orthographic, perspective } from './figure/camera.js';
|
|
52
54
|
export type { Camera3, Camera3Choice, OrthographicChoice, PerspectiveChoice, Projected, Projection } from './figure/camera.js';
|
|
53
|
-
export { arrow3, dot3,
|
|
54
|
-
export type { Arrow3Options, Polyline3Options, SpaceItem,
|
|
55
|
+
export { arrow3, dot3, polyline3, scene3, text3 } from './figure/space.js';
|
|
56
|
+
export type { Arrow3Options, Polyline3Options, SpaceItem, Text3Options } from './figure/space.js';
|
|
57
|
+
export { fieldArrows3, vectorField3 } from './figure/field3.js';
|
|
58
|
+
export type { VectorField3Options } from './figure/field3.js';
|
|
59
|
+
export { surface3, surfaceCells } from './figure/surface3.js';
|
|
60
|
+
export type { Surface3Options } from './figure/surface3.js';
|
|
55
61
|
export { axes3 } from './figure/axis3.js';
|
|
56
62
|
export type { Axes3Options } from './figure/axis3.js';
|
|
57
63
|
export { sectionOf } from './figure/section.js';
|
|
58
64
|
export type { Plane, SectionOptions } from './figure/section.js';
|
|
59
|
-
export { coordsOf, pointOf, scaleOf,
|
|
65
|
+
export { coordsOf, pointOf, scaleOf, toGraph, toUnits } from './figure/scale.js';
|
|
60
66
|
export type { Coords, Scale } from './figure/scale.js';
|
|
61
67
|
export { labelFor, tickStep, ticksOn } from './figure/ticks.js';
|
|
62
68
|
export type { Tick } from './figure/ticks.js';
|
|
@@ -69,17 +75,19 @@ export type { PlayOptions, Span, StaggerOptions } from './figure/timeline.js';
|
|
|
69
75
|
export { lengthOf, pointAlong } from './figure/length.js';
|
|
70
76
|
export { trimPath } from './figure/trim.js';
|
|
71
77
|
export { alignPaths, lerpPath } from './figure/morph.js';
|
|
72
|
-
export {
|
|
73
|
-
export
|
|
74
|
-
export {
|
|
75
|
-
export type {
|
|
78
|
+
export { durationOf, isLoop, marksAt, sameMarks, viewAt } from './figure/figure.js';
|
|
79
|
+
export { frameTimesOf, framesOf } from './figure/frames.js';
|
|
80
|
+
export type { Frame, FrameStep, FramesOptions } from './figure/frames.js';
|
|
81
|
+
export type { Figure, TrackValues } from './figure/figure.js';
|
|
82
|
+
export { paintSvg, pathToData, svgElements, svgMarkup } from './paint/svg.js';
|
|
83
|
+
export type { ElementMaker, PaintNode, PaintTarget, SvgElement, SvgMarkupOptions, SvgTheme } from './paint/svg.js';
|
|
76
84
|
export { paintCanvas } from './paint/canvas.js';
|
|
77
85
|
export type { CanvasLike } from './paint/canvas.js';
|
|
78
86
|
export { arrow, brace, bracePath, callout, dot } from './figure/annotate.js';
|
|
79
87
|
export type { ArrowOptions, BraceOptions, BracedOptions, CalloutOptions } from './figure/annotate.js';
|
|
80
88
|
export { typesetElement } from './figure/typeset.js';
|
|
81
89
|
export type { EquationElement } from './figure/typeset.js';
|
|
82
|
-
export { equationFromTex,
|
|
90
|
+
export { equationFromTex, equationNode, equationOf } from './figure/equation.js';
|
|
83
91
|
export type { Equation, EquationBox, EquationOptions } from './figure/equation.js';
|
|
84
92
|
export { glyphToken, matchGlyphs } from './figure/equation-match.js';
|
|
85
93
|
export type { GlyphMatch } from './figure/equation-match.js';
|