@altpsyche/maths 0.9.5 → 0.11.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.
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The path a point follows through a field of vectors, walked by Runge-Kutta 4.
3
+ *
4
+ * The points come back in graph units and nothing here draws them, the way the
5
+ * curve where a plane cuts a surface comes back as points in space. What a
6
+ * figure does with them is its own.
7
+ *
8
+ * The step is a distance rather than a time, so the field is read as a
9
+ * direction and its magnitude decides nothing about how far the run moves. A
10
+ * step in the field's own time crowds the points where the field is weak and
11
+ * spreads them where it is strong, and a curve drawn from those is faceted
12
+ * exactly where it turns hardest.
13
+ *
14
+ * The step is fixed and never adaptive. An adaptive step hands back a different
15
+ * number of points as the field changes, which is a count no gate can hold and
16
+ * a path no morph can pair up against another.
17
+ */
18
+ import { type Interval } from '../values/interval.js';
19
+ import { type Vec2 } from '../values/vec2.js';
20
+ export interface StreamlineOptions {
21
+ /** How far each step moves, in graph units. */
22
+ step: number;
23
+ /** How many steps the run takes at most, in each direction it is run. */
24
+ steps?: number;
25
+ /** The region the run is held inside. It has no edges where this is left out,
26
+ * and only the step cap and a vanishing field stop it. */
27
+ within?: {
28
+ x: Interval;
29
+ y: Interval;
30
+ };
31
+ /** Which way the run goes from its seed. Both puts the backward half first,
32
+ * so the points read from one end of the curve to the other. */
33
+ direction?: 'forward' | 'backward' | 'both';
34
+ /** The magnitude below which the field is taken to have vanished, in graph
35
+ * units. */
36
+ least?: number;
37
+ }
38
+ /**
39
+ * The streamline of a field through a seed point, in graph units.
40
+ *
41
+ * The run stops on one of three rules: it leaves the region, it reaches its step
42
+ * cap, or the field where it stands is too small to point anywhere. A seed
43
+ * outside the region comes back as that seed alone, which is a curve with
44
+ * nothing to draw rather than a run that starts by escaping.
45
+ *
46
+ * A run that leaves the region stops at the last point inside it and is not cut
47
+ * at the edge, so it ends within one step of the boundary.
48
+ */
49
+ export declare function streamlineOf(of: (at: Vec2) => Vec2, from: Vec2, options: StreamlineOptions): Vec2[];
@@ -0,0 +1,95 @@
1
+ /**
2
+ * The path a point follows through a field of vectors, walked by Runge-Kutta 4.
3
+ *
4
+ * The points come back in graph units and nothing here draws them, the way the
5
+ * curve where a plane cuts a surface comes back as points in space. What a
6
+ * figure does with them is its own.
7
+ *
8
+ * The step is a distance rather than a time, so the field is read as a
9
+ * direction and its magnitude decides nothing about how far the run moves. A
10
+ * step in the field's own time crowds the points where the field is weak and
11
+ * spreads them where it is strong, and a curve drawn from those is faceted
12
+ * exactly where it turns hardest.
13
+ *
14
+ * The step is fixed and never adaptive. An adaptive step hands back a different
15
+ * number of points as the field changes, which is a count no gate can hold and
16
+ * a path no morph can pair up against another.
17
+ */
18
+ import { interval } from '../values/interval.js';
19
+ import { vec2 } from '../values/vec2.js';
20
+ const STEPS = 200;
21
+ const LEAST = 1e-9;
22
+ function holds(within, at) {
23
+ if (!Number.isFinite(at.x) || !Number.isFinite(at.y))
24
+ return false;
25
+ if (!within)
26
+ return true;
27
+ return interval.holds(interval.ordered(within.x), at.x) && interval.holds(interval.ordered(within.y), at.y);
28
+ }
29
+ /**
30
+ * One run from the seed, as the points after it, with the seed left out so the
31
+ * two directions can be joined without repeating it.
32
+ *
33
+ * A stage whose field has vanished stops the run there rather than being taken
34
+ * as a direction, since dividing by a magnitude near nothing turns rounding
35
+ * error into a direction of its own.
36
+ */
37
+ function run(of, from, step, steps, within, least) {
38
+ const direction = (at) => {
39
+ const vector = of(at);
40
+ const magnitude = Math.hypot(vector.x, vector.y);
41
+ if (!Number.isFinite(magnitude) || magnitude <= least)
42
+ return undefined;
43
+ return vec2(vector.x / magnitude, vector.y / magnitude);
44
+ };
45
+ const points = [];
46
+ let at = from;
47
+ for (let taken = 0; taken < steps; taken += 1) {
48
+ const first = direction(at);
49
+ if (!first)
50
+ break;
51
+ const second = direction(vec2.add(at, vec2.scale(first, step / 2)));
52
+ if (!second)
53
+ break;
54
+ const third = direction(vec2.add(at, vec2.scale(second, step / 2)));
55
+ if (!third)
56
+ break;
57
+ const fourth = direction(vec2.add(at, vec2.scale(third, step)));
58
+ if (!fourth)
59
+ break;
60
+ const along = vec2((first.x + 2 * second.x + 2 * third.x + fourth.x) / 6, (first.y + 2 * second.y + 2 * third.y + fourth.y) / 6);
61
+ const next = vec2.add(at, vec2.scale(along, step));
62
+ if (!holds(within, next))
63
+ break;
64
+ points.push(next);
65
+ at = next;
66
+ }
67
+ return points;
68
+ }
69
+ /**
70
+ * The streamline of a field through a seed point, in graph units.
71
+ *
72
+ * The run stops on one of three rules: it leaves the region, it reaches its step
73
+ * cap, or the field where it stands is too small to point anywhere. A seed
74
+ * outside the region comes back as that seed alone, which is a curve with
75
+ * nothing to draw rather than a run that starts by escaping.
76
+ *
77
+ * A run that leaves the region stops at the last point inside it and is not cut
78
+ * at the edge, so it ends within one step of the boundary.
79
+ */
80
+ export function streamlineOf(of, from, options) {
81
+ const steps = Math.max(0, Math.round(options.steps ?? STEPS));
82
+ const least = options.least ?? LEAST;
83
+ const direction = options.direction ?? 'forward';
84
+ if (!holds(options.within, from))
85
+ return [from];
86
+ const forward = direction === 'backward' ? [] : run(of, from, options.step, steps, options.within, least);
87
+ if (direction === 'forward')
88
+ return [from, ...forward];
89
+ const back = (at) => {
90
+ const vector = of(at);
91
+ return vec2(-vector.x, -vector.y);
92
+ };
93
+ const backward = run(back, from, options.step, steps, options.within, least);
94
+ return [...backward.reverse(), from, ...forward];
95
+ }
package/dist/index.d.ts CHANGED
@@ -18,6 +18,8 @@ export { interval } from './values/interval.js';
18
18
  export type { Interval } from './values/interval.js';
