@bubo-squared/gyroview 0.4.2 → 0.6.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +6 -6
  3. package/dist/index.d.ts +56 -15
  4. package/dist/packages/adapters/mediabunny/src/MediabunnyCodecReader.js +2 -2
  5. package/dist/packages/adapters/mediabunny/src/decoderConfigurations.js +20 -4
  6. package/dist/packages/core/src/application/recording/motionOf.js +5 -1
  7. package/dist/packages/core/src/application/stabilization/StabilizingFrameSink.js +4 -2
  8. package/dist/packages/core/src/domain/format/mp4/hevcCodecString.js +161 -0
  9. package/dist/packages/core/src/domain/motion/imu/ImuFrame.js +35 -3
  10. package/dist/packages/core/src/domain/motion/mounting/Mounting.js +117 -0
  11. package/dist/packages/core/src/domain/motion/orientation/OrientationTrack.js +4 -2
  12. package/dist/packages/core/src/domain/motion/orientation/gravity.js +1 -1
  13. package/dist/packages/core/src/domain/view/Framing.js +8 -2
  14. package/dist/packages/core/src/domain/view/ViewState.js +13 -8
  15. package/dist/packages/core/src/domain/view/lensTilesView.js +2 -1
  16. package/dist/packages/core/src/domain/view/motionLookView.js +103 -0
  17. package/dist/packages/core/src/domain/view/normalView.js +12 -5
  18. package/dist/packages/core/src/domain/view/panoramaView.js +2 -1
  19. package/dist/packages/core/src/domain/view/screenLook.js +74 -0
  20. package/dist/packages/core/src/domain/view/viewModes.js +18 -1
  21. package/dist/packages/core/src/index.js +8 -3
  22. package/dist/packages/core/src/shared/binary/ByteReader.js +4 -0
  23. package/dist/packages/core/src/shared/index.js +1 -0
  24. package/dist/packages/core/src/shared/math/Matrix3.js +29 -1
  25. package/dist/packages/core/src/shared/math/minimum.js +17 -0
  26. package/dist/packages/player/src/browserPlayer.js +6 -4
  27. package/dist/packages/player/src/composition/BrowserAttitudeSensor.js +192 -0
  28. package/dist/packages/player/src/composition/attitudeSensor.js +18 -0
  29. package/dist/packages/player/src/composition/buildPipeline.js +9 -5
  30. package/dist/packages/player/src/controls/MotionLookButton.js +35 -0
  31. package/dist/packages/player/src/controls/controlParts.js +1 -0
  32. package/dist/packages/player/src/controls/controls.js +1 -1
  33. package/dist/packages/player/src/controls/controlsBar.js +5 -3
  34. package/dist/packages/player/src/controls/controlsMarkup.js +1 -0
  35. package/dist/packages/player/src/controls/icons.js +1 -0
  36. package/dist/packages/player/src/controls/messages.js +1 -0
  37. package/dist/packages/player/src/element/GyroViewElement.js +21 -0
  38. package/dist/packages/player/src/player/AutomaticStarts.js +54 -0
  39. package/dist/packages/player/src/player/MotionLook.js +150 -0
  40. package/dist/packages/player/src/player/Player.js +46 -39
  41. package/dist/packages/player/src/player/PlayerEvents.js +1 -0
  42. package/dist/packages/player/src/player/PlayerView.js +44 -13
  43. package/dist/packages/player/src/player/ensureFinite.js +2 -1
  44. package/dist/packages/player/src/player/viewAngles.js +17 -0
  45. package/dist/standalone.js +115 -62
  46. package/package.json +3 -3
