@woosh/meep-engine 2.33.70 → 2.33.71

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,115 @@
1
+ //
2
+
3
+ import { v2_magnitude } from "../Vector2.js";
4
+
5
+ function atan2(a, b) {
6
+ return Math.atan2(a, b);
7
+ }
8
+
9
+ /**
10
+ *
11
+ * @param {number[]} mat4
12
+ * @param {number} row_index
13
+ * @param {number} column_index
14
+ * @return {number}
15
+ */
16
+ function coeff(mat4, row_index, column_index) {
17
+ return mat4[row_index + column_index * 4];
18
+ }
19
+
20
+ function sin(a) {
21
+ return Math.sin(a);
22
+ }
23
+
24
+ function cos(a) {
25
+ return Math.cos(a);
26
+ }
27
+
28
+ const EIGEN_PI = Math.PI;
29
+
30
+ /**
31
+ *
32
+ * \returns the Euler-angles of the rotation matrix \c *this using the convention defined by the triplet (\a a0,\a a1,\a a2)
33
+ *
34
+ * Each of the three parameters \a a0,\a a1,\a a2 represents the respective rotation axis as an integer in {0,1,2}.
35
+ * For instance, in:
36
+ * \code Vector3f ea = mat.eulerAngles(2, 0, 2); \endcode
37
+ * "2" represents the z axis and "0" the x axis, etc. The returned angles are such that
38
+ * we have the following equality:
39
+ * \code
40
+ * mat == AngleAxisf(ea[0], Vector3f::UnitZ())
41
+ * * AngleAxisf(ea[1], Vector3f::UnitX())
42
+ * * AngleAxisf(ea[2], Vector3f::UnitZ()); \endcode
43
+ * This corresponds to the right-multiply conventions (with right hand side frames).
44
+ *
45
+ * The returned angles are in the ranges [0:pi]x[-pi:pi]x[-pi:pi].
46
+
47
+ * NOTE: ported from Eigen C++ library
48
+ * @see https://gitlab.com/libeigen/eigen/-/blob/master/Eigen/src/Geometry/EulerAngles.h
49
+ * @see https://stackoverflow.com/questions/11514063/extract-yaw-pitch-and-roll-from-a-rotationmatrix
50
+ * @param {number[]} res
51
+ * @param {number[]|Float32Array|mat4} m4
52
+ * @param {number} a0 axis index
53
+ * @param {number} a1
54
+ * @param {number} a2
55
+ */
56
+ export function eulerAnglesFromMatrix(res, m4, a0, a1, a2) {
57
+ const odd = ((a0 + 1) % 3 === a1) ? 0 : 1;
58
+
59
+ const i = a0;
60
+ const j = (a0 + 1 + odd) % 3;
61
+ const k = (a0 + 2 - odd) % 3;
62
+
63
+ if (a0 === a2) {
64
+ res[0] = atan2(coeff(m4, j, i), coeff(m4, k, i));
65
+ if ((odd && res[0] < 0) || ((!odd) && res[0] > 0)) {
66
+ if (res[0] > 0) {
67
+ res[0] -= EIGEN_PI;
68
+ } else {
69
+ res[0] += EIGEN_PI;
70
+ }
71
+ const s2 = v2_magnitude(coeff(m4, j, i), coeff(m4, k, i));
72
+ res[1] = -atan2(s2, coeff(m4, i, i));
73
+ } else {
74
+ const s2 = v2_magnitude(coeff(m4, j, i), coeff(m4, k, i));
75
+ res[1] = atan2(s2, coeff(m4, i, i));
76
+ }
77
+
78
+ // With a=(0,1,0), we have i=0; j=1; k=2, and after computing the first two angles,
79
+ // we can compute their respective rotation, and apply its inverse to M. Since the result must
80
+ // be a rotation around x, we have:
81
+ //
82
+ // c2 s1.s2 c1.s2 1 0 0
83
+ // 0 c1 -s1 * M = 0 c3 s3
84
+ // -s2 s1.c2 c1.c2 0 -s3 c3
85
+ //
86
+ // Thus: m11.c1 - m21.s1 = c3 & m12.c1 - m22.s1 = s3
87
+
88
+ const s1 = sin(res[0]);
89
+ const c1 = cos(res[0]);
90
+ res[2] = atan2(c1 * coeff(m4, j, k) - s1 * coeff(m4, k, k), c1 * coeff(m4, j, j) - s1 * coeff(m4, k, j));
91
+ } else {
92
+ res[0] = atan2(coeff(m4, j, k), coeff(m4, k, k));
93
+ const c2 = v2_magnitude(coeff(m4, i, i), coeff(m4, i, j));
94
+ if ((odd && res[0] < 0) || ((!odd) && res[0] > 0)) {
95
+ if (res[0] > 0) {
96
+ res[0] -= EIGEN_PI;
97
+ } else {
98
+ res[0] += EIGEN_PI;
99
+ }
100
+ res[1] = atan2(-coeff(m4, i, k), -c2);
101
+ } else
102
+ res[1] = atan2(-coeff(m4, i, k), c2);
103
+ const s1 = sin(res[0]);
104
+ const c1 = cos(res[0]);
105
+ res[2] = atan2(s1 * coeff(m4, k, i) - c1 * coeff(m4, j, i), c1 * coeff(m4, j, j) - s1 * coeff(m4, k, j));
106
+ }
107
+
108
+ if (!odd) {
109
+ // invert result
110
+ res[0] = -res[0];
111
+ res[1] = -res[1];
112
+ res[2] = -res[2];
113
+ }
114
+
115
+ }
@@ -482,7 +482,7 @@ export class TopoMesh {
482
482
  assert.equal(a.isTopoVertex, true, 'a.isTopoVertex !== true');
483
483
  assert.equal(b.isTopoVertex, true, 'b.isTopoVertex !== true');
484
484
 
485
- assert.notEqual(a, b, 'a === b, attempting to create an edge from vertex to itself')
485
+ assert.notEqual(a, b, 'a === b, attempting to create an edge from vertex to itself');
486
486
 
487
487
  const aEdges = a.edges;
488
488
  const n = aEdges.length;
@@ -0,0 +1,18 @@
1
+ import { PI2 } from "../math/MathUtils.js";
2
+
3
+ /**
4
+ * Force angle to [-PI, +PI] range
5
+ * based on answer https://stackoverflow.com/a/2323034/3973586
6
+ * @param {number} angle in radians
7
+ * @return {number} in radians
8
+ */
9
+ export function normalize_angle_rad(angle) {
10
+ let r = angle % PI2;
11
+ r = (r + PI2) % PI2;
12
+
13
+ if (r > Math.PI) {
14
+ r -= PI2;
15
+ }
16
+
17
+ return r;
18
+ }
@@ -0,0 +1,45 @@
1
+ import { normalize_angle_rad } from "./normalize_angle_rad.js";
2
+
3
+ test('0', () => {
4
+ expect(normalize_angle_rad(0)).toBe(0);
5
+ });
6
+
7
+ test('PI', () => {
8
+ expect(normalize_angle_rad(Math.PI)).toBe(Math.PI);
9
+ });
10
+
11
+ test('-PI', () => {
12
+ expect(normalize_angle_rad(-Math.PI)).toBeCloseTo(Math.PI);
13
+ });
14
+
15
+ test('2*PI', () => {
16
+ expect(normalize_angle_rad(Math.PI * 2)).toBeCloseTo(0);
17
+ });
18
+
19
+ test('-2*PI', () => {
20
+ expect(normalize_angle_rad(-Math.PI * 2)).toBeCloseTo(0);
21
+ });
22
+
23
+ test('-1', () => {
24
+ expect(normalize_angle_rad(-1)).toBeCloseTo(-1);
25
+ });
26
+
27
+ test('-3', () => {
28
+ expect(normalize_angle_rad(-3)).toBeCloseTo(-3);
29
+ });
30
+
31
+ test('1', () => {
32
+ expect(normalize_angle_rad(1)).toBeCloseTo(1);
33
+ });
34
+
35
+ test('3', () => {
36
+ expect(normalize_angle_rad(3)).toBeCloseTo(3);
37
+ });
38
+
39
+ test('2*PI + 1', () => {
40
+ expect(normalize_angle_rad(Math.PI * 2 + 1)).toBeCloseTo(1);
41
+ });
42
+
43
+ test('- ( 2*PI + 1)', () => {
44
+ expect(normalize_angle_rad(-(Math.PI * 2 + 1))).toBeCloseTo(-1);
45
+ });
@@ -7,30 +7,14 @@ import Vector3 from '../../../../../core/geom/Vector3.js';
7
7
  import { assert } from "../../../../../core/assert.js";
8
8
  import { lerp } from "../../../../../core/math/lerp.js";
9
9
  import { Euler, Matrix4 } from "three";
10
- import { PI2 } from "../../../../../core/math/MathUtils.js";
10
+ import { normalize_angle_rad } from "../../../../../core/geom/normalize_angle_rad.js";
11
+ import { eulerAnglesFromMatrix } from "../../../../../core/geom/3d/eulerAnglesFromMatrix.js";
11
12
 
12
13
  const m4 = new Matrix4();
13
14
  const euler = new Euler();
14
15
 
15
16
  const panOffset = new Vector3();
16
17
 
17
- /**
18
- * Force angle to [-PI, +PI] range
19
- * based on answer https://stackoverflow.com/a/2323034/3973586
20
- * @param {number} angle in radians
21
- * @return {number} in radians
22
- */
23
- function normalize_angle(angle) {
24
- let r = angle % PI2;
25
- r = (r + PI2) % PI2;
26
-
27
- if (r > Math.PI) {
28
- r -= PI2;
29
- }
30
-
31
- return r;
32
- }
33
-
34
18
  /**
35
19
  *
36
20
  * @param {number} displacement_x
@@ -40,8 +24,8 @@ function normalize_angle(angle) {
40
24
  */
41
25
  export function rotate_from_view(displacement_x, displacement_y, input, output) {
42
26
  // crop yaw and pitch to safe range of -PI to PI
43
- const i_pitch = normalize_angle(input.pitch);
44
- const i_yaw = normalize_angle(input.yaw);
27
+ const i_pitch = normalize_angle_rad(input.pitch);
28
+ const i_yaw = normalize_angle_rad(input.yaw);
45
29
 
46
30
  // flip rotation axis when pitch is over 90deg in either direction, that is UP vector is pointing down
47
31
  const d_yaw = ((i_pitch > Math.PI || i_pitch < -Math.PI) ? -1 : 1) * displacement_x;
@@ -146,9 +130,29 @@ class TopDownCameraController {
146
130
 
147
131
  euler.setFromRotationMatrix(m4, 'ZYX');
148
132
 
149
- this.pitch = normalize_angle(euler.x);
150
- this.yaw = normalize_angle(euler.y);
151
- this.roll = normalize_angle(euler.z);
133
+ const angles = [];
134
+ eulerAnglesFromMatrix(angles, m4.elements, 2, 1, 0);
135
+
136
+ const _p0 = normalize_angle_rad(euler.x);
137
+ const _y0 = normalize_angle_rad(euler.y);
138
+ const _r0 = normalize_angle_rad(euler.z);
139
+
140
+ this.pitch = _p0;
141
+ this.yaw = _y0;
142
+ this.roll = _r0;
143
+
144
+
145
+ const _p1 = angles[2];
146
+ const _y1 = angles[1];
147
+ const _r1 = angles[0];
148
+
149
+ if (_p0 !== _p0 || _y0 !== _y1 || _r0 !== _r1) {
150
+ console.warn('Angular discrepancy');
151
+ }
152
+
153
+ this.pitch = _p1;
154
+ this.yaw = _y1;
155
+ this.roll = _r1;
152
156
 
153
157
  this.distance = eye.distanceTo(target);
154
158
 
@@ -11,6 +11,15 @@ test('setFromEyeAndTarget does not introduce roll when UP is aligned with +Y', (
11
11
  expect(ut.roll).toBe(0);
12
12
  });
13
13
 
14
+ test('setFromEyeAndTarget, with different directional axial combinations', () => {
15
+ const ut = new TopDownCameraController();
16
+
17
+ ut.setFromEyeAndTarget(new Vector3(0, 1, -1), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
18
+ ut.setFromEyeAndTarget(new Vector3(0, 1, -1), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
19
+
20
+ expect(ut.roll).toBe(0);
21
+ });
22
+
14
23
  test('setFromEyeAndTarget results in roll respecting UP vector', () => {
15
24
  const eye = new Vector3();
16
25
  const target = new Vector3();
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "productName": "Meep",
6
6
  "description": "production-ready JavaScript game engine based on Entity Component System Architecture",
7
7
  "author": "Alexander Goldring",
8
- "version": "2.33.70",
8
+ "version": "2.33.71",
9
9
  "dependencies": {
10
10
  "gl-matrix": "3.4.3",
11
11
  "fast-levenshtein": "2.0.6",