@altpsyche/maths 0.9.4 → 0.10.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,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.4",
3
+ "version": "0.10.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",