@@ -0,0 +1,117 @@
1
+ import { HALF_TURN, QUARTER_TURN, degrees, degreesToRadians, radians } from "../../../shared/units/angle.js";
2
+ import { ZERO_VECTOR3, addVectors, dotProduct, magnitudeOf } from "../../../shared/math/Vector3.js";
3
+ import { multiplyMatrices, rotationAboutX, rotationAboutY, rotationAboutZ, transformVector, transposeMatrix } from "../../../shared/math/Matrix3.js";
4
+ import { toBodyFrame } from "../imu/ImuFrame.js";
5
+ import { indexOfLeast } from "../../../shared/math/minimum.js";
6
+ import { measuredGravity } from "../orientation/gravity.js";
7
+ //#region ../../packages/core/src/domain/motion/mounting/Mounting.ts
8
+ var QUARTER_TURN_ANGLE = degreesToRadians(QUARTER_TURN);
9
+ var MINUS_QUARTER_TURN_ANGLE = degreesToRadians(degrees(-QUARTER_TURN));
10
+ var HALF_TURN_ANGLE = degreesToRadians(HALF_TURN);
11
+ /**
12
+ * Also how a recording stands whose gravity cannot be read: one without a gyro record, or whose
13
+ * IMU frame is a guess.
14
+ */
15
+ var UPRIGHT_MOUNTING = {
16
+ name: "upright",
17
+ toBody: lensLevel(radians(0))
18
+ };
19
+ var DOWN = [
20
+ 0,
21
+ 1,
22
+ 0
23
+ ];
24
+ var LENS_AXIS = [
25
+ 0,
26
+ 0,
27
+ 1
28
+ ];
29
+ /**
30
+ * The four ways a camera can stand with its lens axis level, one for each body axis across it
31
+ * that gravity can lie along.
32
+ */
33
+ var LENS_LEVEL_MOUNTINGS = [
34
+ UPRIGHT_MOUNTING,
35
+ {
36
+ name: "upside down",
37
+ toBody: lensLevel(HALF_TURN_ANGLE)
38
+ },
39
+ {
40
+ name: "on its right side",
41
+ toBody: lensLevel(MINUS_QUARTER_TURN_ANGLE)
42
+ },
43
+ {
44
+ name: "on its left side",
45
+ toBody: lensLevel(QUARTER_TURN_ANGLE)
46
+ }
47
+ ];
48
+ /**
49
+ * The two ways a camera can stand with its lens axis the vertical.
50
+ */
51
+ var LENS_VERTICAL_MOUNTINGS = [{
52
+ name: "with lens 0 up",
53
+ toBody: lensVertical(MINUS_QUARTER_TURN_ANGLE)
54
+ }, {
55
+ name: "with lens 0 down",
56
+ toBody: lensVertical(QUARTER_TURN_ANGLE)
57
+ }];
58
+ var LENS_VERTICAL_CONE_COSINE = Math.cos(degreesToRadians(degrees(30)));
59
+ /**
60
+ * A half turn about the vertical faces the upright frame away from lens 0, then a turn about the
61
+ * lens axis stands the body as it stood.
62
+ */
63
+ function lensLevel(roll) {
64
+ return multiplyMatrices(rotationAboutZ(roll), rotationAboutY(HALF_TURN_ANGLE));
65
+ }
66
+ /**
67
+ * A quarter turn about the vertical faces the upright frame along the body's minus x, then a
68
+ * quarter turn about the lateral axis stands lens 0's axis up or down.
69
+ */
70
+ function lensVertical(tilt) {
71
+ return multiplyMatrices(rotationAboutX(tilt), rotationAboutY(MINUS_QUARTER_TURN_ANGLE));
72
+ }
73
+ /**
74
+ * The mounting whose down lies nearest the gravity the accelerometer measured whenever the camera
75
+ * rested, read through the IMU frame: lens-vertical within its cone of the lens axis, lens-level
76
+ * outside it. Quarter turns only, so the picture keeps every tilt the camera really took.
77
+ * Upright for a camera that never rests, and for one whose IMU frame is a guess: read through a
78
+ * wrong frame, gravity would stand the picture on its side or on its head.
79
+ */
80
+ function mountingOf(gyro, frame) {
81
+ if (!frame.isVerified) return UPRIGHT_MOUNTING;
82
+ const gravity = restingGravitySumOf(gyro, frame);
83
+ const mountings = isNearLensAxis(gravity) ? LENS_VERTICAL_MOUNTINGS : LENS_LEVEL_MOUNTINGS;
84
+ const misalignments = mountings.map((mounting) => -dotProduct(transformVector(mounting.toBody, DOWN), gravity));
85
+ return mountings[indexOfLeast(misalignments)] ?? UPRIGHT_MOUNTING;
86
+ }
87
+ function isNearLensAxis(gravity) {
88
+ return Math.abs(dotProduct(gravity, LENS_AXIS)) > LENS_VERTICAL_CONE_COSINE * magnitudeOf(gravity);
89
+ }
90
+ /**
91
+ * The IMU frame as it lies in the mounting's upright frame: what the camera's orientation is
92
+ * integrated in, so that levelling and heading start from the camera as it stood. Its `toBody`
93
+ * reads into the upright frame, which stands for the body throughout the integration.
94
+ */
95
+ function uprightImuFrame(frame, mounting) {
96
+ return {
97
+ ...frame,
98
+ toBody: multiplyMatrices(transposeMatrix(mounting.toBody), frame.toBody)
99
+ };
100
+ }
101
+ /**
102
+ * A rotation into the mounting's upright frame, such as a stabilizer's, carried on into the body
103
+ * the lenses are posed in.
104
+ */
105
+ function rotationIntoBody(mounting, uprightRotation) {
106
+ return multiplyMatrices(mounting.toBody, uprightRotation);
107
+ }
108
+ function restingGravitySumOf(gyro, frame) {
109
+ let sum = ZERO_VECTOR3;
110
+ for (let index = 0; index < gyro.length; index += 1) {
111
+ const gravity = measuredGravity(toBodyFrame(frame, gyro.sampleAt(index).acceleration));
112
+ if (gravity) sum = addVectors(sum, gravity);
113
+ }
114
+ return sum;
115
+ }
116
+ //#endregion
117
+ export { UPRIGHT_MOUNTING, mountingOf, rotationIntoBody, uprightImuFrame };
@@ -24,8 +24,10 @@ var QUATERNION_COMPONENTS = 4;
24
24
  */
25
25
  var MAX_STEP_SECONDS = .05;
26
26
  /**
27
- * The camera's orientation over the whole recording, body frame to world frame, one quaternion
28
- * per gyro sample. World: y down along gravity, z along the body's forward axis at the start.
27
+ * The camera's orientation over the whole recording, from the frame its IMU frame reads into (the
28
+ * body, or the upright frame of its mounting, as the player integrates it) to the world, one
29
+ * quaternion per gyro sample. World: y down along gravity, z along that frame's forward axis at
30
+ * the start.
29
31
  */