19
19
  export { mat3 } from './values/mat3.js';
20
20
  export type { Mat3 } from './values/mat3.js';
21
+ export { mat4 } from './values/mat4.js';
22
+ export type { Mat4, OrthographicOptions, PerspectiveOptions } from './values/mat4.js';
21
23
  export { SAME_TIME, keyAt, sampleTrack, sampleTracks, withKey, withoutKey } from './timing/track.js';
22
24
  export type { Key, Track, TrackValue, Tracks } from './timing/track.js';
23
25
  export { arc, circle, line, polygon, polyline, pointCount, pointOn, rect, slopeOn, splitCurve, straight, transformPath } from './figure/path.js';
@@ -40,8 +42,20 @@ export { boundsOf, boundsOfMarks, centreOf } from './figure/bounds.js';
40
42
  export type { Bounds } from './figure/bounds.js';
41
43
  export { areaUnder, plot, riemannBars, slopeOf, tangentAt } from './figure/plot.js';
42
44
  export type { AreaOptions, BarsOptions, PlotOptions, TangentOptions } from './figure/plot.js';
45
+ export { vectorField } from './figure/field.js';
46
+ export { streamlineOf } from './figure/streamline.js';
47
+ export type { StreamlineOptions } from './figure/streamline.js';
48
+ export type { VectorFieldOptions } from './figure/field.js';
43
49
  export { axes, numberLine, numberPlane } from './figure/axis.js';