30
32
  var OrientationTrack = class OrientationTrack {
31
33
  videoTimes;
@@ -94,4 +94,4 @@ function anyPerpendicular(v) {
94
94
  ];
95
95
  }
96
96
  //#endregion
97
- export { WORLD_DOWN, gravityCorrection, initialOrientation };
97
+ export { WORLD_DOWN, gravityCorrection, initialOrientation, measuredGravity };
@@ -1,4 +1,4 @@
1
- import { DEFAULT_VIEW, isSameView } from "./ViewState.js";
1
+ import { DEFAULT_VIEW, clampView, isSameView } from "./ViewState.js";
2
2
  import { FITTED, isSameMagnification } from "./magnification.js";
3
3
  //#region ../../packages/core/src/domain/view/Framing.ts
4
4
  var DEFAULT_FRAMING = {
@@ -19,6 +19,12 @@ function withView(framing, view) {
19
19
  };
20
20
  }
21
21
  /**
22
+ * The framing with a page's view taken as it is given, within the sphere.
23
+ */
24
+ function withPlacedView(framing, view) {
25
+ return withView(framing, clampView(view));
26
+ }
27
+ /**
22
28
  * The framing with the lens tiles' magnification replaced, the other parts kept.
23
29
  */
24
30
  function withLenses(framing, lenses) {
@@ -28,4 +34,4 @@ function withLenses(framing, lenses) {
28
34
  };
29
35
  }
30
36
  //#endregion
31
- export { DEFAULT_FRAMING, isSameFraming, withLenses, withView };
37
+ export { DEFAULT_FRAMING, isSameFraming, withLenses, withPlacedView, withView };
@@ -1,7 +1,7 @@
1
1
  import { degrees, degreesToRadians, wrapHalfTurn } from "../../shared/units/angle.js";
2
2
  import { ensureInvariant } from "../../shared/errors/GyroViewError.js";
3
3
  import { clamp } from "../../shared/math/clamp.js";
4
- import { multiplyMatrices, rotationAboutX, rotationAboutY } from "../../shared/math/Matrix3.js";
4
+ import { multiplyMatrices, rotationAboutX, rotationAboutY, rotationAboutZ } from "../../shared/math/Matrix3.js";
5
5
  //#region ../../packages/core/src/domain/view/ViewState.ts
6
6
  /**
7
7
  * From a short telephoto to a wide angle: past 120 degrees a rectilinear view stretches its
@@ -14,35 +14,40 @@ var MAX_PITCH_DEGREES = 90;
14
14
  var DEFAULT_VIEW = {
15
15
  yaw: degrees(0),
16
16
  pitch: degrees(0),
17
+ roll: degrees(0),
17
18
  fieldOfView: degrees(DEFAULT_FIELD_OF_VIEW_DEGREES)
18
19
  };
19
20
  /**
20
- * Keeps the view inside the sphere: yaw wrapped to (-180, 180], pitch within the poles, field
21
- * of view within its bounds. Non-finite angles are a programming error.
21
+ * Keeps the view inside the sphere: yaw and roll wrapped to (-180, 180], pitch within the poles,
22
+ * field of view within its bounds. Non-finite angles are a programming error.
22
23
  */
23
24
  function clampView(view) {
24
25
  ensureInvariant([
25
26
  view.yaw,
26
27
  view.pitch,
28
+ view.roll,
27
29
  view.fieldOfView
28
30
  ].every((angle) => Number.isFinite(angle)), "view angles must be finite");
29
31
  return {
30
32
  ...view,
31
33
  yaw: wrapHalfTurn(view.yaw),
34
+ roll: wrapHalfTurn(view.roll),
32
35
  pitch: degrees(clamp(view.pitch, -90, MAX_PITCH_DEGREES)),
33
36
  fieldOfView: degrees(clamp(view.fieldOfView, NARROWEST_DEGREES, WIDEST_DEGREES))
34
37
  };
35
38
  }
36
39
  function isSameView(a, b) {
37
- return a.yaw === b.yaw && a.pitch === b.pitch && a.fieldOfView === b.fieldOfView;
40
+ return a.yaw === b.yaw && a.pitch === b.pitch && a.roll === b.roll && a.fieldOfView === b.fieldOfView;
38
41
  }
39
42
  /**
40
- * Turns view-space directions (z forward) into camera body directions: pitch about the body's
41
- * lateral axis first, then yaw about its vertical axis. Body y points down, so a positive
42
- * rotation about x tilts the forward direction upwards.
43
+ * Turns view-space directions (x right, y down, z forward) into camera body directions: roll
44
+ * about the line of sight first, then pitch about the body's lateral axis, then yaw about its
45
+ * vertical axis. Body y points down, so a positive rotation about x tilts the forward direction
46
+ * upwards, and one about z turns the screen's right toward its bottom: clockwise.
43
47
  */
44
48
  function viewRotation(view) {
45
- return multiplyMatrices(rotationAboutY(degreesToRadians(view.yaw)), rotationAboutX(degreesToRadians(view.pitch)));
49
+ const pointed = multiplyMatrices(rotationAboutY(degreesToRadians(view.yaw)), rotationAboutX(degreesToRadians(view.pitch)));
50
+ return multiplyMatrices(pointed, rotationAboutZ(degreesToRadians(view.roll)));
46
51
  }
47
52
  //#endregion
48
53
  export { DEFAULT_VIEW, clampView, isSameView, viewRotation };
@@ -1,6 +1,6 @@
1
1
  import { aspectOf, boundsOf, lensTiles, shiftOf } from "./screenLayout.js";
2
2
  import { FITTED, clampMagnification, magnifiedArea, magnifyAt, panMagnification } from "./magnification.js";
3
- import { withLenses } from "./Framing.js";
3
+ import { withLenses, withPlacedView } from "./Framing.js";
4
4
  //#region ../../packages/core/src/domain/view/lensTilesView.ts
5
5
  /**
6
6
  * An arrow's angle moves zoomed tiles as a drag across the viewport would move them, a 90-degree
@@ -54,6 +54,7 @@ var LENS_TILES_VIEW = {
54
54
  turn: turnLenses,
55
55
  zoom: (framing, zoom, context) => withLenses(framing, magnifyAt(fittedTiles(context).bounds, framing.lenses, zoom)),
56
56
  reset: (framing) => withLenses(framing, FITTED),
57
+ place: withPlacedView,
57
58
  picture: (framing, context) => ({
58
59
  kind: "lens-tiles",
59
60
  tiles: magnifiedTiles(framing, context)
@@ -0,0 +1,103 @@
1
+ import { degrees, degreesToRadians, wrapHalfTurn } from "../../shared/units/angle.js";
2
+ import { milliseconds } from "../../shared/units/time.js";
3
+ import { angleBetweenRotations } from "../../shared/math/Matrix3.js";
4
+ import { DEFAULT_VIEW, clampView, viewRotation } from "./ViewState.js";
5
+ import { SCREEN_CENTRE, aspectOf } from "./screenLayout.js";
6
+ import { lookAt, panView, zoomViewAt } from "./viewGestures.js";
7
+ import { withView } from "./Framing.js";
8
+ import { rectilinearPicture } from "./normalView.js";
9
+ //#region ../../packages/core/src/domain/view/motionLookView.ts
10
+ var READING_GAP = milliseconds(500);
11
+ var UNSEEN_TURN = degreesToRadians(degrees(.01));
12
+ /**
13
+ * Where a reading turns a view the device holds. The yaw only turns by as much as the device
14
+ * turned since the reading it continues, so a drag, Reset view or a page's yaw move the heading
15
+ * and the device carries on from there; after a gap, and at the first reading, the yaw stays.
16
+ * The pitch and roll are the device's, level with the real horizon. A turn too small to see
17
+ * leaves the view as it is and keeps counting from the look last shown, at the new reading's
18
+ * time: a phone at rest draws nothing, and a slow drift lands in full once it shows.
19
+ */
20
+ function followReading(view, previous, reading) {
21
+ const { pitch, roll } = reading.look;
22
+ if (!isOneMovement(previous, reading)) return {
23
+ view: clampView({
24
+ ...view,
25
+ pitch,
26
+ roll
27
+ }),
28
+ reading
29
+ };
30
+ const yaw = degrees(view.yaw + wrapHalfTurn(degrees(reading.look.yaw - previous.look.yaw)));
31
+ const turned = clampView({
32
+ ...view,
33
+ yaw,
34
+ pitch,
35
+ roll
36
+ });
37
+ return isUnseenTurn(view, turned) ? {
38
+ view,
39
+ reading: {
40
+ look: previous.look,
41
+ at: reading.at
42
+ }
43
+ } : {
44
+ view: turned,
45
+ reading
46
+ };
47
+ }
48
+ function isOneMovement(previous, reading) {
49
+ return previous !== void 0 && reading.at - previous.at <= READING_GAP;
50
+ }
51
+ function isUnseenTurn(view, turned) {
52
+ return angleBetweenRotations(viewRotation(view), viewRotation(turned)) < UNSEEN_TURN;
53
+ }
54
+ /**
55
+ * The view once the device lets go of it: level, looking where it looked.
56
+ */
57
+ function withoutRoll(view) {
58
+ return {
59
+ ...view,
60
+ roll: degrees(0)
61
+ };
62
+ }
63
+ /**
64
+ * The normal view while the device holds it (ADR 0040): the device gives the pitch and the roll,
65
+ * so drags and the arrows turn the heading alone, a zoom narrows about the centre, and a page's
66
+ * view sets the heading and the zoom.
67
+ */
68
+ var MOTION_LOOK_VIEW = {
69
+ isStabilized: true,
70
+ canPan: () => true,
71
+ pan: (framing, delta, { viewport }) => withView(framing, panView(framing.view, {
72
+ x: delta.x,
73
+ y: 0
74
+ }, viewport.width)),
75
+ turn: (framing, turn) => {
76
+ const { view } = framing;
77
+ return withView(framing, lookAt(view, degrees(view.yaw + turn.yaw), view.pitch));
78
+ },
79
+ zoom: (framing, zoom, { viewport }) => withView(framing, zoomViewAt(framing.view, {
80
+ steps: zoom.steps,
81
+ focus: SCREEN_CENTRE
82
+ }, aspectOf(viewport))),
83
+ reset: (framing) => withHeading(framing, {
84
+ ...framing.view,
85
+ yaw: degrees(0),
86
+ fieldOfView: DEFAULT_VIEW.fieldOfView
87
+ }),
88
+ place: withHeading,
89
+ picture: rectilinearPicture
90
+ };
91
+ /**
92
+ * The held view with the yaw and field of view of `view`, the device's pitch and roll kept.
93
+ */
94
+ function withHeading(framing, view) {
95
+ const { yaw, fieldOfView } = view;
96
+ return withView(framing, clampView({
97
+ ...framing.view,
98
+ yaw,
99
+ fieldOfView
100
+ }));
101
+ }
102
+ //#endregion
103
+ export { MOTION_LOOK_VIEW, followReading, withoutRoll };
@@ -2,7 +2,7 @@ import { degrees } from "../../shared/units/angle.js";
2
2
  import { DEFAULT_VIEW, viewRotation } from "./ViewState.js";