44
50
  export type { AxesOptions, NumberLineOptions, NumberPlaneOptions } from './figure/axis.js';
51
+ export { camera3, orthographic, perspective } from './figure/camera.js';
52
+ export type { Camera3, Camera3Choice, OrthographicChoice, PerspectiveChoice, Projected, Projection } from './figure/camera.js';
53
+ export { arrow3, dot3, fieldArrows3, polyline3, space, surface3, surfaceCells, text3, vectorField3 } from './figure/space.js';
54
+ export type { Arrow3Options, Polyline3Options, SpaceItem, Surface3Options, Text3Options, VectorField3Options } from './figure/space.js';
55
+ export { axes3 } from './figure/axis3.js';
56
+ export type { Axes3Options } from './figure/axis3.js';
57
+ export { sectionOf } from './figure/section.js';
58
+ export type { Plane, SectionOptions } from './figure/section.js';
45
59
  export { coordsOf, pointOf, scaleOf, scaled, unscaled } from './figure/scale.js';
46
60
  export type { Coords, Scale } from './figure/scale.js';
47
61
  export { labelFor, tickStep, ticksOn } from './figure/ticks.js';
@@ -55,7 +69,7 @@ export type { PlayOptions, Span, StaggerOptions } from './figure/timeline.js';
55
69
  export { lengthOf, pointAlong } from './figure/length.js';
56
70
  export { trimPath } from './figure/trim.js';
57
71
  export { alignPaths, lerpPath } from './figure/morph.js';
58
- export { at, durationOf, loops, sameMarks } from './figure/figure.js';
72
+ export { at, durationOf, loops, sameMarks, viewAt } from './figure/figure.js';
59
73
  export type { Figure, Values } from './figure/figure.js';
60
74
  export { pathData, paintSvg, svgElements, svgMarkup } from './paint/svg.js';
61
75
  export type { ElementMaker, PaintNode, PaintTarget, SvgElement } from './paint/svg.js';
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ export { vec2 } from './values/vec2.js';
13
13
  export { vec3 } from './values/vec3.js';
14
14
  export { interval } from './values/interval.js';
15
15
  export { mat3 } from './values/mat3.js';
16
+ export { mat4 } from './values/mat4.js';
16
17
  export { SAME_TIME, keyAt, sampleTrack, sampleTracks, withKey, withoutKey } from './timing/track.js';
17
18
  export { arc, circle, line, polygon, polyline, pointCount, pointOn, rect, slopeOn, splitCurve, straight, transformPath } from './figure/path.js';
18
19
  export { pathFromData } from './figure/path-data.js';
@@ -25,7 +26,13 @@ export { curveCrossings } from './figure/intersect.js';
25
26
  export { byAspect, fractionOf, matchingAspect, resolveExtent, viewMatrix } from './figure/extent.js';
26
27
  export { boundsOf, boundsOfMarks, centreOf } from './figure/bounds.js';
27
28
  export { areaUnder, plot, riemannBars, slopeOf, tangentAt } from './figure/plot.js';
29
+ export { vectorField } from './figure/field.js';
30
+ export { streamlineOf } from './figure/streamline.js';
28
31
  export { axes, numberLine, numberPlane } from './figure/axis.js';
32
+ export { camera3, orthographic, perspective } from './figure/camera.js';
33
+ export { arrow3, dot3, fieldArrows3, polyline3, space, surface3, surfaceCells, text3, vectorField3 } from './figure/space.js';
34
+ export { axes3 } from './figure/axis3.js';
35
+ export { sectionOf } from './figure/section.js';
29
36
  export { coordsOf, pointOf, scaleOf, scaled, unscaled } from './figure/scale.js';