3
3
  import { WHOLE_SCREEN, aspectOf } from "./screenLayout.js";
4
4
  import { lookAt, panView, zoomViewAt } from "./viewGestures.js";
5
- import { withView } from "./Framing.js";
5
+ import { withPlacedView, withView } from "./Framing.js";
6
6
  //#region ../../packages/core/src/domain/view/normalView.ts
7
7
  /**
8
8
  * A rectilinear window into the stitched sphere over the whole viewport, turned by drags and
@@ -18,12 +18,19 @@ var NORMAL_VIEW = {
18
18
  },
19
19
  zoom: (framing, zoom, { viewport }) => withView(framing, zoomViewAt(framing.view, zoom, aspectOf(viewport))),
20
20
  reset: (framing) => withView(framing, DEFAULT_VIEW),
21
- picture: ({ view }) => ({
21
+ place: withPlacedView,
22
+ picture: rectilinearPicture
23
+ };
24
+ /**
25
+ * The view's rectilinear picture over the whole viewport, turned by its whole rotation.
26
+ */
27
+ function rectilinearPicture({ view }) {
28
+ return {
22
29
  kind: "rectilinear",
23
30
  rotation: viewRotation(view),
24
31
  fieldOfView: view.fieldOfView,
25
32
  area: WHOLE_SCREEN
26
- })
27
- };
33
+ };
34
+ }
28
35
  //#endregion
29
- export { NORMAL_VIEW };
36
+ export { NORMAL_VIEW, rectilinearPicture };
@@ -3,7 +3,7 @@ import { rotationAboutY } from "../../shared/math/Matrix3.js";
3
3
  import { HALF, aspectOf, fittedRectangle, pictureAt, shiftOf } from "./screenLayout.js";
4
4
  import { lookAt } from "./viewGestures.js";
5
5
  import { FITTED, clampMagnification, magnifiedArea, magnifyAt, panMagnification, pictureShiftOf } from "./magnification.js";
6
- import { withView } from "./Framing.js";
6
+ import { withPlacedView, withView } from "./Framing.js";
7
7
  //#region ../../packages/core/src/domain/view/panoramaView.ts
8
8
  /**
9
9
  * An equirectangular picture spans a full turn across for a half turn from top to bottom.
@@ -86,6 +86,7 @@ var PANORAMA_VIEW = {
86
86
  ...withView(framing, lookAt(framing.view, degrees(0), framing.view.pitch)),
87
87
  panorama: FITTED
88
88
  }),
89
+ place: withPlacedView,
89
90
  picture: (framing, context) => {
90
91
  const { fitted, panorama } = shownPanorama(framing, context);
91
92
  return {
@@ -0,0 +1,74 @@
1
+ import { degrees, degreesToRadians, radians, radiansToDegrees } from "../../shared/units/angle.js";
2
+ import { clamp } from "../../shared/math/clamp.js";
3
+ import { multiplyMatrices, rotationAboutX, rotationAboutY, rotationAboutZ } from "../../shared/math/Matrix3.js";
4
+ //#region ../../packages/core/src/domain/view/screenLook.ts
5
+ /**
6
+ * View space (x right, y down, z into the screen) into the screen's frame (x right, y up, z out
7
+ * of the screen): the viewer looks through the screen, out of its back.
8
+ */
9
+ var VIEW_TO_SCREEN = [
10
+ 1,
11
+ 0,
12
+ 0,
13
+ 0,
14
+ -1,
15
+ 0,
16
+ 0,
17
+ 0,
18
+ -1
19
+ ];
20
+ /**
21
+ * The browser's world (x east, y north, z up) into the view's (x right, y down, z forward), north
22
+ * standing for forward: the yaw only counts changes, so any horizontal direction would do.
23
+ */
24
+ var WORLD_TO_VIEW = [
25
+ 1,
26
+ 0,
27
+ 0,
28
+ 0,
29
+ 0,
30
+ -1,
31
+ 0,
32
+ 1,
33
+ 0
34
+ ];
35
+ /**
36
+ * Below this cosine of the pitch the screen looks straight up or down, where only the sum or the
37
+ * difference of yaw and roll is defined, and each one alone would be read from rounding noise:
38
+ * the look then takes the turn as yaw alone, within a millionth of a radian of the screen.
39
+ */
40
+ var POLE_COSINE = 1e-6;
41
+ /**
42
+ * The rotation that turns view-space directions into the view's world for a device held this
43
+ * way, as `viewRotation` turns them for a view.
44
+ */
45
+ function screenRotationOf(attitude) {
46
+ const device = multiplyMatrices(multiplyMatrices(rotationAbout(rotationAboutZ, attitude.alpha), rotationAbout(rotationAboutX, attitude.beta)), rotationAbout(rotationAboutY, attitude.gamma));
47
+ const screen = multiplyMatrices(rotationAbout(rotationAboutZ, degrees(-attitude.screenAngle)), VIEW_TO_SCREEN);
48
+ return multiplyMatrices(multiplyMatrices(WORLD_TO_VIEW, device), screen);
49
+ }
50
+ /**
51
+ * The yaw, pitch and roll whose `viewRotation` is the screen's rotation: the rotation
52
+ * `Ry(yaw) Rx(pitch) Rz(roll)` read back from its entries.
53
+ */
54
+ function screenLookOf(attitude) {
55
+ const [m00, , m02, m10, m11, m12, m20, , m22] = screenRotationOf(attitude);
56
+ const pitch = angleOf(Math.asin(-clamp(m12, -1, 1)));
57
+ return Math.hypot(m10, m11) < POLE_COSINE ? {
58
+ yaw: angleOf(Math.atan2(-m20, m00)),
59
+ pitch,
60
+ roll: degrees(0)
61
+ } : {
62
+ yaw: angleOf(Math.atan2(m02, m22)),
63
+ pitch,
64
+ roll: angleOf(Math.atan2(m10, m11))
65
+ };
66
+ }
67
+ function rotationAbout(rotation, angle) {
68
+ return rotation(degreesToRadians(angle));
69
+ }
70
+ function angleOf(value) {
71
+ return radiansToDegrees(radians(value));
72
+ }
73
+ //#endregion
74
+ export { screenLookOf, screenRotationOf };
@@ -1,5 +1,6 @@
1
1
  import { LENS_TILES_VIEW } from "./lensTilesView.js";
2
2
  import { NORMAL_VIEW } from "./normalView.js";
3
+ import { MOTION_LOOK_VIEW } from "./motionLookView.js";
3
4
  import { PANORAMA_VIEW } from "./panoramaView.js";
4
5
  //#region ../../packages/core/src/domain/view/viewModes.ts
5
6
  var RULES = {
@@ -10,5 +11,21 @@ var RULES = {
10
11
  function viewModeRulesFor(mode) {
11
12
  return RULES[mode];
12
13
  }
14
+ /**
15
+ * Motion look turns the normal view alone: the flat pictures show the whole sphere or the raw
16
+ * lenses, which a phone held up as a window has nothing to turn.
17
+ */
18
+ var MOTION_LOOK_RULES = {
19
+ normal: MOTION_LOOK_VIEW,
20
+ equirectangular: void 0,
21
+ "raw-lenses": void 0
22
+ };
23
+ /**
24
+ * The rules of a mode while the device holds the view, or none for a mode the device does not
25
+ * turn.
26
+ */
27
+ function motionLookRulesFor(mode) {
28
+ return MOTION_LOOK_RULES[mode];
29
+ }
13
30
  //#endregion
14
- export { viewModeRulesFor };
31
+ export { motionLookRulesFor, viewModeRulesFor };
@@ -15,7 +15,9 @@ import { clamp } from "./shared/math/clamp.js";
15
15
  import "./domain/motion/timing/FrameTimes.js";
16
16
  import { IDENTITY_MATRIX3 } from "./shared/math/Matrix3.js";
17
17
  import "./domain/motion/imu/ImuFrame.js";
18
+ import "./shared/math/minimum.js";
18
19
  import "./shared/math/Quaternion.js";
20
+ import { UPRIGHT_MOUNTING } from "./domain/motion/mounting/Mounting.js";
19
21
  import "./domain/motion/orientation/OrientationTrack.js";
20
22
  import { timeRecording } from "./application/recording/timeRecording.js";
21
23
  import "./domain/format/naming/RecordingFileName.js";
@@ -26,11 +28,12 @@ import "./domain/container/TrackSampleTable.js";
26
28
  import { readSampleTable } from "./application/recording/readSampleTable.js";
27
29
  import "./domain/stitching/LensLayout.js";
28
30
  import { detectLensLayout } from "./domain/format/layout/detectLensLayout.js";
31
+ import { hevcCodecStringOf } from "./domain/format/mp4/hevcCodecString.js";
29
32
  import { DEFAULT_STABILIZATION_MODE, STABILIZATION_MODES } from "./domain/motion/stabilization/Stabilizer.js";
30
33
  import { stabilizerFor } from "./domain/motion/stabilization/stabilizers.js";
31
34
  import "./domain/optics/lensPose.js";
32
35
  import "./domain/optics/gainMatch.js";
33
- import { clampView, isSameView } from "./domain/view/ViewState.js";
36
+ import "./domain/view/ViewState.js";
34
37
  import { DEFAULT_PICTURE_QUALITY, PICTURE_QUALITIES, pixelRatioCapOf } from "./domain/view/PictureQuality.js";
35
38
  import { DEFAULT_VIEW_MODE, VIEW_MODES } from "./domain/view/ViewMode.js";
36
39
  import { SCREEN_CENTRE, aspectOf } from "./domain/view/screenLayout.js";
@@ -38,7 +41,9 @@ import { aspectOfArea, planeHalfExtentOf } from "./domain/view/rectilinear.js";
38
41
  import { lookAt, zoomStepsForPinch } from "./domain/view/viewGestures.js";
39
42
  import "./domain/view/magnification.js";
40
43
  import { DEFAULT_FRAMING, isSameFraming } from "./domain/view/Framing.js";
41
- import { viewModeRulesFor } from "./domain/view/viewModes.js";
44
+ import { followReading, withoutRoll } from "./domain/view/motionLookView.js";
45
+ import { motionLookRulesFor, viewModeRulesFor } from "./domain/view/viewModes.js";
46
+ import { screenLookOf } from "./domain/view/screenLook.js";
42
47
  import "./domain/stitching/seamRing.js";
43
48
  import { buildStitchingSetup, lensFrameOrder } from "./domain/stitching/StitchingSetup.js";
44
49
  import { StabilizingFrameSink } from "./application/stabilization/StabilizingFrameSink.js";
@@ -68,4 +73,4 @@ import { WallClock } from "./application/playback/WallClock.js";
68
73
  import { isFlowing } from "./domain/playback/PlayerState.js";
69
74
  import { TypedEmitter } from "./shared/events/TypedEmitter.js";
70
75
  import { PlaybackSession } from "./application/playback/PlaybackSession.js";
71
- export { BT709_LUMINANCE, ByteRange, COLOUR_PRIMARIES, DEFAULT_FRAMING, DEFAULT_PICTURE_QUALITY, DEFAULT_STABILIZATION_MODE, DEFAULT_VIEW_MODE, Deferred, Ending, GYRO_VIEW_ERROR_CATEGORIES, GYRO_VIEW_ERROR_CODES, GainMatchingFrameSink, GyroViewError, HLG_OETF, IDENTITY_MATRIX3, ITERATION_END, MATRIX_COEFFICIENTS, MEI_TERM_CAPACITY, Outbox, PICTURE_QUALITIES, PlaybackSession, RecordingBuffer, RunStop, SCREEN_CENTRE, STABILIZATION_MODES, STOPPED, SourceByteStream, StabilizingFrameSink, TRANSFER_CHARACTERISTICS, TypedEmitter, VIEW_MODES, WallClock, asGyroViewError, aspectOf, aspectOfArea, buildStitchingSetup, clamp, clampView, degrees, detectLensLayout, displayConversionsOf, downloadPolicyFor, ensureIndexInRange, ensureInvariant, fileNameOfUrl, hasErrorCode, inspectRecording, isAbortError, isFlowing, isGyroViewErrorCode, isSameFraming, isSameView, keysOf, lazy, lensFrameOrder, locateOtherLensFile, lookAt, matrixCorrectionOf, messageOf, microseconds, microsecondsToSeconds, milliseconds, namedOrUnspecified, pixelRatioCapOf, planeHalfExtentOf, probeDecoding, readRecording, readSampleTable, seconds, secondsToMicroseconds, stabilizerFor, startFileDownload, timeRecording, viewModeRulesFor, zoomStepsForPinch };
76
+ export { BT709_LUMINANCE, ByteRange, COLOUR_PRIMARIES, DEFAULT_FRAMING, DEFAULT_PICTURE_QUALITY, DEFAULT_STABILIZATION_MODE, DEFAULT_VIEW_MODE, Deferred, Ending, GYRO_VIEW_ERROR_CATEGORIES, GYRO_VIEW_ERROR_CODES, GainMatchingFrameSink, GyroViewError, HLG_OETF, IDENTITY_MATRIX3, ITERATION_END, MATRIX_COEFFICIENTS, MEI_TERM_CAPACITY, Outbox, PICTURE_QUALITIES, PlaybackSession, RecordingBuffer, RunStop, SCREEN_CENTRE, STABILIZATION_MODES, STOPPED, SourceByteStream, StabilizingFrameSink, TRANSFER_CHARACTERISTICS, TypedEmitter, UPRIGHT_MOUNTING, VIEW_MODES, WallClock, asGyroViewError, aspectOf, aspectOfArea, buildStitchingSetup, clamp, degrees, detectLensLayout, displayConversionsOf, downloadPolicyFor, ensureIndexInRange, ensureInvariant, fileNameOfUrl, followReading, hasErrorCode, hevcCodecStringOf, inspectRecording, isAbortError, isFlowing, isGyroViewErrorCode, isSameFraming, keysOf, lazy, lensFrameOrder, locateOtherLensFile, lookAt, matrixCorrectionOf, messageOf, microseconds, microsecondsToSeconds, milliseconds, motionLookRulesFor, namedOrUnspecified, pixelRatioCapOf, planeHalfExtentOf, probeDecoding, readRecording, readSampleTable, screenLookOf, seconds, secondsToMicroseconds, stabilizerFor, startFileDownload, timeRecording, viewModeRulesFor, withoutRoll, zoomStepsForPinch };
@@ -41,6 +41,10 @@ var ByteReader = class {
41
41
  this.ensureAvailable(offset, UINT32_SIZE);
42
42
  return this.view.getUint32(offset, IS_LITTLE_ENDIAN);
43
43
  }
44
+ uint16BeAt(offset) {
45
+ this.ensureAvailable(offset, UINT16_SIZE);
46
+ return this.view.getUint16(offset, IS_BIG_ENDIAN);
47
+ }
44
48
  uint32BeAt(offset) {
45
49
  this.ensureAvailable(offset, UINT32_SIZE);
46
50
  return this.view.getUint32(offset, IS_BIG_ENDIAN);
@@ -5,6 +5,7 @@ import { microseconds, microsecondsToSeconds, milliseconds, seconds, secondsToMi
5
5
  import "./math/Vector3.js";
6
6
  import { clamp } from "./math/clamp.js";
7
7
  import { IDENTITY_MATRIX3 } from "./math/Matrix3.js";
8
+ import "./math/minimum.js";
8
9
  import "./math/Quaternion.js";
9
10
  import { keysOf } from "./keysOf.js";
10
11
  import { Outbox } from "./events/Outbox.js";
@@ -1,3 +1,5 @@
1
+ import { radians } from "../units/angle.js";
2
+ import { clamp } from "./clamp.js";
1
3
  //#region ../../packages/core/src/shared/math/Matrix3.ts
2
4
  var IDENTITY_MATRIX3 = [
3
5
  1,
@@ -73,6 +75,23 @@ function multiplyMatrices(a, b) {
73
75
  a20 * b02 + a21 * b12 + a22 * b22
74
76
  ];
75
77
  }
78
+ /**
79
+ * The transpose, which for a rotation is its inverse.
80
+ */
81
+ function transposeMatrix(m) {
82
+ const [m00, m01, m02, m10, m11, m12, m20, m21, m22] = m;
83
+ return [
84
+ m00,
85
+ m10,
86
+ m20,
87
+ m01,
88
+ m11,
89
+ m21,
90
+ m02,
91
+ m12,
92
+ m22
93
+ ];
94
+ }
76
95
  function transformVector(m, v) {
77
96
  const [m00, m01, m02, m10, m11, m12, m20, m21, m22] = m;
78
97
  const [x, y, z] = v;
@@ -89,5 +108,14 @@ function determinantOf(m) {
89
108
  const [m00, m01, m02, m10, m11, m12, m20, m21, m22] = m;
90
109
  return m00 * (m11 * m22 - m12 * m21) - m01 * (m10 * m22 - m12 * m20) + m02 * (m10 * m21 - m11 * m20);
91
110
  }
111
+ /**
112
+ * How far rotation `b` turns from rotation `a`: the angle of `aᵀ b`, from its trace
113
+ * (`1 + 2 cos θ`), which is the sum of the two matrices' entrywise products; clamped against
114
+ * rounding.
115
+ */
116
+ function angleBetweenRotations(a, b) {
117
+ const trace = a.reduce((sum, entry, index) => sum + entry * (b[index] ?? 0), 0);
118
+ return radians(Math.acos(clamp((trace - 1) / 2, -1, 1)));
119
+ }
92
120
  //#endregion
93
- export { IDENTITY_MATRIX3, determinantOf, multiplyMatrices, rotationAboutX, rotationAboutY, rotationAboutZ, transformVector };
121
+ export { IDENTITY_MATRIX3, angleBetweenRotations, determinantOf, multiplyMatrices, rotationAboutX, rotationAboutY, rotationAboutZ, transformVector, transposeMatrix };
@@ -0,0 +1,17 @@
1
+ //#region ../../packages/core/src/shared/math/minimum.ts
2
+ /**
3
+ * The index of the least of the values, the first on a tie; values that are not finite never
4
+ * win, so a list of them all gives index 0.
5
+ */
6
+ function indexOfLeast(values) {
7
+ let best = 0;
8
+ let least = Infinity;
9
+ for (const [index, value] of values.entries()) {
10
+ if (!Number.isFinite(value) || !(value < least)) continue;
11
+ best = index;
12
+ least = value;
13
+ }
14
+ return best;
15
+ }
16
+ //#endregion
17
+ export { indexOfLeast };
@@ -1,17 +1,19 @@
1
+ import { BrowserAttitudeSensor } from "./composition/BrowserAttitudeSensor.js";
1
2
  import { browserPorts } from "./composition/browserPorts.js";
2
3
  import { buildPipeline } from "./composition/buildPipeline.js";
3
4
  import { Player } from "./player/Player.js";
4
5
  //#region ../../packages/player/src/browserPlayer.ts
5
6
  /**
6
- * A player composed for a browser: HTTP, blob and WebCodecs ports and the GPU pipeline, drawing
7
- * on the host's canvas and sounding through its audio element. What `<gyro-view>` plays with,
8
- * and where a page that wants the player without the element starts.
7
+ * A player composed for a browser: HTTP, blob and WebCodecs ports, the GPU pipeline and the
8
+ * device's attitude, drawing on the host's canvas and sounding through its audio element. What
9
+ * `<gyro-view>` plays with, and where a page that wants the player without the element starts.
9
10
  */
10
11
  function createBrowserPlayer(host, options) {
11
12
  return new Player({
12
13
  host,
13
14
  ports: browserPorts(options),
14
- pipelines: buildPipeline
15
+ pipelines: buildPipeline,
16
+ attitude: new BrowserAttitudeSensor()
15
17
  });
16
18
  }
17
19
  //#endregion