30
37
  export { labelFor, tickStep, ticksOn } from './figure/ticks.js';
31
38
  export { flatten, group, shape, text } from './figure/node.js';
@@ -34,7 +41,7 @@ export { Timeline } from './figure/timeline.js';
34
41
  export { lengthOf, pointAlong } from './figure/length.js';
35
42
  export { trimPath } from './figure/trim.js';
36
43
  export { alignPaths, lerpPath } from './figure/morph.js';
37
- export { at, durationOf, loops, sameMarks } from './figure/figure.js';
44
+ export { at, durationOf, loops, sameMarks, viewAt } from './figure/figure.js';
38
45
  export { pathData, paintSvg, svgElements, svgMarkup } from './paint/svg.js';
39
46
  export { paintCanvas } from './paint/canvas.js';
40
47
  export { arrow, brace, bracePath, callout, dot } from './figure/annotate.js';
@@ -0,0 +1,118 @@
1
+ /**
2
+ * The transform a point in space carries: how a figure's camera turns a place in
3
+ * the world into a place on the page.
4
+ *
5
+ * Sixteen numbers, column-major, matching the engine's layout the way `Mat3`
6
+ * does: the first four are the first column rather than the first row, and the
7
+ * entry at flat index `col * 4 + row` is the one in that column and row. The
8
+ * fourth row exists so that a translation is a multiplication like every other
9
+ * move, and the fourth coordinate a point picks up is what a perspective divide
10
+ * reads.
11
+ *
12
+ * There is no inverse here because nothing needs one: a camera builds its view
13
+ * and its projection forwards and never undoes either. The day something has to
14
+ * go from the page back into the world, that is when an inverse is written.
15
+ */
16
+ import { type Vec3 } from './vec3.js';
17
+ export type Mat4 = readonly [
18
+ number,
19
+ number,
20
+ number,
21
+ number,
22
+ number,
23
+ number,
24
+ number,
25
+ number,
26
+ number,
27
+ number,
28
+ number,
29
+ number,
30
+ number,
31
+ number,
32
+ number,
33
+ number
34
+ ];
35
+ /** Column-major product, so `multiply(a, b)` applies `b` to a point first and
36
+ * then `a`, which is the order a projection sits outside a view. */
37
+ declare function multiply(a: Mat4, b: Mat4): Mat4;
38
+ /** The last column carries the offset, so this moves a point and leaves a
39
+ * direction where it was. */
40
+ declare function translation(v: Vec3): Mat4;
41
+ declare function scaling(v: Vec3): Mat4;
42
+ /** Turns y towards z, so a positive angle is anticlockwise seen from the far
43
+ * end of the x axis looking back at the origin. */
44
+ declare function rotationX(radians: number): Mat4;
45
+ /** Turns z towards x, which is the odd one out: the pair of axes runs z then x
46
+ * rather than x then z, and writing it the other way flips every y rotation. */
47
+ declare function rotationY(radians: number): Mat4;
48
+ /** Turns x towards y, which is the flat rotation `Mat3` gives with a z left
49
+ * alone. */
50
+ declare function rotationZ(radians: number): Mat4;
51
+ /**
52
+ * The view matrix of an eye at `eye` looking at `target`, with `up` saying which
53
+ * way is up.
54
+ *
55
+ * View space looks down its own negative z, so the target comes out on the
56
+ * negative z axis at the distance between the eye and the target. An `up` lying
57
+ * along the line of sight has no sideways direction in it and gives a matrix of
58
+ * zeroes, which is the pose a caller has to avoid rather than one this can fix.
59
+ */
60
+ declare function lookAt(eye: Vec3, target: Vec3, up: Vec3): Mat4;
61
+ export type PerspectiveOptions = {
62
+ /** The angle the frame covers up and down, in radians. */
63
+ fov: number;
64
+ /** Width over height, so a wide frame shows more sideways rather than less. */
65
+ aspect: number;
66
+ near: number;
67
+ far: number;
68
+ };
69
+ /**
70
+ * The projection of an eye that sees things smaller the further off they are.
71
+ *
72
+ * The third column puts the negated view-space z into the fourth coordinate, so
73
+ * a point twice as far away comes back with twice the divisor and lands half as
74
+ * far from the middle of the frame.
75
+ */
76
+ declare function perspective({ fov, aspect, near, far }: PerspectiveOptions): Mat4;
77
+ export type OrthographicOptions = {
78
+ left: number;
79
+ right: number;
80
+ bottom: number;
81
+ top: number;
82
+ near: number;
83
+ far: number;
84
+ };
85
+ /** The projection of an eye that sees everything at the size it is, which maps
86
+ * the named box onto the frame and leaves the fourth coordinate at one. */
87
+ declare function orthographic({ left, right, bottom, top, near, far }: OrthographicOptions): Mat4;
88
+ /**
89
+ * Applies the matrix to a point, taking the translation with it and dividing by
90
+ * the fourth coordinate the matrix gives it.
91
+ *
92
+ * That divide is why a point through `multiply(a, b)` matches the same point
93
+ * through `b` and then through `a` for matrices that do not touch the fourth
94
+ * coordinate and not for a perspective matrix: dividing halfway throws away the
95
+ * fourth coordinate the outer matrix still needed. A fourth coordinate of zero is
96
+ * a point on the plane through the eye, which has no place on the page at all, so
97
+ * the divide is skipped and the caller is left to notice.
98
+ */
99
+ declare function transformPoint(m: Mat4, v: Vec3): Vec3;
100
+ /** Applies the rotation and scale and neither the translation nor the divide,
101
+ * which is what a direction wants: moving the world must not move where an arrow
102
+ * points, and a direction has no distance for a perspective to shrink. */
103
+ declare function transformDirection(m: Mat4, v: Vec3): Vec3;
104
+ export declare const mat4: {
105
+ IDENTITY: Mat4;
106
+ multiply: typeof multiply;
107
+ translation: typeof translation;
108
+ scaling: typeof scaling;
109
+ rotationX: typeof rotationX;
110
+ rotationY: typeof rotationY;
111
+ rotationZ: typeof rotationZ;
112
+ lookAt: typeof lookAt;
113
+ perspective: typeof perspective;
114
+ orthographic: typeof orthographic;
115
+ transformPoint: typeof transformPoint;
116
+ transformDirection: typeof transformDirection;
117
+ };
118
+ export {};
@@ -0,0 +1,193 @@
1
+ /**
2
+ * The transform a point in space carries: how a figure's camera turns a place in
3
+ * the world into a place on the page.
4
+ *
5
+ * Sixteen numbers, column-major, matching the engine's layout the way `Mat3`
6
+ * does: the first four are the first column rather than the first row, and the
7
+ * entry at flat index `col * 4 + row` is the one in that column and row. The
8
+ * fourth row exists so that a translation is a multiplication like every other
9
+ * move, and the fourth coordinate a point picks up is what a perspective divide
10
+ * reads.
11
+ *
12
+ * There is no inverse here because nothing needs one: a camera builds its view
13
+ * and its projection forwards and never undoes either. The day something has to
14
+ * go from the page back into the world, that is when an inverse is written.
15
+ */
16
+ import { vec3 } from './vec3.js';
17
+ const IDENTITY = [
18
+ 1, 0, 0, 0,
19
+ 0, 1, 0, 0,
20
+ 0, 0, 1, 0,
21
+ 0, 0, 0, 1,
22
+ ];
23
+ /** Column-major product, so `multiply(a, b)` applies `b` to a point first and
24
+ * then `a`, which is the order a projection sits outside a view. */
25
+ function multiply(a, b) {
26
+ const [a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15] = a;
27
+ const [b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15] = b;
28
+ return [
29
+ a0 * b0 + a4 * b1 + a8 * b2 + a12 * b3,
30
+ a1 * b0 + a5 * b1 + a9 * b2 + a13 * b3,
31
+ a2 * b0 + a6 * b1 + a10 * b2 + a14 * b3,
32
+ a3 * b0 + a7 * b1 + a11 * b2 + a15 * b3,
33
+ a0 * b4 + a4 * b5 + a8 * b6 + a12 * b7,
34
+ a1 * b4 + a5 * b5 + a9 * b6 + a13 * b7,
35
+ a2 * b4 + a6 * b5 + a10 * b6 + a14 * b7,
36
+ a3 * b4 + a7 * b5 + a11 * b6 + a15 * b7,
37
+ a0 * b8 + a4 * b9 + a8 * b10 + a12 * b11,
38
+ a1 * b8 + a5 * b9 + a9 * b10 + a13 * b11,
39
+ a2 * b8 + a6 * b9 + a10 * b10 + a14 * b11,
40
+ a3 * b8 + a7 * b9 + a11 * b10 + a15 * b11,
41
+ a0 * b12 + a4 * b13 + a8 * b14 + a12 * b15,
42
+ a1 * b12 + a5 * b13 + a9 * b14 + a13 * b15,
43
+ a2 * b12 + a6 * b13 + a10 * b14 + a14 * b15,
44
+ a3 * b12 + a7 * b13 + a11 * b14 + a15 * b15,
45
+ ];
46
+ }
47
+ /** The last column carries the offset, so this moves a point and leaves a
48
+ * direction where it was. */
49
+ function translation(v) {
50
+ return [
51
+ 1, 0, 0, 0,
52
+ 0, 1, 0, 0,
53
+ 0, 0, 1, 0,
54
+ v.x, v.y, v.z, 1,
55
+ ];
56
+ }
57
+ function scaling(v) {
58
+ return [
59
+ v.x, 0, 0, 0,
60
+ 0, v.y, 0, 0,
61
+ 0, 0, v.z, 0,
62
+ 0, 0, 0, 1,
63
+ ];
64
+ }
65
+ /** Turns y towards z, so a positive angle is anticlockwise seen from the far
66
+ * end of the x axis looking back at the origin. */
67
+ function rotationX(radians) {
68
+ const c = Math.cos(radians);
69
+ const s = Math.sin(radians);
70
+ return [
71
+ 1, 0, 0, 0,
72
+ 0, c, s, 0,
73
+ 0, -s, c, 0,
74
+ 0, 0, 0, 1,
75
+ ];
76
+ }
77
+ /** Turns z towards x, which is the odd one out: the pair of axes runs z then x
78
+ * rather than x then z, and writing it the other way flips every y rotation. */
79
+ function rotationY(radians) {
80
+ const c = Math.cos(radians);
81
+ const s = Math.sin(radians);
82
+ return [
83
+ c, 0, -s, 0,
84
+ 0, 1, 0, 0,
85
+ s, 0, c, 0,
86
+ 0, 0, 0, 1,
87
+ ];
88
+ }
89
+ /** Turns x towards y, which is the flat rotation `Mat3` gives with a z left
90
+ * alone. */
91
+ function rotationZ(radians) {
92
+ const c = Math.cos(radians);
93
+ const s = Math.sin(radians);
94
+ return [
95
+ c, s, 0, 0,
96
+ -s, c, 0, 0,
97
+ 0, 0, 1, 0,
98
+ 0, 0, 0, 1,
99
+ ];
100
+ }
101
+ /**
102
+ * The view matrix of an eye at `eye` looking at `target`, with `up` saying which
103
+ * way is up.
104
+ *
105
+ * View space looks down its own negative z, so the target comes out on the
106
+ * negative z axis at the distance between the eye and the target. An `up` lying
107
+ * along the line of sight has no sideways direction in it and gives a matrix of
108
+ * zeroes, which is the pose a caller has to avoid rather than one this can fix.
109
+ */
110
+ function lookAt(eye, target, up) {
111
+ const forward = vec3.normalize(vec3.sub(target, eye));
112
+ const right = vec3.normalize(vec3.cross(forward, up));
113
+ const above = vec3.cross(right, forward);
114
+ return [
115
+ right.x, above.x, -forward.x, 0,
116
+ right.y, above.y, -forward.y, 0,
117
+ right.z, above.z, -forward.z, 0,
118
+ -vec3.dot(right, eye), -vec3.dot(above, eye), vec3.dot(forward, eye), 1,
119
+ ];
120
+ }
121
+ /**
122
+ * The projection of an eye that sees things smaller the further off they are.
123
+ *
124
+ * The third column puts the negated view-space z into the fourth coordinate, so
125
+ * a point twice as far away comes back with twice the divisor and lands half as
126
+ * far from the middle of the frame.
127
+ */
128
+ function perspective({ fov, aspect, near, far }) {
129
+ const focal = 1 / Math.tan(fov / 2);
130
+ return [
131
+ focal / aspect, 0, 0, 0,
132
+ 0, focal, 0, 0,
133
+ 0, 0, (far + near) / (near - far), -1,
134
+ 0, 0, (2 * far * near) / (near - far), 0,
135
+ ];
136
+ }
137
+ /** The projection of an eye that sees everything at the size it is, which maps
138
+ * the named box onto the frame and leaves the fourth coordinate at one. */
139
+ function orthographic({ left, right, bottom, top, near, far }) {
140
+ return [
141
+ 2 / (right - left), 0, 0, 0,
142
+ 0, 2 / (top - bottom), 0, 0,
143
+ 0, 0, -2 / (far - near), 0,
144
+ -(right + left) / (right - left),
145
+ -(top + bottom) / (top - bottom),
146
+ -(far + near) / (far - near),
147
+ 1,
148
+ ];
149
+ }
150
+ /**
151
+ * Applies the matrix to a point, taking the translation with it and dividing by
152
+ * the fourth coordinate the matrix gives it.
153
+ *
154
+ * That divide is why a point through `multiply(a, b)` matches the same point
155
+ * through `b` and then through `a` for matrices that do not touch the fourth
156
+ * coordinate and not for a perspective matrix: dividing halfway throws away the
157
+ * fourth coordinate the outer matrix still needed. A fourth coordinate of zero is
158
+ * a point on the plane through the eye, which has no place on the page at all, so
159
+ * the divide is skipped and the caller is left to notice.
160
+ */
161
+ function transformPoint(m, v) {
162
+ const w = m[3] * v.x + m[7] * v.y + m[11] * v.z + m[15];
163
+ const divisor = w === 0 ? 1 : w;
164
+ return {
165
+ x: (m[0] * v.x + m[4] * v.y + m[8] * v.z + m[12]) / divisor,
166
+ y: (m[1] * v.x + m[5] * v.y + m[9] * v.z + m[13]) / divisor,
167
+ z: (m[2] * v.x + m[6] * v.y + m[10] * v.z + m[14]) / divisor,
168
+ };
169
+ }
170
+ /** Applies the rotation and scale and neither the translation nor the divide,
171
+ * which is what a direction wants: moving the world must not move where an arrow
172
+ * points, and a direction has no distance for a perspective to shrink. */
173
+ function transformDirection(m, v) {
174
+ return {
175
+ x: m[0] * v.x + m[4] * v.y + m[8] * v.z,
176
+ y: m[1] * v.x + m[5] * v.y + m[9] * v.z,
177
+ z: m[2] * v.x + m[6] * v.y + m[10] * v.z,
178
+ };
179
+ }
180
+ export const mat4 = {
181
+ IDENTITY,
182
+ multiply,
183
+ translation,
184
+ scaling,
185
+ rotationX,
186
+ rotationY,
187
+ rotationZ,
188
+ lookAt,
189
+ perspective,
190
+ orthographic,
191
+ transformPoint,
192
+ transformDirection,
193
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@altpsyche/maths",
3
- "version": "0.9.5",
3
+ "version": "0.11.0",
4
4
  "description": "The mathematics AltPsyche's figures are drawn from: vectors, matrices, curves, and a value walked over time.",
5
5
  "license": "MIT",
6
6
  "author": "Siva",